import { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import CodeMirror from '@uiw/react-codemirror' import { javascript } from '@codemirror/lang-javascript' import { oneDark } from '@codemirror/theme-one-dark' import { api } from '../api' import { Field, downloadJSON } from '../components' import { useDebouncedSave } from '../hooks/useDebouncedSave' const SLOTS = [ { key: 'library_js', label: 'Library', hint: 'Shared code prepended to all three hooks.' }, { key: 'input_js', label: 'Input', hint: "onInput — modifies the player's input before context construction." }, { key: 'context_js', label: 'Context', hint: 'onModelContext — modifies the assembled text sent to the model.' }, { key: 'output_js', label: 'Output', hint: 'onOutput — modifies the model output before it is shown.' }, ] export default function ScriptEditor() { const { id } = useParams() const navigate = useNavigate() const [script, setScript] = useState(null) const [slot, setSlot] = useState('input_js') const [status, setStatus] = useState('') const debounceSave = useDebouncedSave() // Test-run state const [testHook, setTestHook] = useState('input') const [testText, setTestText] = useState('> You look around.') const [testState, setTestState] = useState('{}') const [testResult, setTestResult] = useState(null) useEffect(() => { api.getScript(id).then(setScript).catch(() => navigate('/scripts')) }, [id, navigate]) const setField = (field, value) => { setScript((prev) => ({ ...prev, [field]: value })) debounceSave(field, async () => { await api.updateScript(id, { [field]: value }) setStatus('Saved') setTimeout(() => setStatus(''), 1500) }) } const runTest = async () => { let state try { state = JSON.parse(testState || '{}') } catch { setTestResult({ error: 'Test state is not valid JSON' }) return } try { setTestResult(await api.testScript(id, { hook: testHook, text: testText, state })) } catch (err) { setTestResult({ error: err.message }) } } const exportScript = async () => { downloadJSON(await api.exportScript(id), `${script.name.replace(/\W+/g, '-')}.json`) } const deleteScript = async () => { if (!confirm('Delete this script permanently?')) return await api.deleteScript(id) navigate('/scripts') } if (!script) return null const activeSlot = SLOTS.find((s) => s.key === slot) return (

Edit Script

{status}
setField('name', v)} /> setField('description', v)} textarea rows={2} placeholder="What this script does." />
{SLOTS.map((s) => ( ))}
{activeSlot.hint}
setField(slot, value)} />

Test Run

{testResult && (
{testResult.error ? (
{testResult.error}
) : ( <>
text
{testResult.text}
{testResult.stop &&
stop true — the AI call would be skipped
}
state
{JSON.stringify(testResult.state)}
{testResult.storyCards.length > 0 && (
storyCards
{JSON.stringify(testResult.storyCards, null, 1)}
)} {testResult.logs.length > 0 && (
logs
{testResult.logs.join('\n')}
)} )}
)}
) }