| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631 |
- const messagesEl = document.querySelector("#messages");
- const statusEl = document.querySelector("#connection-status");
- const chatForm = document.querySelector("#chat-form");
- const userMessage = document.querySelector("#user-message");
- const runButton = document.querySelector("#run-button");
- const sessionTitle = document.querySelector("#session-title");
- const sessionList = document.querySelector("#session-list");
- const sessionStatus = document.querySelector("#session-status");
- const auditReplay = document.querySelector("#audit-replay");
- const auditCount = document.querySelector("#audit-count");
- const auditSummaryEl = document.querySelector("#audit-summary");
- const auditDetail = document.querySelector("#audit-detail");
- const sessionUsageSummary = document.querySelector("#session-usage-summary");
- const sessionUsageTurns = document.querySelector("#session-usage-turns");
- const sessionUsageCalls = document.querySelector("#session-usage-calls");
- const snapshotName = document.querySelector("#workspace-snapshot-name");
- const savedSnapshots = document.querySelector("#saved-workspace-snapshots");
- 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");
- const toolCount = document.querySelector("#tool-count");
- const chatConfigDialog = document.querySelector("#chat-config-dialog");
- const eventConfigDialog = document.querySelector("#event-config-dialog");
- const auditDialog = document.querySelector("#audit-dialog");
- const toolInvocationMode = document.querySelector("#tool-invocation-mode");
- const toolModeHelp = document.querySelector("#tool-mode-help");
- const WORKSPACE_SNAPSHOTS_STORAGE_KEY = "agent-lab.workspace-snapshots.v1";
- const LEGACY_PROMPT_SETS_STORAGE_KEY = "agent-lab.prompt-sets.v1";
- let socket = null;
- let activeAssistant = null;
- let runStartedAt = 0;
- let firstTokenAt = 0;
- let elapsedTimer = 0;
- let hasBackendRoundStats = false;
- let pendingEnabledTools = null;
- let availableTools = [];
- let currentSessionId = null;
- let sessionReplayLoadToken = 0;
- let sessions = [];
- let auditEntries = [];
- let selectedAuditKey = null;
- let liveAuditSequence = 0;
- let turnActive = false;
- bindClick("#add-system-prompt", () => {
- systemPrompts.append(createSystemPrompt({ content: "" }));
- });
- bindClick("#add-pre-message", () => {
- preMessages.append(createPreMessage({ role: "user", content: "" }));
- });
- bindClick("#save-workspace-snapshot", saveWorkspaceSnapshot);
- bindClick("#load-workspace-snapshot", loadWorkspaceSnapshot);
- bindClick("#delete-workspace-snapshot", deleteWorkspaceSnapshot);
- bindClick("#new-session", createSession);
- bindClick("#refresh-sessions", () => {
- loadSessions();
- });
- bindClick("#load-session", () => {
- loadSelectedSession();
- });
- bindClick("#open-audit-dialog", () => {
- auditDialog.showModal();
- });
- bindClick("#close-audit-dialog", () => {
- auditDialog.close();
- });
- bindClick("#open-chat-config-panel", openChatConfig);
- bindClick("#close-chat-config", () => {
- chatConfigDialog.close();
- });
- bindClick("#open-event-config-panel", openEventConfig);
- bindClick("#close-event-config", () => {
- eventConfigDialog.close();
- });
- bindClick("#select-all-tools", () => {
- setAllTools(true);
- });
- bindClick("#clear-tools", () => {
- setAllTools(false);
- });
- toolInvocationMode.addEventListener("change", updateToolModeHelp);
- chatForm.addEventListener("submit", (event) => {
- event.preventDefault();
- runDebugSession();
- });
- initializePromptList();
- updateToolModeHelp();
- refreshWorkspaceSnapshotSelector();
- loadTools();
- loadSessions({ preserveStatus: true });
- renderAuditReplay([]);
- renderSessionUsage({ calls: [], turns: [], session: {} });
- function openChatConfig() {
- chatConfigDialog.showModal();
- }
- function openEventConfig() {
- eventConfigDialog.showModal();
- }
- function bindClick(selector, handler) {
- const element = document.querySelector(selector);
- if (element) {
- element.addEventListener("click", handler);
- }
- }
- 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";
- }
- }
- async function loadSessions(options = {}) {
- if (!options.preserveStatus) {
- setSessionStatus("Refreshing sessions");
- }
- try {
- sessions = await fetchJson("/api/sessions");
- renderSessions();
- if (!options.preserveStatus) {
- setSessionStatus("Sessions refreshed");
- }
- } catch (error) {
- if (!options.preserveStatus) {
- setSessionStatus("Failed to load sessions");
- }
- }
- }
- function renderSessions() {
- const previous = currentSessionId || sessionList.value;
- sessionList.textContent = "";
- const placeholder = document.createElement("option");
- placeholder.value = "";
- placeholder.textContent = sessions.length ? "Select a session" : "No saved sessions";
- sessionList.append(placeholder);
- sessions.forEach((session) => {
- const option = document.createElement("option");
- option.value = session.id;
- option.textContent = `${session.title} (${session.turn_count} turns, ${session.total_tokens} tokens)`;
- sessionList.append(option);
- });
- if (previous && sessions.some((session) => session.id === previous)) {
- sessionList.value = previous;
- }
- }
- async function createSession() {
- if (hasActiveTurn()) {
- setSessionStatus("Finish current turn before switching sessions");
- return;
- }
- closeSocket();
- setSessionStatus("Creating session");
- try {
- const title = sessionTitle.value.trim() || "New session";
- const session = await fetchJson("/api/sessions", {
- method: "POST",
- headers: { "content-type": "application/json" },
- body: JSON.stringify({
- title,
- config: buildWorkspaceSnapshot(),
- }),
- });
- currentSessionId = session.id;
- sessionTitle.value = session.title;
- await loadSessions({ preserveStatus: true });
- await loadSessionReplay(session.id, { preserveStatus: true });
- setSessionStatus("Session created");
- } catch (error) {
- setSessionStatus("Failed to create session");
- }
- }
- async function loadSelectedSession() {
- if (hasActiveTurn()) {
- setSessionStatus("Finish current turn before switching sessions");
- return;
- }
- const sessionId = sessionList.value;
- if (!sessionId) {
- setSessionStatus("Select a session");
- return;
- }
- closeSocket();
- setSessionStatus("Loading session");
- currentSessionId = sessionId;
- await loadSessionReplay(sessionId);
- }
- async function loadSessionReplay(sessionId, options = {}) {
- const loadToken = ++sessionReplayLoadToken;
- const isCurrentLoad = () => (
- loadToken === sessionReplayLoadToken && sessionId === currentSessionId
- );
- try {
- const [session, messages, audit, usage] = await Promise.all([
- fetchJson(`/api/sessions/${sessionId}`),
- fetchJson(`/api/sessions/${sessionId}/messages`),
- fetchJson(`/api/sessions/${sessionId}/audit`),
- fetchJson(`/api/sessions/${sessionId}/usage`),
- ]);
- if (!isCurrentLoad()) {
- return;
- }
- restoreWorkspaceSnapshot(session.config || {});
- renderReplayMessages(messages);
- renderAuditReplay(audit);
- renderSessionUsage(usage);
- sessionTitle.value = session.title;
- sessionList.value = sessionId;
- if (!options.preserveStatus && !isSocketOpen()) {
- setSessionStatus("Session loaded");
- }
- } catch (error) {
- if (!isCurrentLoad()) {
- return;
- }
- if (!options.preserveStatus) {
- setSessionStatus("Failed to load session");
- }
- }
- }
- function setSessionStatus(message) {
- sessionStatus.textContent = message;
- statusEl.textContent = message;
- }
- async function fetchJson(url, options) {
- const response = await fetch(url, options);
- if (!response.ok) {
- throw new Error(`Request failed: ${response.status}`);
- }
- return response.json();
- }
- function renderTools(tools) {
- availableTools = tools;
- toolList.textContent = "";
- if (!tools.length) {
- toolList.textContent = "No tools available";
- updateToolCount();
- updateEventPromptItem(tools);
- return;
- }
- tools.forEach((tool) => {
- const item = document.createElement("div");
- item.className = "tool-card";
- 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;
- checkbox.addEventListener("change", () => {
- updateToolCount();
- updateEventPromptItem(availableTools);
- });
- label.append(checkbox, ` ${tool.name}`);
- const description = document.createElement("div");
- description.className = "tool-description";
- description.textContent = tool.description || "No description";
- const required = document.createElement("div");
- required.className = "tool-required";
- required.textContent = formatRequiredParameters(tool);
- item.append(label, description, required);
- toolList.append(item);
- });
- if (pendingEnabledTools) {
- applySelectedTools(pendingEnabledTools);
- pendingEnabledTools = null;
- }
- updateToolCount();
- updateEventPromptItem(tools);
- }
- function formatRequiredParameters(tool) {
- const required = tool.parameters && Array.isArray(tool.parameters.required)
- ? tool.parameters.required
- : [];
- return required.length ? `Required: ${required.join(", ")}` : "No required parameters";
- }
- function setAllTools(checked) {
- toolList.querySelectorAll(".tool-toggle").forEach((checkbox) => {
- checkbox.checked = checked;
- });
- updateToolCount();
- updateEventPromptItem(availableTools);
- }
- function updateToolCount() {
- const selected = selectedTools().length;
- const total = toolList.querySelectorAll(".tool-toggle").length;
- toolCount.textContent = `${selected}/${total} selected`;
- }
- function saveWorkspaceSnapshot() {
- const name = snapshotName.value.trim();
- if (!name) {
- statusEl.textContent = "Snapshot name required";
- return;
- }
- const store = readWorkspaceSnapshots();
- store.snapshots[name] = buildWorkspaceSnapshot();
- writeWorkspaceSnapshots(store);
- refreshWorkspaceSnapshotSelector(name);
- statusEl.textContent = "Workspace snapshot saved";
- }
- function loadWorkspaceSnapshot() {
- const name = savedSnapshots.value;
- if (!name) {
- statusEl.textContent = "Select a snapshot";
- return;
- }
- const snapshot = readWorkspaceSnapshots().snapshots[name];
- if (!snapshot) {
- statusEl.textContent = "Snapshot not found";
- refreshWorkspaceSnapshotSelector();
- return;
- }
- snapshotName.value = name;
- restoreWorkspaceSnapshot(snapshot);
- statusEl.textContent = "Workspace snapshot loaded";
- }
- function deleteWorkspaceSnapshot() {
- const name = savedSnapshots.value;
- if (!name) {
- statusEl.textContent = "Select a snapshot";
- return;
- }
- const store = readWorkspaceSnapshots();
- delete store.snapshots[name];
- writeWorkspaceSnapshots(store);
- if (snapshotName.value.trim() === name) {
- snapshotName.value = "";
- }
- refreshWorkspaceSnapshotSelector();
- statusEl.textContent = "Workspace snapshot deleted";
- }
- function buildWorkspaceSnapshot() {
- return {
- tool_invocation_mode: document.querySelector("#tool-invocation-mode").value,
- prompt_items: buildPromptItems(),
- system_prompts: buildCustomPromptItems()
- .map((item) => item.content)
- .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),
- system_prompt: "",
- extra_body: buildExtraBody("chat"),
- },
- event_agent: {
- model: document.querySelector("#event-model").value.trim(),
- temperature: Number(document.querySelector("#event-temperature").value),
- max_tokens: Number(document.querySelector("#event-max-tokens").value),
- system_prompt: document.querySelector("#event-system-prompt").value.trim(),
- extra_body: buildExtraBody("event"),
- enabled_tools: selectedTools(),
- max_event_loops: Number(document.querySelector("#max-event-loops").value),
- max_parallel_events: Number(document.querySelector("#max-parallel-events").value),
- batch_timeout_seconds: Number(document.querySelector("#batch-timeout-seconds").value),
- },
- };
- }
- function restoreWorkspaceSnapshot(snapshot) {
- document.querySelector("#tool-invocation-mode").value =
- snapshot.tool_invocation_mode || "dual_agent";
- restoreSystemPrompts(snapshot.prompt_items || snapshot.system_prompts || []);
- restorePreMessages(snapshot.pre_messages || []);
- const chatAgent = snapshot.chat_agent || {};
- document.querySelector("#model").value = chatAgent.model || "";
- document.querySelector("#temperature").value = chatAgent.temperature ?? 0.2;
- document.querySelector("#max-tokens").value = chatAgent.max_tokens ?? 800;
- restoreExtraBody("chat", chatAgent.extra_body);
- const eventAgent = snapshot.event_agent || {};
- document.querySelector("#event-model").value = eventAgent.model || "";
- document.querySelector("#event-temperature").value = eventAgent.temperature ?? 0.2;
- document.querySelector("#event-max-tokens").value = eventAgent.max_tokens ?? 800;
- document.querySelector("#event-system-prompt").value = eventAgent.system_prompt || "";
- restoreExtraBody("event", eventAgent.extra_body);
- document.querySelector("#max-event-loops").value = eventAgent.max_event_loops ?? 1;
- document.querySelector("#max-parallel-events").value =
- eventAgent.max_parallel_events ?? 4;
- document.querySelector("#batch-timeout-seconds").value =
- eventAgent.batch_timeout_seconds ?? 15;
- const enabledTools = eventAgent.enabled_tools || [];
- pendingEnabledTools = applySelectedTools(enabledTools) ? null : enabledTools;
- updateEventPromptItem(availableTools);
- updateToolModeHelp();
- }
- function updateToolModeHelp() {
- if (toolInvocationMode.value === "chat_agent_tools") {
- toolModeHelp.textContent =
- "ChatAgent Tools uses provider-resolved arguments. EventAgent model, temperature, max tokens, system prompt, and extra body are inactive; enabled tools, loop budget, batch concurrency, and timeout remain active.";
- return;
- }
- toolModeHelp.textContent =
- "Dual Agent may use the EventAgent model, system prompt, and extra body for missing-argument fallback.";
- }
- function buildExtraBody(prefix) {
- const thinkingDisabled = document.querySelector(`#${prefix}-thinking-disabled`).checked;
- const enableSearch = document.querySelector(`#${prefix}-enable-search`).checked;
- const forcedSearch = document.querySelector(`#${prefix}-forced-search`).checked;
- const extraBody = {
- enable_search: enableSearch,
- search_options: {
- forced_search: forcedSearch,
- },
- };
- if (thinkingDisabled) {
- extraBody.thinking = { type: "disabled" };
- }
- return extraBody;
- }
- function restoreExtraBody(prefix, extraBody = {}) {
- document.querySelector(`#${prefix}-thinking-disabled`).checked =
- !extraBody.thinking || extraBody.thinking.type === "disabled";
- document.querySelector(`#${prefix}-enable-search`).checked =
- Boolean(extraBody.enable_search);
- document.querySelector(`#${prefix}-forced-search`).checked =
- Boolean(extraBody.search_options && extraBody.search_options.forced_search);
- }
- function restoreSystemPrompts(prompts) {
- systemPrompts.textContent = "";
- const promptItems = normalizePromptItems(prompts);
- promptItems.forEach((prompt) => {
- systemPrompts.append(createSystemPrompt(prompt));
- });
- ensureEventPromptItem();
- }
- function restorePreMessages(messages) {
- preMessages.textContent = "";
- messages.forEach((message) => {
- preMessages.append(createPreMessage(message));
- });
- }
- function createSystemPrompt(prompt) {
- const promptItem = normalizePromptItem(prompt);
- const item = document.createElement("div");
- item.className = promptItem.prompt_kind === "event_agent_system"
- ? "prompt-item event-prompt"
- : "prompt-item";
- item.dataset.promptKind = promptItem.prompt_kind;
- item.dataset.promptRole = promptItem.role;
- item.draggable = true;
- const head = document.createElement("div");
- head.className = "prompt-item-head";
- const meta = document.createElement("div");
- meta.className = "prompt-meta";
- const type = document.createElement("span");
- type.className = "prompt-type";
- type.textContent = `type: ${promptItem.role}`;
- const title = document.createElement("span");
- title.className = "prompt-title";
- title.textContent = promptItem.prompt_kind === "event_agent_system"
- ? "EventAgent Rules"
- : "Custom";
- meta.append(type, title);
- const actions = document.createElement("div");
- actions.className = "prompt-actions";
- const moveUp = document.createElement("button");
- moveUp.className = "move-prompt-up";
- moveUp.type = "button";
- moveUp.textContent = "Up";
- moveUp.addEventListener("click", () => movePromptItem(item, -1));
- const moveDown = document.createElement("button");
- moveDown.className = "move-prompt-down";
- moveDown.type = "button";
- moveDown.textContent = "Down";
- moveDown.addEventListener("click", () => movePromptItem(item, 1));
- actions.append(moveUp, moveDown);
- if (promptItem.prompt_kind !== "event_agent_system") {
- const deleteButton = document.createElement("button");
- deleteButton.className = "delete-system-prompt";
- deleteButton.type = "button";
- deleteButton.textContent = "Delete";
- deleteButton.addEventListener("click", () => item.remove());
- actions.append(deleteButton);
- }
- head.append(meta, actions);
- const textarea = document.createElement("textarea");
- textarea.className = "system-prompt";
- textarea.rows = promptItem.prompt_kind === "event_agent_system" ? 7 : 3;
- textarea.value = promptItem.content;
- textarea.readOnly = promptItem.prompt_kind === "event_agent_system";
- item.append(head, textarea);
- bindPromptDrag(item);
- return item;
- }
- function createPreMessage(message) {
- const fragment = preMessageTemplate.content.cloneNode(true);
- fragment.querySelector(".pre-role").value = message.role || "user";
- fragment.querySelector(".pre-content").value = message.content || "";
- return fragment;
- }
- function applySelectedTools(toolNames) {
- const checkboxes = [...toolList.querySelectorAll(".tool-toggle")];
- if (!checkboxes.length) {
- return false;
- }
- const selected = new Set(toolNames);
- checkboxes.forEach((checkbox) => {
- checkbox.checked = selected.has(checkbox.value);
- });
- updateToolCount();
- updateEventPromptItem(availableTools);
- return true;
- }
- function initializePromptList() {
- [...systemPrompts.querySelectorAll(".prompt-item")].forEach((item) => {
- bindPromptItemControls(item);
- bindPromptDrag(item);
- });
- ensureEventPromptItem();
- }
- function buildPromptItems() {
- return [...systemPrompts.querySelectorAll(".prompt-item")]
- .map((item) => ({
- prompt_kind: item.dataset.promptKind || "custom",
- role: item.dataset.promptRole || "system",
- content: item.querySelector(".system-prompt").value.trim(),
- }))
- .filter((item) => item.prompt_kind === "event_agent_system" || item.content);
- }
- function buildCustomPromptItems() {
- return buildPromptItems().filter((item) => item.prompt_kind !== "event_agent_system");
- }
- function collectSystemPrompts() {
- return buildPromptItems()
- .map((item) => item.content)
- .filter(Boolean);
- }
- function normalizePromptItems(prompts) {
- const items = Array.isArray(prompts) ? prompts : [];
- const normalized = items
- .map((prompt) => normalizePromptItem(prompt))
- .filter((prompt) => prompt.prompt_kind === "event_agent_system" || prompt.content);
- if (!normalized.some((prompt) => prompt.prompt_kind === "event_agent_system")) {
- normalized.push({ prompt_kind: "event_agent_system", content: "" });
- }
- if (!normalized.some((prompt) => prompt.prompt_kind === "custom")) {
- normalized.unshift({ prompt_kind: "custom", role: "system", content: "" });
- }
- return normalized;
- }
- function normalizePromptItem(prompt) {
- if (prompt && typeof prompt === "object" && !Array.isArray(prompt)) {
- return {
- prompt_kind: prompt.prompt_kind === "event_agent_system"
- ? "event_agent_system"
- : "custom",
- role: "system",
- content: prompt.content || "",
- };
- }
- return {
- prompt_kind: "custom",
- role: "system",
- content: prompt || "",
- };
- }
- function buildAvailableEventsPrompt(tools) {
- const eventLines = tools.map((tool) => `- ${tool.name}: ${tool.description || "No description"}`);
- if (!eventLines.length) {
- return "";
- }
- return [
- "You may request EventAgent work using this text protocol.",
- "Available events:",
- ...eventLines,
- "First write the user-facing reply normally. If events are needed, append this block after the visible reply:",
- "<agent_events>",
- "event_name",
- "</agent_events>",
- "Use exact event names only, one per line. Do not include parameters.",
- ].join("\n");
- }
- function updateEventPromptItem(tools) {
- const eventPrompt = ensureEventPromptItem();
- const selected = new Set(selectedTools());
- const prompt = buildAvailableEventsPrompt(
- tools.filter((tool) => selected.has(tool.name)),
- );
- eventPrompt.querySelector(".system-prompt").value = prompt;
- }
- function ensureEventPromptItem() {
- const existing = systemPrompts.querySelector('[data-prompt-kind="event_agent_system"]');
- if (existing) {
- return existing;
- }
- const item = createSystemPrompt({
- prompt_kind: "event_agent_system",
- content: "",
- });
- systemPrompts.append(item);
- return item;
- }
- function bindPromptItemControls(item) {
- const moveUp = item.querySelector(".move-prompt-up");
- if (moveUp) {
- moveUp.addEventListener("click", () => movePromptItem(item, -1));
- }
- const moveDown = item.querySelector(".move-prompt-down");
- if (moveDown) {
- moveDown.addEventListener("click", () => movePromptItem(item, 1));
- }
- const deleteButton = item.querySelector(".delete-system-prompt");
- if (deleteButton) {
- deleteButton.addEventListener("click", () => item.remove());
- }
- }
- function movePromptItem(item, direction) {
- if (direction < 0 && item.previousElementSibling) {
- systemPrompts.insertBefore(item, item.previousElementSibling);
- }
- if (direction > 0 && item.nextElementSibling) {
- systemPrompts.insertBefore(item.nextElementSibling, item);
- }
- }
- function bindPromptDrag(item) {
- item.addEventListener("dragstart", () => {
- item.classList.add("dragging");
- });
- item.addEventListener("dragend", () => {
- item.classList.remove("dragging");
- });
- item.addEventListener("dragover", (event) => {
- event.preventDefault();
- const dragging = systemPrompts.querySelector(".prompt-item.dragging");
- if (!dragging || dragging === item) {
- return;
- }
- const rect = item.getBoundingClientRect();
- const afterMidpoint = event.clientY > rect.top + rect.height / 2;
- systemPrompts.insertBefore(dragging, afterMidpoint ? item.nextSibling : item);
- });
- }
- function refreshWorkspaceSnapshotSelector(selectedName = "") {
- const previousName = selectedName || savedSnapshots.value;
- const names = Object.keys(readWorkspaceSnapshots().snapshots).sort();
- savedSnapshots.textContent = "";
- const placeholder = document.createElement("option");
- placeholder.value = "";
- placeholder.textContent = names.length ? "Select a snapshot" : "No saved snapshots";
- savedSnapshots.append(placeholder);
- names.forEach((name) => {
- const option = document.createElement("option");
- option.value = name;
- option.textContent = name;
- savedSnapshots.append(option);
- });
- if (names.includes(previousName)) {
- savedSnapshots.value = previousName;
- }
- }
- function readWorkspaceSnapshots() {
- try {
- const rawValue = localStorage.getItem(WORKSPACE_SNAPSHOTS_STORAGE_KEY);
- if (!rawValue) {
- return readLegacyPromptSets();
- }
- const parsed = JSON.parse(rawValue);
- if (
- !parsed ||
- typeof parsed !== "object" ||
- !parsed.snapshots ||
- typeof parsed.snapshots !== "object" ||
- Array.isArray(parsed.snapshots)
- ) {
- return { snapshots: {} };
- }
- return parsed;
- } catch {
- return { snapshots: {} };
- }
- }
- function readLegacyPromptSets() {
- try {
- const rawValue = localStorage.getItem(LEGACY_PROMPT_SETS_STORAGE_KEY);
- if (!rawValue) {
- return { snapshots: {} };
- }
- const parsed = JSON.parse(rawValue);
- if (
- !parsed ||
- typeof parsed !== "object" ||
- !parsed.sets ||
- typeof parsed.sets !== "object" ||
- Array.isArray(parsed.sets)
- ) {
- return { snapshots: {} };
- }
- return { snapshots: parsed.sets };
- } catch {
- return { snapshots: {} };
- }
- }
- function writeWorkspaceSnapshots(store) {
- localStorage.setItem(WORKSPACE_SNAPSHOTS_STORAGE_KEY, JSON.stringify(store));
- }
- function runDebugSession() {
- const submittedMessage = userMessage.value.trim();
- if (!submittedMessage) {
- statusEl.textContent = "Message required";
- return;
- }
- if (isSocketOpen()) {
- beginTurn(submittedMessage, "Waiting for first token");
- socket.send(JSON.stringify({ type: "user_message", content: submittedMessage }));
- userMessage.value = "";
- return;
- }
- closeSocket();
- resetRun();
- beginTurn(submittedMessage, "Connecting");
- socket = new WebSocket(wsUrl());
- const activeSocket = socket;
- activeSocket.addEventListener("open", () => {
- if (socket !== activeSocket) {
- return;
- }
- statusEl.textContent = "Waiting for first token";
- activeSocket.send(JSON.stringify(buildRequest(submittedMessage)));
- userMessage.value = "";
- });
- activeSocket.addEventListener("message", (event) => {
- if (socket !== activeSocket) {
- return;
- }
- handleServerMessage(JSON.parse(event.data));
- });
- activeSocket.addEventListener("close", () => {
- if (socket !== activeSocket) {
- return;
- }
- socket = null;
- statusEl.textContent = "Idle";
- stopElapsedTimer();
- setRunState(false);
- updateElapsed();
- });
- activeSocket.addEventListener("error", () => {
- if (socket !== activeSocket) {
- return;
- }
- appendLog("error", "WebSocket connection failed");
- });
- }
- function isSocketOpen() {
- return socket && socket.readyState === WebSocket.OPEN;
- }
- function hasActiveTurn() {
- return turnActive;
- }
- function beginTurn(submittedMessage, statusText) {
- activeAssistant = null;
- firstTokenAt = 0;
- hasBackendRoundStats = false;
- resetTurnStats();
- appendLog("user", submittedMessage);
- setRunState(true);
- runStartedAt = performance.now();
- startElapsedTimer();
- statusEl.textContent = statusText;
- }
- function buildRequest(submittedMessage = userMessage.value) {
- return {
- session_id: currentSessionId || undefined,
- user_message: submittedMessage,
- tool_invocation_mode: document.querySelector("#tool-invocation-mode").value,
- system_prompts: collectSystemPrompts(),
- 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),
- system_prompt: "",
- extra_body: buildExtraBody("chat"),
- },
- event_agent: {
- model: document.querySelector("#event-model").value.trim(),
- temperature: Number(document.querySelector("#event-temperature").value),
- max_tokens: Number(document.querySelector("#event-max-tokens").value),
- system_prompt: document.querySelector("#event-system-prompt").value.trim(),
- extra_body: buildExtraBody("event"),
- enabled_tools: selectedTools(),
- max_event_loops: Number(document.querySelector("#max-event-loops").value),
- max_parallel_events: Number(document.querySelector("#max-parallel-events").value),
- batch_timeout_seconds: Number(document.querySelector("#batch-timeout-seconds").value),
- },
- };
- }
- function selectedTools() {
- return [...toolList.querySelectorAll(".tool-toggle:checked")].map(
- (input) => input.value,
- );
- }
- function handleServerMessage(message) {
- if (message.type === "session_started") {
- if (message.session_id) {
- currentSessionId = message.session_id;
- loadSessions({ preserveStatus: true });
- }
- appendLog("session", "Session started");
- return;
- }
- if (message.type === "turn_started") {
- statusEl.textContent = "Waiting for first token";
- return;
- }
- if (message.type === "message_delta") {
- appendAssistantDelta(message.content || "");
- return;
- }
- if (message.type === "usage") {
- updateUsage(message.usage || {});
- return;
- }
- if (message.type === "round_stats") {
- updateRoundStats(message);
- return;
- }
- if (message.type === "audit") {
- appendAuditEntry(message);
- return;
- }
- if (message.type === "event") {
- appendLog("event", message.event.name);
- return;
- }
- if (message.type === "tool_result") {
- appendLog("tool", message.message.content);
- return;
- }
- if (message.type === "error") {
- appendLog("error", message.message);
- stopElapsedTimer();
- setRunState(false);
- return;
- }
- if (message.type === "turn_completed") {
- appendLog("session", `Turn ${message.turn_index || ""} completed`.trim());
- statusEl.textContent = "Idle";
- stopElapsedTimer();
- setRunState(false);
- updateElapsed();
- if (currentSessionId) {
- loadSessions({ preserveStatus: true });
- loadSessionReplay(currentSessionId, { preserveStatus: true });
- }
- return;
- }
- if (message.type === "done") {
- appendLog("session", "Done");
- statusEl.textContent = "Idle";
- stopElapsedTimer();
- setRunState(false);
- if (currentSessionId) {
- loadSessions({ preserveStatus: true });
- loadSessionReplay(currentSessionId, { preserveStatus: true });
- }
- closeSocket();
- }
- }
- function appendAssistantDelta(content) {
- if (!firstTokenAt) {
- firstTokenAt = performance.now();
- statusEl.textContent = "Streaming";
- if (!hasBackendRoundStats) {
- 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 renderReplayMessages(messages) {
- messagesEl.textContent = "";
- activeAssistant = null;
- if (!messages.length) {
- appendLog("session", "No messages yet");
- return;
- }
- messages.forEach((message) => {
- if (
- message.role === "assistant" &&
- !message.content.trim() &&
- Array.isArray(message.tool_calls) &&
- message.tool_calls.length > 0
- ) {
- return;
- }
- const item = document.createElement("div");
- const kind = ["user", "assistant", "tool"].includes(message.role)
- ? message.role
- : "session";
- item.className = `message ${kind}`;
- item.textContent = message.content;
- messagesEl.append(item);
- });
- messagesEl.scrollTop = messagesEl.scrollHeight;
- }
- function appendAuditEntry(message) {
- const entry = normalizeAuditEntry(message);
- renderAuditReplay([...auditEntries, entry], {
- selectedKey: auditEntryKey(entry),
- scrollToEnd: true,
- });
- }
- function normalizeAuditEntry(entry) {
- const details = entry.details || {};
- return {
- id: entry.id || `live-${++liveAuditSequence}`,
- event: entry.event || "unknown_event",
- details,
- turn_index: entry.turn_index ?? details.turn_index ?? null,
- round_index: entry.round_index ?? details.round_index ?? null,
- turn_elapsed_ms: entry.turn_elapsed_ms ?? details.turn_elapsed_ms ?? null,
- created_at: entry.created_at || new Date().toISOString(),
- };
- }
- function auditEntryKey(entry) {
- return String(entry.id);
- }
- function renderAuditReplay(entries, options = {}) {
- auditEntries = entries.map(normalizeAuditEntry);
- auditReplay.textContent = "";
- auditCount.textContent = `${auditEntries.length} events`;
- if (!auditEntries.length) {
- selectedAuditKey = null;
- auditSummaryEl.textContent = "No audit entries";
- auditReplay.append(emptyReplayRow("No audit entries"));
- renderAuditDetail(null);
- return;
- }
- const keys = auditEntries.map(auditEntryKey);
- selectedAuditKey = options.selectedKey || selectedAuditKey;
- if (!selectedAuditKey || !keys.includes(selectedAuditKey)) {
- selectedAuditKey = keys[keys.length - 1];
- }
- auditEntries.forEach((entry) => {
- const key = auditEntryKey(entry);
- const row = document.createElement("button");
- row.type = "button";
- row.className = `audit-event ${auditKindClass(entry.event)}`;
- row.dataset.auditKey = key;
- if (key === selectedAuditKey) {
- row.classList.add("is-selected");
- }
- row.addEventListener("click", () => {
- selectAuditEntry(key);
- });
- const marker = document.createElement("span");
- marker.className = "audit-marker";
- const body = document.createElement("span");
- body.className = "audit-event-body";
- const meta = document.createElement("span");
- meta.className = "audit-event-meta";
- meta.textContent = auditMeta(entry);
- const title = document.createElement("strong");
- title.textContent = auditTitle(entry.event);
- const summary = document.createElement("span");
- summary.className = "audit-event-summary";
- summary.textContent = auditSummary(entry);
- body.append(meta, title, summary);
- row.append(marker, body);
- auditReplay.append(row);
- });
- renderAuditDetail(
- auditEntries.find((entry) => auditEntryKey(entry) === selectedAuditKey) || null,
- );
- const selectedEntry = auditEntries.find(
- (entry) => auditEntryKey(entry) === selectedAuditKey,
- );
- auditSummaryEl.textContent = selectedEntry
- ? `${auditTitle(selectedEntry.event)} · ${auditMeta(selectedEntry)}`
- : `${auditEntries.length} audit events`;
- if (options.scrollToEnd) {
- auditReplay.scrollTop = auditReplay.scrollHeight;
- }
- }
- function selectAuditEntry(key) {
- selectedAuditKey = key;
- renderAuditReplay(auditEntries);
- }
- function renderAuditDetail(entry) {
- auditDetail.textContent = "";
- if (!entry) {
- auditDetail.append(emptyReplayRow("Select an audit event to inspect details"));
- return;
- }
- const header = document.createElement("div");
- header.className = "audit-detail-header";
- const title = document.createElement("h3");
- title.className = "audit-detail-title";
- title.textContent = auditTitle(entry.event);
- const meta = document.createElement("dl");
- meta.className = "audit-detail-meta";
- appendMetaField(meta, "Turn / Round", auditTurnRound(entry));
- appendMetaField(meta, "Since User Message", auditRelativeTime(entry));
- appendMetaField(meta, "Time", formatTime(entry.created_at) || "-");
- appendMetaField(meta, "Agent", auditKindLabel(entry.event));
- header.append(title, meta);
- auditDetail.append(header);
- const details = entry.details || {};
- if (entry.event === "chat_agent_request" || entry.event === "event_agent_request") {
- appendKeyValueSection(auditDetail, "Model Parameters", details.params);
- appendMessagesSection(auditDetail, details.messages ? "Messages" : "History", details.messages || details.history);
- appendEventsSection(auditDetail, details.events);
- appendToolsSection(auditDetail, details.tools);
- appendKeyValueSection(auditDetail, "Extra Body", details.extra_body);
- } else if (entry.event === "chat_agent_response") {
- appendTextSection(auditDetail, "Assistant Text", details.content);
- appendListSection(auditDetail, "Events", details.event_names);
- appendKeyValueSection(auditDetail, "Usage", details.usage);
- appendRawChunksSection(auditDetail, "Raw LLM Chunks", details.raw_chunks);
- } else if (entry.event === "event_agent_response") {
- appendRepliesSection(auditDetail, details.replies);
- appendRawModelChunksSection(auditDetail, details.raw_model_chunks);
- } else if (entry.event === "chat_event_detected") {
- appendKeyValueSection(auditDetail, "Detected Event", {
- name: details.event_name,
- id: details.event_id,
- });
- } else if (entry.event === "event_agent_completed") {
- appendListSection(auditDetail, "Completed Events", details.event_names);
- appendKeyValueSection(auditDetail, "Results", {
- result_count: details.result_count,
- });
- appendTextSection(auditDetail, "Tool Result Summary", details.result_summary);
- } else if (entry.event === "chat_message_stream_started" || entry.event === "chat_message_stream_finished") {
- appendKeyValueSection(auditDetail, "Message Stream", {
- ttft_ms: details.ttft_ms,
- delta_count: details.delta_count,
- content_length: details.content_length,
- });
- } else if (entry.event === "chat_round_started") {
- appendKeyValueSection(auditDetail, "Event State", {
- event_generation_enabled: details.event_generation_enabled,
- });
- appendListSection(auditDetail, "Enabled This Round", details.events_enabled);
- appendListSection(auditDetail, "Configured Events", details.configured_events);
- } else if (entry.event === "chat_round_finished") {
- appendKeyValueSection(auditDetail, "Round Result", {
- had_event: details.had_event,
- elapsed_ms: details.elapsed_ms,
- });
- } else if (entry.event === "turn_completed") {
- appendKeyValueSection(auditDetail, "Turn Result", {
- round_count: details.round_count,
- });
- } else if (details.raw_chunks) {
- appendRawChunksSection(auditDetail, "Raw LLM Chunks", details.raw_chunks);
- } else if (details.raw_model_chunks) {
- appendRawModelChunksSection(auditDetail, details.raw_model_chunks);
- } else {
- appendKeyValueSection(auditDetail, "Details", details);
- }
- appendRawDetails(auditDetail, details);
- }
- function auditTitle(event) {
- return event
- .split("_")
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
- .join(" ");
- }
- function auditMeta(entry) {
- const time = formatTime(entry.created_at);
- const relativeTime = auditRelativeTime(entry);
- return `${auditTurnRound(entry)} / ${relativeTime}${time ? ` / ${time}` : ""}`;
- }
- function auditTurnRound(entry) {
- const turn = entry.turn_index === null || entry.turn_index === undefined ? "-" : entry.turn_index;
- const round = entry.round_index === null || entry.round_index === undefined ? "-" : entry.round_index;
- return `T${turn} / R${round}`;
- }
- function auditRelativeTime(entry) {
- if (entry.turn_elapsed_ms === null || entry.turn_elapsed_ms === undefined) {
- return "-";
- }
- return `+${formatMs(entry.turn_elapsed_ms)}`;
- }
- function appendMetaField(container, label, value) {
- const item = document.createElement("div");
- const term = document.createElement("dt");
- term.textContent = label;
- const description = document.createElement("dd");
- description.textContent = value;
- item.append(term, description);
- container.append(item);
- }
- function auditSummary(entry) {
- const details = entry.details || {};
- if (entry.event === "chat_agent_request") {
- return `${details.params?.model || "model default"} · ${details.messages?.length || 0} messages`;
- }
- if (entry.event === "event_agent_request") {
- return `${details.params?.model || "model default"} · ${(details.events || []).map((event) => event.name).join(", ") || "no events"}`;
- }
- if (entry.event === "chat_agent_response") {
- const rawCount = details.raw_chunks?.length || 0;
- return `${details.usage?.total_tokens || 0} tokens · ${rawCount} raw chunks · ${(details.event_names || []).join(", ") || "no events"}`;
- }
- if (entry.event === "event_agent_response") {
- const rawCount = (details.raw_model_chunks || []).reduce(
- (count, item) => count + (item.chunks?.length || 0),
- 0,
- );
- return `${details.replies?.length || 0} tool replies · ${rawCount} raw chunks`;
- }
- if (entry.event === "chat_event_detected") {
- return details.event_name || "event detected";
- }
- if (entry.event === "event_agent_completed") {
- return `${details.result_count || 0} results · ${(details.event_names || []).join(", ")}`;
- }
- if (entry.event === "chat_message_stream_started") {
- return `visible text started · TTFT ${formatMs(details.ttft_ms)}`;
- }
- if (entry.event === "chat_message_stream_finished") {
- return `${details.delta_count || 0} deltas · ${details.content_length || 0} chars`;
- }
- if (entry.event === "chat_round_started") {
- const state = details.event_generation_enabled ? "event generation on" : "event generation off";
- return `${details.events_enabled?.length || 0} enabled this round · ${state}`;
- }
- if (entry.event === "chat_round_finished") {
- return `${formatMs(details.elapsed_ms)} · ${details.had_event ? "event" : "no event"}`;
- }
- if (entry.event === "turn_completed") {
- return `${details.round_count || 0} rounds`;
- }
- return JSON.stringify(details).slice(0, 120);
- }
- function auditKindClass(event) {
- if (event.includes("request")) {
- return "audit-kind-request";
- }
- if (event.includes("response") || event.includes("completed")) {
- return "audit-kind-response";
- }
- if (event.includes("event_agent")) {
- return "audit-kind-event-agent";
- }
- if (event.includes("chat")) {
- return "audit-kind-chat";
- }
- return "audit-kind-lifecycle";
- }
- function auditKindLabel(event) {
- if (event.includes("event_agent")) {
- return "EventAgent";
- }
- if (event.includes("chat_agent") || event.includes("chat_round") || event.includes("chat_event") || event.includes("chat_message")) {
- return "ChatAgent";
- }
- return "Lifecycle";
- }
- function appendKeyValueSection(container, title, data) {
- if (!data || !Object.keys(data).length) {
- return;
- }
- const grid = document.createElement("div");
- grid.className = "detail-kv";
- Object.entries(data).forEach(([key, value]) => {
- if (value === null || value === undefined || value === "") {
- return;
- }
- const label = document.createElement("span");
- label.textContent = key;
- const content = document.createElement("strong");
- content.textContent = typeof value === "object" ? JSON.stringify(value) : String(value);
- grid.append(label, content);
- });
- if (grid.children.length) {
- appendDetailSection(container, title, grid);
- }
- }
- function appendRawChunksSection(container, title, chunks) {
- if (!chunks || !chunks.length) {
- return;
- }
- const list = document.createElement("div");
- list.className = "detail-list";
- chunks.forEach((chunk, index) => {
- const item = document.createElement("div");
- item.className = "detail-message raw-chunk";
- const label = document.createElement("span");
- label.textContent = `chunk ${index + 1}`;
- const content = document.createElement("pre");
- content.textContent = JSON.stringify(chunk, null, 2);
- item.append(label, content);
- list.append(item);
- });
- appendDetailSection(container, title, list);
- }
- function appendRawModelChunksSection(container, rawGroups) {
- if (!rawGroups || !rawGroups.length) {
- return;
- }
- const list = document.createElement("div");
- list.className = "detail-list";
- rawGroups.forEach((group) => {
- const item = document.createElement("div");
- item.className = "detail-message raw-chunk";
- const label = document.createElement("span");
- label.textContent = `${group.event_name || "event"} · ${group.event_id || "-"}`;
- const content = document.createElement("pre");
- content.textContent = JSON.stringify(group.chunks || [], null, 2);
- item.append(label, content);
- list.append(item);
- });
- appendDetailSection(container, "Raw EventAgent LLM Chunks", list);
- }
- function appendMessagesSection(container, title, messages) {
- if (!messages || !messages.length) {
- return;
- }
- const list = document.createElement("div");
- list.className = "detail-list";
- messages.forEach((message) => {
- const item = document.createElement("div");
- item.className = "detail-message";
- const role = document.createElement("span");
- role.textContent = message.role || "message";
- const content = document.createElement("pre");
- content.textContent = message.content || "";
- item.append(role, content);
- list.append(item);
- });
- appendDetailSection(container, title, list);
- }
- function appendToolsSection(container, tools) {
- if (!tools || !tools.length) {
- return;
- }
- const list = document.createElement("div");
- list.className = "detail-list";
- tools.forEach((tool) => {
- const item = document.createElement("div");
- item.className = "detail-message";
- const name = document.createElement("span");
- name.textContent = tool.function?.name || "tool";
- const description = document.createElement("p");
- description.textContent = tool.function?.description || "No description";
- item.append(name, description);
- list.append(item);
- });
- appendDetailSection(container, "Tools", list);
- }
- function appendEventsSection(container, events) {
- if (!events || !events.length) {
- return;
- }
- const values = events.map((event) => `${event.name}${event.id ? ` (${event.id})` : ""}`);
- appendListSection(container, "Events", values);
- }
- function appendRepliesSection(container, replies) {
- if (!replies || !replies.length) {
- return;
- }
- const list = document.createElement("div");
- list.className = "detail-list";
- replies.forEach((reply) => {
- const item = document.createElement("div");
- item.className = "detail-message";
- const role = document.createElement("span");
- role.textContent = reply.role || "reply";
- const content = document.createElement("pre");
- content.textContent = reply.content || "";
- item.append(role, content);
- list.append(item);
- });
- appendDetailSection(container, "Tool Replies", list);
- }
- function appendListSection(container, title, items) {
- if (!items || !items.length) {
- return;
- }
- const list = document.createElement("ul");
- list.className = "detail-tags";
- items.forEach((item) => {
- const tag = document.createElement("li");
- const code = document.createElement("code");
- code.textContent = String(item);
- tag.append(code);
- list.append(tag);
- });
- appendDetailSection(container, title, list);
- }
- function appendTextSection(container, title, value) {
- if (!value) {
- return;
- }
- const pre = document.createElement("pre");
- pre.textContent = String(value);
- appendDetailSection(container, title, pre);
- }
- function appendDetailSection(container, title, content) {
- const section = document.createElement("section");
- section.className = "audit-detail-block";
- const heading = document.createElement("h3");
- heading.className = "audit-detail-block-title";
- heading.textContent = title;
- section.append(heading, content);
- container.append(section);
- }
- function appendRawDetails(container, details) {
- if (!details || !Object.keys(details).length) {
- return;
- }
- const raw = document.createElement("details");
- raw.className = "raw-detail";
- const summary = document.createElement("summary");
- summary.textContent = "Raw JSON";
- const pre = document.createElement("pre");
- pre.textContent = JSON.stringify(details, null, 2);
- raw.append(summary, pre);
- container.append(raw);
- }
- function formatTime(value) {
- if (!value) {
- return "";
- }
- const date = new Date(value);
- if (Number.isNaN(date.getTime())) {
- return value;
- }
- return date.toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- });
- }
- function renderSessionUsage(usage) {
- const session = usage.session || {};
- sessionUsageSummary.textContent = "";
- [
- ["Tokens", session.total_tokens || 0],
- ["Cached", session.cached_tokens || 0],
- ["Prompt", session.prompt_tokens || 0],
- ["Elapsed", formatMs(session.elapsed_ms || 0)],
- ].forEach(([label, value]) => {
- const item = document.createElement("div");
- const labelEl = document.createElement("span");
- labelEl.textContent = label;
- const valueEl = document.createElement("strong");
- valueEl.textContent = value;
- item.append(labelEl, valueEl);
- sessionUsageSummary.append(item);
- });
- renderUsageRows(
- sessionUsageTurns,
- usage.turns || [],
- (turn) => `Turn ${turn.turn_index}: ${turn.total_tokens} tokens, ${turn.elapsed_ms}ms`,
- );
- renderUsageRows(
- sessionUsageCalls,
- usage.calls || [],
- (call) => `T${call.turn_index} R${call.round_index}: ${call.total_tokens} tokens, TTFT ${formatMs(call.ttft_ms)}, elapsed ${formatMs(call.elapsed_ms)}`,
- );
- }
- function renderUsageRows(container, rows, formatter) {
- container.textContent = "";
- if (!rows.length) {
- container.append(emptyReplayRow("No usage rows"));
- return;
- }
- rows.forEach((row) => {
- const item = document.createElement("div");
- item.className = "replay-row";
- item.textContent = formatter(row);
- container.append(item);
- });
- }
- function emptyReplayRow(content) {
- const row = document.createElement("div");
- row.className = "replay-row";
- row.textContent = content;
- return row;
- }
- function updateUsage(usage) {
- if (hasBackendRoundStats) {
- return;
- }
- document.querySelector("#stat-tokens").textContent = usage.total_tokens || 0;
- document.querySelector("#stat-cached").textContent = usage.cached_tokens || 0;
- }
- function updateRoundStats(stats) {
- if (firstTokenAt) {
- hasBackendRoundStats = true;
- }
- document.querySelector("#stat-tokens").textContent = stats.total_tokens || 0;
- document.querySelector("#stat-cached").textContent = stats.cached_tokens || 0;
- const ttftText = formatMs(stats.ttft_ms);
- const elapsedText = formatMs(stats.elapsed_ms);
- document.querySelector("#stat-ttft").textContent = ttftText;
- document.querySelector("#stat-elapsed").textContent = elapsedText;
- appendLog(
- "session",
- `Round ${stats.round_index}: tokens ${stats.total_tokens || 0}, cached ${stats.cached_tokens || 0}, TTFT ${ttftText}, elapsed ${elapsedText}`,
- );
- }
- function formatMs(value) {
- return value === null || value === undefined ? "-" : `${value}ms`;
- }
- function updateElapsed() {
- if (!runStartedAt || (hasBackendRoundStats && firstTokenAt)) {
- return;
- }
- document.querySelector("#stat-elapsed").textContent = `${Math.round(performance.now() - runStartedAt)}ms`;
- }
- function startElapsedTimer() {
- stopElapsedTimer();
- elapsedTimer = window.setInterval(updateElapsed, 100);
- }
- function stopElapsedTimer() {
- if (elapsedTimer) {
- window.clearInterval(elapsedTimer);
- elapsedTimer = 0;
- }
- }
- function setRunState(isRunning) {
- turnActive = isRunning;
- runButton.disabled = isRunning;
- }
- function resetRun() {
- stopElapsedTimer();
- activeAssistant = null;
- runStartedAt = 0;
- firstTokenAt = 0;
- hasBackendRoundStats = false;
- messagesEl.textContent = "";
- resetTurnStats();
- }
- function resetTurnStats() {
- document.querySelector("#stat-tokens").textContent = "0";
- document.querySelector("#stat-cached").textContent = "0";
- document.querySelector("#stat-ttft").textContent = "-";
- document.querySelector("#stat-elapsed").textContent = "-";
- }
- function closeSocket() {
- const closingSocket = socket;
- if (closingSocket && closingSocket.readyState < WebSocket.CLOSING) {
- closingSocket.close();
- }
- if (socket === closingSocket) {
- socket = null;
- }
- }
- function wsUrl() {
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
- return `${protocol}//${window.location.host}/ws/debug`;
- }
|