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
@@ -1,10 +1,12 @@
1
1
  /**
2
- * Research form handling with settings management
2
+ * Research form handling with settings management and warnings
3
3
  */
4
4
 
5
+ // Global settings cache for warning logic
6
+ let globalSettings = {};
7
+
5
8
  document.addEventListener('DOMContentLoaded', function() {
6
9
  // Initialize the research form
7
- console.log('DOM loaded, initializing research form');
8
10
  initResearchForm();
9
11
  });
10
12
 
@@ -12,13 +14,12 @@ document.addEventListener('DOMContentLoaded', function() {
12
14
  * Initialize the research form with values from settings
13
15
  */
14
16
  function initResearchForm() {
15
- console.log('Initializing research form...');
16
17
  // Get form elements
17
18
  const iterationsInput = document.getElementById('iterations');
18
19
  const questionsInput = document.getElementById('questions_per_iteration');
19
20
 
20
21
  // Fetch all settings at once (more efficient)
21
- fetch('/research/settings/api')
22
+ fetch(URLS.SETTINGS_API.BASE)
22
23
  .then(response => {
23
24
  if (!response.ok) {
24
25
  throw new Error('Failed to fetch settings');
@@ -26,28 +27,30 @@ function initResearchForm() {
26
27
  return response.json();
27
28
  })
28
29
  .then(data => {
29
- console.log('Loaded settings:', data);
30
30
  if (data && data.status === 'success' && data.settings) {
31
31
  // Find our specific settings
32
32
  const settings = data.settings;
33
33
 
34
+ // Cache settings globally for warning logic
35
+ globalSettings = settings;
36
+
34
37
  // Look for the iterations setting
35
38
  for (const key in settings) {
36
39
  const setting = settings[key];
37
40
  if (key === 'search.iterations') {
38
- console.log('Found iterations setting:', setting.value);
39
41
  iterationsInput.value = setting.value;
40
42
  }
41
43
 
42
44
  if (key === 'search.questions_per_iteration') {
43
- console.log('Found questions setting:', setting.value);
44
45
  questionsInput.value = setting.value;
45
46
  }
46
47
  }
48
+
49
+ // Initialize warnings after settings are loaded
50
+ initializeWarnings();
47
51
  }
48
52
  })
49
53
  .catch(error => {
50
- console.warn('Error loading research settings:', error);
51
54
  // Form will use default values if settings can't be loaded
52
55
  });
53
56
 
@@ -79,13 +82,12 @@ function saveResearchSettings() {
79
82
  const iterations = document.getElementById('iterations').value;
80
83
  const questions = document.getElementById('questions_per_iteration').value;
81
84
 
82
- console.log('Saving research settings:', { iterations, questions });
83
85
 
84
86
  // Get CSRF token
85
87
  const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
86
88
 
87
89
  // Save settings
88
- fetch('/research/settings/save_all_settings', {
90
+ fetch(URLS.SETTINGS_API.SAVE_ALL_SETTINGS, {
89
91
  method: 'POST',
90
92
  headers: {
91
93
  'Content-Type': 'application/json',
@@ -98,9 +100,262 @@ function saveResearchSettings() {
98
100
  })
99
101
  .then(response => response.json())
100
102
  .then(data => {
101
- console.log('Settings saved:', data);
102
103
  })
103
104
  .catch(error => {
104
- console.warn('Error saving research settings:', error);
105
105
  });
106
106
  }
107
+
108
+ /**
109
+ * Initialize warning system
110
+ */
111
+ function initializeWarnings() {
112
+
113
+ // Check warnings on form load
114
+ checkAndDisplayWarnings();
115
+
116
+ // Monitor form changes for dynamic warnings
117
+ setupWarningListeners();
118
+
119
+ // Clear any stale warnings immediately when initializing
120
+ setTimeout(() => {
121
+ checkAndDisplayWarnings();
122
+ }, 100);
123
+ }
124
+
125
+ /**
126
+ * Setup event listeners for settings changes
127
+ */
128
+ function setupWarningListeners() {
129
+ // Monitor provider changes directly and refetch settings
130
+ const providerSelect = document.getElementById('model_provider');
131
+ if (providerSelect) {
132
+ providerSelect.addEventListener('change', function() {
133
+ // Wait a bit longer for the saveProviderSetting API call to complete
134
+ setTimeout(refetchSettingsAndUpdateWarnings, 500);
135
+ });
136
+ }
137
+
138
+ // Hook into the existing saveProviderSetting function if it exists
139
+ // This will trigger when the research.js calls saveProviderSetting
140
+ if (typeof window.saveProviderSetting === 'function') {
141
+ const originalSaveProviderSetting = window.saveProviderSetting;
142
+ window.saveProviderSetting = function(providerValue) {
143
+ // Call the original function
144
+ const result = originalSaveProviderSetting.apply(this, arguments);
145
+ // After it completes, refetch settings and update warnings
146
+ setTimeout(refetchSettingsAndUpdateWarnings, 200);
147
+ return result;
148
+ };
149
+ }
150
+
151
+ // Monitor search engine changes
152
+ const searchEngineInput = document.getElementById('search_engine');
153
+ if (searchEngineInput) {
154
+ searchEngineInput.addEventListener('change', function() {
155
+ // Refresh warnings immediately when search engine changes
156
+ setTimeout(checkAndDisplayWarnings, 100);
157
+ });
158
+ }
159
+
160
+ const strategySelect = document.getElementById('strategy');
161
+ if (strategySelect) {
162
+ strategySelect.addEventListener('change', function() {
163
+
164
+ // Save strategy to localStorage
165
+ // Strategy saved to database via API
166
+
167
+ // Save strategy to database
168
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
169
+ fetch('/research/settings/api/search.search_strategy', {
170
+ method: 'PUT',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ 'X-CSRFToken': csrfToken
174
+ },
175
+ body: JSON.stringify({ value: strategySelect.value })
176
+ })
177
+ .then(response => response.json())
178
+ .then(data => {
179
+ })
180
+ .catch(error => {
181
+ });
182
+
183
+ setTimeout(checkAndDisplayWarnings, 100);
184
+ });
185
+ }
186
+
187
+ // Use Socket.IO to listen for settings changes if available (backup)
188
+ if (typeof io !== 'undefined') {
189
+ const socket = io();
190
+ socket.on('settings_changed', function(data) {
191
+ // Update global settings cache
192
+ if (data.settings) {
193
+ Object.assign(globalSettings, data.settings);
194
+ }
195
+ // Recheck warnings with new settings
196
+ setTimeout(checkAndDisplayWarnings, 100);
197
+ });
198
+
199
+ socket.on('connect', function() {
200
+ });
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Refetch settings from the server and update warnings
206
+ */
207
+ function refetchSettingsAndUpdateWarnings() {
208
+
209
+ fetch(URLS.SETTINGS_API.BASE)
210
+ .then(response => {
211
+ if (!response.ok) {
212
+ throw new Error('Failed to fetch settings');
213
+ }
214
+ return response.json();
215
+ })
216
+ .then(data => {
217
+ if (data && data.status === 'success' && data.settings) {
218
+ // Update global settings cache
219
+ globalSettings = data.settings;
220
+ }
221
+ // Recheck warnings from backend (not from cached settings)
222
+ setTimeout(checkAndDisplayWarnings, 100);
223
+ })
224
+ .catch(error => {
225
+ // Still try to check warnings from backend
226
+ setTimeout(checkAndDisplayWarnings, 100);
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Manually clear all warnings (useful for debugging stale warnings)
232
+ */
233
+ function clearAllWarnings() {
234
+ displayWarnings([]);
235
+ }
236
+
237
+ // Make functions globally available for other scripts
238
+ window.refetchSettingsAndUpdateWarnings = refetchSettingsAndUpdateWarnings;
239
+ window.displayWarnings = displayWarnings;
240
+ window.clearAllWarnings = clearAllWarnings;
241
+ window.checkAndDisplayWarnings = checkAndDisplayWarnings;
242
+
243
+ /**
244
+ * Check warning conditions by fetching from backend
245
+ */
246
+ function checkAndDisplayWarnings() {
247
+
248
+ // Get warnings from backend API instead of calculating locally
249
+ fetch(URLS.SETTINGS_API.WARNINGS)
250
+ .then(response => {
251
+ if (!response.ok) {
252
+ throw new Error('Failed to fetch warnings');
253
+ }
254
+ return response.json();
255
+ })
256
+ .then(data => {
257
+ if (data && data.warnings) {
258
+ displayWarnings(data.warnings);
259
+ } else {
260
+ displayWarnings([]);
261
+ }
262
+ })
263
+ .catch(error => {
264
+ // Clear warnings on error
265
+ displayWarnings([]);
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Display warnings in the alert container
271
+ */
272
+ function displayWarnings(warnings) {
273
+ const alertContainer = document.getElementById('research-alert');
274
+ if (!alertContainer) return;
275
+
276
+ if (warnings.length === 0) {
277
+ alertContainer.style.display = 'none';
278
+ alertContainer.innerHTML = '';
279
+ return;
280
+ }
281
+
282
+ const warningsHtml = warnings.map(warning => {
283
+ // Use green styling for recommendations/tips
284
+ const isRecommendation = warning.type === 'searxng_recommendation';
285
+ const bgColor = isRecommendation ? '#d4edda' : '#fff3cd';
286
+ const borderColor = isRecommendation ? '#c3e6cb' : '#ffeaa7';
287
+ const textColor = isRecommendation ? '#155724' : '#856404';
288
+
289
+ return `
290
+ <div class="warning-banner warning-${warning.type}" style="
291
+ background: ${bgColor};
292
+ border: 1px solid ${borderColor};
293
+ border-radius: 6px;
294
+ padding: 12px 16px;
295
+ margin-bottom: 8px;
296
+ display: flex;
297
+ align-items: flex-start;
298
+ gap: 12px;
299
+ ">
300
+ <span style="font-size: 16px; flex-shrink: 0;">${warning.icon}</span>
301
+ <div style="flex: 1;">
302
+ <div style="font-weight: 600; color: ${textColor}; margin-bottom: 4px;">
303
+ ${warning.title}
304
+ </div>
305
+ <div style="color: ${textColor}; font-size: 14px; line-height: 1.4;">
306
+ ${warning.message}
307
+ </div>
308
+ </div>
309
+ <button onclick="dismissWarning('${warning.dismissKey}')" style="
310
+ background: none;
311
+ border: none;
312
+ color: ${textColor};
313
+ cursor: pointer;
314
+ padding: 4px;
315
+ font-size: 16px;
316
+ flex-shrink: 0;
317
+ ">&times;</button>
318
+ </div>
319
+ `;
320
+ }).join('');
321
+
322
+ alertContainer.innerHTML = warningsHtml;
323
+ alertContainer.style.display = 'block';
324
+ }
325
+
326
+ /**
327
+ * Dismiss a warning by updating the setting
328
+ */
329
+ function dismissWarning(dismissKey) {
330
+
331
+ // Get CSRF token
332
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
333
+
334
+ // Update dismissal setting
335
+ fetch(URLS.SETTINGS_API.SAVE_ALL_SETTINGS, {
336
+ method: 'POST',
337
+ headers: {
338
+ 'Content-Type': 'application/json',
339
+ 'X-CSRFToken': csrfToken
340
+ },
341
+ body: JSON.stringify({
342
+ [dismissKey]: true
343
+ })
344
+ })
345
+ .then(response => response.json())
346
+ .then(data => {
347
+ // Update global settings cache
348
+ globalSettings[dismissKey] = { value: true };
349
+ // Recheck warnings
350
+ checkAndDisplayWarnings();
351
+ })
352
+ .catch(error => {
353
+ });
354
+ }
355
+
356
+ /**
357
+ * Helper function to get settings
358
+ */
359
+ function getSetting(key, defaultValue) {
360
+ return globalSettings[key] ? globalSettings[key].value : defaultValue;
361
+ }
@@ -5,7 +5,7 @@
5
5
 
6
6
  // Base URL for API - use existing one if already declared
7
7
  if (typeof API_BASE_URL === 'undefined') {
8
- const API_BASE_URL = '/research/api';
8
+ const API_BASE_URL = '/api';
9
9
  }
10
10
 
11
11
  /**
@@ -78,7 +78,7 @@ async function postJSON(path, data) {
78
78
  * @returns {Promise<Object>} The research response with ID
79
79
  */
80
80
  async function startResearch(query, mode) {
81
- return postJSON('/research/api/start_research', { query, mode });
81
+ return postJSON(URLS.API.START_RESEARCH, { query, mode });
82
82
  }
83
83
 
84
84
  /**
@@ -87,7 +87,7 @@ async function startResearch(query, mode) {
87
87
  * @returns {Promise<Object>} The research status
88
88
  */
89
89
  async function getResearchStatus(researchId) {
90
- return fetchWithErrorHandling(`/research/api/status/${researchId}`);
90
+ return fetchWithErrorHandling(URLBuilder.researchStatus(researchId));
91
91
  }
92
92
 
93
93
  /**
@@ -96,7 +96,7 @@ async function getResearchStatus(researchId) {
96
96
  * @returns {Promise<Object>} The research details
97
97
  */
98
98
  async function getResearchDetails(researchId) {
99
- return fetchWithErrorHandling(`/research/api/details/${researchId}`);
99
+ return fetchWithErrorHandling(URLBuilder.researchDetails(researchId));
100
100
  }
101
101
 
102
102
  /**
@@ -105,7 +105,7 @@ async function getResearchDetails(researchId) {
105
105
  * @returns {Promise<Object>} The research logs
106
106
  */
107
107
  async function getResearchLogs(researchId) {
108
- return fetchWithErrorHandling(`/research/api/logs/${researchId}`);
108
+ return fetchWithErrorHandling(URLBuilder.researchLogs(researchId));
109
109
  }
110
110
 
111
111
  /**
@@ -113,7 +113,7 @@ async function getResearchLogs(researchId) {
113
113
  * @returns {Promise<Array>} The research history
114
114
  */
115
115
  async function getResearchHistory() {
116
- return fetchWithErrorHandling('/research/api/history');
116
+ return fetchWithErrorHandling(URLS.API.HISTORY);
117
117
  }
118
118
 
119
119
  /**
@@ -122,7 +122,7 @@ async function getResearchHistory() {
122
122
  * @returns {Promise<Object>} The research report
123
123
  */
124
124
  async function getReport(researchId) {
125
- return fetchWithErrorHandling(`/research/api/report/${researchId}`);
125
+ return fetchWithErrorHandling(URLBuilder.researchReport(researchId));
126
126
  }
127
127
 
128
128
  /**
@@ -131,7 +131,7 @@ async function getReport(researchId) {
131
131
  * @returns {Promise<Object>} The termination response
132
132
  */
133
133
  async function terminateResearch(researchId) {
134
- return postJSON(`/research/api/terminate/${researchId}`, {});
134
+ return postJSON(URLBuilder.terminateResearch(researchId), {});
135
135
  }
136
136
 
137
137
  /**
@@ -141,7 +141,7 @@ async function terminateResearch(researchId) {
141
141
  */
142
142
  async function deleteResearch(researchId) {
143
143
  const csrfToken = getCsrfToken();
144
- return fetchWithErrorHandling(`/research/api/delete/${researchId}`, {
144
+ return fetchWithErrorHandling(URLBuilder.deleteResearch(researchId), {
145
145
  method: 'DELETE',
146
146
  headers: {
147
147
  'X-CSRFToken': csrfToken
@@ -154,7 +154,7 @@ async function deleteResearch(researchId) {
154
154
  * @returns {Promise<Object>} The response
155
155
  */
156
156
  async function clearResearchHistory() {
157
- return postJSON('/research/api/clear_history', {});
157
+ return postJSON(URLS.API.CLEAR_HISTORY, {});
158
158
  }
159
159
 
160
160
  /**
@@ -163,7 +163,7 @@ async function clearResearchHistory() {
163
163
  * @returns {Promise<Object>} The response
164
164
  */
165
165
  async function openFileLocation(path) {
166
- return postJSON('/research/open_file_location', { path });
166
+ return postJSON('/api/open_file_location', { path });
167
167
  }
168
168
 
169
169
  /**
@@ -172,7 +172,7 @@ async function openFileLocation(path) {
172
172
  * @returns {Promise<Object>} The response
173
173
  */
174
174
  async function saveRawConfig(rawConfig) {
175
- return postJSON('/research/api/save_raw_config', { raw_config: rawConfig });
175
+ return postJSON(URLS.API.SAVE_RAW_CONFIG, { raw_config: rawConfig });
176
176
  }
177
177
 
178
178
  /**
@@ -181,7 +181,7 @@ async function saveRawConfig(rawConfig) {
181
181
  * @returns {Promise<Object>} The response
182
182
  */
183
183
  async function saveMainConfig(config) {
184
- return postJSON('/research/api/save_main_config', config);
184
+ return postJSON(URLS.API.SAVE_MAIN_CONFIG, config);
185
185
  }
186
186
 
187
187
  /**
@@ -190,7 +190,7 @@ async function saveMainConfig(config) {
190
190
  * @returns {Promise<Object>} The response
191
191
  */
192
192
  async function saveSearchEnginesConfig(config) {
193
- return postJSON('/research/api/save_search_engines_config', config);
193
+ return postJSON(URLS.API.SAVE_SEARCH_ENGINES_CONFIG, config);
194
194
  }
195
195
 
196
196
  /**
@@ -199,7 +199,7 @@ async function saveSearchEnginesConfig(config) {
199
199
  * @returns {Promise<Object>} The response
200
200
  */
201
201
  async function saveCollectionsConfig(config) {
202
- return postJSON('/research/api/save_collections_config', config);
202
+ return postJSON(URLS.API.SAVE_COLLECTIONS_CONFIG, config);
203
203
  }
204
204
 
205
205
  /**
@@ -208,7 +208,7 @@ async function saveCollectionsConfig(config) {
208
208
  * @returns {Promise<Object>} The response
209
209
  */
210
210
  async function saveApiKeysConfig(config) {
211
- return postJSON('/research/api/save_api_keys_config', config);
211
+ return postJSON(URLS.API.SAVE_API_KEYS_CONFIG, config);
212
212
  }
213
213
 
214
214
  /**
@@ -217,7 +217,7 @@ async function saveApiKeysConfig(config) {
217
217
  * @returns {Promise<Object>} The response
218
218
  */
219
219
  async function saveLlmConfig(config) {
220
- return postJSON('/research/api/save_llm_config', config);
220
+ return postJSON(URLS.API.SAVE_LLM_CONFIG, config);
221
221
  }
222
222
 
223
223
  /**
@@ -226,7 +226,7 @@ async function saveLlmConfig(config) {
226
226
  * @returns {Promise<Object>} The markdown content
227
227
  */
228
228
  async function getMarkdownExport(researchId) {
229
- return fetchWithErrorHandling(`/research/api/markdown/${researchId}`);
229
+ return fetchWithErrorHandling(URLBuilder.markdownExport(researchId));
230
230
  }
231
231
 
232
232
  // Export the API functions
@@ -12,35 +12,35 @@
12
12
  description: 'Return to main search',
13
13
  handler: () => {
14
14
  // Always navigate to main research page
15
- window.location.href = '/';
15
+ window.location.href = URLS.PAGES.HOME;
16
16
  }
17
17
  },
18
18
  'navNewResearch': {
19
19
  keys: ['ctrl+shift+1'],
20
20
  description: 'Go to New Research',
21
21
  handler: () => {
22
- window.location.href = '/';
22
+ window.location.href = URLS.PAGES.HOME;
23
23
  }
24
24
  },
25
25
  'navHistory': {
26
26
  keys: ['ctrl+shift+2'],
27
27
  description: 'Go to History',
28
28
  handler: () => {
29
- window.location.href = '/research/history';
29
+ window.location.href = URLS.PAGES.HISTORY;
30
30
  }
31
31
  },
32
32
  'navMetrics': {
33
33
  keys: ['ctrl+shift+3'],
34
34
  description: 'Go to Metrics',
35
35
  handler: () => {
36
- window.location.href = '/metrics';
36
+ window.location.href = URLS.PAGES.METRICS;
37
37
  }
38
38
  },
39
39
  'navSettings': {
40
40
  keys: ['ctrl+shift+4'],
41
41
  description: 'Go to Settings',
42
42
  handler: () => {
43
- window.location.href = '/research/settings';
43
+ window.location.href = URLS.PAGES.SETTINGS;
44
44
  }
45
45
  }
46
46
  };
@@ -153,7 +153,7 @@
153
153
  const allShortcuts = { ...shortcuts };
154
154
 
155
155
  // Add page-specific shortcuts
156
- if (currentPath.includes('/research/progress/')) {
156
+ if (currentPath.includes('/progress/')) {
157
157
  allShortcuts.viewResults = {
158
158
  keys: ['enter'],
159
159
  description: 'View results (when complete)',
@@ -166,11 +166,11 @@
166
166
  };
167
167
  }
168
168
 
169
- if (currentPath.includes('/research/results/')) {
169
+ if (currentPath.includes('/results/')) {
170
170
  allShortcuts.escape = {
171
171
  keys: ['escape'],
172
172
  description: 'Back to new search',
173
- handler: () => window.location.href = '/'
173
+ handler: () => window.location.href = URLS.PAGES.HOME
174
174
  };
175
175
  }
176
176