Spaces:
Sleeping
Sleeping
havinashpatil
Finalizing CodeArena RL Benchmark: frontend improvements, GRPO training scripts, and cleaned environment
03a7eb9 | import { motion } from 'framer-motion'; | |
| import { | |
| LineChart, Line, XAxis, YAxis, Tooltip, | |
| ResponsiveContainer, ReferenceLine, Area, AreaChart | |
| } from 'recharts'; | |
| import { | |
| Trophy, TrendingUp, Clock, Sparkles, | |
| CheckCircle2, XCircle, MessageSquareText, BarChart3 | |
| } from 'lucide-react'; | |
| import clsx from 'clsx'; | |
| function rewardColor(r) { | |
| if (r >= 0.75) return '#00FF88'; | |
| if (r >= 0.45) return '#FFAA00'; | |
| return '#FF4455'; | |
| } | |
| function StatCard({ icon: Icon, label, value, color, subtitle }) { | |
| return ( | |
| <div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3"> | |
| <div className="flex items-center gap-2 mb-1.5"> | |
| <Icon size={12} className={color || 'text-[var(--text-muted)]'} /> | |
| <span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">{label}</span> | |
| </div> | |
| <div className="text-xl font-bold font-mono" style={{ color: color ? undefined : 'var(--text-primary)' }}> | |
| <span className={color}>{value}</span> | |
| </div> | |
| {subtitle && <p className="text-[9px] text-[var(--text-muted)] mt-0.5">{subtitle}</p>} | |
| </div> | |
| ); | |
| } | |
| function RewardChart({ rewards }) { | |
| const data = rewards.map((r, i) => ({ step: i + 1, reward: r })); | |
| // Pad to 5 steps for consistent chart | |
| for (let i = data.length + 1; i <= 5; i++) { | |
| data.push({ step: i, reward: null }); | |
| } | |
| return ( | |
| <div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3"> | |
| <div className="flex items-center gap-2 mb-2"> | |
| <BarChart3 size={12} className="text-emerald-400" /> | |
| <span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]"> | |
| Reward Curve | |
| </span> | |
| </div> | |
| <div className="h-[100px]"> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <AreaChart data={data} margin={{ top: 5, right: 5, left: -25, bottom: 0 }}> | |
| <defs> | |
| <linearGradient id="rewardGrad" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stopColor="#00FF88" stopOpacity={0.3} /> | |
| <stop offset="100%" stopColor="#00FF88" stopOpacity={0} /> | |
| </linearGradient> | |
| </defs> | |
| <XAxis dataKey="step" stroke="#1E293B" tick={{ fill: '#334155', fontSize: 9, fontFamily: 'monospace' }} /> | |
| <YAxis domain={[0, 1]} ticks={[0, 0.5, 1]} stroke="#1E293B" tick={{ fill: '#334155', fontSize: 9, fontFamily: 'monospace' }} /> | |
| <ReferenceLine y={0.5} stroke="#1E293B" strokeDasharray="4 4" /> | |
| <Tooltip | |
| contentStyle={{ | |
| backgroundColor: '#0F172A', border: '1px solid #1E293B', | |
| borderRadius: 8, fontFamily: 'monospace', fontSize: 10, | |
| }} | |
| itemStyle={{ color: '#00FF88' }} | |
| formatter={(val) => val !== null ? val.toFixed(3) : 'β'} | |
| /> | |
| <Area type="monotone" dataKey="reward" stroke="#00FF88" strokeWidth={2} | |
| fill="url(#rewardGrad)" dot={{ fill: '#0B0F19', stroke: '#00FF88', strokeWidth: 2, r: 3 }} | |
| connectNulls={false} isAnimationActive /> | |
| </AreaChart> | |
| </ResponsiveContainer> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default function RewardPanel({ | |
| rewards, stepCount, isDone, | |
| rewardComponents, feedback, | |
| attempts, | |
| }) { | |
| const latestReward = rewards.length > 0 ? rewards[rewards.length - 1] : null; | |
| const avgReward = rewards.length > 0 ? rewards.reduce((a, b) => a + b, 0) / rewards.length : 0; | |
| const success = latestReward !== null && latestReward >= 0.85; | |
| return ( | |
| <aside className="flex flex-col h-full border-l border-[var(--border-subtle)] bg-[var(--bg-secondary)] overflow-y-auto"> | |
| {/* ββ Reward Hero βββββββββββββ */} | |
| <div className={clsx( | |
| 'px-4 py-5 border-b border-[var(--border-subtle)] text-center', | |
| isDone && success && 'animate-pulse-glow' | |
| )}> | |
| <p className="text-[9px] font-bold tracking-[0.15em] uppercase text-[var(--text-muted)] mb-1"> | |
| {isDone ? (success ? 'β¦ Episode Complete' : 'Episode Finished') : 'Current Reward'} | |
| </p> | |
| <motion.div | |
| key={latestReward} | |
| initial={{ scale: 0.8, opacity: 0 }} | |
| animate={{ scale: 1, opacity: 1 }} | |
| transition={{ type: 'spring', stiffness: 200 }} | |
| className="text-4xl font-bold font-mono" | |
| style={{ color: latestReward !== null ? rewardColor(latestReward) : 'var(--text-muted)' }} | |
| > | |
| {latestReward !== null ? latestReward.toFixed(3) : 'β'} | |
| </motion.div> | |
| {isDone && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 6 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="mt-2 flex items-center justify-center gap-1.5 text-xs font-medium" | |
| > | |
| {success | |
| ? <><CheckCircle2 size={14} className="text-emerald-400" /> <span className="text-emerald-400">All tests passed!</span></> | |
| : <><XCircle size={14} className="text-red-400" /> <span className="text-red-400">Incomplete fix</span></>} | |
| </motion.div> | |
| )} | |
| </div> | |
| {/* ββ Stats Grid ββββββββββββββ */} | |
| <div className="px-3 py-3 grid grid-cols-2 gap-2"> | |
| <StatCard icon={TrendingUp} label="Steps" value={`${stepCount}/5`} subtitle="Max 5 per episode" /> | |
| <StatCard icon={Trophy} label="Average" value={avgReward.toFixed(3)} | |
| color={avgReward >= 0.7 ? 'text-emerald-400' : avgReward >= 0.4 ? 'text-amber-400' : 'text-red-400'} | |
| subtitle="Mean reward" /> | |
| </div> | |
| {/* ββ Chart βββββββββββββββββββ */} | |
| <div className="px-3 pb-3"> | |
| <RewardChart rewards={rewards} /> | |
| </div> | |
| {/* ββ Reward Components βββββββ */} | |
| {rewardComponents && ( | |
| <div className="px-3 pb-3"> | |
| <div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3"> | |
| <div className="flex items-center gap-2 mb-2.5"> | |
| <Sparkles size={12} className="text-purple-400" /> | |
| <span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]"> | |
| Reward Breakdown | |
| </span> | |
| </div> | |
| <div className="space-y-2"> | |
| {[ | |
| { label: 'Compile', value: rewardComponents.compile_score, color: '#63B3ED' }, | |
| { label: 'Test Ratio', value: rewardComponents.test_ratio, color: '#00FF88' }, | |
| { label: 'Efficiency', value: rewardComponents.efficiency, color: '#FFAA00' }, | |
| { label: 'LLM Correct', value: rewardComponents.llm_correctness, color: '#A78BFA' }, | |
| { label: 'LLM Security', value: rewardComponents.llm_security, color: '#F97316' }, | |
| { label: 'LLM Quality', value: rewardComponents.llm_quality, color: '#EC4899' }, | |
| ].map(({ label, value, color }) => ( | |
| <div key={label}> | |
| <div className="flex items-center justify-between text-[10px] mb-0.5"> | |
| <span className="text-[var(--text-muted)]">{label}</span> | |
| <span className="font-mono font-medium" style={{ color }}>{(value ?? 0).toFixed(2)}</span> | |
| </div> | |
| <div className="h-1 bg-[var(--bg-primary)] rounded-full overflow-hidden"> | |
| <motion.div | |
| initial={{ width: 0 }} | |
| animate={{ width: `${((value ?? 0) * 100)}%` }} | |
| transition={{ duration: 0.6, ease: 'easeOut' }} | |
| className="h-full rounded-full" | |
| style={{ backgroundColor: color }} | |
| /> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* ββ LLM Feedback ββββββββββββ */} | |
| {feedback && ( | |
| <div className="px-3 pb-3"> | |
| <div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3"> | |
| <div className="flex items-center gap-2 mb-2"> | |
| <MessageSquareText size={12} className="text-blue-400" /> | |
| <span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]"> | |
| Execution Info | |
| </span> | |
| </div> | |
| <p className="text-[11px] text-[var(--text-secondary)] leading-relaxed font-mono whitespace-pre-wrap"> | |
| {feedback} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| {/* ββ Attempt Timeline ββββββββ */} | |
| {attempts.length > 0 && ( | |
| <div className="px-3 pb-4"> | |
| <div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3"> | |
| <div className="flex items-center gap-2 mb-2.5"> | |
| <Clock size={12} className="text-amber-400" /> | |
| <span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]"> | |
| Attempt Timeline | |
| </span> | |
| </div> | |
| <div className="space-y-2"> | |
| {attempts.map((a, i) => ( | |
| <motion.div | |
| key={i} | |
| initial={{ opacity: 0, y: 6 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ delay: i * 0.05 }} | |
| className="flex items-center gap-2" | |
| > | |
| <div className="w-5 h-5 rounded-full flex items-center justify-center text-[9px] font-bold border" | |
| style={{ | |
| borderColor: rewardColor(a.reward), | |
| color: rewardColor(a.reward), | |
| backgroundColor: `${rewardColor(a.reward)}15`, | |
| }} | |
| > | |
| {i + 1} | |
| </div> | |
| <div className="flex-1 h-px bg-[var(--border-subtle)]" /> | |
| <span className="text-[10px] font-mono font-medium" style={{ color: rewardColor(a.reward) }}> | |
| {a.reward.toFixed(3)} | |
| </span> | |
| <span className="text-[9px] text-[var(--text-muted)]"> | |
| {a.passed}/{a.total} | |
| </span> | |
| </motion.div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </aside> | |
| ); | |
| } | |