diff --git a/frontend/app/onboarding/_components/animated-provider-steps.tsx b/frontend/app/onboarding/_components/animated-provider-steps.tsx index 79188ba0..4a1b0e47 100644 --- a/frontend/app/onboarding/_components/animated-provider-steps.tsx +++ b/frontend/app/onboarding/_components/animated-provider-steps.tsx @@ -5,210 +5,211 @@ import { CheckIcon, XIcon } from "lucide-react"; import { useEffect, useState } from "react"; import AnimatedProcessingIcon from "@/components/icons/animated-processing-icon"; import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, } from "@/components/ui/accordion"; +import { ONBOARDING_CARD_STEPS_KEY } from "@/lib/constants"; import { cn } from "@/lib/utils"; export function AnimatedProviderSteps({ - currentStep, - isCompleted, - setCurrentStep, - steps, - storageKey = "provider-steps", - processingStartTime, - hasError = false, + currentStep, + isCompleted, + setCurrentStep, + steps, + storageKey = ONBOARDING_CARD_STEPS_KEY, + processingStartTime, + hasError = false, }: { - currentStep: number; - isCompleted: boolean; - setCurrentStep: (step: number) => void; - steps: string[]; - storageKey?: string; - processingStartTime?: number | null; - hasError?: boolean; + currentStep: number; + isCompleted: boolean; + setCurrentStep: (step: number) => void; + steps: string[]; + storageKey?: string; + processingStartTime?: number | null; + hasError?: boolean; }) { - const [startTime, setStartTime] = useState(null); - const [elapsedTime, setElapsedTime] = useState(0); + const [startTime, setStartTime] = useState(null); + const [elapsedTime, setElapsedTime] = useState(0); - // Initialize start time from prop or local storage - useEffect(() => { - const storedElapsedTime = localStorage.getItem(`${storageKey}-elapsed`); + // Initialize start time from prop or local storage + useEffect(() => { + const storedElapsedTime = localStorage.getItem(storageKey); - if (isCompleted && storedElapsedTime) { - // If completed, use stored elapsed time - setElapsedTime(parseFloat(storedElapsedTime)); - } else if (processingStartTime) { - // Use the start time passed from parent (when user clicked Complete) - setStartTime(processingStartTime); - } - }, [storageKey, isCompleted, processingStartTime]); + if (isCompleted && storedElapsedTime) { + // If completed, use stored elapsed time + setElapsedTime(parseFloat(storedElapsedTime)); + } else if (processingStartTime) { + // Use the start time passed from parent (when user clicked Complete) + setStartTime(processingStartTime); + } + }, [storageKey, isCompleted, processingStartTime]); - // Progress through steps - useEffect(() => { - if (currentStep < steps.length - 1 && !isCompleted) { - const interval = setInterval(() => { - setCurrentStep(currentStep + 1); - }, 1500); - return () => clearInterval(interval); - } - }, [currentStep, setCurrentStep, steps, isCompleted]); + // Progress through steps + useEffect(() => { + if (currentStep < steps.length - 1 && !isCompleted) { + const interval = setInterval(() => { + setCurrentStep(currentStep + 1); + }, 1500); + return () => clearInterval(interval); + } + }, [currentStep, setCurrentStep, steps, isCompleted]); - // Calculate and store elapsed time when completed - useEffect(() => { - if (isCompleted && startTime) { - const elapsed = Date.now() - startTime; - setElapsedTime(elapsed); - localStorage.setItem(`${storageKey}-elapsed`, elapsed.toString()); - } - }, [isCompleted, startTime, storageKey]); + // Calculate and store elapsed time when completed + useEffect(() => { + if (isCompleted && startTime) { + const elapsed = Date.now() - startTime; + setElapsedTime(elapsed); + localStorage.setItem(storageKey, elapsed.toString()); + } + }, [isCompleted, startTime, storageKey]); - const isDone = currentStep >= steps.length && !isCompleted && !hasError; + const isDone = currentStep >= steps.length && !isCompleted && !hasError; - return ( - - {!isCompleted ? ( - -
-
- - - -
+ return ( + + {!isCompleted ? ( + +
+
+ + + +
- - {hasError ? "Error" : isDone ? "Done" : "Thinking"} - -
-
- - {!isDone && !hasError && ( - -
-
- - - {steps[currentStep]} - - -
- - )} - -
-
- ) : ( - - - - -
- - {`Initialized in ${(elapsedTime / 1000).toFixed(1)} seconds`} - -
-
- -
- {/* Connecting line on the left */} - + + {hasError ? "Error" : isDone ? "Done" : "Thinking"} + +
+
+ + {!isDone && !hasError && ( + +
+
+ + + {steps[currentStep]} + + +
+ + )} + +
+
+ ) : ( + + + + +
+ + {`Initialized in ${(elapsedTime / 1000).toFixed(1)} seconds`} + +
+
+ +
+ {/* Connecting line on the left */} + -
- - {steps.map((step, index) => ( - - - - - - - - {step} - - - ))} - -
-
-
-
-
-
- )} -
- ); +
+ + {steps.map((step, index) => ( + + + + + + + + {step} + + + ))} + +
+
+
+
+
+
+ )} +
+ ); } diff --git a/frontend/app/onboarding/_components/onboarding-card.tsx b/frontend/app/onboarding/_components/onboarding-card.tsx index d79259cf..34f3680c 100644 --- a/frontend/app/onboarding/_components/onboarding-card.tsx +++ b/frontend/app/onboarding/_components/onboarding-card.tsx @@ -6,8 +6,8 @@ import { Info, X } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { - type OnboardingVariables, - useOnboardingMutation, + type OnboardingVariables, + useOnboardingMutation, } from "@/app/api/mutations/useOnboardingMutation"; import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery"; import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery"; @@ -20,10 +20,11 @@ import OpenAILogo from "@/components/icons/openai-logo"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { - Tooltip, - TooltipContent, - TooltipTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, } from "@/components/ui/tooltip"; +import { ONBOARDING_CARD_STEPS_KEY } from "@/lib/constants"; import { cn } from "@/lib/utils"; import { AnimatedProviderSteps } from "./animated-provider-steps"; import { AnthropicOnboarding } from "./anthropic-onboarding"; @@ -33,506 +34,507 @@ import { OpenAIOnboarding } from "./openai-onboarding"; import { TabTrigger } from "./tab-trigger"; interface OnboardingCardProps { - onComplete: () => void; - isCompleted?: boolean; - isEmbedding?: boolean; - setIsLoadingModels?: (isLoading: boolean) => void; - setLoadingStatus?: (status: string[]) => void; + onComplete: () => void; + isCompleted?: boolean; + isEmbedding?: boolean; + setIsLoadingModels?: (isLoading: boolean) => void; + setLoadingStatus?: (status: string[]) => void; } const STEP_LIST = [ - "Setting up your model provider", - "Defining schema", - "Configuring Langflow", + "Setting up your model provider", + "Defining schema", + "Configuring Langflow", ]; const EMBEDDING_STEP_LIST = [ - "Setting up your model provider", - "Defining schema", - "Configuring Langflow", - "Ingesting sample data", + "Setting up your model provider", + "Defining schema", + "Configuring Langflow", + "Ingesting sample data", ]; const OnboardingCard = ({ - onComplete, - isEmbedding = false, - isCompleted = false, + onComplete, + isEmbedding = false, + isCompleted = false, }: OnboardingCardProps) => { - const { isHealthy: isDoclingHealthy } = useDoclingHealth(); + const { isHealthy: isDoclingHealthy } = useDoclingHealth(); - const [modelProvider, setModelProvider] = useState( - isEmbedding ? "openai" : "anthropic", - ); + const [modelProvider, setModelProvider] = useState( + isEmbedding ? "openai" : "anthropic", + ); - const [sampleDataset, setSampleDataset] = useState(true); + const [sampleDataset, setSampleDataset] = useState(true); - const [isLoadingModels, setIsLoadingModels] = useState(false); + const [isLoadingModels, setIsLoadingModels] = useState(false); - const queryClient = useQueryClient(); + const queryClient = useQueryClient(); - // Fetch current settings to check if providers are already configured - const { data: currentSettings } = useGetSettingsQuery(); + // Fetch current settings to check if providers are already configured + const { data: currentSettings } = useGetSettingsQuery(); - const handleSetModelProvider = (provider: string) => { - setIsLoadingModels(false); - setModelProvider(provider); - setSettings({ - [isEmbedding ? "embedding_provider" : "llm_provider"]: provider, - embedding_model: "", - llm_model: "", - }); - setError(null); - }; + const handleSetModelProvider = (provider: string) => { + setIsLoadingModels(false); + setModelProvider(provider); + setSettings({ + [isEmbedding ? "embedding_provider" : "llm_provider"]: provider, + embedding_model: "", + llm_model: "", + }); + setError(null); + }; - // Check if the selected provider is already configured - const isProviderAlreadyConfigured = (provider: string): boolean => { - if (!isEmbedding || !currentSettings?.providers) return false; + // Check if the selected provider is already configured + const isProviderAlreadyConfigured = (provider: string): boolean => { + if (!isEmbedding || !currentSettings?.providers) return false; - // Check if provider has been explicitly configured (not just from env vars) - if (provider === "openai") { - return currentSettings.providers.openai?.configured === true; - } else if (provider === "anthropic") { - return currentSettings.providers.anthropic?.configured === true; - } else if (provider === "watsonx") { - return currentSettings.providers.watsonx?.configured === true; - } else if (provider === "ollama") { - return currentSettings.providers.ollama?.configured === true; - } - return false; - }; + // Check if provider has been explicitly configured (not just from env vars) + if (provider === "openai") { + return currentSettings.providers.openai?.configured === true; + } else if (provider === "anthropic") { + return currentSettings.providers.anthropic?.configured === true; + } else if (provider === "watsonx") { + return currentSettings.providers.watsonx?.configured === true; + } else if (provider === "ollama") { + return currentSettings.providers.ollama?.configured === true; + } + return false; + }; - const showProviderConfiguredMessage = - isProviderAlreadyConfigured(modelProvider); - const providerAlreadyConfigured = - isEmbedding && showProviderConfiguredMessage; + const showProviderConfiguredMessage = + isProviderAlreadyConfigured(modelProvider); + const providerAlreadyConfigured = + isEmbedding && showProviderConfiguredMessage; - const totalSteps = isEmbedding - ? EMBEDDING_STEP_LIST.length - : STEP_LIST.length; + const totalSteps = isEmbedding + ? EMBEDDING_STEP_LIST.length + : STEP_LIST.length; - const [settings, setSettings] = useState({ - [isEmbedding ? "embedding_provider" : "llm_provider"]: modelProvider, - embedding_model: "", - llm_model: "", - // Provider-specific fields will be set by provider components - openai_api_key: "", - anthropic_api_key: "", - watsonx_api_key: "", - watsonx_endpoint: "", - watsonx_project_id: "", - ollama_endpoint: "", - }); + const [settings, setSettings] = useState({ + [isEmbedding ? "embedding_provider" : "llm_provider"]: modelProvider, + embedding_model: "", + llm_model: "", + // Provider-specific fields will be set by provider components + openai_api_key: "", + anthropic_api_key: "", + watsonx_api_key: "", + watsonx_endpoint: "", + watsonx_project_id: "", + ollama_endpoint: "", + }); - const [currentStep, setCurrentStep] = useState( - isCompleted ? totalSteps : null, - ); + const [currentStep, setCurrentStep] = useState( + isCompleted ? totalSteps : null, + ); - const [processingStartTime, setProcessingStartTime] = useState( - null, - ); + const [processingStartTime, setProcessingStartTime] = useState( + null, + ); - const [error, setError] = useState(null); + const [error, setError] = useState(null); - // Query tasks to track completion - const { data: tasks } = useGetTasksQuery({ - enabled: currentStep !== null, // Only poll when onboarding has started - refetchInterval: currentStep !== null ? 1000 : false, // Poll every 1 second during onboarding - }); + // Query tasks to track completion + const { data: tasks } = useGetTasksQuery({ + enabled: currentStep !== null, // Only poll when onboarding has started + refetchInterval: currentStep !== null ? 1000 : false, // Poll every 1 second during onboarding + }); - // Monitor tasks and call onComplete when all tasks are done - useEffect(() => { - if (currentStep === null || !tasks || !isEmbedding) { - return; - } + // Monitor tasks and call onComplete when all tasks are done + useEffect(() => { + if (currentStep === null || !tasks || !isEmbedding) { + return; + } - // Check if there are any active tasks (pending, running, or processing) - const activeTasks = tasks.find( - (task) => - task.status === "pending" || - task.status === "running" || - task.status === "processing", - ); + // Check if there are any active tasks (pending, running, or processing) + const activeTasks = tasks.find( + (task) => + task.status === "pending" || + task.status === "running" || + task.status === "processing", + ); - // If no active tasks and we've started onboarding, complete it - if ( - (!activeTasks || (activeTasks.processed_files ?? 0) > 0) && - tasks.length > 0 && - !isCompleted - ) { - // Set to final step to show "Done" - setCurrentStep(totalSteps); - // Wait a bit before completing - setTimeout(() => { - onComplete(); - }, 1000); - } - }, [tasks, currentStep, onComplete, isCompleted, isEmbedding, totalSteps]); + // If no active tasks and we've started onboarding, complete it + if ( + (!activeTasks || (activeTasks.processed_files ?? 0) > 0) && + tasks.length > 0 && + !isCompleted + ) { + // Set to final step to show "Done" + setCurrentStep(totalSteps); + // Wait a bit before completing + setTimeout(() => { + onComplete(); + }, 1000); + } + }, [tasks, currentStep, onComplete, isCompleted, isEmbedding, totalSteps]); - // Mutations - const onboardingMutation = useOnboardingMutation({ - onSuccess: (data) => { - console.log("Onboarding completed successfully", data); - // Update provider health cache to healthy since backend just validated - const provider = - (isEmbedding ? settings.embedding_provider : settings.llm_provider) || - modelProvider; - const healthData: ProviderHealthResponse = { - status: "healthy", - message: "Provider is configured and working correctly", - provider: provider, - }; - queryClient.setQueryData(["provider", "health"], healthData); - setError(null); - if (!isEmbedding) { - setCurrentStep(totalSteps); - setTimeout(() => { - onComplete(); - }, 1000); - } else { - setCurrentStep(0); - } - }, - onError: (error) => { - setError(error.message); - setCurrentStep(totalSteps); - // Reset to provider selection after 1 second - setTimeout(() => { - setCurrentStep(null); - }, 1000); - }, - }); + // Mutations + const onboardingMutation = useOnboardingMutation({ + onSuccess: (data) => { + console.log("Onboarding completed successfully", data); + // Update provider health cache to healthy since backend just validated + const provider = + (isEmbedding ? settings.embedding_provider : settings.llm_provider) || + modelProvider; + const healthData: ProviderHealthResponse = { + status: "healthy", + message: "Provider is configured and working correctly", + provider: provider, + }; + queryClient.setQueryData(["provider", "health"], healthData); + setError(null); + if (!isEmbedding) { + setCurrentStep(totalSteps); + setTimeout(() => { + onComplete(); + }, 1000); + } else { + setCurrentStep(0); + } + }, + onError: (error) => { + setError(error.message); + setCurrentStep(totalSteps); + // Reset to provider selection after 1 second + setTimeout(() => { + setCurrentStep(null); + }, 1000); + }, + }); - const handleComplete = () => { - const currentProvider = isEmbedding - ? settings.embedding_provider - : settings.llm_provider; + const handleComplete = () => { + const currentProvider = isEmbedding + ? settings.embedding_provider + : settings.llm_provider; - if ( - !currentProvider || - (isEmbedding && - !settings.embedding_model && - !showProviderConfiguredMessage) || - (!isEmbedding && !settings.llm_model) - ) { - toast.error("Please complete all required fields"); - return; - } + if ( + !currentProvider || + (isEmbedding && + !settings.embedding_model && + !showProviderConfiguredMessage) || + (!isEmbedding && !settings.llm_model) + ) { + toast.error("Please complete all required fields"); + return; + } - // Clear any previous error - setError(null); + // Clear any previous error + setError(null); - // Prepare onboarding data with provider-specific fields - const onboardingData: OnboardingVariables = { - sample_data: sampleDataset, - }; + // Prepare onboarding data with provider-specific fields + const onboardingData: OnboardingVariables = { + sample_data: sampleDataset, + }; - // Set the provider field - if (isEmbedding) { - onboardingData.embedding_provider = currentProvider; - // If provider is already configured, use the existing embedding model from settings - // Otherwise, use the embedding model from the form - if ( - showProviderConfiguredMessage && - currentSettings?.knowledge?.embedding_model - ) { - onboardingData.embedding_model = - currentSettings.knowledge.embedding_model; - } else { - onboardingData.embedding_model = settings.embedding_model; - } - } else { - onboardingData.llm_provider = currentProvider; - onboardingData.llm_model = settings.llm_model; - } + // Set the provider field + if (isEmbedding) { + onboardingData.embedding_provider = currentProvider; + // If provider is already configured, use the existing embedding model from settings + // Otherwise, use the embedding model from the form + if ( + showProviderConfiguredMessage && + currentSettings?.knowledge?.embedding_model + ) { + onboardingData.embedding_model = + currentSettings.knowledge.embedding_model; + } else { + onboardingData.embedding_model = settings.embedding_model; + } + } else { + onboardingData.llm_provider = currentProvider; + onboardingData.llm_model = settings.llm_model; + } - // Add provider-specific credentials based on the selected provider - if (currentProvider === "openai" && settings.openai_api_key) { - onboardingData.openai_api_key = settings.openai_api_key; - } else if (currentProvider === "anthropic" && settings.anthropic_api_key) { - onboardingData.anthropic_api_key = settings.anthropic_api_key; - } else if (currentProvider === "watsonx") { - if (settings.watsonx_api_key) { - onboardingData.watsonx_api_key = settings.watsonx_api_key; - } - if (settings.watsonx_endpoint) { - onboardingData.watsonx_endpoint = settings.watsonx_endpoint; - } - if (settings.watsonx_project_id) { - onboardingData.watsonx_project_id = settings.watsonx_project_id; - } - } else if (currentProvider === "ollama" && settings.ollama_endpoint) { - onboardingData.ollama_endpoint = settings.ollama_endpoint; - } + // Add provider-specific credentials based on the selected provider + if (currentProvider === "openai" && settings.openai_api_key) { + onboardingData.openai_api_key = settings.openai_api_key; + } else if (currentProvider === "anthropic" && settings.anthropic_api_key) { + onboardingData.anthropic_api_key = settings.anthropic_api_key; + } else if (currentProvider === "watsonx") { + if (settings.watsonx_api_key) { + onboardingData.watsonx_api_key = settings.watsonx_api_key; + } + if (settings.watsonx_endpoint) { + onboardingData.watsonx_endpoint = settings.watsonx_endpoint; + } + if (settings.watsonx_project_id) { + onboardingData.watsonx_project_id = settings.watsonx_project_id; + } + } else if (currentProvider === "ollama" && settings.ollama_endpoint) { + onboardingData.ollama_endpoint = settings.ollama_endpoint; + } - // Record the start time when user clicks Complete - setProcessingStartTime(Date.now()); - onboardingMutation.mutate(onboardingData); - setCurrentStep(0); - }; + // Record the start time when user clicks Complete + setProcessingStartTime(Date.now()); + onboardingMutation.mutate(onboardingData); + setCurrentStep(0); + }; - const isComplete = - (isEmbedding && - (!!settings.embedding_model || showProviderConfiguredMessage)) || - (!isEmbedding && !!settings.llm_model && isDoclingHealthy); + const isComplete = + (isEmbedding && + (!!settings.embedding_model || showProviderConfiguredMessage)) || + (!isEmbedding && !!settings.llm_model && isDoclingHealthy); - return ( - - {currentStep === null ? ( - -
- - {error && ( - -
- - - {error} - -
-
- )} -
-
- - - {!isEmbedding && ( - - -
- -
- Anthropic -
-
- )} - - -
- -
- OpenAI -
-
- - -
- -
- IBM watsonx.ai -
-
- - -
- -
- Ollama -
-
-
- {!isEmbedding && ( - - - - )} - - - - - - - - - -
+ return ( + + {currentStep === null ? ( + +
+ + {error && ( + +
+ + + {error} + +
+
+ )} +
+
+ + + {!isEmbedding && ( + + +
+ +
+ Anthropic +
+
+ )} + + +
+ +
+ OpenAI +
+
+ + +
+ +
+ IBM watsonx.ai +
+
+ + +
+ +
+ Ollama +
+
+
+ {!isEmbedding && ( + + + + )} + + + + + + + + + +
- - -
- -
-
- {!isComplete && ( - - {isLoadingModels - ? "Loading models..." - : !!settings.llm_model && - !!settings.embedding_model && - !isDoclingHealthy - ? "docling-serve must be running to continue" - : "Please fill in all required fields"} - - )} -
-
-
-
- ) : ( - - - - )} -
- ); + + +
+ +
+
+ {!isComplete && ( + + {isLoadingModels + ? "Loading models..." + : !!settings.llm_model && + !!settings.embedding_model && + !isDoclingHealthy + ? "docling-serve must be running to continue" + : "Please fill in all required fields"} + + )} +
+
+
+
+ ) : ( + + + + )} +
+ ); }; export default OnboardingCard; diff --git a/frontend/app/onboarding/_components/onboarding-upload.tsx b/frontend/app/onboarding/_components/onboarding-upload.tsx index 70d487bd..60fb676e 100644 --- a/frontend/app/onboarding/_components/onboarding-upload.tsx +++ b/frontend/app/onboarding/_components/onboarding-upload.tsx @@ -4,154 +4,156 @@ import { useGetNudgesQuery } from "@/app/api/queries/useGetNudgesQuery"; import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery"; import { AnimatedProviderSteps } from "@/app/onboarding/_components/animated-provider-steps"; import { Button } from "@/components/ui/button"; +import { ONBOARDING_UPLOAD_STEPS_KEY } from "@/lib/constants"; import { uploadFile } from "@/lib/upload-utils"; interface OnboardingUploadProps { - onComplete: () => void; + onComplete: () => void; } const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => { - const fileInputRef = useRef(null); - const [isUploading, setIsUploading] = useState(false); - const [currentStep, setCurrentStep] = useState(null); + const fileInputRef = useRef(null); + const [isUploading, setIsUploading] = useState(false); + const [currentStep, setCurrentStep] = useState(null); - const STEP_LIST = [ - "Uploading your document", - "Generating embeddings", - "Ingesting document", - "Processing your document", - ]; + const STEP_LIST = [ + "Uploading your document", + "Generating embeddings", + "Ingesting document", + "Processing your document", + ]; - // Query tasks to track completion - const { data: tasks } = useGetTasksQuery({ - enabled: currentStep !== null, // Only poll when upload has started - refetchInterval: currentStep !== null ? 1000 : false, // Poll every 1 second during upload - }); + // Query tasks to track completion + const { data: tasks } = useGetTasksQuery({ + enabled: currentStep !== null, // Only poll when upload has started + refetchInterval: currentStep !== null ? 1000 : false, // Poll every 1 second during upload + }); - const { refetch: refetchNudges } = useGetNudgesQuery(null); + const { refetch: refetchNudges } = useGetNudgesQuery(null); - // Monitor tasks and call onComplete when file processing is done - useEffect(() => { - if (currentStep === null || !tasks) { - return; - } + // Monitor tasks and call onComplete when file processing is done + useEffect(() => { + if (currentStep === null || !tasks) { + return; + } - // Check if there are any active tasks (pending, running, or processing) - const activeTasks = tasks.find( - (task) => - task.status === "pending" || - task.status === "running" || - task.status === "processing", - ); + // Check if there are any active tasks (pending, running, or processing) + const activeTasks = tasks.find( + (task) => + task.status === "pending" || + task.status === "running" || + task.status === "processing", + ); - // If no active tasks and we have more than 1 task (initial + new upload), complete it - if ( - (!activeTasks || (activeTasks.processed_files ?? 0) > 0) && - tasks.length > 1 - ) { - // Set to final step to show "Done" - setCurrentStep(STEP_LIST.length); + // If no active tasks and we have more than 1 task (initial + new upload), complete it + if ( + (!activeTasks || (activeTasks.processed_files ?? 0) > 0) && + tasks.length > 1 + ) { + // Set to final step to show "Done" + setCurrentStep(STEP_LIST.length); - // Refetch nudges to get new ones - refetchNudges(); + // Refetch nudges to get new ones + refetchNudges(); - // Wait a bit before completing - setTimeout(() => { - onComplete(); - }, 1000); - } - }, [tasks, currentStep, onComplete, refetchNudges]); + // Wait a bit before completing + setTimeout(() => { + onComplete(); + }, 1000); + } + }, [tasks, currentStep, onComplete, refetchNudges]); - const resetFileInput = () => { - if (fileInputRef.current) { - fileInputRef.current.value = ""; - } - }; + const resetFileInput = () => { + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }; - const handleUploadClick = () => { - fileInputRef.current?.click(); - }; + const handleUploadClick = () => { + fileInputRef.current?.click(); + }; - const performUpload = async (file: File) => { - setIsUploading(true); - try { - setCurrentStep(0); - await uploadFile(file, true); - console.log("Document upload task started successfully"); - // Move to processing step - task monitoring will handle completion - setTimeout(() => { - setCurrentStep(1); - }, 1500); - } catch (error) { - console.error("Upload failed", (error as Error).message); - // Reset on error - setCurrentStep(null); - } finally { - setIsUploading(false); - } - }; + const performUpload = async (file: File) => { + setIsUploading(true); + try { + setCurrentStep(0); + await uploadFile(file, true); + console.log("Document upload task started successfully"); + // Move to processing step - task monitoring will handle completion + setTimeout(() => { + setCurrentStep(1); + }, 1500); + } catch (error) { + console.error("Upload failed", (error as Error).message); + // Reset on error + setCurrentStep(null); + } finally { + setIsUploading(false); + } + }; - const handleFileChange = async (event: ChangeEvent) => { - const selectedFile = event.target.files?.[0]; - if (!selectedFile) { - resetFileInput(); - return; - } + const handleFileChange = async (event: ChangeEvent) => { + const selectedFile = event.target.files?.[0]; + if (!selectedFile) { + resetFileInput(); + return; + } - try { - await performUpload(selectedFile); - } catch (error) { - console.error( - "Unable to prepare file for upload", - (error as Error).message, - ); - } finally { - resetFileInput(); - } - }; + try { + await performUpload(selectedFile); + } catch (error) { + console.error( + "Unable to prepare file for upload", + (error as Error).message, + ); + } finally { + resetFileInput(); + } + }; - return ( - - {currentStep === null ? ( - - - - - ) : ( - - - - )} - - ); + return ( + + {currentStep === null ? ( + + + + + ) : ( + + + + )} + + ); }; export default OnboardingUpload; diff --git a/frontend/components/chat-renderer.tsx b/frontend/components/chat-renderer.tsx index ca03a5f3..0d9ce0c6 100644 --- a/frontend/components/chat-renderer.tsx +++ b/frontend/components/chat-renderer.tsx @@ -19,8 +19,10 @@ import { ANIMATION_DURATION, HEADER_HEIGHT, ONBOARDING_ASSISTANT_MESSAGE_KEY, + ONBOARDING_CARD_STEPS_KEY, ONBOARDING_SELECTED_NUDGE_KEY, ONBOARDING_STEP_KEY, + ONBOARDING_UPLOAD_STEPS_KEY, SIDEBAR_WIDTH, TOTAL_ONBOARDING_STEPS, } from "@/lib/constants"; @@ -85,6 +87,8 @@ export function ChatRenderer({ localStorage.removeItem(ONBOARDING_STEP_KEY); localStorage.removeItem(ONBOARDING_ASSISTANT_MESSAGE_KEY); localStorage.removeItem(ONBOARDING_SELECTED_NUDGE_KEY); + localStorage.removeItem(ONBOARDING_CARD_STEPS_KEY); + localStorage.removeItem(ONBOARDING_UPLOAD_STEPS_KEY); } setShowLayout(true); } @@ -102,6 +106,8 @@ export function ChatRenderer({ localStorage.removeItem(ONBOARDING_STEP_KEY); localStorage.removeItem(ONBOARDING_ASSISTANT_MESSAGE_KEY); localStorage.removeItem(ONBOARDING_SELECTED_NUDGE_KEY); + localStorage.removeItem(ONBOARDING_CARD_STEPS_KEY); + localStorage.removeItem(ONBOARDING_UPLOAD_STEPS_KEY); } setShowLayout(true); }; diff --git a/frontend/lib/constants.ts b/frontend/lib/constants.ts index ea48fb94..6368ed0e 100644 --- a/frontend/lib/constants.ts +++ b/frontend/lib/constants.ts @@ -36,6 +36,8 @@ export const TOTAL_ONBOARDING_STEPS = 5; export const ONBOARDING_STEP_KEY = "onboarding_current_step"; export const ONBOARDING_ASSISTANT_MESSAGE_KEY = "onboarding_assistant_message"; export const ONBOARDING_SELECTED_NUDGE_KEY = "onboarding_selected_nudge"; +export const ONBOARDING_CARD_STEPS_KEY = "onboarding_card_steps"; +export const ONBOARDING_UPLOAD_STEPS_KEY = "onboarding_upload_steps"; export const FILES_REGEX = /(?<=I'm uploading a document called ['"])[^'"]+\.[^.]+(?=['"]\. Here is its content:)/;