From fb765d6c8f6467c40c23a5622a549af92503cfe8 Mon Sep 17 00:00:00 2001 From: oabrivard Date: Wed, 25 Mar 2026 08:40:50 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20split=20model=20dropdowns=20=E2=80=94?= =?UTF-8?q?=20scraping=20vs=20websearch=20in=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single `models` array in `ProviderConfig` and `AdminProvider` with separate `models_scraping` / `models_websearch` lists. Rename `ai_model_writing` → `ai_model_websearch` in `UserSettings` and all references (Settings page, admin Providers page, E2E test, fixtures, and unit tests). Update i18n label for the second dropdown to "Modele d'IA (Recherche Web)". Co-Authored-By: Claude Sonnet 4.6 --- e2e/tests/generation-live.spec.ts | 2 +- frontend/src/__tests__/config-api.test.ts | 32 ++- frontend/src/__tests__/fixtures.ts | 5 +- .../src/__tests__/pages/settings.test.tsx | 10 +- .../src/__tests__/settings-validation.test.ts | 2 +- frontend/src/i18n/fr.ts | 6 +- frontend/src/pages/GenerateSynthesis.tsx | 2 +- frontend/src/pages/Settings.tsx | 28 +- frontend/src/pages/admin/Providers.tsx | 254 +++++++++++++++--- frontend/src/types.ts | 16 +- 10 files changed, 283 insertions(+), 74 deletions(-) diff --git a/e2e/tests/generation-live.spec.ts b/e2e/tests/generation-live.spec.ts index 7ac9882..6b3094a 100644 --- a/e2e/tests/generation-live.spec.ts +++ b/e2e/tests/generation-live.spec.ts @@ -140,7 +140,7 @@ test.describe('Live generation with OpenAI', () => { search_agent_behavior: '', ai_provider: 'openai', ai_model: 'gpt-4o-mini', - ai_model_writing: 'gpt-4o-mini', + ai_model_websearch: 'gpt-4o-mini', use_llm_for_source_links: false, article_history_days: 90, }); diff --git a/frontend/src/__tests__/config-api.test.ts b/frontend/src/__tests__/config-api.test.ts index f9269a4..9a35af6 100644 --- a/frontend/src/__tests__/config-api.test.ts +++ b/frontend/src/__tests__/config-api.test.ts @@ -20,14 +20,20 @@ describe('Config API - Provider Config', () => { { provider_name: 'gemini', display_name: 'Google Gemini', - models: [ + models_scraping: [ + { model_id: 'gemini-3.1-pro', display_name: 'Gemini 3.1 Pro' }, + ], + models_websearch: [ { model_id: 'gemini-3.1-pro', display_name: 'Gemini 3.1 Pro' }, ], }, { provider_name: 'openai', display_name: 'OpenAI', - models: [ + models_scraping: [ + { model_id: 'gpt-4o', display_name: 'GPT-4o' }, + ], + models_websearch: [ { model_id: 'gpt-4o', display_name: 'GPT-4o' }, ], }, @@ -56,7 +62,7 @@ describe('Config API - Provider Config', () => { expect(result).toEqual(mockProviders); expect(result).toHaveLength(2); expect(result[0].provider_name).toBe('gemini'); - expect(result[0].models).toHaveLength(1); + expect(result[0].models_scraping).toHaveLength(1); expect(result[1].provider_name).toBe('openai'); }); @@ -131,7 +137,14 @@ describe('Admin API - Providers', () => { id: '1', provider_name: 'gemini', display_name: 'Google Gemini', - models: [ + models_scraping: [ + { + model_id: 'gemini-3.1-pro', + display_name: 'Gemini 3.1 Pro', + is_default: true, + }, + ], + models_websearch: [ { model_id: 'gemini-3.1-pro', display_name: 'Gemini 3.1 Pro', @@ -160,14 +173,21 @@ describe('Admin API - Providers', () => { expect(result).toEqual(mockProviders); expect(result[0].is_enabled).toBe(true); - expect(result[0].models[0].is_default).toBe(true); + expect(result[0].models_scraping[0].is_default).toBe(true); }); it('should call POST /api/v1/admin/providers with body', async () => { const newProvider = { provider_name: 'anthropic', display_name: 'Anthropic', - models: [ + models_scraping: [ + { + model_id: 'claude-opus-4', + display_name: 'Claude Opus 4', + is_default: true, + }, + ], + models_websearch: [ { model_id: 'claude-opus-4', display_name: 'Claude Opus 4', diff --git a/frontend/src/__tests__/fixtures.ts b/frontend/src/__tests__/fixtures.ts index 403ec9e..f2c59cb 100644 --- a/frontend/src/__tests__/fixtures.ts +++ b/frontend/src/__tests__/fixtures.ts @@ -53,13 +53,14 @@ export const MOCK_SOURCES: Source[] = [ // ---- Settings ---- export const MOCK_SETTINGS: UserSettings = { - ...DEFAULT_SETTINGS, theme: 'Intelligence Artificielle', ai_provider: 'gemini', ai_model: 'gemini-2.5-pro', ai_model_writing: 'gemini-2.5-flash', + ...DEFAULT_SETTINGS, theme: 'Intelligence Artificielle', ai_provider: 'gemini', ai_model: 'gemini-2.5-pro', ai_model_websearch: 'gemini-2.5-flash', }; // ---- Providers ---- export const MOCK_PROVIDER_CONFIG: ProviderConfig = { provider_name: 'gemini', display_name: 'Google Gemini', - models: [{ model_id: 'gemini-2.5-pro', display_name: 'Gemini 2.5 Pro' }, { model_id: 'gemini-2.5-flash', display_name: 'Gemini 2.5 Flash' }], + models_scraping: [{ model_id: 'gemini-2.5-pro', display_name: 'Gemini 2.5 Pro' }, { model_id: 'gemini-2.5-flash', display_name: 'Gemini 2.5 Flash' }], + models_websearch: [{ model_id: 'gemini-2.5-pro', display_name: 'Gemini 2.5 Pro' }, { model_id: 'gemini-2.5-flash', display_name: 'Gemini 2.5 Flash' }], }; export const MOCK_PROVIDER_CONFIGS: ProviderConfig[] = [MOCK_PROVIDER_CONFIG]; diff --git a/frontend/src/__tests__/pages/settings.test.tsx b/frontend/src/__tests__/pages/settings.test.tsx index 1518d2e..d8fee71 100644 --- a/frontend/src/__tests__/pages/settings.test.tsx +++ b/frontend/src/__tests__/pages/settings.test.tsx @@ -45,7 +45,11 @@ const mockProvidersWithOpenAI = [ { provider_name: 'openai', display_name: 'OpenAI', - models: [ + models_scraping: [ + { model_id: 'gpt-4o', display_name: 'GPT-4o' }, + { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' }, + ], + models_websearch: [ { model_id: 'gpt-4o', display_name: 'GPT-4o' }, { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' }, ], @@ -146,7 +150,7 @@ describe('Settings Page', () => { }); }); - it('should render two model dropdowns (research + writing)', async () => { + it('should render two model dropdowns (scraping + websearch)', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(mockProvidersWithOpenAI); mockedListKeys.mockResolvedValue([]); @@ -159,7 +163,7 @@ describe('Settings Page', () => { ).toBeInTheDocument(); }); expect( - screen.getByLabelText("Modele d'IA (Redaction et Synthese)"), + screen.getByLabelText("Modele d'IA (Recherche Web)"), ).toBeInTheDocument(); }); diff --git a/frontend/src/__tests__/settings-validation.test.ts b/frontend/src/__tests__/settings-validation.test.ts index 555d4b7..415d405 100644 --- a/frontend/src/__tests__/settings-validation.test.ts +++ b/frontend/src/__tests__/settings-validation.test.ts @@ -8,7 +8,7 @@ describe('Settings validation logic', () => { expect(DEFAULT_SETTINGS.max_items_per_category).toBe(4); expect(DEFAULT_SETTINGS.categories.length).toBeGreaterThan(0); expect(DEFAULT_SETTINGS.ai_model).toBe(''); - expect(DEFAULT_SETTINGS.ai_model_writing).toBe(''); + expect(DEFAULT_SETTINGS.ai_model_websearch).toBe(''); expect(DEFAULT_SETTINGS.ai_provider).toBe(''); expect(DEFAULT_SETTINGS.rate_limit_max_requests).toBeNull(); expect(DEFAULT_SETTINGS.rate_limit_time_window_seconds).toBeNull(); diff --git a/frontend/src/i18n/fr.ts b/frontend/src/i18n/fr.ts index 56fff29..15c7971 100644 --- a/frontend/src/i18n/fr.ts +++ b/frontend/src/i18n/fr.ts @@ -142,8 +142,8 @@ const fr = { 'settings.loadError': 'Erreur lors du chargement des parametres.', 'settings.modelResearch': "Modele d'IA (Recherche et Extraction)", 'settings.modelResearchHelp': "Choisissez le modele d'IA utilise pour rechercher et extraire les informations.", - 'settings.modelWriting': "Modele d'IA (Redaction et Synthese)", - 'settings.modelWritingHelp': "Choisissez le modele d'IA utilise pour le second agent, charge de rediger et structurer la synthese finale.", + 'settings.modelWebsearch': "Modele d'IA (Recherche Web)", + 'settings.modelWebsearchHelp': "Choisissez le modele d'IA utilise pour la recherche web et la generation des syntheses.", 'settings.rateLimitSection': 'Limitation de taux', 'settings.rateLimitMaxRequests': 'Requetes maximum', 'settings.rateLimitTimeWindow': 'Fenetre de temps (secondes)', @@ -266,6 +266,8 @@ const fr = { 'admin.providers.displayName': "Nom d'affichage", 'admin.providers.displayNamePlaceholder': 'ex: OpenAI, Google Gemini', 'admin.providers.models': 'Modeles disponibles', + 'admin.providers.modelsScraping': 'Modeles (Extraction / Scraping)', + 'admin.providers.modelsWebsearch': 'Modeles (Recherche Web)', 'admin.providers.modelId': 'Identifiant du modele', 'admin.providers.modelIdPlaceholder': 'ex: gpt-4o', 'admin.providers.modelDisplayName': "Nom d'affichage du modele", diff --git a/frontend/src/pages/GenerateSynthesis.tsx b/frontend/src/pages/GenerateSynthesis.tsx index 6ae1d33..58b020d 100644 --- a/frontend/src/pages/GenerateSynthesis.tsx +++ b/frontend/src/pages/GenerateSynthesis.tsx @@ -87,7 +87,7 @@ const GenerateSynthesis: Component = () => { const modelDisplayName = (): string => { const provider = selectedProvider(); if (!provider) return settings().ai_model; - const model = provider.models.find((m) => m.model_id === settings().ai_model); + const model = provider.models_scraping.find((m) => m.model_id === settings().ai_model); return model?.display_name ?? settings().ai_model; }; diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 9063bc1..8d15a2e 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -31,9 +31,9 @@ import { getProviderInfoKey, providerSupportsWebSearch } from '~/utils/providers * - **Rate limit null handling**: The `rate_limit_max_requests` and * `rate_limit_time_window_seconds` fields accept `null` (meaning "use * server defaults"). Empty inputs are stored as `null`, not zero. - * - **Dual model state**: Research model (`ai_model`) and writing model - * (`ai_model_writing`) are independently selectable from the same - * provider's model list. + * - **Dual model state**: Scraping model (`ai_model`) and websearch model + * (`ai_model_websearch`) are independently selectable from their + * respective provider model lists. */ const Settings: Component = () => { const { t } = useI18n(); @@ -106,7 +106,7 @@ const Settings: Component = () => { setSettings((prev) => ({ ...prev, ai_provider: providerName, - ai_model: provider?.models[0]?.model_id ?? '', + ai_model: provider?.models_scraping[0]?.model_id ?? '', })); }; @@ -117,7 +117,7 @@ const Settings: Component = () => { setSettings((prev) => ({ ...prev, ai_provider: single.provider_name, - ai_model: prev.ai_model || single.models[0]?.model_id || '', + ai_model: prev.ai_model || single.models_scraping[0]?.model_id || '', })); } }); @@ -590,7 +590,7 @@ const Settings: Component = () => { {t('settings.modelPlaceholder')} - + {(model) => (