local-deep-research 0.3.12__py3-none-any.whl → 0.4.0__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 (92) hide show
  1. local_deep_research/__version__.py +1 -1
  2. local_deep_research/advanced_search_system/filters/base_filter.py +2 -3
  3. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +4 -5
  4. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +298 -0
  5. local_deep_research/advanced_search_system/findings/repository.py +0 -3
  6. local_deep_research/advanced_search_system/strategies/base_strategy.py +1 -2
  7. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +14 -18
  8. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +4 -8
  9. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +5 -6
  10. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -2
  11. local_deep_research/advanced_search_system/strategies/standard_strategy.py +9 -7
  12. local_deep_research/api/benchmark_functions.py +288 -0
  13. local_deep_research/api/research_functions.py +8 -4
  14. local_deep_research/benchmarks/README.md +162 -0
  15. local_deep_research/benchmarks/__init__.py +51 -0
  16. local_deep_research/benchmarks/benchmark_functions.py +353 -0
  17. local_deep_research/benchmarks/cli/__init__.py +16 -0
  18. local_deep_research/benchmarks/cli/benchmark_commands.py +338 -0
  19. local_deep_research/benchmarks/cli.py +347 -0
  20. local_deep_research/benchmarks/comparison/__init__.py +12 -0
  21. local_deep_research/benchmarks/comparison/evaluator.py +768 -0
  22. local_deep_research/benchmarks/datasets/__init__.py +53 -0
  23. local_deep_research/benchmarks/datasets/base.py +295 -0
  24. local_deep_research/benchmarks/datasets/browsecomp.py +116 -0
  25. local_deep_research/benchmarks/datasets/custom_dataset_template.py +98 -0
  26. local_deep_research/benchmarks/datasets/simpleqa.py +74 -0
  27. local_deep_research/benchmarks/datasets/utils.py +116 -0
  28. local_deep_research/benchmarks/datasets.py +31 -0
  29. local_deep_research/benchmarks/efficiency/__init__.py +14 -0
  30. local_deep_research/benchmarks/efficiency/resource_monitor.py +367 -0
  31. local_deep_research/benchmarks/efficiency/speed_profiler.py +214 -0
  32. local_deep_research/benchmarks/evaluators/__init__.py +18 -0
  33. local_deep_research/benchmarks/evaluators/base.py +74 -0
  34. local_deep_research/benchmarks/evaluators/browsecomp.py +83 -0
  35. local_deep_research/benchmarks/evaluators/composite.py +121 -0
  36. local_deep_research/benchmarks/evaluators/simpleqa.py +271 -0
  37. local_deep_research/benchmarks/graders.py +410 -0
  38. local_deep_research/benchmarks/metrics/README.md +80 -0
  39. local_deep_research/benchmarks/metrics/__init__.py +24 -0
  40. local_deep_research/benchmarks/metrics/calculation.py +385 -0
  41. local_deep_research/benchmarks/metrics/reporting.py +155 -0
  42. local_deep_research/benchmarks/metrics/visualization.py +205 -0
  43. local_deep_research/benchmarks/metrics.py +11 -0
  44. local_deep_research/benchmarks/optimization/__init__.py +32 -0
  45. local_deep_research/benchmarks/optimization/api.py +274 -0
  46. local_deep_research/benchmarks/optimization/metrics.py +20 -0
  47. local_deep_research/benchmarks/optimization/optuna_optimizer.py +1163 -0
  48. local_deep_research/benchmarks/runners.py +434 -0
  49. local_deep_research/benchmarks/templates.py +65 -0
  50. local_deep_research/config/llm_config.py +26 -23
  51. local_deep_research/config/search_config.py +1 -5
  52. local_deep_research/defaults/default_settings.json +108 -7
  53. local_deep_research/search_system.py +16 -8
  54. local_deep_research/utilities/db_utils.py +3 -6
  55. local_deep_research/utilities/es_utils.py +441 -0
  56. local_deep_research/utilities/log_utils.py +36 -0
  57. local_deep_research/utilities/search_utilities.py +8 -9
  58. local_deep_research/web/app.py +7 -9
  59. local_deep_research/web/app_factory.py +9 -12
  60. local_deep_research/web/database/migrations.py +8 -5
  61. local_deep_research/web/database/models.py +20 -0
  62. local_deep_research/web/database/schema_upgrade.py +5 -8
  63. local_deep_research/web/models/database.py +15 -18
  64. local_deep_research/web/routes/benchmark_routes.py +427 -0
  65. local_deep_research/web/routes/research_routes.py +13 -17
  66. local_deep_research/web/routes/settings_routes.py +264 -67
  67. local_deep_research/web/services/research_service.py +47 -57
  68. local_deep_research/web/services/settings_manager.py +1 -4
  69. local_deep_research/web/services/settings_service.py +4 -6
  70. local_deep_research/web/static/css/styles.css +12 -0
  71. local_deep_research/web/static/js/components/logpanel.js +164 -155
  72. local_deep_research/web/static/js/components/research.js +44 -3
  73. local_deep_research/web/static/js/components/settings.js +27 -0
  74. local_deep_research/web/static/js/services/socket.js +47 -0
  75. local_deep_research/web_search_engines/default_search_engines.py +38 -0
  76. local_deep_research/web_search_engines/engines/meta_search_engine.py +100 -33
  77. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +31 -17
  78. local_deep_research/web_search_engines/engines/search_engine_brave.py +8 -3
  79. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +343 -0
  80. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +14 -6
  81. local_deep_research/web_search_engines/engines/search_engine_local.py +19 -23
  82. local_deep_research/web_search_engines/engines/search_engine_local_all.py +9 -12
  83. local_deep_research/web_search_engines/engines/search_engine_searxng.py +12 -17
  84. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +8 -4
  85. local_deep_research/web_search_engines/search_engine_base.py +22 -5
  86. local_deep_research/web_search_engines/search_engine_factory.py +32 -11
  87. local_deep_research/web_search_engines/search_engines_config.py +14 -1
  88. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/METADATA +10 -2
  89. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/RECORD +92 -49
  90. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/WHEEL +0 -0
  91. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/entry_points.txt +0 -0
  92. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@
13
13
  connectedResearchId: null, // Track which research we're connected to
14
14
  currentFilter: 'all' // Track current filter type
15
15
  };
16
-
16
+
17
17
  /**
18
18
  * Initialize the log panel
19
19
  * @param {string} researchId - Optional research ID to load logs for
@@ -22,32 +22,32 @@
22
22
  // Check if already initialized
23
23
  if (window._logPanelState.initialized) {
24
24
  console.log('Log panel already initialized, checking if research ID has changed');
25
-
25
+
26
26
  // If we're already connected to this research, do nothing
27
27
  if (window._logPanelState.connectedResearchId === researchId) {
28
28
  console.log('Already connected to research ID:', researchId);
29
29
  return;
30
30
  }
31
-
31
+
32
32
  // If the research ID has changed, we'll update our connection
33
33
  console.log('Research ID changed from', window._logPanelState.connectedResearchId, 'to', researchId);
34
34
  window._logPanelState.connectedResearchId = researchId;
35
35
  }
36
-
36
+
37
37
  console.log('Initializing shared log panel, research ID:', researchId);
38
-
38
+
39
39
  // Check if we're on a research-specific page (progress, results)
40
- const isResearchPage = window.location.pathname.includes('/research/progress/') ||
40
+ const isResearchPage = window.location.pathname.includes('/research/progress/') ||
41
41
  window.location.pathname.includes('/research/results/') ||
42
42
  document.getElementById('research-progress') ||
43
43
  document.getElementById('research-results');
44
-
44
+
45
45
  // Get all log panels on the page (there might be duplicates)
46
46
  const logPanels = document.querySelectorAll('.collapsible-log-panel');
47
-
47
+
48
48
  if (logPanels.length > 1) {
49
49
  console.warn(`Found ${logPanels.length} log panels, removing duplicates`);
50
-
50
+
51
51
  // Keep only the first one and remove others
52
52
  for (let i = 1; i < logPanels.length; i++) {
53
53
  console.log(`Removing duplicate log panel #${i}`);
@@ -57,24 +57,24 @@
57
57
  console.error('No log panel found in the DOM!');
58
58
  return;
59
59
  }
60
-
60
+
61
61
  // Get log panel elements with both old and new names for compatibility
62
62
  let logPanelToggle = document.getElementById('log-panel-toggle');
63
63
  let logPanelContent = document.getElementById('log-panel-content');
64
-
64
+
65
65
  // Fallback to the old element IDs if needed
66
66
  if (!logPanelToggle) logPanelToggle = document.getElementById('logToggle');
67
67
  if (!logPanelContent) logPanelContent = document.getElementById('logPanel');
68
-
68
+
69
69
  if (!logPanelToggle || !logPanelContent) {
70
70
  console.warn('Log panel elements not found, skipping initialization');
71
71
  return;
72
72
  }
73
-
73
+
74
74
  // Handle visibility based on page type
75
75
  if (!isResearchPage) {
76
76
  console.log('Not on a research-specific page, hiding log panel');
77
-
77
+
78
78
  // Hide the log panel on non-research pages
79
79
  const panel = logPanelContent.closest('.collapsible-log-panel');
80
80
  if (panel) {
@@ -93,12 +93,12 @@
93
93
  panel.style.display = 'flex';
94
94
  }
95
95
  }
96
-
96
+
97
97
  console.log('Log panel elements found, setting up handlers');
98
-
98
+
99
99
  // Mark as initialized to prevent double initialization
100
100
  window._logPanelState.initialized = true;
101
-
101
+
102
102
  // Check for CSS issue - if the panel's computed style has display:none, the panel won't be visible
103
103
  const computedStyle = window.getComputedStyle(logPanelContent);
104
104
  console.log('Log panel CSS visibility:', {
@@ -107,13 +107,13 @@
107
107
  height: computedStyle.height,
108
108
  overflow: computedStyle.overflow
109
109
  });
110
-
110
+
111
111
  // Ensure the panel is visible in the DOM
112
112
  if (computedStyle.display === 'none') {
113
113
  console.warn('Log panel has display:none - forcing display:flex');
114
114
  logPanelContent.style.display = 'flex';
115
115
  }
116
-
116
+
117
117
  // Ensure we have a console log container
118
118
  const consoleLogContainer = document.getElementById('console-log-container');
119
119
  if (!consoleLogContainer) {
@@ -122,15 +122,15 @@
122
122
  // Add placeholder message
123
123
  consoleLogContainer.innerHTML = '<div class="empty-log-message">No logs available. Expand panel to load logs.</div>';
124
124
  }
125
-
125
+
126
126
  // Set up toggle click handler
127
127
  logPanelToggle.addEventListener('click', function() {
128
128
  console.log('Log panel toggle clicked');
129
-
129
+
130
130
  // Toggle collapsed state
131
131
  logPanelContent.classList.toggle('collapsed');
132
132
  logPanelToggle.classList.toggle('collapsed');
133
-
133
+
134
134
  // Update toggle icon
135
135
  const toggleIcon = logPanelToggle.querySelector('.toggle-icon');
136
136
  if (toggleIcon) {
@@ -138,14 +138,14 @@
138
138
  toggleIcon.className = 'fas fa-chevron-right toggle-icon';
139
139
  } else {
140
140
  toggleIcon.className = 'fas fa-chevron-down toggle-icon';
141
-
141
+
142
142
  // Load logs if not already loaded
143
143
  if (!logPanelContent.dataset.loaded && researchId) {
144
144
  console.log('First expansion of log panel, loading logs');
145
145
  loadLogsForResearch(researchId);
146
146
  logPanelContent.dataset.loaded = 'true';
147
147
  }
148
-
148
+
149
149
  // Process any queued logs
150
150
  if (window._logPanelState.queuedLogs.length > 0) {
151
151
  console.log(`Processing ${window._logPanelState.queuedLogs.length} queued logs`);
@@ -156,34 +156,34 @@
156
156
  }
157
157
  }
158
158
  }
159
-
159
+
160
160
  // Track expanded state
161
161
  window._logPanelState.expanded = !logPanelContent.classList.contains('collapsed');
162
162
  });
163
-
163
+
164
164
  // Set up filter button click handlers
165
165
  const filterButtons = document.querySelectorAll('.log-filter .filter-buttons button');
166
166
  filterButtons.forEach(button => {
167
167
  button.addEventListener('click', function() {
168
168
  const type = this.textContent.toLowerCase();
169
169
  console.log(`Filtering logs by type: ${type}`);
170
-
170
+
171
171
  // Update active state
172
172
  filterButtons.forEach(btn => btn.classList.remove('selected'));
173
173
  this.classList.add('selected');
174
-
174
+
175
175
  // Apply filtering
176
176
  filterLogsByType(type);
177
177
  });
178
178
  });
179
-
179
+
180
180
  // Start with panel collapsed and fix initial chevron direction
181
181
  logPanelContent.classList.add('collapsed');
182
182
  const initialToggleIcon = logPanelToggle.querySelector('.toggle-icon');
183
183
  if (initialToggleIcon) {
184
184
  initialToggleIcon.className = 'fas fa-chevron-right toggle-icon';
185
185
  }
186
-
186
+
187
187
  // Initialize the log count
188
188
  const logIndicators = document.querySelectorAll('.log-indicator');
189
189
  if (logIndicators.length > 0) {
@@ -194,19 +194,19 @@
194
194
  } else {
195
195
  console.warn('No log indicators found for initialization');
196
196
  }
197
-
197
+
198
198
  // Check CSS display property of the log panel
199
199
  const logPanel = document.querySelector('.collapsible-log-panel');
200
200
  if (logPanel) {
201
201
  const panelStyle = window.getComputedStyle(logPanel);
202
202
  console.log('Log panel CSS display:', panelStyle.display);
203
-
203
+
204
204
  if (panelStyle.display === 'none') {
205
205
  console.warn('Log panel has CSS display:none - forcing display:flex');
206
206
  logPanel.style.display = 'flex';
207
207
  }
208
208
  }
209
-
209
+
210
210
  // Pre-load logs if hash includes #logs
211
211
  if (window.location.hash === '#logs' && researchId) {
212
212
  console.log('Auto-loading logs due to #logs in URL');
@@ -214,7 +214,7 @@
214
214
  logPanelToggle.click();
215
215
  }, 500);
216
216
  }
217
-
217
+
218
218
  // DEBUG: Force expand the log panel if URL has debug parameter
219
219
  if (window.location.search.includes('debug=logs') || window.location.hash.includes('debug')) {
220
220
  console.log('DEBUG: Force-expanding log panel');
@@ -224,11 +224,11 @@
224
224
  }
225
225
  }, 800);
226
226
  }
227
-
227
+
228
228
  // Register global functions to ensure they work across modules
229
229
  window.addConsoleLog = addConsoleLog;
230
230
  window.filterLogsByType = filterLogsByType;
231
-
231
+
232
232
  // Add a connector to socket.js
233
233
  // Track when we last received this exact message to avoid re-adding within 10 seconds
234
234
  const processedMessages = new Map();
@@ -237,21 +237,21 @@
237
237
  const message = logEntry.message || logEntry.content || '';
238
238
  const messageKey = `${message}-${logEntry.type || 'info'}`;
239
239
  const now = Date.now();
240
-
240
+
241
241
  // Check if we've seen this message recently (within 10 seconds)
242
242
  if (processedMessages.has(messageKey)) {
243
243
  const lastProcessed = processedMessages.get(messageKey);
244
244
  const timeDiff = now - lastProcessed;
245
-
245
+
246
246
  if (timeDiff < 10000) { // 10 seconds
247
247
  console.log(`Skipping duplicate socket message received within ${timeDiff}ms:`, message);
248
248
  return;
249
249
  }
250
250
  }
251
-
251
+
252
252
  // Update our tracking
253
253
  processedMessages.set(messageKey, now);
254
-
254
+
255
255
  // Clean up old entries (keep map from growing indefinitely)
256
256
  if (processedMessages.size > 100) {
257
257
  // Remove entries older than 60 seconds
@@ -261,14 +261,14 @@
261
261
  }
262
262
  }
263
263
  }
264
-
264
+
265
265
  // Process the log entry
266
266
  addLogEntryToPanel(logEntry);
267
267
  };
268
-
268
+
269
269
  console.log('Log panel initialized');
270
270
  }
271
-
271
+
272
272
  /**
273
273
  * Load logs for a specific research
274
274
  * @param {string} researchId - The research ID to load logs for
@@ -280,32 +280,32 @@
280
280
  if (logContent) {
281
281
  logContent.innerHTML = '<div class="loading-spinner centered"><div class="spinner"></div><div style="margin-left: 10px;">Loading logs...</div></div>';
282
282
  }
283
-
283
+
284
284
  console.log('Loading logs for research ID:', researchId);
285
-
285
+
286
286
  // Fetch logs from API
287
287
  const response = await fetch(`/research/api/logs/${researchId}`);
288
288
  const data = await response.json();
289
-
289
+
290
290
  console.log('Logs API response:', data);
291
-
291
+
292
292
  // Initialize array to hold all logs from different sources
293
293
  const allLogs = [];
294
-
294
+
295
295
  // Track seen messages to avoid duplicate content with different timestamps
296
296
  const seenMessages = new Map();
297
-
297
+
298
298
  // Process progress_log if available
299
299
  if (data.progress_log && typeof data.progress_log === 'string') {
300
300
  try {
301
301
  const progressLogs = JSON.parse(data.progress_log);
302
302
  if (Array.isArray(progressLogs) && progressLogs.length > 0) {
303
303
  console.log(`Found ${progressLogs.length} logs in progress_log`);
304
-
304
+
305
305
  // Process progress logs
306
306
  progressLogs.forEach(logItem => {
307
307
  if (!logItem.time || !logItem.message) return; // Skip invalid logs
308
-
308
+
309
309
  // Skip if we've seen this exact message before
310
310
  const messageKey = normalizeMessage(logItem.message);
311
311
  if (seenMessages.has(messageKey)) {
@@ -314,7 +314,7 @@
314
314
  const previousTime = new Date(previousLog.time);
315
315
  const currentTime = new Date(logItem.time);
316
316
  const timeDiff = Math.abs(currentTime - previousTime) / 1000; // in seconds
317
-
317
+
318
318
  if (timeDiff < 60) { // Within 1 minute
319
319
  // Use the newer timestamp if available
320
320
  if (currentTime > previousTime) {
@@ -322,15 +322,15 @@
322
322
  }
323
323
  return; // Skip this duplicate
324
324
  }
325
-
325
+
326
326
  // If we get here, it's the same message but far apart in time (e.g., a repeated step)
327
327
  // We'll include it as a separate entry
328
328
  }
329
-
329
+
330
330
  // Determine log type based on metadata
331
331
  let logType = 'info';
332
332
  if (logItem.metadata) {
333
- if (logItem.metadata.phase === 'iteration_complete' ||
333
+ if (logItem.metadata.phase === 'iteration_complete' ||
334
334
  logItem.metadata.phase === 'report_complete' ||
335
335
  logItem.metadata.phase === 'complete' ||
336
336
  logItem.metadata.is_milestone === true) {
@@ -339,12 +339,12 @@
339
339
  logType = 'error';
340
340
  }
341
341
  }
342
-
342
+
343
343
  // Add message keywords for better type detection
344
344
  if (logType !== 'milestone') {
345
345
  const msg = logItem.message.toLowerCase();
346
- if (msg.includes('complete') ||
347
- msg.includes('finished') ||
346
+ if (msg.includes('complete') ||
347
+ msg.includes('finished') ||
348
348
  msg.includes('starting phase') ||
349
349
  msg.includes('generated report')) {
350
350
  logType = 'milestone';
@@ -352,7 +352,7 @@
352
352
  logType = 'error';
353
353
  }
354
354
  }
355
-
355
+
356
356
  // Create a log entry object with a unique ID for deduplication
357
357
  const logEntry = {
358
358
  id: `${logItem.time}-${hashString(logItem.message)}`,
@@ -362,10 +362,10 @@
362
362
  metadata: logItem.metadata || {},
363
363
  source: 'progress_log'
364
364
  };
365
-
365
+
366
366
  // Track this message to avoid showing exact duplicates with different timestamps
367
367
  seenMessages.set(messageKey, logEntry);
368
-
368
+
369
369
  // Add to all logs array
370
370
  allLogs.push(logEntry);
371
371
  });
@@ -374,15 +374,15 @@
374
374
  console.error('Error parsing progress_log:', e);
375
375
  }
376
376
  }
377
-
377
+
378
378
  // Standard logs array processing
379
379
  if (data && Array.isArray(data.logs)) {
380
380
  console.log(`Processing ${data.logs.length} standard logs`);
381
-
381
+
382
382
  // Process each standard log
383
383
  data.logs.forEach(log => {
384
384
  if (!log.timestamp && !log.time) return; // Skip invalid logs
385
-
385
+
386
386
  // Skip duplicates based on message content
387
387
  const messageKey = normalizeMessage(log.message || log.content || '');
388
388
  if (seenMessages.has(messageKey)) {
@@ -391,7 +391,7 @@
391
391
  const previousTime = new Date(previousLog.time);
392
392
  const currentTime = new Date(log.timestamp || log.time);
393
393
  const timeDiff = Math.abs(currentTime - previousTime) / 1000; // in seconds
394
-
394
+
395
395
  if (timeDiff < 60) { // Within 1 minute
396
396
  // Use the newer timestamp if available
397
397
  if (currentTime > previousTime) {
@@ -400,7 +400,7 @@
400
400
  return; // Skip this duplicate
401
401
  }
402
402
  }
403
-
403
+
404
404
  // Create standardized log entry
405
405
  const logEntry = {
406
406
  id: `${log.timestamp || log.time}-${hashString(log.message || log.content || '')}`,
@@ -410,50 +410,50 @@
410
410
  metadata: log.metadata || {},
411
411
  source: 'standard_logs'
412
412
  };
413
-
413
+
414
414
  // Track this message
415
415
  seenMessages.set(messageKey, logEntry);
416
-
416
+
417
417
  // Add to all logs array
418
418
  allLogs.push(logEntry);
419
419
  });
420
420
  }
421
-
421
+
422
422
  // Clear container
423
423
  if (logContent) {
424
424
  if (allLogs.length === 0) {
425
425
  logContent.innerHTML = '<div class="empty-log-message">No logs available for this research.</div>';
426
426
  return;
427
427
  }
428
-
428
+
429
429
  logContent.innerHTML = '';
430
-
430
+
431
431
  // Normalize timestamps - in case there are logs with mismatched AM/PM time zones
432
432
  // This attempts to ensure logs are in a proper chronological order
433
433
  normalizeTimestamps(allLogs);
434
-
434
+
435
435
  // Deduplicate logs by ID and sort by timestamp (oldest first)
436
436
  const uniqueLogsMap = new Map();
437
437
  allLogs.forEach(log => {
438
438
  // Use the ID as the key for deduplication
439
439
  uniqueLogsMap.set(log.id, log);
440
440
  });
441
-
441
+
442
442
  // Convert map back to array
443
443
  const uniqueLogs = Array.from(uniqueLogsMap.values());
444
-
444
+
445
445
  // Sort logs by timestamp (oldest first)
446
446
  const sortedLogs = uniqueLogs.sort((a, b) => {
447
447
  return new Date(a.time) - new Date(b.time);
448
448
  });
449
-
449
+
450
450
  console.log(`Displaying ${sortedLogs.length} logs after deduplication (from original ${allLogs.length})`);
451
-
451
+
452
452
  // Add each log entry to panel
453
453
  sortedLogs.forEach(log => {
454
454
  addLogEntryToPanel(log, false); // False means don't increment counter
455
455
  });
456
-
456
+
457
457
  // Update log count indicator
458
458
  const logIndicators = document.querySelectorAll('.log-indicator');
459
459
  if (logIndicators.length > 0) {
@@ -463,10 +463,10 @@
463
463
  });
464
464
  }
465
465
  }
466
-
466
+
467
467
  } catch (error) {
468
468
  console.error('Error loading logs:', error);
469
-
469
+
470
470
  // Show error in log panel
471
471
  const logContent = document.getElementById('console-log-container');
472
472
  if (logContent) {
@@ -474,7 +474,7 @@
474
474
  }
475
475
  }
476
476
  }
477
-
477
+
478
478
  /**
479
479
  * Normalize a message for deduplication comparison
480
480
  * @param {string} message - The message to normalize
@@ -485,7 +485,7 @@
485
485
  // Remove extra whitespace and lowercase
486
486
  return message.trim().toLowerCase();
487
487
  }
488
-
488
+
489
489
  /**
490
490
  * Normalize timestamps across logs to ensure consistent ordering
491
491
  * @param {Array} logs - The logs to normalize
@@ -493,7 +493,7 @@
493
493
  function normalizeTimestamps(logs) {
494
494
  // Find the most common date in the logs (ignoring the time)
495
495
  const dateFrequency = new Map();
496
-
496
+
497
497
  logs.forEach(log => {
498
498
  try {
499
499
  const date = new Date(log.time);
@@ -504,28 +504,28 @@
504
504
  console.error('Error parsing date:', log.time);
505
505
  }
506
506
  });
507
-
507
+
508
508
  // Find the most frequent date
509
509
  let mostCommonDate = null;
510
510
  let highestFrequency = 0;
511
-
511
+
512
512
  dateFrequency.forEach((count, date) => {
513
513
  if (count > highestFrequency) {
514
514
  highestFrequency = count;
515
515
  mostCommonDate = date;
516
516
  }
517
517
  });
518
-
518
+
519
519
  console.log(`Most common date: ${mostCommonDate} with ${highestFrequency} occurrences`);
520
-
520
+
521
521
  if (!mostCommonDate) return; // Can't normalize without a common date
522
-
522
+
523
523
  // Normalize all logs to the most common date
524
524
  logs.forEach(log => {
525
525
  try {
526
526
  const date = new Date(log.time);
527
527
  const dateStr = date.toISOString().split('T')[0];
528
-
528
+
529
529
  // If this log is from a different date, adjust it to the most common date
530
530
  // while preserving the time portion
531
531
  if (dateStr !== mostCommonDate) {
@@ -533,7 +533,7 @@
533
533
  date.setFullYear(parseInt(year));
534
534
  date.setMonth(parseInt(month) - 1); // Months are 0-indexed
535
535
  date.setDate(parseInt(day));
536
-
536
+
537
537
  // Update the log time
538
538
  log.time = date.toISOString();
539
539
  log.id = `${log.time}-${hashString(log.message)}`;
@@ -544,7 +544,7 @@
544
544
  }
545
545
  });
546
546
  }
547
-
547
+
548
548
  /**
549
549
  * Simple hash function for strings
550
550
  * @param {string} str - String to hash
@@ -560,7 +560,7 @@
560
560
  }
561
561
  return hash.toString();
562
562
  }
563
-
563
+
564
564
  /**
565
565
  * Add a log entry to the console - public API
566
566
  * @param {string} message - Log message
@@ -569,7 +569,7 @@
569
569
  */
570
570
  function addConsoleLog(message, level = 'info', metadata = null) {
571
571
  console.log(`[${level.toUpperCase()}] ${message}`);
572
-
572
+
573
573
  const timestamp = new Date().toISOString();
574
574
  const logEntry = {
575
575
  id: `${timestamp}-${hashString(message)}`,
@@ -578,29 +578,29 @@
578
578
  type: level,
579
579
  metadata: metadata || { type: level }
580
580
  };
581
-
581
+
582
582
  // Queue log entries if panel is not expanded yet
583
583
  if (!window._logPanelState.expanded) {
584
584
  window._logPanelState.queuedLogs.push(logEntry);
585
585
  console.log('Queued log entry for later display');
586
-
586
+
587
587
  // Update log count even if not displaying yet
588
588
  updateLogCounter(1);
589
-
589
+
590
590
  // Auto-expand log panel on first log
591
591
  const logPanelToggle = document.getElementById('log-panel-toggle');
592
592
  if (logPanelToggle) {
593
593
  console.log('Auto-expanding log panel because logs are available');
594
594
  logPanelToggle.click();
595
595
  }
596
-
596
+
597
597
  return;
598
598
  }
599
-
599
+
600
600
  // Add directly to panel if it's expanded
601
601
  addLogEntryToPanel(logEntry, true);
602
602
  }
603
-
603
+
604
604
  /**
605
605
  * Add a log entry directly to the panel
606
606
  * @param {Object} logEntry - The log entry to add
@@ -608,37 +608,37 @@
608
608
  */
609
609
  function addLogEntryToPanel(logEntry, incrementCounter = true) {
610
610
  console.log('Adding log entry to panel:', logEntry);
611
-
611
+
612
612
  const consoleLogContainer = document.getElementById('console-log-container');
613
613
  if (!consoleLogContainer) {
614
614
  console.warn('Console log container not found');
615
615
  return;
616
616
  }
617
-
617
+
618
618
  // Clear empty message if present
619
619
  const emptyMessage = consoleLogContainer.querySelector('.empty-log-message');
620
620
  if (emptyMessage) {
621
621
  emptyMessage.remove();
622
622
  }
623
-
623
+
624
624
  // Ensure the log entry has an ID
625
625
  if (!logEntry.id) {
626
626
  const timestamp = logEntry.time || logEntry.timestamp || new Date().toISOString();
627
627
  const message = logEntry.message || logEntry.content || 'No message';
628
628
  logEntry.id = `${timestamp}-${hashString(message)}`;
629
629
  }
630
-
630
+
631
631
  // More robust deduplication: First check by ID if available
632
632
  if (logEntry.id) {
633
633
  const existingEntryById = consoleLogContainer.querySelector(`.console-log-entry[data-log-id="${logEntry.id}"]`);
634
634
  if (existingEntryById) {
635
635
  console.log('Skipping duplicate log entry by ID:', logEntry.id);
636
-
636
+
637
637
  // Increment counter on existing entry
638
638
  let counter = parseInt(existingEntryById.dataset.counter || '1');
639
639
  counter++;
640
640
  existingEntryById.dataset.counter = counter;
641
-
641
+
642
642
  // Update visual counter badge
643
643
  if (counter > 1) {
644
644
  let counterBadge = existingEntryById.querySelector('.duplicate-counter');
@@ -649,41 +649,41 @@
649
649
  }
650
650
  counterBadge.textContent = `(${counter}×)`;
651
651
  }
652
-
652
+
653
653
  // Still update the global counter if needed
654
654
  if (incrementCounter) {
655
655
  updateLogCounter(1);
656
656
  }
657
-
657
+
658
658
  return;
659
659
  }
660
660
  }
661
-
661
+
662
662
  // Secondary check for duplicate by message content (for backward compatibility)
663
663
  const existingEntries = consoleLogContainer.querySelectorAll('.console-log-entry');
664
664
  if (existingEntries.length > 0) {
665
665
  const message = logEntry.message || logEntry.content || '';
666
666
  const logType = (logEntry.type || 'info').toLowerCase();
667
-
667
+
668
668
  // Start from the end since newest logs are now at the bottom
669
669
  for (let i = existingEntries.length - 1; i >= Math.max(0, existingEntries.length - 10); i--) {
670
670
  // Only check the 10 most recent entries for efficiency
671
671
  const entry = existingEntries[i];
672
672
  const entryMessage = entry.querySelector('.log-message')?.textContent;
673
673
  const entryType = entry.dataset.logType;
674
-
674
+
675
675
  // If message and type match, consider it a duplicate (unless it's a milestone)
676
- if (entryMessage === message &&
677
- entryType === logType &&
676
+ if (entryMessage === message &&
677
+ entryType === logType &&
678
678
  logType !== 'milestone') {
679
-
679
+
680
680
  console.log('Skipping duplicate log entry by content:', message);
681
-
681
+
682
682
  // Increment counter on existing entry
683
683
  let counter = parseInt(entry.dataset.counter || '1');
684
684
  counter++;
685
685
  entry.dataset.counter = counter;
686
-
686
+
687
687
  // Update visual counter badge
688
688
  if (counter > 1) {
689
689
  let counterBadge = entry.querySelector('.duplicate-counter');
@@ -694,20 +694,20 @@
694
694
  }
695
695
  counterBadge.textContent = `(${counter}×)`;
696
696
  }
697
-
697
+
698
698
  // Still update the global counter if needed
699
699
  if (incrementCounter) {
700
700
  updateLogCounter(1);
701
701
  }
702
-
702
+
703
703
  return;
704
704
  }
705
705
  }
706
706
  }
707
-
707
+
708
708
  // Get the log template
709
709
  const template = document.getElementById('console-log-entry-template');
710
-
710
+
711
711
  // Determine log level - CHECK FOR DIRECT TYPE FIELD FIRST
712
712
  let logLevel = 'info';
713
713
  if (logEntry.type) {
@@ -715,7 +715,7 @@
715
715
  } else if (logEntry.metadata && logEntry.metadata.type) {
716
716
  logLevel = logEntry.metadata.type;
717
717
  } else if (logEntry.metadata && logEntry.metadata.phase) {
718
- if (logEntry.metadata.phase === 'complete' ||
718
+ if (logEntry.metadata.phase === 'complete' ||
719
719
  logEntry.metadata.phase === 'iteration_complete' ||
720
720
  logEntry.metadata.phase === 'report_complete') {
721
721
  logLevel = 'milestone';
@@ -725,19 +725,19 @@
725
725
  } else if (logEntry.level) {
726
726
  logLevel = logEntry.level;
727
727
  }
728
-
728
+
729
729
  // Format timestamp
730
730
  const timestamp = new Date(logEntry.time || logEntry.timestamp || new Date());
731
731
  const timeStr = timestamp.toLocaleTimeString();
732
-
732
+
733
733
  // Get message
734
734
  const message = logEntry.message || logEntry.content || 'No message';
735
-
735
+
736
736
  if (template) {
737
737
  // Create a new log entry from the template
738
738
  const entry = document.importNode(template.content, true);
739
739
  const logEntryElement = entry.querySelector('.console-log-entry');
740
-
740
+
741
741
  // Add the log type as data attribute for filtering
742
742
  if (logEntryElement) {
743
743
  logEntryElement.dataset.logType = logLevel.toLowerCase();
@@ -748,14 +748,23 @@
748
748
  if (logEntry.id) {
749
749
  logEntryElement.dataset.logId = logEntry.id;
750
750
  }
751
+
752
+ // Add special attribute for engine selection events
753
+ if (logEntry.metadata && logEntry.metadata.phase === 'engine_selected') {
754
+ logEntryElement.dataset.engineSelected = 'true';
755
+ // Store engine name as a data attribute
756
+ if (logEntry.metadata.engine) {
757
+ logEntryElement.dataset.engine = logEntry.metadata.engine;
758
+ }
759
+ }
751
760
  }
752
-
761
+
753
762
  // Set content
754
763
  entry.querySelector('.log-timestamp').textContent = timeStr;
755
764
  entry.querySelector('.log-badge').textContent = logLevel.charAt(0).toUpperCase() + logLevel.slice(1);
756
765
  entry.querySelector('.log-badge').className = `log-badge ${logLevel.toLowerCase()}`;
757
766
  entry.querySelector('.log-message').textContent = message;
758
-
767
+
759
768
  // Add to container (at the end for oldest first)
760
769
  consoleLogContainer.appendChild(entry);
761
770
  } else {
@@ -768,33 +777,33 @@
768
777
  if (logEntry.id) {
769
778
  entry.dataset.logId = logEntry.id;
770
779
  }
771
-
780
+
772
781
  // Create log content
773
782
  entry.innerHTML = `
774
783
  <span class="log-timestamp">${timeStr}</span>
775
784
  <span class="log-badge ${logLevel.toLowerCase()}">${logLevel.charAt(0).toUpperCase() + logLevel.slice(1)}</span>
776
785
  <span class="log-message">${message}</span>
777
786
  `;
778
-
787
+
779
788
  // Add to container (at the end for oldest first)
780
789
  consoleLogContainer.appendChild(entry);
781
790
  }
782
-
791
+
783
792
  // Check if the entry should be visible based on current filter
784
793
  const currentFilter = window._logPanelState.currentFilter || 'all';
785
794
  const shouldShow = checkLogVisibility(logLevel.toLowerCase(), currentFilter);
786
-
795
+
787
796
  // Apply visibility based on the current filter
788
797
  const newEntry = consoleLogContainer.lastElementChild;
789
798
  if (newEntry) {
790
799
  newEntry.style.display = shouldShow ? '' : 'none';
791
800
  }
792
-
801
+
793
802
  // Update log count using helper function if needed
794
803
  if (incrementCounter) {
795
804
  updateLogCounter(1);
796
805
  }
797
-
806
+
798
807
  // No need to scroll when loading all logs
799
808
  // Scroll will be handled after all logs are loaded
800
809
  if (incrementCounter) {
@@ -804,7 +813,7 @@
804
813
  }, 0);
805
814
  }
806
815
  }
807
-
816
+
808
817
  /**
809
818
  * Helper function to update the log counter
810
819
  * @param {number} increment - Amount to increment the counter by
@@ -814,14 +823,14 @@
814
823
  if (logIndicators.length > 0) {
815
824
  const currentCount = parseInt(logIndicators[0].textContent) || 0;
816
825
  const newCount = currentCount + increment;
817
-
826
+
818
827
  // Update all indicators
819
828
  logIndicators.forEach(indicator => {
820
829
  indicator.textContent = newCount;
821
830
  });
822
831
  }
823
832
  }
824
-
833
+
825
834
  /**
826
835
  * Check if a log entry should be visible based on filter type
827
836
  * @param {string} logType - The type of log (info, milestone, error)
@@ -844,43 +853,43 @@
844
853
  return true; // Default to showing everything
845
854
  }
846
855
  }
847
-
856
+
848
857
  /**
849
858
  * Filter logs by type
850
859
  * @param {string} filterType - The type to filter by (all, info, milestone, error)
851
860
  */
852
861
  function filterLogsByType(filterType = 'all') {
853
862
  console.log('Filtering logs by type:', filterType);
854
-
863
+
855
864
  filterType = filterType.toLowerCase();
856
-
865
+
857
866
  // Store current filter in shared state
858
867
  window._logPanelState.currentFilter = filterType;
859
-
868
+
860
869
  // Get all log entries from the DOM
861
870
  const logEntries = document.querySelectorAll('.console-log-entry');
862
871
  console.log(`Found ${logEntries.length} log entries to filter`);
863
-
872
+
864
873
  let visibleCount = 0;
865
-
874
+
866
875
  // Apply filters
867
876
  logEntries.forEach(entry => {
868
877
  // Use data attribute for log type
869
878
  const logType = entry.dataset.logType || 'info';
870
-
879
+
871
880
  // Determine visibility based on filter type
872
881
  const shouldShow = checkLogVisibility(logType, filterType);
873
-
882
+
874
883
  // Set display style based on filter result
875
884
  entry.style.display = shouldShow ? '' : 'none';
876
-
885
+
877
886
  if (shouldShow) {
878
887
  visibleCount++;
879
888
  }
880
889
  });
881
-
890
+
882
891
  console.log(`Filtering complete. Showing ${visibleCount} of ${logEntries.length} logs`);
883
-
892
+
884
893
  // Show 'no logs' message if all logs are filtered out
885
894
  const consoleContainer = document.getElementById('console-log-container');
886
895
  if (consoleContainer && logEntries.length > 0) {
@@ -889,7 +898,7 @@
889
898
  if (existingEmptyMessage) {
890
899
  existingEmptyMessage.remove();
891
900
  }
892
-
901
+
893
902
  // Add empty message if needed
894
903
  if (visibleCount === 0) {
895
904
  console.log(`Adding 'no logs' message for filter: ${filterType}`);
@@ -900,7 +909,7 @@
900
909
  }
901
910
  }
902
911
  }
903
-
912
+
904
913
  // Expose public API
905
914
  window.logPanel = {
906
915
  initialize: initializeLogPanel,
@@ -908,33 +917,33 @@
908
917
  filterLogs: filterLogsByType,
909
918
  loadLogs: loadLogsForResearch
910
919
  };
911
-
920
+
912
921
  // Self-invoke to initialize when DOM content is loaded
913
922
  document.addEventListener('DOMContentLoaded', function() {
914
923
  console.log('DOM ready - checking if log panel should be initialized');
915
-
924
+
916
925
  // Find research ID from URL if available
917
926
  let researchId = null;
918
927
  const urlMatch = window.location.pathname.match(/\/research\/(progress|results)\/(\d+)/);
919
928
  if (urlMatch && urlMatch[2]) {
920
929
  researchId = urlMatch[2];
921
930
  console.log('Found research ID in URL:', researchId);
922
-
931
+
923
932
  // Store the current research ID in the state
924
933
  window._logPanelState.connectedResearchId = researchId;
925
934
  }
926
-
935
+
927
936
  // Check for research page elements
928
- const isResearchPage = window.location.pathname.includes('/research/progress/') ||
937
+ const isResearchPage = window.location.pathname.includes('/research/progress/') ||
929
938
  window.location.pathname.includes('/research/results/') ||
930
939
  document.getElementById('research-progress') ||
931
940
  document.getElementById('research-results');
932
-
941
+
933
942
  // Initialize log panel if on a research page
934
943
  if (isResearchPage) {
935
944
  console.log('On a research page, initializing log panel for research ID:', researchId);
936
945
  initializeLogPanel(researchId);
937
-
946
+
938
947
  // Extra check: If we have a research ID but panel not initialized properly
939
948
  setTimeout(() => {
940
949
  if (researchId && !window._logPanelState.initialized) {
@@ -946,4 +955,4 @@
946
955
  console.log('Not on a research page, skipping log panel initialization');
947
956
  }
948
957
  });
949
- })();
958
+ })();