diff --git a/packages/tracker/meshnet_tracker/dashboard.html b/packages/tracker/meshnet_tracker/dashboard.html index 2b1685e..ffda954 100644 --- a/packages/tracker/meshnet_tracker/dashboard.html +++ b/packages/tracker/meshnet_tracker/dashboard.html @@ -8,14 +8,20 @@ :root { --bg:#0d1117; --panel:#161b22; --border:#30363d; --fg:#c9d1d9; --dim:#8b949e; --accent:#58a6ff; --ok:#3fb950; --bad:#f85149; --warn:#d29922; } * { box-sizing:border-box; } + html, body { height:100%; } body { margin:0; background:var(--bg); color:var(--fg); font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; } + body.chat-tab-active { overflow:hidden; height:100dvh; display:flex; flex-direction:column; } header { display:flex; align-items:baseline; gap:14px; padding:14px 20px; - border-bottom:1px solid var(--border); } + border-bottom:1px solid var(--border); flex-shrink:0; } header h1 { font-size:16px; margin:0; color:var(--accent); } header .meta { color:var(--dim); font-size:12px; } main { display:grid; grid-template-columns:repeat(auto-fit,minmax(340px,1fr)); gap:14px; padding:14px 20px; } + body.chat-tab-active main { + flex:1; min-height:0; display:flex; flex-direction:column; + padding:0; gap:0; overflow:hidden; + } section { background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:12px 14px; min-height:80px; } section h2 { margin:0 0 8px; font-size:12px; text-transform:uppercase; @@ -53,90 +59,116 @@ .tabs { display:flex; gap:10px; margin-bottom:8px; } .tabs a { color:var(--dim); cursor:pointer; } .tabs a.active { color:var(--accent); border-bottom:1px solid var(--accent); } - .dashboard-tabs { display:flex; gap:10px; padding:10px 20px 0; border-bottom:1px solid var(--border); } + .dashboard-tabs { display:flex; gap:10px; padding:10px 20px 0; border-bottom:1px solid var(--border); flex-shrink:0; } .dashboard-tabs button { border:0; border-bottom:1px solid transparent; border-radius:0; background:transparent; color:var(--dim); padding:5px 0 8px; } .dashboard-tabs button.active { color:var(--accent); border-bottom-color:var(--accent); } .wide { grid-column:1 / -1; } section[hidden] { display:none !important; } + section.chat-section { + padding:0; border:0; border-radius:0; background:var(--bg); min-height:0; + } + body.chat-tab-active section.chat-section { + flex:1; display:flex !important; flex-direction:column; min-height:0; + } .chat-app { display:grid; grid-template-columns:260px minmax(0, 1fr); gap:0; - min-height:560px; border:1px solid var(--border); border-radius:8px; - overflow:hidden; background:var(--bg); + flex:1; min-height:0; overflow:hidden; background:var(--bg); } .chat-sidebar { display:flex; flex-direction:column; min-height:0; border-right:1px solid var(--border); background:var(--panel); } - .chat-sidebar-header { - display:flex; flex-direction:column; gap:8px; padding:12px; - border-bottom:1px solid var(--border); + .chat-new-btn { + margin:12px; width:calc(100% - 24px); text-align:left; + border:1px solid var(--border); border-radius:8px; padding:10px 12px; + background:transparent; color:var(--fg); } - .chat-sidebar-header button { width:100%; text-align:left; } + .chat-new-btn:hover { background:#10151d; border-color:var(--accent); } .chat-session-list { - flex:1; overflow:auto; padding:8px; display:flex; flex-direction:column; gap:4px; + flex:1; overflow:auto; padding:0 8px 12px; display:flex; flex-direction:column; gap:2px; + } + .chat-session-list.empty-state { + justify-content:center; align-items:center; color:var(--dim); font-style:italic; + padding:24px 12px; } .chat-session-item { position:relative; display:block; width:100%; text-align:left; - padding:8px 28px 8px 10px; border:1px solid transparent; border-radius:6px; + padding:10px 32px 10px 12px; border:1px solid transparent; border-radius:8px; background:transparent; color:var(--fg); cursor:pointer; } - .chat-session-item:hover { background:#10151d; border-color:#21262d; } - .chat-session-item.active { - background:#10151d; border-color:var(--accent); - } + .chat-session-item:hover { background:#10151d; } + .chat-session-item.active { background:#10151d; border-color:#30363d; } .chat-session-title { - font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; + font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .chat-session-meta { - margin-top:2px; font-size:11px; color:var(--dim); + margin-top:3px; font-size:11px; color:var(--dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .chat-session-delete { - position:absolute; top:6px; right:4px; padding:0 5px; min-width:0; - border:0; background:transparent; color:var(--dim); line-height:1.2; + position:absolute; top:50%; right:6px; transform:translateY(-50%); + padding:2px 6px; min-width:0; border:0; border-radius:4px; + background:transparent; color:var(--dim); line-height:1.2; opacity:0; } - .chat-session-delete:hover { color:var(--bad); } - .chat-main { display:flex; flex-direction:column; min-height:560px; min-width:0; } - .chat-main-header { - display:flex; gap:12px; align-items:center; flex-wrap:wrap; - padding:10px 12px; border-bottom:1px solid var(--border); background:var(--panel); + .chat-session-item:hover .chat-session-delete, + .chat-session-item.active .chat-session-delete { opacity:1; } + .chat-session-delete:hover { color:var(--bad); background:#1a1012; } + .chat-main { display:flex; flex-direction:column; min-height:0; min-width:0; } + .chat-toolbar { + display:flex; gap:12px; align-items:center; flex-shrink:0; + padding:10px 16px; border-bottom:1px solid var(--border); background:var(--panel); } - .chat-main-header label { + .chat-toolbar label { display:flex; align-items:center; gap:8px; color:var(--dim); margin:0; } - .chat-main-header select { min-width:220px; } - .chat-main-title { flex:1; min-width:120px; font-size:12px; color:var(--fg); } + .chat-toolbar select { min-width:220px; max-width:min(420px, 50vw); } .chat-status { color:var(--dim); font-size:12px; margin-left:auto; } .chat-messages { - flex:1; overflow:auto; padding:16px; display:flex; flex-direction:column; gap:12px; - min-height:280px; + flex:1; overflow:auto; padding:24px 16px; min-height:0; } - .chat-messages.empty { - justify-content:center; align-items:center; color:var(--dim); font-style:italic; + .chat-messages-inner { + max-width:768px; margin:0 auto; display:flex; flex-direction:column; gap:20px; } - .chat-bubble-wrap { display:flex; flex-direction:column; gap:4px; max-width:88%; } - .chat-bubble-wrap.user { align-self:flex-end; align-items:flex-end; } - .chat-bubble-wrap.assistant, .chat-bubble-wrap.error { align-self:flex-start; align-items:flex-start; } - .chat-bubble-label { - font-size:10px; text-transform:uppercase; letter-spacing:.06em; color:var(--dim); + .chat-messages.empty .chat-messages-inner { + min-height:100%; justify-content:center; align-items:center; + color:var(--dim); font-size:14px; } + .chat-row { display:flex; width:100%; } + .chat-row.user { justify-content:flex-end; } + .chat-row.assistant, .chat-row.error { justify-content:flex-start; } .chat-bubble { - padding:10px 12px; border-radius:12px; line-height:1.45; - white-space:pre-wrap; word-break:break-word; + max-width:85%; padding:12px 14px; border-radius:16px; line-height:1.55; + white-space:pre-wrap; word-break:break-word; font-size:13px; + } + .chat-bubble.user { + background:#1f3a5f; border:1px solid #264a73; border-bottom-right-radius:4px; + } + .chat-bubble.assistant { + background:transparent; border:0; padding-left:0; padding-right:0; max-width:100%; + } + .chat-bubble.error { + background:#1a1012; border:1px solid #5c2020; color:#ffb4b4; border-bottom-left-radius:4px; + } + .chat-compose-wrap { + flex-shrink:0; padding:12px 16px 16px; border-top:1px solid var(--border); + background:var(--panel); } - .chat-bubble.user { background:#1f3a5f; border:1px solid #264a73; border-bottom-right-radius:4px; } - .chat-bubble.assistant { background:#10151d; border:1px solid #21262d; border-bottom-left-radius:4px; } - .chat-bubble.error { background:#1a1012; border:1px solid #5c2020; color:#ffb4b4; border-bottom-left-radius:4px; } .chat-compose { - display:flex; gap:8px; align-items:flex-end; padding:12px; - border-top:1px solid var(--border); background:var(--panel); + display:flex; gap:8px; align-items:flex-end; max-width:768px; margin:0 auto; + padding:10px 12px; border:1px solid var(--border); border-radius:16px; + background:var(--bg); } + .chat-compose:focus-within { border-color:var(--accent); } .chat-compose textarea { - flex:1; min-height:44px; max-height:160px; resize:none; width:auto; + flex:1; min-height:24px; max-height:200px; resize:none; width:auto; + border:0; background:transparent; padding:2px 0; outline:none; } - .chat-compose button { align-self:flex-end; min-width:72px; } - .chat-hint { padding:0 12px 10px; color:var(--dim); font-size:11px; } + .chat-compose button { + flex-shrink:0; min-width:36px; height:36px; padding:0; + border-radius:8px; border:1px solid var(--border); + } + .chat-compose button:disabled { opacity:.45; cursor:not-allowed; } .console { background:var(--bg); border:1px solid var(--border); border-radius:6px; min-height:160px; max-height:280px; overflow:auto; padding:7px 9px; @@ -174,29 +206,28 @@

Nodes & coverage

loading…

Model usage (RPM)

loading…

Call wall

loading...
-
-

Chat

+
-
+
-
New chat
select a model to start
-
Send a message to start this conversation.
-
- - +
+
Send a message to start this conversation.
+
+
+
+ + +
-
Enter to send · Shift+Enter for a new line · Sessions are saved in this browser
@@ -735,7 +766,13 @@ function persistActiveChatSession() { } saveChatSessionsStore(); renderChatSessionList(); - renderChatMainTitle(); +} + +function clearChatPrompt() { + const promptEl = $("chat-prompt"); + if (!promptEl) return; + promptEl.value = ""; + promptEl.style.height = "auto"; } function createNewChatSession() { @@ -751,21 +788,23 @@ function createNewChatSession() { chatSessions.unshift(session); activeChatSessionId = session.id; chatHistory = []; + clearChatPrompt(); saveChatSessionsStore(); renderChatSessionList(); renderChatHistory(); - renderChatMainTitle(); renderChatAuthHint(); const promptEl = $("chat-prompt"); if (promptEl) promptEl.focus(); } function selectChatSession(sessionId) { - if (chatBusy || sessionId === activeChatSessionId) return; + if (chatBusy) return; const session = chatSessions.find(item => item.id === sessionId); if (!session) return; + if (sessionId === activeChatSessionId) return; activeChatSessionId = session.id; chatHistory = (session.messages || []).slice(); + clearChatPrompt(); if (session.model) { selectedChatModel = session.model; localStorage.setItem("meshnet_chat_model", selectedChatModel); @@ -775,7 +814,6 @@ function selectChatSession(sessionId) { localStorage.setItem(CHAT_ACTIVE_SESSION_KEY, activeChatSessionId); renderChatSessionList(); renderChatHistory(); - renderChatMainTitle(); renderChatAuthHint(); } @@ -792,6 +830,7 @@ function deleteChatSession(sessionId, event) { if (chatSessions.length) { activeChatSessionId = chatSessions[0].id; chatHistory = (chatSessions[0].messages || []).slice(); + clearChatPrompt(); if (chatSessions[0].model) { selectedChatModel = chatSessions[0].model; localStorage.setItem("meshnet_chat_model", selectedChatModel); @@ -805,7 +844,6 @@ function deleteChatSession(sessionId, event) { saveChatSessionsStore(); renderChatSessionList(); renderChatHistory(); - renderChatMainTitle(); renderChatModels(); } @@ -827,37 +865,31 @@ function initChatSessions() { if (active.model) selectedChatModel = active.model; } renderChatSessionList(); - renderChatMainTitle(); renderChatHistory(); } -function renderChatMainTitle() { - const el = $("chat-main-title"); - if (!el) return; - const session = getActiveChatSession(); - el.textContent = session ? chatSessionTitle(session) : "New chat"; -} - function renderChatSessionList() { const list = $("chat-session-list"); if (!list) return; if (!chatSessions.length) { - list.classList.add("empty"); - list.innerHTML = "no chats yet"; + list.className = "chat-session-list empty-state"; + list.innerHTML = "No chats yet"; return; } - list.classList.remove("empty"); + list.className = "chat-session-list"; list.innerHTML = chatSessions.map(session => { const active = session.id === activeChatSessionId ? " active" : ""; const title = esc(chatSessionTitle(session)); - const model = esc(short(session.model || "no model", 18)); const when = esc(formatSessionTime(session.updatedAt || session.createdAt)); const id = JSON.stringify(session.id); - return ``; + (when ? `
${when}
` : "") + + `` + + ``; }).join(""); } @@ -865,11 +897,16 @@ function switchDashboardTab(name) { if (name === "admin" && !isAdmin) name = "overview"; if (name === "billing" && !isLoggedIn) name = "overview"; dashboardTab = name; + document.body.classList.toggle("chat-tab-active", name === "chat"); updateSectionVisibility(); for (const tabName of ["overview", "chat", "billing", "admin"]) { const button = $("tab-" + tabName); if (button) button.classList.toggle("active", tabName === dashboardTab); } + if (name === "chat") { + const promptEl = $("chat-prompt"); + if (promptEl) promptEl.focus(); + } } function updateSectionVisibility() { @@ -889,20 +926,16 @@ function renderChatHistory() { const history = $("chat-history"); if (!history) return; if (!chatHistory.length) { - history.classList.add("empty"); - history.innerHTML = "Send a message to start this conversation."; + history.className = "chat-messages empty"; + history.innerHTML = '
Send a message to start this conversation.
'; return; } - history.classList.remove("empty"); - history.innerHTML = chatHistory.map(msg => { + history.className = "chat-messages"; + const rows = chatHistory.map(msg => { const role = msg.role === "user" ? "user" : msg.role === "assistant" ? "assistant" : "error"; - const label = role === "user" ? "You" : role === "assistant" ? "Assistant" : "Error"; - const meta = msg.model ? ` · ${esc(short(msg.model, 24))}` : ""; - return `
` + - `
${label}${meta}
` + - `
${esc(msg.content)}
` + - `
`; + return `
${esc(msg.content)}
`; }).join(""); + history.innerHTML = `
${rows}
`; history.scrollTop = history.scrollHeight; } @@ -1195,6 +1228,7 @@ async function sendChat() { chatBusy = true; $("chat-send").disabled = true; promptEl.value = ""; + promptEl.style.height = "auto"; chatHistory.push({ role: "user", content: prompt, model: selectedChatModel }); renderChatHistory(); persistActiveChatSession(); @@ -1240,7 +1274,7 @@ function bindChatPromptShortcuts() { }); promptEl.addEventListener("input", () => { promptEl.style.height = "auto"; - promptEl.style.height = Math.min(promptEl.scrollHeight, 160) + "px"; + promptEl.style.height = Math.min(promptEl.scrollHeight, 200) + "px"; }); }