local-deep-research 0.1.26__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_deep_research/__init__.py +23 -22
- local_deep_research/__main__.py +16 -0
- local_deep_research/advanced_search_system/__init__.py +7 -0
- local_deep_research/advanced_search_system/filters/__init__.py +8 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
- local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
- local_deep_research/advanced_search_system/findings/repository.py +452 -0
- local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
- local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
- local_deep_research/advanced_search_system/questions/__init__.py +1 -0
- local_deep_research/advanced_search_system/questions/base_question.py +64 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
- local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
- local_deep_research/advanced_search_system/tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
- local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
- local_deep_research/api/__init__.py +5 -5
- local_deep_research/api/research_functions.py +154 -160
- local_deep_research/app.py +8 -0
- local_deep_research/citation_handler.py +25 -16
- local_deep_research/{config.py → config/config_files.py} +102 -110
- local_deep_research/config/llm_config.py +472 -0
- local_deep_research/config/search_config.py +77 -0
- local_deep_research/defaults/__init__.py +10 -5
- local_deep_research/defaults/main.toml +2 -2
- local_deep_research/defaults/search_engines.toml +60 -34
- local_deep_research/main.py +121 -19
- local_deep_research/migrate_db.py +147 -0
- local_deep_research/report_generator.py +87 -45
- local_deep_research/search_system.py +153 -283
- local_deep_research/setup_data_dir.py +35 -0
- local_deep_research/test_migration.py +178 -0
- local_deep_research/utilities/__init__.py +0 -0
- local_deep_research/utilities/db_utils.py +49 -0
- local_deep_research/{utilties → utilities}/enums.py +2 -2
- local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
- local_deep_research/utilities/search_utilities.py +242 -0
- local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
- local_deep_research/web/__init__.py +0 -1
- local_deep_research/web/app.py +86 -1709
- local_deep_research/web/app_factory.py +289 -0
- local_deep_research/web/database/README.md +70 -0
- local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
- local_deep_research/web/database/migrations.py +447 -0
- local_deep_research/web/database/models.py +117 -0
- local_deep_research/web/database/schema_upgrade.py +107 -0
- local_deep_research/web/models/database.py +294 -0
- local_deep_research/web/models/settings.py +94 -0
- local_deep_research/web/routes/api_routes.py +559 -0
- local_deep_research/web/routes/history_routes.py +354 -0
- local_deep_research/web/routes/research_routes.py +715 -0
- local_deep_research/web/routes/settings_routes.py +1583 -0
- local_deep_research/web/services/research_service.py +947 -0
- local_deep_research/web/services/resource_service.py +149 -0
- local_deep_research/web/services/settings_manager.py +669 -0
- local_deep_research/web/services/settings_service.py +187 -0
- local_deep_research/web/services/socket_service.py +210 -0
- local_deep_research/web/static/css/custom_dropdown.css +277 -0
- local_deep_research/web/static/css/settings.css +1223 -0
- local_deep_research/web/static/css/styles.css +525 -48
- local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
- local_deep_research/web/static/js/components/detail.js +348 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
- local_deep_research/web/static/js/components/fallback/ui.js +215 -0
- local_deep_research/web/static/js/components/history.js +487 -0
- local_deep_research/web/static/js/components/logpanel.js +949 -0
- local_deep_research/web/static/js/components/progress.js +1107 -0
- local_deep_research/web/static/js/components/research.js +1865 -0
- local_deep_research/web/static/js/components/results.js +766 -0
- local_deep_research/web/static/js/components/settings.js +3981 -0
- local_deep_research/web/static/js/components/settings_sync.js +106 -0
- local_deep_research/web/static/js/main.js +226 -0
- local_deep_research/web/static/js/services/api.js +253 -0
- local_deep_research/web/static/js/services/audio.js +31 -0
- local_deep_research/web/static/js/services/formatting.js +119 -0
- local_deep_research/web/static/js/services/pdf.js +622 -0
- local_deep_research/web/static/js/services/socket.js +882 -0
- local_deep_research/web/static/js/services/ui.js +546 -0
- local_deep_research/web/templates/base.html +72 -0
- local_deep_research/web/templates/components/custom_dropdown.html +47 -0
- local_deep_research/web/templates/components/log_panel.html +32 -0
- local_deep_research/web/templates/components/mobile_nav.html +22 -0
- local_deep_research/web/templates/components/settings_form.html +299 -0
- local_deep_research/web/templates/components/sidebar.html +21 -0
- local_deep_research/web/templates/pages/details.html +73 -0
- local_deep_research/web/templates/pages/history.html +51 -0
- local_deep_research/web/templates/pages/progress.html +57 -0
- local_deep_research/web/templates/pages/research.html +139 -0
- local_deep_research/web/templates/pages/results.html +59 -0
- local_deep_research/web/templates/settings_dashboard.html +78 -192
- local_deep_research/web/utils/__init__.py +0 -0
- local_deep_research/web/utils/formatters.py +76 -0
- local_deep_research/web_search_engines/engines/full_search.py +18 -16
- local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
- local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
- local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
- local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
- local_deep_research/web_search_engines/search_engine_base.py +174 -99
- local_deep_research/web_search_engines/search_engine_factory.py +192 -102
- local_deep_research/web_search_engines/search_engines_config.py +22 -15
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
- local_deep_research-0.2.2.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
- local_deep_research/defaults/llm_config.py +0 -338
- local_deep_research/utilties/search_utilities.py +0 -114
- local_deep_research/web/static/js/app.js +0 -3763
- local_deep_research/web/templates/api_keys_config.html +0 -82
- local_deep_research/web/templates/collections_config.html +0 -90
- local_deep_research/web/templates/index.html +0 -348
- local_deep_research/web/templates/llm_config.html +0 -120
- local_deep_research/web/templates/main_config.html +0 -89
- local_deep_research/web/templates/search_engines_config.html +0 -154
- local_deep_research/web/templates/settings.html +0 -519
- local_deep_research-0.1.26.dist-info/RECORD +0 -61
- local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
- /local_deep_research/{utilties → config}/__init__.py +0 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,882 @@
|
|
1
|
+
/**
|
2
|
+
* Socket Service
|
3
|
+
* Manages WebSocket communication using Socket.IO
|
4
|
+
*/
|
5
|
+
|
6
|
+
window.socket = (function() {
|
7
|
+
let socket = null;
|
8
|
+
let researchEventHandlers = {};
|
9
|
+
let reconnectCallback = null;
|
10
|
+
let connectionAttempts = 0;
|
11
|
+
const MAX_CONNECTION_ATTEMPTS = 3;
|
12
|
+
|
13
|
+
// Keep track of the research we're currently subscribed to
|
14
|
+
let currentResearchId = null;
|
15
|
+
|
16
|
+
// Track if we're using polling fallback
|
17
|
+
let usingPolling = false;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Initialize the Socket.IO connection
|
21
|
+
*/
|
22
|
+
function initializeSocket() {
|
23
|
+
if (socket) {
|
24
|
+
// Already initialized
|
25
|
+
return socket;
|
26
|
+
}
|
27
|
+
|
28
|
+
// Get the base URL from the current page
|
29
|
+
const baseUrl = window.location.protocol + '//' + window.location.host;
|
30
|
+
|
31
|
+
// Create a new socket instance
|
32
|
+
try {
|
33
|
+
// Use polling only to avoid WebSocket issues
|
34
|
+
socket = io(baseUrl, {
|
35
|
+
path: '/research/socket.io',
|
36
|
+
reconnection: true,
|
37
|
+
reconnectionDelay: 1000,
|
38
|
+
reconnectionAttempts: 5,
|
39
|
+
transports: ['polling'] // Use only polling to avoid WebSocket issues
|
40
|
+
});
|
41
|
+
|
42
|
+
setupSocketEvents();
|
43
|
+
console.log('Socket.IO initialized with polling only strategy');
|
44
|
+
} catch (error) {
|
45
|
+
console.error('Error initializing Socket.IO:', error);
|
46
|
+
// Set a flag that we're not connected - will use polling for updates
|
47
|
+
usingPolling = true;
|
48
|
+
}
|
49
|
+
|
50
|
+
return socket;
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Set up the socket event handlers
|
55
|
+
*/
|
56
|
+
function setupSocketEvents() {
|
57
|
+
socket.on('connect', () => {
|
58
|
+
console.log('Socket connected');
|
59
|
+
connectionAttempts = 0;
|
60
|
+
usingPolling = false;
|
61
|
+
|
62
|
+
// Re-subscribe to current research if any
|
63
|
+
if (currentResearchId) {
|
64
|
+
subscribeToResearch(currentResearchId);
|
65
|
+
}
|
66
|
+
|
67
|
+
// Call reconnect callback if exists
|
68
|
+
if (reconnectCallback) {
|
69
|
+
reconnectCallback();
|
70
|
+
}
|
71
|
+
});
|
72
|
+
|
73
|
+
socket.on('connect_error', (error) => {
|
74
|
+
console.warn('Socket connection error:', error);
|
75
|
+
connectionAttempts++;
|
76
|
+
|
77
|
+
if (connectionAttempts >= MAX_CONNECTION_ATTEMPTS) {
|
78
|
+
console.warn(`Failed to connect after ${MAX_CONNECTION_ATTEMPTS} attempts, falling back to polling`);
|
79
|
+
usingPolling = true;
|
80
|
+
|
81
|
+
// If we can't establish a socket connection, use polling for any active research
|
82
|
+
if (currentResearchId && typeof window.pollResearchStatus === 'function') {
|
83
|
+
window.pollResearchStatus(currentResearchId);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
});
|
87
|
+
|
88
|
+
socket.on('disconnect', (reason) => {
|
89
|
+
console.log('Socket disconnected:', reason);
|
90
|
+
|
91
|
+
// Fall back to polling on disconnect
|
92
|
+
if (currentResearchId) {
|
93
|
+
fallbackToPolling(currentResearchId);
|
94
|
+
}
|
95
|
+
});
|
96
|
+
|
97
|
+
socket.on('reconnect', (attemptNumber) => {
|
98
|
+
console.log('Socket reconnected after', attemptNumber, 'attempts');
|
99
|
+
connectionAttempts = 0;
|
100
|
+
});
|
101
|
+
|
102
|
+
socket.on('reconnect_attempt', (attemptNumber) => {
|
103
|
+
console.log('Socket reconnection attempt:', attemptNumber);
|
104
|
+
});
|
105
|
+
|
106
|
+
socket.on('error', (error) => {
|
107
|
+
console.error('Socket error:', error);
|
108
|
+
|
109
|
+
// Fall back to polling on any error
|
110
|
+
if (currentResearchId) {
|
111
|
+
fallbackToPolling(currentResearchId);
|
112
|
+
}
|
113
|
+
});
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Subscribe to research events
|
118
|
+
* @param {string} researchId - The research ID to subscribe to
|
119
|
+
* @param {function} callback - Optional callback for progress updates
|
120
|
+
*/
|
121
|
+
function subscribeToResearch(researchId, callback) {
|
122
|
+
if (!socket && !usingPolling) {
|
123
|
+
console.warn('Socket not initialized, initializing now');
|
124
|
+
initializeSocket();
|
125
|
+
}
|
126
|
+
|
127
|
+
if (!researchId) {
|
128
|
+
console.error('No research ID provided');
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
|
132
|
+
console.log('Subscribing to research:', researchId);
|
133
|
+
|
134
|
+
// Remember the current research ID
|
135
|
+
currentResearchId = researchId;
|
136
|
+
|
137
|
+
// Add the callback if provided
|
138
|
+
if (callback && typeof callback === 'function') {
|
139
|
+
addResearchEventHandler(researchId, callback);
|
140
|
+
}
|
141
|
+
|
142
|
+
// If we have a socket connection, join the research room
|
143
|
+
if (socket && socket.connected) {
|
144
|
+
try {
|
145
|
+
socket.emit('join', { research_id: researchId });
|
146
|
+
|
147
|
+
// Setup direct event handler for progress updates
|
148
|
+
socket.on(`progress_${researchId}`, (data) => {
|
149
|
+
handleProgressUpdate(researchId, data);
|
150
|
+
});
|
151
|
+
} catch (error) {
|
152
|
+
console.error('Error subscribing to research:', error);
|
153
|
+
fallbackToPolling(researchId);
|
154
|
+
}
|
155
|
+
} else {
|
156
|
+
// If no socket connection, use polling
|
157
|
+
fallbackToPolling(researchId);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
/**
|
162
|
+
* Handle progress updates from research
|
163
|
+
* @param {string} researchId - The research ID this update is for
|
164
|
+
* @param {Object} data - The progress data
|
165
|
+
*/
|
166
|
+
function handleProgressUpdate(researchId, data) {
|
167
|
+
console.log('Progress update for research', researchId, ':', data);
|
168
|
+
|
169
|
+
// Special handling for synthesis errors to make them more visible to users
|
170
|
+
if (data.metadata && (data.metadata.phase === 'synthesis_error' || data.metadata.error_type)) {
|
171
|
+
const errorType = data.metadata.error_type || 'unknown';
|
172
|
+
let errorMessage = 'Error during research synthesis';
|
173
|
+
let detailedMessage = '';
|
174
|
+
|
175
|
+
// Format user-friendly error messages based on error type
|
176
|
+
switch(errorType) {
|
177
|
+
case 'timeout':
|
178
|
+
errorMessage = 'LLM Timeout Error';
|
179
|
+
detailedMessage = 'The AI model took too long to respond. This may be due to server load or the complexity of your query.';
|
180
|
+
break;
|
181
|
+
case 'token_limit':
|
182
|
+
errorMessage = 'Token Limit Exceeded';
|
183
|
+
detailedMessage = 'Your research query generated too much data for the AI model to process. Try a more specific query.';
|
184
|
+
break;
|
185
|
+
case 'connection':
|
186
|
+
errorMessage = 'LLM Connection Error';
|
187
|
+
detailedMessage = 'Could not connect to the AI service. Please check that your LLM service is running.';
|
188
|
+
break;
|
189
|
+
case 'rate_limit':
|
190
|
+
errorMessage = 'API Rate Limit Reached';
|
191
|
+
detailedMessage = 'The AI service API rate limit was reached. Please wait a few minutes and try again.';
|
192
|
+
break;
|
193
|
+
case 'llm_error':
|
194
|
+
default:
|
195
|
+
errorMessage = 'LLM Synthesis Error';
|
196
|
+
detailedMessage = 'The AI model encountered an error during final answer synthesis. A fallback response will be provided.';
|
197
|
+
}
|
198
|
+
|
199
|
+
// Add prominent error notification
|
200
|
+
try {
|
201
|
+
// If we have a UI notification function available
|
202
|
+
if (typeof window.showNotification === 'function') {
|
203
|
+
window.showNotification(errorMessage, detailedMessage, 'error', 10000); // Show for 10 seconds
|
204
|
+
}
|
205
|
+
|
206
|
+
// Log to console
|
207
|
+
console.error(`Research error (${errorType}): ${errorMessage} - ${detailedMessage}`);
|
208
|
+
|
209
|
+
// Add to log panel with the error status
|
210
|
+
if (typeof window.addConsoleLog === 'function') {
|
211
|
+
window.addConsoleLog(`${errorMessage}: ${detailedMessage}`, 'error', {
|
212
|
+
phase: 'synthesis_error',
|
213
|
+
error_type: errorType
|
214
|
+
});
|
215
|
+
|
216
|
+
// Add explanation about fallback mode as a separate log entry
|
217
|
+
window.addConsoleLog(
|
218
|
+
'Switching to fallback mode. Research will continue with available data.',
|
219
|
+
'milestone',
|
220
|
+
{phase: 'synthesis_fallback'}
|
221
|
+
);
|
222
|
+
}
|
223
|
+
} catch (notificationError) {
|
224
|
+
console.error('Error showing notification:', notificationError);
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
// Continue with normal progress update handling
|
229
|
+
// Call all registered event handlers for this research
|
230
|
+
if (researchEventHandlers[researchId]) {
|
231
|
+
researchEventHandlers[researchId].forEach(handler => {
|
232
|
+
try {
|
233
|
+
handler(data);
|
234
|
+
} catch (error) {
|
235
|
+
console.error('Error in progress update handler:', error);
|
236
|
+
}
|
237
|
+
});
|
238
|
+
}
|
239
|
+
|
240
|
+
// Initialize message tracking if not exists
|
241
|
+
window._processedSocketMessages = window._processedSocketMessages || new Map();
|
242
|
+
|
243
|
+
// Process logs from progress_log if available
|
244
|
+
if (data.progress_log && typeof data.progress_log === 'string') {
|
245
|
+
try {
|
246
|
+
const progressLogs = JSON.parse(data.progress_log);
|
247
|
+
if (Array.isArray(progressLogs) && progressLogs.length > 0) {
|
248
|
+
console.log(`Socket received ${progressLogs.length} logs in progress_log`);
|
249
|
+
|
250
|
+
// Process each log entry
|
251
|
+
progressLogs.forEach(logItem => {
|
252
|
+
// Skip if no message or time
|
253
|
+
if (!logItem.message || !logItem.time) return;
|
254
|
+
|
255
|
+
// Generate a unique key for this message
|
256
|
+
const messageKey = `${logItem.time}-${logItem.message}`;
|
257
|
+
|
258
|
+
// Skip if we've seen this exact message before
|
259
|
+
if (window._processedSocketMessages.has(messageKey)) {
|
260
|
+
console.log('Skipping duplicate socket message:', logItem.message);
|
261
|
+
return;
|
262
|
+
}
|
263
|
+
|
264
|
+
// Record that we've processed this message
|
265
|
+
window._processedSocketMessages.set(messageKey, Date.now());
|
266
|
+
|
267
|
+
// Determine log type based on metadata
|
268
|
+
let logType = 'info';
|
269
|
+
if (logItem.metadata) {
|
270
|
+
if (logItem.metadata.phase === 'iteration_complete' ||
|
271
|
+
logItem.metadata.phase === 'report_complete' ||
|
272
|
+
logItem.metadata.phase === 'complete' ||
|
273
|
+
logItem.metadata.phase === 'search_complete' ||
|
274
|
+
logItem.metadata.is_milestone === true ||
|
275
|
+
logItem.metadata.type === 'milestone') {
|
276
|
+
logType = 'milestone';
|
277
|
+
} else if (logItem.metadata.phase === 'error' ||
|
278
|
+
logItem.metadata.type === 'error') {
|
279
|
+
logType = 'error';
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
// Also check for keywords in the message for better milestone detection
|
284
|
+
if (logType !== 'milestone' && logItem.message) {
|
285
|
+
const msg = logItem.message.toLowerCase();
|
286
|
+
if (msg.includes('complete') ||
|
287
|
+
msg.includes('finished') ||
|
288
|
+
msg.includes('starting phase') ||
|
289
|
+
msg.includes('generated report')) {
|
290
|
+
logType = 'milestone';
|
291
|
+
} else if (msg.includes('error') || msg.includes('failed')) {
|
292
|
+
logType = 'error';
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
// Send to log panel
|
297
|
+
if (typeof window.addConsoleLog === 'function') {
|
298
|
+
// Use the main console log function if available
|
299
|
+
window.addConsoleLog(logItem.message, logType, logItem.metadata);
|
300
|
+
} else if (typeof window._socketAddLogEntry === 'function') {
|
301
|
+
// Fallback to the direct connector if needed
|
302
|
+
const logEntry = {
|
303
|
+
time: logItem.time,
|
304
|
+
message: logItem.message,
|
305
|
+
type: logType,
|
306
|
+
metadata: logItem.metadata || {}
|
307
|
+
};
|
308
|
+
window._socketAddLogEntry(logEntry);
|
309
|
+
} else {
|
310
|
+
console.warn('No log handler function available for log:', logItem);
|
311
|
+
}
|
312
|
+
});
|
313
|
+
|
314
|
+
// Clean up old entries from message tracking (keep only last 5 minutes)
|
315
|
+
const now = Date.now();
|
316
|
+
for (const [key, timestamp] of window._processedSocketMessages.entries()) {
|
317
|
+
if (now - timestamp > 5 * 60 * 1000) { // 5 minutes
|
318
|
+
window._processedSocketMessages.delete(key);
|
319
|
+
}
|
320
|
+
}
|
321
|
+
}
|
322
|
+
} catch (error) {
|
323
|
+
console.error('Error processing progress_log:', error);
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
// If the event contains log data, add it to the console
|
328
|
+
if (data.log_entry) {
|
329
|
+
console.log('Adding log entry from socket event:', data.log_entry);
|
330
|
+
|
331
|
+
// Make sure global tracking is initialized
|
332
|
+
window._processedSocketMessages = window._processedSocketMessages || new Map();
|
333
|
+
|
334
|
+
// Generate a message key
|
335
|
+
const messageKey = `${data.log_entry.time || new Date().toISOString()}-${data.log_entry.message}`;
|
336
|
+
|
337
|
+
// Skip if we've seen this message before
|
338
|
+
if (window._processedSocketMessages.has(messageKey)) {
|
339
|
+
console.log('Skipping duplicate individual log entry:', data.log_entry.message);
|
340
|
+
return;
|
341
|
+
}
|
342
|
+
|
343
|
+
// Record that we've processed this message
|
344
|
+
window._processedSocketMessages.set(messageKey, Date.now());
|
345
|
+
|
346
|
+
if (typeof window.addConsoleLog === 'function') {
|
347
|
+
window.addConsoleLog(
|
348
|
+
data.log_entry.message,
|
349
|
+
data.log_entry.type ||
|
350
|
+
(data.log_entry.metadata && data.log_entry.metadata.type) ||
|
351
|
+
'info',
|
352
|
+
data.log_entry.metadata
|
353
|
+
);
|
354
|
+
} else if (typeof window._socketAddLogEntry === 'function') {
|
355
|
+
window._socketAddLogEntry(data.log_entry);
|
356
|
+
} else {
|
357
|
+
console.warn('No log handler function available for direct log entry');
|
358
|
+
}
|
359
|
+
} else if (data.message && typeof window.addConsoleLog === 'function') {
|
360
|
+
// Use the message field if no specific log entry
|
361
|
+
console.log('Adding message from socket event:', data.message);
|
362
|
+
|
363
|
+
// Skip duplicate general messages too
|
364
|
+
const messageKey = `${new Date().toISOString()}-${data.message}`;
|
365
|
+
if (window._processedSocketMessages.has(messageKey)) {
|
366
|
+
console.log('Skipping duplicate message:', data.message);
|
367
|
+
return;
|
368
|
+
}
|
369
|
+
|
370
|
+
// Record this message
|
371
|
+
window._processedSocketMessages.set(messageKey, Date.now());
|
372
|
+
|
373
|
+
window.addConsoleLog(data.message, determineLogLevel(data.status));
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
/**
|
378
|
+
* Determine log level based on status
|
379
|
+
* @param {string} status - The research status
|
380
|
+
* @returns {string} Log level (info, milestone, error, etc)
|
381
|
+
*/
|
382
|
+
function determineLogLevel(status) {
|
383
|
+
if (!status) return 'info';
|
384
|
+
|
385
|
+
if (status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'error') {
|
386
|
+
return 'milestone';
|
387
|
+
}
|
388
|
+
|
389
|
+
if (status === 'error' || status.includes('error')) {
|
390
|
+
return 'error';
|
391
|
+
}
|
392
|
+
|
393
|
+
return 'info';
|
394
|
+
}
|
395
|
+
|
396
|
+
/**
|
397
|
+
* Add a log entry to the console log container
|
398
|
+
* @param {Object} logEntry - The log entry data
|
399
|
+
*/
|
400
|
+
function addLogEntry(logEntry) {
|
401
|
+
// If the logpanel's log function is available, use it
|
402
|
+
if (typeof window._socketAddLogEntry === 'function') {
|
403
|
+
console.log('Using logpanel\'s _socketAddLogEntry for log:', logEntry.message);
|
404
|
+
window._socketAddLogEntry(logEntry);
|
405
|
+
return;
|
406
|
+
}
|
407
|
+
|
408
|
+
// If window.addConsoleLog is available, use it
|
409
|
+
if (typeof window.addConsoleLog === 'function') {
|
410
|
+
console.log('Using window.addConsoleLog for log:', logEntry.message);
|
411
|
+
let logLevel = 'info';
|
412
|
+
if (logEntry.type) {
|
413
|
+
logLevel = logEntry.type;
|
414
|
+
} else if (logEntry.metadata && logEntry.metadata.type) {
|
415
|
+
logLevel = logEntry.metadata.type;
|
416
|
+
}
|
417
|
+
window.addConsoleLog(logEntry.message, logLevel, logEntry.metadata);
|
418
|
+
return;
|
419
|
+
}
|
420
|
+
|
421
|
+
// Fallback implementation if none of the above is available
|
422
|
+
console.log('Using socket.js fallback log implementation for:', logEntry.message);
|
423
|
+
const consoleLogContainer = document.getElementById('console-log-container');
|
424
|
+
if (!consoleLogContainer) return;
|
425
|
+
|
426
|
+
// Clear empty message if present
|
427
|
+
const emptyMessage = consoleLogContainer.querySelector('.empty-log-message');
|
428
|
+
if (emptyMessage) {
|
429
|
+
emptyMessage.remove();
|
430
|
+
}
|
431
|
+
|
432
|
+
// Get the log template
|
433
|
+
const template = document.getElementById('console-log-entry-template');
|
434
|
+
if (!template) {
|
435
|
+
console.error('Console log entry template not found');
|
436
|
+
return;
|
437
|
+
}
|
438
|
+
|
439
|
+
// Create a new log entry from the template
|
440
|
+
const entry = document.importNode(template.content, true);
|
441
|
+
|
442
|
+
// Determine the log level
|
443
|
+
let logLevel = 'info';
|
444
|
+
if (logEntry.metadata && logEntry.metadata.type) {
|
445
|
+
logLevel = logEntry.metadata.type;
|
446
|
+
} else if (logEntry.metadata && logEntry.metadata.phase) {
|
447
|
+
if (logEntry.metadata.phase === 'complete' ||
|
448
|
+
logEntry.metadata.phase === 'iteration_complete' ||
|
449
|
+
logEntry.metadata.phase === 'report_complete') {
|
450
|
+
logLevel = 'milestone';
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
// Format the timestamp
|
455
|
+
const timestamp = new Date(logEntry.time);
|
456
|
+
const timeStr = timestamp.toLocaleTimeString();
|
457
|
+
|
458
|
+
// Set content
|
459
|
+
entry.querySelector('.log-timestamp').textContent = timeStr;
|
460
|
+
entry.querySelector('.log-badge').textContent = logLevel.charAt(0).toUpperCase() + logLevel.slice(1);
|
461
|
+
entry.querySelector('.log-badge').className = `log-badge ${logLevel}`;
|
462
|
+
entry.querySelector('.log-message').textContent = logEntry.message;
|
463
|
+
|
464
|
+
// Add to container (at the beginning for newest first)
|
465
|
+
consoleLogContainer.insertBefore(entry, consoleLogContainer.firstChild);
|
466
|
+
|
467
|
+
// Update log count
|
468
|
+
const logIndicator = document.getElementById('log-indicator');
|
469
|
+
if (logIndicator) {
|
470
|
+
const currentCount = parseInt(logIndicator.textContent) || 0;
|
471
|
+
logIndicator.textContent = currentCount + 1;
|
472
|
+
}
|
473
|
+
}
|
474
|
+
|
475
|
+
/**
|
476
|
+
* Fall back to polling for research updates
|
477
|
+
* @param {string} researchId - The research ID
|
478
|
+
*/
|
479
|
+
function fallbackToPolling(researchId) {
|
480
|
+
console.log('Falling back to polling for research', researchId);
|
481
|
+
usingPolling = true;
|
482
|
+
|
483
|
+
// Start polling if the global polling function exists
|
484
|
+
if (typeof window.pollResearchStatus === 'function') {
|
485
|
+
window.pollResearchStatus(researchId);
|
486
|
+
} else {
|
487
|
+
// Define a simple polling function if it doesn't exist
|
488
|
+
window.pollResearchStatus = function(id) {
|
489
|
+
if (!window.api || !window.api.getResearchStatus) {
|
490
|
+
console.error('API service not available for polling');
|
491
|
+
return;
|
492
|
+
}
|
493
|
+
|
494
|
+
const pollInterval = setInterval(async () => {
|
495
|
+
try {
|
496
|
+
const data = await window.api.getResearchStatus(id);
|
497
|
+
if (data) {
|
498
|
+
handleProgressUpdate(id, data);
|
499
|
+
|
500
|
+
// Stop polling if the research is complete
|
501
|
+
if (data.status === 'completed' || data.status === 'failed' || data.status === 'cancelled') {
|
502
|
+
clearInterval(pollInterval);
|
503
|
+
}
|
504
|
+
}
|
505
|
+
} catch (error) {
|
506
|
+
console.error('Error polling research status:', error);
|
507
|
+
}
|
508
|
+
}, 3000);
|
509
|
+
|
510
|
+
// Store the interval ID for later cleanup
|
511
|
+
window.pollIntervals = window.pollIntervals || {};
|
512
|
+
window.pollIntervals[id] = pollInterval;
|
513
|
+
};
|
514
|
+
|
515
|
+
// Start polling for this research
|
516
|
+
window.pollResearchStatus(researchId);
|
517
|
+
}
|
518
|
+
}
|
519
|
+
|
520
|
+
/**
|
521
|
+
* Unsubscribe from research events
|
522
|
+
* @param {string} researchId - The research ID to unsubscribe from
|
523
|
+
*/
|
524
|
+
function unsubscribeFromResearch(researchId) {
|
525
|
+
if (!researchId) return;
|
526
|
+
|
527
|
+
console.log('Unsubscribing from research:', researchId);
|
528
|
+
|
529
|
+
// Clear any polling intervals
|
530
|
+
if (window.pollIntervals && window.pollIntervals[researchId]) {
|
531
|
+
clearInterval(window.pollIntervals[researchId]);
|
532
|
+
delete window.pollIntervals[researchId];
|
533
|
+
}
|
534
|
+
|
535
|
+
// If we have a socket connection, leave the research room
|
536
|
+
if (socket && socket.connected) {
|
537
|
+
try {
|
538
|
+
// Leave the research room
|
539
|
+
socket.emit('leave', { research_id: researchId });
|
540
|
+
|
541
|
+
// Remove the event handler
|
542
|
+
socket.off(`progress_${researchId}`);
|
543
|
+
} catch (error) {
|
544
|
+
console.error('Error unsubscribing from research:', error);
|
545
|
+
}
|
546
|
+
}
|
547
|
+
|
548
|
+
// Clear handlers
|
549
|
+
if (researchId === currentResearchId) {
|
550
|
+
currentResearchId = null;
|
551
|
+
}
|
552
|
+
|
553
|
+
// Clear event handlers
|
554
|
+
if (researchEventHandlers[researchId]) {
|
555
|
+
delete researchEventHandlers[researchId];
|
556
|
+
}
|
557
|
+
}
|
558
|
+
|
559
|
+
/**
|
560
|
+
* Add a research event handler
|
561
|
+
* @param {string} researchId - The research ID to handle events for
|
562
|
+
* @param {function} callback - The function to call when an event occurs
|
563
|
+
*/
|
564
|
+
function addResearchEventHandler(researchId, callback) {
|
565
|
+
if (!researchId || typeof callback !== 'function') {
|
566
|
+
console.error('Invalid research event handler');
|
567
|
+
return;
|
568
|
+
}
|
569
|
+
|
570
|
+
// Initialize the handlers array if needed
|
571
|
+
if (!researchEventHandlers[researchId]) {
|
572
|
+
researchEventHandlers[researchId] = [];
|
573
|
+
}
|
574
|
+
|
575
|
+
// Add the handler if it's not already in the array
|
576
|
+
if (!researchEventHandlers[researchId].includes(callback)) {
|
577
|
+
researchEventHandlers[researchId].push(callback);
|
578
|
+
}
|
579
|
+
}
|
580
|
+
|
581
|
+
/**
|
582
|
+
* Remove a research event handler
|
583
|
+
* @param {string} researchId - The research ID to remove handler for
|
584
|
+
* @param {function} callback - The function to remove
|
585
|
+
*/
|
586
|
+
function removeResearchEventHandler(researchId, callback) {
|
587
|
+
if (!researchId || !researchEventHandlers[researchId]) return;
|
588
|
+
|
589
|
+
if (callback) {
|
590
|
+
// Find the handler index
|
591
|
+
const index = researchEventHandlers[researchId].indexOf(callback);
|
592
|
+
|
593
|
+
// Remove if found
|
594
|
+
if (index !== -1) {
|
595
|
+
researchEventHandlers[researchId].splice(index, 1);
|
596
|
+
}
|
597
|
+
} else {
|
598
|
+
// Remove all handlers for this research
|
599
|
+
delete researchEventHandlers[researchId];
|
600
|
+
}
|
601
|
+
}
|
602
|
+
|
603
|
+
/**
|
604
|
+
* Set a callback for socket reconnection
|
605
|
+
* @param {function} callback - The function to call on reconnection
|
606
|
+
*/
|
607
|
+
function setReconnectCallback(callback) {
|
608
|
+
reconnectCallback = callback;
|
609
|
+
}
|
610
|
+
|
611
|
+
/**
|
612
|
+
* Disconnect the socket
|
613
|
+
*/
|
614
|
+
function disconnectSocket() {
|
615
|
+
// Clear any polling intervals
|
616
|
+
if (window.pollIntervals) {
|
617
|
+
Object.keys(window.pollIntervals).forEach(id => {
|
618
|
+
clearInterval(window.pollIntervals[id]);
|
619
|
+
});
|
620
|
+
window.pollIntervals = {};
|
621
|
+
}
|
622
|
+
|
623
|
+
if (socket) {
|
624
|
+
try {
|
625
|
+
socket.disconnect();
|
626
|
+
} catch (error) {
|
627
|
+
console.error('Error disconnecting socket:', error);
|
628
|
+
}
|
629
|
+
socket = null;
|
630
|
+
}
|
631
|
+
|
632
|
+
researchEventHandlers = {};
|
633
|
+
reconnectCallback = null;
|
634
|
+
currentResearchId = null;
|
635
|
+
connectionAttempts = 0;
|
636
|
+
usingPolling = false;
|
637
|
+
}
|
638
|
+
|
639
|
+
/**
|
640
|
+
* Filter logs by type
|
641
|
+
* @param {string} type - The log type to filter by ('all', 'info', 'error', 'milestone')
|
642
|
+
*/
|
643
|
+
function filterLogsByType(type) {
|
644
|
+
// If the logpanel's filter function is available, use it
|
645
|
+
if (typeof window.filterLogsByType === 'function') {
|
646
|
+
console.log('Using logpanel\'s filterLogsByType for filter:', type);
|
647
|
+
window.filterLogsByType(type);
|
648
|
+
return;
|
649
|
+
}
|
650
|
+
|
651
|
+
console.log('Using socket.js filtering implementation for:', type);
|
652
|
+
|
653
|
+
// Update button UI
|
654
|
+
const buttons = document.querySelectorAll('.filter-buttons .small-btn');
|
655
|
+
buttons.forEach(button => {
|
656
|
+
button.classList.remove('selected');
|
657
|
+
if (button.textContent.toLowerCase() === type ||
|
658
|
+
(type === 'all' && button.textContent.toLowerCase() === 'all')) {
|
659
|
+
button.classList.add('selected');
|
660
|
+
}
|
661
|
+
});
|
662
|
+
|
663
|
+
// Get all log entries
|
664
|
+
const logEntries = document.querySelectorAll('.console-log-entry');
|
665
|
+
|
666
|
+
logEntries.forEach(entry => {
|
667
|
+
// Use dataset for type if available (new way)
|
668
|
+
if (entry.dataset && entry.dataset.logType) {
|
669
|
+
if (type === 'all' || entry.dataset.logType === type) {
|
670
|
+
entry.style.display = '';
|
671
|
+
} else {
|
672
|
+
entry.style.display = 'none';
|
673
|
+
}
|
674
|
+
return;
|
675
|
+
}
|
676
|
+
|
677
|
+
// Fallback to badge content (old way)
|
678
|
+
const badge = entry.querySelector('.log-badge');
|
679
|
+
const logType = badge ? badge.textContent.toLowerCase() : 'info';
|
680
|
+
|
681
|
+
if (type === 'all' || logType === type) {
|
682
|
+
entry.style.display = '';
|
683
|
+
} else {
|
684
|
+
entry.style.display = 'none';
|
685
|
+
}
|
686
|
+
});
|
687
|
+
|
688
|
+
// Update empty state message if needed
|
689
|
+
const logContainer = document.getElementById('console-log-container');
|
690
|
+
if (logContainer) {
|
691
|
+
const visibleEntries = logContainer.querySelectorAll('.console-log-entry[style="display: ;"], .console-log-entry:not([style])');
|
692
|
+
const emptyMessage = logContainer.querySelector('.empty-log-message');
|
693
|
+
|
694
|
+
if (visibleEntries.length === 0 && !emptyMessage) {
|
695
|
+
logContainer.innerHTML = `<div class="empty-log-message">No ${type === 'all' ? '' : type + ' '}logs available.</div>` + logContainer.innerHTML;
|
696
|
+
} else if (visibleEntries.length > 0 && emptyMessage) {
|
697
|
+
emptyMessage.remove();
|
698
|
+
}
|
699
|
+
}
|
700
|
+
}
|
701
|
+
|
702
|
+
/**
|
703
|
+
* Check if socket is connected
|
704
|
+
* @returns {boolean} True if connected
|
705
|
+
*/
|
706
|
+
function isConnected() {
|
707
|
+
return socket && socket.connected;
|
708
|
+
}
|
709
|
+
|
710
|
+
/**
|
711
|
+
* Check if we're using polling fallback
|
712
|
+
* @returns {boolean} True if using polling
|
713
|
+
*/
|
714
|
+
function isUsingPolling() {
|
715
|
+
return usingPolling;
|
716
|
+
}
|
717
|
+
|
718
|
+
// Initialize socket on load with a small delay to ensure document is ready
|
719
|
+
setTimeout(initializeSocket, 100);
|
720
|
+
|
721
|
+
// Expose functions globally
|
722
|
+
window.filterLogsByType = filterLogsByType;
|
723
|
+
window._socketAddLogEntry = addLogEntry; // Expose the addLogEntry function
|
724
|
+
|
725
|
+
// Public API
|
726
|
+
return {
|
727
|
+
init: initializeSocket,
|
728
|
+
subscribeToResearch,
|
729
|
+
unsubscribeFromResearch,
|
730
|
+
onReconnect: setReconnectCallback,
|
731
|
+
disconnect: disconnectSocket,
|
732
|
+
getSocketInstance: () => socket,
|
733
|
+
isConnected,
|
734
|
+
isUsingPolling
|
735
|
+
};
|
736
|
+
})();
|
737
|
+
|
738
|
+
/**
|
739
|
+
* Register global functions for filtering logs - ONLY if they don't already exist
|
740
|
+
*/
|
741
|
+
if (!window.filterLogsByType) {
|
742
|
+
window.filterLogsByType = function(type) {
|
743
|
+
console.log('Filter logs by type (socket.js fallback):', type);
|
744
|
+
// If the socket object exists and has the function
|
745
|
+
if (window.socket && typeof window.socket.filterLogsByType === 'function') {
|
746
|
+
window.socket.filterLogsByType(type);
|
747
|
+
return;
|
748
|
+
}
|
749
|
+
|
750
|
+
// Otherwise do basic filtering
|
751
|
+
const logEntries = document.querySelectorAll('.console-log-entry');
|
752
|
+
logEntries.forEach(entry => {
|
753
|
+
// Try using dataset first (new way)
|
754
|
+
if (entry.dataset && entry.dataset.logType) {
|
755
|
+
if (type === 'all' || entry.dataset.logType === type.toLowerCase()) {
|
756
|
+
entry.style.display = '';
|
757
|
+
} else {
|
758
|
+
entry.style.display = 'none';
|
759
|
+
}
|
760
|
+
return;
|
761
|
+
}
|
762
|
+
|
763
|
+
// Fallback to badge (old way)
|
764
|
+
const badge = entry.querySelector('.log-badge');
|
765
|
+
const logType = badge ? badge.textContent.toLowerCase() : 'info';
|
766
|
+
|
767
|
+
if (type === 'all' || logType === type.toLowerCase()) {
|
768
|
+
entry.style.display = '';
|
769
|
+
} else {
|
770
|
+
entry.style.display = 'none';
|
771
|
+
}
|
772
|
+
});
|
773
|
+
};
|
774
|
+
}
|
775
|
+
|
776
|
+
/**
|
777
|
+
* Function to add a log entry to the console - Only create if it doesn't exist
|
778
|
+
* @param {string} message - Log message
|
779
|
+
* @param {string} level - Log level (info, milestone, error)
|
780
|
+
* @param {Object} metadata - Optional metadata
|
781
|
+
*/
|
782
|
+
if (!window.addConsoleLog) {
|
783
|
+
window.addConsoleLog = function(message, level = 'info', metadata = null) {
|
784
|
+
console.log(`Adding console log (socket.js fallback): ${message} (${level})`);
|
785
|
+
|
786
|
+
// Create a log entry object
|
787
|
+
const logEntry = {
|
788
|
+
time: new Date().toISOString(),
|
789
|
+
message: message,
|
790
|
+
type: level,
|
791
|
+
metadata: metadata || { type: level }
|
792
|
+
};
|
793
|
+
|
794
|
+
// Try to use the log panel's direct function first
|
795
|
+
if (window.logPanel && typeof window.logPanel.addLog === 'function') {
|
796
|
+
console.log('Using logPanel.addLog to add log entry');
|
797
|
+
window.logPanel.addLog(message, level, metadata);
|
798
|
+
return;
|
799
|
+
}
|
800
|
+
|
801
|
+
// Then try the socket's connector function
|
802
|
+
if (window._socketAddLogEntry) {
|
803
|
+
console.log('Using _socketAddLogEntry to add log entry');
|
804
|
+
window._socketAddLogEntry(logEntry);
|
805
|
+
return;
|
806
|
+
}
|
807
|
+
|
808
|
+
console.warn('LogPanel functions not available, using fallback implementation');
|
809
|
+
|
810
|
+
// FALLBACK IMPLEMENTATION
|
811
|
+
const consoleLogContainer = document.getElementById('console-log-container');
|
812
|
+
if (!consoleLogContainer) {
|
813
|
+
console.warn('Console log container not found, log will be lost');
|
814
|
+
return;
|
815
|
+
}
|
816
|
+
|
817
|
+
// Clear empty message if present
|
818
|
+
const emptyMessage = consoleLogContainer.querySelector('.empty-log-message');
|
819
|
+
if (emptyMessage) {
|
820
|
+
emptyMessage.remove();
|
821
|
+
}
|
822
|
+
|
823
|
+
// Get or create a new log entry
|
824
|
+
const template = document.getElementById('console-log-entry-template');
|
825
|
+
let entry;
|
826
|
+
|
827
|
+
if (template) {
|
828
|
+
entry = document.importNode(template.content, true);
|
829
|
+
|
830
|
+
// Set content
|
831
|
+
entry.querySelector('.log-timestamp').textContent = new Date().toLocaleTimeString();
|
832
|
+
entry.querySelector('.log-badge').textContent = level.charAt(0).toUpperCase() + level.slice(1);
|
833
|
+
entry.querySelector('.log-badge').className = `log-badge ${level}`;
|
834
|
+
entry.querySelector('.log-message').textContent = message;
|
835
|
+
|
836
|
+
// Add data attribute for filtering
|
837
|
+
const logEntry = entry.querySelector('.console-log-entry');
|
838
|
+
if (logEntry) {
|
839
|
+
logEntry.dataset.logType = level.toLowerCase();
|
840
|
+
logEntry.classList.add(`log-${level.toLowerCase()}`);
|
841
|
+
}
|
842
|
+
} else {
|
843
|
+
// Create a simple log entry without template
|
844
|
+
entry = document.createElement('div');
|
845
|
+
entry.className = 'console-log-entry';
|
846
|
+
entry.dataset.logType = level.toLowerCase();
|
847
|
+
entry.classList.add(`log-${level.toLowerCase()}`);
|
848
|
+
|
849
|
+
// Create log content
|
850
|
+
entry.innerHTML = `
|
851
|
+
<span class="log-timestamp">${new Date().toLocaleTimeString()}</span>
|
852
|
+
<span class="log-badge ${level}">${level.charAt(0).toUpperCase() + level.slice(1)}</span>
|
853
|
+
<span class="log-message">${message}</span>
|
854
|
+
`;
|
855
|
+
}
|
856
|
+
|
857
|
+
// Add to container (at the beginning for newest first)
|
858
|
+
consoleLogContainer.insertBefore(entry, consoleLogContainer.firstChild);
|
859
|
+
|
860
|
+
// Update log count
|
861
|
+
const logIndicator = document.getElementById('log-indicator');
|
862
|
+
if (logIndicator) {
|
863
|
+
const currentCount = parseInt(logIndicator.textContent) || 0;
|
864
|
+
logIndicator.textContent = currentCount + 1;
|
865
|
+
}
|
866
|
+
|
867
|
+
// Show log panel if hidden
|
868
|
+
const logPanelToggle = document.getElementById('log-panel-toggle');
|
869
|
+
const logPanelContent = document.getElementById('log-panel-content');
|
870
|
+
|
871
|
+
if (logPanelContent && logPanelContent.classList.contains('collapsed') && logPanelToggle) {
|
872
|
+
// Auto-expand after a few logs
|
873
|
+
if (!window._logAutoExpandTimer) {
|
874
|
+
window._logAutoExpandTimer = setTimeout(() => {
|
875
|
+
console.log('Auto-expanding log panel due to accumulated logs');
|
876
|
+
logPanelToggle.click();
|
877
|
+
window._logAutoExpandTimer = null;
|
878
|
+
}, 500);
|
879
|
+
}
|
880
|
+
}
|
881
|
+
};
|
882
|
+
}
|