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 (

{d.task_id?.slice(0, 16)}

domain: {d.domain}

tier: {d.difficulty_tier}

pass@k: {d.pass_at_k != null ? d.pass_at_k.toFixed(3) : 'n/a'}

sophistication: {d.corruption_sophistication}

obfuscation: {d.obfuscation_depth}

) } 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 (
{/* Stats strip */}
{[ ['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]) => (
{val}
{label}
))}
{/* Table */}
{/* Filters */}
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" />
{/* Table */}
{loading && !data ? (
) : error ? (
{error}
) : ( {filtered.map(t => ( ))} {filtered.length === 0 && ( )}
ID Domain Tier pass@k Soph. Obfusc. Gen?
{t.task_id?.slice(0, 10)} {t.pass_at_k != null ? t.pass_at_k.toFixed(3) : 'n/a'} {t.corruption_sophistication ?? 'n/a'} {t.obfuscation_depth ?? 'n/a'} {t.is_generated ? gen : seed}
no tasks match filter
)}
{/* Scatter */}

Sophistication vs Obfuscation

Coloured by difficulty tier

{scatterData.length > 0 ? ( <> ['', 'low', 'med', 'high'][v] || ''} tick={{ fill: '#7080aa', fontSize: 10, fontFamily: 'JetBrains Mono' }} /> } cursor={{ fill: 'rgba(0,212,255,0.05)' }} /> {scatterData.map((d, i) => ( ))}
{Object.entries(TIER_COLOR).map(([tier, color]) => ( {tier} ))}
) : (
no tasks
)}
) }