local-deep-research 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,24 @@ document.addEventListener('DOMContentLoaded', () => {
17
17
  let errorSound = null;
18
18
  let notificationsEnabled = true;
19
19
 
20
+ // Add function to cleanup research resources globally
21
+ window.cleanupResearchResources = function() {
22
+ console.log('Cleaning up research resources');
23
+ if (pollingInterval) {
24
+ clearInterval(pollingInterval);
25
+ pollingInterval = null;
26
+ }
27
+ isResearchInProgress = false;
28
+ currentResearchId = null;
29
+ window.currentResearchId = null;
30
+
31
+ // Hide the terminate button if it exists
32
+ const terminateBtn = document.getElementById('terminate-research-btn');
33
+ if (terminateBtn) {
34
+ terminateBtn.style.display = 'none';
35
+ }
36
+ };
37
+
20
38
  // Initialize notification sounds
21
39
  function initializeSounds() {
22
40
  successSound = new Audio('/research/static/sounds/success.mp3');
@@ -47,25 +65,46 @@ document.addEventListener('DOMContentLoaded', () => {
47
65
 
48
66
  // Initialize socket only when needed with a timeout for safety
49
67
  function initializeSocket() {
50
- if (socket) return socket; // Return existing socket if already initialized
68
+ if (socket) {
69
+ // If we already have a socket but it's disconnected, reconnect
70
+ if (!socketConnected) {
71
+ try {
72
+ console.log('Socket disconnected, reconnecting...');
73
+ socket.connect();
74
+ } catch (e) {
75
+ console.error('Error reconnecting socket:', e);
76
+ // Create a new socket
77
+ socket = null;
78
+ return initializeSocket();
79
+ }
80
+ }
81
+ return socket;
82
+ }
51
83
 
52
84
  console.log('Initializing socket connection...');
53
85
  // Create new socket connection with optimized settings for threading mode
54
86
  socket = io({
55
87
  path: '/research/socket.io',
56
- transports: ['websocket', 'polling'],
57
- reconnection: true,
58
- reconnectionAttempts: 3,
59
- reconnectionDelay: 1000,
60
- timeout: 5000,
88
+ transports: ['polling', 'websocket'], // Try polling first, then websocket
89
+ reconnection: true,
90
+ reconnectionAttempts: 10,
91
+ reconnectionDelay: 1000,
92
+ timeout: 25000, // Increase timeout further
61
93
  autoConnect: true,
62
- forceNew: true
94
+ forceNew: true,
95
+ upgrade: false // Disable automatic transport upgrade for more stability
63
96
  });
64
97
 
65
98
  // Add event handlers
66
- socket.on('connect', () => {
99
+ socket.on('connect', () => {
67
100
  console.log('Socket connected');
68
101
  socketConnected = true;
102
+
103
+ // If we're reconnecting and have a current research, resubscribe
104
+ if (currentResearchId) {
105
+ console.log(`Reconnected, resubscribing to research ${currentResearchId}`);
106
+ socket.emit('subscribe_to_research', { research_id: currentResearchId });
107
+ }
69
108
  });
70
109
 
71
110
  socket.on('disconnect', () => {
@@ -78,18 +117,52 @@ document.addEventListener('DOMContentLoaded', () => {
78
117
  socketConnected = false;
79
118
  });
80
119
 
120
+ socket.on('error', (error) => {
121
+ console.error('Socket error:', error);
122
+ });
123
+
81
124
  // Set a timeout to detect hanging connections
82
- setTimeout(() => {
125
+ let connectionTimeoutId = setTimeout(() => {
83
126
  if (!socketConnected) {
84
127
  console.log('Socket connection timeout - forcing reconnect');
85
128
  try {
86
- socket.disconnect();
87
- socket.connect();
129
+ if (socket) {
130
+ // First try to disconnect cleanly
131
+ try {
132
+ socket.disconnect();
133
+ } catch (disconnectErr) {
134
+ console.warn('Error disconnecting socket during timeout:', disconnectErr);
135
+ }
136
+
137
+ // Then try to reconnect
138
+ try {
139
+ socket.connect();
140
+ } catch (connectErr) {
141
+ console.warn('Error reconnecting socket during timeout:', connectErr);
142
+ // Create a new socket only if connect fails
143
+ socket = null;
144
+ socket = initializeSocket();
145
+ }
146
+ } else {
147
+ // Socket is already null, just create a new one
148
+ socket = initializeSocket();
149
+ }
88
150
  } catch (e) {
89
151
  console.error('Error during forced reconnect:', e);
152
+ // Force create a new socket
153
+ socket = null;
154
+ socket = initializeSocket();
90
155
  }
91
156
  }
92
- }, 5000);
157
+ }, 15000); // Longer timeout before forcing reconnection
158
+
159
+ // Clean up timeout if socket connects
160
+ socket.on('connect', () => {
161
+ if (connectionTimeoutId) {
162
+ clearTimeout(connectionTimeoutId);
163
+ connectionTimeoutId = null;
164
+ }
165
+ });
93
166
 
94
167
  return socket;
95
168
  }
@@ -99,22 +172,50 @@ document.addEventListener('DOMContentLoaded', () => {
99
172
  try {
100
173
  if (socket) {
101
174
  console.log('Manually disconnecting socket');
102
- socket.removeAllListeners();
103
- socket.disconnect();
175
+ try {
176
+ // First remove all listeners
177
+ socket.removeAllListeners();
178
+ } catch (listenerErr) {
179
+ console.warn('Error removing socket listeners:', listenerErr);
180
+ }
181
+
182
+ try {
183
+ // Then disconnect
184
+ socket.disconnect();
185
+ } catch (disconnectErr) {
186
+ console.warn('Error during socket disconnect:', disconnectErr);
187
+ }
188
+
189
+ // Always set to null to allow garbage collection
104
190
  socket = null;
105
191
  socketConnected = false;
106
192
  }
107
193
  } catch (e) {
108
194
  console.error('Error disconnecting socket:', e);
195
+ // Ensure socket is nullified even if errors occur
196
+ socket = null;
197
+ socketConnected = false;
109
198
  }
110
199
  };
111
200
 
112
201
  // Helper function to connect to research updates
113
202
  window.connectToResearchSocket = function(researchId) {
114
203
  try {
204
+ // Check if the research ID is valid
205
+ if (!researchId) {
206
+ console.error('Invalid research ID for socket connection');
207
+ return false;
208
+ }
209
+
210
+ // First disconnect any existing socket to avoid duplicates
211
+ window.disconnectSocket();
212
+
115
213
  // Initialize socket if needed
214
+ socket = initializeSocket();
215
+
116
216
  if (!socket) {
117
- socket = initializeSocket();
217
+ console.error('Failed to initialize socket for research updates');
218
+ return false;
118
219
  }
119
220
 
120
221
  // Subscribe to research updates
@@ -124,66 +225,97 @@ document.addEventListener('DOMContentLoaded', () => {
124
225
  const progressEventName = `research_progress_${researchId}`;
125
226
 
126
227
  // Remove existing listeners to prevent duplicates
127
- socket.off(progressEventName);
228
+ try {
229
+ socket.off(progressEventName);
230
+ } catch (e) {
231
+ console.warn(`Error removing existing listeners for ${progressEventName}`, e);
232
+ }
128
233
 
129
234
  // Add new listener
130
235
  socket.on(progressEventName, (data) => {
131
236
  console.log('Received research progress update:', data);
132
- updateProgressUI(data.progress, data.status, data.message);
133
237
 
134
- // If research is complete, show the completion buttons
135
- if (data.status === 'completed' || data.status === 'terminated' || data.status === 'failed' || data.status === 'suspended') {
136
- console.log(`Socket received research final state: ${data.status}`);
238
+ // Handle different message types
239
+ if (data.status === 'in_progress') {
240
+ // Update progress
241
+ updateProgressUI(data.progress, data.status, data.message);
242
+ } else if (data.status === 'completed') {
243
+ // Research completed
244
+ console.log('Socket received research complete notification');
137
245
 
138
- // Clear polling interval if it exists
246
+ // Clear polling interval
139
247
  if (pollingInterval) {
140
248
  console.log('Clearing polling interval from socket event');
141
249
  clearInterval(pollingInterval);
142
250
  pollingInterval = null;
143
251
  }
144
252
 
145
- // Update navigation state
146
- if (data.status === 'completed') {
147
- isResearchInProgress = false;
253
+ // Load the results
254
+ loadResearch(researchId);
255
+
256
+ // Play notification sound
257
+ console.log('Playing success notification sound from socket event');
258
+ playNotificationSound('success');
259
+
260
+ // Clean up resources
261
+ window.cleanupResearchResources();
262
+
263
+ // Update navigation and research status display
264
+ updateNavigationBasedOnResearchStatus();
265
+
266
+ // Update history item status if on history page
267
+ updateHistoryItemStatus(researchId, 'completed');
268
+
269
+ // Dispatch event for any other components that need to know about completion
270
+ document.dispatchEvent(new CustomEvent('research_completed', { detail: data }));
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;
148
280
  }
149
281
 
150
- // Update UI for completion
151
- if (data.status === 'completed') {
152
- console.log('Research completed via socket, loading results automatically');
153
-
154
- // Hide terminate button
155
- const terminateBtn = document.getElementById('terminate-research-btn');
156
- if (terminateBtn) {
157
- terminateBtn.style.display = 'none';
158
- }
159
-
160
- // Auto-load the results
161
- loadResearch(researchId);
162
- } else if (data.status === 'failed' || data.status === 'suspended') {
163
- console.log(`Showing error message for status: ${data.status} from socket event`);
164
- const errorMessage = document.getElementById('error-message');
165
- if (errorMessage) {
166
- errorMessage.style.display = 'block';
167
- errorMessage.textContent = data.status === 'failed' ?
168
- (data.metadata && data.metadata.error ? JSON.parse(data.metadata).error : 'Research failed') :
169
- 'Research was suspended';
170
- } else {
171
- console.error('error-message element not found in socket handler');
172
- }
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;
173
292
  }
174
293
 
294
+ // Update UI with status
295
+ updateProgressUI(
296
+ 0,
297
+ data.status,
298
+ errorText
299
+ );
300
+
301
+ // Update history item status if on history page
302
+ updateHistoryItemStatus(researchId, data.status, errorText);
303
+
304
+ // Update navigation
175
305
  updateNavigationBasedOnResearchStatus();
176
306
 
177
- // Play notification sounds based on status
178
- if (data.status === 'completed') {
179
- console.log('Playing success notification sound from socket event');
180
- playNotificationSound('success');
181
- } else if (data.status === 'failed') {
307
+ if (data.status === 'failed') {
182
308
  console.log('Playing error notification sound from socket event');
183
309
  playNotificationSound('error');
184
310
  }
185
311
 
186
- // Force the UI to update with a manual trigger
312
+ // Clean up resources
313
+ window.cleanupResearchResources();
314
+
315
+ // Update navigation and research status display
316
+ updateNavigationBasedOnResearchStatus();
317
+
318
+ // Dispatch event for any other components that need to know about completion
187
319
  document.dispatchEvent(new CustomEvent('research_completed', { detail: data }));
188
320
  }
189
321
 
@@ -210,22 +342,62 @@ document.addEventListener('DOMContentLoaded', () => {
210
342
  const activeResearch = history.find(item => item.status === 'in_progress');
211
343
 
212
344
  if (activeResearch) {
213
- isResearchInProgress = true;
214
- currentResearchId = activeResearch.id;
215
- window.currentResearchId = currentResearchId;
216
-
217
- // Check if we're on the new research page and redirect to progress
218
- const currentPage = document.querySelector('.page.active');
219
-
220
- if (currentPage && currentPage.id === 'new-research') {
221
- // Navigate to progress page
222
- switchPage('research-progress');
345
+ // Verify the research is truly active by checking its details
346
+ try {
347
+ const detailsResponse = await fetch(getApiUrl(`/api/research/${activeResearch.id}`));
348
+ const details = await detailsResponse.json();
223
349
 
224
- // Connect to socket for this research
225
- window.connectToResearchSocket(currentResearchId);
350
+ // If status is not in_progress in the details, it's stale
351
+ if (details.status !== 'in_progress') {
352
+ console.log(`Research ${activeResearch.id} is stale (status: ${details.status}), ignoring`);
353
+ return;
354
+ }
226
355
 
227
- // Start polling for updates
228
- pollResearchStatus(currentResearchId);
356
+ // Check when the research was started - if it's been more than 1 hour, it might be stale
357
+ if (details.created_at) {
358
+ const startTime = new Date(details.created_at);
359
+ const currentTime = new Date();
360
+ const hoursSinceStart = (currentTime - startTime) / (1000 * 60 * 60);
361
+
362
+ if (hoursSinceStart > 1) {
363
+ console.log(`Research ${activeResearch.id} has been running for ${hoursSinceStart.toFixed(2)} hours, which is unusually long. Checking for activity...`);
364
+
365
+ // Check if there has been log activity in the last 10 minutes
366
+ let recentActivity = false;
367
+ if (details.log && Array.isArray(details.log) && details.log.length > 0) {
368
+ const lastLogTime = new Date(details.log[details.log.length - 1].time);
369
+ const minutesSinceLastLog = (currentTime - lastLogTime) / (1000 * 60);
370
+
371
+ if (minutesSinceLastLog < 10) {
372
+ recentActivity = true;
373
+ } else {
374
+ console.log(`No recent activity for ${minutesSinceLastLog.toFixed(2)} minutes, treating as stale`);
375
+ return;
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ // If we get here, the research seems to be genuinely active
382
+ isResearchInProgress = true;
383
+ currentResearchId = activeResearch.id;
384
+ window.currentResearchId = currentResearchId;
385
+
386
+ // Check if we're on the new research page and redirect to progress
387
+ const currentPage = document.querySelector('.page.active');
388
+
389
+ if (currentPage && currentPage.id === 'new-research') {
390
+ // Navigate to progress page
391
+ switchPage('research-progress');
392
+
393
+ // Connect to socket for this research
394
+ window.connectToResearchSocket(currentResearchId);
395
+
396
+ // Start polling for updates
397
+ pollResearchStatus(currentResearchId);
398
+ }
399
+ } catch (detailsError) {
400
+ console.error('Error checking research details:', detailsError);
229
401
  }
230
402
  }
231
403
  } catch (error) {
@@ -240,10 +412,27 @@ document.addEventListener('DOMContentLoaded', () => {
240
412
 
241
413
  // Function to start research
242
414
  async function startResearch(query, mode) {
243
- // Check if research is already in progress
415
+ // Reset potentially stale state
244
416
  if (isResearchInProgress) {
245
- alert('Another research is already in progress. Please wait for it to complete or check its status in the history tab.');
246
- return;
417
+ // Force a fresh check of active research status
418
+ try {
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
+ }
247
436
  }
248
437
 
249
438
  // Get the start button
@@ -255,6 +444,9 @@ document.addEventListener('DOMContentLoaded', () => {
255
444
  startResearchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting...';
256
445
 
257
446
  try {
447
+ // Set the favicon based on research mode
448
+ setFavicon(mode);
449
+
258
450
  const response = await fetch(getApiUrl('/api/start_research'), {
259
451
  method: 'POST',
260
452
  headers: {
@@ -300,11 +492,17 @@ document.addEventListener('DOMContentLoaded', () => {
300
492
  terminateBtn.disabled = false;
301
493
  }
302
494
  } else {
495
+ // Reset favicon to default on error
496
+ createDynamicFavicon('⚡');
497
+
303
498
  alert('Error starting research: ' + (data.message || 'Unknown error'));
304
499
  startResearchBtn.disabled = false;
305
500
  startResearchBtn.innerHTML = '<i class="fas fa-rocket"></i> Start Research';
306
501
  }
307
502
  } catch (error) {
503
+ // Reset favicon to default on error
504
+ createDynamicFavicon('⚡');
505
+
308
506
  console.error('Error:', error);
309
507
  alert('An error occurred while starting the research. Please try again.');
310
508
  startResearchBtn.disabled = false;
@@ -324,12 +522,26 @@ document.addEventListener('DOMContentLoaded', () => {
324
522
  })
325
523
  .then(data => {
326
524
  console.log('Research status response:', data);
327
- // Update the UI with the current progress
328
- updateProgressUI(data.progress, data.status, data.message);
525
+
526
+ // Get the latest progress and log data
527
+ const progress = data.progress || 0;
528
+ const status = data.status || 'in_progress';
529
+
530
+ // Get the latest message from the log if available
531
+ let latestMessage = "Processing research...";
532
+ if (data.log && Array.isArray(data.log) && data.log.length > 0) {
533
+ const latestLog = data.log[data.log.length - 1];
534
+ if (latestLog && latestLog.message) {
535
+ latestMessage = latestLog.message;
536
+ }
537
+ }
538
+
539
+ // Update the UI with the current progress - always
540
+ updateProgressUI(progress, status, latestMessage);
329
541
 
330
542
  // If research is complete, show the completion buttons
331
- if (data.status === 'completed' || data.status === 'failed' || data.status === 'suspended') {
332
- console.log(`Research is in final state: ${data.status}`);
543
+ if (status === 'completed' || status === 'failed' || status === 'suspended') {
544
+ console.log(`Research is in final state: ${status}`);
333
545
  // Clear the polling interval
334
546
  if (pollingInterval) {
335
547
  console.log('Clearing polling interval');
@@ -338,7 +550,7 @@ document.addEventListener('DOMContentLoaded', () => {
338
550
  }
339
551
 
340
552
  // Update UI for completion
341
- if (data.status === 'completed') {
553
+ if (status === 'completed') {
342
554
  console.log('Research completed, loading results automatically');
343
555
  // Hide the terminate button
344
556
  const terminateBtn = document.getElementById('terminate-research-btn');
@@ -346,26 +558,49 @@ document.addEventListener('DOMContentLoaded', () => {
346
558
  terminateBtn.style.display = 'none';
347
559
  }
348
560
 
561
+ // Update history item status
562
+ updateHistoryItemStatus(researchId, 'completed');
563
+
349
564
  // Auto-load the results instead of showing a button
350
565
  loadResearch(researchId);
351
- } else if (data.status === 'failed' || data.status === 'suspended') {
352
- console.log(`Showing error message for status: ${data.status}`);
566
+ } else if (status === 'failed' || status === 'suspended') {
567
+ console.log(`Showing error message for status: ${status}`);
568
+
569
+ // Get error message from metadata if available or use latest message
570
+ let errorText = latestMessage;
571
+ if (data.metadata && typeof data.metadata === 'string') {
572
+ try {
573
+ const metadataObj = JSON.parse(data.metadata);
574
+ if (metadataObj.error) {
575
+ errorText = metadataObj.error;
576
+ }
577
+ } catch (e) {
578
+ console.log('Error parsing metadata:', e);
579
+ }
580
+ }
581
+
582
+ // Show error message in UI
353
583
  const errorMessage = document.getElementById('error-message');
354
584
  if (errorMessage) {
355
585
  errorMessage.style.display = 'block';
356
- errorMessage.textContent = data.status === 'failed' ?
357
- (data.metadata && data.metadata.error ? JSON.parse(data.metadata).error : 'Research failed') :
358
- 'Research was suspended';
586
+ errorMessage.textContent = errorText;
587
+ console.log('Set error message to:', errorText);
359
588
  } else {
360
589
  console.error('error-message element not found');
361
590
  }
591
+
592
+ // Update progress display with error
593
+ updateProgressUI(0, status, errorText);
594
+
595
+ // Update history item status
596
+ updateHistoryItemStatus(researchId, status, errorText);
362
597
  }
363
598
 
364
599
  // Play notification sound based on status
365
- if (data.status === 'completed') {
600
+ if (status === 'completed') {
366
601
  console.log('Playing success notification sound');
367
602
  playNotificationSound('success');
368
- } else if (data.status === 'failed') {
603
+ } else if (status === 'failed') {
369
604
  console.log('Playing error notification sound');
370
605
  playNotificationSound('error');
371
606
  }
@@ -381,7 +616,7 @@ document.addEventListener('DOMContentLoaded', () => {
381
616
  }
382
617
 
383
618
  // Continue polling if still in progress
384
- if (data.status === 'in_progress') {
619
+ if (status === 'in_progress') {
385
620
  console.log('Research is still in progress, continuing polling');
386
621
  if (!pollingInterval) {
387
622
  console.log('Setting up polling interval');
@@ -403,6 +638,9 @@ document.addEventListener('DOMContentLoaded', () => {
403
638
  // Initialize the sounds
404
639
  initializeSounds();
405
640
 
641
+ // Create a dynamic favicon with the lightning emoji by default
642
+ createDynamicFavicon('⚡');
643
+
406
644
  // Get navigation elements
407
645
  const navItems = document.querySelectorAll('.sidebar-nav li');
408
646
  const mobileNavItems = document.querySelectorAll('.mobile-tab-bar li');
@@ -474,6 +712,10 @@ document.addEventListener('DOMContentLoaded', () => {
474
712
  option.addEventListener('click', function() {
475
713
  modeOptions.forEach(opt => opt.classList.remove('active'));
476
714
  this.classList.add('active');
715
+
716
+ // Update favicon based on selected mode
717
+ const mode = this.dataset.mode;
718
+ setFavicon(mode);
477
719
  });
478
720
  });
479
721
 
@@ -602,9 +844,21 @@ document.addEventListener('DOMContentLoaded', () => {
602
844
  console.log('Termination request sent successfully');
603
845
 
604
846
  // If we're on the history page, update the status of this item
605
- if (document.getElementById('history').classList.contains('active')) {
847
+ if (document.getElementById('history').classList.contains('active')) {
606
848
  updateHistoryItemStatus(researchId, 'terminating', 'Terminating...');
607
849
  }
850
+
851
+ // Set a timeout to reset UI if socket doesn't respond
852
+ setTimeout(() => {
853
+ if (isTerminating) {
854
+ console.log('Termination timeout - resetting UI');
855
+ isTerminating = false;
856
+ resetProgressAnimations();
857
+
858
+ // Update the navigation
859
+ updateNavigationBasedOnResearchStatus();
860
+ }
861
+ }, 10000); // 10 second timeout
608
862
  } else {
609
863
  console.error('Termination request failed:', data.message);
610
864
  alert(`Failed to terminate research: ${data.message}`);
@@ -626,8 +880,8 @@ document.addEventListener('DOMContentLoaded', () => {
626
880
  btn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate';
627
881
  }
628
882
  });
629
- }
630
- } catch (error) {
883
+ }
884
+ } catch (error) {
631
885
  console.error('Error terminating research:', error);
632
886
  alert('An error occurred while trying to terminate the research.');
633
887
 
@@ -660,6 +914,8 @@ document.addEventListener('DOMContentLoaded', () => {
660
914
  const progressFill = document.getElementById('progress-fill');
661
915
  const progressPercentage = document.getElementById('progress-percentage');
662
916
  const progressStatus = document.getElementById('progress-status');
917
+ const errorMessage = document.getElementById('error-message');
918
+ const tryAgainBtn = document.getElementById('try-again-btn');
663
919
 
664
920
  if (progressFill && progressPercentage) {
665
921
  progressFill.style.width = `${progress}%`;
@@ -683,12 +939,51 @@ document.addEventListener('DOMContentLoaded', () => {
683
939
  terminateBtn.style.display = 'inline-flex';
684
940
  terminateBtn.disabled = false;
685
941
  terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
942
+
943
+ // Hide try again button when in progress
944
+ if (tryAgainBtn) {
945
+ tryAgainBtn.style.display = 'none';
946
+ }
686
947
  } else if (status === 'terminating') {
687
948
  terminateBtn.style.display = 'inline-flex';
688
949
  terminateBtn.disabled = true;
689
950
  terminateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Terminating...';
690
951
  } else {
691
952
  terminateBtn.style.display = 'none';
953
+
954
+ // Show try again button for failed or suspended research
955
+ if (tryAgainBtn && (status === 'failed' || status === 'suspended')) {
956
+ tryAgainBtn.style.display = 'inline-flex';
957
+
958
+ // Get the current query for retry
959
+ const currentQuery = document.getElementById('current-query');
960
+ const queryText = currentQuery ? currentQuery.textContent : '';
961
+
962
+ // Add click event to try again button to go back to research form with the query preserved
963
+ tryAgainBtn.onclick = function() {
964
+ // Switch to the research form
965
+ switchPage('new-research');
966
+
967
+ // Set the query text in the form
968
+ const queryTextarea = document.getElementById('query');
969
+ if (queryTextarea && queryText) {
970
+ queryTextarea.value = queryText;
971
+ }
972
+
973
+ // Clean up any remaining research state
974
+ window.cleanupResearchResources();
975
+ };
976
+ }
977
+ }
978
+ }
979
+
980
+ // Show error message when there's an error
981
+ if (errorMessage) {
982
+ if (status === 'failed' || status === 'suspended') {
983
+ errorMessage.style.display = 'block';
984
+ errorMessage.textContent = message || (status === 'failed' ? 'Research failed' : 'Research was suspended');
985
+ } else {
986
+ errorMessage.style.display = 'none';
692
987
  }
693
988
  }
694
989
  }
@@ -764,8 +1059,11 @@ document.addEventListener('DOMContentLoaded', () => {
764
1059
  title.textContent = item.query || 'Untitled Research';
765
1060
 
766
1061
  const status = document.createElement('div');
767
- status.className = `history-item-status status-${item.status || 'unknown'}`;
768
- status.textContent = item.status ? (item.status.charAt(0).toUpperCase() + item.status.slice(1)) : 'Unknown';
1062
+ status.className = `history-item-status status-${item.status ? item.status.replace('_', '-') : 'unknown'}`;
1063
+ status.textContent = item.status ?
1064
+ (item.status === 'in_progress' ? 'In Progress' :
1065
+ item.status.charAt(0).toUpperCase() + item.status.slice(1)) :
1066
+ 'Unknown';
769
1067
 
770
1068
  header.appendChild(title);
771
1069
  header.appendChild(status);
@@ -946,145 +1244,160 @@ document.addEventListener('DOMContentLoaded', () => {
946
1244
  }
947
1245
  }
948
1246
 
949
- // Function to load a specific research result
1247
+ // Function to load research
950
1248
  async function loadResearch(researchId) {
951
- // Navigate to results page
952
- switchPage('research-results');
953
-
954
- const resultsContent = document.getElementById('results-content');
955
- resultsContent.innerHTML = '<div class="loading-spinner centered"><div class="spinner"></div></div>';
956
-
957
1249
  try {
958
- // Load research details
959
- const detailsResponse = await fetch(getApiUrl(`/api/research/${researchId}`));
960
- const details = await detailsResponse.json();
1250
+ const response = await fetch(getApiUrl(`/api/research/${researchId}`));
1251
+ if (!response.ok) {
1252
+ throw new Error('Failed to load research');
1253
+ }
961
1254
 
962
- // Display metadata
963
- document.getElementById('result-query').textContent = details.query;
1255
+ const data = await response.json();
964
1256
 
965
- // Format date with duration if available
966
- let dateText = formatDate(new Date(details.completed_at || details.created_at));
967
- if (details.duration_seconds) {
968
- // Format duration
969
- let durationText = '';
970
- const duration = parseInt(details.duration_seconds);
1257
+ if (data.status === 'completed' && data.report_path) {
1258
+ // Set the favicon based on the research mode
1259
+ setFavicon(data.mode || 'quick');
971
1260
 
972
- if (duration < 60) { // less than a minute
973
- durationText = `${duration}s`;
974
- } else if (duration < 3600) { // less than an hour
975
- durationText = `${Math.floor(duration / 60)}m ${duration % 60}s`;
976
- } else { // hours
977
- durationText = `${Math.floor(duration / 3600)}h ${Math.floor((duration % 3600) / 60)}m`;
1261
+ // Get report content
1262
+ const reportResponse = await fetch(getApiUrl(`/api/report/${researchId}`));
1263
+ if (!reportResponse.ok) {
1264
+ throw new Error('Failed to load report');
978
1265
  }
979
1266
 
980
- dateText += ` (Duration: ${durationText})`;
981
- }
982
- document.getElementById('result-date').textContent = dateText;
983
-
984
- document.getElementById('result-mode').textContent = details.mode === 'quick' ? 'Quick Summary' : 'Detailed Report';
985
-
986
- // Load the report content
987
- const reportResponse = await fetch(getApiUrl(`/api/report/${researchId}`));
988
- const reportData = await reportResponse.json();
989
-
990
- if (reportData.status === 'success') {
991
- // Render markdown
992
- const renderedContent = marked.parse(reportData.content);
993
- resultsContent.innerHTML = renderedContent;
1267
+ const reportData = await reportResponse.json();
994
1268
 
995
- // Apply syntax highlighting
996
- document.querySelectorAll('pre code').forEach((block) => {
997
- hljs.highlightElement(block);
998
- });
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');
1283
+
1284
+ // Enable syntax highlighting
1285
+ const renderedContent = marked.parse(reportData.content);
1286
+ resultsContent.innerHTML = renderedContent;
1287
+
1288
+ // Apply syntax highlighting
1289
+ document.querySelectorAll('pre code').forEach((block) => {
1290
+ hljs.highlightElement(block);
1291
+ });
1292
+ }
1293
+
1294
+ // Switch to results page
1295
+ switchPage('research-results');
1296
+ } else {
1297
+ throw new Error(reportData.message || 'Failed to load report content');
1298
+ }
1299
+ } else if (data.status === 'in_progress') {
1300
+ // Set favicon based on the research mode
1301
+ setFavicon(data.mode || 'quick');
1302
+
1303
+ // Redirect to progress page
1304
+ navigateToResearchProgress(data);
999
1305
  } else {
1000
- resultsContent.innerHTML = '<div class="error-message">Error loading report. Please try again later.</div>';
1306
+ // Show research details for failed or suspended research
1307
+ loadResearchDetails(researchId);
1001
1308
  }
1002
1309
  } catch (error) {
1003
1310
  console.error('Error loading research:', error);
1004
- resultsContent.innerHTML = '<div class="error-message">Error loading research results. Please try again later.</div>';
1311
+ alert('Error loading research: ' + error.message);
1312
+ // Reset favicon to default on error
1313
+ createDynamicFavicon('⚡');
1005
1314
  }
1006
1315
  }
1007
1316
 
1008
- // Function to load research details page
1317
+ // Function to load research details
1009
1318
  async function loadResearchDetails(researchId) {
1010
- // Navigate to details page
1011
- switchPage('research-details');
1012
-
1013
- // Initialize the research log area
1014
- const researchLog = document.getElementById('research-log');
1015
- researchLog.innerHTML = '<div class="loading-spinner centered"><div class="spinner"></div></div>';
1016
-
1017
1319
  try {
1018
- // Load research details
1019
- const response = await fetch(getApiUrl(`/api/research/${researchId}/details`));
1020
- console.log('Research details API response status:', response.status);
1320
+ // Navigate to details page
1321
+ switchPage('research-details');
1322
+
1323
+ const researchLog = document.getElementById('research-log');
1324
+ researchLog.innerHTML = '<div class="loading-spinner centered"><div class="spinner"></div></div>';
1021
1325
 
1326
+ const response = await fetch(getApiUrl(`/api/research/${researchId}/details`));
1022
1327
  if (!response.ok) {
1023
- console.error('API error:', response.status, response.statusText);
1024
- researchLog.innerHTML = `<div class="error-message">Error loading research details. Status: ${response.status}</div>`;
1025
- return;
1328
+ throw new Error('Failed to load research details');
1026
1329
  }
1027
1330
 
1028
1331
  const data = await response.json();
1029
- console.log('Research details data:', data);
1030
1332
 
1031
- if (data.status !== 'success') {
1032
- researchLog.innerHTML = `<div class="error-message">Error loading research details: ${data.message || 'Unknown error'}</div>`;
1033
- return;
1034
- }
1333
+ // Set the favicon based on the research mode
1334
+ setFavicon(data.mode || 'quick');
1035
1335
 
1036
- // Display metadata
1037
- document.getElementById('detail-query').textContent = data.query || 'N/A';
1336
+ // Update UI with research details
1337
+ document.getElementById('detail-query').textContent = data.query || 'Unknown query';
1038
1338
  document.getElementById('detail-status').textContent = capitalizeFirstLetter(data.status || 'unknown');
1039
- document.getElementById('detail-status').className = `metadata-value status-${data.status || 'unknown'}`;
1040
- document.getElementById('detail-mode').textContent = (data.mode === 'quick' ? 'Quick Summary' : 'Detailed Report') || 'N/A';
1339
+ document.getElementById('detail-mode').textContent = data.mode === 'detailed' ? 'Detailed Report' : 'Quick Summary';
1041
1340
 
1042
- // Update progress bar
1341
+ // Update progress indicator
1043
1342
  const progress = data.progress || 0;
1044
1343
  document.getElementById('detail-progress-fill').style.width = `${progress}%`;
1045
1344
  document.getElementById('detail-progress-percentage').textContent = `${progress}%`;
1046
1345
 
1047
- // Render log entries
1048
- renderLogEntries(data.log || []);
1346
+ // Add status classes
1347
+ document.getElementById('detail-status').className = 'metadata-value status-' + (data.status || 'unknown');
1049
1348
 
1050
- // Connect to socket for real-time updates
1051
- window.connectToResearchSocket(researchId);
1349
+ // Clear existing log entries
1350
+ researchLog.innerHTML = '';
1351
+
1352
+ // Render log entries
1353
+ if (data.log && Array.isArray(data.log)) {
1354
+ renderLogEntries(data.log);
1355
+ } else {
1356
+ researchLog.innerHTML = '<div class="empty-message">No log entries available</div>';
1357
+ }
1052
1358
 
1053
- // Add appropriate actions based on research status
1054
- const detailActions = document.getElementById('detail-actions');
1055
- detailActions.innerHTML = '';
1359
+ // Update actions based on status
1360
+ const actionsContainer = document.getElementById('detail-actions');
1361
+ actionsContainer.innerHTML = '';
1056
1362
 
1057
- if (data.status === 'completed') {
1363
+ if (data.status === 'completed' && data.report_path) {
1364
+ // Add button to view results
1058
1365
  const viewResultsBtn = document.createElement('button');
1059
1366
  viewResultsBtn.className = 'btn btn-primary';
1060
1367
  viewResultsBtn.innerHTML = '<i class="fas fa-eye"></i> View Results';
1061
1368
  viewResultsBtn.addEventListener('click', () => loadResearch(researchId));
1062
- detailActions.appendChild(viewResultsBtn);
1063
-
1064
- // Add download PDF button
1065
- const downloadPdfBtn = document.createElement('button');
1066
- downloadPdfBtn.className = 'btn btn-outline';
1067
- downloadPdfBtn.innerHTML = '<i class="fas fa-file-pdf"></i> Download PDF';
1068
- downloadPdfBtn.addEventListener('click', () => generatePdfFromResearch(researchId));
1069
- detailActions.appendChild(downloadPdfBtn);
1369
+ actionsContainer.appendChild(viewResultsBtn);
1070
1370
  } else if (data.status === 'in_progress') {
1371
+ // Add button to view progress
1071
1372
  const viewProgressBtn = document.createElement('button');
1072
1373
  viewProgressBtn.className = 'btn btn-primary';
1073
- viewProgressBtn.innerHTML = '<i class="fas fa-sync"></i> View Live Progress';
1074
- viewProgressBtn.addEventListener('click', () => {
1075
- document.getElementById('current-query').textContent = data.query || '';
1076
-
1077
- // Navigate to progress page
1078
- switchPage('research-progress');
1079
-
1080
- // Connect to socket
1081
- window.connectToResearchSocket(researchId);
1082
- });
1083
- detailActions.appendChild(viewProgressBtn);
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);
1084
1384
  }
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);
1085
1396
  } catch (error) {
1086
1397
  console.error('Error loading research details:', error);
1087
- researchLog.innerHTML = `<div class="error-message">Error loading research details: ${error.message}</div>`;
1398
+ document.getElementById('research-log').innerHTML = '<div class="error-message">Error loading research details. Please try again later.</div>';
1399
+ // Reset favicon to default on error
1400
+ createDynamicFavicon('⚡');
1088
1401
  }
1089
1402
  }
1090
1403
 
@@ -1213,15 +1526,13 @@ document.addEventListener('DOMContentLoaded', () => {
1213
1526
  researchLog.scrollTop = researchLog.scrollHeight;
1214
1527
  }
1215
1528
 
1216
- // Back to history button handlers
1217
- document.getElementById('back-to-history').addEventListener('click', () => {
1218
- const historyNav = Array.from(navItems).find(item => item.getAttribute('data-page') === 'history');
1219
- historyNav.click();
1529
+ // Back to history button handlers - using direct page switching
1530
+ document.getElementById('back-to-history')?.addEventListener('click', () => {
1531
+ switchPage('history');
1220
1532
  });
1221
1533
 
1222
- document.getElementById('back-to-history-from-details').addEventListener('click', () => {
1223
- const historyNav = Array.from(navItems).find(item => item.getAttribute('data-page') === 'history');
1224
- historyNav.click();
1534
+ document.getElementById('back-to-history-from-details')?.addEventListener('click', () => {
1535
+ switchPage('history');
1225
1536
  });
1226
1537
 
1227
1538
  // Helper functions
@@ -1323,6 +1634,25 @@ document.addEventListener('DOMContentLoaded', () => {
1323
1634
  newResearchNav.removeAttribute('data-original-page');
1324
1635
  }
1325
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
+
1326
1656
  // If the terminate button is visible, hide it
1327
1657
  const terminateBtn = document.getElementById('terminate-research-btn');
1328
1658
  if (terminateBtn) {
@@ -1330,6 +1660,20 @@ document.addEventListener('DOMContentLoaded', () => {
1330
1660
  terminateBtn.disabled = false;
1331
1661
  terminateBtn.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
1332
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
+ }
1333
1677
  }
1334
1678
 
1335
1679
  // Make sure the navigation highlights the correct item
@@ -1346,9 +1690,16 @@ document.addEventListener('DOMContentLoaded', () => {
1346
1690
  });
1347
1691
  }
1348
1692
 
1349
- // Connect to socket for updates
1350
- if (currentResearchId) {
1693
+ // Connect to socket for updates only if research is in progress
1694
+ if (isResearchInProgress && currentResearchId) {
1351
1695
  window.connectToResearchSocket(currentResearchId);
1696
+ } else {
1697
+ // Disconnect socket if no active research
1698
+ window.disconnectSocket();
1699
+
1700
+ // Also reset the current research ID
1701
+ currentResearchId = null;
1702
+ window.currentResearchId = null;
1352
1703
  }
1353
1704
  }
1354
1705
 
@@ -1389,14 +1740,22 @@ document.addEventListener('DOMContentLoaded', () => {
1389
1740
  function updateHistoryItemStatus(researchId, status, statusText) {
1390
1741
  const historyList = document.getElementById('history-list');
1391
1742
 
1743
+ // Format the status for display
1744
+ const displayStatus = statusText ||
1745
+ (status === 'in_progress' ? 'In Progress' :
1746
+ status.charAt(0).toUpperCase() + status.slice(1));
1747
+
1748
+ // Format the CSS class
1749
+ const statusClass = `status-${status.replace('_', '-')}`;
1750
+
1392
1751
  // Look for the item in the active research banner
1393
1752
  const activeBanner = historyList.querySelector(`.active-research-banner[data-research-id="${researchId}"]`);
1394
1753
  if (activeBanner) {
1395
1754
  const statusEl = activeBanner.querySelector('.history-item-status');
1396
1755
  if (statusEl) {
1397
- statusEl.textContent = statusText || capitalizeFirstLetter(status);
1756
+ statusEl.textContent = displayStatus;
1398
1757
  statusEl.className = 'history-item-status';
1399
- statusEl.classList.add(`status-${status}`);
1758
+ statusEl.classList.add(statusClass);
1400
1759
  }
1401
1760
 
1402
1761
  // Update buttons
@@ -1423,9 +1782,9 @@ document.addEventListener('DOMContentLoaded', () => {
1423
1782
  if (historyItem) {
1424
1783
  const statusEl = historyItem.querySelector('.history-item-status');
1425
1784
  if (statusEl) {
1426
- statusEl.textContent = statusText || capitalizeFirstLetter(status);
1785
+ statusEl.textContent = displayStatus;
1427
1786
  statusEl.className = 'history-item-status';
1428
- statusEl.classList.add(`status-${status}`);
1787
+ statusEl.classList.add(statusClass);
1429
1788
  }
1430
1789
 
1431
1790
  // Update view button
@@ -1437,6 +1796,15 @@ document.addEventListener('DOMContentLoaded', () => {
1437
1796
  } else if (status === 'failed') {
1438
1797
  viewBtn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Failed';
1439
1798
  viewBtn.disabled = true;
1799
+ } else if (status === 'completed') {
1800
+ viewBtn.innerHTML = '<i class="fas fa-eye"></i> View';
1801
+ viewBtn.disabled = false;
1802
+
1803
+ // Also make the PDF button visible if not already
1804
+ const pdfBtn = historyItem.querySelector('.pdf-btn');
1805
+ if (pdfBtn) {
1806
+ pdfBtn.style.display = 'inline-flex';
1807
+ }
1440
1808
  }
1441
1809
  }
1442
1810
  }
@@ -2075,4 +2443,79 @@ document.addEventListener('DOMContentLoaded', () => {
2075
2443
  // Update navigation
2076
2444
  updateNavigationBasedOnResearchStatus();
2077
2445
  });
2446
+
2447
+ // Function to reset progress animations and UI elements
2448
+ function resetProgressAnimations() {
2449
+ // Reset the progress bar animation
2450
+ const progressFill = document.getElementById('progress-fill');
2451
+ if (progressFill) {
2452
+ // Force a reflow to reset the animation
2453
+ progressFill.style.display = 'none';
2454
+ progressFill.offsetHeight; // Trigger reflow
2455
+ progressFill.style.display = 'block';
2456
+ }
2457
+
2458
+ // Reset any spinning icons
2459
+ const spinners = document.querySelectorAll('.fa-spinner.fa-spin');
2460
+ spinners.forEach(spinner => {
2461
+ const parent = spinner.parentElement;
2462
+ if (parent && parent.tagName === 'BUTTON') {
2463
+ if (parent.classList.contains('terminate-btn')) {
2464
+ parent.innerHTML = '<i class="fas fa-stop-circle"></i> Terminate Research';
2465
+ parent.disabled = false;
2466
+ }
2467
+ }
2468
+ });
2469
+
2470
+ // Ensure the isTerminating flag is reset
2471
+ isTerminating = false;
2472
+ }
2473
+
2474
+ // Create a dynamic favicon
2475
+ function createDynamicFavicon(emoji = '⚡') {
2476
+ console.log(`Creating dynamic favicon with emoji: ${emoji}`);
2477
+
2478
+ // Create a canvas element
2479
+ const canvas = document.createElement('canvas');
2480
+ canvas.width = 32;
2481
+ canvas.height = 32;
2482
+
2483
+ // Get the canvas context
2484
+ const ctx = canvas.getContext('2d');
2485
+
2486
+ // Clear the canvas with a transparent background
2487
+ ctx.clearRect(0, 0, 32, 32);
2488
+
2489
+ // Set the font size and font family
2490
+ ctx.font = '24px Arial';
2491
+ ctx.textAlign = 'center';
2492
+ ctx.textBaseline = 'middle';
2493
+
2494
+ // Draw the emoji in the center of the canvas
2495
+ ctx.fillText(emoji, 16, 16);
2496
+
2497
+ // Convert canvas to favicon URL
2498
+ const faviconUrl = canvas.toDataURL('image/png');
2499
+
2500
+ // Find existing favicon or create a new one
2501
+ let link = document.querySelector('link[rel="icon"]');
2502
+ if (!link) {
2503
+ link = document.createElement('link');
2504
+ link.rel = 'icon';
2505
+ link.id = 'dynamic-favicon';
2506
+ document.head.appendChild(link);
2507
+ }
2508
+
2509
+ // Update the favicon
2510
+ link.type = 'image/x-icon';
2511
+ link.href = faviconUrl;
2512
+
2513
+ return faviconUrl;
2514
+ }
2515
+
2516
+ // Function to set favicon based on research mode
2517
+ function setFavicon(mode) {
2518
+ const emoji = mode === 'detailed' ? '🔬' : '⚡';
2519
+ return createDynamicFavicon(emoji);
2520
+ }
2078
2521
  });