import React from 'react'; import { ASSETS } from '../config/constants'; import { getModelIcon, getShortModelName } from '../utils/modelIcons'; /** * Get rank medal/trophy */ function getRankMedal(rank) { if (rank === 1) return { emoji: '🏆', color: '#FFD700', label: '金牌' }; if (rank === 2) return { emoji: '🥈', color: '#C0C0C0', label: '银牌' }; if (rank === 3) return { emoji: '🥉', color: '#CD7F32', label: '铜牌' }; return { emoji: `#${rank}`, color: '#333333', label: `#${rank}` }; } /** * Agent Performance Card Component * Horizontal dropdown panel displayed below the agent indicator bar */ export default function AgentCard({ agent, onClose, isClosing }) { if (!agent) return null; const bullTotal = agent.bull?.n || 0; const bullWins = agent.bull?.win || 0; const bullUnknown = agent.bull?.unknown || 0; const bearTotal = agent.bear?.n || 0; const bearWins = agent.bear?.win || 0; const bearUnknown = agent.bear?.unknown || 0; const totalSignals = bullTotal + bearTotal; const evaluatedBull = Math.max(bullTotal - bullUnknown, 0); const evaluatedBear = Math.max(bearTotal - bearUnknown, 0); const evaluatedTotal = evaluatedBull + evaluatedBear; const bullWinRate = evaluatedBull > 0 ? (bullWins / evaluatedBull) : null; const bearWinRate = evaluatedBear > 0 ? (bearWins / evaluatedBear) : null; const overallWinRate = agent.winRate != null ? agent.winRate : (evaluatedTotal > 0 ? ((bullWins + bearWins) / evaluatedTotal) : null); const overallColor = overallWinRate != null ? (overallWinRate >= 0.5 ? '#00C853' : '#FF1744') : '#555555'; const rankMedal = agent.rank ? getRankMedal(agent.rank) : null; const isPortfolioManager = agent.id === 'portfolio_manager'; const isRiskManager = agent.id === 'risk_manager'; const displayName = isPortfolioManager ? '团队' : agent.name; // Get model icon configuration const modelInfo = getModelIcon(agent.modelName, agent.modelProvider); const shortModelName = getShortModelName(agent.modelName); return (
{/* Horizontal scrollable content */}
{/* Agent Info with Rank */}
{isPortfolioManager ? ( Team ) : agent.avatar ? ( {agent.name} ) : null}
{displayName}
{rankMedal && !isPortfolioManager && (
{rankMedal.emoji} Rank #{agent.rank}
)}
{/* Risk Manager Note */} {isRiskManager && (
ⓘ 风控经理专注于风险管理,不参与预测准确率排名。
)} {/* Portfolio Manager Note */} {isPortfolioManager && (
ⓘ 投资经理综合所有分析师建议,提供团队最终交易信号,不参与排名。
)} {/* Model Info Card */} {agent.modelName && (
模型
{modelInfo.logoPath ? ( {modelInfo.provider} ) : (
🤖
)}
{shortModelName}
{modelInfo.provider}
)} {/* Overall Win Rate */} {!isRiskManager && !isPortfolioManager && (
胜率
{overallWinRate != null ? `${(overallWinRate * 100).toFixed(1)}%` : 'N/A'}
{bullWins + bearWins}胜 / {evaluatedTotal}评
评估: 总评估多空信号数。{'\n'}胜率 = 正确信号 / 总评估信号
)} {/* Bull Stats */} {!isRiskManager && !isPortfolioManager && (
牛市胜率
= 0.5 ? '#00C853' : '#333333') : '#555555', marginBottom: 2, lineHeight: 1 }}> {bullWinRate != null ? `${(bullWinRate * 100).toFixed(1)}%` : 'N/A'}
{bullWins}胜 / {evaluatedBull}评 {bullUnknown > 0 && ` / ${bullUnknown}P`}
)} {/* Bear Stats */} {!isRiskManager && !isPortfolioManager && (
熊市胜率
= 0.5 ? '#00C853' : '#333333') : '#555555', marginBottom: 2, lineHeight: 1 }}> {bearWinRate != null ? `${(bearWinRate * 100).toFixed(1)}%` : 'N/A'}
{bearWins}胜 / {evaluatedBear}评 {bearUnknown > 0 && ` / ${bearUnknown}P`}
)} {/* Recent Signals - Horizontal scroll */} {agent.signals && agent.signals.length > 0 && (
{[...agent.signals] .filter(signal => signal && signal.signal) .sort((a, b) => { // Sort by date descending (newest first) const dateA = a.date || ''; const dateB = b.date || ''; return dateB.localeCompare(dateA); }) .slice(0, 35) .map((signal, idx) => { const signalType = signal.signal.toLowerCase(); const isBull = signalType.includes('bull') || signalType === 'long'; const isBear = signalType.includes('bear') || signalType === 'short'; const isNeutral = (!isBull && !isBear) || signalType.includes('neutral') || signalType === 'hold'; const isCorrect = signal.is_correct === true; const isUnknown = signal.is_correct === 'unknown' || signal.is_correct === null; // Determine result symbol/text and color: unknown has priority over neutral let resultDisplay; let resultColor = '#555555'; let resultFontSize = 18; if (isNeutral) { resultDisplay = '-'; resultColor = '#555555'; // Gray for neutral } else if (isUnknown) { resultDisplay = '?'; resultColor = '#FFA726'; // Orange for unknown resultFontSize = 14; // Smaller font for text } else { resultDisplay = isCorrect ? '✓' : '✗'; resultColor = isCorrect ? '#00C853' : '#FF1744'; // Green for correct, Red for wrong } return (
{signal.ticker}
{isBull ? '看涨' : isBear ? '看跌' : '中性'}
{signal.date?.substring(5, 10) || 'N/A'}
{resultDisplay}
); })} {/* Info card explaining signal display */}
ⓘ 说明
仅显示最近5个交易日(1周)的信号
)}
); }