feat(web): integrate Claude LLM streaming with markdown rendering

- Add Anthropic SDK with DeepSeek-compatible API config
- Streaming tool-use loop in WebSocket chat handler
- GitHub-style markdown rendering with markdown-it
- Tool status indicators and thinking states in chat UI
- Fix Tailwind content path and CSS border utility

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 03:22:15 +08:00
parent d6b18fb7dc
commit 001dbde9f4
15 changed files with 759 additions and 95 deletions

View File

@@ -2,22 +2,75 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import { chatSocket } from '@/lib/websocket';
import type { Message } from '@/types';
interface StreamingMessage {
id: string;
role: 'assistant';
content: string;
created_at: string;
streaming: boolean;
}
export function useChat(conversationId: string | null) {
const [messages, setMessages] = useState<Message[]>([]);
const [connected, setConnected] = useState(false);
const [thinking, setThinking] = useState(false);
const [toolStatus, setToolStatus] = useState<string | null>(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 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);
});
return () => {
chatSocket.disconnect();
};
@@ -42,5 +95,5 @@ export function useChat(conversationId: string | null) {
chatSocket.send('create_conversation', { title, accountId });
}, []);
return { messages, connected, send, createConversation };
return { messages, connected, thinking, toolStatus, send, createConversation };
}