local-deep-research 0.1.1__py3-none-any.whl → 0.1.13__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/config.py +100 -45
- local_deep_research/search_system.py +1 -1
- local_deep_research/web/app.py +373 -64
- local_deep_research/web/static/css/styles.css +245 -6
- local_deep_research/web/static/js/app.js +1917 -675
- local_deep_research/web/templates/index.html +34 -0
- {local_deep_research-0.1.1.dist-info → local_deep_research-0.1.13.dist-info}/METADATA +9 -3
- {local_deep_research-0.1.1.dist-info → local_deep_research-0.1.13.dist-info}/RECORD +12 -12
- {local_deep_research-0.1.1.dist-info → local_deep_research-0.1.13.dist-info}/WHEEL +1 -1
- {local_deep_research-0.1.1.dist-info → local_deep_research-0.1.13.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.1.1.dist-info → local_deep_research-0.1.13.dist-info/licenses}/LICENSE +0 -0
- {local_deep_research-0.1.1.dist-info → local_deep_research-0.1.13.dist-info}/top_level.txt +0 -0
@@ -20,19 +20,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
20
20
|
// Add function to cleanup research resources globally
|
21
21
|
window.cleanupResearchResources = function() {
|
22
22
|
console.log('Cleaning up research resources');
|
23
|
+
|
24
|
+
// Disconnect any active sockets
|
25
|
+
disconnectAllSockets();
|
26
|
+
|
27
|
+
// Remove any active research data
|
28
|
+
if (window.currentResearchId) {
|
29
|
+
console.log(`Cleaning up research ID: ${window.currentResearchId}`);
|
30
|
+
window.currentResearchId = null;
|
31
|
+
}
|
32
|
+
|
33
|
+
// Reset any active timers
|
23
34
|
if (pollingInterval) {
|
24
35
|
clearInterval(pollingInterval);
|
25
36
|
pollingInterval = null;
|
37
|
+
console.log('Cleared polling interval during cleanup');
|
26
38
|
}
|
27
|
-
isResearchInProgress = false;
|
28
|
-
currentResearchId = null;
|
29
|
-
window.currentResearchId = null;
|
30
39
|
|
31
|
-
//
|
32
|
-
|
33
|
-
if (terminateBtn) {
|
34
|
-
terminateBtn.style.display = 'none';
|
35
|
-
}
|
40
|
+
// Reset research state flags
|
41
|
+
isResearchInProgress = false;
|
36
42
|
};
|
37
43
|
|
38
44
|
// Initialize notification sounds
|
@@ -198,137 +204,342 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
198
204
|
}
|
199
205
|
};
|
200
206
|
|
201
|
-
//
|
202
|
-
window.connectToResearchSocket = function(researchId) {
|
207
|
+
// Function to connect to socket for a research
|
208
|
+
window.connectToResearchSocket = async function(researchId) {
|
209
|
+
if (!researchId) {
|
210
|
+
console.error('No research ID provided for socket connection');
|
211
|
+
return;
|
212
|
+
}
|
213
|
+
|
203
214
|
try {
|
204
|
-
// Check if
|
205
|
-
|
206
|
-
|
207
|
-
return false;
|
208
|
-
}
|
215
|
+
// Check if research is terminated/suspended before connecting
|
216
|
+
const response = await fetch(getApiUrl(`/api/research/${researchId}`));
|
217
|
+
const data = await response.json();
|
209
218
|
|
210
|
-
//
|
211
|
-
|
219
|
+
// Don't connect to socket for terminated or suspended research
|
220
|
+
if (data.status === 'suspended' || data.status === 'failed') {
|
221
|
+
console.log(`Not connecting socket for ${data.status} research ${researchId}`);
|
222
|
+
|
223
|
+
// Make sure UI reflects the suspended state
|
224
|
+
updateTerminationUIState('suspended', `Research was ${data.status}`);
|
225
|
+
return;
|
226
|
+
}
|
227
|
+
// Don't connect to completed research
|
228
|
+
else if (data.status === 'completed') {
|
229
|
+
console.log(`Not connecting socket for completed research ${researchId}`);
|
230
|
+
return;
|
231
|
+
}
|
212
232
|
|
213
|
-
|
214
|
-
socket = initializeSocket();
|
233
|
+
console.log(`Connecting to socket for research ${researchId} (status: ${data.status})`);
|
215
234
|
|
235
|
+
// Initialize socket if it doesn't exist
|
216
236
|
if (!socket) {
|
217
|
-
|
218
|
-
return false;
|
237
|
+
initializeSocket();
|
219
238
|
}
|
220
239
|
|
221
|
-
// Subscribe to research
|
222
|
-
socket.
|
223
|
-
|
224
|
-
|
225
|
-
|
240
|
+
// Subscribe to the research channel
|
241
|
+
if (socket && socket.connected) {
|
242
|
+
socket.emit('subscribe_to_research', { research_id: researchId });
|
243
|
+
console.log(`Subscribed to research ${researchId}`);
|
244
|
+
} else {
|
245
|
+
console.warn('Socket not connected, waiting for connection...');
|
246
|
+
// Wait for socket to connect
|
247
|
+
const maxAttempts = 5;
|
248
|
+
let attempts = 0;
|
249
|
+
|
250
|
+
const socketConnectInterval = setInterval(() => {
|
251
|
+
attempts++;
|
252
|
+
if (socket && socket.connected) {
|
253
|
+
socket.emit('subscribe_to_research', { research_id: researchId });
|
254
|
+
console.log(`Subscribed to research ${researchId} after ${attempts} attempts`);
|
255
|
+
clearInterval(socketConnectInterval);
|
256
|
+
} else if (attempts >= maxAttempts) {
|
257
|
+
console.error(`Failed to connect to socket after ${maxAttempts} attempts`);
|
258
|
+
clearInterval(socketConnectInterval);
|
259
|
+
addConsoleLog('Failed to connect to real-time updates', 'error');
|
260
|
+
}
|
261
|
+
}, 1000);
|
262
|
+
}
|
263
|
+
} catch (error) {
|
264
|
+
console.error(`Error connecting to socket for research ${researchId}:`, error);
|
265
|
+
}
|
266
|
+
};
|
267
|
+
|
268
|
+
// Format the research status for display
|
269
|
+
function formatStatus(status) {
|
270
|
+
if (!status) return 'Unknown';
|
271
|
+
|
272
|
+
// Handle in_progress specially
|
273
|
+
if (status === 'in_progress') return 'In Progress';
|
274
|
+
|
275
|
+
// Capitalize first letter for other statuses
|
276
|
+
return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
277
|
+
}
|
278
|
+
|
279
|
+
// Format the research mode for display
|
280
|
+
function formatMode(mode) {
|
281
|
+
if (!mode) return 'Unknown';
|
282
|
+
|
283
|
+
return mode === 'detailed' ? 'Detailed Report' : 'Quick Summary';
|
284
|
+
}
|
285
|
+
|
286
|
+
// Format a date for display
|
287
|
+
function formatDate(date, duration = null) {
|
288
|
+
// Handle null/undefined gracefully
|
289
|
+
if (!date) return 'Unknown';
|
226
290
|
|
227
|
-
|
291
|
+
// Check if we have a date string instead of a Date object
|
292
|
+
if (typeof date === 'string') {
|
228
293
|
try {
|
229
|
-
|
294
|
+
// Handle ISO string with microseconds (which causes problems)
|
295
|
+
if (date.includes('.') && date.includes('T')) {
|
296
|
+
// Extract only up to milliseconds (3 digits after dot) or remove microseconds entirely
|
297
|
+
const parts = date.split('.');
|
298
|
+
if (parts.length > 1) {
|
299
|
+
// If there's a Z or + or - after microseconds, preserve it
|
300
|
+
let timezone = '';
|
301
|
+
const microsecondPart = parts[1];
|
302
|
+
const tzIndex = microsecondPart.search(/[Z+-]/);
|
303
|
+
|
304
|
+
if (tzIndex !== -1) {
|
305
|
+
timezone = microsecondPart.substring(tzIndex);
|
306
|
+
}
|
307
|
+
|
308
|
+
// Use only milliseconds (first 3 digits after dot) or none if format issues
|
309
|
+
const milliseconds = microsecondPart.substring(0, Math.min(3, tzIndex !== -1 ? tzIndex : microsecondPart.length));
|
310
|
+
|
311
|
+
// Reconstruct with controlled precision
|
312
|
+
const cleanedDateStr = parts[0] + (milliseconds.length > 0 ? '.' + milliseconds : '') + timezone;
|
313
|
+
date = new Date(cleanedDateStr);
|
314
|
+
} else {
|
315
|
+
date = new Date(date);
|
316
|
+
}
|
317
|
+
} else {
|
318
|
+
date = new Date(date);
|
319
|
+
}
|
230
320
|
} catch (e) {
|
231
|
-
console.warn(
|
321
|
+
console.warn('Error parsing date string:', e);
|
322
|
+
return 'Invalid date'; // Return error message if we can't parse
|
323
|
+
}
|
324
|
+
}
|
325
|
+
|
326
|
+
// Ensure we're handling the date properly
|
327
|
+
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
328
|
+
console.warn('Invalid date provided to formatDate:', date);
|
329
|
+
return 'Invalid date';
|
330
|
+
}
|
331
|
+
|
332
|
+
// Get current year to compare with date year
|
333
|
+
const currentYear = new Date().getFullYear();
|
334
|
+
const dateYear = date.getFullYear();
|
335
|
+
|
336
|
+
// Get month name, day, and time
|
337
|
+
const month = date.toLocaleString('en-US', { month: 'short' });
|
338
|
+
const day = date.getDate();
|
339
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
340
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
341
|
+
|
342
|
+
// Format like "Feb 25, 08:09" or "Feb 25, 2022, 08:09" if not current year
|
343
|
+
let formattedDate;
|
344
|
+
if (dateYear === currentYear) {
|
345
|
+
formattedDate = `${month} ${day}, ${hours}:${minutes}`;
|
346
|
+
} else {
|
347
|
+
formattedDate = `${month} ${day}, ${dateYear}, ${hours}:${minutes}`;
|
348
|
+
}
|
349
|
+
|
350
|
+
// Add duration if provided
|
351
|
+
if (duration) {
|
352
|
+
let durationText = '';
|
353
|
+
const durationSec = typeof duration === 'number' ? duration : parseInt(duration);
|
354
|
+
|
355
|
+
if (durationSec < 60) {
|
356
|
+
durationText = `${durationSec}s`;
|
357
|
+
} else if (durationSec < 3600) {
|
358
|
+
durationText = `${Math.floor(durationSec / 60)}m ${durationSec % 60}s`;
|
359
|
+
} else {
|
360
|
+
durationText = `${Math.floor(durationSec / 3600)}h ${Math.floor((durationSec % 3600) / 60)}m`;
|
361
|
+
}
|
362
|
+
|
363
|
+
formattedDate += ` (Duration: ${durationText})`;
|
364
|
+
}
|
365
|
+
|
366
|
+
return formattedDate;
|
367
|
+
}
|
368
|
+
|
369
|
+
// Update the socket event handler to fix termination handling
|
370
|
+
window.handleResearchProgressEvent = function(data) {
|
371
|
+
console.log('Research progress update:', data);
|
372
|
+
|
373
|
+
// Extract research ID from the event
|
374
|
+
const eventResearchId = getActiveResearchId();
|
375
|
+
|
376
|
+
// Track processed messages to prevent duplicates
|
377
|
+
window.processedMessages = window.processedMessages || new Set();
|
378
|
+
|
379
|
+
// Add to console log if there's a message
|
380
|
+
if (data.message) {
|
381
|
+
let logType = 'info';
|
382
|
+
|
383
|
+
// Create a unique identifier for this message (message + timestamp if available)
|
384
|
+
const messageId = data.message + (data.log_entry?.time || '');
|
385
|
+
|
386
|
+
// Check if we've already processed this message
|
387
|
+
if (!window.processedMessages.has(messageId)) {
|
388
|
+
window.processedMessages.add(messageId);
|
389
|
+
|
390
|
+
// Determine log type based on status or message content
|
391
|
+
if (data.status === 'failed' || data.status === 'suspended' || data.status === 'terminating') {
|
392
|
+
logType = 'error';
|
393
|
+
} else if (isMilestoneLog(data.message, data.log_entry?.metadata)) {
|
394
|
+
logType = 'milestone';
|
395
|
+
}
|
396
|
+
|
397
|
+
// Store meaningful messages to avoid overwriting with generic messages
|
398
|
+
if (data.message && data.message !== 'Processing research...') {
|
399
|
+
window.lastMeaningfulStatusMessage = data.message;
|
400
|
+
}
|
401
|
+
|
402
|
+
// Extract metadata for search engine information
|
403
|
+
const metadata = data.log_entry?.metadata || null;
|
404
|
+
|
405
|
+
// Pass metadata to addConsoleLog for potential search engine info
|
406
|
+
// Pass the research ID to respect the current viewing context
|
407
|
+
addConsoleLog(data.message, logType, metadata, eventResearchId);
|
232
408
|
}
|
409
|
+
}
|
410
|
+
|
411
|
+
// Add error messages to log
|
412
|
+
if (data.error && !window.processedMessages.has('error:' + data.error)) {
|
413
|
+
window.processedMessages.add('error:' + data.error);
|
414
|
+
addConsoleLog(data.error, 'error', null, eventResearchId);
|
233
415
|
|
234
|
-
//
|
235
|
-
|
236
|
-
|
416
|
+
// Store error as last meaningful message
|
417
|
+
window.lastMeaningfulStatusMessage = data.error;
|
418
|
+
}
|
419
|
+
|
420
|
+
// Update progress UI if progress is provided
|
421
|
+
if (data.progress !== undefined) {
|
422
|
+
const displayMessage = data.message || window.lastMeaningfulStatusMessage || 'Processing research...';
|
423
|
+
updateProgressUI(data.progress, data.status, displayMessage);
|
424
|
+
}
|
425
|
+
|
426
|
+
// Update detail log if log_entry is provided
|
427
|
+
if (data.log_entry) {
|
428
|
+
updateDetailLogEntry(data.log_entry);
|
429
|
+
}
|
430
|
+
|
431
|
+
// Handle status changes
|
432
|
+
if (data.status) {
|
433
|
+
// Special handling for terminating status and handling already terminated research
|
434
|
+
if (data.status === 'terminating') {
|
435
|
+
// Immediately mark as suspended and update UI
|
436
|
+
isResearchInProgress = false;
|
237
437
|
|
238
|
-
//
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
438
|
+
// Update UI state
|
439
|
+
updateTerminationUIState('suspending', data.message || 'Terminating research...');
|
440
|
+
}
|
441
|
+
// Handle suspended research specifically
|
442
|
+
else if (data.status === 'suspended') {
|
443
|
+
console.log('Research was suspended, updating UI directly');
|
444
|
+
|
445
|
+
const researchId = getActiveResearchId();
|
446
|
+
if (!researchId) return;
|
447
|
+
|
448
|
+
// Mark research as not in progress
|
449
|
+
isResearchInProgress = false;
|
450
|
+
|
451
|
+
// Clear polling interval
|
452
|
+
if (pollingInterval) {
|
453
|
+
console.log('Clearing polling interval due to suspension');
|
454
|
+
clearInterval(pollingInterval);
|
455
|
+
pollingInterval = null;
|
456
|
+
}
|
457
|
+
|
458
|
+
// Play error notification sound
|
459
|
+
playNotificationSound('error');
|
460
|
+
|
461
|
+
// Update UI for suspended research
|
462
|
+
updateTerminationUIState('suspended', data.message || 'Research was suspended');
|
463
|
+
|
464
|
+
// Add console log
|
465
|
+
addConsoleLog('Research suspended', 'error');
|
466
|
+
|
467
|
+
// Reset lastMeaningfulStatusMessage for next research
|
468
|
+
window.lastMeaningfulStatusMessage = '';
|
469
|
+
|
470
|
+
// Reset current research ID
|
471
|
+
currentResearchId = null;
|
472
|
+
window.currentResearchId = null;
|
473
|
+
|
474
|
+
// Update navigation
|
475
|
+
updateNavigationBasedOnResearchStatus();
|
476
|
+
|
477
|
+
// Refresh history if on history page
|
478
|
+
if (document.getElementById('history').classList.contains('active')) {
|
479
|
+
loadResearchHistory();
|
480
|
+
}
|
481
|
+
}
|
482
|
+
// Handle completion states
|
483
|
+
else if (data.status === 'completed' || data.status === 'failed') {
|
484
|
+
const researchId = getActiveResearchId();
|
485
|
+
if (!researchId) return;
|
486
|
+
|
487
|
+
// Mark research as not in progress
|
488
|
+
isResearchInProgress = false;
|
489
|
+
|
490
|
+
// Clear polling interval
|
491
|
+
if (pollingInterval) {
|
492
|
+
console.log('Clearing polling interval from socket event');
|
493
|
+
clearInterval(pollingInterval);
|
494
|
+
pollingInterval = null;
|
495
|
+
}
|
496
|
+
|
497
|
+
if (data.status === 'completed') {
|
498
|
+
// Success sound and notification
|
258
499
|
playNotificationSound('success');
|
259
500
|
|
260
|
-
//
|
261
|
-
|
501
|
+
// Store the completed research ID for navigation
|
502
|
+
const completedResearchId = researchId;
|
262
503
|
|
263
|
-
//
|
264
|
-
|
504
|
+
// Reset current research ID
|
505
|
+
currentResearchId = null;
|
506
|
+
window.currentResearchId = null;
|
265
507
|
|
266
|
-
//
|
267
|
-
|
508
|
+
// Reset lastMeaningfulStatusMessage for next research
|
509
|
+
window.lastMeaningfulStatusMessage = '';
|
268
510
|
|
269
|
-
//
|
270
|
-
|
271
|
-
} else if (data.status === 'failed' || data.status === 'suspended') {
|
272
|
-
// Research failed or was suspended
|
273
|
-
console.log(`Socket received research final state: ${data.status}`);
|
274
|
-
|
275
|
-
// Clear polling interval
|
276
|
-
if (pollingInterval) {
|
277
|
-
console.log('Clearing polling interval from socket event');
|
278
|
-
clearInterval(pollingInterval);
|
279
|
-
pollingInterval = null;
|
280
|
-
}
|
281
|
-
|
282
|
-
// Get the error message from the socket response
|
283
|
-
const errorText = data.error || data.message || (data.status === 'failed' ? 'Research failed' : 'Research was suspended');
|
284
|
-
console.log(`Error message from socket: ${errorText}`);
|
285
|
-
|
286
|
-
// Show error message
|
287
|
-
console.log(`Showing error message for status: ${data.status} from socket event`);
|
288
|
-
const errorMessage = document.getElementById('error-message');
|
289
|
-
if (errorMessage) {
|
290
|
-
errorMessage.style.display = 'block';
|
291
|
-
errorMessage.textContent = errorText;
|
292
|
-
}
|
293
|
-
|
294
|
-
// Update UI with status
|
295
|
-
updateProgressUI(
|
296
|
-
0,
|
297
|
-
data.status,
|
298
|
-
errorText
|
299
|
-
);
|
511
|
+
// Update navigation state
|
512
|
+
updateNavigationBasedOnResearchStatus();
|
300
513
|
|
301
|
-
//
|
302
|
-
|
514
|
+
// Navigate to results page with a slight delay to ensure all updates are processed
|
515
|
+
setTimeout(() => {
|
516
|
+
// Load the research results
|
517
|
+
loadResearch(completedResearchId);
|
518
|
+
}, 800);
|
519
|
+
} else {
|
520
|
+
// Error sound and notification
|
521
|
+
playNotificationSound('error');
|
303
522
|
|
304
|
-
//
|
305
|
-
|
523
|
+
// Use the updateTerminationUIState function for consistency
|
524
|
+
updateTerminationUIState('suspended', data.error || `Research was ${data.status}`);
|
306
525
|
|
307
|
-
|
308
|
-
|
309
|
-
playNotificationSound('error');
|
310
|
-
}
|
526
|
+
// Reset lastMeaningfulStatusMessage for next research
|
527
|
+
window.lastMeaningfulStatusMessage = '';
|
311
528
|
|
312
|
-
//
|
313
|
-
|
529
|
+
// Reset current research ID
|
530
|
+
currentResearchId = null;
|
531
|
+
window.currentResearchId = null;
|
314
532
|
|
315
|
-
// Update navigation
|
533
|
+
// Update navigation - important for correctly showing/hiding various elements
|
534
|
+
// based on the current research state
|
316
535
|
updateNavigationBasedOnResearchStatus();
|
317
|
-
|
318
|
-
// Dispatch event for any other components that need to know about completion
|
319
|
-
document.dispatchEvent(new CustomEvent('research_completed', { detail: data }));
|
320
536
|
}
|
321
537
|
|
322
|
-
//
|
323
|
-
if (
|
324
|
-
|
538
|
+
// Refresh the history list to show the completed research
|
539
|
+
if (document.getElementById('history').classList.contains('active')) {
|
540
|
+
loadResearchHistory();
|
325
541
|
}
|
326
|
-
}
|
327
|
-
|
328
|
-
return true;
|
329
|
-
} catch (e) {
|
330
|
-
console.error('Error connecting to research socket:', e);
|
331
|
-
return false;
|
542
|
+
}
|
332
543
|
}
|
333
544
|
};
|
334
545
|
|
@@ -412,225 +623,320 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
412
623
|
|
413
624
|
// Function to start research
|
414
625
|
async function startResearch(query, mode) {
|
415
|
-
//
|
416
|
-
if (
|
417
|
-
|
418
|
-
|
419
|
-
const response = await fetch(getApiUrl('/api/history'));
|
420
|
-
const history = await response.json();
|
421
|
-
|
422
|
-
// Find any in-progress research
|
423
|
-
const activeResearch = history.find(item => item.status === 'in_progress');
|
424
|
-
|
425
|
-
// If no active research is found, reset the state
|
426
|
-
if (!activeResearch) {
|
427
|
-
console.log('Resetting stale research state - no active research found in history');
|
428
|
-
window.cleanupResearchResources();
|
429
|
-
} else {
|
430
|
-
alert('Another research is already in progress. Please wait for it to complete or check its status in the history tab.');
|
431
|
-
return;
|
432
|
-
}
|
433
|
-
} catch (error) {
|
434
|
-
console.error('Error checking for active research:', error);
|
435
|
-
}
|
626
|
+
// First validate that we have a query
|
627
|
+
if (!query || query.trim() === '') {
|
628
|
+
alert('Please enter a query');
|
629
|
+
return;
|
436
630
|
}
|
437
631
|
|
438
|
-
// Get the start button
|
439
|
-
const startResearchBtn = document.getElementById('start-research-btn');
|
440
|
-
if (!startResearchBtn) return;
|
441
|
-
|
442
|
-
// Disable the start button while we attempt to start the research
|
443
|
-
startResearchBtn.disabled = true;
|
444
|
-
startResearchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting...';
|
445
|
-
|
446
632
|
try {
|
447
|
-
//
|
448
|
-
|
633
|
+
// Update button state
|
634
|
+
const startBtn = document.getElementById('start-research-btn');
|
635
|
+
if (startBtn) {
|
636
|
+
startBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting...';
|
637
|
+
startBtn.disabled = true;
|
638
|
+
}
|
639
|
+
|
640
|
+
// Clear any previous research
|
641
|
+
resetResearchState();
|
642
|
+
|
643
|
+
// Set favicon to loading
|
644
|
+
setFavicon('loading');
|
449
645
|
|
646
|
+
// Get the current query element
|
647
|
+
const currentQueryEl = document.getElementById('current-query');
|
648
|
+
if (currentQueryEl) {
|
649
|
+
currentQueryEl.textContent = query;
|
650
|
+
}
|
651
|
+
|
652
|
+
// Create payload
|
653
|
+
const payload = {
|
654
|
+
query: query,
|
655
|
+
mode: mode || 'quick'
|
656
|
+
};
|
657
|
+
|
658
|
+
// Call the API
|
450
659
|
const response = await fetch(getApiUrl('/api/start_research'), {
|
451
660
|
method: 'POST',
|
452
661
|
headers: {
|
453
662
|
'Content-Type': 'application/json'
|
454
663
|
},
|
455
|
-
body: JSON.stringify(
|
456
|
-
query: query,
|
457
|
-
mode: mode
|
458
|
-
})
|
664
|
+
body: JSON.stringify(payload)
|
459
665
|
});
|
460
666
|
|
461
|
-
|
667
|
+
// Parse the response
|
668
|
+
const result = await response.json();
|
462
669
|
|
463
|
-
if (
|
670
|
+
if (result.status === 'success') {
|
671
|
+
// Update the current research ID
|
672
|
+
currentResearchId = result.research_id;
|
673
|
+
window.currentResearchId = result.research_id;
|
674
|
+
|
675
|
+
console.log(`Started research with ID: ${currentResearchId}`);
|
676
|
+
|
677
|
+
// Mark as in progress
|
464
678
|
isResearchInProgress = true;
|
465
|
-
currentResearchId = data.research_id;
|
466
679
|
|
467
|
-
//
|
468
|
-
|
680
|
+
// Hide the try again button if visible
|
681
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
682
|
+
if (tryAgainBtn) {
|
683
|
+
tryAgainBtn.style.display = 'none';
|
684
|
+
}
|
469
685
|
|
470
|
-
//
|
686
|
+
// Reset progress UI
|
687
|
+
updateProgressUI(0, 'in_progress', `Researching: ${query}`);
|
688
|
+
|
689
|
+
// Store query in case we need to display it again
|
690
|
+
window.currentResearchQuery = query;
|
691
|
+
|
692
|
+
// Update navigation
|
471
693
|
updateNavigationBasedOnResearchStatus();
|
472
694
|
|
473
|
-
//
|
474
|
-
document.getElementById('current-query').textContent = query;
|
475
|
-
document.getElementById('progress-fill').style.width = '0%';
|
476
|
-
document.getElementById('progress-percentage').textContent = '0%';
|
477
|
-
document.getElementById('progress-status').textContent = 'Initializing research process...';
|
478
|
-
|
479
|
-
// Navigate to progress page
|
695
|
+
// Navigate to the progress page
|
480
696
|
switchPage('research-progress');
|
481
697
|
|
482
|
-
// Connect to socket for this research
|
483
|
-
window.connectToResearchSocket(
|
698
|
+
// Connect to the socket for this research
|
699
|
+
window.connectToResearchSocket(currentResearchId);
|
484
700
|
|
485
701
|
// Start polling for status
|
486
|
-
pollResearchStatus(
|
487
|
-
|
488
|
-
// Show the terminate button
|
489
|
-
const terminateBtn = document.getElementById('terminate-research-btn');
|
490
|
-
if (terminateBtn) {
|
491
|
-
terminateBtn.style.display = 'inline-flex';
|
492
|
-
terminateBtn.disabled = false;
|
493
|
-
}
|
702
|
+
pollResearchStatus(currentResearchId);
|
494
703
|
} else {
|
495
|
-
//
|
496
|
-
|
704
|
+
// Handle error
|
705
|
+
const errorMessage = result.message || 'Failed to start research';
|
706
|
+
console.error('Research start error:', errorMessage);
|
707
|
+
|
708
|
+
// Add error to log
|
709
|
+
addConsoleLog(`Error: ${errorMessage}`, 'error');
|
710
|
+
|
711
|
+
alert(errorMessage);
|
712
|
+
|
713
|
+
// Reset the favicon
|
714
|
+
setFavicon('default');
|
497
715
|
|
498
|
-
|
499
|
-
|
500
|
-
|
716
|
+
// Reset button state
|
717
|
+
if (startBtn) {
|
718
|
+
startBtn.innerHTML = '<i class="fas fa-rocket"></i> Start Research';
|
719
|
+
startBtn.disabled = false;
|
720
|
+
}
|
501
721
|
}
|
502
722
|
} catch (error) {
|
503
|
-
|
504
|
-
|
723
|
+
console.error('Error starting research:', error);
|
724
|
+
|
725
|
+
// Add error to log
|
726
|
+
addConsoleLog(`Error: ${error.message}`, 'error');
|
727
|
+
|
728
|
+
// Reset the favicon
|
729
|
+
setFavicon('default');
|
730
|
+
|
731
|
+
// Reset button state
|
732
|
+
const startBtn = document.getElementById('start-research-btn');
|
733
|
+
if (startBtn) {
|
734
|
+
startBtn.innerHTML = '<i class="fas fa-rocket"></i> Start Research';
|
735
|
+
startBtn.disabled = false;
|
736
|
+
}
|
505
737
|
|
506
|
-
console.error('Error:', error);
|
507
738
|
alert('An error occurred while starting the research. Please try again.');
|
508
|
-
startResearchBtn.disabled = false;
|
509
|
-
startResearchBtn.innerHTML = '<i class="fas fa-rocket"></i> Start Research';
|
510
739
|
}
|
511
740
|
}
|
512
741
|
|
513
|
-
// Function to
|
742
|
+
// Function to clear any existing polling interval
|
743
|
+
function clearPollingInterval() {
|
744
|
+
if (pollingInterval) {
|
745
|
+
console.log('Clearing existing polling interval');
|
746
|
+
clearInterval(pollingInterval);
|
747
|
+
pollingInterval = null;
|
748
|
+
}
|
749
|
+
}
|
750
|
+
|
751
|
+
// Update the polling function to prevent "Processing research..." from overwriting actual status messages
|
514
752
|
function pollResearchStatus(researchId) {
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
753
|
+
if (!researchId) {
|
754
|
+
console.error('No research ID provided for polling');
|
755
|
+
return;
|
756
|
+
}
|
757
|
+
|
758
|
+
// Reset message deduplication when starting a new poll
|
759
|
+
window.processedMessages = window.processedMessages || new Set();
|
760
|
+
|
761
|
+
// Don't set "Loading research..." here, as we'll set the actual query from the response
|
762
|
+
// Only set loading if currentQuery is empty
|
763
|
+
const currentQueryEl = document.getElementById('current-query');
|
764
|
+
if (currentQueryEl && (!currentQueryEl.textContent || currentQueryEl.textContent === 'Loading research...')) {
|
765
|
+
currentQueryEl.textContent = window.currentResearchQuery || 'Loading research...';
|
766
|
+
}
|
767
|
+
|
768
|
+
console.log(`Starting polling for research ${researchId}`);
|
769
|
+
|
770
|
+
// Store the last real message to avoid overwriting with generic messages
|
771
|
+
window.lastMeaningfulStatusMessage = window.lastMeaningfulStatusMessage || '';
|
772
|
+
|
773
|
+
// Ensure we have a socket connection
|
774
|
+
if (typeof window.connectToResearchSocket === 'function') {
|
775
|
+
window.connectToResearchSocket(researchId);
|
776
|
+
}
|
777
|
+
|
778
|
+
// Set polling interval for updates
|
779
|
+
if (pollingInterval) {
|
780
|
+
clearInterval(pollingInterval);
|
781
|
+
}
|
782
|
+
|
783
|
+
// Make an immediate request
|
784
|
+
checkResearchStatus(researchId);
|
785
|
+
|
786
|
+
// Then set up polling
|
787
|
+
pollingInterval = setInterval(() => {
|
788
|
+
checkResearchStatus(researchId);
|
789
|
+
}, 2000); // Poll every 2 seconds
|
790
|
+
|
791
|
+
// Function to check research status
|
792
|
+
function checkResearchStatus(researchId) {
|
793
|
+
fetch(getApiUrl(`/api/research/${researchId}/details`))
|
794
|
+
.then(response => response.json())
|
795
|
+
.then(data => {
|
796
|
+
// Update the current query with the actual query from the research
|
797
|
+
const currentQueryEl = document.getElementById('current-query');
|
798
|
+
if (currentQueryEl && data.query) {
|
799
|
+
currentQueryEl.textContent = data.query;
|
800
|
+
// Store the query in case we need it later
|
801
|
+
window.currentResearchQuery = data.query;
|
550
802
|
}
|
551
803
|
|
552
|
-
//
|
553
|
-
if (status
|
554
|
-
|
555
|
-
|
556
|
-
const
|
557
|
-
if (terminateBtn) {
|
558
|
-
terminateBtn.style.display = 'none';
|
559
|
-
}
|
560
|
-
|
561
|
-
// Update history item status
|
562
|
-
updateHistoryItemStatus(researchId, 'completed');
|
804
|
+
// Process status update
|
805
|
+
if (data && data.status !== 'error') {
|
806
|
+
// Update UI with progress
|
807
|
+
const progress = data.progress || 0;
|
808
|
+
const status = data.status || 'in_progress';
|
563
809
|
|
564
|
-
//
|
565
|
-
|
566
|
-
|
567
|
-
|
810
|
+
// Get most recent message
|
811
|
+
let message = '';
|
812
|
+
let foundNewMessage = false;
|
813
|
+
let latestMetadata = null;
|
568
814
|
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
const
|
574
|
-
|
575
|
-
|
815
|
+
if (data.log && data.log.length > 0) {
|
816
|
+
// Get the latest unique log entry
|
817
|
+
for (let i = data.log.length - 1; i >= 0; i--) {
|
818
|
+
const latestLog = data.log[i];
|
819
|
+
const messageId = latestLog.message + (latestLog.time || '');
|
820
|
+
|
821
|
+
if (!window.processedMessages.has(messageId)) {
|
822
|
+
window.processedMessages.add(messageId);
|
823
|
+
message = latestLog.message || '';
|
824
|
+
latestMetadata = latestLog.metadata || null;
|
825
|
+
|
826
|
+
// Only update the lastMeaningfulStatusMessage if we have a real message
|
827
|
+
if (message && message !== 'Processing research...') {
|
828
|
+
window.lastMeaningfulStatusMessage = message;
|
829
|
+
foundNewMessage = true;
|
830
|
+
}
|
831
|
+
|
832
|
+
// Add to console logs
|
833
|
+
if (message) {
|
834
|
+
let logType = 'info';
|
835
|
+
if (isMilestoneLog(message, latestLog.metadata)) {
|
836
|
+
logType = 'milestone';
|
837
|
+
} else if (latestLog.type === 'error' || (latestLog.metadata && latestLog.metadata.phase === 'error')) {
|
838
|
+
logType = 'error';
|
839
|
+
} else if (latestLog.type) {
|
840
|
+
logType = latestLog.type; // Use the type from the database if available
|
841
|
+
}
|
842
|
+
addConsoleLog(message, logType, latestMetadata);
|
843
|
+
}
|
844
|
+
|
845
|
+
break; // Only process one message per poll
|
576
846
|
}
|
577
|
-
} catch (e) {
|
578
|
-
console.log('Error parsing metadata:', e);
|
579
847
|
}
|
580
848
|
}
|
581
849
|
|
582
|
-
//
|
583
|
-
const
|
584
|
-
|
585
|
-
errorMessage.style.display = 'block';
|
586
|
-
errorMessage.textContent = errorText;
|
587
|
-
console.log('Set error message to:', errorText);
|
588
|
-
} else {
|
589
|
-
console.error('error-message element not found');
|
590
|
-
}
|
850
|
+
// Use a meaningful message if available; otherwise, keep the last good one
|
851
|
+
const displayMessage = foundNewMessage ? message :
|
852
|
+
(window.lastMeaningfulStatusMessage || 'Processing research...');
|
591
853
|
|
592
|
-
// Update progress
|
593
|
-
updateProgressUI(
|
854
|
+
// Update progress UI
|
855
|
+
updateProgressUI(progress, status, displayMessage);
|
594
856
|
|
595
|
-
// Update
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
857
|
+
// Update the UI based on research status
|
858
|
+
if (status === 'completed' || status === 'failed' || status === 'suspended') {
|
859
|
+
// Clear polling interval
|
860
|
+
if (pollingInterval) {
|
861
|
+
console.log('Clearing polling interval due to status change');
|
862
|
+
clearInterval(pollingInterval);
|
863
|
+
pollingInterval = null;
|
864
|
+
}
|
865
|
+
|
866
|
+
// Handle completion or failure
|
867
|
+
if (status === 'completed') {
|
868
|
+
addConsoleLog('Research completed successfully', 'milestone');
|
869
|
+
playNotificationSound('success');
|
870
|
+
|
871
|
+
// Store the completed research ID for navigation
|
872
|
+
const completedResearchId = researchId;
|
873
|
+
|
874
|
+
// Reset current research ID
|
875
|
+
currentResearchId = null;
|
876
|
+
window.currentResearchId = null;
|
877
|
+
|
878
|
+
// Update navigation state
|
879
|
+
isResearchInProgress = false;
|
880
|
+
updateNavigationBasedOnResearchStatus();
|
881
|
+
|
882
|
+
// Reset lastMeaningfulStatusMessage for next research
|
883
|
+
window.lastMeaningfulStatusMessage = '';
|
884
|
+
|
885
|
+
// Navigate to results page with a slight delay to ensure all updates are processed
|
886
|
+
setTimeout(() => {
|
887
|
+
// Load the research results
|
888
|
+
loadResearch(completedResearchId);
|
889
|
+
}, 800);
|
890
|
+
} else {
|
891
|
+
addConsoleLog(`Research ${status}`, 'error');
|
892
|
+
playNotificationSound('error');
|
893
|
+
|
894
|
+
// Show error message and Try Again button
|
895
|
+
const errorMessage = document.getElementById('error-message');
|
896
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
897
|
+
|
898
|
+
if (errorMessage) {
|
899
|
+
errorMessage.textContent = data.error || `Research was ${status}`;
|
900
|
+
errorMessage.style.display = 'block';
|
901
|
+
}
|
902
|
+
|
903
|
+
if (tryAgainBtn) {
|
904
|
+
tryAgainBtn.style.display = 'block';
|
905
|
+
}
|
906
|
+
|
907
|
+
// Reset lastMeaningfulStatusMessage for next research
|
908
|
+
window.lastMeaningfulStatusMessage = '';
|
909
|
+
|
910
|
+
// Update navigation
|
911
|
+
isResearchInProgress = false;
|
912
|
+
currentResearchId = null;
|
913
|
+
window.currentResearchId = null;
|
914
|
+
updateNavigationBasedOnResearchStatus();
|
915
|
+
}
|
916
|
+
} else {
|
917
|
+
// Research is still in progress
|
918
|
+
isResearchInProgress = true;
|
919
|
+
currentResearchId = researchId;
|
920
|
+
window.currentResearchId = researchId;
|
921
|
+
}
|
626
922
|
}
|
627
|
-
}
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
923
|
+
})
|
924
|
+
.catch(error => {
|
925
|
+
console.error('Error polling research status:', error);
|
926
|
+
// Don't clear the interval on error - just keep trying
|
927
|
+
});
|
928
|
+
}
|
632
929
|
}
|
633
930
|
|
931
|
+
// Function to reset the start research button to its default state
|
932
|
+
function resetStartResearchButton() {
|
933
|
+
const startBtn = document.getElementById('start-research-btn');
|
934
|
+
if (startBtn) {
|
935
|
+
startBtn.innerHTML = '<i class="fas fa-rocket"></i> Start Research';
|
936
|
+
startBtn.disabled = false;
|
937
|
+
}
|
938
|
+
}
|
939
|
+
|
634
940
|
// Main initialization function
|
635
941
|
function initializeApp() {
|
636
942
|
console.log('Initializing application...');
|
@@ -638,14 +944,33 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
638
944
|
// Initialize the sounds
|
639
945
|
initializeSounds();
|
640
946
|
|
947
|
+
// Initialize socket connection
|
948
|
+
initializeSocket();
|
949
|
+
|
641
950
|
// Create a dynamic favicon with the lightning emoji by default
|
642
951
|
createDynamicFavicon('⚡');
|
643
952
|
|
953
|
+
// Add try again button handler
|
954
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
955
|
+
if (tryAgainBtn) {
|
956
|
+
tryAgainBtn.addEventListener('click', function() {
|
957
|
+
// Switch back to the new research page
|
958
|
+
switchPage('new-research');
|
959
|
+
|
960
|
+
// Reset the research state
|
961
|
+
resetResearchState();
|
962
|
+
|
963
|
+
// Reset the Start Research button
|
964
|
+
resetStartResearchButton();
|
965
|
+
});
|
966
|
+
}
|
967
|
+
|
644
968
|
// Get navigation elements
|
645
969
|
const navItems = document.querySelectorAll('.sidebar-nav li');
|
646
970
|
const mobileNavItems = document.querySelectorAll('.mobile-tab-bar li');
|
647
971
|
const pages = document.querySelectorAll('.page');
|
648
972
|
const mobileTabBar = document.querySelector('.mobile-tab-bar');
|
973
|
+
const logo = document.getElementById('logo-link');
|
649
974
|
|
650
975
|
// Handle responsive navigation based on screen size
|
651
976
|
function handleResponsiveNavigation() {
|
@@ -667,6 +992,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
667
992
|
// Add resize listener for responsive design
|
668
993
|
window.addEventListener('resize', handleResponsiveNavigation);
|
669
994
|
|
995
|
+
// Handle logo click
|
996
|
+
if (logo) {
|
997
|
+
logo.addEventListener('click', () => {
|
998
|
+
switchPage('new-research');
|
999
|
+
resetStartResearchButton();
|
1000
|
+
});
|
1001
|
+
}
|
1002
|
+
|
670
1003
|
// Setup navigation click handlers
|
671
1004
|
navItems.forEach(item => {
|
672
1005
|
if (!item.classList.contains('external-link')) {
|
@@ -674,6 +1007,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
674
1007
|
const pageId = this.dataset.page;
|
675
1008
|
if (pageId) {
|
676
1009
|
switchPage(pageId);
|
1010
|
+
// Reset Start Research button when returning to the form
|
1011
|
+
if (pageId === 'new-research') {
|
1012
|
+
resetStartResearchButton();
|
1013
|
+
}
|
677
1014
|
}
|
678
1015
|
});
|
679
1016
|
}
|
@@ -685,6 +1022,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
685
1022
|
const pageId = this.dataset.page;
|
686
1023
|
if (pageId) {
|
687
1024
|
switchPage(pageId);
|
1025
|
+
// Reset Start Research button when returning to the form
|
1026
|
+
if (pageId === 'new-research') {
|
1027
|
+
resetStartResearchButton();
|
1028
|
+
}
|
688
1029
|
}
|
689
1030
|
});
|
690
1031
|
}
|
@@ -738,44 +1079,41 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
738
1079
|
|
739
1080
|
// Function to switch between pages
|
740
1081
|
function switchPage(pageId) {
|
741
|
-
//
|
1082
|
+
// First hide all pages
|
742
1083
|
const pages = document.querySelectorAll('.page');
|
743
|
-
|
744
|
-
const mobileNavItems = document.querySelectorAll('.mobile-tab-bar li');
|
1084
|
+
pages.forEach(page => page.classList.remove('active'));
|
745
1085
|
|
746
|
-
//
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
// Add active class to target page
|
752
|
-
const targetPage = document.getElementById(pageId);
|
753
|
-
if (targetPage) {
|
754
|
-
targetPage.classList.add('active');
|
1086
|
+
// Then activate the selected page
|
1087
|
+
const selectedPage = document.getElementById(pageId);
|
1088
|
+
if (selectedPage) {
|
1089
|
+
selectedPage.classList.add('active');
|
755
1090
|
}
|
756
1091
|
|
757
|
-
// Update
|
758
|
-
|
759
|
-
if (item.getAttribute('data-page') === pageId) {
|
760
|
-
item.classList.add('active');
|
761
|
-
} else {
|
762
|
-
item.classList.remove('active');
|
763
|
-
}
|
764
|
-
});
|
1092
|
+
// Update the URL hash
|
1093
|
+
window.location.hash = '#' + pageId;
|
765
1094
|
|
766
|
-
// Update
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
1095
|
+
// Update the navigation UI to highlight the active page
|
1096
|
+
updateNavigationUI(pageId);
|
1097
|
+
|
1098
|
+
// Clear console logs when switching to pages that don't show research details
|
1099
|
+
if (pageId === 'new-research' || pageId === 'history') {
|
1100
|
+
clearConsoleLogs();
|
1101
|
+
// Reset the viewing research ID when navigating to non-research pages
|
1102
|
+
viewingResearchId = null;
|
1103
|
+
}
|
774
1104
|
|
775
1105
|
// Special handling for history page
|
776
1106
|
if (pageId === 'history') {
|
777
1107
|
loadResearchHistory();
|
778
1108
|
}
|
1109
|
+
|
1110
|
+
// Reset scroll position for the newly activated page
|
1111
|
+
window.scrollTo(0, 0);
|
1112
|
+
|
1113
|
+
console.log(`Switched to page: ${pageId}`);
|
1114
|
+
|
1115
|
+
// Update the log panel visibility
|
1116
|
+
updateLogPanelVisibility(pageId);
|
779
1117
|
}
|
780
1118
|
|
781
1119
|
// Track termination status
|
@@ -794,15 +1132,41 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
794
1132
|
return `/research${path}`;
|
795
1133
|
}
|
796
1134
|
|
797
|
-
// Function to
|
1135
|
+
// Function to properly disconnect all socket connections
|
1136
|
+
function disconnectAllSockets() {
|
1137
|
+
if (socket) {
|
1138
|
+
try {
|
1139
|
+
console.log('Disconnecting all socket connections');
|
1140
|
+
|
1141
|
+
// Get the active research ID
|
1142
|
+
const researchId = getActiveResearchId();
|
1143
|
+
|
1144
|
+
// If there's an active research, unsubscribe first
|
1145
|
+
if (researchId) {
|
1146
|
+
console.log(`Unsubscribing from research ${researchId}`);
|
1147
|
+
socket.emit('unsubscribe_from_research', { research_id: researchId });
|
1148
|
+
}
|
1149
|
+
|
1150
|
+
// Also attempt to disconnect the socket
|
1151
|
+
socket.disconnect();
|
1152
|
+
socket = null;
|
1153
|
+
|
1154
|
+
console.log('Socket disconnected successfully');
|
1155
|
+
} catch (error) {
|
1156
|
+
console.error('Error disconnecting socket:', error);
|
1157
|
+
}
|
1158
|
+
}
|
1159
|
+
}
|
1160
|
+
|
1161
|
+
// Update the terminateResearch function to handle termination more gracefully
|
798
1162
|
async function terminateResearch(researchId) {
|
799
1163
|
if (!researchId) {
|
800
1164
|
console.error('No research ID provided for termination');
|
801
1165
|
return;
|
802
1166
|
}
|
803
|
-
|
1167
|
+
|
804
1168
|
// Prevent multiple termination requests
|
805
|
-
if (
|
1169
|
+
if (document.getElementById('terminate-research-btn')?.disabled) {
|
806
1170
|
console.log('Termination already in progress');
|
807
1171
|
return;
|
808
1172
|
}
|
@@ -812,97 +1176,219 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
812
1176
|
return;
|
813
1177
|
}
|
814
1178
|
|
1179
|
+
console.log(`Attempting to terminate research: ${researchId}`);
|
1180
|
+
|
815
1181
|
try {
|
816
|
-
//
|
817
|
-
isTerminating = true;
|
818
|
-
|
819
|
-
// Update UI to show we're processing
|
1182
|
+
// Get the terminate button
|
820
1183
|
const terminateBtn = document.getElementById('terminate-research-btn');
|
821
1184
|
if (terminateBtn) {
|
1185
|
+
// Disable the button to prevent multiple clicks
|
822
1186
|
terminateBtn.disabled = true;
|
823
1187
|
terminateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Terminating...';
|
824
1188
|
}
|
825
|
-
|
826
|
-
// Find all terminate buttons in history items and disable them
|
827
|
-
const allTerminateBtns = document.querySelectorAll('.terminate-btn');
|
828
|
-
allTerminateBtns.forEach(btn => {
|
829
|
-
if (btn !== terminateBtn) {
|
830
|
-
btn.disabled = true;
|
831
|
-
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Terminating...';
|
832
|
-
}
|
833
|
-
});
|
834
1189
|
|
835
|
-
//
|
1190
|
+
// Update UI to show terminating state immediately
|
1191
|
+
updateTerminationUIState('suspending', 'Terminating research...');
|
1192
|
+
|
1193
|
+
// Add a log entry
|
1194
|
+
addConsoleLog('Terminating research...', 'error');
|
1195
|
+
|
1196
|
+
// Immediately mark the research as terminated in our app state
|
1197
|
+
// so we don't reconnect to it
|
1198
|
+
isResearchInProgress = false;
|
1199
|
+
|
1200
|
+
// Disconnect all sockets to ensure we stop receiving updates
|
1201
|
+
disconnectAllSockets();
|
1202
|
+
|
1203
|
+
// Clear the polling interval immediately
|
1204
|
+
if (pollingInterval) {
|
1205
|
+
clearInterval(pollingInterval);
|
1206
|
+
pollingInterval = null;
|
1207
|
+
console.log('Cleared polling interval during termination');
|
1208
|
+
}
|
1209
|
+
|
1210
|
+
// Call the API to terminate
|
836
1211
|
const response = await fetch(getApiUrl(`/api/research/${researchId}/terminate`), {
|
837
|
-
method: 'POST'
|
1212
|
+
method: 'POST',
|
1213
|
+
headers: {
|
1214
|
+
'Content-Type': 'application/json'
|
1215
|
+
}
|
838
1216
|
});
|
839
1217
|
|
840
1218
|
const data = await response.json();
|
841
1219
|
|
842
1220
|
if (data.status === 'success') {
|
843
|
-
|
844
|
-
console.log('Termination request sent successfully');
|
1221
|
+
console.log(`Research ${researchId} termination requested successfully`);
|
845
1222
|
|
846
|
-
//
|
847
|
-
|
848
|
-
updateHistoryItemStatus(researchId, 'terminating', 'Terminating...');
|
849
|
-
}
|
1223
|
+
// Add termination log
|
1224
|
+
addConsoleLog('Research termination requested. Please wait...', 'error');
|
850
1225
|
|
851
|
-
//
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
1226
|
+
// Immediately update UI for better responsiveness
|
1227
|
+
updateTerminationUIState('suspended', 'Research was terminated');
|
1228
|
+
|
1229
|
+
// Start polling for the suspended status with a more reliable approach
|
1230
|
+
let checkAttempts = 0;
|
1231
|
+
const maxAttempts = 3; // Reduced from 5
|
1232
|
+
const checkInterval = setInterval(async () => {
|
1233
|
+
checkAttempts++;
|
1234
|
+
console.log(`Checking termination status (attempt ${checkAttempts}/${maxAttempts})...`);
|
1235
|
+
|
1236
|
+
try {
|
1237
|
+
const statusResponse = await fetch(getApiUrl(`/api/research/${researchId}`));
|
1238
|
+
const statusData = await statusResponse.json();
|
1239
|
+
|
1240
|
+
// Check for actual termination status in the response
|
1241
|
+
if (statusData.status === 'suspended' || statusData.status === 'failed') {
|
1242
|
+
console.log(`Research is now ${statusData.status}, updating UI`);
|
1243
|
+
clearInterval(checkInterval);
|
1244
|
+
|
1245
|
+
// Reset research state
|
1246
|
+
currentResearchId = null;
|
1247
|
+
window.currentResearchId = null;
|
1248
|
+
|
1249
|
+
// Disconnect any remaining sockets again, just to be sure
|
1250
|
+
disconnectAllSockets();
|
1251
|
+
|
1252
|
+
// Update navigation
|
1253
|
+
updateNavigationBasedOnResearchStatus();
|
1254
|
+
|
1255
|
+
return;
|
1256
|
+
}
|
857
1257
|
|
858
|
-
//
|
859
|
-
|
1258
|
+
// If we reach the maximum attempts but status isn't updated yet
|
1259
|
+
if (checkAttempts >= maxAttempts) {
|
1260
|
+
console.log('Max termination check attempts reached, forcing UI update');
|
1261
|
+
clearInterval(checkInterval);
|
1262
|
+
|
1263
|
+
// Force update to suspended state even if backend hasn't caught up yet
|
1264
|
+
currentResearchId = null;
|
1265
|
+
window.currentResearchId = null;
|
1266
|
+
|
1267
|
+
// Disconnect any remaining sockets
|
1268
|
+
disconnectAllSockets();
|
1269
|
+
|
1270
|
+
// Update database status directly with a second termination request
|
1271
|
+
try {
|
1272
|
+
console.log('Sending second termination request to ensure completion');
|
1273
|
+
await fetch(getApiUrl(`/api/research/${researchId}/terminate`), {
|
1274
|
+
method: 'POST',
|
1275
|
+
headers: { 'Content-Type': 'application/json' }
|
1276
|
+
});
|
1277
|
+
} catch (secondError) {
|
1278
|
+
console.error('Error sending second termination request:', secondError);
|
1279
|
+
}
|
1280
|
+
|
1281
|
+
updateNavigationBasedOnResearchStatus();
|
1282
|
+
}
|
1283
|
+
} catch (checkError) {
|
1284
|
+
console.error(`Error checking termination status: ${checkError}`);
|
1285
|
+
if (checkAttempts >= maxAttempts) {
|
1286
|
+
clearInterval(checkInterval);
|
1287
|
+
updateTerminationUIState('error', 'Error checking termination status');
|
1288
|
+
}
|
860
1289
|
}
|
861
|
-
},
|
862
|
-
} else {
|
863
|
-
console.error('Termination request failed:', data.message);
|
864
|
-
alert(`Failed to terminate research: ${data.message}`);
|
1290
|
+
}, 300); // Check faster for more responsive feedback
|
865
1291
|
|
866
|
-
|
867
|
-
|
1292
|
+
} else {
|
1293
|
+
console.error(`Error terminating research: ${data.message}`);
|
1294
|
+
addConsoleLog(`Error terminating research: ${data.message}`, 'error');
|
868
1295
|
|
869
|
-
//
|
1296
|
+
// Re-enable the button
|
870
1297
|
if (terminateBtn) {
|
871
1298
|
terminateBtn.disabled = false;
|
872
1299
|
terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
|
873
1300
|
}
|
874
|
-
|
875
|
-
// Reset history button states
|
876
|
-
const allTerminateBtns = document.querySelectorAll('.terminate-btn');
|
877
|
-
allTerminateBtns.forEach(btn => {
|
878
|
-
if (btn !== terminateBtn) {
|
879
|
-
btn.disabled = false;
|
880
|
-
btn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate';
|
881
|
-
}
|
882
|
-
});
|
883
1301
|
}
|
884
1302
|
} catch (error) {
|
885
|
-
console.error(
|
886
|
-
|
1303
|
+
console.error(`Error in terminate request: ${error}`);
|
1304
|
+
addConsoleLog(`Error in terminate request: ${error}`, 'error');
|
887
1305
|
|
888
|
-
//
|
889
|
-
isTerminating = false;
|
890
|
-
|
891
|
-
// Reset the button
|
1306
|
+
// Re-enable the button
|
892
1307
|
const terminateBtn = document.getElementById('terminate-research-btn');
|
893
1308
|
if (terminateBtn) {
|
894
1309
|
terminateBtn.disabled = false;
|
895
1310
|
terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
|
896
1311
|
}
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
1312
|
+
}
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
// Helper function to update UI elements during termination
|
1316
|
+
function updateTerminationUIState(state, message) {
|
1317
|
+
const terminateBtn = document.getElementById('terminate-research-btn');
|
1318
|
+
const errorMessage = document.getElementById('error-message');
|
1319
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
1320
|
+
const progressStatus = document.getElementById('progress-status');
|
1321
|
+
const progressBar = document.getElementById('progress-bar');
|
1322
|
+
|
1323
|
+
switch (state) {
|
1324
|
+
case 'suspending':
|
1325
|
+
if (progressStatus) {
|
1326
|
+
progressStatus.textContent = 'Terminating research...';
|
1327
|
+
progressStatus.className = 'progress-status status-terminating fade-in';
|
904
1328
|
}
|
905
|
-
|
1329
|
+
if (progressBar) {
|
1330
|
+
progressBar.classList.remove('suspended-status');
|
1331
|
+
}
|
1332
|
+
if (terminateBtn) {
|
1333
|
+
terminateBtn.disabled = true;
|
1334
|
+
terminateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Terminating...';
|
1335
|
+
}
|
1336
|
+
if (errorMessage) {
|
1337
|
+
errorMessage.style.display = 'none';
|
1338
|
+
}
|
1339
|
+
if (tryAgainBtn) {
|
1340
|
+
tryAgainBtn.style.display = 'none';
|
1341
|
+
}
|
1342
|
+
break;
|
1343
|
+
|
1344
|
+
case 'suspended':
|
1345
|
+
if (progressStatus) {
|
1346
|
+
progressStatus.innerHTML = '<i class="fas fa-exclamation-triangle termination-icon"></i> ' + (message || 'Research was suspended');
|
1347
|
+
progressStatus.className = 'progress-status status-failed fade-in';
|
1348
|
+
}
|
1349
|
+
if (progressBar) {
|
1350
|
+
progressBar.classList.add('suspended-status');
|
1351
|
+
}
|
1352
|
+
if (terminateBtn) {
|
1353
|
+
terminateBtn.style.display = 'none';
|
1354
|
+
}
|
1355
|
+
if (errorMessage) {
|
1356
|
+
// Hide the error message box completely
|
1357
|
+
errorMessage.style.display = 'none';
|
1358
|
+
}
|
1359
|
+
if (tryAgainBtn) {
|
1360
|
+
tryAgainBtn.style.display = 'block';
|
1361
|
+
// Update try again button to be more attractive
|
1362
|
+
tryAgainBtn.innerHTML = '<i class="fas fa-sync-alt"></i> Try Again';
|
1363
|
+
tryAgainBtn.className = 'btn btn-primary fade-in';
|
1364
|
+
}
|
1365
|
+
|
1366
|
+
// Update page title to show suspension
|
1367
|
+
document.title = '⚠️ Research Suspended - Local Deep Research';
|
1368
|
+
|
1369
|
+
break;
|
1370
|
+
|
1371
|
+
case 'error':
|
1372
|
+
if (progressStatus) {
|
1373
|
+
progressStatus.innerHTML = '<i class="fas fa-exclamation-circle termination-icon"></i>' + (message || 'Error terminating research');
|
1374
|
+
progressStatus.className = 'progress-status status-failed fade-in';
|
1375
|
+
}
|
1376
|
+
if (progressBar) {
|
1377
|
+
progressBar.classList.remove('suspended-status');
|
1378
|
+
}
|
1379
|
+
if (terminateBtn) {
|
1380
|
+
terminateBtn.disabled = false;
|
1381
|
+
terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
|
1382
|
+
}
|
1383
|
+
if (errorMessage) {
|
1384
|
+
errorMessage.innerHTML = '<i class="fas fa-exclamation-circle termination-icon"></i>' + (message || 'Error terminating research');
|
1385
|
+
errorMessage.style.display = 'block';
|
1386
|
+
errorMessage.className = 'error-message fade-in';
|
1387
|
+
}
|
1388
|
+
if (tryAgainBtn) {
|
1389
|
+
tryAgainBtn.style.display = 'none';
|
1390
|
+
}
|
1391
|
+
break;
|
906
1392
|
}
|
907
1393
|
}
|
908
1394
|
|
@@ -1211,6 +1697,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1211
1697
|
|
1212
1698
|
// Function to navigate to research progress
|
1213
1699
|
function navigateToResearchProgress(research) {
|
1700
|
+
// Set the current viewing research ID
|
1701
|
+
viewingResearchId = research.id;
|
1702
|
+
|
1703
|
+
// First check if the research is already terminated/suspended
|
1704
|
+
if (research.status === 'suspended' || research.status === 'failed') {
|
1705
|
+
// Switch to the progress page with terminated state
|
1706
|
+
switchPage('research-progress');
|
1707
|
+
|
1708
|
+
// Show the query
|
1709
|
+
document.getElementById('current-query').textContent = research.query || '';
|
1710
|
+
|
1711
|
+
// Update UI for terminated state
|
1712
|
+
updateTerminationUIState('suspended', `Research was ${research.status}`);
|
1713
|
+
|
1714
|
+
// Update progress percentage
|
1715
|
+
const progress = research.progress || 0;
|
1716
|
+
document.getElementById('progress-fill').style.width = `${progress}%`;
|
1717
|
+
document.getElementById('progress-percentage').textContent = `${progress}%`;
|
1718
|
+
|
1719
|
+
// Load logs for this research
|
1720
|
+
loadLogsForResearch(research.id);
|
1721
|
+
|
1722
|
+
// Don't connect to socket or start polling for terminated research
|
1723
|
+
return;
|
1724
|
+
}
|
1725
|
+
|
1214
1726
|
document.getElementById('current-query').textContent = research.query;
|
1215
1727
|
document.getElementById('progress-fill').style.width = `${research.progress || 0}%`;
|
1216
1728
|
document.getElementById('progress-percentage').textContent = `${research.progress || 0}%`;
|
@@ -1218,6 +1730,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1218
1730
|
// Navigate to progress page
|
1219
1731
|
switchPage('research-progress');
|
1220
1732
|
|
1733
|
+
// Load logs for this research
|
1734
|
+
loadLogsForResearch(research.id);
|
1735
|
+
|
1221
1736
|
// Connect to socket for this research
|
1222
1737
|
window.connectToResearchSocket(research.id);
|
1223
1738
|
|
@@ -1244,165 +1759,204 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1244
1759
|
}
|
1245
1760
|
}
|
1246
1761
|
|
1247
|
-
//
|
1762
|
+
// Update the loadResearch function to handle terminated/suspended research better
|
1248
1763
|
async function loadResearch(researchId) {
|
1249
1764
|
try {
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
const reportResponse = await fetch(getApiUrl(`/api/report/${researchId}`));
|
1263
|
-
if (!reportResponse.ok) {
|
1264
|
-
throw new Error('Failed to load report');
|
1265
|
-
}
|
1266
|
-
|
1267
|
-
const reportData = await reportResponse.json();
|
1268
|
-
|
1269
|
-
if (reportData.status === 'success') {
|
1270
|
-
// Update UI with report content
|
1271
|
-
document.getElementById('result-query').textContent = data.query || 'Unknown query';
|
1272
|
-
document.getElementById('result-mode').textContent = data.mode === 'detailed' ? 'Detailed Report' : 'Quick Summary';
|
1273
|
-
|
1274
|
-
// Format date
|
1275
|
-
const reportDate = data.completed_at ? formatDate(new Date(data.completed_at)) : 'Unknown';
|
1276
|
-
document.getElementById('result-date').textContent = reportDate;
|
1277
|
-
|
1278
|
-
// Render markdown content
|
1279
|
-
const resultsContent = document.getElementById('results-content');
|
1280
|
-
if (resultsContent) {
|
1281
|
-
resultsContent.innerHTML = '';
|
1282
|
-
resultsContent.classList.add('markdown-body');
|
1765
|
+
console.log(`Loading research results for research ID: ${researchId}`);
|
1766
|
+
|
1767
|
+
// Set the current viewing research ID
|
1768
|
+
viewingResearchId = researchId;
|
1769
|
+
|
1770
|
+
// Get research data first to check status
|
1771
|
+
fetch(getApiUrl(`/api/research/${researchId}`))
|
1772
|
+
.then(response => response.json())
|
1773
|
+
.then(researchData => {
|
1774
|
+
// Check if research was terminated or failed
|
1775
|
+
if (researchData.status === 'suspended' || researchData.status === 'failed') {
|
1776
|
+
console.log(`Research ${researchId} was ${researchData.status}, not loading results`);
|
1283
1777
|
|
1284
|
-
//
|
1285
|
-
const
|
1286
|
-
|
1778
|
+
// Switch to research progress page if not already there
|
1779
|
+
const progressPage = document.getElementById('research-progress');
|
1780
|
+
if (!progressPage.classList.contains('active')) {
|
1781
|
+
switchPage('research-progress');
|
1782
|
+
}
|
1287
1783
|
|
1288
|
-
//
|
1289
|
-
document.
|
1290
|
-
|
1291
|
-
|
1784
|
+
// Show error message and Try Again button
|
1785
|
+
const errorMessage = document.getElementById('error-message');
|
1786
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
1787
|
+
|
1788
|
+
if (errorMessage) {
|
1789
|
+
errorMessage.textContent = researchData.error || `Research was ${researchData.status}`;
|
1790
|
+
errorMessage.style.display = 'block';
|
1791
|
+
}
|
1792
|
+
|
1793
|
+
if (tryAgainBtn) {
|
1794
|
+
tryAgainBtn.style.display = 'block';
|
1795
|
+
}
|
1796
|
+
|
1797
|
+
// Update UI elements for this research
|
1798
|
+
document.getElementById('current-query').textContent = researchData.query || 'Unknown query';
|
1799
|
+
|
1800
|
+
// Update progress bar
|
1801
|
+
const progressFill = document.getElementById('progress-fill');
|
1802
|
+
const progressPercentage = document.getElementById('progress-percentage');
|
1803
|
+
if (progressFill) progressFill.style.width = '0%';
|
1804
|
+
if (progressPercentage) progressPercentage.textContent = '0%';
|
1805
|
+
|
1806
|
+
// Load logs for this research
|
1807
|
+
loadLogsForResearch(researchId);
|
1808
|
+
|
1809
|
+
return; // Exit early, no need to load report for terminated research
|
1292
1810
|
}
|
1293
1811
|
|
1294
|
-
//
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1812
|
+
// Normal flow for completed research
|
1813
|
+
fetch(getApiUrl(`/api/report/${researchId}`))
|
1814
|
+
.then(response => response.json())
|
1815
|
+
.then(data => {
|
1816
|
+
if (data.status === 'error') {
|
1817
|
+
throw new Error('Research report not found');
|
1818
|
+
}
|
1819
|
+
|
1820
|
+
if (!data.content) {
|
1821
|
+
console.error('No report content found in research data');
|
1822
|
+
throw new Error('Report content is empty');
|
1823
|
+
}
|
1824
|
+
|
1825
|
+
// Set the report content and metadata
|
1826
|
+
document.getElementById('result-query').textContent = researchData.query || 'Unknown Query';
|
1827
|
+
document.getElementById('result-date').textContent = formatDate(
|
1828
|
+
researchData.completed_at,
|
1829
|
+
researchData.duration_seconds
|
1830
|
+
);
|
1831
|
+
document.getElementById('result-mode').textContent = formatMode(researchData.mode);
|
1832
|
+
|
1833
|
+
// Update duration if available (for backward compatibility with existing UI elements)
|
1834
|
+
if (researchData.created_at && researchData.completed_at && !researchData.duration_seconds) {
|
1835
|
+
// Calculate duration if it's not provided by the API
|
1836
|
+
const startDate = new Date(researchData.created_at);
|
1837
|
+
const endDate = new Date(researchData.completed_at);
|
1838
|
+
const durationSec = Math.floor((endDate - startDate) / 1000);
|
1839
|
+
|
1840
|
+
// Update the date display with calculated duration
|
1841
|
+
document.getElementById('result-date').textContent = formatDate(
|
1842
|
+
researchData.completed_at,
|
1843
|
+
durationSec
|
1844
|
+
);
|
1845
|
+
|
1846
|
+
// Also update any UI elements that might rely on updateResearchDuration
|
1847
|
+
const metadata = {
|
1848
|
+
started_at: researchData.created_at,
|
1849
|
+
completed_at: researchData.completed_at
|
1850
|
+
};
|
1851
|
+
updateResearchDuration(metadata);
|
1852
|
+
}
|
1853
|
+
|
1854
|
+
// Render the content
|
1855
|
+
const resultsContent = document.getElementById('results-content');
|
1856
|
+
resultsContent.innerHTML = ''; // Clear any previous content
|
1857
|
+
|
1858
|
+
// Convert markdown to HTML
|
1859
|
+
const htmlContent = marked.parse(data.content);
|
1860
|
+
resultsContent.innerHTML = htmlContent;
|
1861
|
+
|
1862
|
+
// Apply code highlighting
|
1863
|
+
document.querySelectorAll('pre code').forEach((block) => {
|
1864
|
+
hljs.highlightBlock(block);
|
1865
|
+
});
|
1866
|
+
|
1867
|
+
// Load logs for this research
|
1868
|
+
loadLogsForResearch(researchId);
|
1869
|
+
|
1870
|
+
// Switch to the results page
|
1871
|
+
switchPage('research-results');
|
1872
|
+
})
|
1873
|
+
.catch(error => {
|
1874
|
+
console.error(`Error loading research: ${error}`);
|
1875
|
+
});
|
1876
|
+
})
|
1877
|
+
.catch(error => {
|
1878
|
+
console.error(`Error checking research status: ${error}`);
|
1879
|
+
});
|
1309
1880
|
} catch (error) {
|
1310
|
-
console.error(
|
1311
|
-
alert('Error loading research: ' + error.message);
|
1312
|
-
// Reset favicon to default on error
|
1313
|
-
createDynamicFavicon('⚡');
|
1881
|
+
console.error(`Error loading research: ${error}`);
|
1314
1882
|
}
|
1315
1883
|
}
|
1316
1884
|
|
1317
1885
|
// Function to load research details
|
1318
1886
|
async function loadResearchDetails(researchId) {
|
1319
1887
|
try {
|
1320
|
-
//
|
1321
|
-
|
1888
|
+
// Show loading indicators
|
1889
|
+
document.getElementById('research-log').innerHTML = '<div class="loading-spinner centered"><div class="spinner"></div></div>';
|
1322
1890
|
|
1323
|
-
|
1324
|
-
|
1891
|
+
// Set the current viewing research ID
|
1892
|
+
viewingResearchId = researchId;
|
1325
1893
|
|
1894
|
+
// Fetch the research data
|
1326
1895
|
const response = await fetch(getApiUrl(`/api/research/${researchId}/details`));
|
1327
|
-
if (!response.ok) {
|
1328
|
-
throw new Error('Failed to load research details');
|
1329
|
-
}
|
1330
|
-
|
1331
1896
|
const data = await response.json();
|
1332
1897
|
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1898
|
+
if (data.status === 'success') {
|
1899
|
+
// Update the metadata display
|
1900
|
+
document.getElementById('detail-query').textContent = data.query || 'Unknown query';
|
1901
|
+
document.getElementById('detail-status').textContent = formatStatus(data.status);
|
1902
|
+
document.getElementById('detail-mode').textContent = formatMode(data.mode);
|
1903
|
+
|
1904
|
+
// Update progress percentage
|
1905
|
+
const progressFill = document.getElementById('detail-progress-fill');
|
1906
|
+
const progressPercentage = document.getElementById('detail-progress-percentage');
|
1907
|
+
|
1908
|
+
if (progressFill && progressPercentage) {
|
1909
|
+
const progress = data.progress || 0;
|
1910
|
+
progressFill.style.width = `${progress}%`;
|
1911
|
+
progressPercentage.textContent = `${progress}%`;
|
1912
|
+
}
|
1913
|
+
|
1914
|
+
// Update duration if available
|
1915
|
+
const metadata = {
|
1916
|
+
created_at: data.created_at,
|
1917
|
+
completed_at: data.completed_at
|
1918
|
+
};
|
1919
|
+
updateResearchDuration(metadata);
|
1920
|
+
|
1921
|
+
// Render the log entries from the response directly
|
1922
|
+
if (data.log && Array.isArray(data.log)) {
|
1923
|
+
renderResearchLog(data.log, researchId);
|
1924
|
+
} else {
|
1925
|
+
// Fallback to the dedicated logs endpoint if log data is missing
|
1926
|
+
try {
|
1927
|
+
const logResponse = await fetch(getApiUrl(`/api/research/${researchId}/logs`));
|
1928
|
+
const logData = await logResponse.json();
|
1929
|
+
|
1930
|
+
if (logData.status === 'success' && logData.logs && Array.isArray(logData.logs)) {
|
1931
|
+
renderResearchLog(logData.logs, researchId);
|
1932
|
+
} else {
|
1933
|
+
document.getElementById('research-log').innerHTML = '<div class="empty-state">No log entries available</div>';
|
1934
|
+
}
|
1935
|
+
} catch (logError) {
|
1936
|
+
console.error('Error fetching logs:', logError);
|
1937
|
+
document.getElementById('research-log').innerHTML = '<div class="empty-state">Failed to load log entries</div>';
|
1938
|
+
}
|
1939
|
+
}
|
1940
|
+
|
1941
|
+
// Update detail actions based on research status
|
1942
|
+
updateDetailActions(data);
|
1943
|
+
|
1944
|
+
// Load logs for the console panel as well
|
1945
|
+
loadLogsForResearch(researchId);
|
1946
|
+
|
1947
|
+
// Switch to the details page
|
1948
|
+
switchPage('research-details');
|
1355
1949
|
} else {
|
1356
|
-
|
1357
|
-
}
|
1358
|
-
|
1359
|
-
// Update actions based on status
|
1360
|
-
const actionsContainer = document.getElementById('detail-actions');
|
1361
|
-
actionsContainer.innerHTML = '';
|
1362
|
-
|
1363
|
-
if (data.status === 'completed' && data.report_path) {
|
1364
|
-
// Add button to view results
|
1365
|
-
const viewResultsBtn = document.createElement('button');
|
1366
|
-
viewResultsBtn.className = 'btn btn-primary';
|
1367
|
-
viewResultsBtn.innerHTML = '<i class="fas fa-eye"></i> View Results';
|
1368
|
-
viewResultsBtn.addEventListener('click', () => loadResearch(researchId));
|
1369
|
-
actionsContainer.appendChild(viewResultsBtn);
|
1370
|
-
} else if (data.status === 'in_progress') {
|
1371
|
-
// Add button to view progress
|
1372
|
-
const viewProgressBtn = document.createElement('button');
|
1373
|
-
viewProgressBtn.className = 'btn btn-primary';
|
1374
|
-
viewProgressBtn.innerHTML = '<i class="fas fa-spinner"></i> View Progress';
|
1375
|
-
viewProgressBtn.addEventListener('click', () => navigateToResearchProgress(data));
|
1376
|
-
actionsContainer.appendChild(viewProgressBtn);
|
1377
|
-
|
1378
|
-
// Add button to terminate research
|
1379
|
-
const terminateBtn = document.createElement('button');
|
1380
|
-
terminateBtn.className = 'btn btn-outline terminate-btn';
|
1381
|
-
terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
|
1382
|
-
terminateBtn.addEventListener('click', () => terminateResearch(researchId));
|
1383
|
-
actionsContainer.appendChild(terminateBtn);
|
1950
|
+
alert('Failed to load research details: ' + (data.message || 'Unknown error'));
|
1384
1951
|
}
|
1385
|
-
|
1386
|
-
// Add delete button for all research items
|
1387
|
-
const deleteBtn = document.createElement('button');
|
1388
|
-
deleteBtn.className = 'btn btn-outline delete-btn';
|
1389
|
-
deleteBtn.innerHTML = '<i class="fas fa-trash"></i> Delete Research';
|
1390
|
-
deleteBtn.addEventListener('click', () => {
|
1391
|
-
if (confirm('Are you sure you want to delete this research? This action cannot be undone.')) {
|
1392
|
-
deleteResearch(researchId);
|
1393
|
-
}
|
1394
|
-
});
|
1395
|
-
actionsContainer.appendChild(deleteBtn);
|
1396
1952
|
} catch (error) {
|
1397
1953
|
console.error('Error loading research details:', error);
|
1398
|
-
|
1399
|
-
// Reset favicon to default on error
|
1400
|
-
createDynamicFavicon('⚡');
|
1954
|
+
alert('An error occurred while loading the research details');
|
1401
1955
|
}
|
1402
1956
|
}
|
1403
1957
|
|
1404
1958
|
// Function to render log entries
|
1405
|
-
function
|
1959
|
+
function renderResearchLog(logEntries, researchId) {
|
1406
1960
|
const researchLog = document.getElementById('research-log');
|
1407
1961
|
researchLog.innerHTML = '';
|
1408
1962
|
|
@@ -1480,8 +2034,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1480
2034
|
}
|
1481
2035
|
|
1482
2036
|
// Connect to socket for updates if this is an in-progress research
|
1483
|
-
|
1484
|
-
|
2037
|
+
// Check for research ID and whether it's in progress by checking the database status
|
2038
|
+
if (researchId) {
|
2039
|
+
// Check research status in the database to determine if we should connect to socket
|
2040
|
+
fetch(getApiUrl(`/api/research/${researchId}`))
|
2041
|
+
.then(response => response.json())
|
2042
|
+
.then(data => {
|
2043
|
+
if (data && data.status === 'in_progress') {
|
2044
|
+
console.log(`Connecting to socket for research ${researchId} from log view`);
|
2045
|
+
window.connectToResearchSocket(researchId);
|
2046
|
+
}
|
2047
|
+
})
|
2048
|
+
.catch(err => console.error(`Error checking research status for socket connection: ${err}`));
|
1485
2049
|
}
|
1486
2050
|
}
|
1487
2051
|
|
@@ -1540,31 +2104,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1540
2104
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
1541
2105
|
}
|
1542
2106
|
|
1543
|
-
function formatDate(date) {
|
1544
|
-
// Ensure we're handling the date properly
|
1545
|
-
if (!(date instanceof Date) || isNaN(date)) {
|
1546
|
-
console.warn('Invalid date provided to formatDate:', date);
|
1547
|
-
return 'Invalid date';
|
1548
|
-
}
|
1549
|
-
|
1550
|
-
// Get current year to compare with date year
|
1551
|
-
const currentYear = new Date().getFullYear();
|
1552
|
-
const dateYear = date.getFullYear();
|
1553
|
-
|
1554
|
-
// Get month name, day, and time
|
1555
|
-
const month = date.toLocaleString('en-US', { month: 'short' });
|
1556
|
-
const day = date.getDate();
|
1557
|
-
const hours = date.getHours().toString().padStart(2, '0');
|
1558
|
-
const minutes = date.getMinutes().toString().padStart(2, '0');
|
1559
|
-
|
1560
|
-
// Format like "Feb 25, 08:09" or "Feb 25, 2022, 08:09" if not current year
|
1561
|
-
if (dateYear === currentYear) {
|
1562
|
-
return `${month} ${day}, ${hours}:${minutes}`;
|
1563
|
-
} else {
|
1564
|
-
return `${month} ${day}, ${dateYear}, ${hours}:${minutes}`;
|
1565
|
-
}
|
1566
|
-
}
|
1567
|
-
|
1568
2107
|
// Function to update progress UI from current research
|
1569
2108
|
function updateProgressFromCurrentResearch() {
|
1570
2109
|
if (!isResearchInProgress || !currentResearchId) return;
|
@@ -1587,148 +2126,99 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1587
2126
|
|
1588
2127
|
// Function to update the sidebar navigation based on research status
|
1589
2128
|
function updateNavigationBasedOnResearchStatus() {
|
1590
|
-
|
1591
|
-
|
1592
|
-
console.log("currentResearchId:", currentResearchId);
|
2129
|
+
const isResearchPage = document.getElementById('research-progress').classList.contains('active');
|
2130
|
+
const isAnyResearchInProgress = window.currentResearchId !== null && isResearchInProgress;
|
1593
2131
|
|
1594
|
-
// Get nav items
|
1595
|
-
const
|
1596
|
-
const
|
1597
|
-
|
1598
|
-
const
|
2132
|
+
// Get the sidebar nav items and mobile tab items
|
2133
|
+
const sidebarNewResearchItem = document.querySelector('.sidebar-nav li[data-page="new-research"]');
|
2134
|
+
const sidebarHistoryItem = document.querySelector('.sidebar-nav li[data-page="history"]');
|
2135
|
+
const mobileNewResearchItem = document.querySelector('.mobile-tab-bar li[data-page="new-research"]');
|
2136
|
+
const mobileHistoryItem = document.querySelector('.mobile-tab-bar li[data-page="history"]');
|
1599
2137
|
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
);
|
2138
|
+
// Control elements
|
2139
|
+
const terminateBtn = document.getElementById('terminate-research-btn');
|
2140
|
+
const errorMessage = document.getElementById('error-message');
|
2141
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
1605
2142
|
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
pages.forEach(page => page.classList.remove('active'));
|
1621
|
-
document.getElementById('research-progress').classList.add('active');
|
1622
|
-
|
1623
|
-
// Update the research progress page
|
1624
|
-
updateProgressFromCurrentResearch();
|
1625
|
-
}
|
1626
|
-
} else {
|
1627
|
-
console.log("Research is not in progress, resetting navigation");
|
1628
|
-
// Reset to "New Research" if there's no active research
|
1629
|
-
newResearchNav.innerHTML = '<i class="fas fa-search"></i> New Research';
|
1630
|
-
|
1631
|
-
// Reset the listener
|
1632
|
-
if (newResearchNav.hasAttribute('data-original-page')) {
|
1633
|
-
newResearchNav.setAttribute('data-page', newResearchNav.getAttribute('data-original-page'));
|
1634
|
-
newResearchNav.removeAttribute('data-original-page');
|
1635
|
-
}
|
1636
|
-
|
1637
|
-
// Reset progress UI elements
|
1638
|
-
const progressFill = document.getElementById('progress-fill');
|
1639
|
-
const progressPercentage = document.getElementById('progress-percentage');
|
1640
|
-
const progressStatus = document.getElementById('progress-status');
|
1641
|
-
|
1642
|
-
if (progressFill) progressFill.style.width = '0%';
|
1643
|
-
if (progressPercentage) progressPercentage.textContent = '0%';
|
1644
|
-
if (progressStatus) {
|
1645
|
-
const errorMessage = document.getElementById('error-message');
|
1646
|
-
if (errorMessage && errorMessage.style.display === 'block') {
|
1647
|
-
// If there's an error message displayed, show a more informative status
|
1648
|
-
progressStatus.textContent = 'Research process encountered an error. Please check the error message above and try again.';
|
1649
|
-
progressStatus.classList.add('status-failed');
|
1650
|
-
} else {
|
1651
|
-
progressStatus.textContent = 'Start a new research query when you\'re ready...';
|
1652
|
-
progressStatus.classList.remove('status-failed');
|
1653
|
-
}
|
1654
|
-
}
|
1655
|
-
|
1656
|
-
// If the terminate button is visible, hide it
|
1657
|
-
const terminateBtn = document.getElementById('terminate-research-btn');
|
1658
|
-
if (terminateBtn) {
|
1659
|
-
terminateBtn.style.display = 'none';
|
1660
|
-
terminateBtn.disabled = false;
|
1661
|
-
terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
|
1662
|
-
}
|
1663
|
-
|
1664
|
-
// Also hide error message if visible
|
1665
|
-
const errorMessage = document.getElementById('error-message');
|
1666
|
-
if (errorMessage) {
|
1667
|
-
errorMessage.style.display = 'none';
|
1668
|
-
errorMessage.textContent = '';
|
1669
|
-
}
|
1670
|
-
|
1671
|
-
// Reset research form submit button
|
1672
|
-
const startResearchBtn = document.getElementById('start-research-btn');
|
1673
|
-
if (startResearchBtn) {
|
1674
|
-
startResearchBtn.disabled = false;
|
1675
|
-
startResearchBtn.innerHTML = '<i class="fas fa-rocket"></i> Start Research';
|
1676
|
-
}
|
2143
|
+
// Log panel should only be visible on research-progress and research-results pages
|
2144
|
+
const logPanel = document.querySelector('.collapsible-log-panel');
|
2145
|
+
|
2146
|
+
// If research is in progress
|
2147
|
+
if (isAnyResearchInProgress) {
|
2148
|
+
// Disable new research and history navigation while research is in progress
|
2149
|
+
if (sidebarNewResearchItem) sidebarNewResearchItem.classList.add('disabled');
|
2150
|
+
if (sidebarHistoryItem) sidebarHistoryItem.classList.add('disabled');
|
2151
|
+
if (mobileNewResearchItem) mobileNewResearchItem.classList.add('disabled');
|
2152
|
+
if (mobileHistoryItem) mobileHistoryItem.classList.add('disabled');
|
2153
|
+
|
2154
|
+
// If user is not already on the research progress page, switch to it
|
2155
|
+
if (!isResearchPage) {
|
2156
|
+
switchPage('research-progress');
|
1677
2157
|
}
|
1678
2158
|
|
1679
|
-
//
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
if (document.getElementById('research-progress').classList.contains('active')) {
|
1684
|
-
item.classList.add('active');
|
1685
|
-
}
|
1686
|
-
} else if (document.getElementById('new-research').classList.contains('active')) {
|
1687
|
-
item.classList.add('active');
|
1688
|
-
}
|
1689
|
-
}
|
1690
|
-
});
|
1691
|
-
}
|
1692
|
-
|
1693
|
-
// Connect to socket for updates only if research is in progress
|
1694
|
-
if (isResearchInProgress && currentResearchId) {
|
1695
|
-
window.connectToResearchSocket(currentResearchId);
|
2159
|
+
// Show terminate button and hide error message and try again button
|
2160
|
+
if (terminateBtn) terminateBtn.style.display = 'block';
|
2161
|
+
if (errorMessage) errorMessage.style.display = 'none';
|
2162
|
+
if (tryAgainBtn) tryAgainBtn.style.display = 'none';
|
1696
2163
|
} else {
|
1697
|
-
//
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
2164
|
+
// Enable navigation when no research is in progress
|
2165
|
+
if (sidebarNewResearchItem) sidebarNewResearchItem.classList.remove('disabled');
|
2166
|
+
if (sidebarHistoryItem) sidebarHistoryItem.classList.remove('disabled');
|
2167
|
+
if (mobileNewResearchItem) mobileNewResearchItem.classList.remove('disabled');
|
2168
|
+
if (mobileHistoryItem) mobileHistoryItem.classList.remove('disabled');
|
2169
|
+
|
2170
|
+
// Hide terminate button when no research is in progress
|
2171
|
+
if (terminateBtn) terminateBtn.style.display = 'none';
|
1703
2172
|
}
|
2173
|
+
|
2174
|
+
console.log('Updated navigation based on research status. In progress:', isAnyResearchInProgress);
|
1704
2175
|
}
|
1705
2176
|
|
1706
2177
|
// Function to update the research progress page from nav click
|
1707
2178
|
function updateProgressPage() {
|
1708
|
-
if (!
|
2179
|
+
if (!currentResearchId && !window.currentResearchId) {
|
1709
2180
|
return;
|
1710
2181
|
}
|
1711
2182
|
|
2183
|
+
const researchId = currentResearchId || window.currentResearchId;
|
2184
|
+
|
1712
2185
|
// Update the progress page
|
1713
|
-
fetch(getApiUrl(`/api/research/${
|
2186
|
+
fetch(getApiUrl(`/api/research/${researchId}`))
|
1714
2187
|
.then(response => response.json())
|
1715
2188
|
.then(data => {
|
1716
2189
|
// Update the query display
|
1717
|
-
document.getElementById('current-query').textContent = data.query;
|
1718
|
-
|
1719
|
-
// Update the progress bar
|
1720
|
-
updateProgressUI(data.progress || 0, data.status);
|
1721
|
-
|
1722
|
-
// Connect to socket for live updates
|
1723
|
-
window.connectToResearchSocket(currentResearchId);
|
2190
|
+
document.getElementById('current-query').textContent = data.query;
|
1724
2191
|
|
1725
|
-
// Check
|
2192
|
+
// Check status before updating UI
|
1726
2193
|
if (data.status === 'in_progress') {
|
2194
|
+
// Update the progress bar
|
2195
|
+
updateProgressUI(data.progress || 0, data.status);
|
2196
|
+
|
2197
|
+
// Check if we need to show the terminate button
|
1727
2198
|
const terminateBtn = document.getElementById('terminate-research-btn');
|
1728
2199
|
if (terminateBtn) {
|
1729
2200
|
terminateBtn.style.display = 'inline-flex';
|
1730
2201
|
terminateBtn.disabled = false;
|
1731
2202
|
}
|
2203
|
+
|
2204
|
+
// Only connect to socket for in-progress research
|
2205
|
+
window.connectToResearchSocket(researchId);
|
2206
|
+
// Only start polling for in-progress research
|
2207
|
+
if (!pollingInterval) {
|
2208
|
+
pollResearchStatus(researchId);
|
2209
|
+
}
|
2210
|
+
}
|
2211
|
+
else if (data.status === 'suspended' || data.status === 'failed') {
|
2212
|
+
// Update UI for terminated research
|
2213
|
+
updateTerminationUIState('suspended', data.error || `Research was ${data.status}`);
|
2214
|
+
// Update progress bar
|
2215
|
+
document.getElementById('progress-fill').style.width = `${data.progress || 0}%`;
|
2216
|
+
document.getElementById('progress-percentage').textContent = `${data.progress || 0}%`;
|
2217
|
+
}
|
2218
|
+
else {
|
2219
|
+
// Just update progress for completed research
|
2220
|
+
document.getElementById('progress-fill').style.width = `${data.progress || 0}%`;
|
2221
|
+
document.getElementById('progress-percentage').textContent = `${data.progress || 0}%`;
|
1732
2222
|
}
|
1733
2223
|
})
|
1734
2224
|
.catch(error => {
|
@@ -2321,22 +2811,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
2321
2811
|
hljs.highlightElement(block);
|
2322
2812
|
});
|
2323
2813
|
|
2324
|
-
// Format date
|
2325
|
-
let dateText = formatDate(
|
2326
|
-
|
2327
|
-
|
2328
|
-
|
2329
|
-
|
2330
|
-
if (duration < 60) {
|
2331
|
-
durationText = `${duration}s`;
|
2332
|
-
} else if (duration < 3600) {
|
2333
|
-
durationText = `${Math.floor(duration / 60)}m ${duration % 60}s`;
|
2334
|
-
} else {
|
2335
|
-
durationText = `${Math.floor(duration / 3600)}h ${Math.floor((duration % 3600) / 60)}m`;
|
2336
|
-
}
|
2337
|
-
|
2338
|
-
dateText += ` (Duration: ${durationText})`;
|
2339
|
-
}
|
2814
|
+
// Format date with duration
|
2815
|
+
let dateText = formatDate(
|
2816
|
+
new Date(details.completed_at || details.created_at),
|
2817
|
+
details.duration_seconds
|
2818
|
+
);
|
2340
2819
|
|
2341
2820
|
// Set up data for PDF generation
|
2342
2821
|
document.getElementById('result-query').textContent = details.query;
|
@@ -2371,11 +2850,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
2371
2850
|
// Initialize the terminate button event listener
|
2372
2851
|
const terminateBtn = document.getElementById('terminate-research-btn');
|
2373
2852
|
if (terminateBtn) {
|
2374
|
-
terminateBtn.addEventListener('click', ()
|
2375
|
-
if (
|
2376
|
-
|
2377
|
-
|
2378
|
-
|
2853
|
+
terminateBtn.addEventListener('click', function() {
|
2854
|
+
if (currentResearchId) {
|
2855
|
+
terminateResearch(currentResearchId);
|
2856
|
+
} else {
|
2857
|
+
console.error('No active research ID found for termination');
|
2858
|
+
alert('No active research ID found. Please try again.');
|
2379
2859
|
}
|
2380
2860
|
});
|
2381
2861
|
}
|
@@ -2518,4 +2998,766 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
2518
2998
|
const emoji = mode === 'detailed' ? '🔬' : '⚡';
|
2519
2999
|
return createDynamicFavicon(emoji);
|
2520
3000
|
}
|
3001
|
+
|
3002
|
+
// Global log tracking variables
|
3003
|
+
let consoleLogEntries = [];
|
3004
|
+
let logCount = 0;
|
3005
|
+
let lastLogMessage = null; // Track the last log message to prevent duplicates
|
3006
|
+
let lastLogTimestamp = null; // Track the last log timestamp
|
3007
|
+
let viewingResearchId = null; // Track which research ID is currently being viewed
|
3008
|
+
|
3009
|
+
// Function to filter console logs by type
|
3010
|
+
function filterConsoleLogs(filterType = 'all') {
|
3011
|
+
console.log(`----- FILTER LOGS START (${filterType}) -----`);
|
3012
|
+
console.log(`Filtering logs by type: ${filterType}`);
|
3013
|
+
|
3014
|
+
// Make sure the filter type is lowercase for consistency
|
3015
|
+
filterType = filterType.toLowerCase();
|
3016
|
+
|
3017
|
+
// Get all log entries from the DOM
|
3018
|
+
const logEntries = document.querySelectorAll('.console-log-entry');
|
3019
|
+
console.log(`Found ${logEntries.length} log entries to filter`);
|
3020
|
+
|
3021
|
+
// If no entries found, exit early
|
3022
|
+
if (logEntries.length === 0) {
|
3023
|
+
console.log('No log entries found to filter');
|
3024
|
+
console.log(`----- FILTER LOGS END (${filterType}) -----`);
|
3025
|
+
return;
|
3026
|
+
}
|
3027
|
+
|
3028
|
+
let visibleCount = 0;
|
3029
|
+
|
3030
|
+
// Apply filters
|
3031
|
+
logEntries.forEach(entry => {
|
3032
|
+
// Use the data attribute directly - this comes directly from the database
|
3033
|
+
const logType = entry.dataset.logType;
|
3034
|
+
|
3035
|
+
// Determine visibility based on filter type
|
3036
|
+
let shouldShow = false;
|
3037
|
+
|
3038
|
+
switch (filterType) {
|
3039
|
+
case 'all':
|
3040
|
+
shouldShow = true;
|
3041
|
+
break;
|
3042
|
+
case 'info':
|
3043
|
+
shouldShow = logType === 'info';
|
3044
|
+
break;
|
3045
|
+
case 'milestone':
|
3046
|
+
case 'milestones': // Handle plural form too
|
3047
|
+
shouldShow = logType === 'milestone';
|
3048
|
+
break;
|
3049
|
+
case 'error':
|
3050
|
+
case 'errors': // Handle plural form too
|
3051
|
+
shouldShow = logType === 'error';
|
3052
|
+
break;
|
3053
|
+
default:
|
3054
|
+
shouldShow = true; // Default to showing everything
|
3055
|
+
console.warn(`Unknown filter type: ${filterType}, showing all logs`);
|
3056
|
+
}
|
3057
|
+
|
3058
|
+
// Set display style based on filter result
|
3059
|
+
entry.style.display = shouldShow ? '' : 'none';
|
3060
|
+
|
3061
|
+
if (shouldShow) {
|
3062
|
+
visibleCount++;
|
3063
|
+
}
|
3064
|
+
});
|
3065
|
+
|
3066
|
+
console.log(`Filtering complete. Showing ${visibleCount} of ${logEntries.length} logs with filter: ${filterType}`);
|
3067
|
+
|
3068
|
+
// Show 'no logs' message if all logs are filtered out
|
3069
|
+
const consoleContainer = document.getElementById('console-log-container');
|
3070
|
+
if (consoleContainer && logEntries.length > 0) {
|
3071
|
+
// Remove any existing empty message
|
3072
|
+
const existingEmptyMessage = consoleContainer.querySelector('.empty-log-message');
|
3073
|
+
if (existingEmptyMessage) {
|
3074
|
+
existingEmptyMessage.remove();
|
3075
|
+
}
|
3076
|
+
|
3077
|
+
// Add empty message if needed
|
3078
|
+
if (visibleCount === 0) {
|
3079
|
+
console.log(`Adding 'no logs' message for filter: ${filterType}`);
|
3080
|
+
const newEmptyMessage = document.createElement('div');
|
3081
|
+
newEmptyMessage.className = 'empty-log-message';
|
3082
|
+
newEmptyMessage.textContent = `No ${filterType} logs to display.`;
|
3083
|
+
consoleContainer.appendChild(newEmptyMessage);
|
3084
|
+
}
|
3085
|
+
}
|
3086
|
+
|
3087
|
+
console.log(`----- FILTER LOGS END (${filterType}) -----`);
|
3088
|
+
}
|
3089
|
+
|
3090
|
+
// Function to initialize the log panel
|
3091
|
+
function initializeLogPanel() {
|
3092
|
+
console.log('Initializing log panel');
|
3093
|
+
|
3094
|
+
// Get DOM elements
|
3095
|
+
const logPanelToggle = document.getElementById('log-panel-toggle');
|
3096
|
+
const logPanelContent = document.getElementById('log-panel-content');
|
3097
|
+
const filterButtons = document.querySelectorAll('.filter-buttons .small-btn');
|
3098
|
+
|
3099
|
+
// Check if elements exist
|
3100
|
+
if (!logPanelToggle || !logPanelContent) {
|
3101
|
+
console.error('Log panel elements not found');
|
3102
|
+
return;
|
3103
|
+
}
|
3104
|
+
|
3105
|
+
console.log('Log panel elements found:', {
|
3106
|
+
toggle: logPanelToggle ? 'Found' : 'Missing',
|
3107
|
+
content: logPanelContent ? 'Found' : 'Missing',
|
3108
|
+
filterButtons: filterButtons.length > 0 ? `Found ${filterButtons.length} buttons` : 'Missing'
|
3109
|
+
});
|
3110
|
+
|
3111
|
+
// Set up toggle click handler
|
3112
|
+
logPanelToggle.addEventListener('click', function() {
|
3113
|
+
console.log('Log panel toggle clicked');
|
3114
|
+
|
3115
|
+
// Toggle collapsed state
|
3116
|
+
logPanelContent.classList.toggle('collapsed');
|
3117
|
+
logPanelToggle.classList.toggle('collapsed');
|
3118
|
+
|
3119
|
+
// Update toggle icon
|
3120
|
+
const toggleIcon = logPanelToggle.querySelector('.toggle-icon');
|
3121
|
+
if (toggleIcon) {
|
3122
|
+
if (logPanelToggle.classList.contains('collapsed')) {
|
3123
|
+
toggleIcon.className = 'fas fa-chevron-right toggle-icon';
|
3124
|
+
} else {
|
3125
|
+
toggleIcon.className = 'fas fa-chevron-down toggle-icon';
|
3126
|
+
|
3127
|
+
// Scroll log container to bottom
|
3128
|
+
const consoleContainer = document.getElementById('console-log-container');
|
3129
|
+
if (consoleContainer) {
|
3130
|
+
setTimeout(() => {
|
3131
|
+
consoleContainer.scrollTop = consoleContainer.scrollHeight;
|
3132
|
+
}, 10);
|
3133
|
+
}
|
3134
|
+
}
|
3135
|
+
}
|
3136
|
+
|
3137
|
+
console.log('Log panel is now ' + (logPanelContent.classList.contains('collapsed') ? 'collapsed' : 'expanded'));
|
3138
|
+
});
|
3139
|
+
|
3140
|
+
// Initial state - collapsed
|
3141
|
+
logPanelContent.classList.add('collapsed');
|
3142
|
+
logPanelToggle.classList.add('collapsed');
|
3143
|
+
const toggleIcon = logPanelToggle.querySelector('.toggle-icon');
|
3144
|
+
if (toggleIcon) {
|
3145
|
+
toggleIcon.className = 'fas fa-chevron-right toggle-icon';
|
3146
|
+
}
|
3147
|
+
|
3148
|
+
// Initial filtering - use a longer delay to ensure DOM is ready
|
3149
|
+
setTimeout(() => {
|
3150
|
+
console.log('Applying initial log filter: all');
|
3151
|
+
filterConsoleLogs('all');
|
3152
|
+
}, 300);
|
3153
|
+
|
3154
|
+
console.log('Log panel initialization completed');
|
3155
|
+
}
|
3156
|
+
|
3157
|
+
// Function to clear all console logs
|
3158
|
+
function clearConsoleLogs() {
|
3159
|
+
const consoleContainer = document.getElementById('console-log-container');
|
3160
|
+
if (!consoleContainer) return;
|
3161
|
+
|
3162
|
+
console.log('Clearing console logs');
|
3163
|
+
|
3164
|
+
// Reset the processed messages set to allow new logs after clearing
|
3165
|
+
window.processedMessages = new Set();
|
3166
|
+
|
3167
|
+
// Clear the container
|
3168
|
+
consoleContainer.innerHTML = '<div class="empty-log-message">No logs yet. Research logs will appear here as they occur.</div>';
|
3169
|
+
|
3170
|
+
// Reset the log entries array
|
3171
|
+
consoleLogEntries = [];
|
3172
|
+
|
3173
|
+
// Reset the log count
|
3174
|
+
logCount = 0;
|
3175
|
+
updateLogIndicator();
|
3176
|
+
|
3177
|
+
// Reset last message tracking
|
3178
|
+
lastLogMessage = null;
|
3179
|
+
lastLogTimestamp = null;
|
3180
|
+
|
3181
|
+
// DO NOT reset viewingResearchId here, as clearing logs doesn't mean
|
3182
|
+
// we're changing which research we're viewing
|
3183
|
+
|
3184
|
+
console.log('Console logs cleared');
|
3185
|
+
}
|
3186
|
+
|
3187
|
+
// Function to determine if a log message is a milestone
|
3188
|
+
function isMilestoneLog(message, metadata) {
|
3189
|
+
if (!message) return false;
|
3190
|
+
|
3191
|
+
// Critical milestones - main research flow points
|
3192
|
+
const criticalMilestones = [
|
3193
|
+
// Research start/end
|
3194
|
+
/^Research (started|complete)/i,
|
3195
|
+
/^Starting research/i,
|
3196
|
+
|
3197
|
+
// Iteration markers
|
3198
|
+
/^Starting iteration \d+/i,
|
3199
|
+
/^Iteration \d+ complete/i,
|
3200
|
+
|
3201
|
+
// Final completion
|
3202
|
+
/^Research completed successfully/i,
|
3203
|
+
/^Report generation complete/i,
|
3204
|
+
/^Writing research report to file/i
|
3205
|
+
];
|
3206
|
+
|
3207
|
+
// Check for critical milestones first
|
3208
|
+
const isCriticalMilestone = criticalMilestones.some(pattern => pattern.test(message));
|
3209
|
+
if (isCriticalMilestone) return true;
|
3210
|
+
|
3211
|
+
// Check metadata phase for critical phases only
|
3212
|
+
const isCriticalPhase = metadata && (
|
3213
|
+
metadata.phase === 'init' ||
|
3214
|
+
metadata.phase === 'iteration_start' ||
|
3215
|
+
metadata.phase === 'iteration_complete' ||
|
3216
|
+
metadata.phase === 'complete' ||
|
3217
|
+
metadata.phase === 'report_generation' ||
|
3218
|
+
metadata.phase === 'report_complete'
|
3219
|
+
);
|
3220
|
+
|
3221
|
+
return isCriticalPhase;
|
3222
|
+
}
|
3223
|
+
|
3224
|
+
// Initialize the log panel when the application starts
|
3225
|
+
function initializeAppWithLogs() {
|
3226
|
+
// Original initializeApp function
|
3227
|
+
const originalInitializeApp = window.initializeApp || initializeApp;
|
3228
|
+
window.initializeApp = function() {
|
3229
|
+
// Call the original initialization
|
3230
|
+
originalInitializeApp();
|
3231
|
+
|
3232
|
+
// Ensure global log variables are initialized
|
3233
|
+
window.consoleLogEntries = [];
|
3234
|
+
window.logCount = 0;
|
3235
|
+
window.lastLogMessage = null;
|
3236
|
+
window.lastLogTimestamp = null;
|
3237
|
+
|
3238
|
+
// Initialize the log panel with a delay to ensure DOM is ready
|
3239
|
+
setTimeout(() => {
|
3240
|
+
initializeLogPanel();
|
3241
|
+
|
3242
|
+
// Add an initial welcome log entry
|
3243
|
+
addConsoleLog('Research system initialized and ready', 'milestone');
|
3244
|
+
|
3245
|
+
console.log('Log panel initialization completed');
|
3246
|
+
}, 100);
|
3247
|
+
};
|
3248
|
+
}
|
3249
|
+
|
3250
|
+
// Call the initialization function immediately
|
3251
|
+
initializeAppWithLogs();
|
3252
|
+
|
3253
|
+
// Function to get active research ID
|
3254
|
+
function getActiveResearchId() {
|
3255
|
+
return window.currentResearchId || currentResearchId;
|
3256
|
+
}
|
3257
|
+
|
3258
|
+
// Function to update research duration on results display
|
3259
|
+
function updateResearchDuration(metadata) {
|
3260
|
+
if (!metadata || !metadata.started_at || !metadata.completed_at) return;
|
3261
|
+
|
3262
|
+
try {
|
3263
|
+
// Parse ISO dates
|
3264
|
+
const startDate = new Date(metadata.started_at);
|
3265
|
+
const endDate = new Date(metadata.completed_at);
|
3266
|
+
|
3267
|
+
// Calculate duration in seconds
|
3268
|
+
const durationMs = endDate - startDate;
|
3269
|
+
const durationSec = Math.floor(durationMs / 1000);
|
3270
|
+
|
3271
|
+
// Format as minutes and seconds
|
3272
|
+
const minutes = Math.floor(durationSec / 60);
|
3273
|
+
const seconds = durationSec % 60;
|
3274
|
+
const formattedDuration = `${minutes}m ${seconds}s`;
|
3275
|
+
|
3276
|
+
// Add to results metadata
|
3277
|
+
const resultsMetadata = document.querySelector('.results-metadata');
|
3278
|
+
if (resultsMetadata) {
|
3279
|
+
// Check if duration element already exists
|
3280
|
+
let durationEl = resultsMetadata.querySelector('.metadata-item.duration');
|
3281
|
+
|
3282
|
+
if (!durationEl) {
|
3283
|
+
// Create new duration element
|
3284
|
+
durationEl = document.createElement('div');
|
3285
|
+
durationEl.className = 'metadata-item duration';
|
3286
|
+
|
3287
|
+
const labelEl = document.createElement('span');
|
3288
|
+
labelEl.className = 'metadata-label';
|
3289
|
+
labelEl.textContent = 'Duration:';
|
3290
|
+
|
3291
|
+
const valueEl = document.createElement('span');
|
3292
|
+
valueEl.className = 'metadata-value';
|
3293
|
+
valueEl.id = 'result-duration';
|
3294
|
+
|
3295
|
+
durationEl.appendChild(labelEl);
|
3296
|
+
durationEl.appendChild(valueEl);
|
3297
|
+
|
3298
|
+
// Insert after "Generated" metadata
|
3299
|
+
const generatedEl = resultsMetadata.querySelector('.metadata-item:nth-child(2)');
|
3300
|
+
if (generatedEl) {
|
3301
|
+
resultsMetadata.insertBefore(durationEl, generatedEl.nextSibling);
|
3302
|
+
} else {
|
3303
|
+
resultsMetadata.appendChild(durationEl);
|
3304
|
+
}
|
3305
|
+
}
|
3306
|
+
|
3307
|
+
// Update duration value
|
3308
|
+
const durationValueEl = resultsMetadata.querySelector('#result-duration');
|
3309
|
+
if (durationValueEl) {
|
3310
|
+
durationValueEl.textContent = formattedDuration;
|
3311
|
+
}
|
3312
|
+
}
|
3313
|
+
|
3314
|
+
// Also add to research details metadata
|
3315
|
+
const detailsMetadata = document.querySelector('.research-metadata');
|
3316
|
+
if (detailsMetadata) {
|
3317
|
+
// Check if duration element already exists
|
3318
|
+
let durationEl = null;
|
3319
|
+
|
3320
|
+
// Use querySelectorAll and iterate to find the element with "Duration" label
|
3321
|
+
const metadataItems = detailsMetadata.querySelectorAll('.metadata-item');
|
3322
|
+
for (let i = 0; i < metadataItems.length; i++) {
|
3323
|
+
const labelEl = metadataItems[i].querySelector('.metadata-label');
|
3324
|
+
if (labelEl && labelEl.textContent.includes('Duration')) {
|
3325
|
+
durationEl = metadataItems[i];
|
3326
|
+
break;
|
3327
|
+
}
|
3328
|
+
}
|
3329
|
+
|
3330
|
+
if (!durationEl) {
|
3331
|
+
// Create new duration element
|
3332
|
+
durationEl = document.createElement('div');
|
3333
|
+
durationEl.className = 'metadata-item';
|
3334
|
+
|
3335
|
+
const labelEl = document.createElement('span');
|
3336
|
+
labelEl.className = 'metadata-label';
|
3337
|
+
labelEl.textContent = 'Duration:';
|
3338
|
+
|
3339
|
+
const valueEl = document.createElement('span');
|
3340
|
+
valueEl.className = 'metadata-value';
|
3341
|
+
valueEl.id = 'detail-duration';
|
3342
|
+
|
3343
|
+
durationEl.appendChild(labelEl);
|
3344
|
+
durationEl.appendChild(valueEl);
|
3345
|
+
|
3346
|
+
// Insert after mode metadata
|
3347
|
+
const modeEl = detailsMetadata.querySelector('.metadata-item:nth-child(3)');
|
3348
|
+
if (modeEl) {
|
3349
|
+
detailsMetadata.insertBefore(durationEl, modeEl.nextSibling);
|
3350
|
+
} else {
|
3351
|
+
detailsMetadata.appendChild(durationEl);
|
3352
|
+
}
|
3353
|
+
}
|
3354
|
+
|
3355
|
+
// Update duration value
|
3356
|
+
const durationValueEl = durationEl.querySelector('.metadata-value');
|
3357
|
+
if (durationValueEl) {
|
3358
|
+
durationValueEl.textContent = formattedDuration;
|
3359
|
+
}
|
3360
|
+
}
|
3361
|
+
|
3362
|
+
console.log(`Research duration: ${formattedDuration}`);
|
3363
|
+
} catch (error) {
|
3364
|
+
console.error('Error calculating research duration:', error);
|
3365
|
+
}
|
3366
|
+
}
|
3367
|
+
|
3368
|
+
// Function to add a log entry to the console with reduced deduplication time window
|
3369
|
+
function addConsoleLog(message, type = 'info', metadata = null, forResearchId = null) {
|
3370
|
+
// Skip if identical to the last message to prevent duplication
|
3371
|
+
const currentTime = new Date().getTime();
|
3372
|
+
if (message === lastLogMessage && lastLogTimestamp && currentTime - lastLogTimestamp < 300) {
|
3373
|
+
// Skip duplicate message if it's within 300ms of the last one (reduced from 2000ms)
|
3374
|
+
return null;
|
3375
|
+
}
|
3376
|
+
|
3377
|
+
// Skip if we're viewing a specific research and this log is for a different research
|
3378
|
+
if (viewingResearchId && forResearchId && viewingResearchId !== forResearchId) {
|
3379
|
+
console.log(`Skipping log for research ${forResearchId} because viewing ${viewingResearchId}`);
|
3380
|
+
return null;
|
3381
|
+
}
|
3382
|
+
|
3383
|
+
// Update tracking variables
|
3384
|
+
lastLogMessage = message;
|
3385
|
+
lastLogTimestamp = currentTime;
|
3386
|
+
|
3387
|
+
// Use the direct log addition function
|
3388
|
+
return addConsoleLogDirect(message, type, metadata);
|
3389
|
+
}
|
3390
|
+
|
3391
|
+
// Function to update the log indicator count
|
3392
|
+
function updateLogIndicator() {
|
3393
|
+
const indicator = document.getElementById('log-indicator');
|
3394
|
+
if (indicator) {
|
3395
|
+
indicator.textContent = logCount;
|
3396
|
+
}
|
3397
|
+
}
|
3398
|
+
|
3399
|
+
// New function to update log panel visibility based on current page
|
3400
|
+
function updateLogPanelVisibility(pageId) {
|
3401
|
+
console.log('Updating log panel visibility for page:', pageId);
|
3402
|
+
|
3403
|
+
// Only show log panel on research progress and results pages
|
3404
|
+
if (pageId === 'research-progress' || pageId === 'research-results') {
|
3405
|
+
console.log('Showing log panel');
|
3406
|
+
// Let CSS handle the display
|
3407
|
+
} else {
|
3408
|
+
console.log('Hiding log panel');
|
3409
|
+
// Let CSS handle the display
|
3410
|
+
}
|
3411
|
+
}
|
3412
|
+
|
3413
|
+
// Function to update the navigation UI based on active page
|
3414
|
+
function updateNavigationUI(pageId) {
|
3415
|
+
// Update sidebar navigation
|
3416
|
+
const sidebarNavItems = document.querySelectorAll('.sidebar-nav li');
|
3417
|
+
sidebarNavItems.forEach(item => {
|
3418
|
+
if (item.getAttribute('data-page') === pageId) {
|
3419
|
+
item.classList.add('active');
|
3420
|
+
} else {
|
3421
|
+
item.classList.remove('active');
|
3422
|
+
}
|
3423
|
+
});
|
3424
|
+
|
3425
|
+
// Update mobile tab bar
|
3426
|
+
const mobileNavItems = document.querySelectorAll('.mobile-tab-bar li');
|
3427
|
+
mobileNavItems.forEach(item => {
|
3428
|
+
if (item.getAttribute('data-page') === pageId) {
|
3429
|
+
item.classList.add('active');
|
3430
|
+
} else {
|
3431
|
+
item.classList.remove('active');
|
3432
|
+
}
|
3433
|
+
});
|
3434
|
+
}
|
3435
|
+
|
3436
|
+
// Function to check research status before polling
|
3437
|
+
async function checkResearchStatusBeforePolling(researchId) {
|
3438
|
+
try {
|
3439
|
+
// Get the current status from the server
|
3440
|
+
const response = await fetch(getApiUrl(`/api/research/${researchId}`));
|
3441
|
+
const data = await response.json();
|
3442
|
+
|
3443
|
+
// If research is in_progress, start polling normally
|
3444
|
+
if (data.status === 'in_progress') {
|
3445
|
+
console.log(`Research ${researchId} is in progress, starting polling`);
|
3446
|
+
pollResearchStatus(researchId);
|
3447
|
+
window.connectToResearchSocket(researchId);
|
3448
|
+
return true;
|
3449
|
+
}
|
3450
|
+
// If terminated or complete, update UI but don't start polling
|
3451
|
+
else if (data.status === 'suspended' || data.status === 'failed') {
|
3452
|
+
console.log(`Research ${researchId} is ${data.status}, not starting polling`);
|
3453
|
+
updateTerminationUIState('suspended', `Research was ${data.status}`);
|
3454
|
+
return false;
|
3455
|
+
}
|
3456
|
+
// If completed, don't start polling
|
3457
|
+
else if (data.status === 'completed') {
|
3458
|
+
console.log(`Research ${researchId} is completed, not starting polling`);
|
3459
|
+
return false;
|
3460
|
+
}
|
3461
|
+
} catch (error) {
|
3462
|
+
console.error(`Error checking research status: ${error}`);
|
3463
|
+
return false;
|
3464
|
+
}
|
3465
|
+
|
3466
|
+
return false;
|
3467
|
+
}
|
3468
|
+
|
3469
|
+
// Function to reset research state before starting a new research
|
3470
|
+
function resetResearchState() {
|
3471
|
+
// Clean up any existing research resources
|
3472
|
+
window.cleanupResearchResources();
|
3473
|
+
|
3474
|
+
// Clear any previous polling intervals
|
3475
|
+
clearPollingInterval();
|
3476
|
+
|
3477
|
+
// Reset research flags
|
3478
|
+
isResearchInProgress = false;
|
3479
|
+
currentResearchId = null;
|
3480
|
+
window.currentResearchId = null;
|
3481
|
+
|
3482
|
+
// Clear console logs
|
3483
|
+
clearConsoleLogs();
|
3484
|
+
|
3485
|
+
// Reset any UI elements
|
3486
|
+
const errorMessage = document.getElementById('error-message');
|
3487
|
+
if (errorMessage) {
|
3488
|
+
errorMessage.style.display = 'none';
|
3489
|
+
errorMessage.textContent = '';
|
3490
|
+
}
|
3491
|
+
|
3492
|
+
// Hide the try again button if visible
|
3493
|
+
const tryAgainBtn = document.getElementById('try-again-btn');
|
3494
|
+
if (tryAgainBtn) {
|
3495
|
+
tryAgainBtn.style.display = 'none';
|
3496
|
+
}
|
3497
|
+
|
3498
|
+
// Reset progress bar
|
3499
|
+
const progressFill = document.getElementById('progress-fill');
|
3500
|
+
if (progressFill) {
|
3501
|
+
progressFill.style.width = '0%';
|
3502
|
+
}
|
3503
|
+
|
3504
|
+
// Reset progress percentage
|
3505
|
+
const progressPercentage = document.getElementById('progress-percentage');
|
3506
|
+
if (progressPercentage) {
|
3507
|
+
progressPercentage.textContent = '0%';
|
3508
|
+
}
|
3509
|
+
|
3510
|
+
// Reset progress status
|
3511
|
+
const progressStatus = document.getElementById('progress-status');
|
3512
|
+
if (progressStatus) {
|
3513
|
+
progressStatus.textContent = 'Initializing research process...';
|
3514
|
+
progressStatus.className = 'progress-status';
|
3515
|
+
}
|
3516
|
+
|
3517
|
+
// Reset the Start Research button
|
3518
|
+
resetStartResearchButton();
|
3519
|
+
|
3520
|
+
// Add initial log entry
|
3521
|
+
addConsoleLog('Preparing to start new research', 'info');
|
3522
|
+
}
|
3523
|
+
|
3524
|
+
// Function to load research logs into the console log panel
|
3525
|
+
function loadLogsForResearch(researchId) {
|
3526
|
+
console.log(`Loading logs for research ${researchId}`);
|
3527
|
+
|
3528
|
+
// Clear existing logs
|
3529
|
+
clearConsoleLogs();
|
3530
|
+
|
3531
|
+
// Set the current viewing research ID
|
3532
|
+
viewingResearchId = researchId;
|
3533
|
+
|
3534
|
+
// Fetch logs from the server for this research
|
3535
|
+
fetch(getApiUrl(`/api/research/${researchId}/logs`))
|
3536
|
+
.then(response => response.json())
|
3537
|
+
.then(data => {
|
3538
|
+
if (data.status === 'success' && Array.isArray(data.logs) && data.logs.length > 0) {
|
3539
|
+
console.log(`Loaded ${data.logs.length} logs for research ${researchId}`);
|
3540
|
+
|
3541
|
+
// Sort logs by timestamp if needed
|
3542
|
+
data.logs.sort((a, b) => {
|
3543
|
+
const timeA = new Date(a.time);
|
3544
|
+
const timeB = new Date(b.time);
|
3545
|
+
return timeA - timeB;
|
3546
|
+
});
|
3547
|
+
|
3548
|
+
// Add logs to the console
|
3549
|
+
data.logs.forEach(log => {
|
3550
|
+
let logType = 'info';
|
3551
|
+
|
3552
|
+
// Use the type directly from the database if available
|
3553
|
+
if (log.type) {
|
3554
|
+
logType = log.type;
|
3555
|
+
} else if (isMilestoneLog(log.message, log.metadata)) {
|
3556
|
+
logType = 'milestone';
|
3557
|
+
} else if (log.metadata && log.metadata.phase === 'error') {
|
3558
|
+
logType = 'error';
|
3559
|
+
}
|
3560
|
+
|
3561
|
+
// Add to console without triggering the duplicate detection
|
3562
|
+
// by directly creating the log entry
|
3563
|
+
addConsoleLogDirect(log.message, logType, log.metadata, log.time);
|
3564
|
+
});
|
3565
|
+
|
3566
|
+
// Apply initial filter (all)
|
3567
|
+
filterConsoleLogs('all');
|
3568
|
+
|
3569
|
+
// Make sure the "All" button is selected
|
3570
|
+
const allButton = document.querySelector('.filter-buttons .small-btn');
|
3571
|
+
if (allButton) {
|
3572
|
+
// Remove selected class from all buttons
|
3573
|
+
document.querySelectorAll('.filter-buttons .small-btn').forEach(btn => {
|
3574
|
+
btn.classList.remove('selected');
|
3575
|
+
});
|
3576
|
+
// Add selected class to the All button
|
3577
|
+
allButton.classList.add('selected');
|
3578
|
+
}
|
3579
|
+
} else {
|
3580
|
+
console.log(`No logs found for research ${researchId}`);
|
3581
|
+
// Add a message indicating no logs are available
|
3582
|
+
const consoleContainer = document.getElementById('console-log-container');
|
3583
|
+
if (consoleContainer) {
|
3584
|
+
consoleContainer.innerHTML = '<div class="empty-log-message">No logs available for this research.</div>';
|
3585
|
+
}
|
3586
|
+
}
|
3587
|
+
})
|
3588
|
+
.catch(error => {
|
3589
|
+
console.error(`Error loading logs for research ${researchId}:`, error);
|
3590
|
+
// Show error message in console log
|
3591
|
+
const consoleContainer = document.getElementById('console-log-container');
|
3592
|
+
if (consoleContainer) {
|
3593
|
+
consoleContainer.innerHTML = '<div class="empty-log-message error-message">Failed to load logs. Please try again.</div>';
|
3594
|
+
}
|
3595
|
+
});
|
3596
|
+
}
|
3597
|
+
|
3598
|
+
// New direct log addition function that bypasses duplicate detection
|
3599
|
+
function addConsoleLogDirect(message, type = 'info', metadata = null, timestamp = null) {
|
3600
|
+
// Get DOM elements
|
3601
|
+
const consoleContainer = document.getElementById('console-log-container');
|
3602
|
+
const template = document.getElementById('console-log-entry-template');
|
3603
|
+
|
3604
|
+
if (!consoleContainer || !template) {
|
3605
|
+
console.error('Console log container or template not found');
|
3606
|
+
return null;
|
3607
|
+
}
|
3608
|
+
|
3609
|
+
// Clear the empty message if it's the first log
|
3610
|
+
const emptyMessage = consoleContainer.querySelector('.empty-log-message');
|
3611
|
+
if (emptyMessage) {
|
3612
|
+
consoleContainer.removeChild(emptyMessage);
|
3613
|
+
}
|
3614
|
+
|
3615
|
+
// Create a new log entry from the template
|
3616
|
+
const clone = document.importNode(template.content, true);
|
3617
|
+
const logEntry = clone.querySelector('.console-log-entry');
|
3618
|
+
|
3619
|
+
// Make sure type is valid and standardized
|
3620
|
+
let validType = 'info';
|
3621
|
+
if (['info', 'milestone', 'error'].includes(type.toLowerCase())) {
|
3622
|
+
validType = type.toLowerCase();
|
3623
|
+
}
|
3624
|
+
|
3625
|
+
// Add appropriate class based on type
|
3626
|
+
logEntry.classList.add(`log-${validType}`);
|
3627
|
+
|
3628
|
+
// IMPORTANT: Store the log type directly as a data attribute
|
3629
|
+
logEntry.dataset.logType = validType;
|
3630
|
+
|
3631
|
+
// Set the timestamp
|
3632
|
+
const actualTimestamp = timestamp ? new Date(timestamp).toLocaleTimeString() : new Date().toLocaleTimeString();
|
3633
|
+
const timestampEl = logEntry.querySelector('.log-timestamp');
|
3634
|
+
if (timestampEl) timestampEl.textContent = actualTimestamp;
|
3635
|
+
|
3636
|
+
// Set the badge text based on type
|
3637
|
+
const badgeEl = logEntry.querySelector('.log-badge');
|
3638
|
+
if (badgeEl) {
|
3639
|
+
badgeEl.textContent = validType.toUpperCase();
|
3640
|
+
}
|
3641
|
+
|
3642
|
+
// Process message to add search engine info if it's a search query
|
3643
|
+
let displayMessage = message;
|
3644
|
+
|
3645
|
+
// Check if this is a search query message
|
3646
|
+
if (message && typeof message === 'string' && message.startsWith('Searching for:')) {
|
3647
|
+
// Determine search engine - add SearXNG by default since that's what we see in logs
|
3648
|
+
let searchEngine = 'SearXNG';
|
3649
|
+
|
3650
|
+
// Check metadata if available for any search engine info
|
3651
|
+
if (metadata) {
|
3652
|
+
if (metadata.engine) searchEngine = metadata.engine;
|
3653
|
+
else if (metadata.search_engine) searchEngine = metadata.search_engine;
|
3654
|
+
else if (metadata.source) searchEngine = metadata.source;
|
3655
|
+
else if (metadata.phase && metadata.phase.includes('searxng')) searchEngine = 'SearXNG';
|
3656
|
+
else if (metadata.phase && metadata.phase.includes('google')) searchEngine = 'Google';
|
3657
|
+
else if (metadata.phase && metadata.phase.includes('bing')) searchEngine = 'Bing';
|
3658
|
+
else if (metadata.phase && metadata.phase.includes('duckduckgo')) searchEngine = 'DuckDuckGo';
|
3659
|
+
}
|
3660
|
+
|
3661
|
+
// Append search engine info to message
|
3662
|
+
displayMessage = `${message} [Engine: ${searchEngine}]`;
|
3663
|
+
}
|
3664
|
+
|
3665
|
+
// Set the message
|
3666
|
+
const messageEl = logEntry.querySelector('.log-message');
|
3667
|
+
if (messageEl) messageEl.textContent = displayMessage;
|
3668
|
+
|
3669
|
+
// Add the log entry to the container
|
3670
|
+
consoleContainer.appendChild(logEntry);
|
3671
|
+
|
3672
|
+
// Store the log entry for filtering
|
3673
|
+
consoleLogEntries.push({
|
3674
|
+
element: logEntry,
|
3675
|
+
type: validType,
|
3676
|
+
message: displayMessage,
|
3677
|
+
timestamp: actualTimestamp,
|
3678
|
+
researchId: viewingResearchId
|
3679
|
+
});
|
3680
|
+
|
3681
|
+
// Update log count
|
3682
|
+
logCount++;
|
3683
|
+
updateLogIndicator();
|
3684
|
+
|
3685
|
+
// Use setTimeout to ensure DOM updates before scrolling
|
3686
|
+
setTimeout(() => {
|
3687
|
+
// Scroll to the bottom
|
3688
|
+
consoleContainer.scrollTop = consoleContainer.scrollHeight;
|
3689
|
+
}, 0);
|
3690
|
+
|
3691
|
+
return logEntry;
|
3692
|
+
}
|
3693
|
+
|
3694
|
+
// Add a global function to filter logs directly from HTML
|
3695
|
+
window.filterLogsByType = function(filterType) {
|
3696
|
+
console.log('Direct filterLogsByType called with:', filterType);
|
3697
|
+
if (filterType && typeof filterConsoleLogs === 'function') {
|
3698
|
+
// Update the button styling for the selected filter
|
3699
|
+
const filterButtons = document.querySelectorAll('.filter-buttons .small-btn');
|
3700
|
+
if (filterButtons.length > 0) {
|
3701
|
+
// Remove 'selected' class from all buttons
|
3702
|
+
filterButtons.forEach(btn => {
|
3703
|
+
btn.classList.remove('selected');
|
3704
|
+
});
|
3705
|
+
|
3706
|
+
// Add 'selected' class to the clicked button
|
3707
|
+
const selectedButton = Array.from(filterButtons).find(
|
3708
|
+
btn => btn.textContent.toLowerCase().includes(filterType) ||
|
3709
|
+
(filterType === 'all' && btn.textContent.toLowerCase() === 'all')
|
3710
|
+
);
|
3711
|
+
|
3712
|
+
if (selectedButton) {
|
3713
|
+
selectedButton.classList.add('selected');
|
3714
|
+
}
|
3715
|
+
}
|
3716
|
+
|
3717
|
+
// Apply the filter
|
3718
|
+
filterConsoleLogs(filterType);
|
3719
|
+
} else {
|
3720
|
+
console.error('Unable to filter logs - filterConsoleLogs function not available');
|
3721
|
+
}
|
3722
|
+
};
|
3723
|
+
|
3724
|
+
// Function to check if there are any logs available
|
3725
|
+
window.checkIfLogsAvailable = function() {
|
3726
|
+
const logEntries = document.querySelectorAll('.console-log-entry');
|
3727
|
+
if (logEntries.length === 0) {
|
3728
|
+
console.log('No logs available to filter');
|
3729
|
+
return false;
|
3730
|
+
}
|
3731
|
+
return true;
|
3732
|
+
};
|
3733
|
+
|
3734
|
+
// Add direct log panel toggle handler as backup
|
3735
|
+
const logPanelToggle = document.getElementById('log-panel-toggle');
|
3736
|
+
const logPanelContent = document.getElementById('log-panel-content');
|
3737
|
+
|
3738
|
+
if (logPanelToggle && logPanelContent) {
|
3739
|
+
console.log('Adding direct DOM event listener to log panel toggle');
|
3740
|
+
|
3741
|
+
logPanelToggle.addEventListener('click', function(event) {
|
3742
|
+
console.log('Log panel toggle clicked (direct handler)');
|
3743
|
+
event.preventDefault();
|
3744
|
+
event.stopPropagation();
|
3745
|
+
|
3746
|
+
// Toggle collapsed state
|
3747
|
+
logPanelContent.classList.toggle('collapsed');
|
3748
|
+
logPanelToggle.classList.toggle('collapsed');
|
3749
|
+
|
3750
|
+
// Update toggle icon
|
3751
|
+
const toggleIcon = logPanelToggle.querySelector('.toggle-icon');
|
3752
|
+
if (toggleIcon) {
|
3753
|
+
if (logPanelToggle.classList.contains('collapsed')) {
|
3754
|
+
toggleIcon.className = 'fas fa-chevron-right toggle-icon';
|
3755
|
+
} else {
|
3756
|
+
toggleIcon.className = 'fas fa-chevron-down toggle-icon';
|
3757
|
+
}
|
3758
|
+
}
|
3759
|
+
|
3760
|
+
return false;
|
3761
|
+
});
|
3762
|
+
}
|
2521
3763
|
});
|