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.
- local_deep_research/defaults/main.toml +5 -0
- local_deep_research/search_system.py +98 -38
- local_deep_research/web/app.py +360 -117
- local_deep_research/web/static/css/styles.css +28 -2
- local_deep_research/web/static/js/app.js +640 -197
- local_deep_research/web/templates/index.html +3 -1
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +454 -0
- local_deep_research/web_search_engines/search_engine_factory.py +20 -1
- {local_deep_research-0.1.0.dist-info → local_deep_research-0.1.1.dist-info}/METADATA +16 -4
- {local_deep_research-0.1.0.dist-info → local_deep_research-0.1.1.dist-info}/RECORD +14 -13
- {local_deep_research-0.1.0.dist-info → local_deep_research-0.1.1.dist-info}/LICENSE +0 -0
- {local_deep_research-0.1.0.dist-info → local_deep_research-0.1.1.dist-info}/WHEEL +0 -0
- {local_deep_research-0.1.0.dist-info → local_deep_research-0.1.1.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.1.0.dist-info → local_deep_research-0.1.1.dist-info}/top_level.txt +0 -0
@@ -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)
|
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: ['
|
57
|
-
|
58
|
-
reconnectionAttempts:
|
59
|
-
|
60
|
-
timeout:
|
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
|
-
|
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
|
87
|
-
|
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
|
-
},
|
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
|
-
|
103
|
-
|
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
|
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
|
-
|
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
|
-
//
|
135
|
-
if (data.status === '
|
136
|
-
|
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
|
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
|
-
//
|
146
|
-
|
147
|
-
|
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
|
-
//
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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
|
-
//
|
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
|
-
|
214
|
-
|
215
|
-
|
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
|
-
//
|
225
|
-
|
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
|
-
//
|
228
|
-
|
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
|
-
//
|
415
|
+
// Reset potentially stale state
|
244
416
|
if (isResearchInProgress) {
|
245
|
-
|
246
|
-
|
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
|
-
|
328
|
-
|
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 (
|
332
|
-
console.log(`Research is in final state: ${
|
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 (
|
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 (
|
352
|
-
console.log(`Showing error message for 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 =
|
357
|
-
|
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 (
|
600
|
+
if (status === 'completed') {
|
366
601
|
console.log('Playing success notification sound');
|
367
602
|
playNotificationSound('success');
|
368
|
-
} else if (
|
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 (
|
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
|
-
|
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
|
-
|
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
|
768
|
-
status.textContent = item.status ?
|
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
|
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
|
-
|
959
|
-
|
960
|
-
|
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
|
-
|
963
|
-
document.getElementById('result-query').textContent = details.query;
|
1255
|
+
const data = await response.json();
|
964
1256
|
|
965
|
-
|
966
|
-
|
967
|
-
|
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
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
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
|
-
|
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
|
-
|
996
|
-
|
997
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
//
|
1019
|
-
|
1020
|
-
|
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
|
-
|
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
|
-
|
1032
|
-
|
1033
|
-
return;
|
1034
|
-
}
|
1333
|
+
// Set the favicon based on the research mode
|
1334
|
+
setFavicon(data.mode || 'quick');
|
1035
1335
|
|
1036
|
-
//
|
1037
|
-
document.getElementById('detail-query').textContent = data.query || '
|
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-
|
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
|
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
|
-
//
|
1048
|
-
|
1346
|
+
// Add status classes
|
1347
|
+
document.getElementById('detail-status').className = 'metadata-value status-' + (data.status || 'unknown');
|
1049
1348
|
|
1050
|
-
//
|
1051
|
-
|
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
|
-
//
|
1054
|
-
const
|
1055
|
-
|
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
|
-
|
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-
|
1074
|
-
viewProgressBtn.addEventListener('click', () =>
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
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
|
-
|
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')
|
1218
|
-
|
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')
|
1223
|
-
|
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 =
|
1756
|
+
statusEl.textContent = displayStatus;
|
1398
1757
|
statusEl.className = 'history-item-status';
|
1399
|
-
statusEl.classList.add(
|
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 =
|
1785
|
+
statusEl.textContent = displayStatus;
|
1427
1786
|
statusEl.className = 'history-item-status';
|
1428
|
-
statusEl.classList.add(
|
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
|
});
|