# Conflicts:
#	packages/tracker/meshnet_tracker/cli.py
#	packages/tracker/meshnet_tracker/dashboard.html
#	packages/tracker/meshnet_tracker/server.py
#	tests/test_dashboard.py
This commit is contained in:
Dobromir Popov
2026-07-08 16:14:24 +02:00
12 changed files with 6759 additions and 6249 deletions

View File

@@ -234,6 +234,7 @@
<section data-tab="overview" class="wide"><h2>Routing (learned)</h2><div id="routing" class="empty">loading…</div></section>
<section data-tab="overview" class="wide"><h2>Call wall</h2><div id="call-wall" class="empty">loading...</div></section>
<section data-tab="chat" class="wide chat-section">
<h2 style="display:none">Chat / inference</h2>
<div class="chat-app">
<aside class="chat-sidebar">
<button type="button" class="chat-new-btn" onclick="createNewChatSession()">+ New chat</button>
@@ -283,7 +284,7 @@ async function fetchJson(path) {
const headers = {};
const token = localStorage.getItem("meshnet_session");
if (token) headers["Authorization"] = "Bearer " + token;
const r = await fetch(path, { headers });
const r = await fetch(path, { headers, credentials: "same-origin" });
if (!r.ok) return null;
return await r.json();
} catch { return null; }
@@ -1109,6 +1110,7 @@ async function apiCall(path, method, body, bearerToken) {
const r = await fetch(path, {
method: method || "GET",
headers,
credentials: "same-origin",
body: body ? JSON.stringify(body) : undefined,
});
const data = await r.json().catch(() => ({}));
@@ -1368,10 +1370,11 @@ async function sendChat() {
promptEl.value = "";
promptEl.style.height = "auto";
chatHistory.push({ role: "user", content: prompt, model: selectedChatModel });
const assistantMessage = { role: "assistant", content: "", model: selectedChatModel, streaming: true };
chatHistory.push(assistantMessage);
renderChatHistory();
persistActiveChatSession();
renderChatStatus("sending request…");
const assistantMsg = { role: "assistant", content: "", model: selectedChatModel, streaming: true };
let tokens = 0;
let usage = null;
const started = Date.now();
@@ -1382,6 +1385,7 @@ async function sendChat() {
const resp = await fetch("/v1/chat/completions", {
method: "POST",
headers,
credentials: "same-origin",
body: JSON.stringify(body),
signal: chatAbortController.signal,
});
@@ -1396,12 +1400,10 @@ async function sendChat() {
const contentType = resp.headers.get("Content-Type") || "";
if (!resp.body || !contentType.includes("text/event-stream")) {
const data = await resp.json();
assistantMsg.content = (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) || "";
assistantMessage.content = (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) || "";
usage = data.usage || null;
tokens = (usage && usage.completion_tokens) || 0;
} else {
chatHistory.push(assistantMsg);
renderChatHistory();
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffered = "";
@@ -1425,30 +1427,31 @@ async function sendChat() {
const delta = chunk.choices && chunk.choices[0] && chunk.choices[0].delta;
const piece = delta && delta.content;
if (piece) {
assistantMsg.content += piece;
assistantMessage.content += piece;
tokens += 1;
updateStreamingChatBubble(assistantMsg.content);
updateStreamingChatBubble(assistantMessage.content);
const secs = Math.max((Date.now() - started) / 1000, 0.001);
renderChatStatus(`generating… ${tokens} tokens · ${(tokens / secs).toFixed(1)} tok/s`);
}
}
}
}
finalizeAssistantMessage(assistantMsg);
finalizeAssistantMessage(assistantMessage);
renderChatStatus(usage
? `done: ${usage.total_tokens ?? "?"} tokens`
: `done: ${tokens} tokens`);
} catch (err) {
if (err && err.name === "AbortError") {
if (assistantMsg.content) {
finalizeAssistantMessage(assistantMsg);
if (assistantMessage.content) {
finalizeAssistantMessage(assistantMessage);
renderChatStatus(`stopped after ${tokens} tokens`);
} else {
removeAssistantMessage(assistantMsg);
removeAssistantMessage(assistantMessage);
persistActiveChatSession();
renderChatStatus("stopped");
}
} else {
removeAssistantMessage(assistantMsg);
removeAssistantMessage(assistantMessage);
const error = (err && err.message) || "request failed";
chatHistory.push({ role: "error", content: error, model: selectedChatModel });
renderChatHistory();
@@ -1557,7 +1560,7 @@ renderChatModels();
renderChatHistory();
renderChatAuthHint();
setInterval(refresh, 4000);
setInterval(() => { if (sessionToken) renderAccountPanel(); }, 8000);
setInterval(() => { if (sessionToken || isLoggedIn) renderAccountPanel(); }, 8000);
</script>
</body>
</html>