// src/views/AgentChat/hooks/useAgentChat.ts // 消息处理 Hook - 发送消息、处理响应、错误处理 import { useState, useCallback } from 'react'; import type { Dispatch, SetStateAction, KeyboardEvent } from 'react'; import axios from 'axios'; import { MessageTypes, type Message } from '../constants/messageTypes'; import type { UploadedFile } from './useFileUpload'; import type { User } from './useAgentSessions'; /** * Toast 通知函数类型(来自 Chakra UI) */ export interface ToastFunction { (options: { title: string; description?: string; status: 'success' | 'error' | 'warning' | 'info'; duration?: number; isClosable?: boolean; }): void; } /** * useAgentChat Hook 参数 */ export interface UseAgentChatParams { /** 当前用户信息 */ user: User | null; /** 当前会话 ID */ currentSessionId: string | null; /** 设置当前会话 ID */ setCurrentSessionId: Dispatch>; /** 选中的 AI 模型 */ selectedModel: string; /** 选中的工具列表 */ selectedTools: string[]; /** 已上传文件列表 */ uploadedFiles: UploadedFile[]; /** 清空已上传文件 */ clearFiles: () => void; /** Toast 通知函数 */ toast: ToastFunction; /** 重新加载会话列表(发送消息成功后调用) */ loadSessions: () => Promise; } /** * useAgentChat Hook 返回值 */ export interface UseAgentChatReturn { /** 消息列表 */ messages: Message[]; /** 设置消息列表 */ setMessages: Dispatch>; /** 输入框内容 */ inputValue: string; /** 设置输入框内容 */ setInputValue: Dispatch>; /** 是否正在处理消息 */ isProcessing: boolean; /** 发送消息 */ handleSendMessage: () => Promise; /** 键盘事件处理(Enter 发送) */ handleKeyPress: (e: KeyboardEvent) => void; /** 添加消息到列表 */ addMessage: (message: Partial) => void; } /** * useAgentChat Hook * * 处理消息发送、AI 响应、错误处理逻辑 * * @param params - UseAgentChatParams * @returns UseAgentChatReturn * * @example * ```tsx * const { * messages, * inputValue, * setInputValue, * isProcessing, * handleSendMessage, * handleKeyPress, * } = useAgentChat({ * user, * currentSessionId, * setCurrentSessionId, * selectedModel, * selectedTools, * uploadedFiles, * clearFiles, * toast, * loadSessions, * }); * ``` */ export const useAgentChat = ({ user, currentSessionId, setCurrentSessionId, selectedModel, selectedTools, uploadedFiles, clearFiles, toast, loadSessions, }: UseAgentChatParams): UseAgentChatReturn => { const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(''); const [isProcessing, setIsProcessing] = useState(false); /** * 添加消息到列表 */ const addMessage = useCallback((message: Partial) => { setMessages((prev) => [ ...prev, { id: Date.now() + Math.random(), timestamp: new Date().toISOString(), ...message, } as Message, ]); }, []); /** * 发送消息到后端 API */ const handleSendMessage = useCallback(async () => { if (!inputValue.trim() || isProcessing) return; // 创建用户消息 const userMessage: Partial = { type: MessageTypes.USER, content: inputValue, timestamp: new Date().toISOString(), files: uploadedFiles.length > 0 ? uploadedFiles : undefined, }; addMessage(userMessage); const userInput = inputValue; // 清空输入框和文件 setInputValue(''); clearFiles(); setIsProcessing(true); try { // 显示 "思考中" 状态 addMessage({ type: MessageTypes.AGENT_THINKING, content: '正在分析你的问题...', }); // 调用后端 API const response = await axios.post('/mcp/agent/chat', { message: userInput, conversation_history: messages .filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE) .map((m) => ({ isUser: m.type === MessageTypes.USER, content: m.content, })), user_id: user?.id ? String(user.id) : 'anonymous', user_nickname: user?.nickname || '匿名用户', user_avatar: user?.avatar || '', subscription_type: user?.subscription_type || 'free', session_id: currentSessionId, model: selectedModel, tools: selectedTools, files: uploadedFiles.length > 0 ? uploadedFiles : undefined, }); // 移除 "思考中" 消息 setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING)); if (response.data.success) { const data = response.data; // 更新会话 ID(如果是新会话) if (data.session_id && !currentSessionId) { setCurrentSessionId(data.session_id); } // 显示执行计划(如果有) if (data.plan) { addMessage({ type: MessageTypes.AGENT_PLAN, content: '已制定执行计划', plan: data.plan, }); } // 显示执行步骤(如果有) if (data.steps && data.steps.length > 0) { addMessage({ type: MessageTypes.AGENT_EXECUTING, content: '正在执行步骤...', plan: data.plan, stepResults: data.steps, }); } // 移除 "执行中" 消息 setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING)); // 显示最终回复 addMessage({ type: MessageTypes.AGENT_RESPONSE, content: data.final_answer || data.message || '处理完成', plan: data.plan, stepResults: data.steps, metadata: data.metadata, }); // 重新加载会话列表 loadSessions(); } } catch (error: any) { console.error('Agent chat error:', error); // 移除 "思考中" 和 "执行中" 消息 setMessages((prev) => prev.filter( (m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING ) ); // 显示错误消息 const errorMessage = error.response?.data?.error || error.message || '处理失败'; addMessage({ type: MessageTypes.ERROR, content: `处理失败:${errorMessage}`, }); // 显示 Toast 通知 toast({ title: '处理失败', description: errorMessage, status: 'error', duration: 5000, }); } finally { setIsProcessing(false); } }, [ inputValue, isProcessing, uploadedFiles, messages, user, currentSessionId, selectedModel, selectedTools, addMessage, clearFiles, setCurrentSessionId, loadSessions, toast, ]); /** * 键盘事件处理(Enter 发送,Shift+Enter 换行) */ const handleKeyPress = useCallback( (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }, [handleSendMessage] ); return { messages, setMessages, inputValue, setInputValue, isProcessing, handleSendMessage, handleKeyPress, addMessage, }; };