2026-05-07 02:41:01 +08:00
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
|
|
import { chatSocket } from '@/lib/websocket';
|
|
|
|
|
import type { Message } from '@/types';
|
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
interface StreamingMessage {
|
|
|
|
|
id: string;
|
|
|
|
|
role: 'assistant';
|
|
|
|
|
content: string;
|
|
|
|
|
created_at: string;
|
|
|
|
|
streaming: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 02:41:01 +08:00
|
|
|
export function useChat(conversationId: string | null) {
|
|
|
|
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
|
|
|
const [connected, setConnected] = useState(false);
|
2026-05-07 03:22:15 +08:00
|
|
|
const [thinking, setThinking] = useState(false);
|
|
|
|
|
const [toolStatus, setToolStatus] = useState<string | null>(null);
|
2026-05-07 02:41:01 +08:00
|
|
|
const pendingRef = useRef(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
chatSocket.connect();
|
|
|
|
|
|
|
|
|
|
chatSocket.on('connected', () => setConnected(true));
|
2026-05-07 03:22:15 +08:00
|
|
|
|
2026-05-07 02:41:01 +08:00
|
|
|
chatSocket.on('history', (data) => {
|
|
|
|
|
setMessages((data.messages as Message[]) || []);
|
|
|
|
|
});
|
2026-05-07 03:22:15 +08:00
|
|
|
|
2026-05-07 02:41:01 +08:00
|
|
|
chatSocket.on('message', (data) => {
|
|
|
|
|
setMessages((prev) => [...prev, data as unknown as Message]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
// Streaming handlers
|
|
|
|
|
chatSocket.on('status', (data) => {
|
|
|
|
|
if (data.status === 'thinking') setThinking(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
chatSocket.on('message_start', (data) => {
|
|
|
|
|
setThinking(false);
|
|
|
|
|
// Add placeholder for streaming
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
chatSocket.on('tool_start', (data) => {
|
|
|
|
|
setToolStatus(`正在执行: ${data.tool}...`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
chatSocket.on('tool_result', () => {
|
|
|
|
|
setToolStatus(null);
|
|
|
|
|
setThinking(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
chatSocket.on('tool_error', (data) => {
|
|
|
|
|
setToolStatus(`工具执行失败: ${data.tool}`);
|
|
|
|
|
setTimeout(() => setToolStatus(null), 3000);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-07 02:41:01 +08:00
|
|
|
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 createConversation = useCallback((title: string, accountId?: string) => {
|
|
|
|
|
chatSocket.send('create_conversation', { title, accountId });
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
return { messages, connected, thinking, toolStatus, send, createConversation };
|
2026-05-07 02:41:01 +08:00
|
|
|
}
|