// Browser UI for trying prompts (zero dependencies). // // bun ui.ts then open http://localhost:3000 // PORT=8080 node ui.ts // // Serves ui.html and a JSON endpoint backed by the plain-TypeScript model in model.ts. import { readFileSync } from "node:fs"; import { createServer } from "node:http"; import { join } from "node:path"; import { Autocompleter } from "./model.ts"; const DIR = import.meta.dirname ?? new URL(".", import.meta.url).pathname; const PORT = Number(process.env.PORT ?? 3000); console.log("Loading model..."); const ac = new Autocompleter(DIR); const html = readFileSync(join(DIR, "ui.html")); const cfg = ac.model.cfg; const info = { params: ac.model.numParams, vocab: cfg.vocab_size, layers: cfg.num_hidden_layers, hidden: cfg.hidden_size, neurons: cfg.intermediate_size, }; function json(res: import("node:http").ServerResponse, status: number, body: unknown): void { res.writeHead(status, { "content-type": "application/json" }); res.end(JSON.stringify(body)); } createServer((req, res) => { if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) { res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); res.end(html); } else if (req.method === "GET" && req.url === "/api/info") { json(res, 200, info); } else if (req.method === "POST" && req.url === "/api/complete") { let body = ""; req.on("data", (chunk) => (body += chunk)); req.on("end", () => { try { const { prompt, topK } = JSON.parse(body || "{}"); if (typeof prompt !== "string" || prompt.length > 1000) return json(res, 400, { error: "prompt must be a string under 1000 chars" }); const t0 = performance.now(); const result = prompt.trim() ? ac.complete(prompt, Math.min(Number(topK) || 5, 20)) : { prompt, completion: "", top: [] }; json(res, 200, { ...result, ms: Math.round(performance.now() - t0) }); } catch (e) { json(res, 400, { error: String(e) }); } }); } else { res.writeHead(404).end(); } }).listen(PORT, "127.0.0.1", () => console.log(`tiny-autocomplete UI: http://localhost:${PORT}`));