From 0f1b0306e4ba70f10dccb282edacfc6281bdd878 Mon Sep 17 00:00:00 2001 From: oabrivard Date: Thu, 26 Mar 2026 16:47:11 +0100 Subject: [PATCH] feat: add source_extraction_window setting (default 3) Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- ...326000025_add_source_extraction_window.sql | 1 + backend/src/db/settings.rs | 17 +++++++++----- backend/src/models/settings.rs | 7 ++++++ backend/src/services/prompts.rs | 1 + backend/tests/api_syntheses_test.rs | 1 + backend/tests/pipeline_test.rs | 1 + e2e/tests/generation-live.spec.ts | 1 + .../components/settings/SettingsAdvanced.tsx | 23 +++++++++++++++++++ frontend/src/i18n/fr.ts | 2 ++ frontend/src/types.ts | 2 ++ 11 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 backend/migrations/20260326000025_add_source_extraction_window.sql diff --git a/CLAUDE.md b/CLAUDE.md index 2d6ac1e..01ac14a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,7 +117,7 @@ cd frontend && npx tsc --noEmit - `GET /api/v1/admin/users` — user list - `PUT /api/v1/admin/users/:id/role` — role management -## Database (24 migrations) +## Database (25 migrations) Tables: `users`, `sessions`, `magic_link_tokens`, `user_settings`, `sources`, `syntheses`, `admin_providers`, `admin_rate_limits`, `user_api_keys`, `audit_log` ## Environment Variables diff --git a/backend/migrations/20260326000025_add_source_extraction_window.sql b/backend/migrations/20260326000025_add_source_extraction_window.sql new file mode 100644 index 0000000..cc2f1a9 --- /dev/null +++ b/backend/migrations/20260326000025_add_source_extraction_window.sql @@ -0,0 +1 @@ +ALTER TABLE settings ADD COLUMN source_extraction_window INTEGER NOT NULL DEFAULT 3; diff --git a/backend/src/db/settings.rs b/backend/src/db/settings.rs index a704a3d..db3efee 100644 --- a/backend/src/db/settings.rs +++ b/backend/src/db/settings.rs @@ -23,6 +23,7 @@ struct SettingsRow { article_history_days: i32, batch_size: i32, summary_length: i32, + source_extraction_window: i32, search_agent_behavior: String, ai_provider: String, ai_model: String, @@ -52,6 +53,7 @@ impl TryFrom for UserSettings { article_history_days: row.article_history_days, batch_size: row.batch_size, summary_length: row.summary_length, + source_extraction_window: row.source_extraction_window, search_agent_behavior: row.search_agent_behavior, ai_provider: row.ai_provider, ai_model: row.ai_model, @@ -78,10 +80,10 @@ pub async fn get_or_create_default( let row = sqlx::query_as::<_, SettingsRow>( r#" - INSERT INTO settings (user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) + INSERT INTO settings (user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length, source_extraction_window) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (user_id) DO UPDATE SET user_id = settings.user_id - RETURNING user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length, updated_at + RETURNING user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length, source_extraction_window, updated_at "#, ) .bind(user_id) @@ -101,6 +103,7 @@ pub async fn get_or_create_default( .bind(defaults.article_history_days) .bind(defaults.batch_size) .bind(defaults.summary_length) + .bind(defaults.source_extraction_window) .fetch_one(pool) .await?; @@ -119,8 +122,8 @@ pub async fn upsert( let row = sqlx::query_as::<_, SettingsRow>( r#" - INSERT INTO settings (user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) + INSERT INTO settings (user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length, source_extraction_window) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (user_id) DO UPDATE SET theme = EXCLUDED.theme, max_age_days = EXCLUDED.max_age_days, @@ -138,8 +141,9 @@ pub async fn upsert( article_history_days = EXCLUDED.article_history_days, batch_size = EXCLUDED.batch_size, summary_length = EXCLUDED.summary_length, + source_extraction_window = EXCLUDED.source_extraction_window, updated_at = now() - RETURNING user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length, updated_at + RETURNING user_id, theme, max_age_days, categories, max_items_per_category, search_agent_behavior, ai_provider, ai_model, ai_model_websearch, rate_limit_max_requests, rate_limit_time_window_seconds, max_articles_per_source, use_llm_for_source_links, use_brave_search, article_history_days, batch_size, summary_length, source_extraction_window, updated_at "#, ) .bind(user_id) @@ -159,6 +163,7 @@ pub async fn upsert( .bind(req.article_history_days) .bind(req.batch_size) .bind(req.summary_length) + .bind(req.source_extraction_window) .fetch_one(pool) .await?; diff --git a/backend/src/models/settings.rs b/backend/src/models/settings.rs index a5955fc..a27d328 100644 --- a/backend/src/models/settings.rs +++ b/backend/src/models/settings.rs @@ -19,6 +19,7 @@ pub struct UserSettings { pub article_history_days: i32, pub batch_size: i32, pub summary_length: i32, + pub source_extraction_window: i32, pub search_agent_behavior: String, pub ai_provider: String, pub ai_model: String, @@ -42,6 +43,7 @@ pub struct UpdateSettingsRequest { pub article_history_days: i32, pub batch_size: i32, pub summary_length: i32, + pub source_extraction_window: i32, pub search_agent_behavior: String, pub ai_provider: String, pub ai_model: String, @@ -97,6 +99,9 @@ impl UpdateSettingsRequest { if !(1..=3).contains(&self.summary_length) { return Err("summary_length must be between 1 and 3".into()); } + if !(1..=10).contains(&self.source_extraction_window) { + return Err("source_extraction_window must be between 1 and 10".into()); + } if self.search_agent_behavior.len() > 2000 { return Err("search_agent_behavior must be at most 2000 characters".into()); } @@ -144,6 +149,7 @@ impl Default for UserSettings { article_history_days: 90, batch_size: 5, summary_length: 3, + source_extraction_window: 3, search_agent_behavior: String::new(), ai_provider: String::new(), ai_model: String::new(), @@ -172,6 +178,7 @@ mod tests { article_history_days: 90, batch_size: 5, summary_length: 3, + source_extraction_window: 3, search_agent_behavior: String::new(), ai_provider: String::new(), ai_model: String::new(), diff --git a/backend/src/services/prompts.rs b/backend/src/services/prompts.rs index 648d35e..b047460 100644 --- a/backend/src/services/prompts.rs +++ b/backend/src/services/prompts.rs @@ -212,6 +212,7 @@ mod tests { article_history_days: 90, batch_size: 5, summary_length: 3, + source_extraction_window: 3, search_agent_behavior: String::new(), ai_provider: String::new(), ai_model: String::new(), diff --git a/backend/tests/api_syntheses_test.rs b/backend/tests/api_syntheses_test.rs index 2f3bda7..6955a2f 100644 --- a/backend/tests/api_syntheses_test.rs +++ b/backend/tests/api_syntheses_test.rs @@ -636,6 +636,7 @@ async fn generate_pipeline_resolves_model_from_admin_config() { "article_history_days": 90, "batch_size": 5, "summary_length": 3, + "source_extraction_window": 3, "search_agent_behavior": "", "ai_provider": "openai", "ai_model": "", diff --git a/backend/tests/pipeline_test.rs b/backend/tests/pipeline_test.rs index b4ab149..4233c80 100644 --- a/backend/tests/pipeline_test.rs +++ b/backend/tests/pipeline_test.rs @@ -62,6 +62,7 @@ async fn setup_user_with_settings( "article_history_days": 90, "batch_size": 5, "summary_length": 3, + "source_extraction_window": 3, "search_agent_behavior": "", "ai_provider": "", "ai_model": "", diff --git a/e2e/tests/generation-live.spec.ts b/e2e/tests/generation-live.spec.ts index 69a9bbf..ff9a72a 100644 --- a/e2e/tests/generation-live.spec.ts +++ b/e2e/tests/generation-live.spec.ts @@ -146,6 +146,7 @@ test.describe('Live generation with OpenAI', () => { article_history_days: 90, batch_size: 5, summary_length: 3, + source_extraction_window: 3, }); expect(settingsResp.status).toBe(200); diff --git a/frontend/src/components/settings/SettingsAdvanced.tsx b/frontend/src/components/settings/SettingsAdvanced.tsx index bfc9813..11ecd0a 100644 --- a/frontend/src/components/settings/SettingsAdvanced.tsx +++ b/frontend/src/components/settings/SettingsAdvanced.tsx @@ -81,6 +81,29 @@ const SettingsAdvanced: Component = (props) => { /> + +
+ +

{t('settings.sourceExtractionWindowHelp')}

+
+ + props.setSettings((prev) => ({ + ...prev, + source_extraction_window: parseInt(e.currentTarget.value) || 3, + })) + } + /> +
+
{/* Advanced extraction */} diff --git a/frontend/src/i18n/fr.ts b/frontend/src/i18n/fr.ts index f345afc..02737f7 100644 --- a/frontend/src/i18n/fr.ts +++ b/frontend/src/i18n/fr.ts @@ -161,6 +161,8 @@ const fr = { 'settings.articleHistoryDays': 'Historique des articles (jours)', 'settings.batchSize': 'Taille du lot de traitement', 'settings.batchSizeHelp': 'Nombre d\'articles traites en parallele lors de la generation (defaut: 5).', + 'settings.sourceExtractionWindow': 'Sources par vague d\'extraction', + 'settings.sourceExtractionWindowHelp': 'Nombre de sources analysees en parallele a chaque vague. Reduit le nombre d\'appels IA quand peu de sources suffisent.', 'settings.summaryLength': 'Niveau de detail des resumes', 'settings.summaryLengthHelp': 'Controle la longueur des resumes generes pour chaque article.', 'settings.summaryShort': 'Court', diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d9be0e7..6386e7d 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -49,6 +49,7 @@ export interface UserSettings { article_history_days: number; batch_size: number; summary_length: number; + source_extraction_window: number; search_agent_behavior: string; ai_model: string; ai_model_websearch: string; @@ -68,6 +69,7 @@ export const DEFAULT_SETTINGS: UserSettings = { article_history_days: 90, batch_size: 5, summary_length: 3, + source_extraction_window: 3, search_agent_behavior: '', ai_model: '', ai_model_websearch: '',