diff --git a/frontend/components/ui/accordion.tsx b/frontend/components/ui/accordion.tsx index 2be19b59..37ec5c3c 100644 --- a/frontend/components/ui/accordion.tsx +++ b/frontend/components/ui/accordion.tsx @@ -14,7 +14,7 @@ const AccordionItem = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/frontend/components/ui/tabs.tsx b/frontend/components/ui/tabs.tsx index 5d75c455..874f054f 100644 --- a/frontend/components/ui/tabs.tsx +++ b/frontend/components/ui/tabs.tsx @@ -13,7 +13,7 @@ const TabsList = React.forwardRef< )} - {(hasLanguageModels || hasEmbeddingModels) && !updatedOnboarding && } - {!updatedOnboarding && ( - - - - )} diff --git a/frontend/src/app/onboarding/components/ollama-onboarding.tsx b/frontend/src/app/onboarding/components/ollama-onboarding.tsx index b40e6714..9c86b94c 100644 --- a/frontend/src/app/onboarding/components/ollama-onboarding.tsx +++ b/frontend/src/app/onboarding/components/ollama-onboarding.tsx @@ -7,154 +7,143 @@ import type { OnboardingVariables } from "../../api/mutations/useOnboardingMutat import { useGetOllamaModelsQuery } from "../../api/queries/useGetModelsQuery"; import { useModelSelection } from "../hooks/useModelSelection"; import { useUpdateSettings } from "../hooks/useUpdateSettings"; -import { AdvancedOnboarding } from "./advanced"; import { ModelSelector } from "./model-selector"; export function OllamaOnboarding({ - setSettings, - sampleDataset, - setSampleDataset, + setSettings, + sampleDataset, + setSampleDataset, }: { - setSettings: (settings: OnboardingVariables) => void; - sampleDataset: boolean; - setSampleDataset: (dataset: boolean) => void; + setSettings: (settings: OnboardingVariables) => void; + sampleDataset: boolean; + setSampleDataset: (dataset: boolean) => void; }) { - const [endpoint, setEndpoint] = useState(`http://localhost:11434`); - const [showConnecting, setShowConnecting] = useState(false); - const debouncedEndpoint = useDebouncedValue(endpoint, 500); + const [endpoint, setEndpoint] = useState(`http://localhost:11434`); + const [showConnecting, setShowConnecting] = useState(false); + const debouncedEndpoint = useDebouncedValue(endpoint, 500); - // Fetch models from API when endpoint is provided (debounced) - const { - data: modelsData, - isLoading: isLoadingModels, - error: modelsError, - } = useGetOllamaModelsQuery( - debouncedEndpoint ? { endpoint: debouncedEndpoint } : undefined, - ); + // Fetch models from API when endpoint is provided (debounced) + const { + data: modelsData, + isLoading: isLoadingModels, + error: modelsError, + } = useGetOllamaModelsQuery( + debouncedEndpoint ? { endpoint: debouncedEndpoint } : undefined, + ); - // Use custom hook for model selection logic - const { - languageModel, - embeddingModel, - setLanguageModel, - setEmbeddingModel, - languageModels, - embeddingModels, - } = useModelSelection(modelsData); + // Use custom hook for model selection logic + const { + languageModel, + embeddingModel, + setLanguageModel, + setEmbeddingModel, + languageModels, + embeddingModels, + } = useModelSelection(modelsData); - // Handle delayed display of connecting state - useEffect(() => { - let timeoutId: NodeJS.Timeout; + // Handle delayed display of connecting state + useEffect(() => { + let timeoutId: NodeJS.Timeout; - if (debouncedEndpoint && isLoadingModels) { - timeoutId = setTimeout(() => { - setShowConnecting(true); - }, 500); - } else { - setShowConnecting(false); - } + if (debouncedEndpoint && isLoadingModels) { + timeoutId = setTimeout(() => { + setShowConnecting(true); + }, 500); + } else { + setShowConnecting(false); + } - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }; - }, [debouncedEndpoint, isLoadingModels]); + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [debouncedEndpoint, isLoadingModels]); - const handleSampleDatasetChange = (dataset: boolean) => { - setSampleDataset(dataset); - }; + // Update settings when values change + useUpdateSettings( + "ollama", + { + endpoint, + languageModel, + embeddingModel, + }, + setSettings, + ); - // Update settings when values change - useUpdateSettings( - "ollama", - { - endpoint, - languageModel, - embeddingModel, - }, - setSettings, - ); + // Check validation state based on models query + const hasConnectionError = debouncedEndpoint && modelsError; + const hasNoModels = + modelsData && + !modelsData.language_models?.length && + !modelsData.embedding_models?.length; - // Check validation state based on models query - const hasConnectionError = debouncedEndpoint && modelsError; - const hasNoModels = - modelsData && - !modelsData.language_models?.length && - !modelsData.embedding_models?.length; - - return ( - <> -
-
- setEndpoint(e.target.value)} - /> - {showConnecting && ( -

- Connecting to Ollama server... -

- )} - {hasConnectionError && ( -

- Can’t reach Ollama at {debouncedEndpoint}. Update the base URL or - start the server. -

- )} - {hasNoModels && ( -

- No models found. Install embedding and agent models on your Ollama - server. -

- )} -
- - } - noOptionsPlaceholder={ - isLoadingModels - ? "Loading models..." - : "No embedding models detected. Install an embedding model to continue." - } - value={embeddingModel} - onValueChange={setEmbeddingModel} - /> - - - } - noOptionsPlaceholder={ - isLoadingModels - ? "Loading models..." - : "No language models detected. Install a language model to continue." - } - value={languageModel} - onValueChange={setLanguageModel} - /> - -
- - - ); + return ( +
+
+ setEndpoint(e.target.value)} + /> + {showConnecting && ( +

+ Connecting to Ollama server... +

+ )} + {hasConnectionError && ( +

+ Can’t reach Ollama at {debouncedEndpoint}. Update the base URL or + start the server. +

+ )} + {hasNoModels && ( +

+ No models found. Install embedding and agent models on your Ollama + server. +

+ )} +
+ + } + noOptionsPlaceholder={ + isLoadingModels + ? "Loading models..." + : "No embedding models detected. Install an embedding model to continue." + } + value={embeddingModel} + onValueChange={setEmbeddingModel} + /> + + + } + noOptionsPlaceholder={ + isLoadingModels + ? "Loading models..." + : "No language models detected. Install a language model to continue." + } + value={languageModel} + onValueChange={setLanguageModel} + /> + +
+ ); } diff --git a/frontend/src/app/onboarding/components/onboarding-card.tsx b/frontend/src/app/onboarding/components/onboarding-card.tsx index 10f08d09..745dd076 100644 --- a/frontend/src/app/onboarding/components/onboarding-card.tsx +++ b/frontend/src/app/onboarding/components/onboarding-card.tsx @@ -4,8 +4,8 @@ import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { - type OnboardingVariables, - useOnboardingMutation, + type OnboardingVariables, + useOnboardingMutation, } from "@/app/api/mutations/useOnboardingMutation"; import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery"; import { useDoclingHealth } from "@/components/docling-health-banner"; @@ -14,24 +14,25 @@ import OllamaLogo from "@/components/logo/ollama-logo"; import OpenAILogo from "@/components/logo/openai-logo"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardFooter, - CardHeader, + Card, + CardContent, + CardFooter, + CardHeader, } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { - Tooltip, - TooltipContent, - TooltipTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; import { AnimatedProviderSteps } from "./animated-provider-steps"; import { IBMOnboarding } from "./ibm-onboarding"; import { OllamaOnboarding } from "./ollama-onboarding"; import { OpenAIOnboarding } from "./openai-onboarding"; interface OnboardingCardProps { - onComplete: () => void; + onComplete: () => void; } @@ -45,220 +46,226 @@ const STEP_LIST = [ const TOTAL_PROVIDER_STEPS = STEP_LIST.length; const OnboardingCard = ({ onComplete }: OnboardingCardProps) => { - const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true"; - const { isHealthy: isDoclingHealthy } = useDoclingHealth(); + const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true"; + const { isHealthy: isDoclingHealthy } = useDoclingHealth(); - const [modelProvider, setModelProvider] = useState("openai"); + const [modelProvider, setModelProvider] = useState("openai"); - const [sampleDataset, setSampleDataset] = useState(true); + const [sampleDataset, setSampleDataset] = useState(true); - const handleSetModelProvider = (provider: string) => { - setModelProvider(provider); - setSettings({ - model_provider: provider, - embedding_model: "", - llm_model: "", - }); - }; + const handleSetModelProvider = (provider: string) => { + setModelProvider(provider); + setSettings({ + model_provider: provider, + embedding_model: "", + llm_model: "", + }); + }; - const [settings, setSettings] = useState({ - model_provider: modelProvider, - embedding_model: "", - llm_model: "", - }); + const [settings, setSettings] = useState({ + model_provider: modelProvider, + embedding_model: "", + llm_model: "", + }); - const [currentStep, setCurrentStep] = useState(null); + const [currentStep, setCurrentStep] = 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) { - return; - } + // Monitor tasks and call onComplete when all tasks are 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've started onboarding, complete it - if ( - (!activeTasks || (activeTasks.processed_files ?? 0) > 0) && - tasks.length > 0 - ) { - // Set to final step to show "Done" - setCurrentStep(TOTAL_PROVIDER_STEPS); - // Wait a bit before completing - setTimeout(() => { - onComplete(); - }, 1000); - } - }, [tasks, currentStep, onComplete]); + // If no active tasks and we've started onboarding, complete it + if ( + (!activeTasks || (activeTasks.processed_files ?? 0) > 0) && + tasks.length > 0 + ) { + // Set to final step to show "Done" + setCurrentStep(TOTAL_PROVIDER_STEPS); + // Wait a bit before completing + setTimeout(() => { + onComplete(); + }, 1000); + } + }, [tasks, currentStep, onComplete]); - // Mutations - const onboardingMutation = useOnboardingMutation({ - onSuccess: (data) => { - console.log("Onboarding completed successfully", data); - setCurrentStep(0); - }, - onError: (error) => { - toast.error("Failed to complete onboarding", { - description: error.message, - }); - }, - }); + // Mutations + const onboardingMutation = useOnboardingMutation({ + onSuccess: (data) => { + console.log("Onboarding completed successfully", data); + setCurrentStep(0); + }, + onError: (error) => { + toast.error("Failed to complete onboarding", { + description: error.message, + }); + }, + }); - const handleComplete = () => { - if ( - !settings.model_provider || - !settings.llm_model || - !settings.embedding_model - ) { - toast.error("Please complete all required fields"); - return; - } + const handleComplete = () => { + if ( + !settings.model_provider || + !settings.llm_model || + !settings.embedding_model + ) { + toast.error("Please complete all required fields"); + return; + } - // Prepare onboarding data - const onboardingData: OnboardingVariables = { - model_provider: settings.model_provider, - llm_model: settings.llm_model, - embedding_model: settings.embedding_model, - sample_data: sampleDataset, - }; + // Prepare onboarding data + const onboardingData: OnboardingVariables = { + model_provider: settings.model_provider, + llm_model: settings.llm_model, + embedding_model: settings.embedding_model, + sample_data: sampleDataset, + }; - // Add API key if available - if (settings.api_key) { - onboardingData.api_key = settings.api_key; - } + // Add API key if available + if (settings.api_key) { + onboardingData.api_key = settings.api_key; + } - // Add endpoint if available - if (settings.endpoint) { - onboardingData.endpoint = settings.endpoint; - } + // Add endpoint if available + if (settings.endpoint) { + onboardingData.endpoint = settings.endpoint; + } - // Add project_id if available - if (settings.project_id) { - onboardingData.project_id = settings.project_id; - } + // Add project_id if available + if (settings.project_id) { + onboardingData.project_id = settings.project_id; + } - onboardingMutation.mutate(onboardingData); - setCurrentStep(0); - }; + onboardingMutation.mutate(onboardingData); + setCurrentStep(0); + }; - const isComplete = - !!settings.llm_model && !!settings.embedding_model && isDoclingHealthy; + const isComplete = + !!settings.llm_model && !!settings.embedding_model && isDoclingHealthy; - return ( - - {currentStep === null ? ( - - - - - - - - OpenAI - - - - IBM watsonx.ai - - - - Ollama - - - - - - - - - - - - - - - - - - -
- -
-
- {!isComplete && ( - - {!!settings.llm_model && - !!settings.embedding_model && - !isDoclingHealthy - ? "docling-serve must be running to continue" - : "Please fill in all required fields"} - - )} -
-
-
-
- ) : ( - - + {currentStep === null ? ( + +
+ + + +
+ +
+ OpenAI +
+ +
+ +
+ IBM watsonx.ai +
+ +
+ +
+ Ollama +
+
+ + + + + + + + + +
+ + + +
+ +
+
+ {!isComplete && ( + + {!!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/src/components/chat-renderer.tsx b/frontend/src/components/chat-renderer.tsx index 3087b586..925ddb76 100644 --- a/frontend/src/components/chat-renderer.tsx +++ b/frontend/src/components/chat-renderer.tsx @@ -28,7 +28,7 @@ export function ChatRenderer({ settings, children, }: { - settings: Settings; + settings: Settings | undefined; children: React.ReactNode; }) { const pathname = usePathname(); @@ -51,7 +51,8 @@ export function ChatRenderer({ if (typeof window === "undefined") return false; const savedStep = localStorage.getItem(ONBOARDING_STEP_KEY); // Show layout if settings.edited is true and if no onboarding step is saved - return !!settings?.edited && savedStep === null; + const isEdited = settings?.edited ?? true; + return isEdited ? savedStep === null : false; }); // Only fetch conversations on chat page diff --git a/frontend/src/components/layout-wrapper.tsx b/frontend/src/components/layout-wrapper.tsx index 17339f60..36713e74 100644 --- a/frontend/src/components/layout-wrapper.tsx +++ b/frontend/src/components/layout-wrapper.tsx @@ -35,9 +35,10 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { const isUnhealthy = health?.status === "unhealthy" || isError; const isBannerVisible = !isHealthLoading && isUnhealthy; + const isSettingsLoadingOrError = isSettingsLoading || !settings; // Show loading state when backend isn't ready - if (isLoading || isSettingsLoading || !settings) { + if (isLoading || (isSettingsLoadingOrError && (isNoAuthMode || isAuthenticated))) { return (