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