const messages = document.getElementById("messages"); const input = document.getElementById("user-input"); const sendBtn = document.getElementById("send-btn"); let userName = "Alex"; // текущий SSE поток (ВАЖНО: чтобы не плодить соединения) let eventSource = null; // =============================== // 1. ЗАГРУЗКА ИСТОРИИ // =============================== async function loadHistory() { const res = await fetch("/init"); const data = await res.json(); userName = data.user_name || "User"; data.messages.forEach(msg => { renderMessage(msg.role, msg.content); }); } // =============================== // 2. ОТПРАВКА СООБЩЕНИЯ // =============================== async function sendMessage() { const text = input.value.trim(); if (!text) return; renderMessage("user", text); input.value = ""; // закрываем старый stream если есть if (eventSource) { eventSource.close(); eventSource = null; } // отправляем сообщение на backend (он сохраняет state) await fetch("/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text }) }); // создаём ПУСТОЕ сообщение для стрима const aiMessageDiv = createAIMessagePlaceholder(); // запускаем stream startStream(aiMessageDiv); } // =============================== // 3. SSE STREAM // =============================== function startStream(aiMessageDiv) { eventSource = new EventSource("/stream"); let fullText = ""; eventSource.onmessage = function(event) { const token = event.data; // иногда Ollama может слать [DONE] if (token === "[DONE]") { eventSource.close(); eventSource = null; return; } fullText += token; aiMessageDiv.innerHTML = `AI: ${formatText(fullText)}`; messages.scrollTop = messages.scrollHeight; }; eventSource.onerror = function(err) { console.error("Stream error:", err); eventSource.close(); eventSource = null; }; } // =============================== // 4. РЕНДЕР СООБЩЕНИЙ // =============================== function renderMessage(role, text) { const div = document.createElement("div"); if (role === "user") { div.className = "user-msg"; div.innerHTML = `${userName}: ${text}`; } if (role === "assistant") { div.className = "ai-msg"; div.innerHTML = `AI: ${formatText(text)}`; } messages.appendChild(div); messages.scrollTop = messages.scrollHeight; } // =============================== // 5. ПУСТОЙ КОНТЕЙНЕР ДЛЯ STREAM // =============================== function createAIMessagePlaceholder() { const div = document.createElement("div"); div.className = "ai-msg"; div.innerHTML = `AI: `; messages.appendChild(div); messages.scrollTop = messages.scrollHeight; return div; } // =============================== // 6. ФОРМАТИРОВАНИЕ (*actions*) // =============================== function formatText(text) { // действия *...* return text.replace(/\*(.*?)\*/g, '*$1*'); } // =============================== // 7. EVENTS // =============================== sendBtn.addEventListener("click", sendMessage); input.addEventListener("keypress", (e) => { if (e.key === "Enter") { sendMessage(); } }); // =============================== // 8. INIT // =============================== loadHistory();