import React from 'react'; import { ASSETS } from '../config/constants'; import { getModelIcon, getShortModelName } from '../utils/modelIcons'; import LobeModelLogo from './LobeModelLogo.jsx'; /** * 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 isValuationAnalyst = agent.id === 'valuation_analyst'; 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} 第 {agent.rank} 名
)}
{/* Risk Manager Note */} {isRiskManager && (
ⓘ 风控经理䞓泚于风险管理䞍参䞎预测准确率排名。
)} {/* Portfolio Manager Note */} {isPortfolioManager && (
ⓘ 投资经理绌合所有分析垈建议提䟛团队最终亀易信号䞍参䞎排名。
)} {/* Model Info Card */} {agent.modelName && (
暡型
{agent.modelName || modelInfo.logoPath ? ( ) : (
🀖
)}
{shortModelName}
{modelInfo.provider}
)} {/* Overall Win Rate */} {!isRiskManager && !isPortfolioManager && (
胜率
{overallWinRate != null ? `${(overallWinRate * 100).toFixed(1)}%` : '暂无'}
{bullWins + bearWins}胜 / {evaluatedTotal}评
评䌰: 总评䌰倚空信号数。{'\n'}胜率 = 正确信号 / 总评䌰信号
)} {/* Bull Stats */} {!isRiskManager && !isPortfolioManager && (
牛垂胜率
= 0.5 ? '#00C853' : '#333333') : '#555555', marginBottom: 2, lineHeight: 1 }}> {bullWinRate != null ? `${(bullWinRate * 100).toFixed(1)}%` : '暂无'}
{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)}%` : '暂无'}
{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) || '暂无'}
{resultDisplay}
); })} {/* Info card explaining signal display */}
ⓘ 诎明
仅星瀺最近5䞪亀易日(1呚)的信号
)} {/* Valuation Results Card - Only show for valuation_analyst */} {isValuationAnalyst && agent.signals && agent.signals.length > 0 && (
䌰倌分析
{agent.signals .filter(signal => signal && signal.intrinsic_value != null) .slice(0, 5) .map((signal, idx) => { const fairValue = signal.fair_value_range; const hasValuation = signal.intrinsic_value || fairValue; if (!hasValuation) return null; return (
{signal.ticker}
{signal.intrinsic_value && (
内圚 ${signal.intrinsic_value.toFixed(2)}
)} {signal.value_gap_pct != null && (
0 ? '#00C853' : '#FF1744', fontSize: 9 }}> {signal.value_gap_pct > 0 ? '+' : ''}{signal.value_gap_pct.toFixed(1)}%
)} {fairValue && (
区闎 ${fairValue.bear?.toFixed(0) || '?'}- ${fairValue.bull?.toFixed(0) || '?'}
)} {signal.valuation_methods && signal.valuation_methods.length > 0 && (
{signal.valuation_methods[0]}
)}
); })}
)}
); }