import { useState, useEffect, useRef, useCallback } from 'react' import { useSearchParams } from 'react-router-dom' import { Card, Progress, Badge, Empty, Button, Result, message } from 'antd' import DecisionBadge from '../components/DecisionBadge' import { StatusIcon } from '../components/StatusIcon' 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]) if (!taskId) { return (
暂无分析任务
在股票筛选页面选择股票并点击"分析"开始
) } return (
{/* Current Task Card */} 当前分析任务 {error ? '错误' : wsConnected ? '实时连接' : '连接中'} } />
} > {loading ? (
连接中...
) : error && !task ? ( { fetchInitialState() connectWebSocket() }} > 重新连接 } /> ) : task ? ( <> {/* Task Header */}
{task.ticker}
{/* Signal Detail Row */} {task.status === 'completed' && (task.llm_signal || task.quant_signal || task.confidence != null) && (
{task.llm_signal && ( LLM: )} {task.quant_signal && ( Quant: )} {task.confidence != null && ( 置信度: {(task.confidence * 100).toFixed(0)}% )}
)} {/* Progress */}
{task.progress || 0}%
{/* Stages */}
{ANALYSIS_STAGES.map((stage, index) => { const stageState = task.stages?.[index] const status = stageState?.status || 'pending' return (
{stage.label}
) })}
{/* Logs */}
实时日志
{task.logs?.length > 0 ? ( task.logs.map((log, i) => (
[{log.time}]{' '} {log.stage}:{' '} {log.message}
)) ) : (
等待日志输出...
)}
) : (
暂无任务数据
)}
) }