From 2f7716cd751f88411c24e42e3529702207072e11 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Fri, 20 Mar 2026 18:38:10 +0000 Subject: [PATCH] fix(ui): reduce SSE reconnect churn and polling overhead on navigation --- admin/config/transmit.ts | 2 +- admin/inertia/app/app.tsx | 2 +- admin/inertia/hooks/useDownloads.ts | 6 +++++- admin/inertia/hooks/useEmbedJobs.ts | 6 +++++- admin/inertia/hooks/useOllamaModelDownloads.ts | 10 ++++++++-- admin/inertia/pages/settings/benchmark.tsx | 9 ++++++--- 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/admin/config/transmit.ts b/admin/config/transmit.ts index f8862d7..43f1d42 100644 --- a/admin/config/transmit.ts +++ b/admin/config/transmit.ts @@ -3,7 +3,7 @@ import { defineConfig } from '@adonisjs/transmit' import { redis } from '@adonisjs/transmit/transports' export default defineConfig({ - pingInterval: false, + pingInterval: '30s', transport: { driver: redis({ host: env.get('REDIS_HOST'), diff --git a/admin/inertia/app/app.tsx b/admin/inertia/app/app.tsx index 6026347..b71ab64 100644 --- a/admin/inertia/app/app.tsx +++ b/admin/inertia/app/app.tsx @@ -40,7 +40,7 @@ createInertiaApp({ createRoot(el).render( - + diff --git a/admin/inertia/hooks/useDownloads.ts b/admin/inertia/hooks/useDownloads.ts index 6d9b33b..3cdb859 100644 --- a/admin/inertia/hooks/useDownloads.ts +++ b/admin/inertia/hooks/useDownloads.ts @@ -17,7 +17,11 @@ const useDownloads = (props: useDownloadsProps) => { const queryData = useQuery({ queryKey: queryKey, queryFn: () => api.listDownloadJobs(props.filetype), - refetchInterval: 2000, // Refetch every 2 seconds to get updated progress + refetchInterval: (query) => { + const data = query.state.data + // Only poll when there are active downloads; otherwise use a slower interval + return data && data.length > 0 ? 2000 : 30000 + }, enabled: props.enabled ?? true, }) diff --git a/admin/inertia/hooks/useEmbedJobs.ts b/admin/inertia/hooks/useEmbedJobs.ts index c5a577a..b4e1e0c 100644 --- a/admin/inertia/hooks/useEmbedJobs.ts +++ b/admin/inertia/hooks/useEmbedJobs.ts @@ -7,7 +7,11 @@ const useEmbedJobs = (props: { enabled?: boolean } = {}) => { const queryData = useQuery({ queryKey: ['embed-jobs'], queryFn: () => api.getActiveEmbedJobs().then((data) => data ?? []), - refetchInterval: 2000, + refetchInterval: (query) => { + const data = query.state.data + // Only poll when there are active jobs; otherwise use a slower interval + return data && data.length > 0 ? 2000 : 30000 + }, enabled: props.enabled ?? true, }) diff --git a/admin/inertia/hooks/useOllamaModelDownloads.ts b/admin/inertia/hooks/useOllamaModelDownloads.ts index 701b809..4417321 100644 --- a/admin/inertia/hooks/useOllamaModelDownloads.ts +++ b/admin/inertia/hooks/useOllamaModelDownloads.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useTransmit } from 'react-adonis-transmit' export type OllamaModelDownload = { @@ -10,6 +10,7 @@ export type OllamaModelDownload = { export default function useOllamaModelDownloads() { const { subscribe } = useTransmit() const [downloads, setDownloads] = useState>(new Map()) + const timeoutsRef = useRef>>(new Set()) useEffect(() => { const unsubscribe = subscribe('ollama-model-download', (data: OllamaModelDownload) => { @@ -19,13 +20,15 @@ export default function useOllamaModelDownloads() { if (data.percent >= 100) { // If download is complete, keep it for a short time before removing to allow UI to show 100% progress updated.set(data.model, data) - setTimeout(() => { + const timeout = setTimeout(() => { + timeoutsRef.current.delete(timeout) setDownloads((current) => { const next = new Map(current) next.delete(data.model) return next }) }, 2000) + timeoutsRef.current.add(timeout) } else { updated.set(data.model, data) } @@ -36,7 +39,10 @@ export default function useOllamaModelDownloads() { return () => { unsubscribe() + timeoutsRef.current.forEach(clearTimeout) + timeoutsRef.current.clear() } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [subscribe]) const downloadsArray = Array.from(downloads.values()) diff --git a/admin/inertia/pages/settings/benchmark.tsx b/admin/inertia/pages/settings/benchmark.tsx index 69a4791..23aed6d 100644 --- a/admin/inertia/pages/settings/benchmark.tsx +++ b/admin/inertia/pages/settings/benchmark.tsx @@ -1,5 +1,5 @@ import { Head, Link, usePage } from '@inertiajs/react' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import SettingsLayout from '~/layouts/SettingsLayout' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import CircularGauge from '~/components/systeminfo/CircularGauge' @@ -40,6 +40,7 @@ export default function BenchmarkPage(props: { const aiInstalled = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA) const [progress, setProgress] = useState(null) const [isRunning, setIsRunning] = useState(props.benchmark.status !== 'idle') + const refetchLatestRef = useRef(refetchLatest) const [showDetails, setShowDetails] = useState(false) const [showHistory, setShowHistory] = useState(false) const [showAIRequiredAlert, setShowAIRequiredAlert] = useState(false) @@ -60,6 +61,7 @@ export default function BenchmarkPage(props: { }, initialData: props.benchmark.latestResult, }) + refetchLatestRef.current = refetchLatest // Fetch all benchmark results for history const { data: benchmarkHistory } = useQuery({ @@ -306,14 +308,15 @@ export default function BenchmarkPage(props: { setProgress(data) if (data.status === 'completed' || data.status === 'error') { setIsRunning(false) - refetchLatest() + refetchLatestRef.current() } }) return () => { unsubscribe() } - }, [subscribe, refetchLatest]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [subscribe]) const formatBytes = (bytes: number) => { const gb = bytes / (1024 * 1024 * 1024)