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.
Files changed (68) hide show
  1. quantalogic/__init__.py +20 -0
  2. quantalogic/agent.py +638 -0
  3. quantalogic/agent_config.py +138 -0
  4. quantalogic/coding_agent.py +83 -0
  5. quantalogic/event_emitter.py +223 -0
  6. quantalogic/generative_model.py +226 -0
  7. quantalogic/interactive_text_editor.py +190 -0
  8. quantalogic/main.py +185 -0
  9. quantalogic/memory.py +217 -0
  10. quantalogic/model_names.py +19 -0
  11. quantalogic/print_event.py +66 -0
  12. quantalogic/prompts.py +99 -0
  13. quantalogic/server/__init__.py +3 -0
  14. quantalogic/server/agent_server.py +633 -0
  15. quantalogic/server/models.py +60 -0
  16. quantalogic/server/routes.py +117 -0
  17. quantalogic/server/state.py +199 -0
  18. quantalogic/server/static/js/event_visualizer.js +430 -0
  19. quantalogic/server/static/js/quantalogic.js +571 -0
  20. quantalogic/server/templates/index.html +134 -0
  21. quantalogic/tool_manager.py +68 -0
  22. quantalogic/tools/__init__.py +46 -0
  23. quantalogic/tools/agent_tool.py +88 -0
  24. quantalogic/tools/download_http_file_tool.py +64 -0
  25. quantalogic/tools/edit_whole_content_tool.py +70 -0
  26. quantalogic/tools/elixir_tool.py +240 -0
  27. quantalogic/tools/execute_bash_command_tool.py +116 -0
  28. quantalogic/tools/input_question_tool.py +57 -0
  29. quantalogic/tools/language_handlers/__init__.py +21 -0
  30. quantalogic/tools/language_handlers/c_handler.py +33 -0
  31. quantalogic/tools/language_handlers/cpp_handler.py +33 -0
  32. quantalogic/tools/language_handlers/go_handler.py +33 -0
  33. quantalogic/tools/language_handlers/java_handler.py +37 -0
  34. quantalogic/tools/language_handlers/javascript_handler.py +42 -0
  35. quantalogic/tools/language_handlers/python_handler.py +29 -0
  36. quantalogic/tools/language_handlers/rust_handler.py +33 -0
  37. quantalogic/tools/language_handlers/scala_handler.py +33 -0
  38. quantalogic/tools/language_handlers/typescript_handler.py +42 -0
  39. quantalogic/tools/list_directory_tool.py +123 -0
  40. quantalogic/tools/llm_tool.py +119 -0
  41. quantalogic/tools/markitdown_tool.py +105 -0
  42. quantalogic/tools/nodejs_tool.py +515 -0
  43. quantalogic/tools/python_tool.py +469 -0
  44. quantalogic/tools/read_file_block_tool.py +140 -0
  45. quantalogic/tools/read_file_tool.py +79 -0
  46. quantalogic/tools/replace_in_file_tool.py +300 -0
  47. quantalogic/tools/ripgrep_tool.py +353 -0
  48. quantalogic/tools/search_definition_names.py +419 -0
  49. quantalogic/tools/task_complete_tool.py +35 -0
  50. quantalogic/tools/tool.py +146 -0
  51. quantalogic/tools/unified_diff_tool.py +387 -0
  52. quantalogic/tools/write_file_tool.py +97 -0
  53. quantalogic/utils/__init__.py +17 -0
  54. quantalogic/utils/ask_user_validation.py +12 -0
  55. quantalogic/utils/download_http_file.py +77 -0
  56. quantalogic/utils/get_coding_environment.py +15 -0
  57. quantalogic/utils/get_environment.py +26 -0
  58. quantalogic/utils/get_quantalogic_rules_content.py +19 -0
  59. quantalogic/utils/git_ls.py +121 -0
  60. quantalogic/utils/read_file.py +54 -0
  61. quantalogic/utils/read_http_text_content.py +101 -0
  62. quantalogic/xml_parser.py +242 -0
  63. quantalogic/xml_tool_parser.py +99 -0
  64. quantalogic-0.2.0.dist-info/LICENSE +201 -0
  65. quantalogic-0.2.0.dist-info/METADATA +1034 -0
  66. quantalogic-0.2.0.dist-info/RECORD +68 -0
  67. quantalogic-0.2.0.dist-info/WHEEL +4 -0
  68. 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">&#8203;</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
+ });