const statusEl = document.getElementById("status"); const messagesEl = document.getElementById("messages"); const formEl = document.getElementById("chat-form"); const inputEl = document.getElementById("chat-input"); const settingsToggle = document.getElementById("settings-toggle"); const settingsDrawer = document.getElementById("settings-drawer"); const settingsClose = document.getElementById("settings-close"); const rangeInputs = settingsDrawer ? Array.from(settingsDrawer.querySelectorAll("input[type='range']")) : []; const scheme = window.location.protocol === "https:" ? "wss" : "ws"; const socketUrl = `${scheme}://${window.location.host}/ws`; let socket; let streamingBubble = null; const addMessage = (text, direction) => { const bubble = document.createElement("div"); bubble.className = `message message--${direction}`; bubble.textContent = text; messagesEl.appendChild(bubble); messagesEl.scrollTop = messagesEl.scrollHeight; }; const connect = () => { socket = new WebSocket(socketUrl); socket.addEventListener("open", () => { statusEl.textContent = "Connected"; }); socket.addEventListener("message", (event) => { let payload; try { payload = JSON.parse(event.data); } catch (error) { addMessage(event.data, "in"); return; } if (payload.type === "start") { streamingBubble = document.createElement("div"); streamingBubble.className = "message message--in"; streamingBubble.textContent = ""; messagesEl.appendChild(streamingBubble); messagesEl.scrollTop = messagesEl.scrollHeight; return; } if (payload.type === "chunk") { if (!streamingBubble) { streamingBubble = document.createElement("div"); streamingBubble.className = "message message--in"; messagesEl.appendChild(streamingBubble); } streamingBubble.textContent += payload.data ?? ""; messagesEl.scrollTop = messagesEl.scrollHeight; return; } if (payload.type === "end") { streamingBubble = null; return; } }); socket.addEventListener("close", () => { statusEl.textContent = "Disconnected"; }); socket.addEventListener("error", () => { statusEl.textContent = "Connection error"; }); }; const updateDrawerValue = (input) => { const output = input.parentElement?.querySelector(".drawer__value"); if (!output) return; output.textContent = input.value; }; rangeInputs.forEach((input) => { updateDrawerValue(input); input.addEventListener("input", () => updateDrawerValue(input)); }); const closeDrawer = () => { settingsDrawer?.classList.remove("drawer--open"); if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "true"); if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "false"); }; const openDrawer = () => { settingsDrawer?.classList.add("drawer--open"); if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "false"); if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "true"); }; settingsToggle?.addEventListener("click", () => { if (settingsDrawer?.classList.contains("drawer--open")) { closeDrawer(); } else { openDrawer(); } }); settingsClose?.addEventListener("click", closeDrawer); formEl.addEventListener("submit", (event) => { event.preventDefault(); const text = inputEl.value.trim(); if (!text) return; if (!socket || socket.readyState !== WebSocket.OPEN) { addMessage("Not connected.", "in"); return; } addMessage(text, "out"); socket.send(text); inputEl.value = ""; inputEl.focus(); }); connect();