import { useState, useEffect, useCallback, useRef } from 'react'; import { chatSocket } from '@/lib/websocket'; import type { Message } from '@/types'; interface PipelineState { phase: string; progress: number; currentItem?: number; totalItems?: number; status?: string; } export function useChat(conversationId: string | null) { const [messages, setMessages] = useState([]); const [connected, setConnected] = useState(false); const [thinking, setThinking] = useState(false); const [toolStatus, setToolStatus] = useState(null); const [pipeline, setPipeline] = useState(null); const pendingRef = useRef(false); useEffect(() => { chatSocket.connect(); chatSocket.on('connected', () => setConnected(true)); chatSocket.on('history', (data) => { setMessages((data.messages as Message[]) || []); }); chatSocket.on('message', (data) => { setMessages((prev) => [...prev, data as unknown as Message]); }); // Streaming chatSocket.on('status', (data) => { if (data.status === 'thinking') setThinking(true); if (data.status === 'done') { setThinking(false); setToolStatus(null); } }); chatSocket.on('message_start', (data) => { setThinking(false); setMessages((prev) => [...prev, { id: data.id as string, role: 'assistant', content: '', created_at: new Date().toISOString(), conversation_id: '', } as Message]); }); chatSocket.on('text_delta', (data) => { setMessages((prev) => prev.map((m) => m.id === data.id ? { ...m, content: m.content + (data.text as string) } : m )); }); chatSocket.on('message_end', () => { setThinking(false); }); // Tools chatSocket.on('tool_start', (data) => { setToolStatus(`执行: ${data.tool}...`); }); chatSocket.on('tool_result', (data) => { setToolStatus(null); setThinking(true); // Save tool result as a tool-type message for inline display setMessages((prev) => [...prev, { id: `tool-${Date.now()}`, role: 'tool' as const, content: JSON.stringify({ tool: data.tool, result: data.result }), created_at: new Date().toISOString(), conversation_id: '', }]); }); chatSocket.on('tool_error', (data) => { setToolStatus(`失败: ${data.tool}`); setTimeout(() => setToolStatus(null), 4000); }); // Pipeline progress chatSocket.on('pipeline_progress', (data) => { setPipeline({ phase: data.phase as string, progress: data.progress as number, currentItem: data.currentItem as number, totalItems: data.totalItems as number, status: data.status as string, }); }); return () => { chatSocket.disconnect(); }; }, []); useEffect(() => { if (conversationId && connected && !pendingRef.current) { pendingRef.current = true; chatSocket.send('init', { conversationId }); } if (!conversationId) { pendingRef.current = false; setMessages([]); } }, [conversationId, connected]); const send = useCallback((content: string) => { chatSocket.send('chat', { content }); }, []); const stop = useCallback(() => { chatSocket.stop(); }, []); const createConversation = useCallback((title: string, accountId?: string) => { chatSocket.send('create_conversation', { title, accountId }); }, []); return { messages, connected, thinking, toolStatus, pipeline, send, stop, createConversation }; }