Added search type and search mode selections for chcat input

This commit is contained in:
Utkarsh-Patel-13 2025-07-22 15:11:33 -07:00
parent 3f051b0a19
commit ac99613b24
3 changed files with 198 additions and 42 deletions

View file

@ -20,6 +20,7 @@ export default function ResearchChatPageV2() {
isLoading, isLoading,
setIsLoading, setIsLoading,
searchMode, searchMode,
setSearchMode,
researchMode, researchMode,
setResearchMode, setResearchMode,
selectedConnectors, selectedConnectors,
@ -34,8 +35,6 @@ export default function ResearchChatPageV2() {
const { fetchChatDetails, updateChat, createChat } = useChatAPI({ const { fetchChatDetails, updateChat, createChat } = useChatAPI({
token, token,
search_space_id: search_space_id as string, search_space_id: search_space_id as string,
researchMode,
selectedConnectors,
}); });
// Memoize document IDs to prevent infinite re-renders // Memoize document IDs to prevent infinite re-renders
@ -43,31 +42,37 @@ export default function ResearchChatPageV2() {
return selectedDocuments.map((doc) => doc.id); return selectedDocuments.map((doc) => doc.id);
}, [selectedDocuments]); }, [selectedDocuments]);
// Helper functions for localStorage management // Unified localStorage management for chat state
const getStorageKey = (searchSpaceId: string, chatId: string) => interface ChatState {
`surfsense_selected_docs_${searchSpaceId}_${chatId}`; selectedDocuments: Document[];
searchMode: "DOCUMENTS" | "CHUNKS";
researchMode: ResearchMode;
}
const storeSelectedDocuments = ( const getChatStateStorageKey = (searchSpaceId: string, chatId: string) =>
`surfsense_chat_state_${searchSpaceId}_${chatId}`;
const storeChatState = (
searchSpaceId: string, searchSpaceId: string,
chatId: string, chatId: string,
documents: Document[] state: ChatState
) => { ) => {
const key = getStorageKey(searchSpaceId, chatId); const key = getChatStateStorageKey(searchSpaceId, chatId);
localStorage.setItem(key, JSON.stringify(documents)); localStorage.setItem(key, JSON.stringify(state));
}; };
const restoreSelectedDocuments = ( const restoreChatState = (
searchSpaceId: string, searchSpaceId: string,
chatId: string chatId: string
): Document[] | null => { ): ChatState | null => {
const key = getStorageKey(searchSpaceId, chatId); const key = getChatStateStorageKey(searchSpaceId, chatId);
const stored = localStorage.getItem(key); const stored = localStorage.getItem(key);
if (stored) { if (stored) {
localStorage.removeItem(key); // Clean up after restoration localStorage.removeItem(key); // Clean up after restoration
try { try {
return JSON.parse(stored); return JSON.parse(stored);
} catch (error) { } catch (error) {
console.error("Error parsing stored documents:", error); console.error("Error parsing stored chat state:", error);
return null; return null;
} }
} }
@ -99,14 +104,14 @@ export default function ResearchChatPageV2() {
message: Message | CreateMessage, message: Message | CreateMessage,
chatRequestOptions?: { data?: any } chatRequestOptions?: { data?: any }
) => { ) => {
const newChatId = await createChat(message.content); const newChatId = await createChat(message.content, researchMode);
if (newChatId) { if (newChatId) {
// Store selected documents before navigation // Store chat state before navigation
storeSelectedDocuments( storeChatState(search_space_id as string, newChatId, {
search_space_id as string, selectedDocuments,
newChatId, searchMode,
selectedDocuments researchMode,
); });
router.replace(`/dashboard/${search_space_id}/v2/${newChatId}`); router.replace(`/dashboard/${search_space_id}/v2/${newChatId}`);
} }
return newChatId; return newChatId;
@ -119,18 +124,26 @@ export default function ResearchChatPageV2() {
} }
}, [token, isNewChat, chatIdParam]); }, [token, isNewChat, chatIdParam]);
// Restore selected documents from localStorage on page load // Restore chat state from localStorage on page load
useEffect(() => { useEffect(() => {
if (chatIdParam && search_space_id) { if (chatIdParam && search_space_id) {
const restoredDocuments = restoreSelectedDocuments( const restoredState = restoreChatState(
search_space_id as string, search_space_id as string,
chatIdParam chatIdParam
); );
if (restoredDocuments && restoredDocuments.length > 0) { if (restoredState) {
setSelectedDocuments(restoredDocuments); setSelectedDocuments(restoredState.selectedDocuments);
setSearchMode(restoredState.searchMode);
setResearchMode(restoredState.researchMode);
} }
} }
}, [chatIdParam, search_space_id, setSelectedDocuments]); }, [
chatIdParam,
search_space_id,
setSelectedDocuments,
setSearchMode,
setResearchMode,
]);
const loadChatData = async (chatId: string) => { const loadChatData = async (chatId: string) => {
try { try {
@ -179,9 +192,9 @@ export default function ResearchChatPageV2() {
handler.messages.length > 0 && handler.messages.length > 0 &&
handler.messages[handler.messages.length - 1]?.role === "assistant" handler.messages[handler.messages.length - 1]?.role === "assistant"
) { ) {
updateChat(chatIdParam, handler.messages); updateChat(chatIdParam, handler.messages, researchMode);
} }
}, [handler.messages, handler.status, chatIdParam, isNewChat, updateChat]); }, [handler.messages, handler.status, chatIdParam, isNewChat]);
if (isLoading) { if (isLoading) {
return ( return (
@ -199,6 +212,10 @@ export default function ResearchChatPageV2() {
}} }}
onDocumentSelectionChange={setSelectedDocuments} onDocumentSelectionChange={setSelectedDocuments}
selectedDocuments={selectedDocuments} selectedDocuments={selectedDocuments}
searchMode={searchMode}
onSearchModeChange={setSearchMode}
researchMode={researchMode}
onResearchModeChange={setResearchMode}
/> />
); );
} }

View file

@ -17,16 +17,29 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "../ui/dialog"; } from "../ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { Badge } from "../ui/badge";
import { Suspense, useState, useCallback } from "react"; import { Suspense, useState, useCallback } from "react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useDocuments, DocumentType, Document } from "@/hooks/use-documents"; import { useDocuments, DocumentType, Document } from "@/hooks/use-documents";
import { DocumentsDataTable } from "./DocumentsDataTable"; import { DocumentsDataTable } from "./DocumentsDataTable";
import { ResearchMode } from "@/components/chat";
import React from "react"; import React from "react";
interface ChatInterfaceProps { interface ChatInterfaceProps {
handler: ChatHandler; handler: ChatHandler;
onDocumentSelectionChange?: (documents: Document[]) => void; onDocumentSelectionChange?: (documents: Document[]) => void;
selectedDocuments?: Document[]; selectedDocuments?: Document[];
searchMode?: "DOCUMENTS" | "CHUNKS";
onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void;
researchMode?: ResearchMode;
onResearchModeChange?: (mode: ResearchMode) => void;
} }
const DocumentSelector = React.memo( const DocumentSelector = React.memo(
@ -118,21 +131,127 @@ const DocumentSelector = React.memo(
} }
); );
const SearchModeSelector = ({
searchMode,
onSearchModeChange,
}: {
searchMode?: "DOCUMENTS" | "CHUNKS";
onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void;
}) => {
return (
<div className="flex items-center gap-1 sm:gap-2">
<span className="text-xs text-muted-foreground hidden sm:block">
Scope:
</span>
<div className="flex rounded-md border">
<Button
variant={searchMode === "DOCUMENTS" ? "default" : "ghost"}
size="sm"
className="rounded-r-none border-r h-8 px-2 sm:px-3 text-xs"
onClick={() => onSearchModeChange?.("DOCUMENTS")}
>
<span className="hidden sm:inline">Documents</span>
<span className="sm:hidden">Docs</span>
</Button>
<Button
variant={searchMode === "CHUNKS" ? "default" : "ghost"}
size="sm"
className="rounded-l-none h-8 px-2 sm:px-3 text-xs"
onClick={() => onSearchModeChange?.("CHUNKS")}
>
Chunks
</Button>
</div>
</div>
);
};
const ResearchModeSelector = ({
researchMode,
onResearchModeChange,
}: {
researchMode?: ResearchMode;
onResearchModeChange?: (mode: ResearchMode) => void;
}) => {
const researchModeLabels: Record<ResearchMode, string> = {
QNA: "Q&A",
REPORT_GENERAL: "General Report",
REPORT_DEEP: "Deep Report",
REPORT_DEEPER: "Deeper Report",
};
const researchModeShortLabels: Record<ResearchMode, string> = {
QNA: "Q&A",
REPORT_GENERAL: "General",
REPORT_DEEP: "Deep",
REPORT_DEEPER: "Deeper",
};
return (
<div className="flex items-center gap-1 sm:gap-2">
<span className="text-xs text-muted-foreground hidden sm:block">
Mode:
</span>
<Select
value={researchMode}
onValueChange={(value) =>
onResearchModeChange?.(value as ResearchMode)
}
>
<SelectTrigger className="w-auto min-w-[80px] sm:min-w-[120px] h-8 text-xs">
<SelectValue placeholder="Mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="QNA">Q&A</SelectItem>
<SelectItem value="REPORT_GENERAL">
<span className="hidden sm:inline">General Report</span>
<span className="sm:hidden">General</span>
</SelectItem>
<SelectItem value="REPORT_DEEP">
<span className="hidden sm:inline">Deep Report</span>
<span className="sm:hidden">Deep</span>
</SelectItem>
<SelectItem value="REPORT_DEEPER">
<span className="hidden sm:inline">Deeper Report</span>
<span className="sm:hidden">Deeper</span>
</SelectItem>
</SelectContent>
</Select>
</div>
);
};
const CustomChatInputOptions = ({ const CustomChatInputOptions = ({
onDocumentSelectionChange, onDocumentSelectionChange,
selectedDocuments, selectedDocuments,
searchMode,
onSearchModeChange,
researchMode,
onResearchModeChange,
}: { }: {
onDocumentSelectionChange?: (documents: Document[]) => void; onDocumentSelectionChange?: (documents: Document[]) => void;
selectedDocuments?: Document[]; selectedDocuments?: Document[];
searchMode?: "DOCUMENTS" | "CHUNKS";
onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void;
researchMode?: ResearchMode;
onResearchModeChange?: (mode: ResearchMode) => void;
}) => { }) => {
return ( return (
<div className="flex flex-row gap-2"> <div className="flex flex-wrap gap-2 sm:gap-3 items-center justify-start">
<Suspense fallback={<div>Loading...</div>}> <Suspense fallback={<div>Loading...</div>}>
<DocumentSelector <DocumentSelector
onSelectionChange={onDocumentSelectionChange} onSelectionChange={onDocumentSelectionChange}
selectedDocuments={selectedDocuments} selectedDocuments={selectedDocuments}
/> />
</Suspense> </Suspense>
<SearchModeSelector
searchMode={searchMode}
onSearchModeChange={onSearchModeChange}
/>
<ResearchModeSelector
researchMode={researchMode}
onResearchModeChange={onResearchModeChange}
/>
</div> </div>
); );
}; };
@ -140,9 +259,17 @@ const CustomChatInputOptions = ({
const CustomChatInput = ({ const CustomChatInput = ({
onDocumentSelectionChange, onDocumentSelectionChange,
selectedDocuments, selectedDocuments,
searchMode,
onSearchModeChange,
researchMode,
onResearchModeChange,
}: { }: {
onDocumentSelectionChange?: (documents: Document[]) => void; onDocumentSelectionChange?: (documents: Document[]) => void;
selectedDocuments?: Document[]; selectedDocuments?: Document[];
searchMode?: "DOCUMENTS" | "CHUNKS";
onSearchModeChange?: (mode: "DOCUMENTS" | "CHUNKS") => void;
researchMode?: ResearchMode;
onResearchModeChange?: (mode: ResearchMode) => void;
}) => { }) => {
return ( return (
<ChatInput> <ChatInput>
@ -153,6 +280,10 @@ const CustomChatInput = ({
<CustomChatInputOptions <CustomChatInputOptions
onDocumentSelectionChange={onDocumentSelectionChange} onDocumentSelectionChange={onDocumentSelectionChange}
selectedDocuments={selectedDocuments} selectedDocuments={selectedDocuments}
searchMode={searchMode}
onSearchModeChange={onSearchModeChange}
researchMode={researchMode}
onResearchModeChange={onResearchModeChange}
/> />
</ChatInput> </ChatInput>
); );
@ -162,6 +293,10 @@ export default function ChatInterface({
handler, handler,
onDocumentSelectionChange, onDocumentSelectionChange,
selectedDocuments = [], selectedDocuments = [],
searchMode,
onSearchModeChange,
researchMode,
onResearchModeChange,
}: ChatInterfaceProps) { }: ChatInterfaceProps) {
return ( return (
<ChatSection handler={handler} className="flex h-full"> <ChatSection handler={handler} className="flex h-full">
@ -177,6 +312,10 @@ export default function ChatInterface({
<CustomChatInput <CustomChatInput
onDocumentSelectionChange={onDocumentSelectionChange} onDocumentSelectionChange={onDocumentSelectionChange}
selectedDocuments={selectedDocuments} selectedDocuments={selectedDocuments}
searchMode={searchMode}
onSearchModeChange={onSearchModeChange}
researchMode={researchMode}
onResearchModeChange={onResearchModeChange}
/> />
</div> </div>
</div> </div>

View file

@ -49,16 +49,9 @@ export function useChatState({ search_space_id, chat_id }: UseChatStateProps) {
interface UseChatAPIProps { interface UseChatAPIProps {
token: string | null; token: string | null;
search_space_id: string; search_space_id: string;
researchMode: ResearchMode;
selectedConnectors: string[];
} }
export function useChatAPI({ export function useChatAPI({ token, search_space_id }: UseChatAPIProps) {
token,
search_space_id,
researchMode,
selectedConnectors,
}: UseChatAPIProps) {
const fetchChatDetails = useCallback( const fetchChatDetails = useCallback(
async (chatId: string) => { async (chatId: string) => {
if (!token) return null; if (!token) return null;
@ -93,7 +86,10 @@ export function useChatAPI({
); );
const createChat = useCallback( const createChat = useCallback(
async (initialMessage: string): Promise<string | null> => { async (
initialMessage: string,
researchMode: ResearchMode
): Promise<string | null> => {
if (!token) { if (!token) {
console.error("Authentication token not found"); console.error("Authentication token not found");
return null; return null;
@ -111,7 +107,7 @@ export function useChatAPI({
body: JSON.stringify({ body: JSON.stringify({
type: researchMode, type: researchMode,
title: "Untitled Chat", title: "Untitled Chat",
initial_connectors: selectedConnectors, initial_connectors: [],
messages: [ messages: [
{ {
role: "user", role: "user",
@ -136,11 +132,15 @@ export function useChatAPI({
return null; return null;
} }
}, },
[token, researchMode, selectedConnectors, search_space_id] [token, search_space_id]
); );
const updateChat = useCallback( const updateChat = useCallback(
async (chatId: string, messages: Message[]) => { async (
chatId: string,
messages: Message[],
researchMode: ResearchMode
) => {
if (!token) return; if (!token) return;
try { try {
@ -164,7 +164,7 @@ export function useChatAPI({
body: JSON.stringify({ body: JSON.stringify({
type: researchMode, type: researchMode,
title: title, title: title,
initial_connectors: selectedConnectors, initial_connectors: [],
messages: messages, messages: messages,
search_space_id: Number(search_space_id), search_space_id: Number(search_space_id),
}), }),
@ -180,7 +180,7 @@ export function useChatAPI({
console.error("Error updating chat:", err); console.error("Error updating chat:", err);
} }
}, },
[token, researchMode, selectedConnectors, search_space_id] [token, search_space_id]
); );
return { return {