/** * MatchPredictPanel: 预测弹窗全套(过程可视化 / 结果版面 / 专家意见)。 * * D3: 从 Matches.tsx 拆出,渲染逻辑原样搬迁。对外只导出 PredictModal; * PredictionPanel 复用 Prediction 的 embedded 模式由弹窗内渲染。 */ import { useEffect, useState } from 'react' import TeamSideTag from '../../../components/TeamSideTag' import type { Match, Prediction } from '../types' import { AGENT_LABELS } from '../types' import { AgentsPanel } from './AgentsPanel' import { OutcomePanel } from './OutcomePanel' import { ReasoningPanel } from './ReasoningPanel' function Spinner({ className = '' }: { className?: string }) { return ( ) } /** 胜平负一行文字:选中的红字加方块标记,未选中的退灰 */ .** * P3-1:PredictionPanel 不再自绘,改为组合三个子组件: * OutcomePanel(比分/胜平负/成本) / AgentsPanel(专家意见) / ReasoningPanel(终裁/降级)。 * 渲染输出与拆分前完全一致(仅降级警示 + 报头 + 元信息仍在此处)。 */ function PredictionPanel({ prediction, match, embedded = false, }: { prediction: Prediction match: Match /** 弹窗嵌入模式:弹窗已提供报头,这里省略自带版头 */ embedded?: boolean }) { const homeName = match.home_team_zh || match.home_team const awayName = match.away_team_zh || match.away_team const degraded = prediction.status === 'degraded' || prediction.status === 'failed' const reports = prediction.agent_outputs ?? [] const okReports = reports.filter(r => r.status === 'ok') return (
{!embedded && (

预测版 · {homeName} {awayName}

{prediction.provider} / {prediction.model} {prediction.latency_ms !== null && ` · ${(prediction.latency_ms / 1000).toFixed(1)}s`}
)}
{degraded && (

{prediction.status === 'failed' ? '预测失败' : '预测降级(degraded)'}

{prediction.reasoning || '所有专家均无有效数据或调用失败,无法生成可靠比分。'}

)} {!degraded && }

`多专家模式 · ${okReports.length}/${reports.length} 路有效` {prediction.prompt_version && ` · prompt ${prediction.prompt_version}`}

) } function PredictProgress() { const [elapsed, setElapsed] = useState(0) useEffect(() => { const t = setInterval(() => setElapsed(e => e + 0.5), 500) return () => clearInterval(t) }, []) // 阶段阈值(秒): 切片 → 专家(各路依次点亮) → 终裁 const SLICE_END = 3 const AGENT_START = 4 const AGENT_STEP = 8 // 每路专家约 8s 点亮一路 const AGG_START = AGENT_START + AGENT_STEP * 5 const agents = ['form', 'stats', 'home_away', 'standings', 'h2h'] const phase = elapsed < SLICE_END ? 'slice' : elapsed < AGG_START ? 'agents' : 'agg' const pct = Math.min(95, Math.round((elapsed / 70) * 100)) return (
{/* 阶段标题 */}
{phase === 'slice' && '正在组装比赛数据切片'} {phase === 'agents' && '五路专家并行分析中'} {phase === 'agg' && '终裁专家汇总裁定中'} {elapsed.toFixed(0)}s
{/* 进度条:渐进式,不封顶到 100% */}
{/* 专家灯序(多专家模式) */}

五路专家并行分析后终裁,约需 30-90 秒;多专家调用消耗较多 token,请按需使用。关闭窗口即取消

提示:每分钟限 10 次预测,耗尽后需等待下一分钟。

) } export function PredictModal({ match, predicting, prediction, error, onClose, }: { match: Match predicting: boolean prediction: Prediction | null error: string | null onClose: () => void }) { const homeName = match.home_team_zh || match.home_team const awayName = match.away_team_zh || match.away_team useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', h) return () => document.removeEventListener('keydown', h) }, [onClose]) return (
{ if (e.target === e.currentTarget) onClose() }} >
{/* 弹窗报头 */}

预测版 · {homeName} {awayName}

{/* 弹窗体(小屏可滚动) */}
{predicting ? ( ) : error ? (

预测失败

{error}

) : prediction ? ( ) : null}
) }