fix(webui): activity spine effect loop and chat client socket race

This commit is contained in:
xiaoxixi 2026-07-24 09:47:52 +08:00
parent 79821e11d5
commit 8dc5889b87
2 changed files with 8 additions and 3 deletions

View File

@ -11,22 +11,27 @@ class ChatClient {
connect() { connect() {
if (this.#socket) return; if (this.#socket) return;
this.#stopped = false; this.#stopped = false;
clearTimeout(this.#reconnectTimer);
const scheme = location.protocol === "https:" ? "wss" : "ws"; const scheme = location.protocol === "https:" ? "wss" : "ws";
const ws = new WebSocket(`${scheme}://${location.host}/ws?client_id=${encodeURIComponent(clientId())}`); const ws = new WebSocket(`${scheme}://${location.host}/ws?client_id=${encodeURIComponent(clientId())}`);
this.#socket = ws; this.#socket = ws;
ws.onopen = () => { ws.onopen = () => {
if (this.#socket !== ws) return;
this.connected = true; this.connected = true;
this.#dispatch({ type: "_open" }); this.#dispatch({ type: "_open" });
}; };
ws.onerror = () => ws.close(); ws.onerror = () => ws.close();
ws.onclose = () => { ws.onclose = () => {
if (this.#socket !== ws) return;
this.connected = false; this.connected = false;
this.#socket = null; this.#socket = null;
this.#dispatch({ type: "_close" }); this.#dispatch({ type: "_close" });
if (!this.#stopped) this.#reconnectTimer = setTimeout(() => this.connect(), 1800); if (!this.#stopped) this.#reconnectTimer = setTimeout(() => this.connect(), 1800);
}; };
ws.onmessage = (event) => { ws.onmessage = (event) => {
const frame = JSON.parse(event.data); if (this.#socket !== ws) return;
let frame;
try { frame = JSON.parse(event.data); } catch { return; }
if (frame.type === "turn_updated" && frame.snapshot) this.turn = frame.snapshot; if (frame.type === "turn_updated" && frame.snapshot) this.turn = frame.snapshot;
this.#dispatch(frame); this.#dispatch(frame);
}; };

View File

@ -2,12 +2,12 @@
import { chat } from "../chat.svelte.js"; import { chat } from "../chat.svelte.js";
let { version = "" } = $props(); let { version = "" } = $props();
let lastTokens = $state(null); // { at, completion } let lastTokens = null; // { at, completion } — plain bookkeeping, NOT reactive
let rate = $state(null); let rate = $state(null);
$effect(() => { $effect(() => {
const turn = chat.turn; const turn = chat.turn;
if (!turn || turn.status !== "running") { rate = null; return; } if (!turn || turn.status !== "running") { rate = null; lastTokens = null; return; }
const completion = turn.usage?.completion_tokens; const completion = turn.usage?.completion_tokens;
const now = Date.now(); const now = Date.now();
if (completion != null && lastTokens && now > lastTokens.at) { if (completion != null && lastTokens && now > lastTokens.at) {