import { useEffect, useState } from 'react'; import { Card, Row, Col, Statistic, Tag, Tabs, Button, Table, Space, Progress, Drawer, Descriptions, Timeline, Badge, Select, message, Tooltip, Empty, Modal, List, Calendar, Input, } from 'antd'; import { RobotOutlined, HeartOutlined, AlertOutlined, MedicineBoxOutlined, ToolOutlined, BulbOutlined, SyncOutlined, ArrowUpOutlined, ArrowDownOutlined, MinusOutlined, ThunderboltOutlined, ExperimentOutlined, SafetyCertificateOutlined, EyeOutlined, CheckCircleOutlined, CloseCircleOutlined, WarningOutlined, InfoCircleOutlined, FireOutlined, } from '@ant-design/icons'; import ReactECharts from 'echarts-for-react'; import dayjs from 'dayjs'; import { getAiOpsDashboard, getAiOpsHealth, getAiOpsHealthHistory, getAiOpsAnomalies, updateAnomalyStatus, triggerAnomalyScan, getAiOpsDiagnostics, runDeviceDiagnostics, getAiOpsPredictions, getAiOpsMaintenanceSchedule, getAiOpsInsights, triggerInsights, triggerHealthCalc, triggerPredictions, } from '../../services/api'; const severityColors: Record = { critical: 'red', warning: 'orange', info: 'blue', }; const statusColors: Record = { healthy: 'green', warning: 'orange', critical: 'red', detected: 'red', investigating: 'orange', resolved: 'green', false_positive: 'default', generated: 'blue', reviewed: 'cyan', action_taken: 'green', predicted: 'orange', scheduled: 'blue', completed: 'green', false_alarm: 'default', }; const trendIcons: Record = { improving: , stable: , degrading: , }; const anomalyTypeLabels: Record = { power_drop: '功率下降', efficiency_loss: '能效降低', abnormal_temperature: '温度异常', communication_loss: '通讯中断', pattern_deviation: '模式偏移', }; const urgencyColors: Record = { critical: 'red', high: 'orange', medium: 'blue', low: 'default', }; const impactColors: Record = { high: 'red', medium: 'orange', low: 'blue', }; const insightTypeLabels: Record = { efficiency_trend: '效率趋势', cost_anomaly: '费用异常', performance_comparison: '性能对比', seasonal_pattern: '季节性规律', }; // ── Tab: Health Overview ─────────────────────────────────────────── function HealthOverview() { const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); const [detailDevice, setDetailDevice] = useState(null); const [history, setHistory] = useState([]); const [historyLoading, setHistoryLoading] = useState(false); useEffect(() => { loadHealth(); }, []); const loadHealth = async () => { setLoading(true); try { const data = await getAiOpsHealth(); setDevices(Array.isArray(data) ? data : []); } catch { message.error('加载健康数据失败'); } finally { setLoading(false); } }; const loadHistory = async (deviceId: number) => { setHistoryLoading(true); try { const data = await getAiOpsHealthHistory(deviceId, { days: 30 }); setHistory(Array.isArray(data) ? data : []); } catch { /* ignore */ } finally { setHistoryLoading(false); } }; const showDetail = (device: any) => { setDetailDevice(device); loadHistory(device.device_id); }; const handleRecalculate = async () => { message.loading({ content: '正在计算健康评分...', key: 'calc' }); try { await triggerHealthCalc(); message.success({ content: '健康评分计算完成', key: 'calc' }); loadHealth(); } catch { message.error({ content: '计算失败', key: 'calc' }); } }; const getScoreColor = (score: number) => { if (score >= 80) return '#52c41a'; if (score >= 60) return '#faad14'; return '#f5222d'; }; const gaugeOption = (score: number) => ({ series: [{ type: 'gauge', startAngle: 200, endAngle: -20, min: 0, max: 100, itemStyle: { color: getScoreColor(score) }, progress: { show: true, width: 12 }, pointer: { show: false }, axisLine: { lineStyle: { width: 12, color: [[1, '#e6e6e6']] } }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { show: false }, detail: { valueAnimation: true, fontSize: 24, fontWeight: 'bold', offsetCenter: [0, '0%'], formatter: '{value}', color: getScoreColor(score), }, data: [{ value: score }], }], }); const historyOption = history.length > 0 ? { tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: history.map((h: any) => dayjs(h.timestamp).format('MM-DD HH:mm')), }, yAxis: { type: 'value', min: 0, max: 100, name: '健康评分' }, series: [{ type: 'line', data: history.map((h: any) => h.health_score), smooth: true, areaStyle: { opacity: 0.15 }, markLine: { data: [ { yAxis: 80, label: { formatter: '健康' }, lineStyle: { color: '#52c41a', type: 'dashed' } }, { yAxis: 60, label: { formatter: '警告' }, lineStyle: { color: '#faad14', type: 'dashed' } }, ], }, }], grid: { top: 30, right: 30, bottom: 30, left: 50 }, } : null; const radarOption = detailDevice?.factors ? { radar: { indicator: [ { name: '功率稳定', max: 100 }, { name: '能效水平', max: 100 }, { name: '告警频率', max: 100 }, { name: '运行时间', max: 100 }, { name: '温度状态', max: 100 }, ], }, series: [{ type: 'radar', data: [{ value: [ detailDevice.factors.power_stability || 0, detailDevice.factors.efficiency || 0, detailDevice.factors.alarm_frequency || 0, detailDevice.factors.uptime || 0, detailDevice.factors.temperature || 0, ], areaStyle: { opacity: 0.2 }, }], }], } : null; return (
设备健康总览
{loading ? ( ) : devices.length === 0 ? ( ) : devices.map((d: any) => ( showDetail(d)} styles={{ body: { padding: 16, textAlign: 'center' } }} >
{d.device_name}
{d.device_type}
{d.status === 'healthy' ? '健康' : d.status === 'warning' ? '警告' : '危险'} {trendIcons[d.trend]} {d.trend === 'improving' ? '改善' : d.trend === 'degrading' ? '下降' : '稳定'}
))}
setDetailDevice(null)} width={720} > {detailDevice && ( <> {detailDevice.device_name} {detailDevice.device_type} {detailDevice.health_score} {detailDevice.status === 'healthy' ? '健康' : detailDevice.status === 'warning' ? '警告' : '危险'} {trendIcons[detailDevice.trend]} {detailDevice.trend === 'improving' ? '持续改善' : detailDevice.trend === 'degrading' ? '持续下降' : '保持稳定'} {radarOption && } {historyOption ? ( ) : ( )} )}
); } // ── Tab: Anomaly Center ──────────────────────────────────────────── function AnomalyCenter() { const [anomalies, setAnomalies] = useState({ total: 0, items: [] }); const [loading, setLoading] = useState(true); const [filters, setFilters] = useState>({}); const [page, setPage] = useState(1); useEffect(() => { loadAnomalies(); }, [page, filters]); const loadAnomalies = async () => { setLoading(true); try { const data = await getAiOpsAnomalies({ ...filters, page, page_size: 15 }); setAnomalies(data || { total: 0, items: [] }); } catch { message.error('加载异常数据失败'); } finally { setLoading(false); } }; const handleScan = async () => { message.loading({ content: '正在扫描异常...', key: 'scan' }); try { const result = await triggerAnomalyScan() as any; message.success({ content: `扫描完成,发现 ${result?.anomalies_found || 0} 个异常`, key: 'scan' }); loadAnomalies(); } catch { message.error({ content: '扫描失败', key: 'scan' }); } }; const handleStatusUpdate = async (id: number, status: string) => { try { await updateAnomalyStatus(id, { status }); message.success('状态已更新'); loadAnomalies(); } catch { message.error('更新失败'); } }; const columns = [ { title: '时间', dataIndex: 'detected_at', width: 160, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm'), }, { title: '设备', dataIndex: 'device_name', width: 120 }, { title: '异常类型', dataIndex: 'anomaly_type', width: 100, render: (v: string) => anomalyTypeLabels[v] || v, }, { title: '严重度', dataIndex: 'severity', width: 80, render: (v: string) => {v === 'critical' ? '严重' : v === 'warning' ? '警告' : '信息'}, }, { title: '描述', dataIndex: 'description', ellipsis: true }, { title: '偏差', dataIndex: 'deviation_percent', width: 80, render: (v: number) => v != null ? `${v}%` : '-', }, { title: '状态', dataIndex: 'status', width: 100, render: (v: string) => {v === 'detected' ? '已检测' : v === 'investigating' ? '调查中' : v === 'resolved' ? '已解决' : '误报'}, }, { title: '操作', width: 200, render: (_: any, r: any) => r.status === 'detected' ? ( ) : r.status === 'investigating' ? ( ) : null, }, ]; return (
setFilters((f) => ({ ...f, status: v }))} options={[ { label: '已检测', value: 'detected' }, { label: '调查中', value: 'investigating' }, { label: '已解决', value: 'resolved' }, { label: '误报', value: 'false_positive' }, ]} />
`共 ${t} 条`, }} size="small" /> ); } // ── Tab: Diagnostic Panel ────────────────────────────────────────── function DiagnosticPanel() { const [reports, setReports] = useState({ total: 0, items: [] }); const [loading, setLoading] = useState(true); const [detailReport, setDetailReport] = useState(null); const [page, setPage] = useState(1); const [devices, setDevices] = useState([]); useEffect(() => { loadReports(); loadDeviceList(); }, [page]); const loadReports = async () => { setLoading(true); try { const data = await getAiOpsDiagnostics({ page, page_size: 15 }); setReports(data || { total: 0, items: [] }); } catch { message.error('加载诊断报告失败'); } finally { setLoading(false); } }; const loadDeviceList = async () => { try { const data = await getAiOpsHealth(); setDevices(Array.isArray(data) ? data : []); } catch { /* ignore */ } }; const handleRun = async (deviceId: number) => { message.loading({ content: '正在运行诊断...', key: 'diag' }); try { const result = await runDeviceDiagnostics(deviceId); message.success({ content: '诊断完成', key: 'diag' }); setDetailReport(result); loadReports(); } catch { message.error({ content: '诊断失败', key: 'diag' }); } }; const columns = [ { title: '时间', dataIndex: 'generated_at', width: 160, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm'), }, { title: '设备', dataIndex: 'device_name', width: 120 }, { title: '类型', dataIndex: 'report_type', width: 80, render: (v: string) => v === 'routine' ? '常规' : v === 'triggered' ? '触发' : '综合', }, { title: '发现', dataIndex: 'findings', ellipsis: true, render: (v: any[]) => v?.length ? v.map((f: any) => f.finding).join('; ') : '-', }, { title: '影响评估', dataIndex: 'estimated_impact', render: (v: any) => v ? ( {v.energy_loss_kwh > 0 && 电量损失 {v.energy_loss_kwh} kWh} {v.cost_impact_yuan > 0 && 费用 {v.cost_impact_yuan} 元} {v.energy_loss_kwh === 0 && v.cost_impact_yuan === 0 && 无影响} ) : '-', }, { title: '状态', dataIndex: 'status', width: 80, render: (v: string) => {v === 'generated' ? '已生成' : v === 'reviewed' ? '已审阅' : '已处理'}, }, { title: '操作', width: 80, render: (_: any, r: any) => ( ), }, ]; return (
诊断报告
`共 ${t} 条` }} size="small" /> setDetailReport(null)} width={640} > {detailReport && ( <> {detailReport.device_name} {detailReport.report_type} {dayjs(detailReport.generated_at).format('YYYY-MM-DD HH:mm')} ({ color: f.severity === 'warning' ? 'orange' : f.severity === 'critical' ? 'red' : 'blue', children: (
{f.finding}
{f.detail}
), }))} />
{detailReport.recommendations?.length > 0 && ( ( {r.priority === 'high' ? '高' : r.priority === 'medium' ? '中' : '低'}} title={r.action} description={r.detail} /> )} /> )} {detailReport.estimated_impact && (
)} )} ); } // ── Tab: Maintenance Predictor ───────────────────────────────────── function MaintenancePredictor() { const [predictions, setPredictions] = useState({ total: 0, items: [] }); const [schedule, setSchedule] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); useEffect(() => { loadData(); }, [page]); const loadData = async () => { setLoading(true); try { const [pred, sched] = await Promise.all([ getAiOpsPredictions({ page, page_size: 15 }), getAiOpsMaintenanceSchedule(), ]); setPredictions(pred || { total: 0, items: [] }); setSchedule(Array.isArray(sched) ? sched : []); } catch { message.error('加载预测数据失败'); } finally { setLoading(false); } }; const handleGenerate = async () => { message.loading({ content: '正在生成维护预测...', key: 'pred' }); try { const result = await triggerPredictions() as any; message.success({ content: `生成 ${result?.generated || 0} 条预测`, key: 'pred' }); loadData(); } catch { message.error({ content: '生成失败', key: 'pred' }); } }; const columns = [ { title: '设备', dataIndex: 'device_name', width: 120, }, { title: '部件', dataIndex: 'component', width: 120 }, { title: '故障模式', dataIndex: 'failure_mode', ellipsis: true }, { title: '概率', dataIndex: 'probability', width: 80, render: (v: number) => , }, { title: '预计故障日期', dataIndex: 'predicted_failure_date', width: 120, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD') : '-', }, { title: '紧急度', dataIndex: 'urgency', width: 80, render: (v: string) => {v === 'critical' ? '紧急' : v === 'high' ? '高' : v === 'medium' ? '中' : '低'}, }, { title: '停机(h)', dataIndex: 'estimated_downtime_hours', width: 80, }, { title: '维修费(元)', dataIndex: 'estimated_repair_cost', width: 100, render: (v: number) => v ? `${v.toLocaleString()}` : '-', }, { title: '状态', dataIndex: 'status', width: 80, render: (v: string) => {v === 'predicted' ? '预测' : v === 'scheduled' ? '已排期' : v === 'completed' ? '完成' : '误报'}, }, ]; const calendarSchedule = schedule.reduce((acc: any, item: any) => { if (item.predicted_failure_date) { const key = dayjs(item.predicted_failure_date).format('YYYY-MM-DD'); if (!acc[key]) acc[key] = []; acc[key].push(item); } return acc; }, {} as Record); const dateCellRender = (value: dayjs.Dayjs) => { const key = value.format('YYYY-MM-DD'); const items = calendarSchedule[key]; if (!items) return null; return (
    {items.slice(0, 2).map((item: any, i: number) => (
  • {item.device_name}} />
  • ))} {items.length > 2 &&
  • +{items.length - 2} more
  • }
); }; return (
预测性维护
`共 ${t} 条` }} size="small" /> ), }, { key: 'calendar', label: '维护日历', children: ( { if (info.type === 'date') return dateCellRender(value); return null; }} /> ), }, ]} />
); } // ── Tab: Insights Board ──────────────────────────────────────────── function InsightsBoard() { const [insights, setInsights] = useState({ total: 0, items: [] }); const [loading, setLoading] = useState(true); useEffect(() => { loadInsights(); }, []); const loadInsights = async () => { setLoading(true); try { const data = await getAiOpsInsights({ page_size: 50 }); setInsights(data || { total: 0, items: [] }); } catch { message.error('加载洞察数据失败'); } finally { setLoading(false); } }; const handleGenerate = async () => { message.loading({ content: '正在生成运营洞察...', key: 'ins' }); try { await triggerInsights(); message.success({ content: '洞察生成完成', key: 'ins' }); loadInsights(); } catch { message.error({ content: '生成失败', key: 'ins' }); } }; const typeIcons: Record = { efficiency_trend: , cost_anomaly: , performance_comparison: , seasonal_pattern: , }; const BarChartOutlined = () => {"#"}; return (
运营洞察
{loading ? : insights.items?.length === 0 ? ( ) : ( {insights.items?.map((insight: any) => (
{insight.impact_level === 'high' ? '高影响' : insight.impact_level === 'medium' ? '中影响' : '低影响'} {insightTypeLabels[insight.insight_type] || insight.insight_type}
{insight.title}
{insight.description}
{insight.actionable && insight.recommended_action && (
{insight.recommended_action}
)}
{dayjs(insight.generated_at).format('YYYY-MM-DD HH:mm')} {insight.valid_until && ` | 有效至 ${dayjs(insight.valid_until).format('MM-DD')}`}
))} )} ); } // ── Main Page ────────────────────────────────────────────────────── export default function AIOperations() { const [dashboard, setDashboard] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { loadDashboard(); }, []); const loadDashboard = async () => { setLoading(true); try { const data = await getAiOpsDashboard(); setDashboard(data); } catch { /* initial load may fail if no data */ } finally { setLoading(false); } }; const health = dashboard?.health || {}; const anomalyStats = dashboard?.anomalies?.stats || {}; return (
{/* Overview cards */}
} loading={loading} /> } loading={loading} /> } loading={loading} /> } loading={loading} /> {/* Tabs */} 设备健康, children: , }, { key: 'anomalies', label: 异常检测, children: , }, { key: 'diagnostics', label: 智能诊断, children: , }, { key: 'maintenance', label: 预测维护, children: , }, { key: 'insights', label: 运营洞察, children: , }, ]} /> ); }