add chat flow for search results

This commit is contained in:
2026-05-02 16:48:01 -07:00
parent fa429dcbb3
commit dc9336acf9
9 changed files with 322 additions and 28 deletions

View File

@@ -8,6 +8,7 @@ import { ChatMessagesPanel } from "@/components/chat/chat-messages-panel";
import { SearchResultsPanel } from "@/components/search/search-results-panel";
import {
createChat,
createChatFromSearch,
createSearch,
deleteChat,
deleteSearch,
@@ -164,6 +165,10 @@ function isToolCallLogMessage(message: Message) {
return asToolLogMetadata(message.metadata) !== null;
}
function isDisplayableMessage(message: Message) {
return message.role !== "system";
}
function buildOptimisticToolMessage(event: ToolCallEvent): Message {
return {
id: `temp-tool-${event.toolCallId}`,
@@ -427,6 +432,7 @@ export default function App() {
const [isLoadingCollections, setIsLoadingCollections] = useState(false);
const [isLoadingSelection, setIsLoadingSelection] = useState(false);
const [isSending, setIsSending] = useState(false);
const [isStartingSearchChat, setIsStartingSearchChat] = useState(false);
const [pendingChatState, setPendingChatState] = useState<{ chatId: string | null; messages: Message[] } | null>(null);
const [composer, setComposer] = useState("");
const [provider, setProvider] = useState<Provider>("openai");
@@ -699,14 +705,14 @@ export default function App() {
selectedItem?.kind === "chat" &&
selectedItem.id === pendingChatState.chatId;
const displayMessages = useMemo(() => {
if (!pendingChatState) return messages;
if (!pendingChatState) return messages.filter(isDisplayableMessage);
if (pendingChatState.chatId) {
if (selectedItem?.kind === "chat" && selectedItem.id === pendingChatState.chatId) {
return pendingChatState.messages;
return pendingChatState.messages.filter(isDisplayableMessage);
}
return messages;
return messages.filter(isDisplayableMessage);
}
return isSearchMode ? messages : pendingChatState.messages;
return (isSearchMode ? messages : pendingChatState.messages).filter(isDisplayableMessage);
}, [isSearchMode, messages, pendingChatState, selectedItem]);
const selectedChatSummary = useMemo(() => {
@@ -1149,6 +1155,47 @@ export default function App() {
await refreshCollections({ kind: "search", id: searchId });
};
const handleStartChatFromSearch = async () => {
if (!selectedSearch || isStartingSearchChat || isSending) return;
setError(null);
setIsStartingSearchChat(true);
try {
const chat = await createChatFromSearch(selectedSearch.id);
setDraftKind(null);
setPendingChatState(null);
setComposer("");
setChats((current) => {
const withoutExisting = current.filter((existing) => existing.id !== chat.id);
return [chat, ...withoutExisting];
});
setSelectedItem({ kind: "chat", id: chat.id });
setSelectedChat({
id: chat.id,
title: chat.title,
createdAt: chat.createdAt,
updatedAt: chat.updatedAt,
initiatedProvider: chat.initiatedProvider,
initiatedModel: chat.initiatedModel,
lastUsedProvider: chat.lastUsedProvider,
lastUsedModel: chat.lastUsedModel,
messages: [],
});
setSelectedSearch(null);
await refreshCollections({ kind: "chat", id: chat.id });
await refreshChat(chat.id);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("bearer token")) {
handleAuthFailure(message);
} else {
setError(message);
}
} finally {
setIsStartingSearchChat(false);
}
};
const handleSend = async () => {
const content = composer.trim();
if (!content || isSending) return;
@@ -1388,7 +1435,13 @@ export default function App() {
{!isSearchMode ? (
<ChatMessagesPanel messages={displayMessages} isLoading={isLoadingSelection} isSending={isSendingActiveChat} />
) : (
<SearchResultsPanel search={selectedSearch} isLoading={isLoadingSelection} isRunning={isSearchRunning} />
<SearchResultsPanel
search={selectedSearch}
isLoading={isLoadingSelection}
isRunning={isSearchRunning}
isStartingChat={isStartingSearchChat}
onStartChat={selectedSearch ? handleStartChatFromSearch : undefined}
/>
)}
<div ref={transcriptEndRef} />
</div>