158 lines
4.4 KiB
JavaScript
158 lines
4.4 KiB
JavaScript
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 = '<div class="typing"><span></span><span></span><span></span></div>';
|
|
|
|
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();
|
|
}
|