diff --git a/cmd/creator-web/static/assets/chat.css b/cmd/creator-web/static/assets/chat.css index 98e6390..7e3ca78 100644 --- a/cmd/creator-web/static/assets/chat.css +++ b/cmd/creator-web/static/assets/chat.css @@ -167,6 +167,81 @@ textarea { resize: vertical; min-height: 84px; max-height: 240px; padding: 13px; .chat-side { padding-inline: 14px; } } +/* Chat-first pass: navigation and context stay out of the conversation's way. */ +.chat-header { gap: 10px; padding-inline: 18px; } +.header-icon, .header-control, .profile-button { height: 32px; border: 1px solid var(--line); background: transparent; font: 10px/1 var(--mono); } +.header-icon { width: 32px; padding: 0; color: var(--muted); font-size: 15px; } +.header-control, .profile-button { padding: 0 10px; color: var(--muted); } +.header-control { color: var(--ink); } +.header-icon:hover, .header-icon:focus-visible, .header-control:hover, .header-control:focus-visible, .profile-button:hover, .profile-button:focus-visible { border-color: var(--blue); color: var(--blue); outline: none; } +.header-focus { min-width: 0; overflow: hidden; color: var(--muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; } +.chat-header .cognitive-status { margin-left: auto; max-width: 250px; } +.chat-header .cognitive-console-link { display: none; } +.chat-layout { grid-template-columns: 242px minmax(0, 1fr); } +.chat-layout.nav-collapsed { grid-template-columns: 0 minmax(0, 1fr); } +.chat-left { padding: 24px 16px; transition: opacity .18s ease, padding .18s ease, transform .18s ease; } +.chat-layout.nav-collapsed .chat-left { padding-inline: 0; overflow: hidden; opacity: 0; border-right: 0; } +.side-intro h1 { font-size: 26px; } +.side-section { margin-top: 18px; } +.human-nav { margin-top: 12px; } +.nav-tab { grid-template-columns: 1fr; min-height: 44px; padding-inline: 10px; } +.chat-left .nav-tab b { display: none; } +.new-conversation { margin-top: 16px; } +.chat-main { min-width: 0; } +.messages { width: min(100%, 1040px); padding: 32px 30px 22px; gap: 14px; } +.message { max-width: min(760px, 88%); padding: 14px 17px 15px; border-radius: 12px; } +.message-content { font-size: 15px; line-height: 1.55; } +.message-role { font-size: 10px; } +.message-time { font-size: 10px; } +.message small { font-size: 10px; } +.conversation-starters { width: min(100%, 1040px); padding: 0 30px 10px; } +.conversation-starters span { display: none; } +.composer { width: min(100%, 1040px); padding-inline: 30px; } +.composer-input-wrap { min-height: 64px; padding: 0 12px 36px; border-radius: 14px; } +.composer textarea { min-height: 56px; max-height: 180px; padding: 13px 0 8px; font-size: 15px; } +.composer-actions { right: 9px; bottom: 8px; left: 9px; height: 28px; } +.composer-tool { width: 29px; height: 29px; padding: 0; border: 0; color: var(--muted); background: transparent; } +.composer-tool svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; } +.composer-tool:hover, .composer-tool:focus-visible { border: 0; color: var(--blue); box-shadow: none; } +.composer-hint { font-size: 10px; } +.send-button { width: 33px; height: 33px; flex-basis: 33px; } +.composer-runtime { height: 0; min-height: 0; padding: 0; overflow: visible; } +.attachment-menu { bottom: 42px; left: 8px; } +.context-indicator { display: none; } +.technical-meta { display: none !important; } +.context-backdrop { position: fixed; z-index: 8; inset: 60px 0 0; border: 0; background: rgba(4,9,15,.58); } +.context-drawer { position: fixed; z-index: 9; top: 60px; right: 0; bottom: 0; width: min(360px, 92vw); padding: 24px 20px; border-left: 1px solid var(--line-strong); box-shadow: -20px 0 50px rgba(0,0,0,.28); transform: translateX(105%); transition: transform .2s ease; } +.context-drawer.drawer-open { transform: translateX(0); } +.drawer-heading { display: flex; align-items: start; justify-content: space-between; gap: 12px; } +.drawer-heading h2 { margin-top: 9px; font-size: 26px; } +.drawer-copy { margin: 12px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; } +.drawer-close { width: 30px; height: 30px; border: 1px solid var(--line); color: var(--muted); background: transparent; font-size: 20px; line-height: 1; } +.drawer-close:hover, .drawer-close:focus-visible { border-color: var(--blue); color: var(--blue); outline: none; } +.insight-grid { margin-top: 20px; } +@media (max-width: 780px) { + .chat-header { padding-inline: 10px; } + .header-focus { display: none; } + .chat-header .cognitive-status { max-width: 145px; } + .profile-button { width: 32px; overflow: hidden; padding: 0; font-size: 0; } + .profile-button::before { content: "●"; font-size: 12px; } + .chat-layout { display: block; } + .chat-main { height: calc(100vh - 58px); min-height: 520px; } + .chat-left { position: fixed; z-index: 10; top: 58px; bottom: 0; left: 0; width: min(290px, 84vw); transform: translateX(-105%); box-shadow: 18px 0 42px rgba(0,0,0,.3); } + .chat-layout.nav-open .chat-left { transform: translateX(0); opacity: 1; overflow-y: auto; } + .chat-layout.nav-collapsed .chat-left { padding-inline: 16px; } + .messages { padding: 22px 12px 16px; } + .message { max-width: 94%; } + .message-content { font-size: 14px; } + .conversation-starters { width: 100%; padding: 0 12px 8px; overflow-x: auto; flex-wrap: nowrap; } + .conversation-starters button { flex: 0 0 auto; } + .composer { width: 100%; padding-inline: 12px; } + .composer-input-wrap { padding-right: 10px; } + .composer-hint { display: none; } + .attachment-menu { bottom: 42px; left: 5px; } + .context-backdrop { inset: 58px 0 0; } + .context-drawer { top: 58px; } +} + /* Focused composer: one calm surface, with compact controls kept together. */ .active-panel { grid-template-rows: minmax(0, 1fr) auto auto auto; } .conversation-starters { width: min(100%, 900px); margin: 0 auto; padding: 0 22px 8px; gap: 6px; align-items: center; } diff --git a/cmd/creator-web/static/assets/chat.js b/cmd/creator-web/static/assets/chat.js index 8889d99..10f1f2d 100644 --- a/cmd/creator-web/static/assets/chat.js +++ b/cmd/creator-web/static/assets/chat.js @@ -11,6 +11,13 @@ const messageCount = document.querySelector("#message-count"); const logoutButton = document.querySelector("#logout"); const activeFocus = document.querySelector("#active-focus"); const rightFocus = document.querySelector("#right-focus"); +const headerFocus = document.querySelector("#header-focus"); +const navToggle = document.querySelector("#nav-toggle"); +const chatLayout = document.querySelector(".chat-layout"); +const contextToggle = document.querySelector("#context-toggle"); +const contextClose = document.querySelector("#context-close"); +const contextBackdrop = document.querySelector("#context-backdrop"); +const contextDrawer = document.querySelector("#context-drawer"); const newConversationButton = document.querySelector(".new-conversation"); const cognitiveStatus = document.querySelector("#cognitive-status"); const cognitiveConsoleLink = document.querySelector("#cognitive-console-link"); @@ -23,9 +30,7 @@ const aiDataForm = document.querySelector("#ai-data-form"); const aiDataInput = document.querySelector("#ai-data"); const aiDataError = document.querySelector("#ai-data-error"); const attachToggle = document.querySelector("#attach-toggle"); -const toolbarAttach = document.querySelector("#toolbar-attach"); const attachmentMenu = document.querySelector("#attachment-menu"); -const photoTrigger = document.querySelector("#photo-trigger"); const micTrigger = document.querySelector("#mic-trigger"); const sendIcon = document.querySelector("#send-icon"); const stopIcon = document.querySelector("#stop-icon"); @@ -188,6 +193,27 @@ function updateActiveFocus(value, provenance = "") { activeFocus.textContent = value ? `Сейчас обсуждаем: ${value}` : ""; activeFocus.hidden = !value; if (rightFocus) rightFocus.textContent = value || "не выбран"; + if (headerFocus) headerFocus.textContent = value || "Новый разговор"; +} + +function setContextOpen(open) { + if (!contextDrawer) return; + contextDrawer.classList.toggle("drawer-open", open); + contextDrawer.setAttribute("aria-hidden", String(!open)); + if (contextToggle) contextToggle.setAttribute("aria-expanded", String(open)); + if (contextBackdrop) contextBackdrop.hidden = !open; +} + +if (contextToggle) contextToggle.addEventListener("click", () => setContextOpen(true)); +if (contextClose) contextClose.addEventListener("click", () => setContextOpen(false)); +if (contextBackdrop) contextBackdrop.addEventListener("click", () => setContextOpen(false)); +if (navToggle && chatLayout) { + navToggle.addEventListener("click", () => { + const mobile = window.matchMedia("(max-width: 780px)").matches; + const open = mobile ? chatLayout.classList.toggle("nav-open") : !chatLayout.classList.toggle("nav-collapsed"); + navToggle.setAttribute("aria-expanded", String(open)); + navToggle.title = open ? "Скрыть навигацию" : "Показать навигацию"; + }); } if (newConversationButton) { @@ -197,6 +223,7 @@ if (newConversationButton) { sessionStorage.removeItem("domovoy.chat.conversation"); messages.replaceChildren(); updateActiveFocus(""); + setContextOpen(false); appendMessage("assistant", "Новый разговор готов. Чем займёмся?"); messageInput.focus(); }); @@ -457,14 +484,10 @@ function toggleAttachmentMenu(open) { if (!attachmentMenu) return; const next = typeof open === "boolean" ? open : attachmentMenu.hidden; attachmentMenu.hidden = !next; - for (const control of [attachToggle, toolbarAttach]) { - if (control) control.setAttribute("aria-expanded", String(next)); - } + if (attachToggle) attachToggle.setAttribute("aria-expanded", String(next)); } -for (const control of [attachToggle, toolbarAttach]) { - if (control) control.addEventListener("click", () => toggleAttachmentMenu()); -} +if (attachToggle) attachToggle.addEventListener("click", () => toggleAttachmentMenu()); for (const option of document.querySelectorAll("[data-input]")) { option.addEventListener("click", () => { const input = document.querySelector(`#${option.dataset.input}`); @@ -472,10 +495,9 @@ for (const option of document.querySelectorAll("[data-input]")) { if (input) input.click(); }); } -if (photoTrigger && imageInput) photoTrigger.addEventListener("click", () => imageInput.click()); if (micTrigger && audioInput) micTrigger.addEventListener("click", () => audioInput.click()); document.addEventListener("click", (event) => { - if (attachmentMenu && !attachmentMenu.hidden && !attachmentMenu.contains(event.target) && !attachToggle?.contains(event.target) && !toolbarAttach?.contains(event.target)) toggleAttachmentMenu(false); + if (attachmentMenu && !attachmentMenu.hidden && !attachmentMenu.contains(event.target) && !attachToggle?.contains(event.target)) toggleAttachmentMenu(false); }); if (sendButton) { sendButton.addEventListener("click", (event) => { diff --git a/cmd/creator-web/static/chat.html b/cmd/creator-web/static/chat.html index b8ace94..6485967 100644 --- a/cmd/creator-web/static/chat.html +++ b/cmd/creator-web/static/chat.html @@ -25,49 +25,38 @@ -