fix(ui): reduce idle GPU usage for route status (#5767)

* fix(ui): reduce idle GPU usage for route status

* fix(ui): disable heartbeat for reduced motion

* fix(ui): preserve polling outside Tauri focus
This commit is contained in:
Allen Xu
2026-08-04 08:34:19 +08:00
committed by GitHub
parent 492245dcb9
commit 968794e30e
7 changed files with 97 additions and 4 deletions
@@ -84,7 +84,7 @@ export function ClaudeDesktopRouteToggle({
className={cn(
"h-4 w-4 transition-colors",
isRunning
? "text-emerald-500 animate-pulse"
? "text-emerald-500 status-heartbeat"
: "text-muted-foreground",
)}
/>
+1 -1
View File
@@ -72,7 +72,7 @@ export function FailoverToggle({ className, activeApp }: FailoverToggleProps) {
className={cn(
"h-4 w-4 transition-colors",
isEnabled
? "text-emerald-500 animate-pulse"
? "text-emerald-500 status-heartbeat"
: "text-muted-foreground",
)}
/>
+1 -1
View File
@@ -75,7 +75,7 @@ export function ProxyToggle({ className, activeApp }: ProxyToggleProps) {
className={cn(
"h-4 w-4 transition-colors",
takeoverEnabled
? "text-emerald-500 animate-pulse"
? "text-emerald-500 status-heartbeat"
: "text-muted-foreground",
)}
/>
+1 -1
View File
@@ -118,7 +118,7 @@ export function ProxyTabContent({
className="gap-1.5 h-6 ml-auto mr-2"
>
<Activity
className={`h-3 w-3 ${isRunning ? "animate-pulse" : ""}`}
className={`h-3 w-3 ${isRunning ? "status-heartbeat" : ""}`}
/>
{isRunning
? t("settings.advanced.proxy.running")
+29
View File
@@ -145,6 +145,35 @@ html.dark {
color-scheme: dark;
}
.status-heartbeat {
opacity: 1;
transition-property: color, opacity;
transition-duration: 150ms, 300ms;
transition-timing-function: ease, ease-out;
}
html[data-window-active="true"][data-status-heartbeat="true"]
.status-heartbeat {
opacity: 0.5;
}
html[data-window-active="false"] .status-heartbeat {
opacity: 1;
transition-duration: 0s;
}
@media (prefers-reduced-motion: reduce) {
.status-heartbeat {
opacity: 1;
transition-property: color;
}
html[data-window-active="true"][data-status-heartbeat="true"]
.status-heartbeat {
opacity: 1;
}
}
::-webkit-scrollbar {
display: none;
}
+61
View File
@@ -0,0 +1,61 @@
import { isTauri } from "@tauri-apps/api/core";
import { getCurrentWindow } from "@tauri-apps/api/window";
const HEARTBEAT_INTERVAL_MS = 3000;
const HEARTBEAT_DIM_MS = 300;
let initialized = false;
let heartbeatInterval: number | undefined;
let heartbeatReset: number | undefined;
function stopHeartbeat() {
if (heartbeatInterval !== undefined) {
window.clearInterval(heartbeatInterval);
heartbeatInterval = undefined;
}
if (heartbeatReset !== undefined) {
window.clearTimeout(heartbeatReset);
heartbeatReset = undefined;
}
delete document.documentElement.dataset.statusHeartbeat;
}
function startHeartbeat() {
stopHeartbeat();
heartbeatInterval = window.setInterval(() => {
document.documentElement.dataset.statusHeartbeat = "true";
heartbeatReset = window.setTimeout(() => {
delete document.documentElement.dataset.statusHeartbeat;
heartbeatReset = undefined;
}, HEARTBEAT_DIM_MS);
}, HEARTBEAT_INTERVAL_MS);
}
function setWindowActive(active: boolean) {
document.documentElement.dataset.windowActive = String(active);
if (active) {
startHeartbeat();
} else {
stopHeartbeat();
}
}
export function initializeWindowActivity() {
if (initialized) return;
initialized = true;
setWindowActive(document.hasFocus());
// Browser focus events are a fallback for non-Tauri renderer tests and dev mode.
window.addEventListener("focus", () => setWindowActive(true));
window.addEventListener("blur", () => setWindowActive(false));
if (isTauri()) {
void getCurrentWindow()
.onFocusChanged(({ payload }) => setWindowActive(payload))
.catch((error) => {
console.error("Failed to observe window focus changes", error);
});
}
}
+3
View File
@@ -23,6 +23,7 @@ import {
MODELS_DEV_SYNC_CONFIG_QUERY_KEY,
syncModelsDevPricingOnStartup,
} from "./lib/modelsDevAutoSync";
import { initializeWindowActivity } from "@/lib/windowActivity";
installGlobalErrorHandlers();
@@ -114,6 +115,8 @@ async function bootstrap() {
reportFrontendError("get_init_error", e);
}
initializeWindowActivity();
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<FrontendErrorBoundary>