const messagesEl = document.querySelector("#messages"); const statusEl = document.querySelector("#connection-status"); const chatForm = document.querySelector("#chat-form"); const userMessage = document.querySelector("#user-message"); const systemPrompts = document.querySelector("#system-prompts"); const preMessages = document.querySelector("#pre-messages"); const preMessageTemplate = document.querySelector("#pre-message-template"); const toolList = document.querySelector("#tool-list"); let socket = null; let activeAssistant = null; let runStartedAt = 0; let firstTokenAt = 0; document.querySelector("#add-system-prompt").addEventListener("click", () => { const textarea = document.createElement("textarea"); textarea.className = "system-prompt"; textarea.rows = 4; systemPrompts.append(textarea); }); document.querySelector("#add-pre-message").addEventListener("click", () => { preMessages.append(preMessageTemplate.content.cloneNode(true)); }); chatForm.addEventListener("submit", (event) => { event.preventDefault(); runDebugSession(); }); loadTools(); async function loadTools() { try { const response = await fetch("/api/tools"); if (!response.ok) { throw new Error(`Tool API failed: ${response.status}`); } renderTools(await response.json()); } catch (error) { toolList.textContent = "Failed to load tools"; } } function renderTools(tools) { toolList.textContent = ""; if (!tools.length) { toolList.textContent = "No tools available"; return; } tools.forEach((tool) => { const label = document.createElement("label"); label.className = "checkbox"; label.title = tool.description || tool.name; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.className = "tool-toggle"; checkbox.value = tool.name; checkbox.checked = true; label.append(checkbox, ` ${tool.name}`); toolList.append(label); }); } function runDebugSession() { closeSocket(); resetRun(); socket = new WebSocket(wsUrl()); socket.addEventListener("open", () => { statusEl.textContent = "Streaming"; runStartedAt = performance.now(); socket.send(JSON.stringify(buildRequest())); }); socket.addEventListener("message", (event) => { handleServerMessage(JSON.parse(event.data)); }); socket.addEventListener("close", () => { statusEl.textContent = "Idle"; updateElapsed(); }); socket.addEventListener("error", () => { appendLog("error", "WebSocket connection failed"); }); } function buildRequest() { return { user_message: userMessage.value, system_prompts: [...document.querySelectorAll(".system-prompt")] .map((input) => input.value.trim()) .filter(Boolean), pre_messages: [...document.querySelectorAll(".pre-message")] .map((row) => ({ role: row.querySelector(".pre-role").value, content: row.querySelector(".pre-content").value.trim(), })) .filter((message) => message.content), chat_agent: { model: document.querySelector("#model").value.trim(), temperature: Number(document.querySelector("#temperature").value), max_tokens: Number(document.querySelector("#max-tokens").value), }, event_agent: { enabled_tools: selectedTools(), max_event_loops: Number(document.querySelector("#max-event-loops").value), }, }; } function selectedTools() { return [...toolList.querySelectorAll(".tool-toggle:checked")].map( (input) => input.value, ); } function handleServerMessage(message) { if (message.type === "session_started") { appendLog("session", "Session started"); return; } if (message.type === "message_delta") { appendAssistantDelta(message.content || ""); return; } if (message.type === "usage") { updateUsage(message.usage || {}); return; } if (message.type === "event") { appendLog("event", `${message.event.name}: ${JSON.stringify(message.event.arguments)}`); return; } if (message.type === "tool_result") { appendLog("tool", message.message.content); return; } if (message.type === "error") { appendLog("error", message.message); return; } if (message.type === "done") { appendLog("session", "Done"); closeSocket(); } } function appendAssistantDelta(content) { if (!firstTokenAt) { firstTokenAt = performance.now(); document.querySelector("#stat-ttft").textContent = `${Math.round(firstTokenAt - runStartedAt)}ms`; } if (!activeAssistant) { activeAssistant = document.createElement("div"); activeAssistant.className = "message assistant"; messagesEl.append(activeAssistant); } activeAssistant.textContent += content; updateElapsed(); messagesEl.scrollTop = messagesEl.scrollHeight; } function appendLog(kind, content) { const item = document.createElement("div"); item.className = `message ${kind}`; item.textContent = content; messagesEl.append(item); messagesEl.scrollTop = messagesEl.scrollHeight; } function updateUsage(usage) { document.querySelector("#stat-tokens").textContent = usage.total_tokens || 0; document.querySelector("#stat-cached").textContent = usage.cached_tokens || 0; } function updateElapsed() { if (!runStartedAt) { return; } document.querySelector("#stat-elapsed").textContent = `${Math.round(performance.now() - runStartedAt)}ms`; } function resetRun() { activeAssistant = null; runStartedAt = 0; firstTokenAt = 0; messagesEl.textContent = ""; document.querySelector("#stat-tokens").textContent = "0"; document.querySelector("#stat-cached").textContent = "0"; document.querySelector("#stat-ttft").textContent = "-"; document.querySelector("#stat-elapsed").textContent = "-"; } function closeSocket() { if (socket && socket.readyState < WebSocket.CLOSING) { socket.close(); } } function wsUrl() { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; return `${protocol}//${window.location.host}/ws/debug`; }