import { useState, useRef } from 'react'
import { Play, Terminal, Activity } from 'lucide-react'
import { llmRunStream } from '../lib/api.js'
import { RewardBreakdown } from '../components/RewardBreakdown.jsx'
import { TurnCard } from '../components/TurnCard.jsx'
import { KpiCard } from '../components/KpiCard.jsx'
const PRESET_ENDPOINTS = [
{ label: 'Ollama (localhost:11434)', url: 'http://localhost:11434/v1' },
{ label: 'Hugging Face Router', url: 'https://router.huggingface.co/v1' },
{ label: 'OpenAI', url: 'https://api.openai.com/v1' },
{ label: 'Custom', url: '' },
]
const MODELS = [
'qwen2.5:3b', 'qwen2.5:7b', 'qwen2.5:1.5b',
'Qwen/Qwen2.5-7B-Instruct', 'Qwen/Qwen2.5-3B-Instruct',
'meta-llama/Llama-3.2-3B-Instruct', 'gpt-4o-mini',
]
export function RunWithLlm() {
const [endpoint, setEndpoint] = useState(PRESET_ENDPOINTS[0].label)
const [customUrl, setCustomUrl] = useState('')
const [apiKey, setApiKey] = useState('')
const [model, setModel] = useState(MODELS[0])
const [tier, setTier] = useState('T0')
const [seed, setSeed] = useState(42)
const [temperature, setTemperature] = useState(0.7)
const [maxTurns, setMaxTurns] = useState(10)
const [running, setRunning] = useState(false)
const [turns, setTurns] = useState([])
const [header, setHeader] = useState(null)
const [done, setDone] = useState(null)
const stopRef = useRef(null)
function run() {
setTurns([]); setHeader(null); setDone(null); setRunning(true)
const preset = PRESET_ENDPOINTS.find(p => p.label === endpoint)
const base_url = (customUrl || preset.url).replace(/\/$/, '')
if (stopRef.current) stopRef.current()
stopRef.current = llmRunStream(
{
base_url, api_key: apiKey, model,
tier, seed, temperature, max_turns: maxTurns,
},
(ev) => {
if (ev.kind === 'header') {
setHeader(ev)
} else if (ev.kind === 'turn') {
// SSE event uses `kind` for event type and `kind_of` for action kind;
// TurnCard expects `kind` = action kind, so rename here.
setTurns(prev => [...prev, { ...ev, kind: ev.kind_of }])
} else if (ev.kind === 'done') {
setDone(ev)
setRunning(false)
} else if (ev.kind === 'error') {
setHeader(h => ({ ...(h || {}), error: ev.message }))
setRunning(false)
}
},
)
}
return (
{/* ─── MAIN PANE ─── */}
Transcript
{!header && !turns.length && (
Configure the LLM on the right and hit Run episode.
Each turn streams here as the model plays.
)}
{header && (
Model {header.model}
via
{header.base_url}
Landscape: {header.landscape}
Dim: {header.dim} · Initial budget:{' '}
{header.budget}
{header.error && (
{header.error}
)}
)}
{turns.map((t, i) => )}
{done &&
}
{/* ─── SIDEBAR ─── */}
)
}
function RangeRow({ label, min, max, step, value, onChange }) {
return (
onChange(Number(e.target.value))}
className="w-full accent-accent"
/>
)
}
function EpisodeDone({ done }) {
const reward = done.reward
const speedup = done.speedup_vs_adam
const myProg = done.my_progress
const adamProg = done.adam_progress
const rewardTone =
reward >= 0.5 ? 'good' : reward >= 0 ? 'warn' : 'bad'
// --- Speedup display ---
// "Speedup" only makes sense when both optimizers descended. Handle the
// degenerate cases cleanly rather than showing "-0.44×" (mathematically
// correct, semantically nonsense).
let speedupDisplay, speedupTone, speedupSub
if (myProg < 0) {
// Our optimizer went uphill
speedupDisplay = 'diverged'
speedupTone = 'bad'
speedupSub = `f moved +${Math.abs(myProg).toFixed(2)} (wrong direction)`
} else if (adamProg <= 0) {
// Adam itself couldn't descend — unfair denominator
speedupDisplay = myProg > 0 ? '∞' : '—'
speedupTone = myProg > 0 ? 'good' : 'warn'
speedupSub = 'Adam made no progress on this landscape'
} else {
const f = speedup < 100 ? speedup.toFixed(2) : Math.round(speedup).toString()
speedupDisplay = `${f}×`
speedupTone = speedup >= 1.0 ? 'good' : 'warn'
speedupSub = `descent ${myProg.toFixed(2)} vs Adam ${adamProg.toFixed(2)}`
}
// --- Verdict ---
let verdict, verdictTone, verdictSub
if (myProg < 0) {
verdict = 'Diverged'
verdictTone = 'bad'
verdictSub = 'optimizer moved away from the minimum'
} else if (adamProg <= 0) {
verdict = myProg > 0 ? 'Succeeds where Adam fails' : 'Tied · both stuck'
verdictTone = myProg > 0 ? 'good' : 'warn'
verdictSub = `you: ${myProg.toFixed(2)}, Adam: ${adamProg.toFixed(2)}`
} else if (speedup >= 1.5) {
verdict = 'Beats Adam'
verdictTone = 'good'
verdictSub = `${((speedup - 1) * 100).toFixed(0)}% further than Adam`
} else if (speedup >= 1.1) {
verdict = 'Edges Adam'
verdictTone = 'good'
verdictSub = `${((speedup - 1) * 100).toFixed(0)}% further than Adam`
} else if (speedup >= 0.9) {
verdict = 'Matches Adam'
verdictTone = 'warn'
verdictSub = 'within ±10% of Adam'
} else {
verdict = 'Behind Adam'
verdictTone = 'bad'
verdictSub = `covered ${(speedup * 100).toFixed(0)}% of Adam's descent`
}
return (
Episode complete
ended by {done.reason}
= 0 ? '+' : ''} />
)
}