local-deep-research 0.3.12__py3-none-any.whl → 0.4.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.
- local_deep_research/__init__.py +1 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/advanced_search_system/filters/base_filter.py +2 -3
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +4 -5
- local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +298 -0
- local_deep_research/advanced_search_system/findings/repository.py +0 -3
- local_deep_research/advanced_search_system/strategies/base_strategy.py +1 -2
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +14 -18
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +4 -8
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +5 -6
- local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -2
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +9 -7
- local_deep_research/api/benchmark_functions.py +288 -0
- local_deep_research/api/research_functions.py +8 -4
- local_deep_research/benchmarks/README.md +162 -0
- local_deep_research/benchmarks/__init__.py +51 -0
- local_deep_research/benchmarks/benchmark_functions.py +353 -0
- local_deep_research/benchmarks/cli/__init__.py +16 -0
- local_deep_research/benchmarks/cli/benchmark_commands.py +338 -0
- local_deep_research/benchmarks/cli.py +347 -0
- local_deep_research/benchmarks/comparison/__init__.py +12 -0
- local_deep_research/benchmarks/comparison/evaluator.py +768 -0
- local_deep_research/benchmarks/datasets/__init__.py +53 -0
- local_deep_research/benchmarks/datasets/base.py +295 -0
- local_deep_research/benchmarks/datasets/browsecomp.py +116 -0
- local_deep_research/benchmarks/datasets/custom_dataset_template.py +98 -0
- local_deep_research/benchmarks/datasets/simpleqa.py +74 -0
- local_deep_research/benchmarks/datasets/utils.py +116 -0
- local_deep_research/benchmarks/datasets.py +31 -0
- local_deep_research/benchmarks/efficiency/__init__.py +14 -0
- local_deep_research/benchmarks/efficiency/resource_monitor.py +367 -0
- local_deep_research/benchmarks/efficiency/speed_profiler.py +214 -0
- local_deep_research/benchmarks/evaluators/__init__.py +18 -0
- local_deep_research/benchmarks/evaluators/base.py +74 -0
- local_deep_research/benchmarks/evaluators/browsecomp.py +83 -0
- local_deep_research/benchmarks/evaluators/composite.py +121 -0
- local_deep_research/benchmarks/evaluators/simpleqa.py +271 -0
- local_deep_research/benchmarks/graders.py +410 -0
- local_deep_research/benchmarks/metrics/README.md +80 -0
- local_deep_research/benchmarks/metrics/__init__.py +24 -0
- local_deep_research/benchmarks/metrics/calculation.py +385 -0
- local_deep_research/benchmarks/metrics/reporting.py +155 -0
- local_deep_research/benchmarks/metrics/visualization.py +205 -0
- local_deep_research/benchmarks/metrics.py +11 -0
- local_deep_research/benchmarks/optimization/__init__.py +32 -0
- local_deep_research/benchmarks/optimization/api.py +274 -0
- local_deep_research/benchmarks/optimization/metrics.py +20 -0
- local_deep_research/benchmarks/optimization/optuna_optimizer.py +1163 -0
- local_deep_research/benchmarks/runners.py +434 -0
- local_deep_research/benchmarks/templates.py +65 -0
- local_deep_research/config/llm_config.py +26 -23
- local_deep_research/config/search_config.py +1 -5
- local_deep_research/defaults/default_settings.json +108 -7
- local_deep_research/search_system.py +16 -8
- local_deep_research/utilities/db_utils.py +3 -6
- local_deep_research/utilities/es_utils.py +441 -0
- local_deep_research/utilities/log_utils.py +36 -0
- local_deep_research/utilities/search_utilities.py +8 -9
- local_deep_research/web/app.py +15 -10
- local_deep_research/web/app_factory.py +9 -12
- local_deep_research/web/database/migrations.py +8 -5
- local_deep_research/web/database/models.py +20 -0
- local_deep_research/web/database/schema_upgrade.py +5 -8
- local_deep_research/web/models/database.py +15 -18
- local_deep_research/web/routes/benchmark_routes.py +427 -0
- local_deep_research/web/routes/research_routes.py +13 -17
- local_deep_research/web/routes/settings_routes.py +264 -67
- local_deep_research/web/services/research_service.py +58 -73
- local_deep_research/web/services/settings_manager.py +1 -4
- local_deep_research/web/services/settings_service.py +4 -6
- local_deep_research/web/static/css/styles.css +12 -0
- local_deep_research/web/static/js/components/logpanel.js +164 -155
- local_deep_research/web/static/js/components/research.js +44 -3
- local_deep_research/web/static/js/components/settings.js +27 -0
- local_deep_research/web/static/js/services/socket.js +47 -0
- local_deep_research/web_search_engines/default_search_engines.py +38 -0
- local_deep_research/web_search_engines/engines/meta_search_engine.py +100 -33
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +31 -17
- local_deep_research/web_search_engines/engines/search_engine_brave.py +8 -3
- local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +343 -0
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +14 -6
- local_deep_research/web_search_engines/engines/search_engine_local.py +19 -23
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +9 -12
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +12 -17
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +8 -4
- local_deep_research/web_search_engines/search_engine_base.py +22 -5
- local_deep_research/web_search_engines/search_engine_factory.py +30 -11
- local_deep_research/web_search_engines/search_engines_config.py +14 -1
- {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/METADATA +10 -2
- {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/RECORD +93 -51
- local_deep_research/app.py +0 -8
- {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/WHEEL +0 -0
- {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.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
|
+
})();
|