Skip to content

Commit da12daa

Browse files
committed
feat: optimize session status action design
- implement session-isolated processing state management - remove redundant HTTP polling mechanism - add page refresh status recovery - simplify architecture with SSE-only approach fix(session): ensure status API has higher priority than events on refresh - Move getSessionStatus call after getSessionEvents to override incorrect state - Prevent chat input stuck in loading state after page refresh - Maintain consistent processing state across race conditions
1 parent c2815f5 commit da12daa

File tree

5 files changed

+59
-92
lines changed

5 files changed

+59
-92
lines changed

multimodal/tarko/agent-ui/src/common/hooks/useSession.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
deleteSessionAction,
1616
sendMessageAction,
1717
abortQueryAction,
18-
checkSessionStatusAction,
1918
} from '../state/actions/sessionActions';
2019
import {
2120
initConnectionMonitoringAction,
@@ -59,29 +58,6 @@ export function useSession() {
5958
const abortQuery = useSetAtom(abortQueryAction);
6059
const initConnectionMonitoring = useSetAtom(initConnectionMonitoringAction);
6160
const checkServerStatus = useSetAtom(checkConnectionStatusAction);
62-
const checkSessionStatus = useSetAtom(checkSessionStatusAction);
63-
64-
const statusCheckTimeoutRef = useRef<NodeJS.Timeout | null>(null);
65-
66-
useEffect(() => {
67-
if (!activeSessionId || !connectionStatus.connected || isReplayMode) return;
68-
69-
if (statusCheckTimeoutRef.current) {
70-
clearTimeout(statusCheckTimeoutRef.current);
71-
}
72-
73-
statusCheckTimeoutRef.current = setTimeout(() => {
74-
if (activeSessionId && connectionStatus.connected && !isReplayMode) {
75-
checkSessionStatus(activeSessionId);
76-
}
77-
}, 200);
78-
79-
return () => {
80-
if (statusCheckTimeoutRef.current) {
81-
clearTimeout(statusCheckTimeoutRef.current);
82-
}
83-
};
84-
}, [activeSessionId, connectionStatus.connected, checkSessionStatus, isReplayMode]);
8561

8662
const sessionState = useMemo(
8763
() => ({
@@ -113,7 +89,7 @@ export function useSession() {
11389
initConnectionMonitoring,
11490
checkServerStatus,
11591

116-
checkSessionStatus,
92+
11793
}),
11894
[
11995
sessions,
@@ -138,7 +114,6 @@ export function useSession() {
138114
setActivePanelContent,
139115
initConnectionMonitoring,
140116
checkServerStatus,
141-
checkSessionStatus,
142117
],
143118
);
144119

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { isProcessingAtom } from '@/common/state/atoms/ui';
1+
import { sessionProcessingStatesAtom } from '@/common/state/atoms/ui';
22
import { AgentEventStream } from '@/common/types';
33
import { EventHandler, EventHandlerContext } from '../types';
4-
import { shouldUpdateProcessingState } from '../utils/panelContentUpdater';
54

65
export class AgentRunStartHandler implements EventHandler<AgentEventStream.AgentRunStartEvent> {
76
canHandle(event: AgentEventStream.Event): event is AgentEventStream.AgentRunStartEvent {
@@ -15,10 +14,11 @@ export class AgentRunStartHandler implements EventHandler<AgentEventStream.Agent
1514
): void {
1615
const { set } = context;
1716

18-
// Update processing state
19-
if (shouldUpdateProcessingState(sessionId)) {
20-
set(isProcessingAtom, true);
21-
}
17+
// Update session-isolated processing state
18+
set(sessionProcessingStatesAtom, (prev) => ({
19+
...prev,
20+
[sessionId]: true,
21+
}));
2222
}
2323
}
2424

@@ -30,9 +30,10 @@ export class AgentRunEndHandler implements EventHandler<AgentEventStream.Event>
3030
handle(context: EventHandlerContext, sessionId: string, event: AgentEventStream.Event): void {
3131
const { set } = context;
3232

33-
// Update processing state
34-
if (shouldUpdateProcessingState(sessionId)) {
35-
set(isProcessingAtom, false);
36-
}
33+
// Update session-isolated processing state
34+
set(sessionProcessingStatesAtom, (prev) => ({
35+
...prev,
36+
[sessionId]: false,
37+
}));
3738
}
3839
}

multimodal/tarko/agent-ui/src/common/state/actions/eventProcessors/utils/panelContentUpdater.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,3 @@ export function shouldUpdatePanelContent(get: Getter, sessionId: string): boolea
1717

1818
return true;
1919
}
20-
21-
/**
22-
* Helper function to determine if processing state should be updated for a session
23-
* Always allow processing state updates for the session that owns the event
24-
*/
25-
export function shouldUpdateProcessingState(sessionId: string): boolean {
26-
// Processing state is now session-isolated, so we always update for the event's session
27-
return Boolean(sessionId);
28-
}

multimodal/tarko/agent-ui/src/common/state/actions/sessionActions.ts

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { apiService } from '../../services/apiService';
44
import { sessionsAtom, activeSessionIdAtom } from '../atoms/session';
55
import { messagesAtom } from '../atoms/message';
66
import { toolResultsAtom, toolCallResultMap } from '../atoms/tool';
7-
import { sessionPanelContentAtom, isProcessingAtom } from '../atoms/ui';
7+
import { sessionPanelContentAtom, sessionProcessingStatesAtom, isProcessingAtom } from '../atoms/ui';
88
import { processEventAction } from './eventProcessors';
99
import { Message, SessionInfo } from '@/common/types';
1010
import { connectionStatusAtom } from '../atoms/ui';
@@ -150,7 +150,7 @@ export const setActiveSessionAction = atom(null, async (get, set, sessionId: str
150150
});
151151
}
152152

153-
// Processing state will be managed by SSE events
153+
154154

155155
toolCallResultMap.clear();
156156

@@ -170,6 +170,27 @@ export const setActiveSessionAction = atom(null, async (get, set, sessionId: str
170170
}
171171
}
172172

173+
// Status recovery with higher priority - always called after events processing
174+
// This ensures accurate processing state when SSE connection is established
175+
// and overrides any incorrect state from events processing
176+
if (!replayState.isActive) {
177+
try {
178+
const status = await apiService.getSessionStatus(sessionId);
179+
set(sessionProcessingStatesAtom, (prev) => ({
180+
...prev,
181+
[sessionId]: status.isProcessing,
182+
}));
183+
console.log(`Recovered processing state for session ${sessionId}: ${status.isProcessing}`);
184+
} catch (error) {
185+
console.warn(`Failed to recover session status for ${sessionId}:`, error);
186+
// Default to false on error to avoid stuck processing state
187+
set(sessionProcessingStatesAtom, (prev) => ({
188+
...prev,
189+
[sessionId]: false,
190+
}));
191+
}
192+
}
193+
173194
// Always ensure we have the latest session metadata (including modelConfig)
174195
// This is lightweight since server always provides it
175196
try {
@@ -411,46 +432,3 @@ export const abortQueryAction = atom(null, async (get, set) => {
411432
return false;
412433
}
413434
});
414-
415-
// Cache to prevent frequent status checks for the same session
416-
const statusCheckCache = new Map<string, { timestamp: number; promise?: Promise<any> }>();
417-
const STATUS_CACHE_TTL = 2000; // 2 seconds cache
418-
419-
export const checkSessionStatusAction = atom(null, async (get, set, sessionId: string) => {
420-
if (!sessionId) return;
421-
422-
const now = Date.now();
423-
const cached = statusCheckCache.get(sessionId);
424-
425-
// If we have a recent check or an ongoing request, skip
426-
if (cached) {
427-
if (cached.promise) {
428-
// There's already an ongoing request for this session
429-
return cached.promise;
430-
}
431-
if (now - cached.timestamp < STATUS_CACHE_TTL) {
432-
// Recent check, skip
433-
return;
434-
}
435-
}
436-
437-
try {
438-
// Mark that we're making a request
439-
const promise = apiService.getSessionStatus(sessionId);
440-
statusCheckCache.set(sessionId, { timestamp: now, promise });
441-
442-
const status = await promise;
443-
444-
// Update simple processing state
445-
set(isProcessingAtom, status.isProcessing);
446-
447-
// Clear the promise and update timestamp
448-
statusCheckCache.set(sessionId, { timestamp: now });
449-
450-
return status;
451-
} catch (error) {
452-
console.error('Failed to check session status:', error);
453-
// Clear the failed request
454-
statusCheckCache.delete(sessionId);
455-
}
456-
});

multimodal/tarko/agent-ui/src/common/state/atoms/ui.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,31 @@ export const sidebarCollapsedAtom = atom<boolean>(true);
5656
export const workspacePanelCollapsedAtom = atom<boolean>(false);
5757

5858
/**
59-
* Simple processing state atom based on SSE events
59+
* Session-isolated processing state atom based on SSE events
60+
* Maps session IDs to their processing states
6061
*/
61-
export const isProcessingAtom = atom<boolean>(false);
62+
export const sessionProcessingStatesAtom = atom<Record<string, boolean>>({});
63+
64+
/**
65+
* Derived atom for the current active session's processing state
66+
* Automatically isolates state by active session
67+
*/
68+
export const isProcessingAtom = atom(
69+
(get) => {
70+
const activeSessionId = get(activeSessionIdAtom);
71+
const sessionProcessingStates = get(sessionProcessingStatesAtom);
72+
return activeSessionId ? sessionProcessingStates[activeSessionId] ?? false : false;
73+
},
74+
(get, set, update: boolean) => {
75+
const activeSessionId = get(activeSessionIdAtom);
76+
if (activeSessionId) {
77+
set(sessionProcessingStatesAtom, (prev) => ({
78+
...prev,
79+
[activeSessionId]: update,
80+
}));
81+
}
82+
},
83+
);
6284

6385
/**
6486
* Atom for offline mode state (view-only when disconnected)

0 commit comments

Comments
 (0)