diff --git a/frontend/src/components/ApiKeyManager.tsx b/frontend/src/components/ApiKeyManager.tsx index d3e8601..48bb330 100644 --- a/frontend/src/components/ApiKeyManager.tsx +++ b/frontend/src/components/ApiKeyManager.tsx @@ -35,13 +35,13 @@ const ApiKeyManager: Component = (props) => { }; return ( -
-
+
+
-

+

{t('settings.apiKeys.title')} -

+

{t('settings.apiKeys.description')} diff --git a/frontend/src/components/settings/SettingsRateLimit.tsx b/frontend/src/components/settings/SettingsRateLimit.tsx index b65ad8c..92405c1 100644 --- a/frontend/src/components/settings/SettingsRateLimit.tsx +++ b/frontend/src/components/settings/SettingsRateLimit.tsx @@ -20,7 +20,6 @@ const SettingsRateLimit: Component = (props) => { return ( <> -


{t('settings.rateLimitSection')} diff --git a/frontend/src/i18n/fr.ts b/frontend/src/i18n/fr.ts index 9cc27e8..c254e78 100644 --- a/frontend/src/i18n/fr.ts +++ b/frontend/src/i18n/fr.ts @@ -115,6 +115,20 @@ const fr = { 'synthesis.export.downloading': 'Telechargement...', 'synthesis.export.error': 'Erreur lors de l\'export.', + // Settings - Section headings + 'settings.section.content': 'Contenu', + 'settings.section.contentDesc': 'Definissez le theme, les categories et le format des syntheses.', + 'settings.section.sources': 'Sources', + 'settings.section.sourcesDesc': 'Configurez l\'extraction des articles depuis vos sources personnalisees.', + 'settings.section.ai': 'Intelligence Artificielle', + 'settings.section.aiDesc': 'Choisissez le fournisseur, les modeles et configurez vos cles API.', + 'settings.section.performance': 'Performance', + 'settings.section.performanceDesc': 'Parametres techniques pour optimiser la generation.', + 'settings.section.importExport': 'Import / Export', + 'settings.section.importExportDesc': 'Sauvegardez ou restaurez votre configuration.', + 'settings.aiKeyConfigured': 'Cle configuree', + 'settings.aiKeyNotConfigured': 'Cle non configuree', + // Settings 'settings.title': 'Parametres de generation', 'settings.theme': 'Theme de la recherche', diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 468846f..b5244e8 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -7,6 +7,7 @@ import { For, createEffect, } from 'solid-js'; +import { A } from '@solidjs/router'; import { Settings as SettingsIcon, Save, Plus, Trash2, Info, Download, Upload } from 'lucide-solid'; import Button from '~/components/ui/Button'; import { settingsApi } from '~/api/settings'; @@ -19,7 +20,6 @@ import LoadingSpinner from '~/components/ui/LoadingSpinner'; import ApiKeyManager from '~/components/ApiKeyManager'; import { getProviderInfoKey, providerSupportsWebSearch } from '~/utils/providers'; import SettingsBraveSearch from '~/components/settings/SettingsBraveSearch'; -import SettingsAdvanced from '~/components/settings/SettingsAdvanced'; import SettingsRateLimit from '~/components/settings/SettingsRateLimit'; /** @@ -53,6 +53,7 @@ const Settings: Component = () => { const [includeApiKeys, setIncludeApiKeys] = createSignal(false); const [providers] = createResource(() => configApi.listProviders()); + const [apiKeys, { refetch: refetchApiKeys }] = createResource(() => apiKeysApi.list()); const [providerWarning, setProviderWarning] = createSignal(false); let fileInputRef: HTMLInputElement | undefined; @@ -101,6 +102,10 @@ const Settings: Component = () => { ); }; + const getKeyForProvider = (providerName: string) => { + return apiKeys()?.find((k) => k.provider_name === providerName); + }; + const handleProviderChange = (providerName: string) => { setProviderWarning(false); const provider = providers()?.find( @@ -232,6 +237,7 @@ const Settings: Component = () => { }); } } + refetchApiKeys(); } setMessage({ type: 'success', text: t('settings.importSuccess') }); @@ -248,63 +254,12 @@ const Settings: Component = () => { return ( }>
-
-
- -

- {t('settings.title')} -

-
-
- {/* Export button */} - - {/* Import button */} - - { - const file = e.currentTarget.files?.[0]; - if (file) handleImport(file); - }} - /> -
-
- - {/* Export: Include API keys checkbox */} -
- - -

- {t('settings.exportKeysWarning')} -

-
+ {/* Page header */} +
+ +

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

@@ -328,8 +283,11 @@ const Settings: Component = () => {
-
-
+ {/* ── Section 1: Contenu ── */} +
+

{t('settings.section.content')}

+

{t('settings.section.contentDesc')}

+
{/* Theme */}
+ {/* Categories */} +
+
+ + +
+
+ + {(category, index) => ( +
+ + {index() + 1}. + + + handleCategoryChange(index(), e.currentTarget.value) + } + /> + +
+ )} +
+
+
+ {/* Max age days + Max items per category */}
@@ -367,7 +371,7 @@ const Settings: Component = () => { id="maxAgeDays" min="1" max="365" - class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md py-2 px-3 border" + class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-24 sm:text-sm border-gray-300 rounded-md py-2 px-3 border" value={settings().max_age_days} onInput={(e) => setSettings((prev) => ({ @@ -392,7 +396,7 @@ const Settings: Component = () => { id="maxItemsPerCategory" min="1" max="20" - class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md py-2 px-3 border" + class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-24 sm:text-sm border-gray-300 rounded-md py-2 px-3 border" value={settings().max_items_per_category} onInput={(e) => setSettings((prev) => ({ @@ -404,7 +408,51 @@ const Settings: Component = () => { />
+
+ + {/* Summary length slider */} +
+ +

{t('settings.summaryLengthHelp')}

+
+ {t('settings.summaryShort')} + + setSettings((prev) => ({ + ...prev, + summary_length: parseInt(e.currentTarget.value) || 3, + })) + } + /> + {t('settings.summaryDetailed')} +
+
+ {settings().summary_length === 1 + ? t('settings.summaryShort') + : settings().summary_length === 2 + ? t('settings.summaryMedium') + : t('settings.summaryDetailed')} +
+
+
+
+ {/* ── Section 2: Sources ── */} +
+

{t('settings.section.sources')}

+

{t('settings.section.sourcesDesc')}

+
+ {/* Max articles per source + Max links per source */} +
-
- - {/* Summary length slider */} -
- -

{t('settings.summaryLengthHelp')}

-
- {t('settings.summaryShort')} - - setSettings((prev) => ({ - ...prev, - summary_length: parseInt(e.currentTarget.value) || 3, - })) - } - /> - {t('settings.summaryDetailed')} -
-
- {settings().summary_length === 1 - ? t('settings.summaryShort') - : settings().summary_length === 2 - ? t('settings.summaryMedium') - : t('settings.summaryDetailed')} +
+ +

{t('settings.sourceExtractionWindowHelp')}

+
+ + setSettings((prev) => ({ + ...prev, + source_extraction_window: parseInt(e.currentTarget.value) || 3, + })) + } + /> +
- - + {/* Brave Search */} +
+
- {/* AI Provider & Model - Dynamic selection */} + {/* ── Section 3: Intelligence Artificielle ── */} +
+

{t('settings.section.ai')}

+

{t('settings.section.aiDesc')}

+
0} fallback={ @@ -508,214 +549,343 @@ const Settings: Component = () => {
} > - {/* Provider dropdown - only if multiple providers */} - -
- -
+ {/* Provider card with integrated key status */} +
+
+
+ {/* Provider dropdown or single provider name */} + + {selectedProvider()?.display_name} + + } + > + + + + + {(provider) => ( + + {t('settings.provider.noWebSearchBadge')} + + } + > + + {t('settings.provider.webSearchBadge')} + + + )} + +
+ + {/* API key status badge */} + + {(provider) => ( + + {t('settings.aiKeyNotConfigured')} + + } + > + + {t('settings.aiKeyConfigured')} + + + )} + +
+ + {/* Provider info text */} + + {(provider) => ( +
+ +

+ {t(getProviderInfoKey(provider().provider_name))} +

+
+ )} +
+ + {/* Model dropdowns inside the provider card */} +
+ {/* Research model dropdown */} +
+ +

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

+
+ + {/* Websearch model dropdown */} +
+ + +

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

-

- {t('settings.providerHelp')} -

- +
- {/* Research model dropdown */} + {/* Search agent behavior */}
-