// Chat-with-the-LLM drawer on the planner: stateless per page load, grounded server-side in // the current plan + learned profiles. The whole visible conversation is resent each turn. import { api } from "./api.js?v=__ASSET_VERSION__"; export function initPlanChat({ getPlan }) { const thread = document.getElementById("chat-thread"); const form = document.getElementById("chat-form"); const input = document.getElementById("chat-input"); const send = document.getElementById("chat-send"); if (!thread || !form) return; const messages = []; function bubble(role, content, pending = false) { const div = document.createElement("div"); div.className = `chat-msg ${role}${pending ? " pending" : ""}`; div.textContent = content; thread.append(div); thread.scrollTop = thread.scrollHeight; return div; } bubble( "assistant", "Ask me anything about this plan — why a number is what it is, what to change for a different cup, or how your machine's history should shape it.", ); form.addEventListener("submit", async (event) => { event.preventDefault(); const content = input.value.trim(); if (!content || send.disabled) return; input.value = ""; messages.push({ role: "user", content }); bubble("user", content); const pending = bubble("assistant", "Thinking…", true); send.disabled = true; try { const body = await api("/api/plan-chat", { method: "POST", body: JSON.stringify({ plan: getPlan(), messages }), }); messages.push({ role: "assistant", content: body.reply }); pending.classList.remove("pending"); pending.textContent = body.reply; } catch (error) { messages.pop(); // keep history consistent with what's on screen pending.classList.remove("pending"); pending.textContent = error.code === "no_model" ? "No LLM model is configured on the server — ask an admin to set one on the Admin page." : `That didn't work: ${error.message}`; } finally { send.disabled = false; thread.scrollTop = thread.scrollHeight; } }); }