feat(dashboard): 在交易者详情页显示系统提示词模板名称 (#775)

* feat(dashboard): display system prompt template and extract color constant
* style(api): format server.go with go fmt
This commit is contained in:
Lawrence Liu
2025-11-13 01:38:55 +08:00
committed by tangmengqiu
parent ced6c3d9de
commit 217ccb08dd
5 changed files with 139 additions and 49 deletions

View File

@@ -1283,6 +1283,7 @@ func (s *Server) handleTraderList(c *gin.Context) {
"exchange_id": trader.ExchangeID, "exchange_id": trader.ExchangeID,
"is_running": isRunning, "is_running": isRunning,
"initial_balance": trader.InitialBalance, "initial_balance": trader.InitialBalance,
"system_prompt_template": trader.SystemPromptTemplate,
}) })
} }
@@ -2180,6 +2181,7 @@ func (s *Server) handlePublicTraderList(c *gin.Context) {
"total_pnl_pct": trader["total_pnl_pct"], "total_pnl_pct": trader["total_pnl_pct"],
"position_count": trader["position_count"], "position_count": trader["position_count"],
"margin_used_pct": trader["margin_used_pct"], "margin_used_pct": trader["margin_used_pct"],
"system_prompt_template": trader["system_prompt_template"],
}) })
} }

View File

@@ -225,3 +225,81 @@ func TestUpdateTraderRequest_CompleteFields(t *testing.T) {
t.Errorf("SystemPromptTemplate mismatch: expected %q, got %q", "nof1", req.SystemPromptTemplate) t.Errorf("SystemPromptTemplate mismatch: expected %q, got %q", "nof1", req.SystemPromptTemplate)
} }
} }
// TestTraderListResponse_SystemPromptTemplate 测试 handleTraderList API 返回的 trader 对象是否包含 system_prompt_template 字段
func TestTraderListResponse_SystemPromptTemplate(t *testing.T) {
// 模拟 handleTraderList 中的 trader 对象构造
trader := &config.TraderRecord{
ID: "trader-001",
UserID: "user-1",
Name: "My Trader",
AIModelID: "gpt-4",
ExchangeID: "binance",
InitialBalance: 5000,
SystemPromptTemplate: "nof1",
IsRunning: true,
}
// 构造 API 响应对象(与 api/server.go 中的逻辑一致)
response := map[string]interface{}{
"trader_id": trader.ID,
"trader_name": trader.Name,
"ai_model": trader.AIModelID,
"exchange_id": trader.ExchangeID,
"is_running": trader.IsRunning,
"initial_balance": trader.InitialBalance,
"system_prompt_template": trader.SystemPromptTemplate,
}
// ✅ 验证 system_prompt_template 字段存在
if _, exists := response["system_prompt_template"]; !exists {
t.Errorf("Trader list response is missing 'system_prompt_template' field")
}
// ✅ 验证 system_prompt_template 值正确
if response["system_prompt_template"] != "nof1" {
t.Errorf("Expected system_prompt_template='nof1', got %v", response["system_prompt_template"])
}
}
// TestPublicTraderListResponse_SystemPromptTemplate 测试 handlePublicTraderList API 返回的 trader 对象是否包含 system_prompt_template 字段
func TestPublicTraderListResponse_SystemPromptTemplate(t *testing.T) {
// 模拟 getConcurrentTraderData 返回的 trader 数据
traderData := map[string]interface{}{
"trader_id": "trader-002",
"trader_name": "Public Trader",
"ai_model": "claude",
"exchange": "binance",
"total_equity": 10000.0,
"total_pnl": 500.0,
"total_pnl_pct": 5.0,
"position_count": 3,
"margin_used_pct": 25.0,
"is_running": true,
"system_prompt_template": "default",
}
// 构造 API 响应对象(与 api/server.go handlePublicTraderList 中的逻辑一致)
response := map[string]interface{}{
"trader_id": traderData["trader_id"],
"trader_name": traderData["trader_name"],
"ai_model": traderData["ai_model"],
"exchange": traderData["exchange"],
"total_equity": traderData["total_equity"],
"total_pnl": traderData["total_pnl"],
"total_pnl_pct": traderData["total_pnl_pct"],
"position_count": traderData["position_count"],
"margin_used_pct": traderData["margin_used_pct"],
"system_prompt_template": traderData["system_prompt_template"],
}
// ✅ 验证 system_prompt_template 字段存在
if _, exists := response["system_prompt_template"]; !exists {
t.Errorf("Public trader list response is missing 'system_prompt_template' field")
}
// ✅ 验证 system_prompt_template 值正确
if response["system_prompt_template"] != "default" {
t.Errorf("Expected system_prompt_template='default', got %v", response["system_prompt_template"])
}
}

View File

@@ -600,6 +600,7 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
"position_count": account["position_count"], "position_count": account["position_count"],
"margin_used_pct": account["margin_used_pct"], "margin_used_pct": account["margin_used_pct"],
"is_running": status["is_running"], "is_running": status["is_running"],
"system_prompt_template": trader.GetSystemPromptTemplate(),
} }
case err := <-errorChan: case err := <-errorChan:
// 获取账户信息失败 // 获取账户信息失败
@@ -615,6 +616,7 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
"position_count": 0, "position_count": 0,
"margin_used_pct": 0.0, "margin_used_pct": 0.0,
"is_running": status["is_running"], "is_running": status["is_running"],
"system_prompt_template": trader.GetSystemPromptTemplate(),
"error": "账户数据获取失败", "error": "账户数据获取失败",
} }
case <-ctx.Done(): case <-ctx.Done():
@@ -631,6 +633,7 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
"position_count": 0, "position_count": 0,
"margin_used_pct": 0.0, "margin_used_pct": 0.0,
"is_running": status["is_running"], "is_running": status["is_running"],
"system_prompt_template": trader.GetSystemPromptTemplate(),
"error": "获取超时", "error": "获取超时",
} }
} }

View File

@@ -282,6 +282,8 @@ export default function TraderDashboard() {
) )
} }
const highlightColor = '#60a5fa'
return ( return (
<div> <div>
{/* Trader Header */} {/* Trader Header */}
@@ -346,7 +348,7 @@ export default function TraderDashboard() {
style={{ style={{
color: selectedTrader.ai_model.includes('qwen') color: selectedTrader.ai_model.includes('qwen')
? '#c084fc' ? '#c084fc'
: '#60a5fa', : highlightColor,
}} }}
> >
{getModelDisplayName( {getModelDisplayName(
@@ -355,6 +357,10 @@ export default function TraderDashboard() {
)} )}
</span> </span>
</span> </span>
<span></span>
<span>
Prompt: <span className="font-semibold" style={{ color: highlightColor }}>{selectedTrader.system_prompt_template || '-'}</span>
</span>
{status && ( {status && (
<> <>
<span></span> <span></span>

View File

@@ -94,6 +94,7 @@ export interface TraderInfo {
custom_prompt?: string custom_prompt?: string
use_coin_pool?: boolean use_coin_pool?: boolean
use_oi_top?: boolean use_oi_top?: boolean
system_prompt_template?: string
} }
export interface AIModel { export interface AIModel {