03f1673fce
- Workspace, SettingsHub, SessionDetail and the Workflow sub-tab are lazy (main bundle 2,166 kB -> ~920 kB), wrapped in RouteErrorBoundary: a stale chunk after a self-update reloads once (vite:preloadError too), any other error shows a Reload panel instead of a blank page. - Live refresh: lib/throttle.ts + hooks/useLiveRefresh.ts replace trailing debounces that froze under a steady event stream; refresh skips hidden tabs and refetches after a WebSocket reconnect. Monitor loads only while shown, with one batched GET /api/agents?session_ids= instead of N+1; a failing endpoint is an inline error, not a blank Dashboard. - NeedsYouStrip on Monitor and above Analytics. Tabs, filters, page, sort and search live in the URL (Dashboard, Sessions, Activity, SessionDetail). - Sessions: tool / workspace / date-range filters, CSV/JSON export (formula-neutralised), focusable row links, Open in workspace (the dead /run links are gone), stale responses dropped, error + Retry. - Fixes: "NaNs" durations, 30/7-day charts skipping idle days, weekday labels west of UTC (now averages), cache hit rate denominator, billable tokens vs cache reads, exact tool-call counts, "To" date = end of day, invalid ?from= no longer throws, recent list no longer shrinks to 15, stable message keys, transcript switch races. - Light-theme-unreadable bg-black code panels and dark-only DAG fills use tokens; status configs and chart colours use theme tokens; Health tab and other literals translated. - Server (additive): GET /api/agents?session_ids=, GET /api/sessions awaiting=1&from=&to=, stats tool_call_count/distinct_tool_count, event rows carry session/agent labels.
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
/**
|
|
* @file useLiveRefresh.ts
|
|
* @description Keeps a page's data fresh without hammering the server: re-runs
|
|
* `load` on matching WebSocket messages through a leading+trailing throttle (a
|
|
* steady event stream can never freeze or flood the page), on a poll that skips
|
|
* hidden tabs, when the tab becomes visible again, and after the socket
|
|
* reconnects (messages sent while it was down are lost). The caller still does
|
|
* its own initial load.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { eventBus } from "../lib/eventBus";
|
|
import { throttle } from "../lib/throttle";
|
|
import type { WSMessage } from "../lib/types";
|
|
|
|
export interface LiveRefreshOptions {
|
|
/** Which WebSocket messages warrant a reload; none when omitted. */
|
|
match?: (msg: WSMessage) => boolean;
|
|
/** Poll period while the tab is visible; no poll when omitted. */
|
|
intervalMs?: number;
|
|
/** Minimum gap between event-driven reloads (default 3 s). */
|
|
throttleMs?: number;
|
|
/** Off = no subscription, no poll (e.g. an inactive tab). Default true. */
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export function useLiveRefresh(load: () => void, opts: LiveRefreshOptions = {}): void {
|
|
const { intervalMs, throttleMs = 3000, enabled = true } = opts;
|
|
const loadRef = useRef(load);
|
|
const matchRef = useRef(opts.match);
|
|
loadRef.current = load;
|
|
matchRef.current = opts.match;
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
const live = throttle(() => loadRef.current(), throttleMs);
|
|
const visible = () => document.visibilityState === "visible";
|
|
const offMsg = eventBus.subscribe((msg) => {
|
|
if (matchRef.current?.(msg)) live();
|
|
});
|
|
let wasDown = !eventBus.connected;
|
|
// (Guarded: test doubles of the bus often stub `subscribe` only.)
|
|
const offConn =
|
|
typeof eventBus.onConnection === "function"
|
|
? eventBus.onConnection((up) => {
|
|
if (!up) wasDown = true;
|
|
else if (wasDown) {
|
|
wasDown = false;
|
|
live();
|
|
}
|
|
})
|
|
: () => undefined;
|
|
const onVis = () => {
|
|
if (visible()) live();
|
|
};
|
|
document.addEventListener("visibilitychange", onVis);
|
|
const timer = intervalMs
|
|
? setInterval(() => {
|
|
if (visible()) loadRef.current();
|
|
}, intervalMs)
|
|
: undefined;
|
|
return () => {
|
|
live.cancel();
|
|
offMsg();
|
|
offConn();
|
|
document.removeEventListener("visibilitychange", onVis);
|
|
clearInterval(timer);
|
|
};
|
|
}, [enabled, intervalMs, throttleMs]);
|
|
}
|