| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631 |
- // dashboard-v4/dashboard/src/hooks/useChatData.ts
- import { useState, useCallback, useMemo, useEffect } from "react";
- import type {
- MessageNode,
- ChatState,
- ChatActions,
- PendingMessage,
- OpenAIMessage,
- TOpenAIRole,
- ParsedChunk,
- ToolCall,
- CreateMessageRequest,
- } from "../types/chat";
- import { useActivePath } from "./useActivePath";
- import { useSessionGroups } from "./useSessionGroups";
- //import { messageApi } from "../services/messageApi";
- import { mockMessageApi as messageApi } from "../services/mockMessageApi";
- import { getModelAdapter } from "../services/modelAdapters";
- import type { IAiModel } from "../api/ai";
- export function useChatData(chatId: string): {
- chatState: ChatState;
- actions: ChatActions;
- } {
- // Mock模式:直接使用mock数据
- const [rawMessages, setRawMessages] = useState<MessageNode[]>([]);
- const [pendingMessages, setPendingMessages] = useState<PendingMessage[]>([]);
- const [isLoading, setIsLoading] = useState(false);
- const [isInitialized, setIsInitialized] = useState(false); // 新增:标记是否已初始化
- const [streamingMessage, setStreamingMessage] = useState<string>();
- const [streamingSessionId, setStreamingSessionId] = useState<string>();
- const [error, setError] = useState<string>();
- const [activePoint, setActivePoint] = useState<string>();
- const [currModel, setCurrModel] = useState<IAiModel>();
- // 合并已保存和待保存的消息用于显示
- const allMessages = useMemo(() => {
- const pending = pendingMessages.flatMap((p) => p.messages);
- return [...rawMessages, ...pending];
- }, [rawMessages, pendingMessages]);
- const activePath = useActivePath(allMessages, activePoint);
- const sessionGroups = useSessionGroups(activePath, allMessages);
- // 加载消息列表
- const loadMessages = useCallback(async () => {
- // 如果 chatId 为空或无效,不执行加载
- /*
- if (!chatId || chatId.trim() === "") {
- return;
- }
- */
- try {
- setIsLoading(true);
- setError(undefined); // 清除之前的错误
- const response = await messageApi.getMessages(chatId);
- setRawMessages(response.data.rows);
- setIsInitialized(true);
- } catch (err) {
- const errorMessage = err instanceof Error ? err.message : "加载消息失败";
- setError(errorMessage);
- console.error("加载消息失败:", err);
- } finally {
- setIsLoading(false);
- }
- }, [chatId]);
- // 当 chatId 变化时自动加载消息
- useEffect(() => {
- // 重置状态
- setIsInitialized(false);
- setRawMessages([]);
- setPendingMessages([]);
- setError(undefined);
- setActivePoint(undefined);
- // 加载新的消息
- loadMessages();
- }, [chatId, loadMessages]);
- // 手动刷新方法(供外部调用)
- const refreshMessages = useCallback(async () => {
- setIsInitialized(false);
- await loadMessages();
- }, [loadMessages]);
- // 构建对话历史(用于AI API调用)
- const buildConversationHistory = useCallback(
- (_baseMessages: MessageNode[], newUserMessage?: MessageNode) => {
- const history: OpenAIMessage[] = activePath.map((m) => ({
- role: m.role,
- content: m.content || "",
- tool_calls: m.tool_calls,
- tool_call_id: m.tool_call_id,
- }));
- if (newUserMessage) {
- history.push({
- role: "user",
- content: newUserMessage.content || "",
- });
- }
- return history;
- },
- [activePath]
- );
- // 保存消息组到数据库
- const saveMessageGroup = useCallback(
- async (tempId: string, messages: MessageNode[]) => {
- try {
- const data: CreateMessageRequest = {
- messages: messages.map((m) => ({
- uid: m.uid,
- parent_id: m.parent_id,
- role: m.role,
- content: m.content,
- session_id: m.session_id,
- model_id: m.model_id,
- tool_calls: m.tool_calls,
- tool_call_id: m.tool_call_id,
- metadata: m.metadata,
- })),
- };
- const savedMessages = await messageApi.createMessages(chatId, data);
- // 更新本地状态:移除pending,添加到已保存消息
- setPendingMessages((prev) => prev.filter((p) => p.temp_id !== tempId));
- setRawMessages((prev) => [...prev, ...savedMessages.data]);
- } catch (err) {
- console.error("保存消息组失败:", err);
- setPendingMessages((prev) =>
- prev.map((p) =>
- p.temp_id === tempId
- ? {
- ...p,
- error: err instanceof Error ? err.message : "保存失败",
- messages: p.messages.map((m) => ({
- ...m,
- save_status: "failed" as const,
- })),
- }
- : p
- )
- );
- }
- },
- [chatId]
- );
- // 一个安全 JSON 解析函数
- function ___safeJsonParse(str: string): any {
- try {
- return JSON.parse(str);
- } catch {
- return str; // 返回原始字符串,避免崩溃
- }
- }
- // 发送消息给AI并处理响应
- const sendMessageToAI = useCallback(
- async (userMessage: MessageNode, pendingGroup: PendingMessage) => {
- try {
- if (!currModel) {
- console.error("no model selected");
- return;
- }
- console.debug("ai chat send message current model", currModel);
- setIsLoading(true);
- setStreamingSessionId(pendingGroup.session_id);
- const conversationHistory = buildConversationHistory(
- rawMessages,
- userMessage
- );
- const adapter = getModelAdapter(currModel);
- // 处理Function Call的循环逻辑
- const currentMessages = conversationHistory;
- let maxIterations = 10;
- const allAiMessages: MessageNode[] = [];
- while (maxIterations-- > 0) {
- // 流式处理AI响应
- let responseContent = "";
- let functionCalls: ToolCall[] = [];
- const metadata: any = {};
- const streamResponse = await adapter.sendMessage(currentMessages, {
- temperature: 0.7,
- max_tokens: 2048,
- });
- // 用于存储流式拼接的 tool_calls
- const toolCallBuffer = new Map<number, ToolCall>();
- // 流式输出处理
- await new Promise((resolve, reject) => {
- const processStream = async () => {
- try {
- for await (const chunk of streamResponse) {
- const parsed: ParsedChunk | null =
- adapter.parseStreamChunk(chunk);
- // 处理内容流
- if (parsed?.content) {
- responseContent += parsed.content;
- setStreamingMessage(responseContent);
- }
- // 处理 tool_calls
- if (parsed?.tool_calls) {
- for (const call of parsed.tool_calls) {
- //console.info("ai chat call", call);
- const existing = toolCallBuffer.get(call.index);
- if (existing) {
- // 拼接 arguments
- existing.function.arguments +=
- call.function.arguments || "";
- //console.debug("ai chat 拼接 arguments", existing);
- } else {
- // 初始化新 tool_call
- //console.debug("ai chat 初始化新 tool_call", call);
- toolCallBuffer.set(call.index, {
- ...call,
- function: {
- ...call.function,
- arguments: call.function.arguments || "",
- },
- });
- }
- }
- console.info(
- "ai chat function call (buffer)",
- toolCallBuffer
- );
- }
- // 如果模型说明调用结束
- if (parsed?.finish_reason === "tool_calls") {
- // 在这里 arguments 已经拼接完整,可以解析
- const toolCalls: ToolCall[] = [];
- toolCallBuffer.forEach((value: ToolCall, _key: number) => {
- toolCalls.push(value);
- });
- console.info("ai chat Final tool calls", toolCalls);
- // TODO: 在这里触发你实际的函数调用逻辑
- functionCalls = toolCalls;
- }
- }
- resolve(undefined);
- } catch (err) {
- reject(err);
- }
- };
- processStream();
- });
- // 创建AI请求消息
- const toolCallsMessage: MessageNode = {
- id: 0,
- uid: `temp_ai_${pendingGroup.temp_id}_${allAiMessages.length}`,
- temp_id: pendingGroup.temp_id,
- chat_id: chatId,
- session_id: pendingGroup.session_id,
- parent_id:
- allAiMessages.length === 0
- ? userMessage.uid
- : allAiMessages[allAiMessages.length - 1].uid,
- role: "assistant",
- content: responseContent,
- model_id: currModel.uid,
- tool_calls: functionCalls.length > 0 ? functionCalls : undefined,
- metadata,
- is_active: true,
- save_status: "pending",
- created_at: new Date().toISOString(),
- updated_at: new Date().toISOString(),
- };
- allAiMessages.push(toolCallsMessage);
- // 如果有function calls,处理它们
- if (functionCalls.length > 0) {
- const toolResults = await Promise.all(
- functionCalls.map((call) => adapter.handleFunctionCall(call))
- );
- //ai相应消息
- const toolMessages: MessageNode[] = functionCalls.map(
- (call, index) => ({
- id: 0,
- uid: `temp_tool_${pendingGroup.temp_id}_${index}`,
- temp_id: pendingGroup.temp_id,
- chat_id: chatId,
- session_id: pendingGroup.session_id,
- parent_id: toolCallsMessage.uid,
- role: "tool" as const,
- content: JSON.stringify(toolResults[index]),
- tool_call_id: call.id,
- is_active: true,
- save_status: "pending" as const,
- created_at: new Date().toISOString(),
- updated_at: new Date().toISOString(),
- })
- );
- allAiMessages.push(...toolMessages);
- console.debug("ai chat allAiMessages", allAiMessages);
- // 更新对话历史,继续循环
- currentMessages.push(
- {
- role: "assistant",
- content: responseContent,
- tool_calls: functionCalls,
- },
- ...toolMessages.map((tm) => ({
- role: "tool" as const,
- content: tm.content || "",
- tool_call_id: tm.tool_call_id,
- }))
- );
- console.debug("ai chat currentMessages", currentMessages);
- continue;
- }
- // 没有function call,结束循环
- break;
- }
- // 更新pending消息组
- setPendingMessages((prev) =>
- prev.map((p) =>
- p.temp_id === pendingGroup.temp_id
- ? { ...p, messages: [...p.messages, ...allAiMessages] }
- : p
- )
- );
- // 保存整个消息组到数据库
- await saveMessageGroup(pendingGroup.temp_id, [
- userMessage,
- ...allAiMessages,
- ]);
- } catch (err) {
- console.error("AI响应失败:", err);
- setPendingMessages((prev) =>
- prev.map((p) =>
- p.temp_id === pendingGroup.temp_id
- ? {
- ...p,
- error: err instanceof Error ? err.message : "未知错误",
- retry_count: p.retry_count + 1,
- }
- : p
- )
- );
- } finally {
- setIsLoading(false);
- setStreamingMessage(undefined);
- setStreamingSessionId(undefined);
- }
- },
- [currModel, buildConversationHistory, rawMessages, saveMessageGroup, chatId]
- );
- // 编辑消息 - 创建新版本
- const editMessage = useCallback(
- async (sessionId: string, content: string, role: TOpenAIRole = "user") => {
- const tempId = `temp_${Date.now()}`;
- try {
- // 找到要编辑的消息的父消息
- let parentId: string | undefined;
- if (sessionId === "new") {
- // 新消息,找到最后一个激活消息作为父消息
- const lastMessage = activePath[activePath.length - 1];
- parentId = lastMessage?.uid;
- } else {
- // 编辑现有session,找到该session的父消息
- const sessionMessages = activePath.filter(
- (m) => m.session_id === sessionId
- );
- const firstMessage = sessionMessages[0];
- parentId = firstMessage?.parent_id;
- }
- const newSessionId =
- sessionId === "new" ? `session_${tempId}` : `session_${tempId}`;
- const maxId = Math.max(...rawMessages.map((msg) => msg.id));
- // 创建新的用户消息
- const newUserMessage: MessageNode = {
- id: maxId + 1,
- uid: `temp_user_${tempId}`,
- temp_id: tempId,
- chat_id: chatId,
- parent_id: parentId,
- session_id: newSessionId,
- role: "user",
- content,
- is_active: true,
- save_status: "pending",
- created_at: new Date().toISOString(),
- updated_at: new Date().toISOString(),
- };
- // 创建待保存消息组
- const pendingGroup: PendingMessage = {
- temp_id: tempId,
- session_id: newSessionId,
- messages: [newUserMessage],
- retry_count: 0,
- created_at: new Date().toISOString(),
- };
- setPendingMessages((prev) => [...prev, pendingGroup]);
- console.debug("ai chat", pendingGroup);
- // 如果是用户消息,发送给AI
- if (role === "user") {
- await sendMessageToAI(newUserMessage, pendingGroup);
- }
- } catch (err) {
- console.error("编辑消息失败:", err);
- setPendingMessages((prev) =>
- prev.map((p) =>
- p.temp_id === tempId
- ? {
- ...p,
- messages: p.messages.map((m) => ({
- ...m,
- save_status: "failed" as const,
- })),
- error: err instanceof Error ? err.message : "编辑失败",
- }
- : p
- )
- );
- }
- },
- [rawMessages, chatId, activePath, sendMessageToAI]
- );
- // 重试失败的消息
- const retryMessage = useCallback(
- async (tempId: string) => {
- const pendingGroup = pendingMessages.find((p) => p.temp_id === tempId);
- if (!pendingGroup) return;
- const userMessage = pendingGroup.messages.find((m) => m.role === "user");
- if (!userMessage) return;
- // 重置状态并重试
- setPendingMessages((prev) =>
- prev.map((p) =>
- p.temp_id === tempId
- ? {
- ...p,
- messages: [{ ...userMessage, save_status: "pending" }],
- error: undefined,
- }
- : p
- )
- );
- await sendMessageToAI(userMessage, {
- ...pendingGroup,
- messages: [userMessage],
- });
- },
- [pendingMessages, sendMessageToAI]
- );
- // 切换版本
- const switchVersion = useCallback((activeMsgId: string) => {
- console.debug("activeMsgId", activeMsgId);
- setActivePoint(activeMsgId);
- }, []);
- // 刷新AI回答
- const refreshResponse = useCallback(
- async (sessionId: string, _modelId?: string) => {
- const session = sessionGroups.find((sg) => sg.session_id === sessionId);
- if (!session?.user_message) return;
- const tempId = `temp_refresh_${Date.now()}`;
- try {
- // 创建基于原用户消息的新AI回答
- const userMsg = session.user_message;
- const newSessionId = `session_${tempId}`;
- const pendingGroup: PendingMessage = {
- temp_id: tempId,
- session_id: newSessionId,
- messages: [
- {
- ...userMsg,
- temp_id: tempId,
- session_id: newSessionId,
- save_status: "pending",
- },
- ],
- retry_count: 0,
- created_at: new Date().toISOString(),
- };
- setPendingMessages((prev) => [...prev, pendingGroup]);
- // 发送给AI获取新回答
- await sendMessageToAI(pendingGroup.messages[0], pendingGroup);
- } catch (err) {
- console.error("刷新回答失败:", err);
- setError(err instanceof Error ? err.message : "刷新失败");
- }
- },
- [sessionGroups, sendMessageToAI]
- );
- // 消息操作功能
- const likeMessage = useCallback(async (messageId: string) => {
- try {
- await messageApi.likeMessage(messageId);
- // 可以添加本地状态更新
- } catch (err) {
- console.error("点赞失败:", err);
- }
- }, []);
- const dislikeMessage = useCallback(async (messageId: string) => {
- try {
- await messageApi.dislikeMessage(messageId);
- // 可以添加本地状态更新
- } catch (err) {
- console.error("点踩失败:", err);
- }
- }, []);
- const copyMessage = useCallback(
- (messageId: string) => {
- const message = allMessages.find((m) => m.uid === messageId);
- if (message?.content) {
- navigator.clipboard.writeText(message.content);
- }
- },
- [allMessages]
- );
- const shareMessage = useCallback(
- async (messageId: string): Promise<string> => {
- try {
- const response = await messageApi.shareMessage(messageId);
- return response.data.shareUrl;
- } catch (err) {
- console.error("分享失败:", err);
- throw err;
- }
- },
- []
- );
- const deleteMessage = useCallback(
- async (messageId: string) => {
- try {
- await messageApi.deleteMessage(messageId);
- await refreshMessages(); // 使用 refreshMessages 而不是 loadMessages
- } catch (err) {
- console.error("删除失败:", err);
- setError(err instanceof Error ? err.message : "删除失败");
- }
- },
- [refreshMessages]
- );
- const setModel = useCallback((model: IAiModel | undefined) => {
- setCurrModel(model);
- }, []);
- const actions = useMemo(
- () => ({
- switchVersion,
- editMessage,
- retryMessage,
- refreshResponse,
- loadMessages: refreshMessages, // 对外暴露的是 refreshMessages
- likeMessage,
- dislikeMessage,
- copyMessage,
- shareMessage,
- deleteMessage,
- setModel,
- }),
- [
- switchVersion,
- editMessage,
- retryMessage,
- refreshResponse,
- refreshMessages,
- likeMessage,
- dislikeMessage,
- copyMessage,
- shareMessage,
- deleteMessage,
- setModel,
- ]
- );
- return {
- chatState: {
- chat_id: chatId,
- title: "", // 可以从props传入或另行管理
- raw_messages: rawMessages,
- active_path: activePath,
- session_groups: sessionGroups,
- pending_messages: pendingMessages,
- is_loading: isLoading,
- is_initialized: isInitialized, // 新增:初始化状态
- streaming_message: streamingMessage,
- streaming_session_id: streamingSessionId,
- current_model: currModel,
- error,
- },
- actions,
- };
- }
|