import type { Dispatch, SetStateAction } from "react"; import { Eye, EyeOff, Pencil } from "lucide-react"; import { useTranslation } from "react-i18next"; import { ProviderPicker } from "@/components/settings/shared/ModelControls"; import { CapabilityInstallNotice, NumberInput, RestartSettingsFooter, SettingsGroup, SettingsRow, SettingsSectionTitle, StatusPill, } from "@/components/settings/shared/SettingsControls"; import { ToggleButton } from "@/components/settings/ToggleButton"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { NanobotFeatureInfo, SettingsPayload, WebSearchSettingsUpdate, } from "@/lib/types"; export const DEFAULT_WEB_SEARCH_FORM: WebSearchSettingsUpdate = { provider: "duckduckgo", apiKey: "", baseUrl: "", maxResults: 5, timeout: 30, useJinaReader: true, }; export function webSearchFormFromPayload( payload: SettingsPayload, previous?: WebSearchSettingsUpdate, ): WebSearchSettingsUpdate { return { provider: payload.web_search.provider, apiKey: previous?.provider === payload.web_search.provider ? previous.apiKey ?? "" : "", baseUrl: payload.web_search.base_url ?? "", maxResults: payload.web_search.max_results, timeout: payload.web_search.timeout, useJinaReader: payload.web.fetch.use_jina_reader, }; } type WebSearchProviderOption = SettingsPayload["web_search"]["providers"][number]; export function webSearchProviderAcceptsApiKey(provider?: WebSearchProviderOption): boolean { return provider?.credential === "api_key" || provider?.credential === "optional_api_key"; } export function webSearchProviderRequiresApiKey(provider?: WebSearchProviderOption): boolean { return provider?.credential === "api_key"; } export function WebSettings({ settings, form, keyVisible, keyEditing, saving, onChangeForm, onChangeProvider, onToggleKey, onToggleKeyEditing, onReset, onSave, showBrandLogos, onRestart, isRestarting, requiresRestartPending, olostepFeature, olostepInstalling, capabilityError, }: { settings: SettingsPayload; form: WebSearchSettingsUpdate; keyVisible: boolean; keyEditing: boolean; saving: boolean; onChangeForm: Dispatch>; onChangeProvider: (provider: string) => void; onToggleKey: () => void; onToggleKeyEditing: () => void; onReset: () => void; onSave: () => void; showBrandLogos: boolean; onRestart?: () => void; isRestarting?: boolean; requiresRestartPending: boolean; olostepFeature?: NanobotFeatureInfo; olostepInstalling: boolean; capabilityError: string | null; }) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const selectedProvider = settings.web_search.providers.find((provider) => provider.name === form.provider) ?? settings.web_search.providers[0]; const hasExistingSecret = webSearchProviderAcceptsApiKey(selectedProvider) && form.provider === settings.web_search.provider && !!settings.web_search.api_key_hint; const showKeyInput = webSearchProviderAcceptsApiKey(selectedProvider) && (!hasExistingSecret || keyEditing); const apiKey = form.apiKey?.trim() ?? ""; const baseUrl = form.baseUrl?.trim() ?? ""; const effectiveJinaReader = form.useJinaReader ?? settings.web.fetch.use_jina_reader; const dirty = form.provider !== settings.web_search.provider || apiKey.length > 0 || baseUrl !== (settings.web_search.base_url ?? "") || form.maxResults !== settings.web_search.max_results || form.timeout !== settings.web_search.timeout || effectiveJinaReader !== settings.web.fetch.use_jina_reader; const jinaReaderDirty = effectiveJinaReader !== settings.web.fetch.use_jina_reader; const missingCredential = webSearchProviderRequiresApiKey(selectedProvider) ? !apiKey && !hasExistingSecret : selectedProvider?.credential === "base_url" ? !baseUrl : false; return (
{tx("settings.sections.webSearch", "Web search")} {form.provider === "olostep" && olostepFeature && !olostepFeature.installed ? (
) : null} {capabilityError ? (

{capabilityError}

) : null} {selectedProvider?.credential === "none" ? ( {t("settings.byok.webSearch.noCredentialRequired")} ) : null} {webSearchProviderAcceptsApiKey(selectedProvider) ? (
{showKeyInput ? ( <> onChangeForm((prev) => ({ ...prev, apiKey: event.target.value })) } placeholder={ hasExistingSecret ? t("settings.byok.apiKeyConfiguredPlaceholder") : t("settings.byok.apiKeyPlaceholder") } className="h-9 rounded-full pr-11 text-[13px]" /> ) : ( <>
{settings.web_search.api_key_hint ?? t("settings.byok.configuredKeyHint")}
)}
) : null} {selectedProvider?.credential === "base_url" ? ( onChangeForm((prev) => ({ ...prev, baseUrl: event.target.value })) } placeholder={t("settings.byok.webSearch.baseUrlPlaceholder")} className="h-9 w-[280px] rounded-full text-[13px]" /> ) : null}
{tx("settings.sections.webBehavior", "Behavior")} onChangeForm((prev) => ({ ...prev, maxResults }))} /> onChangeForm((prev) => ({ ...prev, timeout }))} suffix="s" /> onChangeForm((prev) => ({ ...prev, useJinaReader }))} ariaLabel={tx("settings.rows.jinaReader", "Jina reader")} label={effectiveJinaReader ? tx("settings.values.on", "On") : tx("settings.values.off", "Off")} />
); }