-
+ // 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
+
+
+
+ )}
+
+
+
+
);
}