/ src / views / settings / section-web-search.tsx
section-web-search.tsx
  1  'use client'
  2  
  3  import type { SettingsSectionProps } from './types'
  4  
  5  export function WebSearchSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
  6    const provider = appSettings.webSearchProvider || 'duckduckgo'
  7    const hasTavilyKey = appSettings.tavilyApiKeyConfigured === true
  8    const hasBraveKey = appSettings.braveApiKeyConfigured === true
  9    const hasExaKey = appSettings.exaApiKeyConfigured === true
 10  
 11    return (
 12      <div className="mb-10">
 13        <h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
 14          Web Search
 15        </h3>
 16        <p className="text-[12px] text-text-3 mb-5">
 17          Choose which search engine agents use for the <code className="text-[11px] font-mono text-text-2">web_search</code> tool.
 18        </p>
 19        <div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
 20          <div className="mb-5">
 21            <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Search Provider</label>
 22            <select
 23              value={provider}
 24              onChange={(e) => patchSettings({ webSearchProvider: e.target.value as typeof provider })}
 25              className={inputClass}
 26              style={{ fontFamily: 'inherit' }}
 27            >
 28              <option value="duckduckgo">DuckDuckGo (default, no key required)</option>
 29              <option value="google">Google (scraping, no key required)</option>
 30              <option value="bing">Bing (scraping, no key required)</option>
 31              <option value="searxng">SearXNG (self-hosted, no key required)</option>
 32              <option value="tavily">Tavily (API key required)</option>
 33              <option value="brave">Brave Search (API key required)</option>
 34              <option value="exa">Exa (API key required)</option>
 35            </select>
 36          </div>
 37  
 38          {provider === 'searxng' && (
 39            <div>
 40              <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">SearXNG URL</label>
 41              <input
 42                type="text"
 43                value={appSettings.searxngUrl || ''}
 44                onChange={(e) => patchSettings({ searxngUrl: e.target.value || undefined })}
 45                placeholder="http://localhost:8080"
 46                className={inputClass}
 47                style={{ fontFamily: 'inherit' }}
 48              />
 49            </div>
 50          )}
 51  
 52          {provider === 'tavily' && (
 53            <div>
 54              <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Tavily API Key</label>
 55              <input
 56                type="password"
 57                value={appSettings.tavilyApiKey || ''}
 58                onChange={(e) => patchSettings({ tavilyApiKey: e.target.value || null })}
 59                placeholder={hasTavilyKey ? 'Stored securely. Enter a new key to replace it.' : 'tvly-...'}
 60                className={inputClass}
 61                style={{ fontFamily: 'inherit' }}
 62              />
 63              {hasTavilyKey && (
 64                <p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
 65              )}
 66              <p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://tavily.com" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">tavily.com</a></p>
 67            </div>
 68          )}
 69  
 70          {provider === 'brave' && (
 71            <div>
 72              <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Brave Search API Key</label>
 73              <input
 74                type="password"
 75                value={appSettings.braveApiKey || ''}
 76                onChange={(e) => patchSettings({ braveApiKey: e.target.value || null })}
 77                placeholder={hasBraveKey ? 'Stored securely. Enter a new key to replace it.' : 'BSA...'}
 78                className={inputClass}
 79                style={{ fontFamily: 'inherit' }}
 80              />
 81              {hasBraveKey && (
 82                <p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
 83              )}
 84              <p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://brave.com/search/api/" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">brave.com/search/api</a></p>
 85            </div>
 86          )}
 87  
 88          {provider === 'exa' && (
 89            <div>
 90              <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Exa API Key</label>
 91              <input
 92                type="password"
 93                value={appSettings.exaApiKey || ''}
 94                onChange={(e) => patchSettings({ exaApiKey: e.target.value || null })}
 95                placeholder={hasExaKey ? 'Stored securely. Enter a new key to replace it.' : 'exa-...'}
 96                className={inputClass}
 97                style={{ fontFamily: 'inherit' }}
 98              />
 99              {hasExaKey && (
100                <p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
101              )}
102              <p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://exa.ai" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">exa.ai</a>. You can also set the <code className="text-[11px] font-mono text-text-2">EXA_API_KEY</code> environment variable.</p>
103            </div>
104          )}
105        </div>
106      </div>
107    )
108  }