feat: add source_extraction_window setting (default 3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
oabrivard 3 months ago
parent 94a200a2ab
commit 0f1b0306e4

@ -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

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN source_extraction_window INTEGER NOT NULL DEFAULT 3;

@ -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<SettingsRow> 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?;

@ -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(),

@ -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(),

@ -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": "",

@ -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": "",

@ -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);

@ -81,6 +81,29 @@ const SettingsAdvanced: Component<SettingsAdvancedProps> = (props) => {
/>
</div>
</div>
<div>
<label for="sourceExtractionWindow" class="block text-sm font-medium text-gray-700">
{t('settings.sourceExtractionWindow')}
</label>
<p class="text-xs text-gray-500 mb-1">{t('settings.sourceExtractionWindowHelp')}</p>
<div class="mt-1">
<input
type="number"
id="sourceExtractionWindow"
min="1"
max="10"
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"
value={props.settings().source_extraction_window}
onInput={(e) =>
props.setSettings((prev) => ({
...prev,
source_extraction_window: parseInt(e.currentTarget.value) || 3,
}))
}
/>
</div>
</div>
</div>
{/* Advanced extraction */}

@ -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',

@ -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: '',

Loading…
Cancel
Save