From afca89de8f374bb5d69e989ea4165d03f6ab4dd2 Mon Sep 17 00:00:00 2001 From: JakeBreath Date: Thu, 17 Sep 2026 10:32:51 -0500 Subject: [PATCH] Show SPA e621 request times in the status pill - New e621 metrics store tracks request count, cumulative time and the last request (path + duration) for the session - The e621 client measures each request around fetch and JSON parsing - Status pill appends a teal 'e621: ' segment with a tooltip showing request count and the last request; a server-side e621 time is shown separately if the backend ever reports one --- frontend/src/components/StatusPill.tsx | 26 +++++++++++++-- frontend/src/lib/e621.ts | 44 +++++++++++++++----------- frontend/src/store/e621Metrics.ts | 31 ++++++++++++++++++ 3 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 frontend/src/store/e621Metrics.ts diff --git a/frontend/src/components/StatusPill.tsx b/frontend/src/components/StatusPill.tsx index a3cb990..9d449be 100644 --- a/frontend/src/components/StatusPill.tsx +++ b/frontend/src/components/StatusPill.tsx @@ -2,6 +2,7 @@ import { Laptop, Monitor, Terminal, type LucideIcon } from "lucide-react"; import { cn } from "@/lib/cn"; import type { SystemStatus } from "@/lib/types"; +import { useE621Metrics } from "@/store/e621Metrics"; const osIcons: Record = { linux: Terminal, @@ -10,10 +11,16 @@ const osIcons: Record = { }; function formatMs(value: number): string { - return value >= 100 ? `${Math.round(value)}ms` : `${value.toFixed(1)}ms`; + if (value >= 1000) return `${(value / 1000).toFixed(1)}s`; + if (value >= 100) return `${Math.round(value)}ms`; + return `${value.toFixed(1)}ms`; } export function StatusPill({ status }: { status?: SystemStatus }) { + const requestCount = useE621Metrics((state) => state.requestCount); + const totalMs = useE621Metrics((state) => state.totalMs); + const last = useE621Metrics((state) => state.last); + if (!status) return null; const OsIcon = osIcons[status.os.icon] ?? Monitor; @@ -37,10 +44,25 @@ export function StatusPill({ status }: { status?: SystemStatus }) { {status.os.name} · {formatMs(status.server_time_ms)} + {requestCount > 0 ? ( + <> + · + + e621: {formatMs(totalMs)} + + + ) : null} {status.e621_time_ms !== null ? ( <> · - e621: {formatMs(status.e621_time_ms)} + + e621 (server): {formatMs(status.e621_time_ms)} + ) : null} diff --git a/frontend/src/lib/e621.ts b/frontend/src/lib/e621.ts index 4420a60..6a8f529 100644 --- a/frontend/src/lib/e621.ts +++ b/frontend/src/lib/e621.ts @@ -1,10 +1,11 @@ +import { useE621Metrics } from "@/store/e621Metrics"; + export interface E621Credentials { username: string; api_key: string; base_url: string; configured: boolean; } - export interface E621User { id: number; name: string; @@ -141,26 +142,33 @@ export function e621Request( ); } - const response = await fetch(url, { - method: options.method ?? "GET", - headers, - body: options.body, - signal: options.signal, - }); + const startedAt = performance.now(); + try { + const response = await fetch(url, { + method: options.method ?? "GET", + headers, + body: options.body, + signal: options.signal, + }); - if (!response.ok) { - let data: unknown = null; - try { - data = await response.json(); - } catch { - data = null; + if (!response.ok) { + let data: unknown = null; + try { + data = await response.json(); + } catch { + data = null; + } + throw new E621Error(response.status, data); } - throw new E621Error(response.status, data); + if (response.status === 204) { + return undefined as T; + } + return (await response.json()) as T; + } finally { + useE621Metrics + .getState() + .record(path, performance.now() - startedAt); } - if (response.status === 204) { - return undefined as T; - } - return (await response.json()) as T; }); } diff --git a/frontend/src/store/e621Metrics.ts b/frontend/src/store/e621Metrics.ts new file mode 100644 index 0000000..f429f3f --- /dev/null +++ b/frontend/src/store/e621Metrics.ts @@ -0,0 +1,31 @@ +import { create } from "zustand"; + +export interface E621RequestSample { + path: string; + ms: number; + at: number; +} + +interface E621MetricsState { + requestCount: number; + totalMs: number; + last: E621RequestSample | null; + record: (path: string, ms: number) => void; + reset: () => void; +} + +/** Rolling e621 request timings for the current SPA session. */ +export const useE621Metrics = create((set) => ({ + requestCount: 0, + totalMs: 0, + last: null, + + record: (path, ms) => + set((state) => ({ + requestCount: state.requestCount + 1, + totalMs: state.totalMs + ms, + last: { path, ms, at: Date.now() }, + })), + + reset: () => set({ requestCount: 0, totalMs: 0, last: null }), +}));