forge-arena / ui /src /pages /TaskBank.jsx
Amogh-kal1's picture
Upload folder using huggingface_hub
db75f77 verified
import { useState, useCallback, useMemo } from 'react'
import {
ScatterChart, Scatter, XAxis, YAxis, Tooltip, CartesianGrid,
ResponsiveContainer, Cell,
} from 'recharts'
import { Search, Filter, RefreshCw } from 'lucide-react'
import Card from '../components/ui/Card'
import Badge from '../components/ui/Badge'
import Button from '../components/ui/Button'
import Spinner from '../components/ui/Spinner'
import { usePolling } from '../hooks/usePolling'
import { getTasks } from '../api/client'
const TIER_COLOR = {
learnable: '#00ff88',
'too-easy': '#00d4ff',
'too-hard': '#ff3366',
unestimated: '#2e3452',
}
function CustomTooltip({ active, payload }) {
if (!active || !payload?.length) return null
const d = payload[0]?.payload
if (!d) return null
return (
<div className="custom-tooltip text-xs space-y-1">
<p className="text-primary font-semibold">{d.task_id?.slice(0, 16)}</p>
<p><span className="text-muted">domain: </span><span className="text-neon">{d.domain}</span></p>
<p><span className="text-muted">tier: </span><span style={{ color: TIER_COLOR[d.difficulty_tier] }}>{d.difficulty_tier}</span></p>
<p><span className="text-muted">pass@k: </span><span className="text-primary">{d.pass_at_k != null ? d.pass_at_k.toFixed(3) : 'n/a'}</span></p>
<p><span className="text-muted">sophistication: </span><span className="text-primary">{d.corruption_sophistication}</span></p>
<p><span className="text-muted">obfuscation: </span><span className="text-primary">{d.obfuscation_depth}</span></p>
</div>
)
}
const OBF_ORDER = { low: 1, medium: 2, high: 3 }
export default function TaskBank() {
const fetcher = useCallback(() => getTasks(), [])
const { data, loading, error, refresh } = usePolling(fetcher, 30000)
const [search, setSearch] = useState('')
const [filterDomain, setFilterDomain] = useState('all')
const [filterTier, setFilterTier] = useState('all')
const tasks = data?.tasks ?? []
const domains = useMemo(() => ['all', ...new Set(tasks.map(t => t.domain))], [tasks])
const tiers = ['all', 'learnable', 'too-easy', 'too-hard', 'unestimated']
const filtered = useMemo(() => tasks.filter(t => {
if (filterDomain !== 'all' && t.domain !== filterDomain) return false
if (filterTier !== 'all' && t.difficulty_tier !== filterTier) return false
if (search && !t.task_id?.includes(search) && !t.domain?.includes(search) && !t.task_description?.toLowerCase().includes(search.toLowerCase())) return false
return true
}), [tasks, filterDomain, filterTier, search])
// Scatter data: x = corruption_sophistication, y = obfuscation (encoded), colored by tier
const scatterData = useMemo(() =>
filtered.map(t => ({
...t,
x: t.corruption_sophistication ?? 0,
y: OBF_ORDER[t.obfuscation_depth] ?? 2,
})), [filtered])
const stats = useMemo(() => ({
total: tasks.length,
learnable: tasks.filter(t => t.difficulty_tier === 'learnable').length,
tooEasy: tasks.filter(t => t.difficulty_tier === 'too-easy').length,
tooHard: tasks.filter(t => t.difficulty_tier === 'too-hard').length,
generated: tasks.filter(t => t.is_generated).length,
}), [tasks])
return (
<div className="p-6 space-y-5 animate-fade-in">
{/* Stats strip */}
<div className="grid grid-cols-5 gap-3">
{[
['Total Tasks', stats.total, '#7080aa'],
['Learnable', stats.learnable, '#00ff88'],
['Too Easy', stats.tooEasy, '#00d4ff'],
['Too Hard', stats.tooHard, '#ff3366'],
['Generated', stats.generated, '#9955ff'],
].map(([label, val, color]) => (
<Card key={label} className="p-4" glow>
<div className="text-2xl font-mono font-bold" style={{ color }}>{val}</div>
<div className="text-xs text-muted mt-0.5">{label}</div>
</Card>
))}
</div>
<div className="grid grid-cols-5 gap-5">
{/* Table */}
<div className="col-span-3 space-y-3">
{/* Filters */}
<div className="flex gap-2">
<div className="flex-1 relative">
<Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" />
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search tasks..."
className="w-full bg-card border border-border rounded-lg pl-9 pr-3 py-2 text-sm text-primary font-mono placeholder:text-muted focus:border-neon-dim transition-colors"
/>
</div>
<select
value={filterDomain}
onChange={e => setFilterDomain(e.target.value)}
className="bg-card border border-border text-sm text-secondary rounded-lg px-3 py-2 font-mono focus:border-neon-dim transition-colors"
>
{domains.map(d => <option key={d} value={d}>{d === 'all' ? 'All Domains' : d.replace(/_/g, ' ')}</option>)}
</select>
<select
value={filterTier}
onChange={e => setFilterTier(e.target.value)}
className="bg-card border border-border text-sm text-secondary rounded-lg px-3 py-2 font-mono focus:border-neon-dim transition-colors"
>
{tiers.map(t => <option key={t} value={t}>{t === 'all' ? 'All Tiers' : t}</option>)}
</select>
<Button onClick={refresh} variant="secondary" size="md">
<RefreshCw size={13} />
</Button>
</div>
{/* Table */}
<Card>
<div className="overflow-auto max-h-[540px]">
{loading && !data ? (
<div className="flex justify-center py-12"><Spinner /></div>
) : error ? (
<div className="text-center py-12 text-red-neon text-sm">{error}</div>
) : (
<table className="w-full text-xs font-mono">
<thead className="sticky top-0 bg-card">
<tr className="border-b border-border text-muted">
<th className="text-left py-3 px-4">ID</th>
<th className="text-left py-3 px-4">Domain</th>
<th className="text-left py-3 px-4">Tier</th>
<th className="text-right py-3 px-4">pass@k</th>
<th className="text-right py-3 px-4">Soph.</th>
<th className="text-left py-3 px-4">Obfusc.</th>
<th className="text-left py-3 px-4">Gen?</th>
</tr>
</thead>
<tbody>
{filtered.map(t => (
<tr key={t.task_id} className="border-b border-border/40 hover:bg-card-hover transition-colors">
<td className="py-2.5 px-4 text-muted">{t.task_id?.slice(0, 10)}</td>
<td className="py-2.5 px-4"><Badge value={t.domain} type="domain" /></td>
<td className="py-2.5 px-4"><Badge value={t.difficulty_tier ?? 'unestimated'} type="tier" /></td>
<td className="py-2.5 px-4 text-right" style={{ color: TIER_COLOR[t.difficulty_tier] || '#7080aa' }}>
{t.pass_at_k != null ? t.pass_at_k.toFixed(3) : 'n/a'}
</td>
<td className="py-2.5 px-4 text-right text-primary">{t.corruption_sophistication ?? 'n/a'}</td>
<td className="py-2.5 px-4">
<span className={`px-1.5 py-0.5 rounded text-xs ${
t.obfuscation_depth === 'high' ? 'text-red-neon bg-red-neon/10' :
t.obfuscation_depth === 'medium' ? 'text-orange-neon bg-orange-neon/10' :
'text-green-neon bg-green-neon/10'
}`}>{t.obfuscation_depth ?? 'n/a'}</span>
</td>
<td className="py-2.5 px-4">
{t.is_generated ? <span className="text-purple-neon">gen</span> : <span className="text-muted">seed</span>}
</td>
</tr>
))}
{filtered.length === 0 && (
<tr><td colSpan={7} className="py-12 text-center text-muted">no tasks match filter</td></tr>
)}
</tbody>
</table>
)}
</div>
</Card>
</div>
{/* Scatter */}
<div className="col-span-2">
<Card className="p-5 h-full" glow>
<h3 className="text-sm font-semibold text-secondary mb-1">Sophistication vs Obfuscation</h3>
<p className="text-xs text-muted mb-4">Coloured by difficulty tier</p>
{scatterData.length > 0 ? (
<>
<ResponsiveContainer width="100%" height={340}>
<ScatterChart margin={{ left: -10 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1c1c3a" />
<XAxis dataKey="x" name="Sophistication"
label={{ value: 'Sophistication', position: 'insideBottom', offset: -4, fill: '#7080aa', fontSize: 10 }}
tick={{ fill: '#7080aa', fontSize: 10, fontFamily: 'JetBrains Mono' }}
/>
<YAxis dataKey="y" name="Obfuscation" domain={[0, 4]}
tickFormatter={v => ['', 'low', 'med', 'high'][v] || ''}
tick={{ fill: '#7080aa', fontSize: 10, fontFamily: 'JetBrains Mono' }}
/>
<Tooltip content={<CustomTooltip />} cursor={{ fill: 'rgba(0,212,255,0.05)' }} />
<Scatter data={scatterData} isAnimationActive={false}>
{scatterData.map((d, i) => (
<Cell key={i} fill={TIER_COLOR[d.difficulty_tier] || '#2e3452'}
style={{ filter: `drop-shadow(0 0 4px ${TIER_COLOR[d.difficulty_tier] || '#2e3452'}80)` }}
/>
))}
</Scatter>
</ScatterChart>
</ResponsiveContainer>
<div className="flex flex-wrap gap-3 mt-3">
{Object.entries(TIER_COLOR).map(([tier, color]) => (
<span key={tier} className="flex items-center gap-1 text-xs font-mono text-secondary">
<span className="w-2 h-2 rounded-full" style={{ background: color }} />
{tier}
</span>
))}
</div>
</>
) : (
<div className="flex items-center justify-center h-64 text-muted text-sm">no tasks</div>
)}
</Card>
</div>
</div>
</div>
)
}