From 2fe1c55997a1505003b2e2c5965aa8cbc9bbfb00 Mon Sep 17 00:00:00 2001 From: kurihada Date: Fri, 12 Dec 2025 14:27:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E4=BF=AE=E5=A4=8D=20useChat=20?= =?UTF-8?q?=E6=97=A0=E9=99=90=E8=AF=B7=E6=B1=82=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用 useRef 存储回调函数,避免依赖变化导致无限循环 - 添加 onSessionNotFound 回调,会话不存在时自动创建新会话 - 限制 WebSocket 重连次数为 5 次 - 添加 web:dev 快捷脚本 --- package.json | 1 + packages/web/src/App.tsx | 14 +++++++++-- packages/web/src/hooks/useChat.ts | 39 +++++++++++++++++++++++-------- packages/web/src/pages/Chat.tsx | 4 +++- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index dbab64b..840d111 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "clean": "pnpm -r exec rm -rf dist node_modules", "server:start": "pnpm --filter @ai-assistant/server start", "server:dev": "pnpm --filter @ai-assistant/server start:dev", + "web:dev": "pnpm --filter @ai-assistant/web dev", "desktop:dev": "pnpm --filter @ai-assistant/desktop dev", "desktop:build": "pnpm --filter @ai-assistant/desktop build" }, diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 935b0c3..d6284e3 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -4,7 +4,7 @@ * 响应式布局:支持桌面端和移动端 */ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { Sidebar } from './components/Sidebar'; import { ChatPage } from './pages/Chat'; import { FileBrowser } from './components/FileBrowser'; @@ -49,6 +49,16 @@ export function App() { setCurrentSessionId(session.id); }; + // 会话不存在时自动创建新会话 + const handleSessionNotFound = useCallback(async () => { + try { + const { data: newSession } = await createSession(); + setCurrentSessionId(newSession.id); + } catch (error) { + console.error('Failed to create new session:', error); + } + }, []); + if (isInitializing) { return (
@@ -116,7 +126,7 @@ export function App() { {/* 聊天区域 */}
{currentSessionId ? ( - + ) : (

Select or create a session

diff --git a/packages/web/src/hooks/useChat.ts b/packages/web/src/hooks/useChat.ts index 52a339f..548116e 100644 --- a/packages/web/src/hooks/useChat.ts +++ b/packages/web/src/hooks/useChat.ts @@ -10,6 +10,7 @@ import { createWebSocket, getMessages, type Message } from '../api/client'; interface UseChatOptions { sessionId: string; onError?: (error: Error) => void; + onSessionNotFound?: () => void; } interface ChatState { @@ -19,7 +20,7 @@ interface ChatState { streamingContent: string; } -export function useChat({ sessionId, onError }: UseChatOptions) { +export function useChat({ sessionId, onError, onSessionNotFound }: UseChatOptions) { const [state, setState] = useState({ messages: [], isConnected: false, @@ -29,6 +30,14 @@ export function useChat({ sessionId, onError }: UseChatOptions) { const wsRef = useRef(null); const reconnectTimeoutRef = useRef(); + const reconnectAttemptsRef = useRef(0); + const maxReconnectAttempts = 5; + + // 用 ref 存储回调,避免依赖变化导致无限循环 + const onErrorRef = useRef(onError); + const onSessionNotFoundRef = useRef(onSessionNotFound); + onErrorRef.current = onError; + onSessionNotFoundRef.current = onSessionNotFound; // 加载历史消息 const loadMessages = useCallback(async () => { @@ -36,9 +45,15 @@ export function useChat({ sessionId, onError }: UseChatOptions) { const { data } = await getMessages(sessionId); setState((prev) => ({ ...prev, messages: data })); } catch (error) { - onError?.(error instanceof Error ? error : new Error('Failed to load messages')); + // 会话不存在(404 或 "Session not found"),通知上层重新创建 + const msg = error instanceof Error ? error.message : ''; + if (msg.includes('404') || msg.toLowerCase().includes('not found')) { + onSessionNotFoundRef.current?.(); + return; + } + onErrorRef.current?.(error instanceof Error ? error : new Error('Failed to load messages')); } - }, [sessionId, onError]); + }, [sessionId]); // 连接 WebSocket const connect = useCallback(() => { @@ -47,17 +62,21 @@ export function useChat({ sessionId, onError }: UseChatOptions) { const ws = createWebSocket(sessionId); ws.onopen = () => { + reconnectAttemptsRef.current = 0; // 连接成功,重置重连次数 setState((prev) => ({ ...prev, isConnected: true })); }; ws.onclose = () => { setState((prev) => ({ ...prev, isConnected: false })); - // 自动重连 - reconnectTimeoutRef.current = setTimeout(connect, 3000); + // 限制重连次数 + if (reconnectAttemptsRef.current < maxReconnectAttempts) { + reconnectAttemptsRef.current++; + reconnectTimeoutRef.current = setTimeout(connect, 3000); + } }; ws.onerror = () => { - onError?.(new Error('WebSocket connection error')); + onErrorRef.current?.(new Error('WebSocket connection error')); }; ws.onmessage = (event) => { @@ -98,7 +117,7 @@ export function useChat({ sessionId, onError }: UseChatOptions) { break; case 'error': - onError?.(new Error(message.payload?.message || 'Unknown error')); + onErrorRef.current?.(new Error(message.payload?.message || 'Unknown error')); setState((prev) => ({ ...prev, isLoading: false, streamingContent: '' })); break; } @@ -108,13 +127,13 @@ export function useChat({ sessionId, onError }: UseChatOptions) { }; wsRef.current = ws; - }, [sessionId, onError]); + }, [sessionId]); // 发送消息 const sendMessage = useCallback( (content: string) => { if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { - onError?.(new Error('WebSocket not connected')); + onErrorRef.current?.(new Error('WebSocket not connected')); return; } @@ -128,7 +147,7 @@ export function useChat({ sessionId, onError }: UseChatOptions) { }) ); }, - [sessionId, onError] + [sessionId] ); // 取消处理 diff --git a/packages/web/src/pages/Chat.tsx b/packages/web/src/pages/Chat.tsx index 14c18e5..b13524c 100644 --- a/packages/web/src/pages/Chat.tsx +++ b/packages/web/src/pages/Chat.tsx @@ -10,9 +10,10 @@ import { ChatInput } from '../components/ChatInput'; interface ChatPageProps { sessionId: string; + onSessionNotFound?: () => void; } -export function ChatPage({ sessionId }: ChatPageProps) { +export function ChatPage({ sessionId, onSessionNotFound }: ChatPageProps) { const { messages, isConnected, @@ -25,6 +26,7 @@ export function ChatPage({ sessionId }: ChatPageProps) { onError: (error) => { console.error('Chat error:', error); }, + onSessionNotFound, }); const messagesEndRef = useRef(null);