From 6d97328ff6f96c0a8b12b1bd46e92b98ac598fee Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 12 May 2026 22:36:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20CopilotAlert=20=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20+=20=E5=91=8A=E8=AD=A6=20API=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CopilotAlert: 分级告警列表,30秒轮询刷新,危急 banner - copilot.ts 新增 listAlerts 函数 --- apps/web/src/api/copilot.ts | 4 + .../src/components/Copilot/CopilotAlert.tsx | 106 ++++++++++++++++++ apps/web/src/components/Copilot/index.ts | 1 + 3 files changed, 111 insertions(+) create mode 100644 apps/web/src/components/Copilot/CopilotAlert.tsx diff --git a/apps/web/src/api/copilot.ts b/apps/web/src/api/copilot.ts index 358e99c..efe53a2 100644 --- a/apps/web/src/api/copilot.ts +++ b/apps/web/src/api/copilot.ts @@ -54,6 +54,10 @@ export function dismissInsight(id: string) { return client.post(`/copilot/insights/${id}/dismiss`); } +export function listAlerts(params?: { severity?: string; page?: number; page_size?: number }) { + return listInsights({ insight_type: 'anomaly', ...params }); +} + export function listRules(params?: { page?: number; page_size?: number }) { return client.get>>('/copilot/rules', { params }); } diff --git a/apps/web/src/components/Copilot/CopilotAlert.tsx b/apps/web/src/components/Copilot/CopilotAlert.tsx new file mode 100644 index 0000000..343e011 --- /dev/null +++ b/apps/web/src/components/Copilot/CopilotAlert.tsx @@ -0,0 +1,106 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Alert, Badge, List, Button, Space, Typography, Spin } from 'antd'; +import { CheckOutlined } from '@ant-design/icons'; +import { listAlerts, dismissInsight } from '../../api/copilot'; +import type { CopilotInsight } from '../../api/copilot'; + +const severityConfig: Record = { + critical: { type: 'error', label: '危急' }, + warning: { type: 'warning', label: '警告' }, + info: { type: 'info', label: '提示' }, +}; + +export function CopilotAlert() { + const [alerts, setAlerts] = useState([]); + const [loading, setLoading] = useState(false); + const [dismissing, setDismissing] = useState(null); + + const fetchAlerts = useCallback(async () => { + setLoading(true); + try { + const res = await listAlerts({ page_size: 50 }); + const result = res.data as unknown as { items: CopilotInsight[]; total: number }; + setAlerts(result.items ?? []); + } catch { + // 静默 + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchAlerts(); + const timer = setInterval(fetchAlerts, 30_000); + return () => clearInterval(timer); + }, [fetchAlerts]); + + const handleDismiss = async (id: string) => { + setDismissing(id); + try { + await dismissInsight(id); + setAlerts((prev) => prev.filter((a) => a.id !== id)); + } finally { + setDismissing(null); + } + }; + + if (!alerts.length && !loading) return null; + + const criticalCount = alerts.filter((a) => a.severity === 'critical').length; + + return ( +
+ {criticalCount > 0 && ( + + )} + {loading && alerts.length === 0 ? ( + + ) : ( + { + const config = severityConfig[item.severity] ?? severityConfig.info; + return ( + } + loading={dismissing === item.id} + onClick={() => handleDismiss(item.id)} + > + 已知悉 + , + ]} + > + + + {item.title} + + } + description={ + item.content?.suggestion ? ( + + {item.content.suggestion as string} + + ) : undefined + } + /> + + ); + }} + /> + )} +
+ ); +} diff --git a/apps/web/src/components/Copilot/index.ts b/apps/web/src/components/Copilot/index.ts index 9f06213..888a35b 100644 --- a/apps/web/src/components/Copilot/index.ts +++ b/apps/web/src/components/Copilot/index.ts @@ -1,4 +1,5 @@ export { CopilotBadge } from './CopilotBadge'; export { CopilotCard } from './CopilotCard'; +export { CopilotAlert } from './CopilotAlert'; export { useCopilotRisk } from './useCopilotRisk'; export { useCopilotInsights } from './useCopilotInsights';