diff --git a/ui/src/components/shared/connection-indicator.tsx b/ui/src/components/shared/connection-indicator.tsx
index eb00487d..84932a53 100644
--- a/ui/src/components/shared/connection-indicator.tsx
+++ b/ui/src/components/shared/connection-indicator.tsx
@@ -1,19 +1,29 @@
/**
* Connection Indicator (Phase 04)
*
- * Shows WebSocket connection status in the header.
+ * Shows WebSocket connection status in the header with reconnection state.
*/
-import { Wifi, WifiOff } from 'lucide-react';
+import { Wifi, WifiOff, RefreshCw } from 'lucide-react';
import { useWebSocket } from '@/hooks/use-websocket';
export function ConnectionIndicator() {
- const { status } = useWebSocket();
+ const { status, isReconnecting } = useWebSocket();
const statusConfig = {
connected: { icon: Wifi, color: 'text-green-600', label: 'Connected' },
- connecting: { icon: Wifi, color: 'text-yellow-500', label: 'Connecting...' },
- disconnected: { icon: WifiOff, color: 'text-red-500', label: 'Disconnected' },
+ connecting: {
+ icon: RefreshCw,
+ color: 'text-yellow-500',
+ label: 'Connecting...',
+ animate: true,
+ },
+ disconnected: {
+ icon: isReconnecting ? RefreshCw : WifiOff,
+ color: isReconnecting ? 'text-amber-500' : 'text-red-500',
+ label: isReconnecting ? 'Reconnecting...' : 'Disconnected',
+ animate: isReconnecting,
+ },
};
const config = statusConfig[status];
@@ -21,7 +31,7 @@ export function ConnectionIndicator() {
return (
-
+
{config.label}
);
diff --git a/ui/src/hooks/use-websocket.ts b/ui/src/hooks/use-websocket.ts
index 198b10ca..81164214 100644
--- a/ui/src/hooks/use-websocket.ts
+++ b/ui/src/hooks/use-websocket.ts
@@ -18,6 +18,7 @@ type ConnectionStatus = 'connecting' | 'connected' | 'disconnected';
export function useWebSocket() {
const [status, setStatus] = useState('disconnected');
+ const [isReconnecting, setIsReconnecting] = useState(false);
const wsRef = useRef(null);
const queryClient = useQueryClient();
const reconnectAttempts = useRef(0);
@@ -75,6 +76,7 @@ export function useWebSocket() {
ws.onopen = () => {
setStatus('connected');
+ setIsReconnecting(false);
reconnectAttempts.current = 0;
console.log('[WS] Connected');
};
@@ -97,6 +99,7 @@ export function useWebSocket() {
// Attempt reconnect with exponential backoff
if (reconnectAttempts.current < maxReconnectAttempts) {
+ setIsReconnecting(true);
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000);
reconnectAttempts.current++;
console.log(`[WS] Reconnecting in ${delay}ms (attempt ${reconnectAttempts.current})`);
@@ -104,6 +107,8 @@ export function useWebSocket() {
reconnectTimeoutRef.current = setTimeout(() => {
connectRef.current();
}, delay);
+ } else {
+ setIsReconnecting(false);
}
};
@@ -117,6 +122,7 @@ export function useWebSocket() {
const disconnect = useCallback(() => {
reconnectAttempts.current = maxReconnectAttempts; // Prevent reconnect
+ setIsReconnecting(false);
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
@@ -142,5 +148,8 @@ export function useWebSocket() {
return () => clearInterval(interval);
}, []);
- return useMemo(() => ({ status, connect, disconnect }), [status, connect, disconnect]);
+ return useMemo(
+ () => ({ status, isReconnecting, connect, disconnect }),
+ [status, isReconnecting, connect, disconnect]
+ );
}
diff --git a/ui/src/pages/settings/index.tsx b/ui/src/pages/settings/index.tsx
index b7d447af..1571fdde 100644
--- a/ui/src/pages/settings/index.tsx
+++ b/ui/src/pages/settings/index.tsx
@@ -115,7 +115,25 @@ function SettingsPageInner() {
return (
-
+ {/* Mobile View - Stacked vertically */}
+
+
+
+
+
+ }>
+ {activeTab === 'websearch' && }
+ {activeTab === 'globalenv' && }
+ {activeTab === 'thinking' && }
+ {activeTab === 'proxy' && }
+ {activeTab === 'auth' && }
+ {activeTab === 'backups' && }
+
+
+
+
+ {/* Desktop View - Side-by-side panels */}
+
{/* Left Panel - Settings Controls */}
diff --git a/ui/src/pages/settings/sections/websearch/index.tsx b/ui/src/pages/settings/sections/websearch/index.tsx
index 5891f6d2..7b061fd8 100644
--- a/ui/src/pages/settings/sections/websearch/index.tsx
+++ b/ui/src/pages/settings/sections/websearch/index.tsx
@@ -7,7 +7,7 @@ import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { ScrollArea } from '@/components/ui/scroll-area';
-import { RefreshCw, CheckCircle2, AlertCircle } from 'lucide-react';
+import { RefreshCw, CheckCircle2, AlertCircle, Package } from 'lucide-react';
import { useWebSearchConfig, useRawConfig } from '../../hooks';
import { ProviderCard } from './provider-card';
@@ -141,6 +141,21 @@ export default function WebSearchSection() {
Providers
+ {/* Empty state when no providers available */}
+ {!status?.geminiCli && !status?.opencodeCli && !status?.grokCli && !statusLoading && (
+
+
+
No providers configured
+
+ Install CLI tools to enable web search providers
+
+
+
+ )}
+