import { useState, useEffect } from 'react';
import { BrainCircuit, ThumbsUp, ThumbsDown, X, LineChart, FileJson, Zap } from 'lucide-react';
import {
ResponsiveContainer,
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip
} from 'recharts';
interface Strategy {
tool_name: string;
success_rate: number;
usage_count: number;
success_count: number;
failure_count: number;
created_at: string;
updated_at: string;
content?: string;
}
export default function StrategiesTab() {
const [strategies, setStrategies] = useState<Strategy[]>([]);
const [stats, setStats] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Detail Modal State
const [selectedStrategy, setSelectedStrategy] = useState<Strategy | null>(null);
const [strategyTrend, setStrategyTrend] = useState<any[]>([]);
const [loadingTrend, setLoadingTrend] = useState(false);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const [stratRes, statRes] = await Promise.all([
fetch('/api/strategies'),
fetch('/api/strategies/stats')
]);
if (!stratRes.ok) throw new Error('获取策略列表失败');
if (!statRes.ok) throw new Error('获取策略统计失败');
const stratData = await stratRes.json();
if (Array.isArray(stratData)) {
setStrategies(stratData);
} else {
console.error('Strategies API returned non-array:', stratData);
setStrategies([]);
}
setStats(await statRes.json());
} catch (err) {
console.error(err);
setError('无法获取 ACE 策略数据,请检查 ACE Manager 与数据库连接。');
} finally {
setLoading(false);
}
};
const handleStrategyClick = async (strategy: Strategy) => {
setSelectedStrategy(strategy);
setLoadingTrend(true);
setError(null);
try {
const res = await fetch(`/api/strategies/trend?tool_name=${encodeURIComponent(strategy.tool_name)}`);
if (!res.ok) throw new Error('获取策略趋势失败');
const data = await res.json();
setStrategyTrend(data);
} catch (err) {
console.error(err);
setStrategyTrend([]);
setError('获取该策略的历史趋势失败。');
} finally {
setLoadingTrend(false);
}
};
if (loading && strategies.length === 0) return <div>正在加载策略...</div>;
return (
<div className="space-y-8 relative">
{error && (
<div className="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg">
{error}
</div>
)}
{/* Stats Overview */}
<div className="grid gap-4 md:grid-cols-4">
<div className="bg-card border rounded-xl p-4 shadow-sm">
<div className="text-sm text-muted-foreground">策略总数</div>
<div className="text-2xl font-bold">{stats?.total_strategies || 0}</div>
</div>
<div className="bg-card border rounded-xl p-4 shadow-sm">
<div className="text-sm text-muted-foreground">平均成功率</div>
<div className="text-2xl font-bold text-green-600">
{((stats?.avg_success_rate || 0) * 100).toFixed(1)}%
</div>
</div>
<div className="bg-card border rounded-xl p-4 shadow-sm">
<div className="text-sm text-muted-foreground">总使用次数</div>
<div className="text-2xl font-bold">{stats?.total_usage || 0}</div>
</div>
<div className="bg-card border rounded-xl p-4 shadow-sm">
<div className="text-sm text-muted-foreground">活跃工具</div>
<div className="text-2xl font-bold">{stats?.tool_count || 0}</div>
</div>
</div>
{/* Strategy List */}
<div className="border rounded-xl bg-card shadow-sm overflow-hidden">
<div className="p-4 border-b bg-muted/10">
<h3 className="font-semibold flex items-center gap-2">
<BrainCircuit className="w-4 h-4" />
活跃 Playbook 策略
</h3>
</div>
<div className="relative w-full overflow-auto">
<table className="w-full caption-bottom text-sm">
<thead className="[&_tr]:border-b">
<tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">工具</th>
<th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">成功率</th>
<th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">使用量</th>
<th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">成功/失败</th>
<th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">最后更新</th>
</tr>
</thead>
<tbody className="[&_tr:last-child]:border-0">
{strategies.length === 0 ? (
<tr>
<td colSpan={5} className="p-8 text-center text-muted-foreground">
暂无学习到的策略。请使用工具以生成数据。
</td>
</tr>
) : (
strategies.map((s, i) => (
<tr
key={i}
className="border-b transition-colors hover:bg-muted/50 cursor-pointer"
onClick={() => handleStrategyClick(s)}
>
<td className="p-4 font-medium">{s.tool_name}</td>
<td className="p-4">
<div className="flex items-center gap-2">
<div className="w-16 h-2 bg-muted rounded-full overflow-hidden">
<div
className={`h-full ${s.success_rate > 0.8 ? 'bg-green-500' : s.success_rate > 0.5 ? 'bg-yellow-500' : 'bg-red-500'}`}
style={{ width: `${s.success_rate * 100}%` }}
/>
</div>
<span>{(s.success_rate * 100).toFixed(0)}%</span>
</div>
</td>
<td className="p-4">{s.usage_count}</td>
<td className="p-4">
<div className="flex items-center gap-3 text-xs">
<span className="flex items-center gap-1 text-green-600">
<ThumbsUp className="w-3 h-3" /> {s.success_count}
</span>
<span className="flex items-center gap-1 text-red-600">
<ThumbsDown className="w-3 h-3" /> {s.failure_count}
</span>
</div>
</td>
<td className="p-4 text-muted-foreground">
{new Date(s.updated_at).toLocaleDateString()}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
{/* Detail Modal */}
{selectedStrategy && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm p-4">
<div className="bg-card w-full max-w-4xl max-h-[90vh] rounded-xl border shadow-lg flex flex-col overflow-hidden animate-in fade-in zoom-in-95 duration-200">
{/* Header */}
<div className="p-6 border-b flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold flex items-center gap-2">
<Zap className="w-6 h-6 text-yellow-500" />
{selectedStrategy.tool_name}
</h2>
<p className="text-muted-foreground text-sm mt-1">策略详情与洞察</p>
</div>
<button
onClick={() => setSelectedStrategy(null)}
className="p-2 hover:bg-muted rounded-full transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6 space-y-8">
{/* Trend Chart */}
<div className="space-y-4">
<h3 className="text-lg font-semibold flex items-center gap-2">
<LineChart className="w-5 h-5" />
历史表现趋势
</h3>
<div className="h-[250px] w-full bg-muted/5 rounded-xl border p-4">
{loadingTrend ? (
<div className="h-full flex items-center justify-center">加载中...</div>
) : strategyTrend.length > 0 ? (
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={strategyTrend}>
<defs>
<linearGradient id="colorSuccess" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#22c55e" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#22c55e" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" opacity={0.1} />
<XAxis dataKey="date" fontSize={12} tickFormatter={d => new Date(d).toLocaleDateString(undefined, {month:'short', day:'numeric'})} />
<YAxis fontSize={12} />
<Tooltip
contentStyle={{ backgroundColor: 'hsl(var(--card))', borderRadius: '8px' }}
labelFormatter={l => new Date(l).toLocaleDateString()}
/>
<Area type="monotone" dataKey="usage_count" name="使用量" stroke="#94a3b8" fill="transparent" strokeDasharray="5 5" />
<Area type="monotone" dataKey="success_count" name="成功数" stroke="#22c55e" fill="url(#colorSuccess)" strokeWidth={2} />
</AreaChart>
</ResponsiveContainer>
) : (
<div className="h-full flex items-center justify-center text-muted-foreground">暂无历史趋势数据</div>
)}
</div>
</div>
{/* Strategy Content (JSON) */}
<div className="space-y-4">
<h3 className="text-lg font-semibold flex items-center gap-2">
<FileJson className="w-5 h-5" />
当前策略配置 (JSON)
</h3>
<div className="bg-muted/30 rounded-xl p-4 border overflow-hidden">
<pre className="text-sm font-mono overflow-auto max-h-[300px] whitespace-pre-wrap">
{(() => {
try {
// Try to format if string is JSON
return JSON.stringify(JSON.parse(selectedStrategy.content || '{}'), null, 2);
} catch {
return selectedStrategy.content || '无详细配置内容';
}
})()}
</pre>
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
}