diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index a021168..887d4f9 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -7,7 +7,7 @@ import { For, createEffect, } from 'solid-js'; -import { Settings as SettingsIcon, Save, Plus, Trash2, Info, Download, Upload } from 'lucide-solid'; +import { Settings as SettingsIcon, Save, Plus, Trash2, Info, Download, Upload, RefreshCw } from 'lucide-solid'; import { A } from '@solidjs/router'; import { settingsApi } from '~/api/settings'; import { configApi } from '~/api/config'; @@ -18,6 +18,7 @@ import type { UserSettings, ProviderConfig } from '~/types'; import LoadingSpinner from '~/components/ui/LoadingSpinner'; import ApiKeyManager from '~/components/ApiKeyManager'; import { getProviderInfoKey, providerSupportsWebSearch } from '~/utils/providers'; +import { useToast } from '~/components/ui/Toast'; /** * Settings page for configuring the user's synthesis preferences. @@ -37,6 +38,7 @@ import { getProviderInfoKey, providerSupportsWebSearch } from '~/utils/providers */ const Settings: Component = () => { const { t } = useI18n(); + const { addToast } = useToast(); const [settings, setSettings] = createSignal({ ...DEFAULT_SETTINGS, @@ -52,6 +54,13 @@ const Settings: Component = () => { const [providers] = createResource(() => configApi.listProviders()); const [providerWarning, setProviderWarning] = createSignal(false); + // Brave Search key management + const [apiKeys, { refetch: refetchApiKeys }] = createResource(() => apiKeysApi.list()); + const braveKey = () => apiKeys()?.find((k) => k.provider_name === 'brave_search'); + const [braveKeyInput, setBraveKeyInput] = createSignal(''); + const [braveSaving, setBraveSaving] = createSignal(false); + const [braveTesting, setBraveTesting] = createSignal(false); + let fileInputRef: HTMLInputElement | undefined; onMount(async () => { @@ -122,6 +131,53 @@ const Settings: Component = () => { } }); + const handleBraveKeySave = async () => { + const key = braveKeyInput().trim(); + if (!key) return; + setBraveSaving(true); + try { + await apiKeysApi.create({ provider_name: 'brave_search', api_key: key }); + addToast({ type: 'success', message: t('settings.apiKeys.saved'), duration: 4000 }); + setBraveKeyInput(''); + refetchApiKeys(); + } catch (err) { + const msg = isApiError(err) ? err.message : t('settings.apiKeys.saveError'); + addToast({ type: 'error', message: msg, duration: 5000 }); + } finally { + setBraveSaving(false); + } + }; + + const handleBraveKeyTest = async () => { + setBraveTesting(true); + try { + const result = await apiKeysApi.test('brave_search'); + if (result.success) { + addToast({ type: 'success', message: t('settings.apiKeys.testSuccess'), duration: 4000 }); + } else { + addToast({ type: 'error', message: t('settings.apiKeys.testFailure', { message: result.message }), duration: 6000 }); + } + } catch (err) { + const msg = isApiError(err) ? err.message : t('settings.apiKeys.testFailure', { message: 'Erreur inconnue' }); + addToast({ type: 'error', message: msg, duration: 5000 }); + } finally { + setBraveTesting(false); + } + }; + + const handleBraveKeyDelete = async () => { + try { + await apiKeysApi.remove('brave_search'); + addToast({ type: 'success', message: t('settings.apiKeys.deleted'), duration: 4000 }); + // Auto-disable use_brave_search when the key is removed + setSettings((prev) => ({ ...prev, use_brave_search: false })); + refetchApiKeys(); + } catch (err) { + const msg = isApiError(err) ? err.message : t('settings.apiKeys.deleteError'); + addToast({ type: 'error', message: msg, duration: 5000 }); + } + }; + const handleSave = async () => { setSaving(true); setMessage(null); @@ -513,6 +569,115 @@ const Settings: Component = () => { + {/* Brave Search */} +
+

+ {t('settings.braveSearch')} +

+

+ {t('settings.braveSearchKeyHelp')} +

+ + {/* Key management */} + + setBraveKeyInput(e.currentTarget.value)} + /> + +
+ } + > + {(key) => ( +
+
+ + {t('settings.apiKeys.configured')} + + + {t('settings.apiKeys.keyPrefix', { prefix: key().key_prefix })} + +
+
+ + +
+
+ )} + + + {/* use_brave_search toggle */} +
+
+ + setSettings((prev) => ({ + ...prev, + use_brave_search: e.currentTarget.checked, + })) + } + class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded disabled:opacity-50" + /> + +
+ +

+ {t('settings.braveSearchNotConfigured')} +

+
+ +

+ {t('settings.useBraveSearchHelp')} +

+
+
+ + {/* Search agent behavior */}