{
@@ -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 */}