Spaces:
Sleeping
Sleeping
File size: 6,408 Bytes
3c665d2 ce1c471 3c665d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | import { useEffect } from 'react'
import {
LineChart, Line, BarChart, Bar,
XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, ReferenceLine,
} from 'recharts'
import { TrendingUp, Loader2, RefreshCw } from 'lucide-react'
import { useStore } from '../store/useStore'
import { fetchRLState } from '../lib/api'
const CustomTooltip = ({
active,
payload,
label,
}: {
active?: boolean
payload?: { value: number; name: string; color: string }[]
label?: string | number
}) => {
if (active && payload?.length) {
return (
<div
className="border border-white/10 rounded-lg px-3 py-2 text-xs"
style={{ background: '#1a1a2e' }}
>
<p className="text-gray-400 mb-1">#{label}</p>
{payload.map((p) => (
<p key={p.name} style={{ color: p.color }}>
{p.name}: <span className="font-semibold">{p.value}</span>
</p>
))}
</div>
)
}
return null
}
export function PerformanceGraph() {
const { rlState, setRlState } = useStore()
const load = async () => {
try {
const data = await fetchRLState()
setRlState(data)
} catch {
// noop — backend might not be up
}
}
useEffect(() => {
void load()
const interval = setInterval(() => void load(), 10_000)
return () => clearInterval(interval)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
if (!rlState) {
return (
<div className="flex flex-col items-center justify-center h-40 text-gray-600 gap-2">
<TrendingUp size={24} className="text-gray-700" />
<p className="text-[11px] text-center">
RL metrics appear after agent episodes
</p>
<Loader2 size={14} className="animate-spin text-gray-700" />
</div>
)
}
const totalEpisodes: number = rlState.totalEpisodes ?? 0
const successRate: number = rlState.successRate ?? 0
const currentAlpha: number = rlState.currentAlpha ?? 0
const episodes: { episode: number; totalReward: number; successRate: number }[] = Array.isArray(rlState.episodes) ? rlState.episodes : []
const actionDistribution: { action: string; count: number }[] = Array.isArray(rlState.actionDistribution) ? rlState.actionDistribution : []
return (
<div className="flex flex-col gap-3">
{/* Stats row */}
<div className="grid grid-cols-3 gap-1.5">
{[
{ label: 'Episodes', value: totalEpisodes, color: 'text-blue-400' },
{ label: 'Success', value: `${(successRate * 100).toFixed(0)}%`, color: 'text-green-400' },
{ label: 'Alpha', value: currentAlpha.toFixed(3), color: 'text-orange-400' },
].map((s) => (
<div
key={s.label}
className="bg-white/5 rounded-xl p-2 text-center"
>
<div className={`text-sm font-bold font-mono ${s.color}`}>{s.value}</div>
<div className="text-[9px] text-gray-500 mt-0.5">{s.label}</div>
</div>
))}
</div>
{/* Reward per episode */}
{episodes.length > 0 && (
<div>
<div className="flex items-center justify-between mb-1.5">
<div className="text-[10px] text-gray-500 font-medium">Reward per Episode</div>
<button
onClick={() => void load()}
className="p-1 rounded hover:bg-white/5 text-gray-600 hover:text-gray-400 transition-colors"
title="Refresh"
>
<RefreshCw size={10} />
</button>
</div>
<ResponsiveContainer width="100%" height={110}>
<LineChart data={episodes} margin={{ top: 4, right: 4, bottom: 0, left: -20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#ffffff08" />
<XAxis dataKey="episode" tick={{ fontSize: 9, fill: '#6b7280' }} />
<YAxis domain={[-1, 1]} tick={{ fontSize: 9, fill: '#6b7280' }} />
<Tooltip content={<CustomTooltip />} />
<ReferenceLine y={0} stroke="#ffffff20" strokeDasharray="3 3" />
<Line
type="monotone"
dataKey="totalReward"
name="Reward"
stroke="#f97316"
strokeWidth={2}
dot={episodes.length < 30 ? { fill: '#f97316', r: 2 } : false}
activeDot={{ r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
)}
{/* Action distribution */}
{actionDistribution.length > 0 && (
<div>
<div className="text-[10px] text-gray-500 mb-1.5 font-medium">
LinUCB Action Distribution
</div>
<ResponsiveContainer width="100%" height={90}>
<BarChart
data={actionDistribution}
margin={{ top: 4, right: 4, bottom: 0, left: -20 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="#ffffff08" />
<XAxis
dataKey="action"
tick={{ fontSize: 8, fill: '#6b7280' }}
tickFormatter={(v: string) => v.replace('FIX_', '').slice(0, 6)}
/>
<YAxis tick={{ fontSize: 9, fill: '#6b7280' }} />
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="count" name="Uses" fill="#8b5cf6" radius={[3, 3, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
)}
{/* Success rate line */}
{episodes.length >= 3 && (
<div>
<div className="text-[10px] text-gray-500 mb-1.5 font-medium">
Rolling Success Rate
</div>
<ResponsiveContainer width="100%" height={80}>
<LineChart data={episodes} margin={{ top: 4, right: 4, bottom: 0, left: -20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#ffffff08" />
<XAxis dataKey="episode" tick={{ fontSize: 9, fill: '#6b7280' }} />
<YAxis domain={[0, 1]} tick={{ fontSize: 9, fill: '#6b7280' }} />
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="successRate"
name="Success"
stroke="#22c55e"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
)}
</div>
)
}
|