import { MODEL_ID, createPiAgent } from "./piAgent.js"; import { createSandbox } from "./sandbox.js"; import "./styles.css"; const nodes = { status: document.querySelector("#status"), modelStatus: document.querySelector("#model-status"), sandboxStatus: document.querySelector("#sandbox-status"), transcript: document.querySelector("#transcript"), eventLog: document.querySelector("#event-log"), files: document.querySelector("#files"), prompt: document.querySelector("#prompt"), run: document.querySelector("#run"), boot: document.querySelector("#boot-sandbox"), reset: document.querySelector("#reset-sandbox"), demo: document.querySelector("#demo-prompt"), mode: document.querySelector("#mode"), device: document.querySelector("#device"), maxTokens: document.querySelector("#max-new-tokens"), temperature: document.querySelector("#temperature"), modelLabel: document.querySelector("#model-label"), }; nodes.modelLabel.textContent = MODEL_ID; if (!navigator.gpu) nodes.device.value = "wasm"; const params = new URLSearchParams(window.location.search); if (params.get("mode") === "mock") nodes.mode.value = "mock"; if (params.get("device")) nodes.device.value = params.get("device"); const sandbox = createSandbox({ onStatus: (text) => setSandboxStatus(text), onLog: (text) => logEvent("sandbox", text), }); let agent = createAgent(); function textFromContent(content) { if (typeof content === "string") return content; if (!Array.isArray(content)) return ""; return content .filter((part) => part.type === "text") .map((part) => part.text) .join("\n"); } function setStatus(text) { nodes.status.textContent = text; } function setSandboxStatus(text) { nodes.sandboxStatus.textContent = text; } function setModelStatus(text) { nodes.modelStatus.textContent = text; } function logEvent(kind, text) { const line = `[${new Date().toLocaleTimeString()}] ${kind}: ${text}`; nodes.eventLog.textContent = `${nodes.eventLog.textContent}${line}\n`; nodes.eventLog.scrollTop = nodes.eventLog.scrollHeight; } function createAgent() { const next = createPiAgent({ sandbox, modelMode: () => nodes.mode.value, device: () => nodes.device.value, maxTokens: () => nodes.maxTokens.value, temperature: () => nodes.temperature.value, onModelStatus: setModelStatus, }); next.subscribe((event) => { switch (event.type) { case "agent_start": setStatus("Agent running"); logEvent("agent", "start"); break; case "message_end": renderTranscript(); break; case "tool_execution_start": logEvent("tool", `${event.toolName} started`); break; case "tool_execution_end": logEvent("tool", `${event.toolName} finished`); break; case "agent_end": setStatus("Ready"); renderTranscript(); refreshFiles().catch((error) => logEvent("files", error.message)); break; default: break; } }); return next; } function resetAgent() { agent.abort(); agent = createAgent(); renderTranscript(); } function renderTranscript() { const rendered = agent.state.messages .map((message) => { if (message.role === "toolResult") { return `TOOL ${message.toolName}${message.isError ? " ERROR" : ""}\n${textFromContent(message.content)}`; } const heading = message.role.toUpperCase(); const toolCalls = message.role === "assistant" ? message.content .filter((part) => part.type === "toolCall") .map((part) => `\nTOOL CALL ${part.name} ${JSON.stringify(part.arguments)}`) .join("") : ""; return `${heading}\n${textFromContent(message.content)}${toolCalls}`; }) .join("\n\n"); nodes.transcript.textContent = rendered || "No messages yet."; nodes.transcript.scrollTop = nodes.transcript.scrollHeight; } async function refreshFiles() { if (!sandbox.isReady) { nodes.files.textContent = "Sandbox not booted."; return; } nodes.files.textContent = await sandbox.listFiles("."); } async function bootSandbox() { nodes.boot.disabled = true; try { await sandbox.boot(); await refreshFiles(); } catch (error) { setSandboxStatus("Sandbox error"); logEvent("sandbox", error.stack || error.message || String(error)); } finally { nodes.boot.disabled = false; } } nodes.boot.addEventListener("click", bootSandbox); nodes.reset.addEventListener("click", async () => { nodes.reset.disabled = true; try { await sandbox.reset(); resetAgent(); await refreshFiles(); } catch (error) { logEvent("reset", error.stack || error.message || String(error)); } finally { nodes.reset.disabled = false; } }); nodes.demo.addEventListener("click", () => { nodes.prompt.value = "Create hello.js that prints the result of 21 * 2, run it with Node, and tell me the command output."; }); nodes.run.addEventListener("click", async () => { nodes.run.disabled = true; try { await bootSandbox(); await agent.prompt(nodes.prompt.value); } catch (error) { setStatus("Error"); logEvent("agent", error.stack || error.message || String(error)); } finally { nodes.run.disabled = false; } }); nodes.mode.addEventListener("change", () => { resetAgent(); setModelStatus(nodes.mode.value === "mock" ? "Deterministic test model" : "Model idle"); }); setStatus("Ready"); setSandboxStatus("Not booted"); setModelStatus(nodes.mode.value === "mock" ? "Deterministic test model" : "Model idle"); renderTranscript(); refreshFiles().catch(() => {});