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
@@ -172,12 +172,12 @@
172
172
  function cacheData(key, data) {
173
173
  try {
174
174
  // Store the data
175
- localStorage.setItem(key, JSON.stringify(data));
175
+ // Don't cache - always fetch fresh from API
176
176
 
177
177
  // Update or set the timestamp
178
178
  let timestamps;
179
179
  try {
180
- timestamps = JSON.parse(localStorage.getItem(CACHE_KEYS.CACHE_TIMESTAMP) || '{}');
180
+ timestamps = {}; // No cache timestamps
181
181
  // Ensure timestamps is an object, not a number or other type
182
182
  if (typeof timestamps !== 'object' || timestamps === null) {
183
183
  timestamps = {};
@@ -188,7 +188,7 @@
188
188
  }
189
189
 
190
190
  timestamps[key] = Date.now();
191
- localStorage.setItem(CACHE_KEYS.CACHE_TIMESTAMP, JSON.stringify(timestamps));
191
+ // Don't save cache timestamps
192
192
 
193
193
  console.log(`Cached data for ${key}`);
194
194
  } catch (error) {
@@ -206,7 +206,7 @@
206
206
  // Get timestamps
207
207
  let timestamps;
208
208
  try {
209
- timestamps = JSON.parse(localStorage.getItem(CACHE_KEYS.CACHE_TIMESTAMP) || '{}');
209
+ timestamps = {}; // No cache timestamps
210
210
  // Ensure timestamps is an object, not a number or other type
211
211
  if (typeof timestamps !== 'object' || timestamps === null) {
212
212
  timestamps = {};
@@ -221,7 +221,7 @@
221
221
  // Check if data exists and is not expired
222
222
  if (timestamp && (Date.now() - timestamp < CACHE_EXPIRATION)) {
223
223
  try {
224
- const data = JSON.parse(localStorage.getItem(key));
224
+ const data = null; // No cached data
225
225
  return data;
226
226
  } catch (e) {
227
227
  console.error('Error parsing cached data:', e);
@@ -997,7 +997,7 @@
997
997
  // Only run this for the main settings dashboard
998
998
  if (!settingsContent) return;
999
999
 
1000
- fetch('/research/settings/api')
1000
+ fetch(URLS.SETTINGS_API.BASE)
1001
1001
  .then(response => response.json())
1002
1002
  .then(data => {
1003
1003
  if (data.status === 'success') {
@@ -1054,6 +1054,7 @@
1054
1054
  if (category === 'report_parameters') return 'Report Parameters';
1055
1055
  if (category === 'search_general') return 'Search General';
1056
1056
  if (category === 'search_parameters') return 'Search Parameters';
1057
+ if (category === 'warnings') return 'Warnings';
1057
1058
 
1058
1059
  // Remove any underscores and capitalize each word
1059
1060
  let formattedCategory = category.replace(/_/g, ' ');
@@ -1128,13 +1129,14 @@
1128
1129
  'enable_web',
1129
1130
  'dark_mode',
1130
1131
  'default_theme',
1131
- 'theme'
1132
+ 'theme',
1133
+ 'warnings'
1132
1134
  ]
1133
1135
  };
1134
1136
 
1135
1137
  // Priority settings that should appear at the top of each tab
1136
1138
  const prioritySettings = {
1137
- 'app': ['enable_web', 'enable_notifications', 'web_interface', 'theme', 'default_theme', 'dark_mode', 'debug', 'host', 'port'],
1139
+ 'app': ['enable_web', 'enable_notifications', 'web_interface', 'theme', 'default_theme', 'dark_mode', 'debug', 'host', 'port', 'warnings'],
1138
1140
  'llm': ['provider', 'model', 'temperature', 'max_tokens', 'api_key', 'openai_endpoint_url', 'lmstudio_url', 'llamacpp_model_path'],
1139
1141
  'search': ['tool', 'iterations', 'questions_per_iteration', 'max_results', 'region', 'search_engine'],
1140
1142
  'report': ['enable_fact_checking', 'knowledge_accumulation', 'output_dir', 'detailed_citations']
@@ -2009,10 +2011,10 @@
2009
2011
  });
2010
2012
 
2011
2013
  // --- ADD THIS LINE ---
2012
- console.log('[submitSettingsData] Preparing to fetch /research/settings/save_all_settings with data:', JSON.stringify(formData));
2014
+ console.log('[submitSettingsData] Preparing to fetch /settings/save_all_settings with data:', JSON.stringify(formData));
2013
2015
  // --- END ADD ---
2014
2016
 
2015
- fetch('/research/settings/save_all_settings', {
2017
+ fetch(URLS.SETTINGS_API.SAVE_ALL_SETTINGS, {
2016
2018
  method: 'POST',
2017
2019
  headers: {
2018
2020
  'Content-Type': 'application/json',
@@ -2363,7 +2365,7 @@
2363
2365
  // Show confirmation dialog
2364
2366
  if (confirm('Are you sure you want to reset ALL settings to their default values? This cannot be undone.')) {
2365
2367
  // Call the reset to defaults API
2366
- fetch('/research/settings/reset_to_defaults', {
2368
+ fetch(URLS.SETTINGS_API.RESET_TO_DEFAULTS, {
2367
2369
  method: 'POST',
2368
2370
  headers: {
2369
2371
  'Content-Type': 'application/json',
@@ -2512,7 +2514,7 @@
2512
2514
  // Create a hidden form and submit it to a route that will open the file location
2513
2515
  const form = document.createElement('form');
2514
2516
  form.method = 'POST';
2515
- form.action = "/research/open_file_location";
2517
+ form.action = "/api/open_file_location";
2516
2518
 
2517
2519
  const input = document.createElement('input');
2518
2520
  input.type = 'hidden';
@@ -2563,7 +2565,7 @@
2563
2565
  */
2564
2566
  function handleFixCorruptedSettings() {
2565
2567
  // Call the fix corrupted settings API
2566
- fetch('/research/settings/fix_corrupted_settings', {
2568
+ fetch(URLS.SETTINGS_API.FIX_CORRUPTED_SETTINGS, {
2567
2569
  method: 'POST',
2568
2570
  headers: {
2569
2571
  'Content-Type': 'application/json',
@@ -2601,7 +2603,7 @@
2601
2603
  const controller = new AbortController();
2602
2604
  const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
2603
2605
 
2604
- const response = await fetch('/research/settings/api/ollama-status', {
2606
+ const response = await fetch(URLS.SETTINGS_API.OLLAMA_STATUS, {
2605
2607
  signal: controller.signal
2606
2608
  });
2607
2609
 
@@ -2642,7 +2644,11 @@
2642
2644
  console.log('Fetching model providers from API');
2643
2645
 
2644
2646
  // Create a promise and store it
2645
- window.modelProvidersRequestInProgress = fetch('/research/settings/api/available-models')
2647
+ const url = forceRefresh
2648
+ ? `${URLS.SETTINGS_API.AVAILABLE_MODELS}?force_refresh=true`
2649
+ : URLS.SETTINGS_API.AVAILABLE_MODELS;
2650
+
2651
+ window.modelProvidersRequestInProgress = fetch(url)
2646
2652
  .then(response => {
2647
2653
  if (!response.ok) {
2648
2654
  throw new Error(`API returned status: ${response.status}`);
@@ -2695,7 +2701,7 @@
2695
2701
  console.log('Fetching search engines from API');
2696
2702
 
2697
2703
  // Create a promise and store it
2698
- window.searchEnginesRequestInProgress = fetch('/research/settings/api/available-search-engines')
2704
+ window.searchEnginesRequestInProgress = fetch(URLS.SETTINGS_API.AVAILABLE_SEARCH_ENGINES)
2699
2705
  .then(response => {
2700
2706
  if (!response.ok) {
2701
2707
  throw new Error(`API returned status: ${response.status}`);
@@ -2915,7 +2921,7 @@
2915
2921
  filterModelOptionsForProvider(value);
2916
2922
 
2917
2923
  // Save to localStorage
2918
- localStorage.setItem('lastUsedProvider', value);
2924
+ // Provider saved to DB
2919
2925
 
2920
2926
  // Trigger save
2921
2927
  const changeEvent = new Event('change', { bubbles: true });
@@ -2961,7 +2967,7 @@
2961
2967
  modelHiddenInput.value = value;
2962
2968
 
2963
2969
  // Save to localStorage
2964
- localStorage.setItem('lastUsedModel', value);
2970
+ // Model saved to DB
2965
2971
  }
2966
2972
  },
2967
2973
  true // Allow custom values
@@ -3100,7 +3106,7 @@
3100
3106
  const changeEvent = new Event('change', { bubbles: true });
3101
3107
  searchEngineHiddenInput.dispatchEvent(changeEvent);
3102
3108
  // Save to localStorage
3103
- localStorage.setItem('lastUsedSearchEngine', value);
3109
+ // Search engine saved to DB
3104
3110
  },
3105
3111
  false, // Don't allow custom values
3106
3112
  'No search engines available.'
@@ -3115,7 +3121,7 @@
3115
3121
  }
3116
3122
  }
3117
3123
  if (!currentValue) {
3118
- currentValue = localStorage.getItem('lastUsedSearchEngine') || 'auto';
3124
+ currentValue = 'auto'; // Default value, actual value comes from DB
3119
3125
  }
3120
3126
 
3121
3127
  // Set initial value
@@ -3388,7 +3394,7 @@
3388
3394
  const logoLink = document.getElementById('logo-link');
3389
3395
  if (logoLink) {
3390
3396
  logoLink.addEventListener('click', () => {
3391
- window.location.href = '/research/';
3397
+ window.location.href = URLS.PAGES.HOME;
3392
3398
  });
3393
3399
  }
3394
3400
 
@@ -3507,11 +3513,11 @@
3507
3513
  // Fallback to localStorage values if we don't have a value yet
3508
3514
  if (!currentValue) {
3509
3515
  if (settingKey === 'llm.model') {
3510
- currentValue = localStorage.getItem('lastUsedModel') || '';
3516
+ currentValue = ''; // Value comes from DB
3511
3517
  } else if (settingKey === 'llm.provider') {
3512
- currentValue = localStorage.getItem('lastUsedProvider') || '';
3518
+ currentValue = ''; // Value comes from DB
3513
3519
  } else if (settingKey === 'search.tool') {
3514
- currentValue = localStorage.getItem('lastUsedSearchEngine') || '';
3520
+ currentValue = ''; // Value comes from DB
3515
3521
  }
3516
3522
  }
3517
3523
 
@@ -3623,11 +3629,11 @@
3623
3629
 
3624
3630
  // Save to localStorage for persistence
3625
3631
  if (settingKey === 'llm.model') {
3626
- localStorage.setItem('lastUsedModel', value);
3632
+ // Model saved to DB
3627
3633
  } else if (settingKey === 'llm.provider') {
3628
3634
  localStorage.setItem('lastUsedProvider', value);
3629
3635
  } else if (settingKey === 'search.tool') {
3630
- localStorage.setItem('lastUsedSearchEngine', value);
3636
+ // Search engine saved to DB
3631
3637
  }
3632
3638
  },
3633
3639
  allowCustom
@@ -1,45 +1,46 @@
1
- // Function to save settings using the settings page endpoint
1
+ // Function to save settings using the individual settings manager API
2
2
  function saveMenuSettings(settingKey, settingValue) {
3
- // Create payload with the single setting being changed
4
- const payload = {};
5
- payload[settingKey] = settingValue;
6
-
7
3
  console.log('Saving setting:', settingKey, '=', settingValue);
8
4
 
9
5
  // Get CSRF token
10
6
  const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
11
7
 
12
- // Use fetch to send to the same endpoint as the settings page
13
- fetch('/research/settings/save_all_settings', {
14
- method: 'POST',
8
+ // Use the individual settings API endpoint that uses the settings manager
9
+ fetch(`/settings/api/${settingKey}`, {
10
+ method: 'PUT',
15
11
  headers: {
16
12
  'Content-Type': 'application/json',
17
13
  'X-CSRFToken': csrfToken
18
14
  },
19
- body: JSON.stringify(payload)
15
+ body: JSON.stringify({ value: settingValue })
20
16
  })
21
17
  .then(response => response.json())
22
18
  .then(data => {
23
- if (data.status === 'success') {
24
- // Show success notification if UI module is available
25
- if (window.ui && window.ui.showMessage) {
26
- window.ui.showMessage('Setting saved successfully', 'success');
27
- } else {
28
- console.log('Setting saved successfully:', data);
19
+ console.log(`Setting ${settingKey} saved via settings manager:`, data);
20
+
21
+ // If the response includes warnings, display them directly
22
+ if (data.warnings && typeof window.displayWarnings === 'function') {
23
+ window.displayWarnings(data.warnings);
24
+ }
25
+
26
+ // Also trigger client-side warning recalculation for search settings
27
+ if (settingKey.startsWith('search.') || settingKey === 'llm.provider') {
28
+ if (typeof window.refetchSettingsAndUpdateWarnings === 'function') {
29
+ window.refetchSettingsAndUpdateWarnings();
29
30
  }
31
+ }
32
+
33
+ // Show success notification if UI module is available
34
+ if (window.ui && window.ui.showMessage) {
35
+ window.ui.showMessage(`${settingKey.split('.').pop()} updated successfully`, 'success');
30
36
  } else {
31
- // Show error notification
32
- if (window.ui && window.ui.showMessage) {
33
- window.ui.showMessage('Failed to save setting: ' + (data.message || 'Unknown error'), 'error');
34
- } else {
35
- console.error('Failed to save setting:', data);
36
- }
37
+ console.log('Setting saved successfully:', data);
37
38
  }
38
39
  })
39
40
  .catch(error => {
40
- console.error('Error saving setting:', error);
41
+ console.error(`Error saving setting ${settingKey}:`, error);
41
42
  if (window.ui && window.ui.showMessage) {
42
- window.ui.showMessage('Error saving setting', 'error');
43
+ window.ui.showMessage(`Error updating ${settingKey}: ${error.message}`, 'error');
43
44
  }
44
45
  });
45
46
  }
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Centralized URL configuration for the Local Deep Research application
3
+ * This prevents hardcoded URLs scattered throughout the codebase
4
+ */
5
+
6
+ const URLS = {
7
+ // API endpoints
8
+ API: {
9
+ START_RESEARCH: '/api/start_research',
10
+ RESEARCH_STATUS: '/api/research/{id}/status',
11
+ RESEARCH_DETAILS: '/api/research/{id}',
12
+ RESEARCH_LOGS: '/api/research/{id}/logs',
13
+ RESEARCH_REPORT: '/api/report/{id}',
14
+ TERMINATE_RESEARCH: '/api/terminate/{id}',
15
+ DELETE_RESEARCH: '/api/delete/{id}',
16
+ CLEAR_HISTORY: '/api/clear_history',
17
+ SAVE_RAW_CONFIG: '/api/save_raw_config',
18
+ SAVE_MAIN_CONFIG: '/api/save_main_config',
19
+ SAVE_SEARCH_ENGINES_CONFIG: '/api/save_search_engines_config',
20
+ SAVE_COLLECTIONS_CONFIG: '/api/save_collections_config',
21
+ SAVE_API_KEYS_CONFIG: '/api/save_api_keys_config',
22
+ SAVE_LLM_CONFIG: '/api/save_llm_config',
23
+ MARKDOWN_EXPORT: '/api/markdown/{id}',
24
+ HISTORY: '/history/api', // Changed to match backend route
25
+ HEALTH: '/api/v1/health' // Added health check endpoint
26
+ },
27
+
28
+ // Page routes
29
+ PAGES: {
30
+ HOME: '/',
31
+ PROGRESS: '/progress/{id}',
32
+ RESULTS: '/results/{id}',
33
+ DETAILS: '/details/{id}',
34
+ HISTORY: '/history/',
35
+ SETTINGS: '/settings/',
36
+ METRICS: '/metrics/',
37
+ METRICS_COSTS: '/metrics/costs', // Added metrics subpage
38
+ METRICS_STAR_REVIEWS: '/metrics/star-reviews' // Added metrics subpage
39
+ },
40
+
41
+ // History API routes
42
+ HISTORY_API: {
43
+ STATUS: '/history/status/{id}',
44
+ DETAILS: '/history/details/{id}',
45
+ LOGS: '/history/logs/{id}',
46
+ REPORT: '/history/history/report/{id}',
47
+ MARKDOWN: '/history/markdown/{id}',
48
+ LOG_COUNT: '/history/log_count/{id}'
49
+ },
50
+
51
+ // Settings API routes
52
+ SETTINGS_API: {
53
+ BASE: '/settings/api',
54
+ GET_SETTING: '/settings/api/{key}',
55
+ UPDATE_SETTING: '/settings/api/{key}',
56
+ DELETE_SETTING: '/settings/api/{key}',
57
+ IMPORT_SETTINGS: '/settings/api/import',
58
+ CATEGORIES: '/settings/api/categories',
59
+ TYPES: '/settings/api/types',
60
+ UI_ELEMENTS: '/settings/api/ui_elements',
61
+ AVAILABLE_MODELS: '/settings/api/available-models',
62
+ AVAILABLE_SEARCH_ENGINES: '/settings/api/available-search-engines',
63
+ WARNINGS: '/settings/api/warnings',
64
+ OLLAMA_STATUS: '/settings/api/ollama-status',
65
+ LLM_MODEL: '/settings/api/llm.model',
66
+ LLM_PROVIDER: '/settings/api/llm.provider',
67
+ LLM_CONFIG: '/settings/api/llm',
68
+ SEARCH_TOOL: '/settings/api/search.tool',
69
+ SAVE_ALL_SETTINGS: '/settings/save_all_settings',
70
+ RESET_TO_DEFAULTS: '/settings/reset_to_defaults',
71
+ FIX_CORRUPTED_SETTINGS: '/settings/fix_corrupted_settings'
72
+ },
73
+
74
+ // Metrics API routes
75
+ METRICS_API: {
76
+ BASE: '/metrics/api/metrics',
77
+ RESEARCH: '/metrics/api/metrics/research/{id}',
78
+ RESEARCH_TIMELINE: '/metrics/api/metrics/research/{id}/timeline',
79
+ RESEARCH_SEARCH: '/metrics/api/metrics/research/{id}/search',
80
+ COST_ANALYTICS: '/metrics/api/cost-analytics',
81
+ PRICING: '/metrics/api/pricing',
82
+ RATINGS_GET: '/metrics/api/ratings/{id}',
83
+ RATINGS_SAVE: '/metrics/api/ratings/{id}',
84
+ RESEARCH_COSTS: '/metrics/api/research-costs/{id}'
85
+ }
86
+ };
87
+
88
+ /**
89
+ * URL builder utility functions
90
+ */
91
+ const URLBuilder = {
92
+ /**
93
+ * Build a URL by replacing {id} placeholders with actual values
94
+ * @param {string} urlTemplate - URL template with {id} placeholders
95
+ * @param {string|number} id - The ID to substitute
96
+ * @returns {string} - The built URL
97
+ */
98
+ build(urlTemplate, id) {
99
+ return urlTemplate.replace('{id}', id);
100
+ },
101
+
102
+ /**
103
+ * Build a URL with custom replacements
104
+ * @param {string} urlTemplate - URL template with placeholders
105
+ * @param {Object} replacements - Object with key-value pairs for replacement
106
+ * @returns {string} - The built URL
107
+ */
108
+ buildWithReplacements(urlTemplate, replacements) {
109
+ let url = urlTemplate;
110
+ for (const [key, value] of Object.entries(replacements)) {
111
+ url = url.replace(`{${key}}`, value);
112
+ }
113
+ return url;
114
+ },
115
+
116
+ // Convenience methods for common URL patterns
117
+ progressPage(researchId) {
118
+ return this.build(URLS.PAGES.PROGRESS, researchId);
119
+ },
120
+
121
+ resultsPage(researchId) {
122
+ return this.build(URLS.PAGES.RESULTS, researchId);
123
+ },
124
+
125
+ detailsPage(researchId) {
126
+ return this.build(URLS.PAGES.DETAILS, researchId);
127
+ },
128
+
129
+ researchStatus(researchId) {
130
+ return this.build(URLS.API.RESEARCH_STATUS, researchId);
131
+ },
132
+
133
+ researchDetails(researchId) {
134
+ return this.build(URLS.API.RESEARCH_DETAILS, researchId);
135
+ },
136
+
137
+ researchLogs(researchId) {
138
+ return this.build(URLS.API.RESEARCH_LOGS, researchId);
139
+ },
140
+
141
+ researchReport(researchId) {
142
+ return this.build(URLS.API.RESEARCH_REPORT, researchId);
143
+ },
144
+
145
+ terminateResearch(researchId) {
146
+ return this.build(URLS.API.TERMINATE_RESEARCH, researchId);
147
+ },
148
+
149
+ deleteResearch(researchId) {
150
+ return this.build(URLS.API.DELETE_RESEARCH, researchId);
151
+ },
152
+
153
+ // History API convenience methods
154
+ historyStatus(researchId) {
155
+ return this.build(URLS.HISTORY_API.STATUS, researchId);
156
+ },
157
+
158
+ historyDetails(researchId) {
159
+ return this.build(URLS.HISTORY_API.DETAILS, researchId);
160
+ },
161
+
162
+ historyLogs(researchId) {
163
+ return this.build(URLS.HISTORY_API.LOGS, researchId);
164
+ },
165
+
166
+ markdownExport(researchId) {
167
+ return this.build(URLS.API.MARKDOWN_EXPORT, researchId);
168
+ },
169
+
170
+ historyReport(researchId) {
171
+ return this.build(URLS.HISTORY_API.REPORT, researchId);
172
+ },
173
+
174
+ // URL Pattern Extraction Methods
175
+ /**
176
+ * Extract research ID from current URL path
177
+ * @returns {string|null} Research ID or null if not found
178
+ */
179
+ extractResearchId() {
180
+ const path = window.location.pathname;
181
+
182
+ // Try different URL patterns
183
+ const patterns = [
184
+ /\/results\/(\d+)/, // /results/123
185
+ /\/details\/(\d+)/, // /details/123
186
+ /\/progress\/(\d+)/ // /progress/123
187
+ ];
188
+
189
+ for (const pattern of patterns) {
190
+ const match = path.match(pattern);
191
+ if (match) {
192
+ return match[1];
193
+ }
194
+ }
195
+
196
+ return null;
197
+ },
198
+
199
+ /**
200
+ * Extract research ID from a specific URL pattern
201
+ * @param {string} pattern - The pattern to match ('results', 'details', 'progress')
202
+ * @returns {string|null} Research ID or null if not found
203
+ */
204
+ extractResearchIdFromPattern(pattern) {
205
+ const path = window.location.pathname;
206
+ const regex = new RegExp(`\\/${pattern}\\/(\\d+)`);
207
+ const match = path.match(regex);
208
+ return match ? match[1] : null;
209
+ },
210
+
211
+ /**
212
+ * Get current page type based on URL
213
+ * @returns {string} Page type ('home', 'results', 'details', 'progress', 'history', 'settings', 'metrics', 'unknown')
214
+ */
215
+ getCurrentPageType() {
216
+ const path = window.location.pathname;
217
+
218
+ if (path === '/' || path === '/index' || path === '/home') return 'home';
219
+ if (path.startsWith('/results/')) return 'results';
220
+ if (path.startsWith('/details/')) return 'details';
221
+ if (path.startsWith('/progress/')) return 'progress';
222
+ if (path.startsWith('/history')) return 'history';
223
+ if (path.startsWith('/settings')) return 'settings';
224
+ if (path.startsWith('/metrics')) return 'metrics';
225
+
226
+ return 'unknown';
227
+ },
228
+
229
+ historyMarkdown(researchId) {
230
+ return this.build(URLS.HISTORY_API.MARKDOWN, researchId);
231
+ },
232
+
233
+ historyLogCount(researchId) {
234
+ return this.build(URLS.HISTORY_API.LOG_COUNT, researchId);
235
+ },
236
+
237
+ // Settings API convenience methods
238
+ getSetting(key) {
239
+ return this.buildWithReplacements(URLS.SETTINGS_API.GET_SETTING, { key });
240
+ },
241
+
242
+ updateSetting(key) {
243
+ return this.buildWithReplacements(URLS.SETTINGS_API.UPDATE_SETTING, { key });
244
+ },
245
+
246
+ deleteSetting(key) {
247
+ return this.buildWithReplacements(URLS.SETTINGS_API.DELETE_SETTING, { key });
248
+ },
249
+
250
+ // Metrics API convenience methods
251
+ researchMetrics(researchId) {
252
+ return this.build(URLS.METRICS_API.RESEARCH, researchId);
253
+ },
254
+
255
+ researchTimelineMetrics(researchId) {
256
+ return this.build(URLS.METRICS_API.RESEARCH_TIMELINE, researchId);
257
+ },
258
+
259
+ researchSearchMetrics(researchId) {
260
+ return this.build(URLS.METRICS_API.RESEARCH_SEARCH, researchId);
261
+ },
262
+
263
+ getRating(researchId) {
264
+ return this.build(URLS.METRICS_API.RATINGS_GET, researchId);
265
+ },
266
+
267
+ saveRating(researchId) {
268
+ return this.build(URLS.METRICS_API.RATINGS_SAVE, researchId);
269
+ },
270
+
271
+ researchCosts(researchId) {
272
+ return this.build(URLS.METRICS_API.RESEARCH_COSTS, researchId);
273
+ }
274
+ };
275
+
276
+ // Export for module systems first (before window assignment)
277
+ if (typeof module !== 'undefined' && module.exports) {
278
+ module.exports = { URLS, URLBuilder };
279
+ }
280
+
281
+ // Make URLs and URLBuilder available globally
282
+ if (typeof window !== 'undefined') {
283
+ window.URLS = URLS;
284
+ window.URLBuilder = URLBuilder;
285
+ }
@@ -75,7 +75,7 @@
75
75
  function loadAudioServiceFirst(callback) {
76
76
  console.log('Loading audio service script first...');
77
77
  const audioScript = document.createElement('script');
78
- audioScript.src = `/research/static/js/services/audio.js?t=${new Date().getTime()}`; // Add timestamp to avoid cache
78
+ audioScript.src = `/static/js/services/audio.js?t=${new Date().getTime()}`; // Add timestamp to avoid cache
79
79
  audioScript.async = false;
80
80
 
81
81
  // Set up callback for when script loads
@@ -122,17 +122,17 @@
122
122
  // Check URL patterns as fallback
123
123
  const path = window.location.pathname;
124
124
 
125
- if (path === '/' || path === '/index' || path === '/home' || path === '/research/') {
125
+ if (path === '/' || path === '/index' || path === '/home') {
126
126
  return 'page-home';
127
- } else if (path.includes('/research/progress')) {
127
+ } else if (path.includes('/progress')) {
128
128
  return 'page-progress';
129
- } else if (path.includes('/research/results')) {
129
+ } else if (path.includes('/results')) {
130
130
  return 'page-results';
131
- } else if (path.includes('/research/detail')) {
131
+ } else if (path.includes('/details')) {
132
132
  return 'page-detail';
133
- } else if (path.includes('/research/history')) {
133
+ } else if (path.includes('/history')) {
134
134
  return 'page-history';
135
- } else if (path.includes('/research/settings')) {
135
+ } else if (path.includes('/settings')) {
136
136
  return 'page-settings';
137
137
  }
138
138
 
@@ -149,7 +149,7 @@
149
149
 
150
150
  scripts.forEach(script => {
151
151
  const scriptElement = document.createElement('script');
152
- scriptElement.src = `/research/static/js/${folder}/${script}`;
152
+ scriptElement.src = `/static/js/${folder}/${script}`;
153
153
  scriptElement.async = false; // Load in sequence
154
154
  document.body.appendChild(scriptElement);
155
155
  });