Skip to content

Commit 5a84e0d

Browse files
authored
feat: Add decision limit selector with 5/10/20/50 options (NoFxAiOS#638)
## Summary Allow users to select the number of decision records to display (5/10/20/50) in the Web UI, with persistent storage in localStorage. ## Changes ### Backend - api/server.go: Add 'limit' query parameter support to /api/decisions/latest - Default: 5 (maintains current behavior) - Max: 50 (prevents excessive data loading) - Fully backward compatible ### Frontend - web/src/lib/api.ts: Update getLatestDecisions() to accept limit parameter - web/src/pages/TraderDashboard.tsx: - Add decisionLimit state management with localStorage persistence - Add dropdown selector UI (5/10/20/50 options) - Pass limit to API calls and update SWR cache key ## Time Coverage - 5 records = 15 minutes (default, quick check) - 10 records = 30 minutes (short-term review) - 20 records = 1 hour (medium-term analysis) - 50 records = 2.5 hours (deep pattern analysis)
1 parent caaf0c6 commit 5a84e0d

File tree

3 files changed

+85
-27
lines changed

3 files changed

+85
-27
lines changed

api/server.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,15 @@ func (s *Server) handleLatestDecisions(c *gin.Context) {
14481448
return
14491449
}
14501450

1451-
records, err := trader.GetDecisionLogger().GetLatestRecords(5)
1451+
// 从 query 参数读取 limit,默认 5,最大 50
1452+
limit := 5
1453+
if limitStr := c.Query("limit"); limitStr != "" {
1454+
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 50 {
1455+
limit = l
1456+
}
1457+
}
1458+
1459+
records, err := trader.GetDecisionLogger().GetLatestRecords(limit)
14521460
if err != nil {
14531461
c.JSON(http.StatusInternalServerError, gin.H{
14541462
"error": fmt.Sprintf("获取决策日志失败: %v", err),

web/src/lib/api.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,21 @@ export const api = {
261261
return res.json()
262262
},
263263

264-
// 获取最新决策(支持trader_id)
265-
async getLatestDecisions(traderId?: string): Promise<DecisionRecord[]> {
266-
const url = traderId
267-
? `${API_BASE}/decisions/latest?trader_id=${traderId}`
268-
: `${API_BASE}/decisions/latest`
269-
const res = await httpClient.get(url, getAuthHeaders())
264+
// 获取最新决策(支持trader_id和limit参数)
265+
async getLatestDecisions(
266+
traderId?: string,
267+
limit: number = 5
268+
): Promise<DecisionRecord[]> {
269+
const params = new URLSearchParams()
270+
if (traderId) {
271+
params.append('trader_id', traderId)
272+
}
273+
params.append('limit', limit.toString())
274+
275+
const res = await httpClient.get(
276+
`${API_BASE}/decisions/latest?${params}`,
277+
getAuthHeaders()
278+
)
270279
if (!res.ok) throw new Error('获取最新决策失败')
271280
return res.json()
272281
},

web/src/pages/TraderDashboard.tsx

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ export default function TraderDashboard() {
5454
)
5555
const [lastUpdate, setLastUpdate] = useState<string>('--:--:--')
5656

57+
// 决策记录数量选择(从 localStorage 读取,默认 5)
58+
const [decisionLimit, setDecisionLimit] = useState<number>(() => {
59+
const saved = localStorage.getItem('decisionLimit')
60+
return saved ? parseInt(saved, 10) : 5
61+
})
62+
63+
// 当 limit 变化时保存到 localStorage
64+
const handleLimitChange = (newLimit: number) => {
65+
setDecisionLimit(newLimit)
66+
localStorage.setItem('decisionLimit', newLimit.toString())
67+
}
68+
5769
// 获取trader列表(仅在用户登录时)
5870
const { data: traders, error: tradersError } = useSWR<TraderInfo[]>(
5971
user && token ? 'traders' : null,
@@ -111,8 +123,10 @@ export default function TraderDashboard() {
111123
)
112124

113125
const { data: decisions } = useSWR<DecisionRecord[]>(
114-
selectedTraderId ? `decisions/latest-${selectedTraderId}` : null,
115-
() => api.getLatestDecisions(selectedTraderId),
126+
selectedTraderId
127+
? `decisions/latest-${selectedTraderId}-${decisionLimit}`
128+
: null,
129+
() => api.getLatestDecisions(selectedTraderId, decisionLimit),
116130
{
117131
refreshInterval: 30000,
118132
revalidateOnFocus: false,
@@ -570,27 +584,54 @@ export default function TraderDashboard() {
570584
style={{ animationDelay: '0.2s' }}
571585
>
572586
<div
573-
className="flex items-center gap-3 mb-5 pb-4 border-b"
587+
className="flex items-center justify-between mb-5 pb-4 border-b"
574588
style={{ borderColor: '#2B3139' }}
575589
>
576-
<div
577-
className="w-10 h-10 rounded-xl flex items-center justify-center"
578-
style={{
579-
background: 'linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%)',
580-
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)',
581-
}}
582-
>
583-
<Brain className="w-5 h-5" style={{ color: '#FFFFFF' }} />
590+
<div className="flex items-center gap-3">
591+
<div
592+
className="w-10 h-10 rounded-xl flex items-center justify-center"
593+
style={{
594+
background: 'linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%)',
595+
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)',
596+
}}
597+
>
598+
<Brain className="w-5 h-5" style={{ color: '#FFFFFF' }} />
599+
</div>
600+
<div>
601+
<h2 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
602+
{t('recentDecisions', language)}
603+
</h2>
604+
{decisions && decisions.length > 0 && (
605+
<div className="text-xs" style={{ color: '#848E9C' }}>
606+
{t('lastCycles', language, { count: decisions.length })}
607+
</div>
608+
)}
609+
</div>
584610
</div>
585-
<div>
586-
<h2 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
587-
{t('recentDecisions', language)}
588-
</h2>
589-
{decisions && decisions.length > 0 && (
590-
<div className="text-xs" style={{ color: '#848E9C' }}>
591-
{t('lastCycles', language, { count: decisions.length })}
592-
</div>
593-
)}
611+
612+
{/* 显示数量选择器 */}
613+
<div className="flex items-center gap-2">
614+
<span className="text-xs" style={{ color: '#848E9C' }}>
615+
{language === 'zh' ? '显示' : 'Show'}:
616+
</span>
617+
<select
618+
value={decisionLimit}
619+
onChange={(e) => handleLimitChange(parseInt(e.target.value, 10))}
620+
className="rounded px-2 py-1 text-xs font-medium cursor-pointer transition-colors"
621+
style={{
622+
background: '#1E2329',
623+
border: '1px solid #2B3139',
624+
color: '#EAECEF',
625+
}}
626+
>
627+
<option value={5}>5</option>
628+
<option value={10}>10</option>
629+
<option value={20}>20</option>
630+
<option value={50}>50</option>
631+
</select>
632+
<span className="text-xs" style={{ color: '#848E9C' }}>
633+
{language === 'zh' ? '条' : ''}
634+
</span>
594635
</div>
595636
</div>
596637

0 commit comments

Comments
 (0)