diff --git a/surfsense_web/components/chat_v2/ChatInputGroup.tsx b/surfsense_web/components/chat_v2/ChatInputGroup.tsx index 44824d2cc..f20496a2b 100644 --- a/surfsense_web/components/chat_v2/ChatInputGroup.tsx +++ b/surfsense_web/components/chat_v2/ChatInputGroup.tsx @@ -1,7 +1,7 @@ "use client"; import { ChatInput } from "@llamaindex/chat-ui"; -import { FolderOpen, Check } from "lucide-react"; +import { FolderOpen, Check, Zap, Brain } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -18,6 +18,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Badge } from "@/components/ui/badge"; import { Suspense, useState, useCallback } from "react"; import { useParams } from "next/navigation"; import { useDocuments, Document } from "@/hooks/use-documents"; @@ -28,6 +29,7 @@ import { ConnectorButton as ConnectorButtonComponent, } from "@/components/chat/ConnectorComponents"; import { ResearchMode } from "@/components/chat"; +import { useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs"; import React from "react"; const DocumentSelector = React.memo( @@ -65,9 +67,12 @@ const DocumentSelector = React.memo( const handleDone = useCallback(() => { setIsOpen(false); - }, [selectedDocuments]); + }, []); - const selectedCount = selectedDocuments.length; + const selectedCount = React.useMemo( + () => selectedDocuments.length, + [selectedDocuments.length] + ); return ( @@ -120,6 +125,8 @@ const DocumentSelector = React.memo( } ); +DocumentSelector.displayName = "DocumentSelector"; + const ConnectorSelector = React.memo( ({ onSelectionChange, @@ -240,162 +247,399 @@ const ConnectorSelector = React.memo( } ); -const SearchModeSelector = ({ - searchMode, - onSearchModeChange, -}: { - searchMode?: "DOCUMENTS" | "CHUNKS"; - onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void; -}) => { - return ( -
- - Scope: - -
+ConnectorSelector.displayName = "ConnectorSelector"; + +const SearchModeSelector = React.memo( + ({ + searchMode, + onSearchModeChange, + }: { + searchMode?: "DOCUMENTS" | "CHUNKS"; + onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void; + }) => { + const handleDocumentsClick = React.useCallback(() => { + onSearchModeChange?.("DOCUMENTS"); + }, [onSearchModeChange]); + + const handleChunksClick = React.useCallback(() => { + onSearchModeChange?.("CHUNKS"); + }, [onSearchModeChange]); + + return ( +
+ + Scope: + +
+ + +
+
+ ); + } +); + +SearchModeSelector.displayName = "SearchModeSelector"; + +const ResearchModeSelector = React.memo( + ({ + researchMode, + onResearchModeChange, + }: { + researchMode?: ResearchMode; + onResearchModeChange?: (mode: ResearchMode) => void; + }) => { + const handleValueChange = React.useCallback( + (value: string) => { + onResearchModeChange?.(value as ResearchMode); + }, + [onResearchModeChange] + ); + + // Memoize mode options to prevent recreation + const modeOptions = React.useMemo( + () => [ + { value: "QNA", label: "Q&A", shortLabel: "Q&A" }, + { + value: "REPORT_GENERAL", + label: "General Report", + shortLabel: "General", + }, + { + value: "REPORT_DEEP", + label: "Deep Report", + shortLabel: "Deep", + }, + { + value: "REPORT_DEEPER", + label: "Deeper Report", + shortLabel: "Deeper", + }, + ], + [] + ); + + return ( +
+ + Mode: + + +
+ ); + } +); + +ResearchModeSelector.displayName = "ResearchModeSelector"; + +const LLMSelector = React.memo(() => { + const { llmConfigs, loading: llmLoading, error } = useLLMConfigs(); + const { + preferences, + updatePreferences, + loading: preferencesLoading, + } = useLLMPreferences(); + + const isLoading = llmLoading || preferencesLoading; + + // Memoize the selected config to avoid repeated lookups + const selectedConfig = React.useMemo(() => { + if (!preferences.fast_llm_id || !llmConfigs.length) return null; + return ( + llmConfigs.find( + (config) => config.id === preferences.fast_llm_id + ) || null + ); + }, [preferences.fast_llm_id, llmConfigs]); + + // Memoize the display value for the trigger + const displayValue = React.useMemo(() => { + if (!selectedConfig) return null; + return ( +
+ + {selectedConfig.provider} + + + + {selectedConfig.name} + +
+ ); + }, [selectedConfig]); + + const handleValueChange = React.useCallback( + (value: string) => { + const llmId = value ? parseInt(value, 10) : undefined; + updatePreferences({ fast_llm_id: llmId }); + }, + [updatePreferences] + ); + + // Loading skeleton + if (isLoading) { + return ( +
+
+
+
+
+
+ ); + } + + // Error state + if (error) { + return ( +
-
-
- ); -}; + ); + } -const ResearchModeSelector = ({ - researchMode, - onResearchModeChange, -}: { - researchMode?: ResearchMode; - onResearchModeChange?: (mode: ResearchMode) => void; -}) => { return ( -
- - Mode: - +
); -}; +}); -const CustomChatInputOptions = ({ - onDocumentSelectionChange, - selectedDocuments, - onConnectorSelectionChange, - selectedConnectors, - searchMode, - onSearchModeChange, - researchMode, - onResearchModeChange, -}: { - onDocumentSelectionChange?: (documents: Document[]) => void; - selectedDocuments?: Document[]; - onConnectorSelectionChange?: (connectorTypes: string[]) => void; - selectedConnectors?: string[]; - searchMode?: "DOCUMENTS" | "CHUNKS"; - onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void; - researchMode?: ResearchMode; - onResearchModeChange?: (mode: ResearchMode) => void; -}) => { - return ( -
- Loading...
}> - void; + selectedDocuments?: Document[]; + onConnectorSelectionChange?: (connectorTypes: string[]) => void; + selectedConnectors?: string[]; + searchMode?: "DOCUMENTS" | "CHUNKS"; + onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void; + researchMode?: ResearchMode; + onResearchModeChange?: (mode: ResearchMode) => void; + }) => { + // Memoize the loading fallback to prevent recreation + const loadingFallback = React.useMemo( + () => ( +
+ ), + [] + ); + + return ( +
+ + + + + + + + + +
+ ); + } +); + +CustomChatInputOptions.displayName = "CustomChatInputOptions"; + +export const CustomChatInput = React.memo( + ({ + onDocumentSelectionChange, + selectedDocuments, + onConnectorSelectionChange, + selectedConnectors, + searchMode, + onSearchModeChange, + researchMode, + onResearchModeChange, + }: { + onDocumentSelectionChange?: (documents: Document[]) => void; + selectedDocuments?: Document[]; + onConnectorSelectionChange?: (connectorTypes: string[]) => void; + selectedConnectors?: string[]; + searchMode?: "DOCUMENTS" | "CHUNKS"; + onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void; + researchMode?: ResearchMode; + onResearchModeChange?: (mode: ResearchMode) => void; + }) => { + return ( + + + + + + - - Loading...
}> - - - - -
- ); -}; + + ); + } +); -export const CustomChatInput = ({ - onDocumentSelectionChange, - selectedDocuments, - onConnectorSelectionChange, - selectedConnectors, - searchMode, - onSearchModeChange, - researchMode, - onResearchModeChange, -}: { - onDocumentSelectionChange?: (documents: Document[]) => void; - selectedDocuments?: Document[]; - onConnectorSelectionChange?: (connectorTypes: string[]) => void; - selectedConnectors?: string[]; - searchMode?: "DOCUMENTS" | "CHUNKS"; - onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void; - researchMode?: ResearchMode; - onResearchModeChange?: (mode: ResearchMode) => void; -}) => { - return ( - - - - - - - - ); -}; +CustomChatInput.displayName = "CustomChatInput";