Merge branch 'main' into docs-check-onboarding
This commit is contained in:
commit
e77821dcc3
14 changed files with 332 additions and 348 deletions
|
|
@ -7,29 +7,29 @@ const DogIcon = ({ disabled = false, stroke, ...props }: DogIconProps) => {
|
|||
|
||||
// CSS for the stepped animation states
|
||||
const animationCSS = `
|
||||
.state1 { animation: showState1 600ms infinite; }
|
||||
.state2 { animation: showState2 600ms infinite; }
|
||||
.state3 { animation: showState3 600ms infinite; }
|
||||
.state4 { animation: showState4 600ms infinite; }
|
||||
.state1 { animation: showDogState1 600ms infinite; }
|
||||
.state2 { animation: showDogState2 600ms infinite; }
|
||||
.state3 { animation: showDogState3 600ms infinite; }
|
||||
.state4 { animation: showDogState4 600ms infinite; }
|
||||
|
||||
@keyframes showState1 {
|
||||
@keyframes showDogState1 {
|
||||
0%, 24.99% { opacity: 1; }
|
||||
25%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes showState2 {
|
||||
@keyframes showDogState2 {
|
||||
0%, 24.99% { opacity: 0; }
|
||||
25%, 49.99% { opacity: 1; }
|
||||
50%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes showState3 {
|
||||
@keyframes showDogState3 {
|
||||
0%, 49.99% { opacity: 0; }
|
||||
50%, 74.99% { opacity: 1; }
|
||||
75%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes showState4 {
|
||||
@keyframes showDogState4 {
|
||||
0%, 74.99% { opacity: 0; }
|
||||
75%, 100% { opacity: 1; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export function AssistantMessage({
|
|||
>
|
||||
<Message
|
||||
icon={
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
||||
<div className="w-8 h-8 flex items-center justify-center flex-shrink-0 select-none">
|
||||
<DogIcon
|
||||
className="h-6 w-6 transition-colors duration-300"
|
||||
disabled={isCompleted || isInactive}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ArrowRight, Check, Funnel, Loader2, Plus } from "lucide-react";
|
||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
||||
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
import type { FilterColor } from "@/components/filter-icon-popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -9,7 +9,6 @@ import {
|
|||
PopoverContent,
|
||||
} from "@/components/ui/popover";
|
||||
import type { KnowledgeFilterData } from "../types";
|
||||
import { useState } from "react";
|
||||
import { FilePreview } from "./file-preview";
|
||||
import { SelectedKnowledgeFilter } from "./selected-knowledge-filter";
|
||||
|
||||
|
|
@ -81,7 +80,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|||
fileInputRef.current?.click();
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
const handleFilePickerChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
if (files && files.length > 0) {
|
||||
|
|
@ -92,242 +91,247 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<form onSubmit={onSubmit} className="relative">
|
||||
{/* Outer container - flex-col to stack file preview above input */}
|
||||
<div className="flex flex-col w-full gap-2 rounded-xl border border-input focus-within:ring-1 focus-within:ring-ring p-2">
|
||||
{/* File Preview Section - Always above */}
|
||||
{uploadedFile && (
|
||||
<FilePreview
|
||||
uploadedFile={uploadedFile}
|
||||
onClear={() => {
|
||||
onFileSelected(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="w-full">
|
||||
<form onSubmit={onSubmit} className="relative">
|
||||
{/* Outer container - flex-col to stack file preview above input */}
|
||||
<div className="flex flex-col w-full gap-2 rounded-xl border border-input focus-within:ring-1 focus-within:ring-ring p-2">
|
||||
{/* File Preview Section - Always above */}
|
||||
{uploadedFile && (
|
||||
<FilePreview
|
||||
uploadedFile={uploadedFile}
|
||||
onClear={() => {
|
||||
onFileSelected(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Main Input Container - flex-row or flex-col based on textarea height */}
|
||||
<div className={`relative flex w-full gap-2 ${
|
||||
textareaHeight > 40 ? 'flex-col' : 'flex-row items-center'
|
||||
}`}>
|
||||
{/* Filter + Textarea Section */}
|
||||
<div className={`flex items-center gap-2 ${textareaHeight > 40 ? 'w-full' : 'flex-1'}`}>
|
||||
{textareaHeight <= 40 && (
|
||||
selectedFilter ? (
|
||||
<SelectedKnowledgeFilter
|
||||
selectedFilter={selectedFilter}
|
||||
parsedFilterData={parsedFilterData}
|
||||
onClear={() => {
|
||||
setSelectedFilter(null);
|
||||
setIsFilterHighlighted(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="iconSm"
|
||||
className="h-8 w-8 p-0 rounded-md hover:bg-muted/50"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={onAtClick}
|
||||
data-filter-button
|
||||
>
|
||||
<Funnel className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className="relative flex-1"
|
||||
style={{ height: `${textareaHeight}px` }}
|
||||
>
|
||||
<TextareaAutosize
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
onHeightChange={(height) => setTextareaHeight(height)}
|
||||
maxRows={7}
|
||||
minRows={1}
|
||||
placeholder="Ask a question..."
|
||||
disabled={loading}
|
||||
className={`w-full text-sm bg-transparent focus-visible:outline-none resize-none`}
|
||||
rows={1}
|
||||
{/* Main Input Container - flex-row or flex-col based on textarea height */}
|
||||
<div
|
||||
className={`relative flex w-full gap-2 ${
|
||||
textareaHeight > 40 ? "flex-col" : "flex-row items-center"
|
||||
}`}
|
||||
>
|
||||
{/* Filter + Textarea Section */}
|
||||
<div
|
||||
className={`flex items-center gap-2 ${textareaHeight > 40 ? "w-full" : "flex-1"}`}
|
||||
>
|
||||
{textareaHeight <= 40 &&
|
||||
(selectedFilter ? (
|
||||
<SelectedKnowledgeFilter
|
||||
selectedFilter={selectedFilter}
|
||||
parsedFilterData={parsedFilterData}
|
||||
onClear={() => {
|
||||
setSelectedFilter(null);
|
||||
setIsFilterHighlighted(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons Section */}
|
||||
<div className={`flex items-center gap-2 ${textareaHeight > 40 ? 'justify-between w-full' : ''}`}>
|
||||
{textareaHeight > 40 && (
|
||||
selectedFilter ? (
|
||||
<SelectedKnowledgeFilter
|
||||
selectedFilter={selectedFilter}
|
||||
parsedFilterData={parsedFilterData}
|
||||
onClear={() => {
|
||||
setSelectedFilter(null);
|
||||
setIsFilterHighlighted(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="iconSm"
|
||||
className="h-8 w-8 p-0 rounded-md hover:bg-muted/50"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={onAtClick}
|
||||
data-filter-button
|
||||
>
|
||||
<Funnel className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="iconSm"
|
||||
onClick={onFilePickerClick}
|
||||
disabled={isUploading}
|
||||
className="h-8 w-8 p-0 !rounded-md hover:bg-muted/50"
|
||||
className="h-8 w-8 p-0 rounded-md hover:bg-muted/50"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={onAtClick}
|
||||
data-filter-button
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
<Funnel className="h-4 w-4" />
|
||||
</Button>
|
||||
))}
|
||||
<div
|
||||
className="relative flex-1"
|
||||
style={{ height: `${textareaHeight}px` }}
|
||||
>
|
||||
<TextareaAutosize
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
onHeightChange={(height) => setTextareaHeight(height)}
|
||||
maxRows={7}
|
||||
autoComplete="off"
|
||||
minRows={1}
|
||||
placeholder="Ask a question..."
|
||||
disabled={loading}
|
||||
className={`w-full text-sm bg-transparent focus-visible:outline-none resize-none`}
|
||||
rows={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons Section */}
|
||||
<div
|
||||
className={`flex items-center gap-2 ${textareaHeight > 40 ? "justify-between w-full" : ""}`}
|
||||
>
|
||||
{textareaHeight > 40 &&
|
||||
(selectedFilter ? (
|
||||
<SelectedKnowledgeFilter
|
||||
selectedFilter={selectedFilter}
|
||||
parsedFilterData={parsedFilterData}
|
||||
onClear={() => {
|
||||
setSelectedFilter(null);
|
||||
setIsFilterHighlighted(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
variant="default"
|
||||
type="submit"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="iconSm"
|
||||
disabled={(!input.trim() && !uploadedFile) || loading}
|
||||
className="!rounded-md h-8 w-8 p-0"
|
||||
className="h-8 w-8 p-0 rounded-md hover:bg-muted/50"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={onAtClick}
|
||||
data-filter-button
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
)}
|
||||
<Funnel className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="iconSm"
|
||||
onClick={onFilePickerClick}
|
||||
disabled={isUploading}
|
||||
className="h-8 w-8 p-0 !rounded-md hover:bg-muted/50"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
type="submit"
|
||||
size="iconSm"
|
||||
disabled={(!input.trim() && !uploadedFile) || loading}
|
||||
className="!rounded-md h-8 w-8 p-0"
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
onChange={handleFilePickerChange}
|
||||
className="hidden"
|
||||
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
onChange={handleFilePickerChange}
|
||||
className="hidden"
|
||||
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
|
||||
/>
|
||||
|
||||
<Popover
|
||||
open={isFilterDropdownOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsFilterDropdownOpen(open);
|
||||
}}
|
||||
>
|
||||
{anchorPosition && (
|
||||
<PopoverAnchor
|
||||
asChild
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: anchorPosition.x,
|
||||
top: anchorPosition.y,
|
||||
width: 1,
|
||||
height: 1,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<div />
|
||||
</PopoverAnchor>
|
||||
)}
|
||||
<PopoverContent
|
||||
className="w-64 p-2"
|
||||
side="top"
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
alignOffset={-18}
|
||||
onOpenAutoFocus={(e) => {
|
||||
// Prevent auto focus on the popover content
|
||||
e.preventDefault();
|
||||
// Keep focus on the input
|
||||
<Popover
|
||||
open={isFilterDropdownOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsFilterDropdownOpen(open);
|
||||
}}
|
||||
>
|
||||
{anchorPosition && (
|
||||
<PopoverAnchor
|
||||
asChild
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: anchorPosition.x,
|
||||
top: anchorPosition.y,
|
||||
width: 1,
|
||||
height: 1,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{filterSearchTerm && (
|
||||
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
||||
Searching: @{filterSearchTerm}
|
||||
</div>
|
||||
)}
|
||||
{availableFilters.length === 0 ? (
|
||||
<div className="px-2 py-3 text-sm text-muted-foreground">
|
||||
No knowledge filters available
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!filterSearchTerm && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onFilterSelect(null)}
|
||||
className={`w-full text-left px-2 py-2 text-sm rounded hover:bg-muted/50 flex items-center justify-between ${
|
||||
selectedFilterIndex === -1 ? "bg-muted/50" : ""
|
||||
}`}
|
||||
>
|
||||
<span>No knowledge filter</span>
|
||||
{!selectedFilter && (
|
||||
<Check className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{availableFilters
|
||||
.filter((filter) =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase()),
|
||||
)
|
||||
.map((filter, index) => (
|
||||
<button
|
||||
key={filter.id}
|
||||
type="button"
|
||||
onClick={() => onFilterSelect(filter)}
|
||||
className={`w-full overflow-hidden text-left px-2 py-2 gap-2 text-sm rounded hover:bg-muted/50 flex items-center justify-between ${
|
||||
index === selectedFilterIndex ? "bg-muted/50" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className="font-medium truncate">
|
||||
{filter.name}
|
||||
</div>
|
||||
{filter.description && (
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{filter.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selectedFilter?.id === filter.id && (
|
||||
<Check className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{availableFilters.filter((filter) =>
|
||||
<div />
|
||||
</PopoverAnchor>
|
||||
)}
|
||||
<PopoverContent
|
||||
className="w-64 p-2"
|
||||
side="top"
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
alignOffset={-18}
|
||||
onOpenAutoFocus={(e) => {
|
||||
// Prevent auto focus on the popover content
|
||||
e.preventDefault();
|
||||
// Keep focus on the input
|
||||
}}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{filterSearchTerm && (
|
||||
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
||||
Searching: @{filterSearchTerm}
|
||||
</div>
|
||||
)}
|
||||
{availableFilters.length === 0 ? (
|
||||
<div className="px-2 py-3 text-sm text-muted-foreground">
|
||||
No knowledge filters available
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!filterSearchTerm && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onFilterSelect(null)}
|
||||
className={`w-full text-left px-2 py-2 text-sm rounded hover:bg-muted/50 flex items-center justify-between ${
|
||||
selectedFilterIndex === -1 ? "bg-muted/50" : ""
|
||||
}`}
|
||||
>
|
||||
<span>No knowledge filter</span>
|
||||
{!selectedFilter && (
|
||||
<Check className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{availableFilters
|
||||
.filter((filter) =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase()),
|
||||
).length === 0 &&
|
||||
filterSearchTerm && (
|
||||
<div className="px-2 py-3 text-sm text-muted-foreground">
|
||||
No filters match "{filterSearchTerm}"
|
||||
)
|
||||
.map((filter, index) => (
|
||||
<button
|
||||
key={filter.id}
|
||||
type="button"
|
||||
onClick={() => onFilterSelect(filter)}
|
||||
className={`w-full overflow-hidden text-left px-2 py-2 gap-2 text-sm rounded hover:bg-muted/50 flex items-center justify-between ${
|
||||
index === selectedFilterIndex ? "bg-muted/50" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className="font-medium truncate">
|
||||
{filter.name}
|
||||
</div>
|
||||
{filter.description && (
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{filter.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</form>
|
||||
</div>
|
||||
{selectedFilter?.id === filter.id && (
|
||||
<Check className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{availableFilters.filter((filter) =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase()),
|
||||
).length === 0 &&
|
||||
filterSearchTerm && (
|
||||
<div className="px-2 py-3 text-sm text-muted-foreground">
|
||||
No filters match "{filterSearchTerm}"
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { Suspense, useState } from "react";
|
||||
import { DoclingHealthBanner } from "@/components/docling-health-banner";
|
||||
import { ProtectedRoute } from "@/components/protected-route";
|
||||
import { OnboardingContent } from "./components/onboarding-content";
|
||||
import { ProgressBar } from "./components/progress-bar";
|
||||
|
||||
const TOTAL_STEPS = 4;
|
||||
|
||||
function NewOnboardingPage() {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
|
||||
const handleStepComplete = () => {
|
||||
if (currentStep < TOTAL_STEPS - 1) {
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-primary-foreground relative p-4">
|
||||
<DoclingHealthBanner className="absolute top-0 left-0 right-0 w-full z-20" />
|
||||
|
||||
{/* Chat-like content area */}
|
||||
<div className="flex flex-col items-center gap-5 w-full max-w-3xl z-10">
|
||||
<div className="w-full h-[872px] bg-background border rounded-lg p-4 shadow-sm overflow-y-auto">
|
||||
<OnboardingContent handleStepComplete={handleStepComplete} currentStep={currentStep} />
|
||||
</div>
|
||||
|
||||
<ProgressBar currentStep={currentStep} totalSteps={TOTAL_STEPS} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProtectedNewOnboardingPage() {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<NewOnboardingPage />
|
||||
</Suspense>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
|
@ -37,7 +37,6 @@ export function AdvancedOnboarding({
|
|||
languageModel !== undefined &&
|
||||
setLanguageModel !== undefined;
|
||||
|
||||
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
||||
return (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="item-1">
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export function AnimatedProviderSteps({
|
|||
<div
|
||||
className={cn(
|
||||
"transition-all duration-150 relative",
|
||||
isDone ? "w-3.5 h-3.5" : "w-3.5 h-2.5",
|
||||
isDone ? "w-3.5 h-3.5" : "w-6 h-6",
|
||||
)}
|
||||
>
|
||||
<CheckIcon
|
||||
|
|
@ -110,7 +110,7 @@ export function AnimatedProviderSteps({
|
|||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
className="flex items-center gap-4 overflow-y-hidden relative h-6"
|
||||
>
|
||||
<div className="w-px h-6 bg-border ml-2" />
|
||||
<div className="w-px h-6 bg-border ml-3" />
|
||||
<div className="relative h-5 w-full">
|
||||
<AnimatePresence mode="sync" initial={false}>
|
||||
<motion.span
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { AnimatedProviderSteps } from "./animated-provider-steps";
|
|||
import { IBMOnboarding } from "./ibm-onboarding";
|
||||
import { OllamaOnboarding } from "./ollama-onboarding";
|
||||
import { OpenAIOnboarding } from "./openai-onboarding";
|
||||
import { TabTrigger } from "./tab-trigger";
|
||||
|
||||
interface OnboardingCardProps {
|
||||
onComplete: () => void;
|
||||
|
|
@ -191,84 +192,77 @@ const OnboardingCard = ({
|
|||
>
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="openai">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||
modelProvider === "openai" ? "bg-white" : "bg-muted",
|
||||
)}
|
||||
<TabTrigger
|
||||
selected={modelProvider === "openai"}
|
||||
isLoading={isLoadingModels}
|
||||
>
|
||||
<OpenAILogo
|
||||
<div
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0",
|
||||
modelProvider === "openai"
|
||||
? "text-black"
|
||||
: "text-muted-foreground",
|
||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||
modelProvider === "openai" ? "bg-white" : "bg-muted",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
OpenAI
|
||||
>
|
||||
<OpenAILogo
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0",
|
||||
modelProvider === "openai"
|
||||
? "text-black"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
OpenAI
|
||||
</TabTrigger>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="watsonx">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||
modelProvider === "watsonx" ? "bg-[#1063FE]" : "bg-muted",
|
||||
)}
|
||||
<TabTrigger
|
||||
selected={modelProvider === "watsonx"}
|
||||
isLoading={isLoadingModels}
|
||||
>
|
||||
<IBMLogo
|
||||
<div
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0",
|
||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||
modelProvider === "watsonx"
|
||||
? "text-white"
|
||||
: "text-muted-foreground",
|
||||
? "bg-[#1063FE]"
|
||||
: "bg-muted",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
IBM watsonx.ai
|
||||
>
|
||||
<IBMLogo
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0",
|
||||
modelProvider === "watsonx"
|
||||
? "text-white"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
IBM watsonx.ai
|
||||
</TabTrigger>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ollama">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||
modelProvider === "ollama" ? "bg-white" : "bg-muted",
|
||||
)}
|
||||
<TabTrigger
|
||||
selected={modelProvider === "ollama"}
|
||||
isLoading={isLoadingModels}
|
||||
>
|
||||
<OllamaLogo
|
||||
<div
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0",
|
||||
modelProvider === "ollama"
|
||||
? "text-black"
|
||||
: "text-muted-foreground",
|
||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||
modelProvider === "ollama" ? "bg-white" : "bg-muted",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
Ollama
|
||||
>
|
||||
<OllamaLogo
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0",
|
||||
modelProvider === "ollama"
|
||||
? "text-black"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
Ollama
|
||||
</TabTrigger>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<AnimatePresence>
|
||||
{isLoadingModels && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.1, ease: "easeInOut" }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="py-3">
|
||||
<AnimatedProviderSteps
|
||||
currentStep={loadingStep}
|
||||
isCompleted={false}
|
||||
setCurrentStep={setLoadingStep}
|
||||
steps={[
|
||||
"Connecting to the provider",
|
||||
"Fetching language models",
|
||||
"Fetching embedding models",
|
||||
]}
|
||||
storageKey="model-loading-steps"
|
||||
/></div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<TabsContent value="openai">
|
||||
<OpenAIOnboarding
|
||||
setSettings={setSettings}
|
||||
|
|
@ -295,8 +289,6 @@ const OnboardingCard = ({
|
|||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export function OnboardingStep({
|
|||
<div className="w-8 h-8 rounded-lg flex-shrink-0" />
|
||||
) : (
|
||||
icon || (
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
||||
<div className="w-8 h-8 flex items-center justify-center flex-shrink-0 select-none">
|
||||
<DogIcon
|
||||
className="h-6 w-6 text-accent-foreground transition-colors duration-300"
|
||||
disabled={isCompleted}
|
||||
|
|
@ -76,7 +76,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
|||
onClick={handleUploadClick}
|
||||
disabled={isUploading}
|
||||
>
|
||||
{isUploading ? "Uploading..." : "Add a Document"}
|
||||
<div>{isUploading ? "Uploading..." : "Add a document"}</div>
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
|
|
@ -30,12 +30,12 @@ export function ProgressBar({ currentStep, totalSteps, onSkip }: ProgressBarProp
|
|||
<div className="flex-1 flex justify-end">
|
||||
{currentStep > 0 && onSkip && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
variant="link"
|
||||
size="sm"
|
||||
onClick={onSkip}
|
||||
className="flex items-center gap-2 text-xs text-muted-foreground"
|
||||
className="flex items-center gap-2 text-mmd !text-placeholder-foreground hover:!text-foreground hover:!no-underline"
|
||||
>
|
||||
Skip onboarding
|
||||
Skip overview
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
33
frontend/src/app/onboarding/components/tab-trigger.tsx
Normal file
33
frontend/src/app/onboarding/components/tab-trigger.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import AnimatedProcessingIcon from "@/components/ui/animated-processing-icon";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function TabTrigger({
|
||||
children,
|
||||
selected,
|
||||
isLoading,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
selected: boolean;
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col relative items-start justify-between gap-4 h-full w-full">
|
||||
<div
|
||||
className={cn(
|
||||
"flex absolute items-center justify-center h-full w-full transition-opacity duration-200",
|
||||
isLoading && selected ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
<AnimatedProcessingIcon className="text-current shrink-0 h-10 w-10" />
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-start justify-between gap-4 h-full w-full transition-opacity duration-200",
|
||||
isLoading && selected ? "opacity-0" : "opacity-100",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import {
|
|||
useGetConversationsQuery,
|
||||
} from "@/app/api/queries/useGetConversationsQuery";
|
||||
import type { Settings } from "@/app/api/queries/useGetSettingsQuery";
|
||||
import { OnboardingContent } from "@/app/new-onboarding/components/onboarding-content";
|
||||
import { ProgressBar } from "@/app/new-onboarding/components/progress-bar";
|
||||
import { OnboardingContent } from "@/app/onboarding/components/onboarding-content";
|
||||
import { ProgressBar } from "@/app/onboarding/components/progress-bar";
|
||||
import { AnimatedConditional } from "@/components/animated-conditional";
|
||||
import { Header } from "@/components/header";
|
||||
import { Navigation } from "@/components/navigation";
|
||||
|
|
@ -156,7 +156,7 @@ export function ChatRenderer({
|
|||
}}
|
||||
className={cn(
|
||||
"flex h-full w-full max-w-full max-h-full items-center justify-center overflow-hidden",
|
||||
!showLayout && "absolute",
|
||||
!showLayout && "absolute max-h-[calc(100vh-190px)]",
|
||||
showLayout && !isOnChatPage && "bg-background",
|
||||
)}
|
||||
>
|
||||
|
|
@ -199,7 +199,7 @@ export function ChatRenderer({
|
|||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: showLayout ? 0 : 1, y: showLayout ? 20 : 0 }}
|
||||
transition={{ duration: ANIMATION_DURATION, ease: "easeOut" }}
|
||||
className={cn("absolute bottom-10 left-0 right-0")}
|
||||
className={cn("absolute bottom-6 left-0 right-0")}
|
||||
>
|
||||
<ProgressBar
|
||||
currentStep={currentStep}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ const AnimatedProcessingIcon = ({
|
|||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue