const chatContainer = document.getElementById("chat-container"); const messageInput = document.getElementById("message-input"); const sendBtn = document.getElementById("send-btn"); const welcomeEl = document.getElementById("welcome"); const statsEl = document.getElementById("stats"); let sessionId = localStorage.getItem("erowid_session") || ""; let isStreaming = false; // Load stats async function loadStats() { try { const resp = await fetch("/stats"); const data = await resp.json(); statsEl.textContent = `${data.experiences} reports | ${data.substances} substances | ${data.chunks} chunks`; } catch { statsEl.textContent = "connecting..."; } } loadStats(); // Auto-resize textarea messageInput.addEventListener("input", () => { messageInput.style.height = "auto"; messageInput.style.height = Math.min(messageInput.scrollHeight, 120) + "px"; }); // Send on Enter (Shift+Enter for newline) messageInput.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); sendBtn.addEventListener("click", sendMessage); // Suggestion clicks document.querySelectorAll(".suggestion").forEach((el) => { el.addEventListener("click", () => { messageInput.value = el.textContent; sendMessage(); }); }); function addMessage(role, content) { if (welcomeEl) welcomeEl.style.display = "none"; const msg = document.createElement("div"); msg.className = `message ${role}`; const avatar = document.createElement("div"); avatar.className = "message-avatar"; avatar.textContent = role === "user" ? "You" : "E"; const contentEl = document.createElement("div"); contentEl.className = "message-content"; contentEl.textContent = content; msg.appendChild(avatar); msg.appendChild(contentEl); chatContainer.appendChild(msg); chatContainer.scrollTop = chatContainer.scrollHeight; return contentEl; } function addTypingIndicator() { const msg = document.createElement("div"); msg.className = "message assistant"; msg.id = "typing-indicator"; const avatar = document.createElement("div"); avatar.className = "message-avatar"; avatar.textContent = "E"; const contentEl = document.createElement("div"); contentEl.className = "message-content"; contentEl.innerHTML = '
'; msg.appendChild(avatar); msg.appendChild(contentEl); chatContainer.appendChild(msg); chatContainer.scrollTop = chatContainer.scrollHeight; } function removeTypingIndicator() { const el = document.getElementById("typing-indicator"); if (el) el.remove(); } async function sendMessage() { const text = messageInput.value.trim(); if (!text || isStreaming) return; isStreaming = true; sendBtn.disabled = true; messageInput.value = ""; messageInput.style.height = "auto"; addMessage("user", text); addTypingIndicator(); try { const resp = await fetch("/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text, session_id: sessionId }), }); removeTypingIndicator(); const contentEl = addMessage("assistant", ""); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let fullResponse = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (!line.startsWith("data: ")) continue; const jsonStr = line.slice(6).trim(); if (!jsonStr) continue; try { const data = JSON.parse(jsonStr); if (data.token) { fullResponse += data.token; contentEl.textContent = fullResponse; chatContainer.scrollTop = chatContainer.scrollHeight; } if (data.session_id) { sessionId = data.session_id; localStorage.setItem("erowid_session", sessionId); } if (data.error) { contentEl.textContent = `Error: ${data.error}`; } } catch {} } } } catch (err) { removeTypingIndicator(); addMessage("assistant", `Connection error: ${err.message}`); } isStreaming = false; sendBtn.disabled = false; messageInput.focus(); }