+ {/* Hero Row Skeleton */}
+
@@ -44,42 +77,31 @@ export function HomePage() {
-
-
- {/* Stats Skeleton */}
-
- {[1, 2, 3, 4].map((i) => (
-
- ))}
-
-
- {/* Quick Actions Skeleton */}
-
-
- {/* Quick Commands Skeleton */}
-
-
-
+
{[1, 2, 3, 4].map((i) => (
-
+
))}
+
+ {/* Auth Monitor Skeleton */}
+
+
+
+
+
+
+
+
+ {[1, 2, 3].map((i) => (
+
+
+
+
+
+
+ ))}
+
);
}
@@ -90,13 +112,57 @@ export function HomePage() {
return (
- {/* Hero Section */}
-
+ {/* Hero Row: Logo/Title + Inline Stats */}
+
+ {/* Subtle background pattern */}
+
+
+ {/* Single Row Layout */}
+
+ {/* Left: Logo + Title */}
+
+
+ {/* Right: Inline Stats */}
+
+ navigate('/api')}
+ />
+ navigate('/cliproxy')}
+ />
+ navigate('/accounts')}
+ />
+ navigate('/health')}
+ />
+
+
+
{/* Configuration Warning */}
{shared?.symlinkStatus && !shared.symlinkStatus.valid && (
@@ -107,98 +173,8 @@ export function HomePage() {
)}
- {/* Stats Grid */}
-
- navigate('/api')}
- />
- navigate('/cliproxy')}
- />
- navigate('/accounts')}
- />
- navigate('/health')}
- />
-
-
- {/* Quick Actions */}
-
-
- Quick Actions
-
-
-
-
-
-
-
-
- {/* Quick Commands */}
-
-
- {/* Shared Data Summary */}
-
-
-
-
- Shared Data
-
-
-
-
-
-
- {shared?.commands ?? 0}
- Commands
-
-
- {shared?.skills ?? 0}
- Skills
-
-
- {shared?.agents ?? 0}
- Agents
-
-
-
-
+ {/* Auth Monitor */}
+
);
}
diff --git a/ui/src/pages/settings.tsx b/ui/src/pages/settings.tsx
index 98d5c4e9..d14175db 100644
--- a/ui/src/pages/settings.tsx
+++ b/ui/src/pages/settings.tsx
@@ -1,46 +1,721 @@
/**
- * Settings Page - Deprecated
- * Settings functionality has been moved to API Profiles page
+ * Settings Page - WebSearch Configuration
+ * Supports Gemini CLI and Grok CLI providers
*/
-import { Link } from 'react-router-dom';
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { useState, useEffect } from 'react';
+import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { Button } from '@/components/ui/button';
-import { Construction, ArrowRight } from 'lucide-react';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Switch } from '@/components/ui/switch';
+import { Input } from '@/components/ui/input';
+import {
+ Globe,
+ RefreshCw,
+ CheckCircle2,
+ AlertCircle,
+ FileCode,
+ Copy,
+ Check,
+ GripVertical,
+ Terminal,
+ ExternalLink,
+ ChevronDown,
+ ChevronUp,
+} from 'lucide-react';
+import { CodeEditor } from '@/components/code-editor';
+
+interface ProviderConfig {
+ enabled?: boolean;
+ model?: string;
+ timeout?: number;
+}
+
+interface WebSearchProvidersConfig {
+ gemini?: ProviderConfig;
+ grok?: ProviderConfig;
+ opencode?: ProviderConfig;
+}
+
+interface WebSearchConfig {
+ enabled: boolean;
+ providers?: WebSearchProvidersConfig;
+}
+
+interface CliStatus {
+ installed: boolean;
+ path: string | null;
+ version: string | null;
+}
+
+interface WebSearchStatus {
+ geminiCli: CliStatus;
+ grokCli: CliStatus;
+ opencodeCli: CliStatus;
+ readiness: {
+ status: 'ready' | 'unavailable';
+ message: string;
+ };
+}
export function SettingsPage() {
- return (
-
-
Settings
+ const [config, setConfig] = useState
(null);
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [error, setError] = useState(null);
+ const [success, setSuccess] = useState(false);
+ const [status, setStatus] = useState(null);
+ const [statusLoading, setStatusLoading] = useState(true);
+ // Config viewer state
+ const [rawConfig, setRawConfig] = useState(null);
+ const [rawConfigLoading, setRawConfigLoading] = useState(false);
+ const [copied, setCopied] = useState(false);
+ // Local model input state (to avoid saving on every keystroke)
+ const [geminiModelInput, setGeminiModelInput] = useState('');
+ const [opencodeModelInput, setOpencodeModelInput] = useState('');
+ // Collapsible install hints state
+ const [showGeminiHint, setShowGeminiHint] = useState(false);
+ const [showOpencodeHint, setShowOpencodeHint] = useState(false);
+ const [showGrokHint, setShowGrokHint] = useState(false);
-
-
-
-
- Page Relocated
-
-
-
-
- The settings editor has been integrated into the API Profiles page for
- a better user experience.
-
- To edit environment variables for a profile:
-
- - Go to API Profiles page
- - Click the actions menu (...) on any profile
- - Select "Edit Settings"
-
-
-
+ // Load config and status on mount
+ useEffect(() => {
+ fetchConfig();
+ fetchStatus();
+ fetchRawConfig();
+ }, []);
+
+ // Sync local model inputs when config changes
+ useEffect(() => {
+ if (config) {
+ setGeminiModelInput(config.providers?.gemini?.model ?? 'gemini-2.5-flash');
+ setOpencodeModelInput(config.providers?.opencode?.model ?? 'opencode/grok-code');
+ }
+ }, [config]);
+
+ const fetchConfig = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const res = await fetch('/api/websearch');
+ if (!res.ok) throw new Error('Failed to load WebSearch config');
+ const data = await res.json();
+ setConfig(data);
+ } catch (err) {
+ setError((err as Error).message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const fetchStatus = async () => {
+ try {
+ setStatusLoading(true);
+ const res = await fetch('/api/websearch/status');
+ if (!res.ok) throw new Error('Failed to load status');
+ const data = await res.json();
+ setStatus(data);
+ } catch (err) {
+ console.error('Failed to fetch WebSearch status:', err);
+ } finally {
+ setStatusLoading(false);
+ }
+ };
+
+ const fetchRawConfig = async () => {
+ try {
+ setRawConfigLoading(true);
+ const res = await fetch('/api/config/raw');
+ if (!res.ok) {
+ setRawConfig(null);
+ return;
+ }
+ const text = await res.text();
+ setRawConfig(text);
+ } catch (err) {
+ console.error('Failed to fetch raw config:', err);
+ setRawConfig(null);
+ } finally {
+ setRawConfigLoading(false);
+ }
+ };
+
+ const copyToClipboard = async () => {
+ if (!rawConfig) return;
+ try {
+ await navigator.clipboard.writeText(rawConfig);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error('Failed to copy:', err);
+ }
+ };
+
+ // Toggle Gemini provider
+ const toggleGemini = () => {
+ const providers = config?.providers || {};
+ const currentState = providers.gemini?.enabled ?? false;
+ const grokState = providers.grok?.enabled ?? false;
+ const opencodeState = providers.opencode?.enabled ?? false;
+
+ saveConfig({
+ enabled: !currentState || grokState || opencodeState, // Enable WebSearch if any provider is enabled
+ providers: {
+ ...providers,
+ gemini: {
+ ...providers.gemini,
+ enabled: !currentState,
+ },
+ },
+ });
+ };
+
+ const saveConfig = async (updates: Partial
) => {
+ if (!config) return;
+
+ // Optimistic update - apply changes immediately to local state
+ const optimisticConfig = { ...config, ...updates };
+ setConfig(optimisticConfig);
+
+ try {
+ setSaving(true);
+ setError(null);
+
+ const res = await fetch('/api/websearch', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(optimisticConfig),
+ });
+
+ if (!res.ok) {
+ const data = await res.json();
+ throw new Error(data.error || 'Failed to save');
+ }
+
+ const data = await res.json();
+ setConfig(data.websearch);
+ // Quick flash of success (shorter duration, less intrusive)
+ setSuccess(true);
+ setTimeout(() => setSuccess(false), 1500);
+ // Silently refresh raw config without loading state
+ fetch('/api/config/raw')
+ .then((r) => (r.ok ? r.text() : null))
+ .then((text) => text && setRawConfig(text))
+ .catch(() => {});
+ } catch (err) {
+ // Revert optimistic update on error
+ setConfig(config);
+ setError((err as Error).message);
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const isGeminiEnabled = config?.providers?.gemini?.enabled ?? false;
+ const isGrokEnabled = config?.providers?.grok?.enabled ?? false;
+ const isOpenCodeEnabled = config?.providers?.opencode?.enabled ?? false;
+
+ // Toggle Grok provider
+ const toggleGrok = () => {
+ const providers = config?.providers || {};
+ const currentState = providers.grok?.enabled ?? false;
+
+ saveConfig({
+ enabled: isGeminiEnabled || !currentState || isOpenCodeEnabled, // Enable WebSearch if any provider is enabled
+ providers: {
+ ...providers,
+ grok: {
+ ...providers.grok,
+ enabled: !currentState,
+ },
+ },
+ });
+ };
+
+ // Toggle OpenCode provider
+ const toggleOpenCode = () => {
+ const providers = config?.providers || {};
+ const currentState = providers.opencode?.enabled ?? false;
+
+ saveConfig({
+ enabled: isGeminiEnabled || isGrokEnabled || !currentState, // Enable WebSearch if any provider is enabled
+ providers: {
+ ...providers,
+ opencode: {
+ ...providers.opencode,
+ enabled: !currentState,
+ },
+ },
+ });
+ };
+
+ // Save Gemini model on blur (only if changed)
+ const saveGeminiModel = () => {
+ const currentModel = config?.providers?.gemini?.model ?? 'gemini-2.5-flash';
+ if (geminiModelInput !== currentModel) {
+ const providers = config?.providers || {};
+ saveConfig({
+ providers: {
+ ...providers,
+ gemini: {
+ ...providers.gemini,
+ model: geminiModelInput,
+ },
+ },
+ });
+ }
+ };
+
+ // Save OpenCode model on blur (only if changed)
+ const saveOpencodeModel = () => {
+ const currentModel = config?.providers?.opencode?.model ?? 'opencode/grok-code';
+ if (opencodeModelInput !== currentModel) {
+ const providers = config?.providers || {};
+ saveConfig({
+ providers: {
+ ...providers,
+ opencode: {
+ ...providers.opencode,
+ model: opencodeModelInput,
+ },
+ },
+ });
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+ Loading configuration...
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Left Panel - WebSearch Controls */}
+
+
+ {/* Header */}
+
+
+
+
+
WebSearch
+
+ CLI-based web search for third-party profiles
+
+
+
+
+
+ {/* Toast-style alerts - absolute positioned, no layout shift */}
+
+ {error && (
+
+
+ {error}
+
+ )}
+ {success && (
+
+
+ Saved
+
+ )}
+
+
+ {/* Scrollable Content */}
+
+
+ {/* Status Summary */}
+
+
+
+ {isGeminiEnabled ? 'WebSearch enabled' : 'WebSearch disabled'}
+
+ {statusLoading ? (
+
Checking status...
+ ) : status?.readiness ? (
+
{status.readiness.message}
+ ) : null}
+
+
+
+
+ {/* CLI Providers */}
+
+
Providers
+
+ {/* Gemini CLI Provider */}
+
+
+
+
+
+
+
gemini
+
+ FREE
+
+ {status?.geminiCli?.installed ? (
+
+ installed
+
+ ) : (
+
+ not installed
+
+ )}
+
+
+ Google Gemini CLI (1000 req/day free)
+
+
+
+
+
+ {/* Model input when enabled */}
+ {isGeminiEnabled && (
+
+
+
+ setGeminiModelInput(e.target.value)}
+ onBlur={saveGeminiModel}
+ placeholder="gemini-2.5-flash"
+ className="h-8 text-sm font-mono"
+ disabled={saving}
+ />
+
+
+ )}
+ {/* Installation hint when not installed - inside card */}
+ {!status?.geminiCli?.installed && !statusLoading && (
+
+
+ {showGeminiHint && (
+
+ )}
+
+ )}
+
+
+ {/* OpenCode CLI Provider */}
+
+
+
+
+
+
+
opencode
+
+ FREE
+
+ {status?.opencodeCli?.installed ? (
+
+ installed
+
+ ) : (
+
+ not installed
+
+ )}
+
+
+ OpenCode (web search via Zen)
+
+
+
+
+
+ {/* Model input when enabled */}
+ {isOpenCodeEnabled && (
+
+
+
+ setOpencodeModelInput(e.target.value)}
+ onBlur={saveOpencodeModel}
+ placeholder="opencode/grok-code"
+ className="h-8 text-sm font-mono"
+ disabled={saving}
+ />
+
+
+ )}
+ {/* Installation hint when not installed - inside card */}
+ {!status?.opencodeCli?.installed && !statusLoading && (
+
+
+ {showOpencodeHint && (
+
+
+ Install globally (FREE tier available):
+
+
+ curl -fsSL https://opencode.ai/install | bash
+
+
+
+ View documentation
+
+
+ )}
+
+ )}
+
+
+ {/* Grok CLI Provider */}
+
+
+
+
+
+
+
grok
+
+ GROK_API_KEY
+
+ {status?.grokCli?.installed ? (
+
+ installed
+
+ ) : (
+
+ not installed
+
+ )}
+
+
+ xAI Grok CLI (web + X search)
+
+
+
+
+
+ {/* Installation hint when not installed - inside card */}
+ {!status?.grokCli?.installed && !statusLoading && (
+
+
+ {showGrokHint && (
+
+ )}
+
+ )}
+
+
+
+
+
+ {/* Footer */}
+
+
+
-
-
+
+
+ {/* Resize Handle */}
+
+
+
+
+ {/* Right Panel - Config Viewer */}
+
+
+ {/* Header */}
+
+
+
+
+
config.yaml
+
~/.ccs/config.yaml
+
+
+
+
+
+
+
+
+ {/* Config Content - scrollable */}
+
+ {rawConfigLoading ? (
+
+
+ Loading...
+
+ ) : rawConfig ? (
+
{}}
+ language="yaml"
+ readonly
+ minHeight="auto"
+ className="min-h-full"
+ />
+ ) : (
+
+
+
+
Config file not found
+
+ ccs migrate
+
+
+
+ )}
+
+
+
+
);
}
diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/ui/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/ui/tsconfig.app.json b/ui/tsconfig.app.json
index 2ad9a724..99fbd39c 100644
--- a/ui/tsconfig.app.json
+++ b/ui/tsconfig.app.json
@@ -5,7 +5,6 @@
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
- "types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */