hts/apps/migrant/lib/hooks/use-isdk_b.ts

439 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Dispatch, MutableRefObject, SetStateAction, useCallback, useEffect, useId, useRef, useState } from 'react';
import useSWR, { KeyedMutator } from 'swr';
export interface FunctionCall {
/**
* The arguments to call the function with, as generated by the model in JSON
* format. Note that the model does not always generate valid JSON, and may
* hallucinate parameters not defined by your function schema. Validate the
* arguments in your code before calling your function.
*/
arguments?: string;
/**
* The name of the function to call.
*/
name?: string;
}
/**
* The tool calls generated by the model, such as function calls.
*/
export interface ToolCall {
// The ID of the tool call.
id: string;
// The type of the tool. Currently, only `function` is supported.
type: string;
// The function that the model called.
function: {
// The name of the function.
name: string;
// The arguments to call the function with, as generated by the model in JSON
arguments: string;
};
}
export type JSONValue =
| null
| string
| number
| boolean
| { [x: string]: JSONValue }
| Array<JSONValue>;
/**
* Shared types between the API and UI packages.
*/
export interface Message {
id?: string;
tool_call_id?: string;
createdAt?: Date;
content: string;
ui?: string | JSX.Element | JSX.Element[] | null | undefined;
// role: 'system' | 'user' | 'assistant' | 'function' | 'data' | 'tool';
/**
* If the message has a role of `function`, the `name` field is the name of the function.
* Otherwise, the name field should not be set.
*/
name?: string;
/**
* If the assistant role makes a function call, the `function_call` field
* contains the function call name and arguments. Otherwise, the field should
* not be set. (Deprecated and replaced by tool_calls.)
*/
function_call?: string | FunctionCall;
data?: JSONValue;
/**
* If the assistant role makes a tool call, the `tool_calls` field contains
* the tool call name and arguments. Otherwise, the field should not be set.
*/
tool_calls?: string | ToolCall[];
/**
* Additional message-specific information added on the server via StreamData
*/
annotations?: JSONValue[] | undefined;
}
interface Error {
name: string;
message: string;
stack?: string;
}
interface WebSocketMessage {
messageId?: string;
conversationId?: string;
type: string;
content: string;
streamStatus: "start" | "end" | "middle";
}
// type WebSocketData = WebSocketMessage;
interface WebSocketOptions {
id?: string;
initialMessages?: WebSocketMessage[];
initialInput?: string;
onResponse?: (response: Response) => void | Promise<void>;
onFinish?: (message: WebSocketMessage[] | string[]) => void;
onError?: (error: Error | any) => void;
}
export type WebSocketHook = {
messages: WebSocketMessage[];
isLoading: boolean;
input: string;
error: undefined | Error;
setMessages: (messages: WebSocketMessage[]) => void;
setInput: React.Dispatch<React.SetStateAction<string>>;
// sendMessage: (message: string) => void;
// append: (message: string) => void;
append: (
message: Message,
// chatRequestOptions?: ChatRequestOptions,
) => Promise<string | null | undefined | void>;
}
const getStreamedResponse = async (
event: MessageEvent,
setMessages: (messages: WebSocketMessage[]) => void,
mutate: KeyedMutator<WebSocketMessage[]>,
messagesRef: MutableRefObject<WebSocketMessage[]>,
mutateLoading: KeyedMutator<boolean>,
setInput: Dispatch<SetStateAction<string>>
) => {
const receivedMessage = event.data;
let parsedMessage = receivedMessage;
try {
parsedMessage = JSON.parse(receivedMessage);
} catch (error) {
// console.error("解析接收到的消息时出错:", error);
}
let messageBuffer: WebSocketMessage = {
messageId: '',
conversationId: '',
type: '',
content: "",
streamStatus: 'middle'
};
switch (parsedMessage.streamStatus) {
case "start":
mutateLoading(true);
console.log("开始接收信息", parsedMessage);
messageBuffer.type = parsedMessage.type
// setMessages([...messagesRef.current, messageBuffer])
messagesRef.current = [...messagesRef.current, messageBuffer]
break;
case "end":
console.log("结束接收信息", parsedMessage);
setInput("")
mutateLoading(false);
break;
default:
const lastMessage = messagesRef.current[messagesRef.current.length - 1];
console.log("中间接收信息进行消息输出拼接", parsedMessage, lastMessage);
lastMessage.content = lastMessage.content + parsedMessage
// setMessages([...messagesRef.current.slice(0, -1), lastMessage])
// // 更新最后一条消息的内容
// const lastMessageIndex = messagesRef.current.length - 1;
// const lastMessage = messagesRef.current[lastMessageIndex];
// lastMessage.content += parsedMessage;
// 更新 messagesRef.current并触发 UI 重新渲染
messagesRef.current = [
...messagesRef.current.slice(0, -1),
lastMessage
];
break;
}
};
const useISDK_B = (url: string, options: WebSocketOptions): WebSocketHook => {
const websocketOptions = {
headers: {
'Sec-WebSocket-Version': '13',
// 添加其他自定义请求头
'Custom-Header': 'value',
}
};
const socketRef = useRef<WebSocket | null>()
const { onResponse, onFinish, onError } = options || {};
const [input, setInput] = useState<string>(options?.initialInput || '');
const [initialMessagesFallback] = useState([]);
// let messages = options?.initialMessages ?? initialMessagesFallback
const hookId = useId();
const idKey = options?.id ?? hookId;
const chatKey = typeof url === 'string' ? [url, idKey] : idKey;
const { data: messages, mutate } = useSWR<WebSocketMessage[]>(
[chatKey, 'messages'],
null,
{ fallbackData: (options?.initialMessages ?? initialMessagesFallback) || [] },
);
const { data: isLoading = false, mutate: mutateLoading } = useSWR<boolean>(
[chatKey, 'loading'],
null,
);
const { data: error = undefined, mutate: setError } = useSWR<
undefined | Error
>([chatKey, 'error'], null);
// Keep the latest messages in a ref.
const messagesRef = useRef<WebSocketMessage[]>(messages || []);
useEffect(() => {
// messagesRef.current = messages || [];
mutate(messagesRef.current)
}, [messagesRef]);
const setMessages = useCallback(
(newMessages: WebSocketMessage[]) => {
// messagesRef.current = newMessages || [];
mutate(newMessages, false);
console.log("--setMessages---", messagesRef.current);
},
[mutate]
);
const setupWebSocket = useCallback((newURL: string) => {
// if (socketRef.current) {
// socketRef.current.close(); // 关闭旧连接
// }
// const webSocket = new WebSocket(newURL)
const socket = new WebSocket(newURL, []);
if (socketRef.current) return;
// setWs(socket);
socket.onopen = () => {
console.log('WebSocket 连接已建立');
if (options?.initialInput !== undefined) {
// console.log("---useWebSocket------", options)
// ws.send(options.initialInput as string); // 将 undefined 转换为 string
setInput("")
append({
id: '',
content: options.initialInput,
})
}
};
socket.onclose = () => {
console.log('WebSocket 连接已关闭');
// if (onFinish && !!messages) {
// onFinish(messages);
// }
if (socketRef?.current) {
socketRef.current.close()
setupWebSocket(newURL)
}
};
socket.onerror = (error) => {
console.error('WebSocket 连接发生错误:', error);
if (onError) {
onError(error);
}
mutateLoading(false);// 发生错误后,设置 isLoading 为 false
};
socketRef.current = socket
}, [
socketRef.current,
// input,
mutate,
messagesRef,
onError,
],
)
useEffect(() => {
const socket = socketRef.current
if (url && socket && socket?.url !== url) {
socket.close()
setupWebSocket(url)
} else if (url && !socket) {
setupWebSocket(url)
}
}, [socketRef.current, onResponse, onFinish, onError])
// 发送消息
const triggerRequest = useCallback(async (chatRequest: Message) => {
console.log("-----triggerRequest--------", chatRequest)
try {
if (socketRef?.current && socketRef?.current.readyState === WebSocket.OPEN) {
mutateLoading(true); // 发送消息时,设置 isLoading 为 true
socketRef.current.send(chatRequest.content);
} else {
console.error('WebSocket 连接未建立或已关闭');
}
} catch (error) {
} finally {
mutateLoading(false);
}
}, [
socketRef.current,
messagesRef,
// input,
mutate,
mutateLoading,
onResponse,
onFinish,
onError,
]);
// 追加消息并发送
const append = useCallback(
async (
message: Message,
) => {
let chatRequest = message
// setMessages([...messagesRef.current, {
// // messageId: message.id,
// conversationId: '',
// type: 'text',
// content: message.content,
// streamStatus: 'middle'
// }])
messagesRef.current = [...messagesRef.current, {
// messageId: message.id,
conversationId: '',
type: 'text',
content: message.content,
streamStatus: 'middle'
}]
return triggerRequest(chatRequest);
},
[triggerRequest],
);
// 在依赖项数组中使用函数的正确方式
useEffect(() => {
if (socketRef.current) {
// socketRef.current.onmessage = handleWebSocketMessage;
socketRef.current.onmessage = (event) => getStreamedResponse(
event,
setMessages,
mutate,
messagesRef,
mutateLoading,
setInput,
);
}
return () => {
if (socketRef.current) {
socketRef.current.onmessage = null; // 清除事件处理程序以防止内存泄漏
}
};
}, [
socketRef.current,
mutate,
mutateLoading,
messagesRef,
onResponse,
onFinish,
onError,
]);
return {
messages: messagesRef.current || [],
isLoading,
error,
append,
setMessages,
input,
setInput,
};
};
export default useISDK_B;