Files
nofx/web/src/components/landing/core/LiveFeed.tsx
tinkle-community 110bf52908 feat: cream terminal redesign, English-only UI, autopilot launch fixes
- Redesign dashboard into a cream-paper + vermilion IBM Plex Mono terminal
  (live L2 order book, cost/liq map, WS K-line, signal matrix, orchestration
  topology, risk radar, execution log, current positions, equity curve)
- Convert all user-facing UI and backend strings/prompts from Chinese to
  English (multi-language retained, default English)
- Add /api/statistics/full endpoint + full-stats frontend wiring
- Fix Autopilot launch: reuse the existing trader instead of creating
  duplicates (eliminates repeat ~35s create cost and stale-trader 404s);
  launch sends 5m scan interval
- Fix unreadable toasts: cream theme with high-contrast text + per-type accent
- Silence background dashboard polls (getTraderConfig) to stop error-toast spam
2026-06-30 16:03:52 +08:00

122 lines
5.5 KiB
TypeScript

import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
interface LogEntry {
id: number
time: string
type: string
msg: string
color: string
}
const generateLog = (id: number): LogEntry => {
const types = ['EXEC', 'SIGNAL', 'RISK', 'MACRO', 'SYS']
const pairs = ['AAPL-USDC', 'NVDA-USDC', 'GOLD-USDC', 'EURUSD-USDC', 'OPENAI-IPO']
const actions = ['BUY', 'SELL', 'HEDGE', 'ROTATE']
const type = types[Math.floor(Math.random() * types.length)]
let msg = ''
let color = ''
switch (type) {
case 'EXEC':
msg = `AGENT-${Math.floor(Math.random() * 99)} ${actions[Math.floor(Math.random() * 4)]} ${pairs[Math.floor(Math.random() * pairs.length)]} @ ${Math.floor(Math.random() * 600)}`
color = 'text-nofx-success'
break;
case 'SIGNAL':
msg = `US equities momentum signal confirmed (${(Math.random()).toFixed(3)} z-score)`
color = 'text-nofx-gold'
break;
case 'RISK':
msg = `Risk check passed: ${pairs[Math.floor(Math.random() * pairs.length)]} exposure within limits`
color = 'text-nofx-danger'
break;
case 'MACRO':
msg = `Macro feed latency < ${Math.floor(Math.random() * 10)}ms`
color = 'text-nofx-text-muted'
break;
default:
msg = `System optimization cycle complete. Allocating resources.`
color = 'text-nofx-accent'
}
return { id, time: new Date().toLocaleTimeString('en-US', { hour12: false }) + '.' + Math.floor(Math.random() * 999), type, msg, color }
}
export default function LiveFeed() {
const [logs, setLogs] = useState<LogEntry[]>([])
useEffect(() => {
// Initial population
const initialLogs = Array.from({ length: 8 }).map((_, i) => generateLog(i))
setLogs(initialLogs)
const interval = setInterval(() => {
setLogs(prev => {
const newLog = generateLog(Date.now())
return [newLog, ...prev.slice(0, 7)]
})
}, 800) // Fast 800ms updates for HFT feel
return () => clearInterval(interval)
}, [])
return (
<section className="w-full bg-nofx-bg-lighter border-y border-[rgba(26,24,19,0.14)] py-1 overflow-hidden relative">
<div className="max-w-[1920px] mx-auto px-4 flex flex-col md:flex-row gap-0 md:gap-8 items-stretch h-[240px] md:h-12 text-xs font-mono">
{/* Left Status Bar (Static) */}
<div className="hidden md:flex items-center gap-6 text-nofx-text-muted border-r border-[rgba(26,24,19,0.14)] pr-6 shrink-0">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 bg-nofx-success rounded-full animate-pulse"></div>
<span className="font-bold text-nofx-text">WS_CONN: STABLE</span>
</div>
<div className="flex items-center gap-2">
<span className="text-nofx-gold">TPS: 48,291</span>
</div>
</div>
{/* Right Scrolling Log - Vertical on mobile, Single line ticker on Desktop */}
<div className="flex-1 overflow-hidden relative font-mono text-[10px] md:text-sm h-full flex items-center">
{/* Desktop View: Single Line Fade */}
<div className="hidden md:block w-full h-full relative">
{logs.slice(0, 1).map((log) => (
<motion.div
key={log.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="absolute inset-0 flex items-center gap-4"
>
<span className="text-nofx-text-muted">[{log.time}]</span>
<span className={`font-bold w-10 ${log.type === 'RISK' ? 'text-nofx-danger bg-nofx-danger/10 px-1 rounded' :
log.type === 'SIGNAL' ? 'text-nofx-gold bg-nofx-gold/10 px-1 rounded' :
log.type === 'EXEC' ? 'text-nofx-success' : 'text-nofx-text-muted'
}`}>{log.type}</span>
<span className={`${log.color}`}>{log.msg}</span>
</motion.div>
))}
</div>
{/* Mobile View: Vertical Stack */}
<div className="md:hidden flex flex-col gap-2 w-full p-4 h-full overflow-hidden">
{logs.map((log) => (
<div key={log.id} className="flex gap-2 w-full truncate border-b border-[rgba(26,24,19,0.10)] pb-1 last:border-0">
<span className="text-nofx-text-muted w-16 shrink-0">{log.time.split('.')[0]}</span>
<span className={`font-bold w-8 shrink-0 ${log.type === 'RISK' ? 'text-nofx-danger' :
log.type === 'SIGNAL' ? 'text-nofx-gold' :
'text-nofx-text-muted'
}`}>{log.type}</span>
<span className={`${log.color} truncate`}>{log.msg}</span>
</div>
))}
</div>
</div>
</div>
</section>
)
}