diff --git a/frontend/src/hooks/useAgentStream.ts b/frontend/src/hooks/useAgentStream.ts index 3818473..9975e0f 100644 --- a/frontend/src/hooks/useAgentStream.ts +++ b/frontend/src/hooks/useAgentStream.ts @@ -18,7 +18,7 @@ export interface UseAgentStreamOptions extends StreamOptions { } export interface UseAgentStreamReturn extends AgentStreamState { - connect: () => void; + connect: (overrideAfterSequence?: number) => void; disconnect: () => void; isConnected: boolean; clearEvents: () => void; @@ -98,7 +98,7 @@ export function useAgentStream( afterSequenceRef.current = afterSequence; // 连接 - const connect = useCallback(() => { + const connect = useCallback((overrideAfterSequence?: number) => { if (!taskId) return; // 断开现有连接 @@ -118,8 +118,8 @@ export function useAgentStream( setError(null); thinkingBufferRef.current = []; - // 🔥 使用 ref 获取最新的 afterSequence 值 - const currentAfterSequence = afterSequenceRef.current; + // 🔥 使用 ref 获取最新的 afterSequence 值,或者使用覆盖值 + const currentAfterSequence = overrideAfterSequence !== undefined ? overrideAfterSequence : afterSequenceRef.current; console.log(`[useAgentStream] Creating handler with afterSequence=${currentAfterSequence}`); // 创建新的 handler diff --git a/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts b/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts index 30f7cf0..255b988 100644 --- a/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts +++ b/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts @@ -49,7 +49,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag if (newFinding.id && existingIds.has(newFinding.id)) { return state; // 已存在,不添加 } - return { ...state, findings: [...state.findings, newFinding] }; + return { ...state, findings: [...state.findings, newFinding as AgentFinding] }; } case 'SET_AGENT_TREE': @@ -99,7 +99,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag } case 'UPDATE_OR_ADD_PROGRESS_LOG': { - const { progressKey, title, agentName } = action.payload; + const { progressKey, title, agentName, time } = action.payload; // 查找是否已存在相同 progressKey 的进度日志 const existingIndex = state.logs.findIndex( log => log.type === 'progress' && log.progressKey === progressKey @@ -111,7 +111,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag updatedLogs[existingIndex] = { ...updatedLogs[existingIndex], title, - time: new Date().toLocaleTimeString('en-US', { hour12: false }), + time: time || new Date().toLocaleTimeString('en-US', { hour12: false }), }; return { ...state, logs: updatedLogs }; } else { @@ -121,6 +121,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag title, progressKey, agentName, + time, }); return { ...state, logs: [...state.logs, newLog] }; } diff --git a/frontend/src/pages/AgentAudit/index.tsx b/frontend/src/pages/AgentAudit/index.tsx index ff107f6..e476ba4 100644 --- a/frontend/src/pages/AgentAudit/index.tsx +++ b/frontend/src/pages/AgentAudit/index.tsx @@ -146,19 +146,26 @@ function AgentAuditPageContent() { }, [loadAgentTree]); // 🔥 NEW: 加载历史事件并转换为日志项 - const loadHistoricalEvents = useCallback(async () => { + const loadHistoricalEvents = useCallback(async (isIncremental = false) => { if (!taskId) return 0; - // 🔥 防止重复加载历史事件 - if (hasLoadedHistoricalEventsRef.current) { + // 🔥 如果不是增量加载,且已经加载过历史事件,则跳过 + if (!isIncremental && hasLoadedHistoricalEventsRef.current) { console.log('[AgentAudit] Historical events already loaded, skipping'); return 0; } - hasLoadedHistoricalEventsRef.current = true; + + // 标记已尝试加载(初次加载时) + if (!isIncremental) { + hasLoadedHistoricalEventsRef.current = true; + } try { - console.log(`[AgentAudit] Fetching historical events for task ${taskId}...`); - const events = await getAgentEvents(taskId, { limit: 500 }); + console.log(`[AgentAudit] Fetching ${isIncremental ? 'incremental' : 'initial'} events for task ${taskId}, after_sequence: ${isIncremental ? lastEventSequenceRef.current : 0}...`); + const events = await getAgentEvents(taskId, { + limit: 500, + after_sequence: isIncremental ? lastEventSequenceRef.current : 0 + }); console.log(`[AgentAudit] Received ${events.length} events from API`); if (events.length === 0) { @@ -606,6 +613,7 @@ function AgentAuditPageContent() { line_start: finding.line_start as number, description: finding.description as string, is_verified: (finding.is_verified as boolean) || false, + task_id: taskId || '', } }); }, @@ -722,6 +730,33 @@ function AgentAuditPageContent() { } }, [logs, isAutoScroll]); + // 🔥 Visibility Change Handler - 处理离开页面后返回时的同步 + useEffect(() => { + const handleVisibilityChange = async () => { + if (document.visibilityState === 'visible' && taskId) { + console.log('[AgentAudit] Tab became visible, checking for updates...'); + + // 1. 刷新任务状态 + const updatedTask = await getAgentTask(taskId); + setTask(updatedTask); + + // 2. 无论什么状态,都增量加载错过的事件 + await loadHistoricalEvents(true); + + if (updatedTask.status === 'running') { + // 3. 强制重新连接流,确保使用最新的 sequence 且不是僵尸连接 + console.log('[AgentAudit] Reconnecting stream on visibility change, last sequence:', lastEventSequenceRef.current); + connectStream(lastEventSequenceRef.current); + } + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, [taskId, loadHistoricalEvents, isConnected, connectStream, setTask]); + // ============ Handlers ============ const handleAgentSelect = useCallback((agentId: string) => { diff --git a/frontend/src/pages/AgentAudit/types.ts b/frontend/src/pages/AgentAudit/types.ts index 1ab9ae4..31550a8 100644 --- a/frontend/src/pages/AgentAudit/types.ts +++ b/frontend/src/pages/AgentAudit/types.ts @@ -78,7 +78,7 @@ export type AgentAuditAction = | { type: 'SET_LOGS'; payload: LogItem[] } | { type: 'ADD_LOG'; payload: Omit & { id?: string; time?: string } } | { type: 'UPDATE_LOG'; payload: { id: string; updates: Partial } } - | { type: 'UPDATE_OR_ADD_PROGRESS_LOG'; payload: { progressKey: string; title: string; agentName?: string } } + | { type: 'UPDATE_OR_ADD_PROGRESS_LOG'; payload: { progressKey: string; title: string; agentName?: string; time?: string } } | { type: 'COMPLETE_TOOL_LOG'; payload: { toolName: string; output: string; duration: number } } | { type: 'REMOVE_LOG'; payload: string } | { type: 'SELECT_AGENT'; payload: string | null }