feat: implement UI design system unification
- Add design-system tokens (colors, spacing, typography, radii, shadows) - Add cn utility for class merging - Refactor StatCard to dark theme (fixes light theme issue) - Refactor PositionTable to use Tailwind CSS (fixes inline styles and color inconsistency) - Remove duplicate AgentTable from ui/ - Update AgentTable in agents/ to use AgentStatus type - Update AgentStatus type to include all status values - Update mockData to match AgentStatus type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
b5d8d4e71b
131
frontend/src/app/page.tsx
Normal file
131
frontend/src/app/page.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { DollarSign, TrendingUp, Activity, Bot } from "lucide-react";
|
||||
|
||||
import { MetricCard } from "@/components/ui/MetricCard";
|
||||
import { AgentTable } from "@/components/agents/AgentTable";
|
||||
import { TradeList } from "@/components/ui/TradeList";
|
||||
import { EquityChart } from "@/components/ui/EquityChart";
|
||||
import { PnlDistributionChart } from "@/components/ui/PnlDistributionChart";
|
||||
import { getMetrics, getEquityCurve, getPnlDistribution } from "@/api/metrics";
|
||||
import { getAgents } from "@/api/agents";
|
||||
import { getRecentTrades } from "@/api/trades";
|
||||
import type {
|
||||
AgentStatus,
|
||||
TradeRecord,
|
||||
Metrics,
|
||||
EquityPoint,
|
||||
PnlDistribution,
|
||||
} from "@/types/models";
|
||||
|
||||
export default function Dashboard() {
|
||||
const [metrics, setMetrics] = useState<Metrics | null>(null);
|
||||
const [equityData, setEquityData] = useState<EquityPoint[]>([]);
|
||||
const [pnlDistribution, setPnlDistribution] = useState<PnlDistribution[]>([]);
|
||||
const [agents, setAgents] = useState<AgentStatus[]>([]);
|
||||
const [trades, setTrades] = useState<TradeRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [metricsRes, equityRes, pnlRes, agentsRes, tradesRes] =
|
||||
await Promise.all([
|
||||
getMetrics(),
|
||||
getEquityCurve(),
|
||||
getPnlDistribution(),
|
||||
getAgents(),
|
||||
getRecentTrades(10),
|
||||
]);
|
||||
setMetrics(metricsRes);
|
||||
setEquityData(equityRes);
|
||||
setPnlDistribution(pnlRes);
|
||||
setAgents(agentsRes);
|
||||
setTrades(tradesRes);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-[#a0a0a0] animate-pulse">
|
||||
Loading dashboard data...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-[#ff5252] font-bold">Error: {error}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Metric Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<MetricCard
|
||||
title="Total Equity"
|
||||
value={metrics?.totalEquity?.toFixed(2) ?? "0.00"}
|
||||
change={metrics?.dailyPnlPercent}
|
||||
changeType={
|
||||
metrics && metrics.dailyPnl >= 0 ? "positive" : "negative"
|
||||
}
|
||||
icon={DollarSign}
|
||||
/>
|
||||
<MetricCard
|
||||
title="Daily P&L"
|
||||
value={metrics?.dailyPnl?.toFixed(2) ?? "0.00"}
|
||||
change={metrics?.dailyPnlPercent}
|
||||
changeType={
|
||||
metrics && metrics.dailyPnl >= 0 ? "positive" : "negative"
|
||||
}
|
||||
icon={TrendingUp}
|
||||
/>
|
||||
<MetricCard
|
||||
title="Total Trades"
|
||||
value={metrics?.totalTrades ?? 0}
|
||||
icon={Activity}
|
||||
/>
|
||||
<MetricCard
|
||||
title="Active Agents"
|
||||
value={`${metrics?.activeAgents ?? 0} / ${metrics?.totalAgents ?? 0}`}
|
||||
icon={Bot}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Charts */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 bg-[#1e1e1e] border border-[#2c2c2c] rounded-xl overflow-hidden">
|
||||
<EquityChart data={equityData} />
|
||||
</div>
|
||||
<div className="bg-[#1e1e1e] border border-[#2c2c2c] rounded-xl overflow-hidden">
|
||||
<PnlDistributionChart data={pnlDistribution} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tables */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-[#1e1e1e] border border-[#2c2c2c] rounded-xl overflow-hidden">
|
||||
<AgentTable agents={agents} />
|
||||
</div>
|
||||
<div className="bg-[#1e1e1e] border border-[#2c2c2c] rounded-xl overflow-hidden">
|
||||
<TradeList trades={trades} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
131
frontend/src/components/agents/AgentTable.tsx
Normal file
131
frontend/src/components/agents/AgentTable.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
'use client';
|
||||
|
||||
import { Play, Pause, Square, Bot } from "lucide-react";
|
||||
import { cn } from "@/design-system/utils/cn";
|
||||
import type { AgentStatus } from "@/types/models";
|
||||
|
||||
interface AgentTableProps {
|
||||
agents: AgentStatus[];
|
||||
}
|
||||
|
||||
const statusConfig: Record<
|
||||
string,
|
||||
{ color: string; icon: typeof Play; label: string; textColor: string }
|
||||
> = {
|
||||
running: { color: "bg-[#00ffcc]", icon: Play, label: "RUNNING", textColor: "text-[#00ffcc]" },
|
||||
active: { color: "bg-[#00ffcc]", icon: Play, label: "ACTIVE", textColor: "text-[#00ffcc]" },
|
||||
paused: { color: "bg-[#ffb74d]", icon: Pause, label: "PAUSED", textColor: "text-[#ffb74d]" },
|
||||
resting: { color: "bg-[#ffb74d]", icon: Pause, label: "RESTING", textColor: "text-[#ffb74d]" },
|
||||
stopped: { color: "bg-[#666666]", icon: Square, label: "STOPPED", textColor: "text-[#666666]" },
|
||||
error: { color: "bg-[#ff5252]", icon: Square, label: "ERROR", textColor: "text-[#ff5252]" },
|
||||
danger: { color: "bg-[#ff5252]", icon: Square, label: "DANGER", textColor: "text-[#ff5252]" },
|
||||
bankrupt: { color: "bg-[#ff5252]", icon: Square, label: "BANKRUPT", textColor: "text-[#ff5252]" },
|
||||
};
|
||||
|
||||
export function AgentTable({ agents }: AgentTableProps) {
|
||||
return (
|
||||
<div className="bg-[#1e1e1e] rounded-2xl border border-[#2c2c2c] overflow-hidden shadow-2xl">
|
||||
<div className="px-6 py-4 border-b border-[#2c2c2c] bg-[#121212] flex items-center justify-between">
|
||||
<h3 className="text-sm font-black text-[#e1e1e1] uppercase tracking-widest flex items-center gap-2">
|
||||
<Bot className="w-4 h-4 text-[#00ffcc]" />
|
||||
Deployment Matrix
|
||||
</h3>
|
||||
<span className="text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
{agents.length} Active Nodes
|
||||
</span>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-[#121212]/50">
|
||||
<th className="px-6 py-3 text-left text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
Unit
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
Logic
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
Asset
|
||||
</th>
|
||||
<th className="px-6 py-3 text-right text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
P&L
|
||||
</th>
|
||||
<th className="px-6 py-3 text-right text-[10px] font-black text-[#666666] uppercase tracking-widest">
|
||||
OPS
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[#2c2c2c]">
|
||||
{agents.map((agent) => {
|
||||
const status = statusConfig[agent.status] || {
|
||||
color: "bg-[#666666]",
|
||||
icon: Square,
|
||||
label: agent.status.toUpperCase(),
|
||||
textColor: "text-[#666666]"
|
||||
};
|
||||
const StatusIcon = status.icon;
|
||||
|
||||
return (
|
||||
<tr key={agent.id} className="hover:bg-[#121212] group transition-colors cursor-default">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div>
|
||||
<p className="text-sm font-black text-[#e1e1e1] group-hover:text-[#00ffcc] transition-colors">
|
||||
{agent.name}
|
||||
</p>
|
||||
<p className="text-[10px] text-[#666666] font-mono uppercase">{agent.id ? agent.id.slice(0, 12) : 'Unknown ID'}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn("w-2 h-2 rounded-full shadow-[0_0_5px_currentColor]", status.color)}
|
||||
/>
|
||||
<span className={cn("text-[10px] font-black uppercase tracking-widest", status.textColor)}>
|
||||
{status.label}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className="text-xs font-bold text-[#a0a0a0] uppercase">
|
||||
{agent.strategy}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black bg-[#00ffcc]/10 text-[#00ffcc] uppercase tracking-tighter">
|
||||
{agent.symbol}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right">
|
||||
<div>
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm font-black italic tracking-tighter",
|
||||
agent.profitLoss >= 0
|
||||
? "text-[#00ffcc]"
|
||||
: "text-[#ff5252]",
|
||||
)}
|
||||
>
|
||||
{agent.profitLoss >= 0 ? "+" : ""}
|
||||
{agent.profitLoss?.toFixed(2) ?? "0.00"}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right">
|
||||
<span className="text-xs font-bold text-[#e1e1e1]">
|
||||
{agent.tradesCount}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgentTable;
|
||||
71
frontend/src/components/agents/mockData.ts
Normal file
71
frontend/src/components/agents/mockData.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import type { AgentStatus } from "@/types/models";
|
||||
|
||||
// 虚拟数据
|
||||
export const mockAgents: AgentStatus[] = [
|
||||
{
|
||||
id: "AGT-001",
|
||||
name: "Alpha Trader",
|
||||
status: "running",
|
||||
strategy: "Momentum",
|
||||
symbol: "BTC/USDT",
|
||||
profitLoss: 2580.5,
|
||||
profitLossPercent: 25.8,
|
||||
tradesCount: 156,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "AGT-002",
|
||||
name: "ETH Scalper",
|
||||
status: "active",
|
||||
strategy: "Scalping",
|
||||
symbol: "ETH/USDT",
|
||||
profitLoss: 920.25,
|
||||
profitLossPercent: 9.2,
|
||||
tradesCount: 89,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "AGT-003",
|
||||
name: "Range Bot",
|
||||
status: "resting",
|
||||
strategy: "Mean Reversion",
|
||||
symbol: "SOL/USDT",
|
||||
profitLoss: -360.0,
|
||||
profitLossPercent: -3.6,
|
||||
tradesCount: 45,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "AGT-004",
|
||||
name: "Danger Bot",
|
||||
status: "danger",
|
||||
strategy: "Trend Following",
|
||||
symbol: "DOGE/USDT",
|
||||
profitLoss: -899.25,
|
||||
profitLossPercent: -9.0,
|
||||
tradesCount: 23,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "AGT-005",
|
||||
name: "Failed Bot",
|
||||
status: "bankrupt",
|
||||
strategy: "Arbitrage",
|
||||
symbol: "BNB/USDT",
|
||||
profitLoss: -1000.0,
|
||||
profitLossPercent: -10.0,
|
||||
tradesCount: 12,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "AGT-006",
|
||||
name: "Star Trader",
|
||||
status: "running",
|
||||
strategy: "Grid Trading",
|
||||
symbol: "XRP/USDT",
|
||||
profitLoss: 4670.8,
|
||||
profitLossPercent: 46.7,
|
||||
tradesCount: 312,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
144
frontend/src/components/trading/PositionTable.tsx
Normal file
144
frontend/src/components/trading/PositionTable.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
'use client';
|
||||
|
||||
import type { Position } from '@/types/models';
|
||||
import { cn } from '@/design-system/utils/cn';
|
||||
|
||||
interface PositionTableProps {
|
||||
positions: Position[];
|
||||
onClosePosition?: (positionId: string) => void;
|
||||
}
|
||||
|
||||
export function PositionTable({ positions, onClosePosition }: PositionTableProps) {
|
||||
return (
|
||||
<div className="bg-[#121212] rounded-lg border border-[#2c2c2c] overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-3 border-b border-[#2c2c2c]">
|
||||
<h3 className="text-sm font-semibold text-[#e1e1e1]">Positions</h3>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-[#1e1e1e]">
|
||||
<th className="px-4 py-2 text-left text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Symbol
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Side
|
||||
</th>
|
||||
<th className="px-4 py-2 text-right text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Quantity
|
||||
</th>
|
||||
<th className="px-4 py-2 text-right text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Entry Price
|
||||
</th>
|
||||
<th className="px-4 py-2 text-right text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Mark Price
|
||||
</th>
|
||||
<th className="px-4 py-2 text-right text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Unrealized P&L
|
||||
</th>
|
||||
<th className="px-4 py-2 text-center text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{positions.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={7}
|
||||
className="px-4 py-8 text-center text-sm text-[#666666]"
|
||||
>
|
||||
No positions
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
positions.map((position) => (
|
||||
<tr
|
||||
key={position.id}
|
||||
className="border-b border-[#2c2c2c] hover:bg-[#1e1e1e] transition-colors"
|
||||
>
|
||||
{/* Symbol */}
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<span className="text-sm font-medium text-[#e1e1e1]">
|
||||
{position.symbol}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Side */}
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium',
|
||||
position.side === 'long'
|
||||
? 'bg-[rgba(0,255,204,0.15)] text-[#00ffcc]'
|
||||
: 'bg-[rgba(255,82,82,0.15)] text-[#ff5252]'
|
||||
)}
|
||||
>
|
||||
{position.side === 'long' ? 'Long' : 'Short'}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Quantity */}
|
||||
<td className="px-4 py-3 whitespace-nowrap text-right">
|
||||
<span className="text-sm text-[#a0a0a0]">{position.quantity}</span>
|
||||
</td>
|
||||
|
||||
{/* Entry Price */}
|
||||
<td className="px-4 py-3 whitespace-nowrap text-right">
|
||||
<span className="text-sm text-[#a0a0a0]">
|
||||
{position.entryPrice.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Mark Price */}
|
||||
<td className="px-4 py-3 whitespace-nowrap text-right">
|
||||
<span className="text-sm text-[#a0a0a0]">
|
||||
{position.markPrice.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Unrealized P&L */}
|
||||
<td className="px-4 py-3 whitespace-nowrap text-right">
|
||||
<div className="flex flex-col items-end">
|
||||
<p
|
||||
className={cn(
|
||||
'text-sm font-medium',
|
||||
position.unrealizedPnl >= 0 ? 'text-[#00ffcc]' : 'text-[#ff5252]'
|
||||
)}
|
||||
>
|
||||
{position.unrealizedPnl >= 0 ? '+' : ''}
|
||||
{position.unrealizedPnl.toFixed(2)}
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
'text-xs',
|
||||
position.unrealizedPnlPercent >= 0 ? 'text-[#00ffcc]' : 'text-[#ff5252]'
|
||||
)}
|
||||
>
|
||||
{position.unrealizedPnlPercent >= 0 ? '+' : ''}
|
||||
{position.unrealizedPnlPercent.toFixed(2)}%
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Action */}
|
||||
<td className="px-4 py-3 whitespace-nowrap text-center">
|
||||
<button
|
||||
onClick={() => onClosePosition?.(position.id)}
|
||||
className="px-3 py-1 text-xs font-medium rounded border border-[#ff5252] bg-transparent text-[#ff5252] hover:bg-[rgba(255,82,82,0.1)] transition-colors cursor-pointer"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
68
frontend/src/components/ui/StatCard.tsx
Normal file
68
frontend/src/components/ui/StatCard.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { cn } from "@/design-system/utils/cn";
|
||||
|
||||
interface StatCardProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
subValue?: string;
|
||||
change?: {
|
||||
value: number;
|
||||
isPositive: boolean;
|
||||
};
|
||||
icon?: React.ReactNode;
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function StatCard({
|
||||
label,
|
||||
value,
|
||||
subValue,
|
||||
change,
|
||||
icon,
|
||||
isLoading,
|
||||
className,
|
||||
}: StatCardProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div
|
||||
className={cn("bg-[#1e1e1e] rounded-lg p-6 animate-pulse", className)}
|
||||
>
|
||||
<div className="h-4 w-20 bg-[#2c2c2c] rounded mb-4" />
|
||||
<div className="h-8 w-32 bg-[#2c2c2c] rounded" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-[#1e1e1e] rounded-lg p-6 border border-transparent hover:border-[#2c2c2c] transition-colors",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-xs font-bold text-[#a0a0a0] uppercase tracking-widest">
|
||||
{label}
|
||||
</span>
|
||||
{icon && <span className="text-[#666666]">{icon}</span>}
|
||||
</div>
|
||||
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-2xl font-bold text-[#e1e1e1]">{value}</span>
|
||||
{change && (
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
change.isPositive ? "text-[#00ffcc]" : "text-[#ff5252]",
|
||||
)}
|
||||
>
|
||||
{change.isPositive ? "+" : ""}
|
||||
{change.value}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{subValue && <p className="text-sm text-[#a0a0a0] mt-2">{subValue}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
frontend/src/design-system/tokens/colors.ts
Normal file
44
frontend/src/design-system/tokens/colors.ts
Normal file
@ -0,0 +1,44 @@
|
||||
export const colors = {
|
||||
// 背景层级 - 从深到浅
|
||||
background: {
|
||||
base: '#121212', // 页面最底层背景
|
||||
elevated: '#1e1e1e', // 卡片、面板、弹窗
|
||||
overlay: '#2c2c2c', // 悬停状态、下拉菜单
|
||||
sunken: '#0a0a0a', // 输入框、内嵌区域
|
||||
},
|
||||
|
||||
// 品牌色
|
||||
primary: {
|
||||
DEFAULT: '#00ffcc',
|
||||
hover: '#00e6b8',
|
||||
active: '#00cca3',
|
||||
muted: 'rgba(0, 255, 204, 0.1)',
|
||||
glow: 'rgba(0, 255, 204, 0.3)',
|
||||
},
|
||||
|
||||
// 功能色
|
||||
semantic: {
|
||||
success: '#00ffcc', // 与主色保持一致
|
||||
warning: '#ffb74d',
|
||||
error: '#ff5252',
|
||||
info: '#64b5f6',
|
||||
},
|
||||
|
||||
// 文字色
|
||||
text: {
|
||||
primary: '#e1e1e1', // 主要文字
|
||||
secondary: '#a0a0a0', // 次要文字
|
||||
muted: '#666666', // 弱化文字
|
||||
disabled: '#444444', // 禁用状态
|
||||
inverse: '#121212', // 用于主色背景上的文字
|
||||
},
|
||||
|
||||
// 边框
|
||||
border: {
|
||||
DEFAULT: '#2c2c2c',
|
||||
light: '#383838',
|
||||
focus: '#00ffcc',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type Colors = typeof colors;
|
||||
5
frontend/src/design-system/tokens/index.ts
Normal file
5
frontend/src/design-system/tokens/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { colors } from './colors';
|
||||
export { spacing } from './spacing';
|
||||
export { typography } from './typography';
|
||||
export { radii } from './radii';
|
||||
export { shadows } from './shadows';
|
||||
12
frontend/src/design-system/tokens/radii.ts
Normal file
12
frontend/src/design-system/tokens/radii.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const radii = {
|
||||
none: '0',
|
||||
sm: '0.25rem', // 4px
|
||||
DEFAULT: '0.375rem', // 6px
|
||||
md: '0.5rem', // 8px
|
||||
lg: '0.75rem', // 12px
|
||||
xl: '1rem', // 16px
|
||||
'2xl': '1.5rem', // 24px
|
||||
full: '9999px',
|
||||
} as const;
|
||||
|
||||
export type Radii = typeof radii;
|
||||
12
frontend/src/design-system/tokens/shadows.ts
Normal file
12
frontend/src/design-system/tokens/shadows.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const shadows = {
|
||||
none: 'none',
|
||||
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.3)',
|
||||
DEFAULT: '0 4px 6px -1px rgba(0, 0, 0, 0.4)',
|
||||
md: '0 6px 12px -2px rgba(0, 0, 0, 0.5)',
|
||||
lg: '0 10px 20px -4px rgba(0, 0, 0, 0.5)',
|
||||
xl: '0 20px 40px -8px rgba(0, 0, 0, 0.6)',
|
||||
glow: '0 0 20px rgba(0, 255, 204, 0.3)',
|
||||
'glow-sm': '0 0 10px rgba(0, 255, 204, 0.2)',
|
||||
} as const;
|
||||
|
||||
export type Shadows = typeof shadows;
|
||||
15
frontend/src/design-system/tokens/spacing.ts
Normal file
15
frontend/src/design-system/tokens/spacing.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export const spacing = {
|
||||
0: '0',
|
||||
1: '0.25rem', // 4px
|
||||
2: '0.5rem', // 8px
|
||||
3: '0.75rem', // 12px
|
||||
4: '1rem', // 16px
|
||||
5: '1.25rem', // 20px
|
||||
6: '1.5rem', // 24px
|
||||
8: '2rem', // 32px
|
||||
10: '2.5rem', // 40px
|
||||
12: '3rem', // 48px
|
||||
16: '4rem', // 64px
|
||||
} as const;
|
||||
|
||||
export type Spacing = typeof spacing;
|
||||
26
frontend/src/design-system/tokens/typography.ts
Normal file
26
frontend/src/design-system/tokens/typography.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export const typography = {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
||||
},
|
||||
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1rem', letterSpacing: '0.05em' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.25rem', letterSpacing: '0.025em' }],
|
||||
base: ['1rem', { lineHeight: '1.5rem' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
||||
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
||||
},
|
||||
|
||||
fontWeight: {
|
||||
normal: '400',
|
||||
medium: '500',
|
||||
semibold: '600',
|
||||
bold: '700',
|
||||
black: '900',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type Typography = typeof typography;
|
||||
6
frontend/src/design-system/utils/cn.ts
Normal file
6
frontend/src/design-system/utils/cn.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
1
frontend/src/design-system/utils/index.ts
Normal file
1
frontend/src/design-system/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { cn } from './cn';
|
||||
176
frontend/src/types/models.ts
Normal file
176
frontend/src/types/models.ts
Normal file
@ -0,0 +1,176 @@
|
||||
export interface AgentStatus {
|
||||
id: string;
|
||||
name: string;
|
||||
status:
|
||||
| "running"
|
||||
| "active"
|
||||
| "paused"
|
||||
| "resting"
|
||||
| "stopped"
|
||||
| "error"
|
||||
| "danger"
|
||||
| "bankrupt";
|
||||
strategy: string;
|
||||
symbol: string;
|
||||
profitLoss: number;
|
||||
profitLossPercent: number;
|
||||
tradesCount: number;
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
export interface TradeRecord {
|
||||
id: string;
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
symbol: string;
|
||||
side: "buy" | "sell";
|
||||
quantity: number;
|
||||
price: number;
|
||||
total: number;
|
||||
timestamp: string;
|
||||
pnl?: number;
|
||||
pnlPercent?: number;
|
||||
}
|
||||
|
||||
export interface Metrics {
|
||||
totalEquity: number;
|
||||
dailyPnl: number;
|
||||
dailyPnlPercent: number;
|
||||
totalTrades: number;
|
||||
winRate: number;
|
||||
activeAgents: number;
|
||||
totalAgents: number;
|
||||
}
|
||||
|
||||
export interface EquityPoint {
|
||||
timestamp: string;
|
||||
equity: number;
|
||||
}
|
||||
|
||||
export interface PnlDistribution {
|
||||
range: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// Trading related types
|
||||
export interface Order {
|
||||
id: string;
|
||||
symbol: string;
|
||||
side: "buy" | "sell";
|
||||
type: "market" | "limit" | "stop" | "stop_limit";
|
||||
status:
|
||||
| "pending"
|
||||
| "open"
|
||||
| "filled"
|
||||
| "partially_filled"
|
||||
| "cancelled"
|
||||
| "rejected";
|
||||
quantity: number;
|
||||
filledQuantity: number;
|
||||
price?: number;
|
||||
stopPrice?: number;
|
||||
total?: number;
|
||||
timestamp: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
id: string;
|
||||
symbol: string;
|
||||
side: "long" | "short";
|
||||
quantity: number;
|
||||
entryPrice: number;
|
||||
markPrice: number;
|
||||
liquidationPrice?: number;
|
||||
margin: number;
|
||||
leverage: number;
|
||||
unrealizedPnl: number;
|
||||
unrealizedPnlPercent: number;
|
||||
realizedPnl: number;
|
||||
openedAt: string;
|
||||
}
|
||||
|
||||
export interface Ticker {
|
||||
symbol: string;
|
||||
lastPrice: number;
|
||||
priceChange: number;
|
||||
priceChangePercent: number;
|
||||
high24h: number;
|
||||
low24h: number;
|
||||
volume24h: number;
|
||||
quoteVolume24h: number;
|
||||
bidPrice: number;
|
||||
askPrice: number;
|
||||
bidQty: number;
|
||||
askQty: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface OrderBookEntry {
|
||||
price: number;
|
||||
quantity: number;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface OrderBook {
|
||||
symbol: string;
|
||||
bids: OrderBookEntry[];
|
||||
asks: OrderBookEntry[];
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface Kline {
|
||||
timestamp: string;
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
volume: number;
|
||||
quoteVolume: number;
|
||||
}
|
||||
|
||||
export type TradingMode = "simulation" | "live";
|
||||
|
||||
// Trade type for recent trades
|
||||
export interface Trade {
|
||||
id: string;
|
||||
symbol: string;
|
||||
side: "buy" | "sell";
|
||||
price: number;
|
||||
quantity: number;
|
||||
total: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// Exchange configuration
|
||||
export interface Exchange {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "binance" | "okx" | "bybit" | "custom";
|
||||
apiKey: string;
|
||||
apiSecret?: string;
|
||||
passphrase?: string;
|
||||
testnet: boolean;
|
||||
enabled?: boolean;
|
||||
status: "connected" | "disconnected" | "error";
|
||||
lastTested?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ExchangeCreateRequest {
|
||||
name: string;
|
||||
type: "binance" | "okx" | "bybit" | "custom";
|
||||
apiKey: string;
|
||||
apiSecret: string;
|
||||
passphrase?: string;
|
||||
testnet: boolean;
|
||||
}
|
||||
|
||||
export interface ExchangeUpdateRequest {
|
||||
name?: string;
|
||||
apiKey?: string;
|
||||
apiSecret?: string;
|
||||
passphrase?: string;
|
||||
testnet?: boolean;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user