From d336baba329305ecb37b27d4f2e133af68abeb48 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Mon, 20 Oct 2025 18:33:15 -0300 Subject: [PATCH] changed useChatStreaming to work with the chat page --- frontend/src/hooks/useChatStreaming.ts | 301 ++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 8 deletions(-) diff --git a/frontend/src/hooks/useChatStreaming.ts b/frontend/src/hooks/useChatStreaming.ts index 1c5a0a03..df8f4aa6 100644 --- a/frontend/src/hooks/useChatStreaming.ts +++ b/frontend/src/hooks/useChatStreaming.ts @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import type { FunctionCall, Message } from "@/app/chat/types"; +import type { FunctionCall, Message, SelectedFilters } from "@/app/chat/types"; interface UseChatStreamingOptions { endpoint?: string; @@ -7,6 +7,14 @@ interface UseChatStreamingOptions { onError?: (error: Error) => void; } +interface SendMessageOptions { + prompt: string; + previousResponseId?: string; + filters?: SelectedFilters; + limit?: number; + scoreThreshold?: number; +} + export function useChatStreaming({ endpoint = "/api/chat", onComplete, @@ -19,7 +27,13 @@ export function useChatStreaming({ const streamAbortRef = useRef(null); const streamIdRef = useRef(0); - const sendMessage = async (prompt: string, previousResponseId?: string) => { + const sendMessage = async ({ + prompt, + previousResponseId, + filters, + limit = 10, + scoreThreshold = 0, + }: SendMessageOptions) => { try { setIsLoading(true); @@ -36,19 +50,28 @@ export function useChatStreaming({ prompt: string; stream: boolean; previous_response_id?: string; + filters?: SelectedFilters; + limit?: number; + scoreThreshold?: number; } = { prompt, stream: true, + limit, + scoreThreshold, }; if (previousResponseId) { requestBody.previous_response_id = previousResponseId; } + if (filters) { + requestBody.filters = filters; + } + const response = await fetch(endpoint, { method: "POST", headers: { - "Content-Type": "text/event-stream", + "Content-Type": "application/json", }, body: JSON.stringify(requestBody), signal: controller.signal, @@ -104,13 +127,275 @@ export function useChatStreaming({ newResponseId = chunk.response_id; } - // Handle OpenRAG backend format (from agent.py async_response_stream) - // The chunk is serialized via chunk.model_dump() and contains output_text or delta - if (chunk.output_text) { - // Direct output text from chunk + // Handle OpenAI Chat Completions streaming format + if (chunk.object === "response.chunk" && chunk.delta) { + // Handle function calls in delta + if (chunk.delta.function_call) { + if (chunk.delta.function_call.name) { + const functionCall: FunctionCall = { + name: chunk.delta.function_call.name, + arguments: undefined, + status: "pending", + argumentsString: + chunk.delta.function_call.arguments || "", + }; + currentFunctionCalls.push(functionCall); + } else if (chunk.delta.function_call.arguments) { + const lastFunctionCall = + currentFunctionCalls[currentFunctionCalls.length - 1]; + if (lastFunctionCall) { + if (!lastFunctionCall.argumentsString) { + lastFunctionCall.argumentsString = ""; + } + lastFunctionCall.argumentsString += + chunk.delta.function_call.arguments; + + if (lastFunctionCall.argumentsString.includes("}")) { + try { + const parsed = JSON.parse( + lastFunctionCall.argumentsString + ); + lastFunctionCall.arguments = parsed; + lastFunctionCall.status = "completed"; + } catch (e) { + // Arguments not yet complete + } + } + } + } + } + // Handle tool calls in delta + else if ( + chunk.delta.tool_calls && + Array.isArray(chunk.delta.tool_calls) + ) { + for (const toolCall of chunk.delta.tool_calls) { + if (toolCall.function) { + if (toolCall.function.name) { + const functionCall: FunctionCall = { + name: toolCall.function.name, + arguments: undefined, + status: "pending", + argumentsString: toolCall.function.arguments || "", + }; + currentFunctionCalls.push(functionCall); + } else if (toolCall.function.arguments) { + const lastFunctionCall = + currentFunctionCalls[ + currentFunctionCalls.length - 1 + ]; + if (lastFunctionCall) { + if (!lastFunctionCall.argumentsString) { + lastFunctionCall.argumentsString = ""; + } + lastFunctionCall.argumentsString += + toolCall.function.arguments; + + if ( + lastFunctionCall.argumentsString.includes("}") + ) { + try { + const parsed = JSON.parse( + lastFunctionCall.argumentsString + ); + lastFunctionCall.arguments = parsed; + lastFunctionCall.status = "completed"; + } catch (e) { + // Arguments not yet complete + } + } + } + } + } + } + } + // Handle content/text in delta + else if (chunk.delta.content) { + currentContent += chunk.delta.content; + } + + // Handle finish reason + if (chunk.delta.finish_reason) { + currentFunctionCalls.forEach((fc) => { + if (fc.status === "pending" && fc.argumentsString) { + try { + fc.arguments = JSON.parse(fc.argumentsString); + fc.status = "completed"; + } catch (e) { + fc.arguments = { raw: fc.argumentsString }; + fc.status = "error"; + } + } + }); + } + } + // Handle Realtime API format - function call added + else if ( + chunk.type === "response.output_item.added" && + chunk.item?.type === "function_call" + ) { + let existing = currentFunctionCalls.find( + (fc) => fc.id === chunk.item.id + ); + if (!existing) { + existing = [...currentFunctionCalls] + .reverse() + .find( + (fc) => + fc.status === "pending" && + !fc.id && + fc.name === (chunk.item.tool_name || chunk.item.name) + ); + } + + if (existing) { + existing.id = chunk.item.id; + existing.type = chunk.item.type; + existing.name = + chunk.item.tool_name || chunk.item.name || existing.name; + existing.arguments = + chunk.item.inputs || existing.arguments; + } else { + const functionCall: FunctionCall = { + name: + chunk.item.tool_name || chunk.item.name || "unknown", + arguments: chunk.item.inputs || undefined, + status: "pending", + argumentsString: "", + id: chunk.item.id, + type: chunk.item.type, + }; + currentFunctionCalls.push(functionCall); + } + } + // Handle Realtime API format - tool call added + else if ( + chunk.type === "response.output_item.added" && + chunk.item?.type?.includes("_call") && + chunk.item?.type !== "function_call" + ) { + let existing = currentFunctionCalls.find( + (fc) => fc.id === chunk.item.id + ); + if (!existing) { + existing = [...currentFunctionCalls] + .reverse() + .find( + (fc) => + fc.status === "pending" && + !fc.id && + fc.name === + (chunk.item.tool_name || + chunk.item.name || + chunk.item.type) + ); + } + + if (existing) { + existing.id = chunk.item.id; + existing.type = chunk.item.type; + existing.name = + chunk.item.tool_name || + chunk.item.name || + chunk.item.type || + existing.name; + existing.arguments = + chunk.item.inputs || existing.arguments; + } else { + const functionCall = { + name: + chunk.item.tool_name || + chunk.item.name || + chunk.item.type || + "unknown", + arguments: chunk.item.inputs || {}, + status: "pending" as const, + id: chunk.item.id, + type: chunk.item.type, + }; + currentFunctionCalls.push(functionCall); + } + } + // Handle function call done + else if ( + chunk.type === "response.output_item.done" && + chunk.item?.type === "function_call" + ) { + const functionCall = currentFunctionCalls.find( + (fc) => + fc.id === chunk.item.id || + fc.name === chunk.item.tool_name || + fc.name === chunk.item.name + ); + + if (functionCall) { + functionCall.status = + chunk.item.status === "completed" ? "completed" : "error"; + functionCall.id = chunk.item.id; + functionCall.type = chunk.item.type; + functionCall.name = + chunk.item.tool_name || + chunk.item.name || + functionCall.name; + functionCall.arguments = + chunk.item.inputs || functionCall.arguments; + + if (chunk.item.results) { + functionCall.result = chunk.item.results; + } + } + } + // Handle tool call done with results + else if ( + chunk.type === "response.output_item.done" && + chunk.item?.type?.includes("_call") && + chunk.item?.type !== "function_call" + ) { + const functionCall = currentFunctionCalls.find( + (fc) => + fc.id === chunk.item.id || + fc.name === chunk.item.tool_name || + fc.name === chunk.item.name || + fc.name === chunk.item.type || + fc.name.includes(chunk.item.type.replace("_call", "")) || + chunk.item.type.includes(fc.name) + ); + + if (functionCall) { + functionCall.arguments = + chunk.item.inputs || functionCall.arguments; + functionCall.status = + chunk.item.status === "completed" ? "completed" : "error"; + functionCall.id = chunk.item.id; + functionCall.type = chunk.item.type; + + if (chunk.item.results) { + functionCall.result = chunk.item.results; + } + } else { + const newFunctionCall = { + name: + chunk.item.tool_name || + chunk.item.name || + chunk.item.type || + "unknown", + arguments: chunk.item.inputs || {}, + status: "completed" as const, + id: chunk.item.id, + type: chunk.item.type, + result: chunk.item.results, + }; + currentFunctionCalls.push(newFunctionCall); + } + } + // Handle text output streaming (Realtime API) + else if (chunk.type === "response.output_text.delta") { + currentContent += chunk.delta || ""; + } + // Handle OpenRAG backend format + else if (chunk.output_text) { currentContent += chunk.output_text; } else if (chunk.delta) { - // Handle delta - could be string, dict, or have content/text properties if (typeof chunk.delta === "string") { currentContent += chunk.delta; } else if (typeof chunk.delta === "object") {