import type { Example } from './examples'; import { EXAMPLES, DEFAULT_TOOLS } from './examples'; export interface UI { toolsEl: HTMLTextAreaElement; queryEl: HTMLInputElement; resultEl: HTMLPreElement; statusEl: HTMLSpanElement; examplesEl: HTMLDivElement; } export function mountUI(): UI { const toolsEl = document.getElementById('tools') as HTMLTextAreaElement; const queryEl = document.getElementById('query') as HTMLInputElement; const resultEl = document.getElementById('result') as HTMLPreElement; const statusEl = document.getElementById('status') as HTMLSpanElement; const examplesEl = document.getElementById('examples') as HTMLDivElement; // Pre-populate tools textarea with DEFAULT_TOOLS toolsEl.value = JSON.stringify(DEFAULT_TOOLS, null, 2); // Render example chips — disabled until the model finishes loading queryEl.disabled = true; EXAMPLES.forEach((ex: Example) => { const chip = document.createElement('button'); chip.className = 'example-chip'; chip.textContent = ex.label; chip.disabled = true; chip.addEventListener('click', () => { // Set query value from example queryEl.value = ex.query; // Set tools value if example provides custom tools if (ex.tools) { toolsEl.value = JSON.stringify(ex.tools, null, 2); } else { toolsEl.value = JSON.stringify(DEFAULT_TOOLS, null, 2); } // Dispatch change event on queryEl so main.ts's listener fires queryEl.dispatchEvent(new Event('change', { bubbles: true })); }); examplesEl.appendChild(chip); }); return { toolsEl, queryEl, resultEl, statusEl, examplesEl, }; } export function setStatus(ui: UI, msg: string, busy = false): void { ui.statusEl.textContent = msg; ui.statusEl.classList.toggle('busy', busy); } /** * Toggle the disabled state of the example chips and the query input. * Chips start disabled at mount; main.ts flips them on once the model finishes * loading. Toggling off again during a busy / failed state is also valid. */ export function setInteractiveEnabled(ui: UI, enabled: boolean): void { ui.queryEl.disabled = !enabled; ui.examplesEl.querySelectorAll('button.example-chip') .forEach(btn => { btn.disabled = !enabled; }); } export function renderResult(ui: UI, rawText: string): void { // Remove error class ui.resultEl.classList.remove('error'); // Try parsing as JSON try { const parsed = JSON.parse(rawText); ui.resultEl.textContent = JSON.stringify(parsed, null, 2); } catch { // Not valid JSON, render raw text with note ui.resultEl.textContent = rawText + '\n(could not parse as JSON)'; } } export function renderError(ui: UI, message: string): void { ui.resultEl.classList.add('error'); ui.resultEl.textContent = message; } export function readTools( ui: UI ): { ok: true; tools: unknown[] } | { ok: false; error: string } { try { const parsed = JSON.parse(ui.toolsEl.value); // Ensure result is an array if (!Array.isArray(parsed)) { return { ok: false, error: 'Tools must be a JSON array', }; } return { ok: true, tools: parsed, }; } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); return { ok: false, error: `Failed to parse tools JSON: ${errorMsg}`, }; } }