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"); const todayList = document.querySelector("#today-list"); const refreshToday = document.querySelector("#refresh-today"); const historyList = document.querySelector("#history-list"); const navTabs = Array.from(document.querySelectorAll(".nav-tab")); let token = sessionStorage.getItem("domovoy.chat.token") || ""; let conversationID = sessionStorage.getItem("domovoy.chat.conversation") || ""; let localHistory = []; 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; localHistory.push({kind, text, meta, at: new Date()}); renderHistory(); } function updateActiveFocus(value, provenance = "") { if (!activeFocus) return; activeFocus.textContent = value ? `Сейчас обсуждаем: ${value}` : ""; activeFocus.hidden = !value; } 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(); } function humanMeta(result) { if (result.reminder_scheduled) return "Напоминание сохранено. Я вернусь к нему сам."; if (result.capability_status && result.capability_status !== "AVAILABLE") return "Возможность пока не подключена."; if (result.work_planned) return "Я разложил это как работу."; return ""; } function renderHistory() { if (!historyList) return; historyList.replaceChildren(); const items = localHistory.slice(-30).reverse(); if (!items.length) { const empty = document.createElement("p"); empty.className = "empty-state"; empty.textContent = "История появится после сообщений в этом окне."; historyList.appendChild(empty); return; } for (const item of items) { const row = document.createElement("article"); row.className = `history-item ${item.kind}`; const title = document.createElement("strong"); title.textContent = item.kind === "user" ? "Вы" : "Домовой"; const body = document.createElement("p"); body.textContent = item.text; const time = document.createElement("span"); time.textContent = item.at.toLocaleTimeString("ru-RU", {hour: "2-digit", minute: "2-digit"}); row.append(title, body, time); historyList.appendChild(row); } } async function loadToday() { if (!todayList || !token) return; todayList.replaceChildren(); const loading = document.createElement("p"); loading.className = "empty-state"; loading.textContent = "Смотрю, что требует внимания..."; todayList.appendChild(loading); try { const result = await api("/api/today", {method: "GET"}); todayList.replaceChildren(); const items = result.Items || result.items || []; if (!items.length) { const empty = document.createElement("p"); empty.className = "empty-state"; empty.textContent = "Сейчас ничего срочного не требует внимания."; todayList.appendChild(empty); return; } for (const item of items) { const card = document.createElement("article"); card.className = "attention-card"; const when = document.createElement("time"); const stamp = item.WhenUnixMillis || item.when_unix_millis || 0; when.textContent = stamp ? new Date(stamp).toLocaleString("ru-RU", {day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit"}) : "сейчас"; const title = document.createElement("h2"); title.textContent = item.Title || item.title || "Событие Домового"; const state = document.createElement("p"); state.textContent = item.HumanState || item.human_state || "ожидает внимания"; const source = document.createElement("small"); source.textContent = [item.SourceDescription || item.source_description, item.DeliveryState || item.delivery_state].filter(Boolean).join(" · "); const hintText = item.ResponseHint || item.response_hint || ""; const actions = document.createElement("div"); actions.className = "attention-actions"; for (const [label, text] of [["Готово", "Готово"], ["Отложить", "Отложи на 10 минут"], ["Отменить", "Отмени"]]) { const button = document.createElement("button"); button.type = "button"; button.textContent = label; button.addEventListener("click", async () => { await sendChatMessage(text, {silentUser: false}); await loadToday(); activateTab("chat-panel"); }); actions.appendChild(button); } if (hintText) { const hint = document.createElement("small"); hint.textContent = hintText; card.append(when, title, state, source, hint, actions); } else { card.append(when, title, state, source, actions); } todayList.appendChild(card); } } catch { todayList.replaceChildren(); const error = document.createElement("p"); error.className = "empty-state error"; error.textContent = "Внимание сейчас недоступно. Чат можно продолжать."; todayList.appendChild(error); } } async function sendChatMessage(text, options = {}) { const clean = text.trim(); if (!clean) return; if (!options.silentUser) appendMessage("user", clean); sendButton.disabled = true; try { const result = await api("/api/chat/messages", {method: "POST", body: JSON.stringify({conversation_id: conversationID, message: clean})}); conversationID = result.conversation_id; sessionStorage.setItem("domovoy.chat.conversation", conversationID); updateActiveFocus(result.active_focus, result.project_provenance); appendMessage("assistant", result.reply_text || "Сообщение записано.", humanMeta(result)); if (/что сейчас требует моего внимания/i.test(clean)) { await loadToday(); activateTab("today-panel"); } } catch { appendMessage("assistant", "Чат сейчас недоступен. Сообщение не принято runtime."); } finally { sendButton.disabled = false; messageInput.focus(); } } function activateTab(panelID) { for (const tab of navTabs) tab.classList.toggle("active", tab.dataset.tab === panelID); for (const panel of document.querySelectorAll(".surface-panel")) { const active = panel.id === panelID; panel.hidden = !active; panel.classList.toggle("active-panel", active); } if (panelID === "today-panel") loadToday(); if (panelID === "history-panel") renderHistory(); } 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; messageInput.value = ""; await sendChatMessage(text); }); for (const tab of navTabs) { tab.addEventListener("click", () => activateTab(tab.dataset.tab)); } if (refreshToday) refreshToday.addEventListener("click", loadToday); logoutButton.addEventListener("click", () => { token = ""; conversationID = ""; sessionStorage.removeItem("domovoy.chat.token"); sessionStorage.removeItem("domovoy.chat.conversation"); messages.replaceChildren(); updateActiveFocus(""); showLogin(); }); if (token) { showChat(); } else { showLogin(); }