import { useState, useEffect, useRef, useCallback } from 'react' import { useSearchParams } from 'react-router-dom' import { Card, Progress, Badge, Empty, Button, Result, message } from 'antd' import { CheckCircleOutlined, SyncOutlined, CloseCircleOutlined } from '@ant-design/icons' const ANALYSIS_STAGES = [ { key: 'analysts', label: '分析师团队' }, { key: 'research', label: '研究员辩论' }, { key: 'trading', label: '交易员' }, { key: 'risk', label: '风险管理' }, { key: 'portfolio', label: '组合经理' }, ] export default function AnalysisMonitor() { const [searchParams] = useSearchParams() const taskId = searchParams.get('task_id') const [task, setTask] = useState(null) const [wsConnected, setWsConnected] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const wsRef = useRef(null) const fetchInitialState = useCallback(async () => { if (!taskId) return setLoading(true) try { const res = await fetch(`/api/analysis/status/${taskId}`) if (!res.ok) throw new Error('获取任务状态失败') const data = await res.json() setTask(data) } catch (err) { setError(err.message) } finally { setLoading(false) } }, [taskId]) const connectWebSocket = useCallback(() => { if (wsRef.current) wsRef.current.close() const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const host = window.location.host const ws = new WebSocket(`${protocol}//${host}/ws/analysis/${taskId}`) ws.onopen = () => { setWsConnected(true) setError(null) } ws.onmessage = (event) => { try { const data = JSON.parse(event.data) if (data.type === 'progress') { const { type, ...taskData } = data setTask(taskData) } } catch (e) { // ignore parse errors } } ws.onerror = () => { setError('WebSocket连接失败') setWsConnected(false) } ws.onclose = () => { setWsConnected(false) } wsRef.current = ws }, [taskId]) useEffect(() => { if (!taskId) return fetchInitialState() connectWebSocket() return () => { if (wsRef.current) wsRef.current.close() } }, [taskId, fetchInitialState, connectWebSocket]) const formatTime = (seconds) => { const mins = Math.floor(seconds / 60) const secs = seconds % 60 return `${mins}:${secs.toString().padStart(2, '0')}` } const getStageIcon = (status) => { switch (status) { case 'completed': return case 'running': return case 'failed': return default: return } } const getDecisionBadge = (decision) => { if (!decision) return null const badgeClass = decision === 'BUY' ? 'badge-buy' : decision === 'SELL' ? 'badge-sell' : 'badge-hold' return {decision} } if (!taskId) { return (
暂无分析任务
在股票筛选页面选择股票并点击"分析"开始
) } return (
{/* Current Task Card */} 当前分析任务 {error ? '错误' : wsConnected ? '实时连接' : '连接中'} } />
} > {loading ? (
连接中...
) : error && !task ? ( { fetchInitialState() connectWebSocket() }} > 重新连接 } /> ) : task ? ( <> {/* Task Header */}
{task.ticker} {getDecisionBadge(task.decision)}
{/* Progress */}
{task.progress || 0}%
{/* Stages */}
{ANALYSIS_STAGES.map((stage, index) => { const stageState = task.stages?.[index] const status = stageState?.status || 'pending' return (
{getStageIcon(status)} {stage.label}
) })}
{/* Logs */}
实时日志
{task.logs?.length > 0 ? ( task.logs.map((log, i) => (
[{log.time}]{' '} {log.stage}:{' '} {log.message}
)) ) : (
等待日志输出...
)}
) : (
暂无任务数据
)}
) }