From 88560c71194b66093410cd5189a84d1224b16b2a Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Fri, 23 Jan 2026 13:46:47 -0500 Subject: [PATCH] fix(ui): sync backend state across all CLIProxy UI components - Add useUpdateBackend mutation hook that invalidates related queries - Reduce update-check stale time from 1hr to 5min + enable refetchOnWindowFocus - Settings proxy page now uses mutation hook for proper query invalidation - Make all CLIProxy labels dynamic based on backendLabel from API: - cliproxy-header.tsx: page title - app-sidebar.tsx: nav item label - cliproxy-stats-overview.tsx: both offline and running descriptions - model-preferences-grid.tsx: card description - settings proxy section: descriptions for local/remote modes - Fix API route to use DEFAULT_BACKEND constant instead of hardcoded 'plus' Ensures backend switching in Settings immediately reflects in all CLIProxy pages. --- src/web-server/routes/proxy-routes.ts | 5 +- .../components/cliproxy/cliproxy-header.tsx | 6 ++- .../cliproxy/cliproxy-stats-overview.tsx | 9 +++- .../overview/model-preferences-grid.tsx | 6 ++- ui/src/components/layout/app-sidebar.tsx | 21 ++++++-- ui/src/hooks/use-cliproxy.ts | 37 ++++++++++++-- .../pages/settings/sections/proxy/index.tsx | 49 +++++++++---------- 7 files changed, 93 insertions(+), 40 deletions(-) diff --git a/src/web-server/routes/proxy-routes.ts b/src/web-server/routes/proxy-routes.ts index 636fe5d8..b4cc3f68 100644 --- a/src/web-server/routes/proxy-routes.ts +++ b/src/web-server/routes/proxy-routes.ts @@ -11,6 +11,7 @@ import { Router, Request, Response } from 'express'; import { loadOrCreateUnifiedConfig, saveUnifiedConfig } from '../../config/unified-config-loader'; import { testConnection } from '../../cliproxy/remote-proxy-client'; import { isProxyRunning } from '../../cliproxy/services/proxy-lifecycle-service'; +import { DEFAULT_BACKEND } from '../../cliproxy/platform-detector'; import { DEFAULT_CLIPROXY_SERVER_CONFIG, CliproxyServerConfig, @@ -73,7 +74,7 @@ router.put('/', async (req: Request, res: Response) => { router.get('/backend', async (_req: Request, res: Response) => { try { const config = await loadOrCreateUnifiedConfig(); - res.json({ backend: config.cliproxy?.backend ?? 'plus' }); + res.json({ backend: config.cliproxy?.backend ?? DEFAULT_BACKEND }); } catch (error) { console.error('[cliproxy-server-routes] Failed to load backend config:', error); res.status(500).json({ error: 'Failed to load backend config' }); @@ -99,7 +100,7 @@ router.put('/backend', async (req: Request, res: Response) => { // Check if proxy is running - warn about restart requirement const config = await loadOrCreateUnifiedConfig(); - const currentBackend = config.cliproxy?.backend ?? 'plus'; + const currentBackend = config.cliproxy?.backend ?? DEFAULT_BACKEND; if (currentBackend !== backend && isProxyRunning() && !force) { res.status(409).json({ error: 'Proxy is running. Stop proxy first or use force=true to change backend.', diff --git a/ui/src/components/cliproxy/cliproxy-header.tsx b/ui/src/components/cliproxy/cliproxy-header.tsx index 956d37ca..6b5bb51a 100644 --- a/ui/src/components/cliproxy/cliproxy-header.tsx +++ b/ui/src/components/cliproxy/cliproxy-header.tsx @@ -15,6 +15,7 @@ interface VersionInfo { currentVersion: string; isStable: boolean; stabilityMessage?: string; + backendLabel?: string; } interface LoginButtonProps { @@ -127,6 +128,7 @@ export function CliproxyHeader({ currentVersion: data.currentVersion, isStable: data.isStable, stabilityMessage: data.stabilityMessage, + backendLabel: data.backendLabel, }); } }) @@ -157,7 +159,9 @@ export function CliproxyHeader({ {/* Top row: Title and Login Buttons */}
-

CLIProxy Plus

+

+ {versionInfo?.backendLabel ?? 'CLIProxy'} +

CCS-level account management

diff --git a/ui/src/components/cliproxy/cliproxy-stats-overview.tsx b/ui/src/components/cliproxy/cliproxy-stats-overview.tsx index e3f114d2..e4668e80 100644 --- a/ui/src/components/cliproxy/cliproxy-stats-overview.tsx +++ b/ui/src/components/cliproxy/cliproxy-stats-overview.tsx @@ -27,6 +27,7 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useCliproxyStats, useCliproxyStatus } from '@/hooks/use-cliproxy-stats'; +import { useCliproxyUpdateCheck } from '@/hooks/use-cliproxy'; import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; interface CliproxyStatsOverviewProps { @@ -37,6 +38,8 @@ export function CliproxyStatsOverview({ className }: CliproxyStatsOverviewProps) const { privacyMode } = usePrivacy(); const { data: status, isLoading: statusLoading } = useCliproxyStatus(); const { data: stats, isLoading: statsLoading, error } = useCliproxyStats(status?.running); + const { data: updateCheck } = useCliproxyUpdateCheck(); + const backendLabel = updateCheck?.backendLabel ?? 'CLIProxy'; const isLoading = statusLoading || (status?.running && statsLoading); @@ -71,7 +74,7 @@ export function CliproxyStatsOverview({ className }: CliproxyStatsOverviewProps) Session Statistics

- Real-time usage metrics from CLIProxy Plus + Real-time usage metrics from {backendLabel}

@@ -146,7 +149,9 @@ export function CliproxyStatsOverview({ className }: CliproxyStatsOverviewProps) Session Statistics -

Real-time usage metrics from CLIProxyAPI

+

+ Real-time usage metrics from {backendLabel} +

{ @@ -102,7 +104,7 @@ export function ModelPreferencesGrid() { - Models available through CLIProxy Plus, grouped by provider + Models available through {backendLabel}, grouped by provider diff --git a/ui/src/components/layout/app-sidebar.tsx b/ui/src/components/layout/app-sidebar.tsx index dc5da3b4..a2cf8040 100644 --- a/ui/src/components/layout/app-sidebar.tsx +++ b/ui/src/components/layout/app-sidebar.tsx @@ -30,6 +30,7 @@ import { } from '@/components/ui/sidebar'; import { CcsLogo } from '@/components/shared/ccs-logo'; import { useSidebar } from '@/hooks/use-sidebar'; +import { useCliproxyUpdateCheck } from '@/hooks/use-cliproxy'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; @@ -87,6 +88,18 @@ export function AppSidebar() { const location = useLocation(); const navigate = useNavigate(); const { state } = useSidebar(); + const { data: updateCheck } = useCliproxyUpdateCheck(); + + // Dynamic label for CLIProxy based on backend + const cliproxyLabel = updateCheck?.backendLabel ?? 'CLIProxy'; + + // Helper to get dynamic label (for CLIProxy route) + const getItemLabel = (item: { path: string; label: string }) => { + if (item.path === '/cliproxy') { + return cliproxyLabel; + } + return item.label; + }; // Helper to check if a route is active (exact match) const isRouteActive = (path: string) => location.pathname === path; @@ -122,13 +135,13 @@ export function AppSidebar() { {/* Click navigates to overview AND opens submenu */} navigate(item.path)} > {item.icon && } - {item.label} + {getItemLabel(item)} @@ -155,12 +168,12 @@ export function AppSidebar() { {item.icon && } - {item.label} + {getItemLabel(item)} {item.badge && ( diff --git a/ui/src/hooks/use-cliproxy.ts b/ui/src/hooks/use-cliproxy.ts index 3d310f02..dce70f76 100644 --- a/ui/src/hooks/use-cliproxy.ts +++ b/ui/src/hooks/use-cliproxy.ts @@ -333,9 +333,40 @@ export function useCliproxyUpdateCheck() { return useQuery({ queryKey: ['cliproxy-update-check'], queryFn: () => api.cliproxy.updateCheck(), - staleTime: 60 * 60 * 1000, // 1 hour (matches backend cache) - refetchInterval: 60 * 60 * 1000, // Refresh every hour - refetchOnWindowFocus: false, // Don't refresh on window focus (save API calls) + staleTime: 5 * 60 * 1000, // 5 minutes (reduced from 1 hour for faster backend switch response) + refetchInterval: 5 * 60 * 1000, // Refresh every 5 minutes + refetchOnWindowFocus: true, // Refetch on window focus to catch backend changes + }); +} + +// ==================== Backend Management ==================== + +/** + * Hook for switching CLIProxy backend (original vs plus) + * Invalidates all backend-dependent queries to ensure UI consistency + */ +export function useUpdateBackend() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ backend, force = false }: { backend: 'original' | 'plus'; force?: boolean }) => + api.cliproxyServer.updateBackend(backend, force), + onSuccess: () => { + // Invalidate all queries that depend on backend setting + queryClient.invalidateQueries({ queryKey: ['cliproxy-update-check'] }); + queryClient.invalidateQueries({ queryKey: ['cliproxy-versions'] }); + queryClient.invalidateQueries({ queryKey: ['proxy-status'] }); + queryClient.invalidateQueries({ queryKey: ['cliproxy-stats'] }); + toast.success('Backend updated'); + }, + onError: (error: Error) => { + // Handle 409 conflict (proxy running) + if (error.message.includes('Proxy is running')) { + toast.error('Stop the proxy first to change backend'); + } else { + toast.error(error.message); + } + }, }); } diff --git a/ui/src/pages/settings/sections/proxy/index.tsx b/ui/src/pages/settings/sections/proxy/index.tsx index 275b356f..4c1518f7 100644 --- a/ui/src/pages/settings/sections/proxy/index.tsx +++ b/ui/src/pages/settings/sections/proxy/index.tsx @@ -18,8 +18,8 @@ import { Box, AlertTriangle, } from 'lucide-react'; -import { toast } from 'sonner'; import { useProxyConfig, useRawConfig } from '../../hooks'; +import { useUpdateBackend } from '@/hooks/use-cliproxy'; import { LocalProxyCard } from './local-proxy-card'; import { RemoteProxyCard } from './remote-proxy-card'; import { api } from '@/lib/api-client'; @@ -74,10 +74,10 @@ export default function ProxySection() { } }; - // Backend state (loaded from API) + // Backend state (loaded from API) + mutation hook for proper query invalidation const [backend, setBackend] = useState<'original' | 'plus'>('plus'); - const [backendSaving, setBackendSaving] = useState(false); const [hasKiroGhcpVariants, setHasKiroGhcpVariants] = useState(false); + const updateBackendMutation = useUpdateBackend(); // Fetch backend setting const fetchBackend = useCallback(async () => { @@ -100,24 +100,18 @@ export default function ProxySection() { } }, []); - // Save backend setting - const handleBackendChange = async (value: 'original' | 'plus') => { + // Save backend setting using mutation hook (invalidates all related queries) + const handleBackendChange = (value: 'original' | 'plus') => { const previousValue = backend; - setBackend(value); - setBackendSaving(true); - try { - await api.cliproxyServer.updateBackend(value); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Failed to save backend'; - // Check if error is due to proxy running (409 conflict) - if (errorMessage.includes('Proxy is running')) { - toast.error('Stop the proxy first to change backend'); + setBackend(value); // Optimistic update + updateBackendMutation.mutate( + { backend: value }, + { + onError: () => { + setBackend(previousValue); // Rollback on error + }, } - console.error('[Proxy] Failed to save backend:', err); - setBackend(previousValue); - } finally { - setBackendSaving(false); - } + ); }; // Log when debug mode changes (sanitize sensitive fields) @@ -140,8 +134,10 @@ export default function ProxySection() { useEffect(() => { fetchConfig(); fetchRawConfig(); - fetchBackend(); - checkPlusOnlyVariants(); + // eslint-disable-next-line react-hooks/set-state-in-effect -- Async data fetching on mount is intended + void fetchBackend(); + + void checkPlusOnlyVariants(); }, [fetchConfig, fetchRawConfig, fetchBackend, checkPlusOnlyVariants]); if (loading || !config) { @@ -253,7 +249,8 @@ export default function ProxySection() {

- Configure local or remote CLIProxy Plus connection for proxy-based profiles + Configure local or remote {backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy'} connection + for proxy-based profiles

{/* Mode Toggle - Card based selection */} @@ -277,7 +274,7 @@ export default function ProxySection() { Local

- Run CLIProxy Plus binary on this machine + Run {backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy'} binary on this machine

@@ -298,7 +295,7 @@ export default function ProxySection() { Remote

- Connect to a remote CLIProxy Plus server + Connect to a remote {backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy'} server

@@ -314,7 +311,7 @@ export default function ProxySection() { {/* Plus Backend Card */}