fix(web): 修复 useChat 无限请求问题
- 用 useRef 存储回调函数,避免依赖变化导致无限循环 - 添加 onSessionNotFound 回调,会话不存在时自动创建新会话 - 限制 WebSocket 重连次数为 5 次 - 添加 web:dev 快捷脚本
This commit is contained in:
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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 (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-900">
|
||||
@@ -116,7 +126,7 @@ export function App() {
|
||||
{/* 聊天区域 */}
|
||||
<div className={`flex-1 min-w-0 ${showFileBrowser ? 'hidden md:block md:w-1/2' : 'w-full'}`}>
|
||||
{currentSessionId ? (
|
||||
<ChatPage key={currentSessionId} sessionId={currentSessionId} />
|
||||
<ChatPage key={currentSessionId} sessionId={currentSessionId} onSessionNotFound={handleSessionNotFound} />
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center h-full">
|
||||
<p className="text-gray-400">Select or create a session</p>
|
||||
|
||||
@@ -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<ChatState>({
|
||||
messages: [],
|
||||
isConnected: false,
|
||||
@@ -29,6 +30,14 @@ export function useChat({ sessionId, onError }: UseChatOptions) {
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
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;
|
||||
}
|
||||
}, [sessionId, onError]);
|
||||
onErrorRef.current?.(error instanceof Error ? error : new Error('Failed to load messages'));
|
||||
}
|
||||
}, [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 }));
|
||||
// 自动重连
|
||||
// 限制重连次数
|
||||
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]
|
||||
);
|
||||
|
||||
// 取消处理
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
|
||||
Reference in New Issue
Block a user