Spaces:
Sleeping
Sleeping
File size: 8,727 Bytes
03a7eb9 | 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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Play, RotateCcw, Zap, Shield, AlertTriangle,
ChevronRight, Cpu, Gauge
} from 'lucide-react';
import clsx from 'clsx';
const TASKS = [
{
id: 'auto', label: 'Auto', name: 'Adaptive Curriculum', difficulty: 'info', icon: Gauge,
desc: 'Scales difficulty based on agent performance.'
},
{
id: 'easy', label: 'Easy', name: 'Fix average_list()', difficulty: 'easy', icon: Zap,
desc: 'Syntax errors β missing colon, wrong built-in.'
},
{
id: 'medium', label: 'Medium', name: 'Fix binary_search()', difficulty: 'medium', icon: Cpu,
desc: 'Logic bugs β off-by-one, infinite loop.'
},
{
id: 'hard', label: 'Hard', name: 'Optimize subarray', difficulty: 'hard', icon: AlertTriangle,
desc: 'Replace O(NΒ³) with Kadane\'s O(N).'
},
{
id: 'sandbox', label: 'Sandbox', name: 'Custom Code & Debug', difficulty: 'sandbox', icon: Play,
desc: 'Write custom code, debug it, and measure time complexity.'
},
];
const diffColors = {
info: { bg: 'bg-blue-500/10', border: 'border-blue-500/30', text: 'text-blue-400', dot: 'bg-blue-400' },
easy: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400', dot: 'bg-emerald-400' },
medium: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400', dot: 'bg-amber-400' },
hard: { bg: 'bg-red-500/10', border: 'border-red-500/30', text: 'text-red-400', dot: 'bg-red-400' },
sandbox: { bg: 'bg-purple-500/10', border: 'border-purple-500/30', text: 'text-purple-400', dot: 'bg-purple-400' },
};
export default function Sidebar({
selectedTask, onSelectTask,
onStartEpisode, onReset,
isRunning, episodeHistory,
serverStatus,
}) {
const [historyOpen, setHistoryOpen] = useState(false);
return (
<aside className="flex flex-col h-full border-r border-[var(--border-subtle)] bg-[var(--bg-secondary)] overflow-hidden">
{/* ββ Header βββββββββββββββββββββββ */}
<div className="px-4 py-4 border-b border-[var(--border-subtle)]">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-400 to-emerald-600 flex items-center justify-center">
<span className="text-sm font-bold text-black">C</span>
</div>
<div>
<h1 className="text-sm font-bold tracking-wide text-[var(--text-primary)]">
Code<span className="text-emerald-400">Arena</span>
<span className="text-purple-400 ml-1">RL</span>
</h1>
<p className="text-[10px] text-[var(--text-muted)] tracking-widest uppercase">
Debug Benchmark
</p>
</div>
</div>
{/* Server status */}
<div className="mt-3 flex items-center gap-2 text-[11px]">
<span className={clsx(
'w-2 h-2 rounded-full',
serverStatus === 'online' ? 'bg-emerald-400 shadow-[0_0_6px_rgba(0,255,136,0.5)]' :
serverStatus === 'checking' ? 'bg-amber-400 animate-pulse' : 'bg-red-400'
)} />
<span className="text-[var(--text-muted)] font-mono">
FastAPI {serverStatus === 'online' ? 'β Online' : serverStatus === 'checking' ? 'β Checkingβ¦' : 'β Offline'}
</span>
</div>
</div>
{/* ββ Task Selector ββββββββββββββββ */}
<div className="flex-1 overflow-y-auto px-3 py-3 space-y-2">
<p className="text-[10px] font-semibold tracking-[0.15em] uppercase text-[var(--text-muted)] px-1 mb-1">
Select Task
</p>
{TASKS.map((t) => {
const colors = diffColors[t.difficulty];
const active = selectedTask === t.id;
const Icon = t.icon;
return (
<motion.button
key={t.id}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
disabled={isRunning}
onClick={() => onSelectTask(t.id)}
className={clsx(
'w-full text-left rounded-xl p-3 transition-all duration-200 border cursor-pointer',
'disabled:opacity-40 disabled:cursor-not-allowed',
active
? `${colors.bg} ${colors.border} shadow-lg`
: 'bg-[var(--bg-elevated)] border-[var(--border-subtle)] hover:border-[var(--border-active)]'
)}
>
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<Icon size={14} className={active ? colors.text : 'text-[var(--text-muted)]'} />
<span className={clsx('text-xs font-semibold', active ? colors.text : 'text-[var(--text-primary)]')}>
{t.name}
</span>
</div>
<span className={clsx(
'text-[9px] font-bold tracking-wider uppercase px-2 py-0.5 rounded',
colors.bg, colors.text, 'border', colors.border
)}>
{t.label}
</span>
</div>
<p className="text-[10px] text-[var(--text-muted)] leading-relaxed pl-[22px]">{t.desc}</p>
</motion.button>
);
})}
</div>
{/* ββ Actions ββββββββββββββββββββββ */}
<div className="px-3 pb-3 space-y-2">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.97 }}
disabled={isRunning || serverStatus !== 'online'}
onClick={onStartEpisode}
className={clsx(
'w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-xs font-bold tracking-wide',
'transition-all duration-200 cursor-pointer',
'disabled:opacity-40 disabled:cursor-not-allowed',
'bg-gradient-to-r from-emerald-500 to-emerald-600 text-black',
'hover:shadow-[0_0_20px_rgba(0,255,136,0.3)]'
)}
>
<Play size={14} /> START EPISODE
</motion.button>
<button
disabled={isRunning}
onClick={onReset}
className={clsx(
'w-full flex items-center justify-center gap-2 py-2 rounded-xl text-xs font-medium',
'border border-[var(--border-subtle)] text-[var(--text-secondary)]',
'hover:border-[var(--border-active)] hover:text-[var(--text-primary)]',
'transition-all disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer'
)}
>
<RotateCcw size={12} /> Reset
</button>
</div>
{/* ββ Episode History ββββββββββββββ */}
<div className="border-t border-[var(--border-subtle)]">
<button
onClick={() => setHistoryOpen(o => !o)}
className="w-full flex items-center justify-between px-4 py-2.5 text-[10px] font-semibold tracking-[0.15em] uppercase text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors cursor-pointer"
>
<span>History ({episodeHistory.length})</span>
<ChevronRight size={12} className={clsx('transition-transform', historyOpen && 'rotate-90')} />
</button>
<AnimatePresence>
{historyOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="overflow-hidden"
>
<div className="px-3 pb-3 space-y-1.5 max-h-40 overflow-y-auto">
{episodeHistory.length === 0 && (
<p className="text-[10px] text-[var(--text-muted)] italic px-1">No episodes yet</p>
)}
{episodeHistory.map((ep, i) => (
<div key={i} className="flex items-center justify-between bg-[var(--bg-elevated)] rounded-lg px-3 py-2 text-[10px]">
<span className="text-[var(--text-secondary)] font-mono">
#{episodeHistory.length - i} Β· {ep.taskId}
</span>
<span className="font-bold font-mono" style={{ color: ep.reward >= 0.7 ? '#00FF88' : ep.reward >= 0.4 ? '#FFAA00' : '#FF4455' }}>
{ep.reward.toFixed(2)}
</span>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</aside>
);
}
|