quantalogic 0.2.0__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.
- quantalogic/__init__.py +20 -0
- quantalogic/agent.py +638 -0
- quantalogic/agent_config.py +138 -0
- quantalogic/coding_agent.py +83 -0
- quantalogic/event_emitter.py +223 -0
- quantalogic/generative_model.py +226 -0
- quantalogic/interactive_text_editor.py +190 -0
- quantalogic/main.py +185 -0
- quantalogic/memory.py +217 -0
- quantalogic/model_names.py +19 -0
- quantalogic/print_event.py +66 -0
- quantalogic/prompts.py +99 -0
- quantalogic/server/__init__.py +3 -0
- quantalogic/server/agent_server.py +633 -0
- quantalogic/server/models.py +60 -0
- quantalogic/server/routes.py +117 -0
- quantalogic/server/state.py +199 -0
- quantalogic/server/static/js/event_visualizer.js +430 -0
- quantalogic/server/static/js/quantalogic.js +571 -0
- quantalogic/server/templates/index.html +134 -0
- quantalogic/tool_manager.py +68 -0
- quantalogic/tools/__init__.py +46 -0
- quantalogic/tools/agent_tool.py +88 -0
- quantalogic/tools/download_http_file_tool.py +64 -0
- quantalogic/tools/edit_whole_content_tool.py +70 -0
- quantalogic/tools/elixir_tool.py +240 -0
- quantalogic/tools/execute_bash_command_tool.py +116 -0
- quantalogic/tools/input_question_tool.py +57 -0
- quantalogic/tools/language_handlers/__init__.py +21 -0
- quantalogic/tools/language_handlers/c_handler.py +33 -0
- quantalogic/tools/language_handlers/cpp_handler.py +33 -0
- quantalogic/tools/language_handlers/go_handler.py +33 -0
- quantalogic/tools/language_handlers/java_handler.py +37 -0
- quantalogic/tools/language_handlers/javascript_handler.py +42 -0
- quantalogic/tools/language_handlers/python_handler.py +29 -0
- quantalogic/tools/language_handlers/rust_handler.py +33 -0
- quantalogic/tools/language_handlers/scala_handler.py +33 -0
- quantalogic/tools/language_handlers/typescript_handler.py +42 -0
- quantalogic/tools/list_directory_tool.py +123 -0
- quantalogic/tools/llm_tool.py +119 -0
- quantalogic/tools/markitdown_tool.py +105 -0
- quantalogic/tools/nodejs_tool.py +515 -0
- quantalogic/tools/python_tool.py +469 -0
- quantalogic/tools/read_file_block_tool.py +140 -0
- quantalogic/tools/read_file_tool.py +79 -0
- quantalogic/tools/replace_in_file_tool.py +300 -0
- quantalogic/tools/ripgrep_tool.py +353 -0
- quantalogic/tools/search_definition_names.py +419 -0
- quantalogic/tools/task_complete_tool.py +35 -0
- quantalogic/tools/tool.py +146 -0
- quantalogic/tools/unified_diff_tool.py +387 -0
- quantalogic/tools/write_file_tool.py +97 -0
- quantalogic/utils/__init__.py +17 -0
- quantalogic/utils/ask_user_validation.py +12 -0
- quantalogic/utils/download_http_file.py +77 -0
- quantalogic/utils/get_coding_environment.py +15 -0
- quantalogic/utils/get_environment.py +26 -0
- quantalogic/utils/get_quantalogic_rules_content.py +19 -0
- quantalogic/utils/git_ls.py +121 -0
- quantalogic/utils/read_file.py +54 -0
- quantalogic/utils/read_http_text_content.py +101 -0
- quantalogic/xml_parser.py +242 -0
- quantalogic/xml_tool_parser.py +99 -0
- quantalogic-0.2.0.dist-info/LICENSE +201 -0
- quantalogic-0.2.0.dist-info/METADATA +1034 -0
- quantalogic-0.2.0.dist-info/RECORD +68 -0
- quantalogic-0.2.0.dist-info/WHEEL +4 -0
- quantalogic-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,571 @@
|
|
1
|
+
import { EventVisualizer } from './event_visualizer.js';
|
2
|
+
|
3
|
+
class QuantaLogicUI {
|
4
|
+
constructor() {
|
5
|
+
this.eventSource = null;
|
6
|
+
this.isProcessing = false;
|
7
|
+
this.eventVisualizer = new EventVisualizer();
|
8
|
+
this.processedEventIds = new Set();
|
9
|
+
this.activeValidationDialog = null;
|
10
|
+
this.connectionState = 'disconnected';
|
11
|
+
this.initializeElements();
|
12
|
+
this.attachEventListeners();
|
13
|
+
this.connectSSE();
|
14
|
+
this.currentTaskId = null; // Add tracking for current task
|
15
|
+
}
|
16
|
+
|
17
|
+
initializeElements() {
|
18
|
+
this.elements = {
|
19
|
+
taskInput: document.getElementById('taskInput'),
|
20
|
+
submitTask: document.getElementById('submitTask'),
|
21
|
+
clearEvents: document.getElementById('clearEvents'),
|
22
|
+
eventsContainer: document.getElementById('eventsContainer'),
|
23
|
+
connectionStatus: document.getElementById('connectionStatus'),
|
24
|
+
modelSelect: document.getElementById('modelSelect'),
|
25
|
+
maxIterations: document.getElementById('maxIterations'),
|
26
|
+
autoScroll: document.getElementById('autoScroll'),
|
27
|
+
finalAnswer: document.getElementById('finalAnswer'),
|
28
|
+
copyAnswer: document.getElementById('copyAnswer')
|
29
|
+
};
|
30
|
+
|
31
|
+
// Verify elements exist and throw error if critical elements are missing
|
32
|
+
if (!this.elements.eventsContainer || !this.elements.clearEvents) {
|
33
|
+
console.error('Critical UI elements are missing');
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
attachEventListeners() {
|
38
|
+
// Task submission handlers
|
39
|
+
this.elements.submitTask.addEventListener('click', () => this.handleTaskSubmit());
|
40
|
+
this.elements.taskInput.addEventListener('keydown', (e) => {
|
41
|
+
if (e.ctrlKey && e.key === 'Enter') {
|
42
|
+
this.handleTaskSubmit();
|
43
|
+
}
|
44
|
+
});
|
45
|
+
|
46
|
+
// Clear events handler
|
47
|
+
this.elements.clearEvents.addEventListener('click', () => this.clearEvents());
|
48
|
+
|
49
|
+
// Copy answer handler
|
50
|
+
this.elements.copyAnswer?.addEventListener('click', () => this.handleCopyAnswer());
|
51
|
+
}
|
52
|
+
|
53
|
+
connectSSE() {
|
54
|
+
if (this.eventSource) {
|
55
|
+
this.eventSource.close();
|
56
|
+
this.processedEventIds.clear();
|
57
|
+
}
|
58
|
+
|
59
|
+
this.eventSource = new EventSource('/events');
|
60
|
+
|
61
|
+
this.eventSource.onopen = () => {
|
62
|
+
this.updateConnectionStatus(true);
|
63
|
+
console.log('SSE Connection established');
|
64
|
+
};
|
65
|
+
|
66
|
+
// Standard event handlers
|
67
|
+
const eventTypes = [
|
68
|
+
'session_start', 'session_end', 'session_add_message',
|
69
|
+
'task_solve_start', 'task_solve_end',
|
70
|
+
'task_think_start', 'task_think_end', 'task_complete',
|
71
|
+
'tool_execution_start', 'tool_execution_end',
|
72
|
+
'tool_execute_validation_start', 'tool_execute_validation_end',
|
73
|
+
'memory_full', 'memory_compacted', 'memory_summary',
|
74
|
+
'error_max_iterations_reached', 'error_tool_execution',
|
75
|
+
'error_model_response', 'final_result', 'error'
|
76
|
+
];
|
77
|
+
|
78
|
+
eventTypes.forEach(eventType => {
|
79
|
+
this.eventSource.addEventListener(eventType, (event) => {
|
80
|
+
try {
|
81
|
+
const data = JSON.parse(event.data);
|
82
|
+
if (!data.id || !this.processedEventIds.has(data.id)) {
|
83
|
+
this.handleEvent(data);
|
84
|
+
}
|
85
|
+
} catch (error) {
|
86
|
+
console.error(`Error handling ${eventType} event:`, error);
|
87
|
+
}
|
88
|
+
});
|
89
|
+
});
|
90
|
+
|
91
|
+
// Validation request handler
|
92
|
+
this.eventSource.addEventListener('user_validation_request', async (event) => {
|
93
|
+
try {
|
94
|
+
const data = JSON.parse(event.data);
|
95
|
+
const validationData = data.data;
|
96
|
+
const response = await this.showValidationDialog(validationData.question);
|
97
|
+
await this.submitValidationResponse(validationData.validation_id, response);
|
98
|
+
} catch (error) {
|
99
|
+
console.error('Error handling validation request:', error);
|
100
|
+
}
|
101
|
+
});
|
102
|
+
|
103
|
+
this.eventSource.onerror = (error) => {
|
104
|
+
console.error('SSE Error:', error);
|
105
|
+
this.updateConnectionStatus(false);
|
106
|
+
this.eventSource.close();
|
107
|
+
setTimeout(() => this.connectSSE(), 5000);
|
108
|
+
};
|
109
|
+
}
|
110
|
+
|
111
|
+
async handleTaskSubmit() {
|
112
|
+
if (this.isProcessing) {
|
113
|
+
this.showNotification('Please wait for the current task to complete.', 'warning');
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
|
117
|
+
const task = this.elements.taskInput.value.trim();
|
118
|
+
if (!task) {
|
119
|
+
this.showNotification('Please enter a task.', 'warning');
|
120
|
+
return;
|
121
|
+
}
|
122
|
+
|
123
|
+
this.clearEvents();
|
124
|
+
this.setProcessingState(true);
|
125
|
+
|
126
|
+
try {
|
127
|
+
// Submit task using new endpoint
|
128
|
+
const submitResponse = await fetch('/tasks', {
|
129
|
+
method: 'POST',
|
130
|
+
headers: {
|
131
|
+
'Content-Type': 'application/json',
|
132
|
+
},
|
133
|
+
body: JSON.stringify({
|
134
|
+
task: task,
|
135
|
+
model_name: this.elements.modelSelect.value,
|
136
|
+
max_iterations: parseInt(this.elements.maxIterations.value)
|
137
|
+
}),
|
138
|
+
});
|
139
|
+
|
140
|
+
if (!submitResponse.ok) {
|
141
|
+
throw new Error('Failed to submit task');
|
142
|
+
}
|
143
|
+
|
144
|
+
const { task_id } = await submitResponse.json();
|
145
|
+
this.currentTaskId = task_id; // Set current task ID
|
146
|
+
|
147
|
+
// Start polling for task status
|
148
|
+
await this.pollTaskStatus(task_id);
|
149
|
+
|
150
|
+
// Initialize Event Stream with task_id
|
151
|
+
this.initializeEventStream(task_id);
|
152
|
+
|
153
|
+
} catch (error) {
|
154
|
+
console.error('Error:', error);
|
155
|
+
this.handleEvent({
|
156
|
+
event: 'error',
|
157
|
+
timestamp: new Date().toISOString(),
|
158
|
+
data: {
|
159
|
+
error: error.message
|
160
|
+
}
|
161
|
+
});
|
162
|
+
} finally {
|
163
|
+
this.setProcessingState(false);
|
164
|
+
this.currentTaskId = null; // Clear task ID when done
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
initializeEventStream(taskId) {
|
169
|
+
// Close existing event source if any
|
170
|
+
if (this.eventSource) {
|
171
|
+
this.eventSource.close();
|
172
|
+
}
|
173
|
+
|
174
|
+
// Reset connection state
|
175
|
+
this.connectionState = 'connecting';
|
176
|
+
this.updateConnectionStatus();
|
177
|
+
|
178
|
+
// Initialize a new EventSource with task_id
|
179
|
+
this.eventSource = new EventSource(`/events?task_id=${taskId}`);
|
180
|
+
|
181
|
+
this.eventSource.onopen = () => {
|
182
|
+
this.connectionState = 'connected';
|
183
|
+
this.updateConnectionStatus();
|
184
|
+
console.log(`Connected to event stream for task ${taskId}`);
|
185
|
+
};
|
186
|
+
|
187
|
+
this.eventSource.onmessage = (event) => {
|
188
|
+
try {
|
189
|
+
const eventData = JSON.parse(event.data);
|
190
|
+
this.displayEvent(eventData);
|
191
|
+
} catch (parseError) {
|
192
|
+
console.error('Error parsing event data:', parseError);
|
193
|
+
}
|
194
|
+
};
|
195
|
+
|
196
|
+
this.eventSource.addEventListener('task_complete', (event) => {
|
197
|
+
const data = JSON.parse(event.data);
|
198
|
+
console.log(`Task ${data.task_id} completed with result:`, data.result);
|
199
|
+
this.closeEventStream();
|
200
|
+
});
|
201
|
+
|
202
|
+
this.eventSource.addEventListener('task_error', (event) => {
|
203
|
+
const data = JSON.parse(event.data);
|
204
|
+
console.error(`Task ${data.task_id} failed with error:`, data.error);
|
205
|
+
this.closeEventStream();
|
206
|
+
});
|
207
|
+
|
208
|
+
this.eventSource.onerror = (error) => {
|
209
|
+
this.connectionState = 'disconnected';
|
210
|
+
this.updateConnectionStatus();
|
211
|
+
console.error('EventSource failed:', error);
|
212
|
+
|
213
|
+
// Attempt reconnection with exponential backoff
|
214
|
+
this.reconnectEventStream(taskId);
|
215
|
+
};
|
216
|
+
}
|
217
|
+
|
218
|
+
closeEventStream() {
|
219
|
+
if (this.eventSource) {
|
220
|
+
this.eventSource.close();
|
221
|
+
this.eventSource = null;
|
222
|
+
}
|
223
|
+
this.connectionState = 'disconnected';
|
224
|
+
this.updateConnectionStatus();
|
225
|
+
}
|
226
|
+
|
227
|
+
reconnectEventStream(taskId, attempt = 1) {
|
228
|
+
const maxAttempts = 5;
|
229
|
+
const baseDelay = 1000; // 1 second
|
230
|
+
|
231
|
+
if (attempt > maxAttempts) {
|
232
|
+
console.error('Max reconnection attempts reached');
|
233
|
+
return;
|
234
|
+
}
|
235
|
+
|
236
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
237
|
+
|
238
|
+
setTimeout(() => {
|
239
|
+
console.log(`Attempting to reconnect (Attempt ${attempt})...`);
|
240
|
+
this.initializeEventStream(taskId);
|
241
|
+
}, delay);
|
242
|
+
}
|
243
|
+
|
244
|
+
updateConnectionStatus() {
|
245
|
+
const statusElement = document.getElementById('connectionStatus');
|
246
|
+
if (statusElement) {
|
247
|
+
statusElement.textContent = `Connection: ${this.connectionState}`;
|
248
|
+
statusElement.className = this.connectionState;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
async pollTaskStatus(taskId) {
|
253
|
+
const pollInterval = 1000; // 1 second
|
254
|
+
const maxAttempts = 300; // 5 minutes maximum
|
255
|
+
let attempts = 0;
|
256
|
+
|
257
|
+
const poll = async () => {
|
258
|
+
try {
|
259
|
+
const response = await fetch(`/tasks/${taskId}`);
|
260
|
+
if (!response.ok) {
|
261
|
+
throw new Error('Failed to fetch task status');
|
262
|
+
}
|
263
|
+
|
264
|
+
const taskStatus = await response.json();
|
265
|
+
|
266
|
+
switch (taskStatus.status) {
|
267
|
+
case 'completed':
|
268
|
+
this.handleEvent({
|
269
|
+
event: 'task_complete',
|
270
|
+
task_id: taskId,
|
271
|
+
timestamp: new Date().toISOString(),
|
272
|
+
data: {
|
273
|
+
result: taskStatus.result,
|
274
|
+
total_tokens: taskStatus.total_tokens,
|
275
|
+
model_name: taskStatus.model_name
|
276
|
+
}
|
277
|
+
});
|
278
|
+
return true;
|
279
|
+
|
280
|
+
case 'failed':
|
281
|
+
this.handleEvent({
|
282
|
+
event: 'error',
|
283
|
+
task_id: taskId,
|
284
|
+
timestamp: new Date().toISOString(),
|
285
|
+
data: {
|
286
|
+
error: taskStatus.error
|
287
|
+
}
|
288
|
+
});
|
289
|
+
return true;
|
290
|
+
|
291
|
+
case 'running':
|
292
|
+
// Continue polling
|
293
|
+
return false;
|
294
|
+
|
295
|
+
default:
|
296
|
+
if (++attempts >= maxAttempts) {
|
297
|
+
throw new Error('Task polling timeout');
|
298
|
+
}
|
299
|
+
return false;
|
300
|
+
}
|
301
|
+
} catch (error) {
|
302
|
+
console.error('Polling error:', error);
|
303
|
+
this.handleEvent({
|
304
|
+
event: 'error',
|
305
|
+
task_id: taskId,
|
306
|
+
timestamp: new Date().toISOString(),
|
307
|
+
data: {
|
308
|
+
error: error.message
|
309
|
+
}
|
310
|
+
});
|
311
|
+
return true;
|
312
|
+
}
|
313
|
+
};
|
314
|
+
|
315
|
+
while (!(await poll())) {
|
316
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
setProcessingState(processing) {
|
321
|
+
this.isProcessing = processing;
|
322
|
+
this.elements.submitTask.disabled = processing;
|
323
|
+
|
324
|
+
const buttonText = this.elements.submitTask.querySelector('span');
|
325
|
+
const loadingIndicator = this.elements.submitTask.querySelector('.loading-indicator');
|
326
|
+
|
327
|
+
if (processing) {
|
328
|
+
buttonText.textContent = 'Processing...';
|
329
|
+
loadingIndicator?.classList.remove('hidden');
|
330
|
+
this.elements.submitTask.classList.add('opacity-75');
|
331
|
+
} else {
|
332
|
+
buttonText.textContent = 'Submit Task';
|
333
|
+
loadingIndicator?.classList.add('hidden');
|
334
|
+
this.elements.submitTask.classList.remove('opacity-75');
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
async showValidationDialog(question) {
|
339
|
+
if (this.activeValidationDialog) {
|
340
|
+
this.closeValidationDialog();
|
341
|
+
}
|
342
|
+
|
343
|
+
return new Promise((resolve) => {
|
344
|
+
const dialog = document.createElement('div');
|
345
|
+
dialog.className = 'fixed inset-0 z-50 overflow-y-auto';
|
346
|
+
dialog.setAttribute('role', 'dialog');
|
347
|
+
dialog.setAttribute('aria-modal', 'true');
|
348
|
+
|
349
|
+
dialog.innerHTML = `
|
350
|
+
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
351
|
+
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" aria-hidden="true"></div>
|
352
|
+
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
353
|
+
<div class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
354
|
+
<div class="sm:flex sm:items-start">
|
355
|
+
<div class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-blue-100 rounded-full sm:mx-0 sm:h-10 sm:w-10">
|
356
|
+
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
357
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
358
|
+
</svg>
|
359
|
+
</div>
|
360
|
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
361
|
+
<h3 class="text-lg font-medium leading-6 text-gray-900">Validation Required</h3>
|
362
|
+
<div class="mt-2">
|
363
|
+
<p class="text-sm text-gray-500">${question}</p>
|
364
|
+
</div>
|
365
|
+
</div>
|
366
|
+
</div>
|
367
|
+
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
368
|
+
<button type="button" class="validate-yes inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
|
369
|
+
Yes
|
370
|
+
</button>
|
371
|
+
<button type="button" class="validate-no mt-3 inline-flex justify-center w-full px-4 py-2 text-base font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:w-auto sm:text-sm">
|
372
|
+
No
|
373
|
+
</button>
|
374
|
+
</div>
|
375
|
+
</div>
|
376
|
+
</div>
|
377
|
+
`;
|
378
|
+
|
379
|
+
const handleResponse = (response) => {
|
380
|
+
this.closeValidationDialog();
|
381
|
+
resolve(response);
|
382
|
+
};
|
383
|
+
|
384
|
+
dialog.querySelector('.validate-yes').addEventListener('click', () => handleResponse(true));
|
385
|
+
dialog.querySelector('.validate-no').addEventListener('click', () => handleResponse(false));
|
386
|
+
|
387
|
+
document.body.appendChild(dialog);
|
388
|
+
this.activeValidationDialog = dialog;
|
389
|
+
});
|
390
|
+
}
|
391
|
+
|
392
|
+
closeValidationDialog() {
|
393
|
+
if (this.activeValidationDialog) {
|
394
|
+
document.body.removeChild(this.activeValidationDialog);
|
395
|
+
this.activeValidationDialog = null;
|
396
|
+
}
|
397
|
+
}
|
398
|
+
|
399
|
+
async submitValidationResponse(validationId, response) {
|
400
|
+
try {
|
401
|
+
const result = await fetch(`/validate_response/${validationId}`, {
|
402
|
+
method: 'POST',
|
403
|
+
headers: {
|
404
|
+
'Content-Type': 'application/json',
|
405
|
+
},
|
406
|
+
body: JSON.stringify({ response })
|
407
|
+
});
|
408
|
+
|
409
|
+
if (!result.ok) {
|
410
|
+
throw new Error('Failed to submit validation response');
|
411
|
+
}
|
412
|
+
|
413
|
+
return await result.json();
|
414
|
+
} catch (error) {
|
415
|
+
console.error('Error submitting validation response:', error);
|
416
|
+
throw error;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
handleEvent(eventData) {
|
421
|
+
try {
|
422
|
+
// Only process events for the current task or events without task_id
|
423
|
+
if (eventData.task_id && eventData.task_id !== this.currentTaskId) {
|
424
|
+
return;
|
425
|
+
}
|
426
|
+
|
427
|
+
if (eventData.id && this.processedEventIds.has(eventData.id)) {
|
428
|
+
return;
|
429
|
+
}
|
430
|
+
|
431
|
+
this.addEvent(eventData);
|
432
|
+
|
433
|
+
if (eventData.id) {
|
434
|
+
this.processedEventIds.add(eventData.id);
|
435
|
+
|
436
|
+
if (this.processedEventIds.size > 1000) {
|
437
|
+
const idsArray = Array.from(this.processedEventIds);
|
438
|
+
this.processedEventIds = new Set(idsArray.slice(-500));
|
439
|
+
}
|
440
|
+
}
|
441
|
+
|
442
|
+
// Update final answer without clearing events
|
443
|
+
if ((eventData.event === 'final_result' || eventData.event === 'task_complete') &&
|
444
|
+
(!eventData.task_id || eventData.task_id === this.currentTaskId)) {
|
445
|
+
this.displayFinalAnswer(eventData.data);
|
446
|
+
}
|
447
|
+
} catch (error) {
|
448
|
+
console.error('Error handling event:', error);
|
449
|
+
}
|
450
|
+
}
|
451
|
+
|
452
|
+
addEvent(eventData) {
|
453
|
+
const eventElement = this.eventVisualizer.createEventCard(eventData);
|
454
|
+
if (!eventElement) return;
|
455
|
+
|
456
|
+
// Remove running state from previous event if it exists
|
457
|
+
if (this.lastRunningEvent) {
|
458
|
+
this.eventVisualizer.updateRunningState(this.lastRunningEvent, false);
|
459
|
+
}
|
460
|
+
|
461
|
+
this.elements.eventsContainer.appendChild(eventElement);
|
462
|
+
|
463
|
+
// Update running state tracking
|
464
|
+
const isRunning = this.eventVisualizer.isRunningEvent(eventData.event || eventData.type);
|
465
|
+
if (isRunning) {
|
466
|
+
this.lastRunningEvent = eventElement;
|
467
|
+
} else if (this.lastRunningEvent) {
|
468
|
+
// If this is an "end" event that matches the last running event's type
|
469
|
+
const lastRunningType = this.lastRunningEvent.querySelector('.font-medium').textContent;
|
470
|
+
const currentType = eventData.event || eventData.type;
|
471
|
+
if (currentType.toLowerCase().includes('end') &&
|
472
|
+
currentType.toLowerCase().replace('_end', '').includes(lastRunningType.toLowerCase().replace('Start', ''))) {
|
473
|
+
this.eventVisualizer.updateRunningState(this.lastRunningEvent, false);
|
474
|
+
this.lastRunningEvent = null;
|
475
|
+
}
|
476
|
+
}
|
477
|
+
|
478
|
+
if (this.elements.autoScroll.checked) {
|
479
|
+
this.elements.eventsContainer.scrollTop = this.elements.eventsContainer.scrollHeight;
|
480
|
+
}
|
481
|
+
|
482
|
+
if (window.Prism) {
|
483
|
+
Prism.highlightAllUnder(eventElement);
|
484
|
+
}
|
485
|
+
}
|
486
|
+
|
487
|
+
clearEvents() {
|
488
|
+
if (this.elements.eventsContainer) {
|
489
|
+
while (this.elements.eventsContainer.firstChild) {
|
490
|
+
this.elements.eventsContainer.removeChild(this.elements.eventsContainer.firstChild);
|
491
|
+
}
|
492
|
+
}
|
493
|
+
this.processedEventIds.clear();
|
494
|
+
this.clearFinalAnswer();
|
495
|
+
this.lastRunningEvent = null;
|
496
|
+
this.currentTaskId = null; // Reset current task ID
|
497
|
+
}
|
498
|
+
|
499
|
+
clearFinalAnswer() {
|
500
|
+
if (this.elements.finalAnswer) {
|
501
|
+
this.elements.finalAnswer.classList.add('hidden');
|
502
|
+
const answerContent = this.elements.finalAnswer.querySelector('.answer-content');
|
503
|
+
if (answerContent) {
|
504
|
+
answerContent.innerHTML = '';
|
505
|
+
}
|
506
|
+
}
|
507
|
+
}
|
508
|
+
|
509
|
+
displayFinalAnswer(data) {
|
510
|
+
if (!this.elements.finalAnswer || !data.result) return;
|
511
|
+
|
512
|
+
const answerContent = this.elements.finalAnswer.querySelector('.answer-content');
|
513
|
+
if (!answerContent) return;
|
514
|
+
|
515
|
+
// Parse markdown and render without affecting events
|
516
|
+
const renderedContent = marked.parse(data.result, {
|
517
|
+
highlight: (code, lang) => {
|
518
|
+
if (Prism.languages[lang]) {
|
519
|
+
return Prism.highlight(code, Prism.languages[lang], lang);
|
520
|
+
}
|
521
|
+
return code;
|
522
|
+
}
|
523
|
+
});
|
524
|
+
|
525
|
+
answerContent.innerHTML = renderedContent;
|
526
|
+
this.elements.finalAnswer.classList.remove('hidden');
|
527
|
+
|
528
|
+
// Apply syntax highlighting
|
529
|
+
answerContent.querySelectorAll('pre code').forEach((block) => {
|
530
|
+
Prism.highlightElement(block);
|
531
|
+
});
|
532
|
+
|
533
|
+
// Scroll to show the answer
|
534
|
+
if (this.elements.autoScroll.checked) {
|
535
|
+
this.elements.finalAnswer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
536
|
+
}
|
537
|
+
}
|
538
|
+
|
539
|
+
async handleCopyAnswer() {
|
540
|
+
const answerContent = this.elements.finalAnswer.querySelector('.answer-content');
|
541
|
+
if (!answerContent) return;
|
542
|
+
|
543
|
+
try {
|
544
|
+
await navigator.clipboard.writeText(answerContent.textContent);
|
545
|
+
|
546
|
+
const copyButton = this.elements.copyAnswer;
|
547
|
+
const originalText = copyButton.querySelector('span').textContent;
|
548
|
+
const originalClasses = copyButton.className;
|
549
|
+
|
550
|
+
copyButton.className = 'inline-flex items-center px-3 py-1 text-sm font-medium rounded-md text-green-700 bg-green-100 transition-colors duration-200';
|
551
|
+
copyButton.querySelector('span').textContent = 'Copied!';
|
552
|
+
|
553
|
+
setTimeout(() => {
|
554
|
+
copyButton.className = originalClasses;
|
555
|
+
copyButton.querySelector('span').textContent = originalText;
|
556
|
+
}, 2000);
|
557
|
+
} catch (error) {
|
558
|
+
console.error('Failed to copy text:', error);
|
559
|
+
}
|
560
|
+
}
|
561
|
+
|
562
|
+
showNotification(message, type = 'info') {
|
563
|
+
// Implementation can be added for showing notifications
|
564
|
+
console.log(`${type}: ${message}`);
|
565
|
+
}
|
566
|
+
}
|
567
|
+
|
568
|
+
// Initialize the UI when the document is ready
|
569
|
+
document.addEventListener('DOMContentLoaded', () => {
|
570
|
+
window.quantaLogicUI = new QuantaLogicUI();
|
571
|
+
});
|