const loginView = document.querySelector("#login-view"); const chatView = document.querySelector("#chat-view"); const loginForm = document.querySelector("#login-form"); const loginError = document.querySelector("#login-error"); const messages = document.querySelector("#messages"); const chatForm = document.querySelector("#chat-form"); const messageInput = document.querySelector("#message"); const sendButton = document.querySelector("#send"); const logoutButton = document.querySelector("#logout"); const activeFocus = document.querySelector("#active-focus"); let token = sessionStorage.getItem("domovoy.chat.token") || ""; let conversationID = sessionStorage.getItem("domovoy.chat.conversation") || ""; function showChat() { loginView.hidden = true; chatView.hidden = false; if (!messages.children.length) { appendMessage("assistant", "Я рядом. Напишите обычное сообщение, без внутренних идентификаторов."); } messageInput.focus(); } function showLogin() { chatView.hidden = true; loginView.hidden = false; } function appendMessage(kind, text, meta = "") { const node = document.createElement("div"); node.className = `message ${kind}`; node.textContent = text; if (meta) { const small = document.createElement("small"); small.textContent = meta; node.appendChild(small); } messages.appendChild(node); messages.scrollTop = messages.scrollHeight; } function updateActiveFocus(value, provenance = "") { if (!activeFocus) return; activeFocus.textContent = value ? `фокус: ${value}${provenance ? ` · ${provenance}` : ""}` : "фокус: не выбран"; } async function api(path, options = {}) { const headers = {"Content-Type": "application/json", ...(options.headers || {})}; if (token) headers.Authorization = `Bearer ${token}`; const response = await fetch(path, {...options, headers}); if (!response.ok) throw new Error(await response.text()); return response.json(); } loginForm.addEventListener("submit", async (event) => { event.preventDefault(); loginError.textContent = ""; const username = document.querySelector("#username").value; const password = document.querySelector("#password").value; try { const result = await api("/api/session", {method: "POST", body: JSON.stringify({username, password})}); token = result.Token || result.token; sessionStorage.setItem("domovoy.chat.token", token); showChat(); } catch { loginError.textContent = "Вход не выполнен"; } }); chatForm.addEventListener("submit", async (event) => { event.preventDefault(); const text = messageInput.value.trim(); if (!text) return; appendMessage("user", text); messageInput.value = ""; sendButton.disabled = true; try { const result = await api("/api/chat/messages", {method: "POST", body: JSON.stringify({conversation_id: conversationID, message: text})}); conversationID = result.conversation_id; sessionStorage.setItem("domovoy.chat.conversation", conversationID); updateActiveFocus(result.active_focus, result.project_provenance); const semanticMeta = [result.speech_act, `cognition=${result.cognition}`]; if (result.work_planned) semanticMeta.push("work=planned"); if (result.capability_status) semanticMeta.push(`capability=${result.capability_kind || "unknown"}:${result.capability_status}`); if (result.reminder_scheduled) semanticMeta.push("reminder=scheduled"); if (result.clarification) semanticMeta.push(`clarification=${result.clarification}`); appendMessage("assistant", result.reply_text || "Сообщение записано.", semanticMeta.join("; ")); } catch { appendMessage("assistant", "Чат сейчас недоступен. Сообщение не принято runtime."); } finally { sendButton.disabled = false; messageInput.focus(); } }); logoutButton.addEventListener("click", () => { token = ""; conversationID = ""; sessionStorage.removeItem("domovoy.chat.token"); sessionStorage.removeItem("domovoy.chat.conversation"); messages.replaceChildren(); updateActiveFocus(""); showLogin(); }); if (token) { showChat(); } else { showLogin(); }