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);