(() => { const el = { statusDot: document.getElementById("statusDot"), messages: document.getElementById("messages"), form: document.getElementById("chatForm"), input: document.getElementById("messageInput"), sendBtn: document.getElementById("sendBtn"), tplUser: document.getElementById("msg-user"), tplAssistant: document.getElementById("msg-assistant"), }; const state = { sending: false, }; function setStatus(ok) { el.statusDot.classList.toggle("ok", !!ok); el.statusDot.classList.toggle("err", !ok); } async function healthCheck() { try { const res = await fetch("/api/health", { cache: "no-store" }); setStatus(res.ok); } catch { setStatus(false); } } function appendMessage(role, text) { const tpl = role === "user" ? el.tplUser : el.tplAssistant; const node = tpl.content.cloneNode(true); const bubble = node.querySelector(".bubble"); bubble.textContent = text; el.messages.appendChild(node); el.messages.scrollTop = el.messages.scrollHeight; } function setSending(sending) { state.sending = sending; el.input.disabled = sending; el.sendBtn.disabled = sending; el.sendBtn.textContent = sending ? "Sending..." : "Send"; } async function sendMessage(text) { setSending(true); try { const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "same-origin", body: JSON.stringify({ message: text }), }); if (!res.ok) { let detail = ""; try { const data = await res.json(); detail = data.detail || res.statusText; } catch { detail = res.statusText; } throw new Error(detail || `HTTP ${res.status}`); } const data = await res.json(); appendMessage("assistant", data.reply ?? ""); setStatus(true); } catch (err) { appendMessage("assistant", `Error: ${err.message || err}`); setStatus(false); } finally { setSending(false); } } el.form.addEventListener("submit", async (e) => { e.preventDefault(); const text = (el.input.value || "").trim(); if (!text || state.sending) return; appendMessage("user", text); el.input.value = ""; await sendMessage(text); }); // Submit on Enter, allow Shift+Enter for newline (if we switch to textarea later) el.input.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); el.form.requestSubmit(); } }); // Initial status check healthCheck(); // Initialize session and show start message if configured (async function initSession() { try { const res = await fetch("/api/session", { credentials: "same-origin" }); if (res.ok) { const data = await res.json(); if (data.start_message) { appendMessage("assistant", data.start_message); } } } catch (_) { // no-op } })(); // Periodic health check setInterval(healthCheck, 15000); })();