changed useChatStreaming to work with the chat page
This commit is contained in:
parent
f3911afe92
commit
d336baba32
1 changed files with 293 additions and 8 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import type { FunctionCall, Message } from "@/app/chat/types";
|
import type { FunctionCall, Message, SelectedFilters } from "@/app/chat/types";
|
||||||
|
|
||||||
interface UseChatStreamingOptions {
|
interface UseChatStreamingOptions {
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
|
|
@ -7,6 +7,14 @@ interface UseChatStreamingOptions {
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SendMessageOptions {
|
||||||
|
prompt: string;
|
||||||
|
previousResponseId?: string;
|
||||||
|
filters?: SelectedFilters;
|
||||||
|
limit?: number;
|
||||||
|
scoreThreshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function useChatStreaming({
|
export function useChatStreaming({
|
||||||
endpoint = "/api/chat",
|
endpoint = "/api/chat",
|
||||||
onComplete,
|
onComplete,
|
||||||
|
|
@ -19,7 +27,13 @@ export function useChatStreaming({
|
||||||
const streamAbortRef = useRef<AbortController | null>(null);
|
const streamAbortRef = useRef<AbortController | null>(null);
|
||||||
const streamIdRef = useRef(0);
|
const streamIdRef = useRef(0);
|
||||||
|
|
||||||
const sendMessage = async (prompt: string, previousResponseId?: string) => {
|
const sendMessage = async ({
|
||||||
|
prompt,
|
||||||
|
previousResponseId,
|
||||||
|
filters,
|
||||||
|
limit = 10,
|
||||||
|
scoreThreshold = 0,
|
||||||
|
}: SendMessageOptions) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
|
@ -36,19 +50,28 @@ export function useChatStreaming({
|
||||||
prompt: string;
|
prompt: string;
|
||||||
stream: boolean;
|
stream: boolean;
|
||||||
previous_response_id?: string;
|
previous_response_id?: string;
|
||||||
|
filters?: SelectedFilters;
|
||||||
|
limit?: number;
|
||||||
|
scoreThreshold?: number;
|
||||||
} = {
|
} = {
|
||||||
prompt,
|
prompt,
|
||||||
stream: true,
|
stream: true,
|
||||||
|
limit,
|
||||||
|
scoreThreshold,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (previousResponseId) {
|
if (previousResponseId) {
|
||||||
requestBody.previous_response_id = previousResponseId;
|
requestBody.previous_response_id = previousResponseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filters) {
|
||||||
|
requestBody.filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/event-stream",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
|
|
@ -104,13 +127,275 @@ export function useChatStreaming({
|
||||||
newResponseId = chunk.response_id;
|
newResponseId = chunk.response_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle OpenRAG backend format (from agent.py async_response_stream)
|
// Handle OpenAI Chat Completions streaming format
|
||||||
// The chunk is serialized via chunk.model_dump() and contains output_text or delta
|
if (chunk.object === "response.chunk" && chunk.delta) {
|
||||||
if (chunk.output_text) {
|
// Handle function calls in delta
|
||||||
// Direct output text from chunk
|
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;
|
currentContent += chunk.output_text;
|
||||||
} else if (chunk.delta) {
|
} else if (chunk.delta) {
|
||||||
// Handle delta - could be string, dict, or have content/text properties
|
|
||||||
if (typeof chunk.delta === "string") {
|
if (typeof chunk.delta === "string") {
|
||||||
currentContent += chunk.delta;
|
currentContent += chunk.delta;
|
||||||
} else if (typeof chunk.delta === "object") {
|
} else if (typeof chunk.delta === "object") {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue