diff --git a/src/frontend/src/store.ts b/src/frontend/src/store.ts index 59fdbef..9185f1c 100644 --- a/src/frontend/src/store.ts +++ b/src/frontend/src/store.ts @@ -718,6 +718,13 @@ let _es: EventSource | null = null; let _sseRetryCount = 0; const SSE_MAX_RETRY = 5; + +// 推送 SSE 事件到通知中心 +function _pushSseEvent(item: NotifItem) { + const sseEvents = (useStore.getState().sseEvents || []) as NotifItem[]; + useStore.setState({ sseEvents: [item, ...sseEvents].slice(0, 30) }); +} + export function startSSE() { if (_es) return; try { @@ -735,6 +742,7 @@ export function startSSE() { } else { s.loadV2Tasks(); } + _pushSseEvent({ id: `sse-${Date.now()}`, type: 'info', title: `任务更新`, message: `${data.old_status || '?'} → ${data.new_status || '?'}`, time: new Date().toISOString(), read: false, source: 'event', taskId: data.task_id, projectId: data.project_id }); } catch { /* ignore */ } }); _es.addEventListener('task_created', () => { @@ -750,6 +758,7 @@ export function startSSE() { tasks[idx] = { ...tasks[idx], status: 'done' }; useStore.setState({ v2tasks: tasks }); } + _pushSseEvent({ id: `sse-${Date.now()}`, type: 'success', title: `任务完成`, message: `${data.task_id || ''} 已完成`, time: new Date().toISOString(), read: false, source: 'event', taskId: data.task_id, projectId: data.project_id }); } catch { /* ignore */ } }); _es.addEventListener('task_failed', (e: MessageEvent) => { @@ -762,31 +771,9 @@ export function startSSE() { tasks[idx] = { ...tasks[idx], status: 'failed' }; useStore.setState({ v2tasks: tasks }); } + _pushSseEvent({ id: `sse-${Date.now()}`, type: 'error', title: `任务失败`, message: `${data.task_id || ''} 执行失败`, time: new Date().toISOString(), read: false, source: 'event', taskId: data.task_id, projectId: data.project_id }); } catch { /* ignore */ } }); - // 将事件推入 store 供 NotificationCenter 消费 - _es.onmessage = (e: MessageEvent) => { - try { - const sseEvents = useStore.getState().sseEvents as NotifItem[]; - const data = JSON.parse(e.data); - const etype = (e as any).type || data.event_type || ''; - const importantTypes = ['task_completed', 'task_failed', 'review_result', 'task_updated']; - if (!importantTypes.includes(etype)) return; - const ntype = etype === 'task_failed' ? 'error' : etype === 'task_completed' ? 'success' : etype === 'review_result' ? 'warning' : 'info'; - const item: NotifItem = { - id: data.id || `sse-${Date.now()}`, - type: ntype, - title: data.task_id ? `任务 ${data.task_id.slice(0, 8)}...` : etype, - message: `${data.old_status || ''} → ${data.new_status || etype}`, - time: data.timestamp || new Date().toISOString(), - read: false, - source: 'event', - taskId: data.task_id, - projectId: data.project_id, - }; - useStore.setState({ sseEvents: [item, ...sseEvents].slice(0, 30) }); - } catch { /* ignore */ } - }; _es.onerror = () => { _es?.close(); _es = null;