feat: Implement incremental historical event loading and stream reconnection on tab visibility change to improve event synchronization.
Build and Push CodeReview / build (push) Waiting to run
Details
Build and Push CodeReview / build (push) Waiting to run
Details
This commit is contained in:
parent
0f9c1e2bc9
commit
0735834931
|
|
@ -18,7 +18,7 @@ export interface UseAgentStreamOptions extends StreamOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseAgentStreamReturn extends AgentStreamState {
|
export interface UseAgentStreamReturn extends AgentStreamState {
|
||||||
connect: () => void;
|
connect: (overrideAfterSequence?: number) => void;
|
||||||
disconnect: () => void;
|
disconnect: () => void;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
clearEvents: () => void;
|
clearEvents: () => void;
|
||||||
|
|
@ -98,7 +98,7 @@ export function useAgentStream(
|
||||||
afterSequenceRef.current = afterSequence;
|
afterSequenceRef.current = afterSequence;
|
||||||
|
|
||||||
// 连接
|
// 连接
|
||||||
const connect = useCallback(() => {
|
const connect = useCallback((overrideAfterSequence?: number) => {
|
||||||
if (!taskId) return;
|
if (!taskId) return;
|
||||||
|
|
||||||
// 断开现有连接
|
// 断开现有连接
|
||||||
|
|
@ -118,8 +118,8 @@ export function useAgentStream(
|
||||||
setError(null);
|
setError(null);
|
||||||
thinkingBufferRef.current = [];
|
thinkingBufferRef.current = [];
|
||||||
|
|
||||||
// 🔥 使用 ref 获取最新的 afterSequence 值
|
// 🔥 使用 ref 获取最新的 afterSequence 值,或者使用覆盖值
|
||||||
const currentAfterSequence = afterSequenceRef.current;
|
const currentAfterSequence = overrideAfterSequence !== undefined ? overrideAfterSequence : afterSequenceRef.current;
|
||||||
console.log(`[useAgentStream] Creating handler with afterSequence=${currentAfterSequence}`);
|
console.log(`[useAgentStream] Creating handler with afterSequence=${currentAfterSequence}`);
|
||||||
|
|
||||||
// 创建新的 handler
|
// 创建新的 handler
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag
|
||||||
if (newFinding.id && existingIds.has(newFinding.id)) {
|
if (newFinding.id && existingIds.has(newFinding.id)) {
|
||||||
return state; // 已存在,不添加
|
return state; // 已存在,不添加
|
||||||
}
|
}
|
||||||
return { ...state, findings: [...state.findings, newFinding] };
|
return { ...state, findings: [...state.findings, newFinding as AgentFinding] };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_AGENT_TREE':
|
case 'SET_AGENT_TREE':
|
||||||
|
|
@ -99,7 +99,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'UPDATE_OR_ADD_PROGRESS_LOG': {
|
case 'UPDATE_OR_ADD_PROGRESS_LOG': {
|
||||||
const { progressKey, title, agentName } = action.payload;
|
const { progressKey, title, agentName, time } = action.payload;
|
||||||
// 查找是否已存在相同 progressKey 的进度日志
|
// 查找是否已存在相同 progressKey 的进度日志
|
||||||
const existingIndex = state.logs.findIndex(
|
const existingIndex = state.logs.findIndex(
|
||||||
log => log.type === 'progress' && log.progressKey === progressKey
|
log => log.type === 'progress' && log.progressKey === progressKey
|
||||||
|
|
@ -111,7 +111,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag
|
||||||
updatedLogs[existingIndex] = {
|
updatedLogs[existingIndex] = {
|
||||||
...updatedLogs[existingIndex],
|
...updatedLogs[existingIndex],
|
||||||
title,
|
title,
|
||||||
time: new Date().toLocaleTimeString('en-US', { hour12: false }),
|
time: time || new Date().toLocaleTimeString('en-US', { hour12: false }),
|
||||||
};
|
};
|
||||||
return { ...state, logs: updatedLogs };
|
return { ...state, logs: updatedLogs };
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -121,6 +121,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag
|
||||||
title,
|
title,
|
||||||
progressKey,
|
progressKey,
|
||||||
agentName,
|
agentName,
|
||||||
|
time,
|
||||||
});
|
});
|
||||||
return { ...state, logs: [...state.logs, newLog] };
|
return { ...state, logs: [...state.logs, newLog] };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,19 +146,26 @@ function AgentAuditPageContent() {
|
||||||
}, [loadAgentTree]);
|
}, [loadAgentTree]);
|
||||||
|
|
||||||
// 🔥 NEW: 加载历史事件并转换为日志项
|
// 🔥 NEW: 加载历史事件并转换为日志项
|
||||||
const loadHistoricalEvents = useCallback(async () => {
|
const loadHistoricalEvents = useCallback(async (isIncremental = false) => {
|
||||||
if (!taskId) return 0;
|
if (!taskId) return 0;
|
||||||
|
|
||||||
// 🔥 防止重复加载历史事件
|
// 🔥 如果不是增量加载,且已经加载过历史事件,则跳过
|
||||||
if (hasLoadedHistoricalEventsRef.current) {
|
if (!isIncremental && hasLoadedHistoricalEventsRef.current) {
|
||||||
console.log('[AgentAudit] Historical events already loaded, skipping');
|
console.log('[AgentAudit] Historical events already loaded, skipping');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
hasLoadedHistoricalEventsRef.current = true;
|
|
||||||
|
// 标记已尝试加载(初次加载时)
|
||||||
|
if (!isIncremental) {
|
||||||
|
hasLoadedHistoricalEventsRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[AgentAudit] Fetching historical events for task ${taskId}...`);
|
console.log(`[AgentAudit] Fetching ${isIncremental ? 'incremental' : 'initial'} events for task ${taskId}, after_sequence: ${isIncremental ? lastEventSequenceRef.current : 0}...`);
|
||||||
const events = await getAgentEvents(taskId, { limit: 500 });
|
const events = await getAgentEvents(taskId, {
|
||||||
|
limit: 500,
|
||||||
|
after_sequence: isIncremental ? lastEventSequenceRef.current : 0
|
||||||
|
});
|
||||||
console.log(`[AgentAudit] Received ${events.length} events from API`);
|
console.log(`[AgentAudit] Received ${events.length} events from API`);
|
||||||
|
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
|
|
@ -606,6 +613,7 @@ function AgentAuditPageContent() {
|
||||||
line_start: finding.line_start as number,
|
line_start: finding.line_start as number,
|
||||||
description: finding.description as string,
|
description: finding.description as string,
|
||||||
is_verified: (finding.is_verified as boolean) || false,
|
is_verified: (finding.is_verified as boolean) || false,
|
||||||
|
task_id: taskId || '',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -722,6 +730,33 @@ function AgentAuditPageContent() {
|
||||||
}
|
}
|
||||||
}, [logs, isAutoScroll]);
|
}, [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 ============
|
// ============ Handlers ============
|
||||||
|
|
||||||
const handleAgentSelect = useCallback((agentId: string) => {
|
const handleAgentSelect = useCallback((agentId: string) => {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ export type AgentAuditAction =
|
||||||
| { type: 'SET_LOGS'; payload: LogItem[] }
|
| { type: 'SET_LOGS'; payload: LogItem[] }
|
||||||
| { type: 'ADD_LOG'; payload: Omit<LogItem, 'id' | 'time'> & { id?: string; time?: string } }
|
| { type: 'ADD_LOG'; payload: Omit<LogItem, 'id' | 'time'> & { id?: string; time?: string } }
|
||||||
| { type: 'UPDATE_LOG'; payload: { id: string; updates: Partial<LogItem> } }
|
| { type: 'UPDATE_LOG'; payload: { id: string; updates: Partial<LogItem> } }
|
||||||
| { 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: 'COMPLETE_TOOL_LOG'; payload: { toolName: string; output: string; duration: number } }
|
||||||
| { type: 'REMOVE_LOG'; payload: string }
|
| { type: 'REMOVE_LOG'; payload: string }
|
||||||
| { type: 'SELECT_AGENT'; payload: string | null }
|
| { type: 'SELECT_AGENT'; payload: string | null }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue