From ea1e876355a6f94193640ae3564746daaf08f9ce Mon Sep 17 00:00:00 2001 From: gilnett Date: Thu, 8 Oct 2026 18:17:36 +0200 Subject: [PATCH 1/6] feat(desktop): implement upload speed test and health check for instant mode --- .../src/components/NetworkHealthIndicator.tsx | 133 ++++++++ .../routes/(window-chrome)/new-main/index.tsx | 19 ++ apps/desktop/src/utils/network-health.test.ts | 75 +++++ apps/desktop/src/utils/network-health.ts | 306 ++++++++++++++++++ apps/web/app/api/desktop/[...route]/health.ts | 66 ++++ apps/web/app/api/desktop/[...route]/route.ts | 2 + 6 files changed, 601 insertions(+) create mode 100644 apps/desktop/src/components/NetworkHealthIndicator.tsx create mode 100644 apps/desktop/src/utils/network-health.test.ts create mode 100644 apps/desktop/src/utils/network-health.ts create mode 100644 apps/web/app/api/desktop/[...route]/health.ts diff --git a/apps/desktop/src/components/NetworkHealthIndicator.tsx b/apps/desktop/src/components/NetworkHealthIndicator.tsx new file mode 100644 index 00000000000..d951935dc13 --- /dev/null +++ b/apps/desktop/src/components/NetworkHealthIndicator.tsx @@ -0,0 +1,133 @@ +import { cx } from "cva"; +import { Show } from "solid-js"; +import Tooltip from "~/components/Tooltip"; +import { + isRecordingInProgress, + networkHealth, + runSpeedTest, + runUploadHealthCheck, + speedTest, +} from "~/utils/network-health"; +import IconLucideAlertTriangle from "~icons/lucide/alert-triangle"; +import IconLucideRefreshCw from "~icons/lucide/refresh-cw"; +import IconLucideWifi from "~icons/lucide/wifi"; + +interface NetworkHealthIndicatorProps { + class?: string; +} + +export default function NetworkHealthIndicator( + props: NetworkHealthIndicatorProps, +) { + const isUnhealthy = () => networkHealth().status === "unhealthy"; + const isChecking = () => + networkHealth().status === "checking" || speedTest().status === "running"; + const isRecording = () => isRecordingInProgress(); + + const handleRefresh = async (e: MouseEvent) => { + e.stopPropagation(); + if (isRecording() || isChecking()) return; + await runUploadHealthCheck(); + await runSpeedTest(); + }; + + const speedLabel = () => { + const mbps = speedTest().speedMbps; + if (mbps === null) return "Checking..."; + return `${mbps} Mbps`; + }; + + const qualityLabel = () => { + const tier = speedTest().qualityTier; + const res = speedTest().recommendedResolution; + if (!tier) return "Standard (1080p)"; + if (tier === "high") return `Full (${res}p)`; + if (tier === "medium") return `Standard (${res}p)`; + return `Adapted (${res}p)`; + }; + + const dotColor = () => { + if (isUnhealthy()) return "bg-red-9"; + if (speedTest().status === "running") return "bg-blue-9 animate-pulse"; + const speed = speedTest().speedMbps; + if (speed === null) return "bg-gray-8"; + if (speed >= 15) return "bg-green-9"; + if (speed >= 5) return "bg-blue-9"; + return "bg-amber-9"; + }; + + const tooltipContent = () => ( +
+
+ Network Health + + {isUnhealthy() ? "Failed" : "Operational"} + +
+
+ Upload Speed: + {speedLabel()} +
+
+ Recording Quality: + {qualityLabel()} +
+ +
+ Upload test failed. Recording uploads might fail. Please contact + support at hello@cap.so +
+
+
+ +
+
+ ); + + return ( +
+ + + +
+ ); +} diff --git a/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx b/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx index b0981c60573..178feadad43 100644 --- a/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx +++ b/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx @@ -37,6 +37,7 @@ import { createStore, produce, reconcile } from "solid-js/store"; import toast from "solid-toast"; import { Transition } from "solid-transition-group"; import Mode from "~/components/Mode"; +import NetworkHealthIndicator from "~/components/NetworkHealthIndicator"; import { RecoveryToast } from "~/components/RecoveryToast"; import Tooltip from "~/components/Tooltip"; import { Input } from "~/routes/editor/ui"; @@ -59,6 +60,10 @@ import { importVideoFromPicker, showImportError, } from "~/utils/importMedia"; +import { + initNetworkHealthMonitoring, + setRecordingState, +} from "~/utils/network-health"; import { createCameraMutation, createCleanCaptureQuery, @@ -1846,6 +1851,16 @@ function Page() { const isRecording = () => !!currentRecording.data; const isActivelyRecording = () => currentRecording.data?.status === "recording"; + + createEffect(() => { + setRecordingState(isRecording()); + }); + + onMount(() => { + const cleanup = initNetworkHealthMonitoring(); + onCleanup(cleanup); + }); + const auth = authStore.createQuery(); const recordingSettingsQuery = recordingDeviceSettingsStore.createQuery(); const generalSettings = generalSettingsStore.createQuery(); @@ -2984,6 +2999,9 @@ function Page() { +
+ +
); @@ -3153,6 +3171,7 @@ function Page() { ? new URL("/dashboard", serverUrl()).toString() : serverUrl() } + rel="noopener" >