Add configurable data providers and localize frontend UI

This commit is contained in:
2026-03-15 00:55:12 +08:00
parent 12de93aa30
commit d233a3f55d
38 changed files with 1936 additions and 1038 deletions

View File

@@ -3,7 +3,7 @@ import Header from './Header.jsx';
export default function AboutModal({ onClose }) {
const [isClosing, setIsClosing] = useState(false);
const [language, setLanguage] = useState('en'); // 'en' or 'zh'
const [language] = useState('zh');
const handleClose = () => {
setIsClosing(true);
@@ -188,79 +188,14 @@ export default function AboutModal({ onClose }) {
{/* Content */}
<div style={contentStyle} onClick={(e) => e.stopPropagation()}>
{/* Language Switch */}
<div style={languageSwitchStyle}>
<span
style={getLangStyle(language === 'zh')}
onClick={() => setLanguage('zh')}
style={getLangStyle(true)}
>
中文
</span>
<span style={{ padding: '0 4px', color: '#999' }}></span>
<span
style={getLangStyle(language === 'en')}
onClick={() => setLanguage('en')}
>
EN
</span>
</div>
{language === 'en' ? (
// English Content
<>
<div style={{ marginBottom: '40px', fontSize: '15px', fontWeight: 600 }}>
{content.en.question}
<span style={highlight}>{content.en.questionHighlight}</span>
{content.en.questionEnd}
</div>
<div style={{ marginBottom: '30px' }}>
{content.en.intro}
<span style={highlight}>{content.en.introHighlight1}</span>
{content.en.introContinue}
<span style={highlight}>{content.en.introHighlight2}</span>
{content.en.introContinue2}
</div>
<div style={{ marginBottom: '25px' }}>
<span style={highlight}>{content.en.point1Highlight}</span>
{content.en.point1}
</div>
<div style={{ marginBottom: '25px' }}>
<span style={highlight}>{content.en.point2Highlight}</span>
{content.en.point2}
</div>
<div style={{ marginBottom: '40px' }}>
<span style={highlight}>{content.en.point3Highlight}</span>
{content.en.point3}
</div>
<div style={{ marginBottom: '25px', opacity: 0.7 }}>
Everything is fully open-source. Built on{' '}
<a
href="https://github.com/agentscope-ai"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
AgentScope
</a>
, using{' '}
<a
href="https://github.com/agentscope-ai/ReMe"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
ReMe
</a>
{' '}for memory management.
</div>
</>
) : (
// Chinese Content
<>
<div style={{ marginBottom: '30px' }}>
@@ -309,7 +244,7 @@ export default function AboutModal({ onClose }) {
</div>
<div style={{ marginBottom: '10px', opacity: 0.7 }}>
我们已经在github上开源
我们已经在 GitHub 上开源
</div>
<div style={{ marginBottom: '25px', opacity: 0.7 }}>
EvoTraders 基于{' '}
@@ -337,7 +272,6 @@ export default function AboutModal({ onClose }) {
你可以在此找到完整项目与示例
</div>
</>
)}
<div style={{ marginTop: '40px' }}>
<a
@@ -351,11 +285,10 @@ export default function AboutModal({ onClose }) {
</div>
<div style={closeHintStyle} onClick={handleClose}>
{language === 'en' ? 'Click here to close' : '点击此处关闭'}
点击此处关闭
</div>
</div>
</div>
</>
);
}

View File

@@ -124,7 +124,7 @@ export default function AgentCard({ agent, onClose, isClosing }) {
</div>
{rankMedal && !isPortfolioManager && (
<div style={{ fontSize: 18 }}>
{rankMedal.emoji} Rank #{agent.rank}
{rankMedal.emoji} {agent.rank}
</div>
)}
</div>
@@ -188,7 +188,7 @@ export default function AgentCard({ agent, onClose, isClosing }) {
position: 'relative',
cursor: 'help'
}}
title={`Model: ${agent.modelName}\nProvider: ${modelInfo.provider}`}>
title={`模型:${agent.modelName}\n提供方:${modelInfo.provider}`}>
<div style={{
fontSize: 10,
fontWeight: 700,
@@ -272,7 +272,7 @@ export default function AgentCard({ agent, onClose, isClosing }) {
lineHeight: 1,
marginBottom: 2
}}>
{overallWinRate != null ? `${(overallWinRate * 100).toFixed(1)}%` : 'N/A'}
{overallWinRate != null ? `${(overallWinRate * 100).toFixed(1)}%` : '暂无'}
</div>
<div style={{
fontSize: 9,
@@ -318,7 +318,7 @@ export default function AgentCard({ agent, onClose, isClosing }) {
marginBottom: 2,
lineHeight: 1
}}>
{bullWinRate != null ? `${(bullWinRate * 100).toFixed(1)}%` : 'N/A'}
{bullWinRate != null ? `${(bullWinRate * 100).toFixed(1)}%` : '暂无'}
</div>
<div style={{
fontSize: 9,
@@ -355,7 +355,7 @@ export default function AgentCard({ agent, onClose, isClosing }) {
marginBottom: 2,
lineHeight: 1
}}>
{bearWinRate != null ? `${(bearWinRate * 100).toFixed(1)}%` : 'N/A'}
{bearWinRate != null ? `${(bearWinRate * 100).toFixed(1)}%` : '暂无'}
</div>
<div style={{
fontSize: 9,
@@ -439,7 +439,7 @@ export default function AgentCard({ agent, onClose, isClosing }) {
fontSize: 8,
color: '#555555'
}}>
{signal.date?.substring(5, 10) || 'N/A'}
{signal.date?.substring(5, 10) || '暂无'}
</div>
<div style={{
fontSize: resultFontSize,
@@ -514,4 +514,3 @@ export default function AgentCard({ agent, onClose, isClosing }) {
</div>
);
}

View File

@@ -525,7 +525,7 @@ function MessageItem({ message, itemId, isHighlighted, getAgentModelInfo }) {
const colors = message.agent === 'Memory' ? MESSAGE_COLORS.memory :
getAgentColors(message.agentId, message.agent);
const title = message.agent === 'Memory' ? '记忆' : message.agent || 'AGENT';
const title = message.agent === 'Memory' ? '记忆' : message.agent || '智能体';
const agentModelData = message.agentId && getAgentModelInfo ?
getAgentModelInfo(message.agentId) :

View File

@@ -13,24 +13,24 @@ export default function PerformanceView({ leaderboard }) {
{/* Agent Performance Section */}
<div className="section">
<div className="section-header">
<h2 className="section-title">Agent Performance - Signal Accuracy</h2>
<h2 className="section-title">分析师表现 - 信号准确率</h2>
</div>
{rankedAgents.length === 0 ? (
<div className="empty-state">No leaderboard data available</div>
<div className="empty-state">暂无排行榜数据</div>
) : (
<div className="table-wrapper">
<table className="data-table">
<thead>
<tr>
<th>Rank</th>
<th>Agent</th>
<th>Win Rate</th>
<th>Bull Signals</th>
<th>Bull Win Rate</th>
<th>Bear Signals</th>
<th>Bear Win Rate</th>
<th>Total Signals</th>
<th>排名</th>
<th>分析师</th>
<th>胜率</th>
<th>看涨信号</th>
<th>看涨胜率</th>
<th>看跌信号</th>
<th>看跌胜率</th>
<th>总信号数</th>
</tr>
</thead>
<tbody>
@@ -66,27 +66,27 @@ export default function PerformanceView({ leaderboard }) {
<div style={{ fontSize: 10, color: '#666666' }}>{agent.role}</div>
</td>
<td style={{ fontWeight: 700, color: overallColor }}>
{overallWinRate != null ? `${(overallWinRate * 100).toFixed(1)}%` : 'N/A'}
{overallWinRate != null ? `${(overallWinRate * 100).toFixed(1)}%` : '暂无'}
</td>
<td>
<div style={{ fontSize: 12 }}>{bullTotal} signals</div>
<div style={{ fontSize: 10, color: '#666666' }}>{bullWins} wins</div>
<div style={{ fontSize: 12 }}>{bullTotal} 个信号</div>
<div style={{ fontSize: 10, color: '#666666' }}>{bullWins} 次命中</div>
{bullUnknown > 0 && (
<div style={{ fontSize: 10, color: '#999999' }}>{bullUnknown} unknown</div>
<div style={{ fontSize: 10, color: '#999999' }}>{bullUnknown} 条未判定</div>
)}
</td>
<td style={{ color: bullWinRate != null ? (bullWinRate >= 0.5 ? '#00C853' : '#999999') : '#999999' }}>
{bullWinRate != null ? `${(bullWinRate * 100).toFixed(1)}%` : 'N/A'}
{bullWinRate != null ? `${(bullWinRate * 100).toFixed(1)}%` : '暂无'}
</td>
<td>
<div style={{ fontSize: 12 }}>{bearTotal} signals</div>
<div style={{ fontSize: 10, color: '#666666' }}>{bearWins} wins</div>
<div style={{ fontSize: 12 }}>{bearTotal} 个信号</div>
<div style={{ fontSize: 10, color: '#666666' }}>{bearWins} 次命中</div>
{bearUnknown > 0 && (
<div style={{ fontSize: 10, color: '#999999' }}>{bearUnknown} unknown</div>
<div style={{ fontSize: 10, color: '#999999' }}>{bearUnknown} 条未判定</div>
)}
</td>
<td style={{ color: bearWinRate != null ? (bearWinRate >= 0.5 ? '#00C853' : '#999999') : '#999999' }}>
{bearWinRate != null ? `${(bearWinRate * 100).toFixed(1)}%` : 'N/A'}
{bearWinRate != null ? `${(bearWinRate * 100).toFixed(1)}%` : '暂无'}
</td>
<td style={{ fontWeight: 700 }}>{totalSignals}</td>
</tr>
@@ -102,7 +102,7 @@ export default function PerformanceView({ leaderboard }) {
{rankedAgents.length > 0 && rankedAgents.some(agent => agent.signals && agent.signals.length > 0) && (
<div className="section" style={{ marginTop: 32 }}>
<div className="section-header">
<h2 className="section-title">Signal History</h2>
<h2 className="section-title">信号历史</h2>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: 20 }}>
@@ -150,7 +150,7 @@ export default function PerformanceView({ leaderboard }) {
const hasRealReturn = typeof realReturnValue === 'number' && Number.isFinite(realReturnValue);
const realReturnDisplay = hasRealReturn
? `${realReturnValue >= 0 ? '+' : ''}${(realReturnValue * 100).toFixed(2)}%`
: 'Unknown';
: '未判定';
const realReturnColor = hasRealReturn
? (realReturnValue >= 0 ? '#00C853' : '#FF1744')
: '#999999';
@@ -189,7 +189,7 @@ export default function PerformanceView({ leaderboard }) {
color: isBull ? '#00C853' : isBear ? '#FF1744' : '#999999',
fontSize: 12
}}>
{isBull ? 'Bull' : isBear ? 'Bear' : 'Neutral'}
{isBull ? '看涨' : isBear ? '看跌' : '中性'}
</span>
{!isNeutral && (
<span style={{
@@ -222,7 +222,7 @@ export default function PerformanceView({ leaderboard }) {
color: '#666666',
textAlign: 'center'
}}>
Total: {sortedSignals.length} signals
{sortedSignals.length} 条信号
</div>
</div>
);
@@ -233,4 +233,3 @@ export default function PerformanceView({ leaderboard }) {
</div>
);
}

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
import { LLM_MODEL_LOGOS } from '../config/constants';
export default function RulesView() {
const [language, setLanguage] = useState('en'); // 'en' or 'zh'
const [language] = useState('zh');
const [scale, setScale] = useState(1);
const containerRef = useRef(null);
const contentRef = useRef(null);
@@ -197,19 +197,19 @@ export default function RulesView() {
section2Title: "Agent 决策机制",
tradingProcess: "交易流程",
tradingDesc: "Agents 进行日频交易并持续跟踪组合净值。每天最终交易决策前,agents 经历三个关键阶段:",
tradingDesc: "智能体以日频进行交易并持续跟踪组合净值。每天最终交易决策前,经历三个关键阶段:",
analysisPhase: "• 分析阶段",
analysisDesc: "所有 agents 根据各自的工具和信息独立分析并形成判断。",
analysisDesc: "所有智能体根据各自的工具和信息独立分析并形成判断。",
communicationPhase: "• 沟通阶段",
commIntro: "提供了多种沟通渠道1v1 私聊 / 1vN 通知 / NvN 会议",
decisionPhase: "• 决策阶段",
decisionDesc: "由 portfolio manager 汇总所有信息,并给出最终的团队交易。analysts 给出的原始交易信号仅个人维度排名。",
decisionDesc: "由投资经理汇总所有信息,并给出最终的团队交易决策。分析师给出的原始交易信号仅用于个人维度排名。",
reflectionTitle: "学习与进化",
reflectionDesc: "Agents 根据当日实际收益反思决策、总结经验,并存入 ",
reflectionDesc: "智能体根据当日实际收益反思决策、总结经验,并存入 ",
remeLink: "ReMe",
reflectionDesc2: " 记忆框架以持续改进。",
@@ -219,14 +219,14 @@ export default function RulesView() {
chartDesc: "追踪组合收益曲线 vs. 基准策略(等权、市值加权、动量)。用于评估整体策略有效性。",
rankingTitle: "• 分析师排名",
rankingDesc: "在 Trading Room 点击头像查看分析师表现(胜率、牛/熊市胜率)。用于了解哪些分析师提供最有价值的洞察。",
rankingDesc: "在交易室点击头像查看分析师表现(胜率、牛/熊市胜率),用来了解哪些分析师提供最有价值的洞察。",
statsTitle: "• 统计数据",
statsDesc: "详细的持仓和交易历史。用于深入分析仓位管理和执行质量。",
callToAction: "在 ",
callToAction: "在 ",
repoLink: "GitHub",
callToAction2: " 上 fork 并自定义!"
callToAction2: " 上 Fork 并自行定制。"
}
};
@@ -234,136 +234,14 @@ export default function RulesView() {
<div ref={containerRef} style={containerStyle}>
<div ref={contentRef} style={contentWrapperStyle}>
<div style={innerContentStyle}>
{/* Language Switch */}
<div style={languageSwitchStyle}>
<span
style={getLangStyle(language === 'zh')}
onClick={() => setLanguage('zh')}
style={getLangStyle(true)}
>
中文
</span>
<span style={{ padding: '0 4px', color: '#999' }}></span>
<span
style={getLangStyle(language === 'en')}
onClick={() => setLanguage('en')}
>
EN
</span>
</div>
{language === 'en' ? (
// English Content
<>
{/* Section 1: Agent Setup */}
<div style={sectionTitleStyle}>{content.en.section1Title}</div>
{/* Roles */}
<div style={{ marginBottom: '8px', fontSize: '12px' }}>
<div style={{ marginBottom: '3px' }}>
<span style={{ fontWeight: 600 }}>{content.en.pmRole}:</span> {content.en.pmDesc}
</div>
<div style={{ marginBottom: '3px' }}>
<span style={{ fontWeight: 600 }}>{content.en.rmRole}:</span> {content.en.rmDesc}
</div>
<div style={{ marginBottom: '3px' }}>
<span style={{ fontWeight: 600 }}>{content.en.analystsRole}:</span> {content.en.analystsDesc}
</div>
</div>
{/* Analysts with AI Models */}
<div style={{ marginLeft: '10px', marginBottom: '8px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px 14px', fontSize: '11px' }}>
{content.en.analysts.map(analyst => {
const logo = llmLogos.find(l => l.name === analyst.modelKey);
return (
<div key={analyst.name} style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
{logo && (
<img
src={logo.url}
alt={logo.label}
style={{
height: '16px',
width: 'auto',
objectFit: 'contain'
}}
/>
)}
<span style={{ fontWeight: 600 }}>{analyst.name}</span>
<span style={{ color: '#666' }}>- {analyst.model}</span>
</div>
);
})}
</div>
<div style={{ marginBottom: '10px', fontSize: '11px', fontStyle: 'italic', opacity: 0.8 }}>
{content.en.callToAction}
<a
href="https://github.com/agentscope-ai/agentscope-samples"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
{content.en.repoLink}
</a>
{content.en.callToAction2}
</div>
{/* Section 2: Agent Decision Mechanism */}
<div style={sectionTitleStyle}>{content.en.section2Title}</div>
<div style={{ marginBottom: '6px' }}>
<div style={{ fontWeight: 600, marginBottom: '3px' }}>{content.en.tradingProcess}</div>
<div style={{ marginBottom: '6px', fontSize: '12px' }}>{content.en.tradingDesc}</div>
<div style={subsectionStyle}>
<div style={{ marginBottom: '4px', fontSize: '12px' }}>
<span style={highlight}>{content.en.analysisPhase.replace('• ', '')}:</span> {content.en.analysisDesc}
</div>
<div style={{ marginBottom: '4px', fontSize: '12px' }}>
<span style={highlight}>{content.en.communicationPhase.replace('• ', '')}:</span> {content.en.commIntro}
</div>
<div style={{ fontSize: '12px' }}>
<span style={highlight}>{content.en.decisionPhase.replace('• ', '')}:</span> {content.en.decisionDesc}
</div>
</div>
</div>
<div style={{ marginBottom: '10px' }}>
<div style={{ fontWeight: 600, marginBottom: '3px' }}>{content.en.reflectionTitle}</div>
<div style={{ fontSize: '12px' }}>
{content.en.reflectionDesc}
<a
href="https://github.com/agentscope-ai/ReMe"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
{content.en.remeLink}
</a>
{content.en.reflectionDesc2}
</div>
</div>
{/* Section 3: Performance Evaluation */}
<div style={sectionTitleStyle}>{content.en.section3Title}</div>
<div style={subsectionStyle}>
<div style={{ marginBottom: '3px', fontSize: '12px' }}>
<span style={{ fontWeight: 600 }}>{content.en.chartTitle.replace('• ', '')}:</span> {content.en.chartDesc}
</div>
<div style={{ marginBottom: '3px', fontSize: '12px' }}>
<span style={{ fontWeight: 600 }}>{content.en.rankingTitle.replace('• ', '')}:</span> {content.en.rankingDesc}
</div>
<div style={{ fontSize: '12px' }}>
<span style={{ fontWeight: 600 }}>{content.en.statsTitle.replace('• ', '')}:</span> {content.en.statsDesc}
</div>
</div>
</>
) : (
// Chinese Content
<>
{/* 第一部分Agent 设定 */}
@@ -475,7 +353,6 @@ export default function RulesView() {
</div>
</div>
</>
)}
</div>
</div>
</div>

View File

@@ -243,7 +243,7 @@ export default function StatisticsView({ trades, holdings, stats, baseline_vw, e
}}>
{pmWinRateData?.winRate != null
? `${(pmWinRateData.winRate * 100).toFixed(1)}%`
: 'N/A'}
: '暂无'}
</div>
{pmWinRateData && (
<div style={{