diff --git a/web/src/App.tsx b/web/src/App.tsx index 3df7ab6c..8fca6e4d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -602,13 +602,7 @@ export default function App() { }; }, [session, sessionValidated, currentUser.id, projects]); - const handleLogout = () => { - void authClient.logout().finally(() => { - setSession(null); - setCapabilities([]); - setSessionValidated(false); - }); - }; + // handleLogout 定义在 turnRuntime/taskRunStream hook 之后,便于 abort 全会话在途流。 // 核心功能权限计算 const canCreateProject = useMemo(() => { @@ -2821,6 +2815,16 @@ export default function App() { } }; + const handleLogout = () => { + turnRuntime.abortAllSessions(); + taskRunStream.abortAllSessions(); + void authClient.logout().finally(() => { + setSession(null); + setCapabilities([]); + setSessionValidated(false); + }); + }; + // 发送消息与对话驱动双轨逻辑 const handleSendMessage = async ( text: string, diff --git a/web/src/hooks/useTaskRunStream.ts b/web/src/hooks/useTaskRunStream.ts index d919e95a..ba678491 100644 --- a/web/src/hooks/useTaskRunStream.ts +++ b/web/src/hooks/useTaskRunStream.ts @@ -89,6 +89,15 @@ export function useTaskRunStream(activeSessionId: string) { [getRuntime, notify], ); + const abortAllSessions = useCallback(() => { + for (const rt of storeRef.current.sessions.values()) { + rt.controller?.abort(); + rt.controller = null; + rt.thinking = false; + } + notify(); + }, [notify]); + return { runtime: snapshot, isThinking: snapshot.thinking, @@ -98,5 +107,6 @@ export function useTaskRunStream(activeSessionId: string) { setRunId, setController, abortSession, + abortAllSessions, }; } diff --git a/web/src/hooks/useTurnRuntime.ts b/web/src/hooks/useTurnRuntime.ts index cf66589e..7b95fc8e 100644 --- a/web/src/hooks/useTurnRuntime.ts +++ b/web/src/hooks/useTurnRuntime.ts @@ -108,6 +108,18 @@ export function useTurnRuntime(activeSessionId: string) { notify(); }, [activeSessionId, getSessionMap, notify]); + const abortAllSessions = useCallback(() => { + for (const sessionMap of storeRef.current.turns.values()) { + for (const rt of sessionMap.values()) { + if (rt.status === 'pending' || rt.status === 'streaming') { + rt.controller.abort(); + rt.status = 'cancelled'; + } + } + } + notify(); + }, [notify]); + const isSessionThinking = useCallback( (sessionId: string) => { const sessionMap = storeRef.current.turns.get(sessionId); @@ -138,6 +150,7 @@ export function useTurnRuntime(activeSessionId: string) { removeTurn, abortTurn, abortActiveSessionTurns, + abortAllSessions, isThinking, isSessionThinking, getTurn,