local-deep-research 0.5.9__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. local_deep_research/__version__.py +1 -1
  2. local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +11 -1
  3. local_deep_research/advanced_search_system/questions/browsecomp_question.py +32 -6
  4. local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +32 -8
  5. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -0
  6. local_deep_research/api/__init__.py +2 -0
  7. local_deep_research/api/research_functions.py +177 -3
  8. local_deep_research/benchmarks/graders.py +150 -5
  9. local_deep_research/benchmarks/models/__init__.py +19 -0
  10. local_deep_research/benchmarks/models/benchmark_models.py +283 -0
  11. local_deep_research/benchmarks/ui/__init__.py +1 -0
  12. local_deep_research/benchmarks/web_api/__init__.py +6 -0
  13. local_deep_research/benchmarks/web_api/benchmark_routes.py +862 -0
  14. local_deep_research/benchmarks/web_api/benchmark_service.py +920 -0
  15. local_deep_research/config/llm_config.py +106 -21
  16. local_deep_research/defaults/default_settings.json +447 -2
  17. local_deep_research/error_handling/report_generator.py +10 -0
  18. local_deep_research/llm/__init__.py +19 -0
  19. local_deep_research/llm/llm_registry.py +155 -0
  20. local_deep_research/metrics/db_models.py +3 -7
  21. local_deep_research/metrics/search_tracker.py +25 -11
  22. local_deep_research/search_system.py +12 -9
  23. local_deep_research/utilities/log_utils.py +23 -10
  24. local_deep_research/utilities/thread_context.py +99 -0
  25. local_deep_research/web/app_factory.py +32 -8
  26. local_deep_research/web/database/benchmark_schema.py +230 -0
  27. local_deep_research/web/database/convert_research_id_to_string.py +161 -0
  28. local_deep_research/web/database/models.py +55 -1
  29. local_deep_research/web/database/schema_upgrade.py +397 -2
  30. local_deep_research/web/database/uuid_migration.py +265 -0
  31. local_deep_research/web/routes/api_routes.py +62 -31
  32. local_deep_research/web/routes/history_routes.py +13 -6
  33. local_deep_research/web/routes/metrics_routes.py +264 -4
  34. local_deep_research/web/routes/research_routes.py +45 -18
  35. local_deep_research/web/routes/route_registry.py +352 -0
  36. local_deep_research/web/routes/settings_routes.py +382 -22
  37. local_deep_research/web/services/research_service.py +22 -29
  38. local_deep_research/web/services/settings_manager.py +53 -0
  39. local_deep_research/web/services/settings_service.py +2 -0
  40. local_deep_research/web/static/css/styles.css +8 -0
  41. local_deep_research/web/static/js/components/detail.js +7 -14
  42. local_deep_research/web/static/js/components/details.js +8 -10
  43. local_deep_research/web/static/js/components/fallback/ui.js +4 -4
  44. local_deep_research/web/static/js/components/history.js +6 -6
  45. local_deep_research/web/static/js/components/logpanel.js +14 -11
  46. local_deep_research/web/static/js/components/progress.js +51 -46
  47. local_deep_research/web/static/js/components/research.js +250 -89
  48. local_deep_research/web/static/js/components/results.js +5 -7
  49. local_deep_research/web/static/js/components/settings.js +32 -26
  50. local_deep_research/web/static/js/components/settings_sync.js +24 -23
  51. local_deep_research/web/static/js/config/urls.js +285 -0
  52. local_deep_research/web/static/js/main.js +8 -8
  53. local_deep_research/web/static/js/research_form.js +267 -12
  54. local_deep_research/web/static/js/services/api.js +18 -18
  55. local_deep_research/web/static/js/services/keyboard.js +8 -8
  56. local_deep_research/web/static/js/services/socket.js +53 -35
  57. local_deep_research/web/static/js/services/ui.js +1 -1
  58. local_deep_research/web/templates/base.html +4 -1
  59. local_deep_research/web/templates/components/custom_dropdown.html +5 -3
  60. local_deep_research/web/templates/components/mobile_nav.html +3 -3
  61. local_deep_research/web/templates/components/sidebar.html +9 -3
  62. local_deep_research/web/templates/pages/benchmark.html +2697 -0
  63. local_deep_research/web/templates/pages/benchmark_results.html +1274 -0
  64. local_deep_research/web/templates/pages/benchmark_simple.html +453 -0
  65. local_deep_research/web/templates/pages/cost_analytics.html +1 -1
  66. local_deep_research/web/templates/pages/metrics.html +212 -39
  67. local_deep_research/web/templates/pages/research.html +8 -6
  68. local_deep_research/web/templates/pages/star_reviews.html +1 -1
  69. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +14 -1
  70. local_deep_research/web_search_engines/engines/search_engine_brave.py +15 -1
  71. local_deep_research/web_search_engines/engines/search_engine_ddg.py +20 -1
  72. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +26 -2
  73. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +15 -1
  74. local_deep_research/web_search_engines/engines/search_engine_retriever.py +192 -0
  75. local_deep_research/web_search_engines/engines/search_engine_tavily.py +307 -0
  76. local_deep_research/web_search_engines/rate_limiting/__init__.py +14 -0
  77. local_deep_research/web_search_engines/rate_limiting/__main__.py +9 -0
  78. local_deep_research/web_search_engines/rate_limiting/cli.py +209 -0
  79. local_deep_research/web_search_engines/rate_limiting/exceptions.py +21 -0
  80. local_deep_research/web_search_engines/rate_limiting/tracker.py +506 -0
  81. local_deep_research/web_search_engines/retriever_registry.py +108 -0
  82. local_deep_research/web_search_engines/search_engine_base.py +161 -43
  83. local_deep_research/web_search_engines/search_engine_factory.py +14 -0
  84. local_deep_research/web_search_engines/search_engines_config.py +20 -0
  85. local_deep_research-0.6.1.dist-info/METADATA +374 -0
  86. {local_deep_research-0.5.9.dist-info → local_deep_research-0.6.1.dist-info}/RECORD +89 -64
  87. local_deep_research-0.5.9.dist-info/METADATA +0 -420
  88. {local_deep_research-0.5.9.dist-info → local_deep_research-0.6.1.dist-info}/WHEEL +0 -0
  89. {local_deep_research-0.5.9.dist-info → local_deep_research-0.6.1.dist-info}/entry_points.txt +0 -0
  90. {local_deep_research-0.5.9.dist-info → local_deep_research-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -139,36 +139,18 @@
139
139
  advancedToggle = document.querySelector('.advanced-options-toggle');
140
140
  advancedPanel = document.querySelector('.advanced-options-panel');
141
141
 
142
- // First, try to load settings from localStorage for immediate display
143
- const lastProvider = localStorage.getItem('lastUsedProvider');
144
- const lastModel = localStorage.getItem('lastUsedModel');
145
- const lastSearchEngine = localStorage.getItem('lastUsedSearchEngine');
146
- const lastStrategy = localStorage.getItem('lastUsedStrategy') || 'source-based';
147
-
148
- console.log('Local storage values:', { provider: lastProvider, model: lastModel, searchEngine: lastSearchEngine, strategy: lastStrategy });
149
-
150
- // Apply local storage values if available
151
- if (lastProvider && modelProviderSelect) {
152
- console.log('Setting provider from localStorage:', lastProvider);
153
- modelProviderSelect.value = lastProvider;
154
- // Show/hide endpoint container as needed
155
- if (endpointContainer) {
156
- endpointContainer.style.display = lastProvider === 'OPENAI_ENDPOINT' ? 'block' : 'none';
157
- }
158
- }
159
-
160
- // Apply saved strategy
161
- const strategySelect = document.getElementById('strategy');
162
- if (strategySelect && lastStrategy) {
163
- console.log('Setting strategy from localStorage:', lastStrategy);
164
- strategySelect.value = lastStrategy;
165
- }
142
+ // Note: Settings are now loaded from the database via the template
143
+ // The form values are already set by the server-side rendering
144
+ // We just need to initialize the UI components
166
145
 
167
146
  // Initialize the UI first (immediate operations)
168
147
  setupEventListeners();
169
148
  populateModelProviders();
170
149
  initializeDropdowns();
171
150
 
151
+ // Also set initial values here for immediate feedback
152
+ setInitialFormValues();
153
+
172
154
  // Auto-focus the query input
173
155
  if (queryInput) {
174
156
  queryInput.focus();
@@ -179,14 +161,17 @@
179
161
  }
180
162
 
181
163
  // Set initial state of the advanced options panel based on localStorage
182
- const savedState = localStorage.getItem('advancedOptionsOpen') === 'true';
164
+ const savedState = localStorage.getItem('advancedMenuOpen') === 'true';
183
165
  if (savedState && advancedPanel) {
184
166
  advancedPanel.style.display = 'block';
185
167
  advancedPanel.classList.add('expanded');
186
168
  if (advancedToggle) {
187
169
  advancedToggle.classList.add('open');
170
+ advancedToggle.setAttribute('aria-expanded', 'true');
188
171
  const icon = advancedToggle.querySelector('i');
189
172
  if (icon) icon.className = 'fas fa-chevron-up';
173
+ const srText = advancedToggle.querySelector('.sr-only');
174
+ if (srText) srText.textContent = 'Click to collapse advanced options';
190
175
  }
191
176
  }
192
177
 
@@ -195,16 +180,13 @@
195
180
  loadModelOptions(false),
196
181
  loadSearchEngineOptions(false)
197
182
  ]).then(([modelData, searchEngineData]) => {
198
- console.log('Data loaded successfully');
199
-
200
183
  // After loading model data, update the UI with the loaded data
201
- const currentProvider = modelProviderSelect ? modelProviderSelect.value : (lastProvider || 'OLLAMA');
184
+ const currentProvider = modelProviderSelect ? modelProviderSelect.value : 'OLLAMA';
202
185
  updateModelOptionsForProvider(currentProvider, false);
203
186
 
204
187
  // Update search engine options
205
188
  if (searchEngineData && Array.isArray(searchEngineData)) {
206
189
  searchEngineOptions = searchEngineData;
207
- console.log('Loaded search engines:', searchEngineData.length);
208
190
 
209
191
  // Force search engine dropdown to update with new data
210
192
  if (searchEngineDropdownList && window.setupCustomDropdown) {
@@ -214,7 +196,6 @@
214
196
  searchEngineDropdownList,
215
197
  () => searchEngineOptions.length > 0 ? searchEngineOptions : [{ value: '', label: 'No search engines available' }],
216
198
  (value, item) => {
217
- console.log('Search engine selected:', value, item);
218
199
  selectedSearchEngineValue = value;
219
200
 
220
201
  // Update the input field
@@ -235,13 +216,11 @@
235
216
 
236
217
  // If we have a last selected search engine, try to select it
237
218
  if (lastSearchEngine) {
238
- console.log('Trying to select last search engine:', lastSearchEngine);
239
219
  // Find the matching engine
240
220
  const matchingEngine = searchEngineOptions.find(engine =>
241
221
  engine.value === lastSearchEngine || engine.id === lastSearchEngine);
242
222
 
243
223
  if (matchingEngine) {
244
- console.log('Found matching search engine:', matchingEngine);
245
224
  searchEngineInput.value = matchingEngine.label;
246
225
  selectedSearchEngineValue = matchingEngine.value;
247
226
 
@@ -255,11 +234,17 @@
255
234
  }
256
235
  }
257
236
 
237
+ // Set initial form values from data attributes
238
+ setInitialFormValues();
239
+
258
240
  // Finally, load settings after data is available
259
241
  loadSettings();
260
242
  }).catch(error => {
261
243
  console.error('Failed to load options:', error);
262
244
 
245
+ // Set initial form values even if data loading fails
246
+ setInitialFormValues();
247
+
263
248
  // Still load settings even if data loading fails
264
249
  loadSettings();
265
250
 
@@ -423,6 +408,65 @@
423
408
  }
424
409
  }
425
410
 
411
+ /**
412
+ * Set initial form values from data attributes
413
+ */
414
+ function setInitialFormValues() {
415
+ console.log('Setting initial form values...');
416
+
417
+ // Set initial model value if available
418
+ if (modelInput) {
419
+ const initialModel = modelInput.getAttribute('data-initial-value');
420
+ console.log('Initial model value from data attribute:', initialModel);
421
+ if (initialModel) {
422
+ // Find the matching model in the options
423
+ const matchingModel = modelOptions.find(m =>
424
+ m.value === initialModel || m.id === initialModel
425
+ );
426
+
427
+ if (matchingModel) {
428
+ modelInput.value = matchingModel.label;
429
+ selectedModelValue = matchingModel.value;
430
+ } else {
431
+ // If not found in options, set it as custom value
432
+ modelInput.value = initialModel;
433
+ selectedModelValue = initialModel;
434
+ }
435
+
436
+ // Update hidden input
437
+ const hiddenInput = document.getElementById('model_hidden');
438
+ if (hiddenInput) {
439
+ hiddenInput.value = selectedModelValue;
440
+ }
441
+ }
442
+ }
443
+
444
+ // Set initial search engine value if available
445
+ if (searchEngineInput) {
446
+ const initialSearchEngine = searchEngineInput.getAttribute('data-initial-value');
447
+ if (initialSearchEngine) {
448
+ // Find the matching search engine in the options
449
+ const matchingEngine = searchEngineOptions.find(e =>
450
+ e.value === initialSearchEngine || e.id === initialSearchEngine
451
+ );
452
+
453
+ if (matchingEngine) {
454
+ searchEngineInput.value = matchingEngine.label;
455
+ selectedSearchEngineValue = matchingEngine.value;
456
+ } else {
457
+ searchEngineInput.value = initialSearchEngine;
458
+ selectedSearchEngineValue = initialSearchEngine;
459
+ }
460
+
461
+ // Update hidden input
462
+ const hiddenInput = document.getElementById('search_engine_hidden');
463
+ if (hiddenInput) {
464
+ hiddenInput.value = selectedSearchEngineValue;
465
+ }
466
+ }
467
+ }
468
+ }
469
+
426
470
  /**
427
471
  * Setup event listeners
428
472
  */
@@ -432,23 +476,19 @@
432
476
  // INITIALIZE ADVANCED OPTIONS FIRST - before any async operations
433
477
  // Advanced options toggle - make immediately responsive
434
478
  if (advancedToggle && advancedPanel) {
435
- // Set initial state based on localStorage, relying only on CSS classes
436
- const savedState = localStorage.getItem('advancedOptionsOpen') === 'true';
479
+ // Set initial state based on localStorage
480
+ const savedState = localStorage.getItem('advancedMenuOpen') === 'true';
437
481
 
438
482
  if (savedState) {
439
483
  advancedToggle.classList.add('open');
440
484
  advancedPanel.classList.add('expanded');
441
-
442
- // Update ARIA attributes
443
485
  advancedToggle.setAttribute('aria-expanded', 'true');
444
486
 
445
- // Update screen reader text
446
487
  const srText = advancedToggle.querySelector('.sr-only');
447
488
  if (srText) {
448
489
  srText.textContent = 'Click to collapse advanced options';
449
490
  }
450
491
 
451
- // Update icon immediately
452
492
  const icon = advancedToggle.querySelector('i');
453
493
  if (icon) {
454
494
  icon.className = 'fas fa-chevron-up';
@@ -456,17 +496,13 @@
456
496
  } else {
457
497
  advancedToggle.classList.remove('open');
458
498
  advancedPanel.classList.remove('expanded');
459
-
460
- // Update ARIA attributes
461
499
  advancedToggle.setAttribute('aria-expanded', 'false');
462
500
 
463
- // Update screen reader text
464
501
  const srText = advancedToggle.querySelector('.sr-only');
465
502
  if (srText) {
466
503
  srText.textContent = 'Click to expand advanced options';
467
504
  }
468
505
 
469
- // Ensure icon is correct
470
506
  const icon = advancedToggle.querySelector('i');
471
507
  if (icon) {
472
508
  icon.className = 'fas fa-chevron-down';
@@ -489,7 +525,7 @@
489
525
  }
490
526
 
491
527
  // Save state to localStorage
492
- localStorage.setItem('advancedOptionsOpen', isOpen);
528
+ localStorage.setItem('advancedMenuOpen', isOpen.toString());
493
529
 
494
530
  // Update icon
495
531
  const icon = this.querySelector('i');
@@ -622,6 +658,33 @@
622
658
  });
623
659
  }
624
660
 
661
+ // Search engine change - save to settings manager
662
+ if (searchEngineInput) {
663
+ searchEngineInput.addEventListener('change', function() {
664
+ const searchEngine = this.value;
665
+ console.log('Search engine changed to:', searchEngine);
666
+ saveSearchSetting('search.tool', searchEngine);
667
+ });
668
+ }
669
+
670
+ // Iterations change - save to settings manager
671
+ if (iterationsInput) {
672
+ iterationsInput.addEventListener('change', function() {
673
+ const iterations = parseInt(this.value);
674
+ console.log('Iterations changed to:', iterations);
675
+ saveSearchSetting('search.iterations', iterations);
676
+ });
677
+ }
678
+
679
+ // Questions per iteration change - save to settings manager
680
+ if (questionsPerIterationInput) {
681
+ questionsPerIterationInput.addEventListener('change', function() {
682
+ const questions = parseInt(this.value);
683
+ console.log('Questions per iteration changed to:', questions);
684
+ saveSearchSetting('search.questions_per_iteration', questions);
685
+ });
686
+ }
687
+
625
688
  // Load options data from APIs
626
689
  Promise.all([
627
690
  loadModelOptions(false),
@@ -678,11 +741,21 @@
678
741
  modelProviderSelect.appendChild(option);
679
742
  });
680
743
 
681
- // Default to Ollama
682
- modelProviderSelect.value = 'OLLAMA';
744
+ // Set initial value from data attribute or default to Ollama
745
+ const initialProvider = modelProviderSelect.getAttribute('data-initial-value') || 'OLLAMA';
746
+ console.log('Initial provider from data attribute:', initialProvider);
747
+ modelProviderSelect.value = initialProvider.toUpperCase();
748
+
749
+ // Show custom endpoint input if OpenAI endpoint is selected
750
+ if (endpointContainer) {
751
+ console.log('Setting endpoint container display for provider:', initialProvider.toUpperCase());
752
+ endpointContainer.style.display = initialProvider.toUpperCase() === 'OPENAI_ENDPOINT' ? 'block' : 'none';
753
+ } else {
754
+ console.warn('Endpoint container not found');
755
+ }
683
756
 
684
757
  // Initial update of model options
685
- updateModelOptionsForProvider('OLLAMA');
758
+ updateModelOptionsForProvider(initialProvider.toUpperCase());
686
759
  }
687
760
 
688
761
  /**
@@ -932,10 +1005,10 @@
932
1005
  console.log(`Updated model options for provider ${provider}: ${modelOptions.length} models`);
933
1006
 
934
1007
  // Check for stored last model before deciding what to select
935
- let lastSelectedModel = localStorage.getItem('lastUsedModel');
1008
+ let lastSelectedModel = null; // Don't use localStorage
936
1009
 
937
1010
  // Also check the database setting
938
- fetch('/research/settings/api/llm.model', {
1011
+ fetch(URLS.SETTINGS_API.LLM_MODEL, {
939
1012
  method: 'GET',
940
1013
  headers: {
941
1014
  'Content-Type': 'application/json'
@@ -1063,7 +1136,7 @@
1063
1136
  const controller = new AbortController();
1064
1137
  const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
1065
1138
 
1066
- const response = await fetch('/research/settings/api/ollama-status', {
1139
+ const response = await fetch(URLS.SETTINGS_API.OLLAMA_STATUS, {
1067
1140
  signal: controller.signal
1068
1141
  });
1069
1142
 
@@ -1144,7 +1217,7 @@
1144
1217
  const controller = new AbortController();
1145
1218
  const timeoutId = setTimeout(() => controller.abort(), 5000);
1146
1219
 
1147
- const response = await fetch(`/research/api/check/ollama_model?model=${encodeURIComponent(model)}`, {
1220
+ const response = await fetch(`/api/check/ollama_model?model=${encodeURIComponent(model)}`, {
1148
1221
  signal: controller.signal
1149
1222
  });
1150
1223
 
@@ -1186,8 +1259,11 @@
1186
1259
  console.log('Loading settings from database...');
1187
1260
  let numApiCallsPending = 1;
1188
1261
 
1262
+ // Increase the API calls counter to include strategy loading
1263
+ numApiCallsPending = 3;
1264
+
1189
1265
  // Fetch the current settings from the settings API
1190
- fetch('/research/settings/api', {
1266
+ fetch(URLS.SETTINGS_API.LLM_CONFIG, {
1191
1267
  method: 'GET',
1192
1268
  headers: {
1193
1269
  'Content-Type': 'application/json'
@@ -1223,7 +1299,7 @@
1223
1299
  console.log('Found matching provider option:', matchingOption.value);
1224
1300
  modelProviderSelect.value = matchingOption.value;
1225
1301
  // Also save to localStorage
1226
- localStorage.setItem('lastUsedProvider', matchingOption.value);
1302
+ // Provider saved to DB: matchingOption.value);
1227
1303
  } else {
1228
1304
  // If no match, try to find case-insensitive or partial match
1229
1305
  const caseInsensitiveMatch = Array.from(modelProviderSelect.options).find(
@@ -1235,7 +1311,7 @@
1235
1311
  console.log('Found case-insensitive provider match:', caseInsensitiveMatch.value);
1236
1312
  modelProviderSelect.value = caseInsensitiveMatch.value;
1237
1313
  // Also save to localStorage
1238
- localStorage.setItem('lastUsedProvider', caseInsensitiveMatch.value);
1314
+ // Provider saved to DB: caseInsensitiveMatch.value);
1239
1315
  } else {
1240
1316
  console.warn(`No matching provider option found for '${providerValue}'`);
1241
1317
  }
@@ -1266,7 +1342,7 @@
1266
1342
  console.log('Setting model to:', modelValue);
1267
1343
 
1268
1344
  // Save to localStorage
1269
- localStorage.setItem('lastUsedModel', modelValue);
1345
+ // Model saved to DB
1270
1346
 
1271
1347
  // Find the model in our loaded options
1272
1348
  const matchingModel = modelOptions.find(m =>
@@ -1308,7 +1384,7 @@
1308
1384
  console.log('Setting search engine to:', engineValue);
1309
1385
 
1310
1386
  // Save to localStorage
1311
- localStorage.setItem('lastUsedSearchEngine', engineValue);
1387
+ // Search engine saved to DB
1312
1388
 
1313
1389
  // Find the engine in our loaded options
1314
1390
  const matchingEngine = searchEngineOptions.find(e =>
@@ -1361,13 +1437,59 @@
1361
1437
  numApiCallsPending--;
1362
1438
  isInitializing = (numApiCallsPending === 0);
1363
1439
  });
1440
+
1441
+ // Load search strategy setting
1442
+ fetch(URLS.SETTINGS_API.SEARCH_TOOL, {
1443
+ method: 'GET',
1444
+ headers: {
1445
+ 'Content-Type': 'application/json'
1446
+ }
1447
+ })
1448
+ .then(response => {
1449
+ if (!response.ok) {
1450
+ throw new Error(`API error: ${response.status}`);
1451
+ }
1452
+ return response.json();
1453
+ })
1454
+ .then(data => {
1455
+ console.log('Loaded strategy from database:', data);
1456
+
1457
+ const strategySelect = document.getElementById('strategy');
1458
+ if (data && data.setting && data.setting.value && strategySelect) {
1459
+ const strategyValue = data.setting.value;
1460
+ console.log('Setting strategy to:', strategyValue);
1461
+
1462
+ // Update the select element
1463
+ strategySelect.value = strategyValue;
1464
+
1465
+ // Save to localStorage
1466
+ // Strategy saved to DB
1467
+ }
1468
+
1469
+ numApiCallsPending--;
1470
+ isInitializing = (numApiCallsPending === 0);
1471
+ })
1472
+ .catch(error => {
1473
+ console.error('Error loading strategy:', error);
1474
+
1475
+ // Fallback to localStorage
1476
+ const lastStrategy = null; // Strategy loaded from DB
1477
+ const strategySelect = document.getElementById('strategy');
1478
+ if (lastStrategy && strategySelect) {
1479
+ strategySelect.value = lastStrategy;
1480
+ }
1481
+
1482
+ numApiCallsPending--;
1483
+ isInitializing = (numApiCallsPending === 0);
1484
+ });
1364
1485
  }
1365
1486
 
1366
1487
  // Add a fallback function to use localStorage settings
1367
1488
  function fallbackToLocalStorageSettings() {
1368
- const provider = localStorage.getItem('lastUsedProvider');
1369
- const model = localStorage.getItem('lastUsedModel');
1370
- const searchEngine = localStorage.getItem('lastUsedSearchEngine');
1489
+ // Settings are loaded from database, not localStorage
1490
+ const provider = null;
1491
+ const model = null;
1492
+ const searchEngine = null;
1371
1493
 
1372
1494
  console.log('Falling back to localStorage settings:', { provider, model, searchEngine });
1373
1495
 
@@ -1440,7 +1562,11 @@
1440
1562
  }
1441
1563
 
1442
1564
  // Fetch from API if cache is invalid or refresh is forced
1443
- fetch('/research/settings/api/available-models')
1565
+ const url = forceRefresh
1566
+ ? `${URLS.SETTINGS_API.AVAILABLE_MODELS}?force_refresh=true`
1567
+ : URLS.SETTINGS_API.AVAILABLE_MODELS;
1568
+
1569
+ fetch(url)
1444
1570
  .then(response => {
1445
1571
  if (!response.ok) {
1446
1572
  throw new Error(`API error: ${response.status}`);
@@ -1578,23 +1704,23 @@
1578
1704
  ];
1579
1705
  }
1580
1706
 
1581
- // Cache and retrieve data in localStorage
1707
+ // In-memory cache to avoid excessive API calls within a session
1708
+ const memoryCache = {};
1709
+ const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
1710
+
1582
1711
  function cacheData(key, data) {
1583
- try {
1584
- localStorage.setItem(key, JSON.stringify(data));
1585
- } catch (e) {
1586
- console.error('Error caching data:', e);
1587
- }
1712
+ memoryCache[key] = {
1713
+ data: data,
1714
+ timestamp: Date.now()
1715
+ };
1588
1716
  }
1589
1717
 
1590
1718
  function getCachedData(key) {
1591
- try {
1592
- const item = localStorage.getItem(key);
1593
- return item ? JSON.parse(item) : null;
1594
- } catch (e) {
1595
- console.error('Error retrieving cached data:', e);
1596
- return null;
1719
+ const cached = memoryCache[key];
1720
+ if (cached && (Date.now() - cached.timestamp < CACHE_DURATION)) {
1721
+ return cached.data;
1597
1722
  }
1723
+ return null;
1598
1724
  }
1599
1725
 
1600
1726
  // Load search engine options
@@ -1622,7 +1748,7 @@
1622
1748
  console.log('Fetching search engines from API...');
1623
1749
 
1624
1750
  // Fetch from API
1625
- fetch('/research/settings/api/available-search-engines')
1751
+ fetch(URLS.SETTINGS_API.AVAILABLE_SEARCH_ENGINES)
1626
1752
  .then(response => {
1627
1753
  if (!response.ok) {
1628
1754
  throw new Error(`API error: ${response.status}`);
@@ -1739,8 +1865,7 @@
1739
1865
 
1740
1866
  // Save model settings to database
1741
1867
  function saveModelSettings(modelValue) {
1742
- // Save selection to localStorage for persistence between sessions
1743
- localStorage.setItem('lastUsedModel', modelValue);
1868
+ // Only save to database, not localStorage
1744
1869
 
1745
1870
  // Update any hidden input with the same settings key that might exist in other forms
1746
1871
  const hiddenInputs = document.querySelectorAll('input[id$="_hidden"][name="llm.model"]');
@@ -1749,7 +1874,7 @@
1749
1874
  });
1750
1875
 
1751
1876
  // Save to the database using the settings API
1752
- fetch('/research/settings/api/llm.model', {
1877
+ fetch(URLBuilder.updateSetting('llm.model'), {
1753
1878
  method: 'PUT',
1754
1879
  headers: {
1755
1880
  'Content-Type': 'application/json',
@@ -1778,8 +1903,7 @@
1778
1903
 
1779
1904
  // Save search engine settings to database
1780
1905
  function saveSearchEngineSettings(engineValue) {
1781
- // Save to localStorage
1782
- localStorage.setItem('lastUsedSearchEngine', engineValue);
1906
+ // Only save to database, not localStorage
1783
1907
 
1784
1908
  // Update any hidden input with the same settings key that might exist in other forms
1785
1909
  const hiddenInputs = document.querySelectorAll('input[id$="_hidden"][name="search.tool"]');
@@ -1788,7 +1912,7 @@
1788
1912
  });
1789
1913
 
1790
1914
  // Save to the database using the settings API
1791
- fetch('/research/settings/api/search.tool', {
1915
+ fetch(URLS.SETTINGS_API.SEARCH_TOOL, {
1792
1916
  method: 'PUT',
1793
1917
  headers: {
1794
1918
  'Content-Type': 'application/json',
@@ -1817,8 +1941,7 @@
1817
1941
 
1818
1942
  // Save provider setting to database
1819
1943
  function saveProviderSetting(providerValue) {
1820
- // Save to localStorage
1821
- localStorage.setItem('lastUsedProvider', providerValue);
1944
+ // Only save to database, not localStorage
1822
1945
 
1823
1946
  // Update any hidden input with the same settings key that might exist in other forms
1824
1947
  const hiddenInputs = document.querySelectorAll('input[id$="_hidden"][name="llm.provider"]');
@@ -1827,7 +1950,7 @@
1827
1950
  });
1828
1951
 
1829
1952
  // Save to the database using the settings API
1830
- fetch('/research/settings/api/llm.provider', {
1953
+ fetch(URLS.SETTINGS_API.LLM_PROVIDER, {
1831
1954
  method: 'PUT',
1832
1955
  headers: {
1833
1956
  'Content-Type': 'application/json',
@@ -1839,6 +1962,14 @@
1839
1962
  .then(data => {
1840
1963
  console.log('Provider setting saved to database:', data);
1841
1964
 
1965
+ // If the response includes warnings, display them directly
1966
+ if (data.warnings && typeof window.displayWarnings === 'function') {
1967
+ window.displayWarnings(data.warnings);
1968
+ } else if (typeof window.refetchSettingsAndUpdateWarnings === 'function') {
1969
+ // Fallback: trigger warning system update
1970
+ window.refetchSettingsAndUpdateWarnings();
1971
+ }
1972
+
1842
1973
  // Optionally show a notification
1843
1974
  if (window.ui && window.ui.showMessage) {
1844
1975
  window.ui.showMessage(`Provider updated to: ${providerValue}`, 'success', 2000);
@@ -1854,6 +1985,41 @@
1854
1985
  });
1855
1986
  }
1856
1987
 
1988
+ // Save search setting to database
1989
+ function saveSearchSetting(settingKey, value) {
1990
+ // Save to the database using the settings API
1991
+ fetch(`/settings/api/${settingKey}`, {
1992
+ method: 'PUT',
1993
+ headers: {
1994
+ 'Content-Type': 'application/json',
1995
+ 'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
1996
+ },
1997
+ body: JSON.stringify({ value: value })
1998
+ })
1999
+ .then(response => response.json())
2000
+ .then(data => {
2001
+ console.log(`Search setting ${settingKey} saved to database:`, data);
2002
+
2003
+ // If the response includes warnings, display them directly
2004
+ if (data.warnings && typeof window.displayWarnings === 'function') {
2005
+ window.displayWarnings(data.warnings);
2006
+ }
2007
+
2008
+ // Optionally show a notification
2009
+ if (window.ui && window.ui.showMessage) {
2010
+ window.ui.showMessage(`${settingKey.split('.').pop()} updated to: ${value}`, 'success', 2000);
2011
+ }
2012
+ })
2013
+ .catch(error => {
2014
+ console.error(`Error saving search setting ${settingKey} to database:`, error);
2015
+
2016
+ // Show error notification if available
2017
+ if (window.ui && window.ui.showMessage) {
2018
+ window.ui.showMessage(`Error updating ${settingKey}: ${error.message}`, 'error', 3000);
2019
+ }
2020
+ });
2021
+ }
2022
+
1857
2023
  // Research form submission handler
1858
2024
  function handleResearchSubmit(event) {
1859
2025
  event.preventDefault();
@@ -1918,7 +2084,7 @@
1918
2084
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
1919
2085
 
1920
2086
  // Submit the form data to the backend
1921
- fetch('/research/api/start_research', {
2087
+ fetch(URLS.API.START_RESEARCH, {
1922
2088
  method: 'POST',
1923
2089
  headers: {
1924
2090
  'Content-Type': 'application/json',
@@ -1932,15 +2098,10 @@
1932
2098
  console.log('Research started successfully:', data);
1933
2099
 
1934
2100
  // Store research preferences in localStorage
1935
- localStorage.setItem('lastResearchMode', mode);
1936
- localStorage.setItem('lastModelProvider', modelProvider);
1937
- localStorage.setItem('lastModel', model);
1938
- localStorage.setItem('lastSearchEngine', searchEngine);
1939
- localStorage.setItem('enableNotifications', enableNotifications);
1940
- localStorage.setItem('lastUsedStrategy', strategy);
2101
+ // Settings are saved to database via the API, not localStorage
1941
2102
 
1942
2103
  // Redirect to the progress page
1943
- window.location.href = `/research/progress/${data.research_id}`;
2104
+ window.location.href = URLBuilder.progressPage(data.research_id);
1944
2105
  } else {
1945
2106
  // Show error message
1946
2107
  showAlert(data.message || 'Failed to start research.', 'error');
@@ -55,7 +55,7 @@
55
55
  const metricsBtn = document.getElementById('view-metrics-btn');
56
56
  if (metricsBtn) {
57
57
  metricsBtn.addEventListener('click', () => {
58
- window.location.href = `/research/details/${researchId}`;
58
+ window.location.href = URLBuilder.detailsPage(researchId);
59
59
  });
60
60
  }
61
61
 
@@ -73,20 +73,18 @@
73
73
  const backBtn = document.getElementById('back-to-history');
74
74
  if (backBtn) {
75
75
  backBtn.addEventListener('click', () => {
76
- window.location.href = '/research/history';
76
+ window.location.href = URLS.PAGES.HISTORY;
77
77
  });
78
78
  }
79
79
 
80
80
  }
81
81
 
82
82
  /**
83
- * Get research ID from URL
83
+ * Get research ID from URL using centralized URL system
84
84
  * @returns {string|null} Research ID
85
85
  */
86
86
  function getResearchIdFromUrl() {
87
- const path = window.location.pathname;
88
- const match = path.match(/\/research\/results\/(\d+)/);
89
- return match ? match[1] : null;
87
+ return URLBuilder.extractResearchIdFromPattern('results');
90
88
  }
91
89
 
92
90
  /**
@@ -98,7 +96,7 @@
98
96
  resultsContainer.innerHTML = '<div class="text-center my-5"><i class="fas fa-spinner fa-pulse"></i><p class="mt-3">Loading research results...</p></div>';
99
97
 
100
98
  // Fetch result from API
101
- const response = await fetch(`/research/api/history/report/${researchId}`);
99
+ const response = await fetch(`/api/report/${researchId}`);
102
100
 
103
101
  if (!response.ok) {
104
102
  throw new Error(`HTTP error ${response.status}`);