/** * Settings Page - WebSearch Configuration * Supports Gemini CLI and Grok CLI providers */ import { useState, useEffect } from 'react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Switch } from '@/components/ui/switch'; import { Globe, RefreshCw, CheckCircle2, AlertCircle, FileCode, Copy, Check, GripVertical, Terminal, ExternalLink, } from 'lucide-react'; import { CodeEditor } from '@/components/code-editor'; interface ProviderConfig { enabled?: boolean; timeout?: number; } interface OpenCodeProviderConfig extends ProviderConfig { model?: string; } interface WebSearchProvidersConfig { gemini?: ProviderConfig; grok?: ProviderConfig; opencode?: OpenCodeProviderConfig; } 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() { 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); // Load config and status on mount useEffect(() => { fetchConfig(); fetchStatus(); fetchRawConfig(); }, []); 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, }, }, }); }; 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)

{/* Gemini Installation hint when not installed */} {!status?.geminiCli?.installed && !statusLoading && (

Gemini CLI not installed

Install globally (FREE tier available):

npm install -g @google/gemini-cli
)} {/* OpenCode CLI Provider */}

opencode

FREE {status?.opencodeCli?.installed ? ( installed ) : ( not installed )}

OpenCode (web search via Zen)

{/* OpenCode Installation hint when not installed */} {!status?.opencodeCli?.installed && !statusLoading && (

OpenCode not installed

Install globally (FREE tier available):

curl -fsSL https://opencode.ai/install | bash
)} {/* Grok CLI Provider */}

grok

GROK_API_KEY {status?.grokCli?.installed ? ( installed ) : ( not installed )}

xAI Grok CLI (web + X search)

{/* Grok Installation hint when not installed */} {!status?.grokCli?.installed && !statusLoading && (

Grok CLI not installed

Install globally (requires xAI API key):

npm install -g @vibe-kit/grok-cli
)}
{/* 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
)}
); }