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,430 @@
1
+ export class EventVisualizer {
2
+ constructor() {
3
+ this.typeStyles = {
4
+ start: {
5
+ border: 'border-green-500',
6
+ bg: 'bg-green-50',
7
+ text: 'text-green-800',
8
+ icon: `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
9
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 1.414L10.586 9H7a1 1 0 100 2h3.586l-1.293 1.293a1 1 0 101.414 1.414l3-3a1 1 0 000-1.414z" clip-rule="evenodd"/>
10
+ </svg>`
11
+ },
12
+ end: {
13
+ border: 'border-blue-500',
14
+ bg: 'bg-blue-50',
15
+ text: 'text-blue-800',
16
+ icon: `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
17
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm.707-10.293a1 1 0 00-1.414-1.414l-3 3a1 1 0 000 1.414l3 3a1 1 0 001.414-1.414L9.414 11H13a1 1 0 100-2H9.414l1.293-1.293z" clip-rule="evenodd"/>
18
+ </svg>`
19
+ },
20
+ error: {
21
+ border: 'border-red-500',
22
+ bg: 'bg-red-50',
23
+ text: 'text-red-800',
24
+ icon: `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
25
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
26
+ </svg>`
27
+ },
28
+ info: {
29
+ border: 'border-indigo-500',
30
+ bg: 'bg-indigo-50',
31
+ text: 'text-indigo-800',
32
+ icon: `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
33
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
34
+ </svg>`
35
+ },
36
+ warning: {
37
+ border: 'border-yellow-500',
38
+ bg: 'bg-yellow-50',
39
+ text: 'text-yellow-800',
40
+ icon: `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
41
+ <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
42
+ </svg>`
43
+ },
44
+ validation: {
45
+ border: 'border-purple-500',
46
+ bg: 'bg-purple-50',
47
+ text: 'text-purple-800',
48
+ icon: `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
49
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
50
+ </svg>`
51
+ }
52
+ };
53
+ this.lastRunningEvent = null;
54
+ }
55
+
56
+ getEventTypeInfo(type) {
57
+ if (!type) return this.typeStyles.info;
58
+ const lowerType = type.toLowerCase();
59
+
60
+ for (const [key, style] of Object.entries(this.typeStyles)) {
61
+ if (lowerType.includes(key)) {
62
+ return style;
63
+ }
64
+ }
65
+
66
+ return this.typeStyles.info;
67
+ }
68
+
69
+ createEventCard(event) {
70
+ const card = document.createElement('div');
71
+ const eventType = event.type || event.event || 'unknown';
72
+ const typeInfo = this.getEventTypeInfo(eventType);
73
+ const isRunning = this.isRunningEvent(eventType);
74
+
75
+ card.dataset.running = isRunning;
76
+ card.dataset.eventType = eventType.toLowerCase();
77
+
78
+ card.className = `
79
+ mb-4 rounded-lg shadow-sm border-l-4 ${typeInfo.border} ${typeInfo.bg}
80
+ transition-all duration-300 hover:shadow-md transform hover:-translate-y-px
81
+ `;
82
+
83
+ if (isRunning) {
84
+ const processingIndicator = document.createElement('div');
85
+ processingIndicator.className = 'absolute right-0 top-0 mt-4 mr-12';
86
+ processingIndicator.innerHTML = `
87
+ <div class="flex items-center space-x-2">
88
+ <span class="text-sm text-gray-500">Processing</span>
89
+ <svg class="animate-spin h-4 w-4 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
90
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
91
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
92
+ </svg>
93
+ </div>
94
+ `;
95
+ card.classList.add('relative');
96
+ card.insertBefore(processingIndicator, card.firstChild);
97
+ }
98
+
99
+ const header = this.createEventHeader(event, typeInfo);
100
+ const contentWrapper = this.createEventContentWrapper(event, typeInfo);
101
+
102
+ header.addEventListener('click', () => {
103
+ contentWrapper.classList.toggle('hidden');
104
+ header.querySelector('.expand-icon').classList.toggle('rotate-180');
105
+ });
106
+
107
+ card.appendChild(header);
108
+ card.appendChild(contentWrapper);
109
+ return card;
110
+ }
111
+
112
+ isRunningEvent(eventType) {
113
+ const type = eventType.toLowerCase();
114
+ return type.includes('start') || type.includes('running') ||
115
+ (type.includes('think') && !type.includes('end')) ||
116
+ (type.includes('execution') && !type.includes('end'));
117
+ }
118
+
119
+ updateRunningState(eventElement, isRunning) {
120
+ if (isRunning) {
121
+ eventElement.dataset.running = 'true';
122
+ } else {
123
+ eventElement.dataset.running = 'false';
124
+ }
125
+
126
+ // Update processing indicator
127
+ const existingIndicator = eventElement.querySelector('.animate-spin');
128
+ if (isRunning && !existingIndicator) {
129
+ const processingIndicator = document.createElement('div');
130
+ processingIndicator.className = 'absolute right-0 top-0 mt-4 mr-12';
131
+ processingIndicator.innerHTML = `
132
+ <svg class="animate-spin h-4 w-4 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
133
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
134
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
135
+ </svg>
136
+ `;
137
+ eventElement.classList.add('relative');
138
+ eventElement.insertBefore(processingIndicator, eventElement.firstChild);
139
+ } else if (!isRunning && existingIndicator) {
140
+ existingIndicator.parentElement.remove();
141
+ }
142
+ }
143
+
144
+ createEventHeader(event, typeInfo) {
145
+ const header = document.createElement('div');
146
+ header.className = `
147
+ flex flex-wrap items-center justify-between p-4 cursor-pointer select-none
148
+ hover:bg-opacity-50 transition-colors duration-200 gap-2
149
+ `;
150
+
151
+ const leftContent = document.createElement('div');
152
+ leftContent.className = 'flex flex-wrap items-center gap-3 flex-1 min-w-0';
153
+
154
+ // Type indicator with improved styling and better wrapping
155
+ const typeIndicator = document.createElement('div');
156
+ typeIndicator.className = `
157
+ inline-flex items-center space-x-2 ${typeInfo.text}
158
+ px-2.5 py-1.5 rounded-full text-sm font-medium
159
+ ${typeInfo.bg} border border-${typeInfo.border.split('-')[1]}
160
+ break-normal
161
+ `;
162
+ typeIndicator.innerHTML = `
163
+ <span class="flex-shrink-0">${typeInfo.icon}</span>
164
+ <span class="font-medium truncate max-w-[200px]">${this.formatEventType(event.type || event.event)}</span>
165
+ `;
166
+
167
+ // Timestamp with improved layout
168
+ const timestamp = document.createElement('div');
169
+ timestamp.className = 'text-sm text-gray-500 flex items-center flex-shrink-0 whitespace-nowrap';
170
+ timestamp.innerHTML = `
171
+ <svg class="w-4 h-4 opacity-50 mr-1" fill="currentColor" viewBox="0 0 20 20">
172
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
173
+ </svg>
174
+ <span>${new Date(event.timestamp || Date.now()).toLocaleTimeString()}</span>
175
+ `;
176
+
177
+ leftContent.appendChild(typeIndicator);
178
+ leftContent.appendChild(timestamp);
179
+
180
+ // Expand icon with improved positioning
181
+ const expandIcon = document.createElement('div');
182
+ expandIcon.className = 'flex-shrink-0 ml-auto';
183
+ expandIcon.innerHTML = `
184
+ <svg class="w-5 h-5 expand-icon transform transition-transform duration-200 text-gray-400">
185
+ <path fill="currentColor" fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
186
+ </svg>
187
+ `;
188
+
189
+ header.appendChild(leftContent);
190
+ header.appendChild(expandIcon);
191
+
192
+ return header;
193
+ }
194
+
195
+ createEventContentWrapper(event, typeInfo) {
196
+ const wrapper = document.createElement('div');
197
+ wrapper.className = 'hidden transition-all duration-300 ease-in-out';
198
+
199
+ const content = document.createElement('div');
200
+ content.className = 'p-4 border-t border-gray-200';
201
+
202
+ // Add metadata section
203
+ const metadata = document.createElement('div');
204
+ metadata.className = 'flex items-center space-x-4 text-xs text-gray-500 mb-3';
205
+ metadata.innerHTML = `
206
+ <span class="flex items-center">
207
+ <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
208
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
209
+ </svg>
210
+ ${new Date(event.timestamp || Date.now()).toLocaleString()}
211
+ </span>
212
+ `;
213
+ content.appendChild(metadata);
214
+
215
+ const inner = this.createEventContent(event.data || event);
216
+ content.appendChild(inner);
217
+ wrapper.appendChild(content);
218
+
219
+ return wrapper;
220
+ }
221
+
222
+ createEventContent(data) {
223
+ const content = document.createElement('div');
224
+ content.className = 'space-y-3';
225
+
226
+ if (!data) {
227
+ const emptyState = document.createElement('div');
228
+ emptyState.className = 'text-center py-4';
229
+ emptyState.innerHTML = `
230
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
231
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
232
+ </svg>
233
+ <p class="mt-2 text-sm text-gray-500">No data available</p>
234
+ `;
235
+ content.appendChild(emptyState);
236
+ return content;
237
+ }
238
+
239
+ if (typeof data === 'object') {
240
+ Object.entries(data).forEach(([key, value]) => {
241
+ if (value !== null && value !== undefined) {
242
+ const pair = this.createKeyValuePair(key, value);
243
+ content.appendChild(pair);
244
+ }
245
+ });
246
+ } else {
247
+ const valueWrapper = document.createElement('div');
248
+ valueWrapper.className = 'prose prose-sm max-w-none';
249
+ valueWrapper.innerHTML = this.formatValue(data);
250
+ content.appendChild(valueWrapper);
251
+ }
252
+
253
+ // Add click handler for copy button if it exists
254
+ const copyBtn = content.querySelector('.copy-prompt-btn');
255
+ if (copyBtn) {
256
+ copyBtn.addEventListener('click', (e) => {
257
+ e.stopPropagation();
258
+ const promptText = data.current_prompt;
259
+ navigator.clipboard.writeText(promptText).then(() => {
260
+ const originalText = copyBtn.innerHTML;
261
+ copyBtn.innerHTML = `<svg class="h-3 w-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
262
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
263
+ </svg>Copied!`;
264
+ copyBtn.classList.remove('text-gray-600', 'bg-gray-100', 'hover:bg-gray-200');
265
+ copyBtn.classList.add('text-green-600', 'bg-green-100');
266
+ setTimeout(() => {
267
+ copyBtn.innerHTML = originalText;
268
+ copyBtn.classList.remove('text-green-600', 'bg-green-100');
269
+ copyBtn.classList.add('text-gray-600', 'bg-gray-100', 'hover:bg-gray-200');
270
+ }, 2000);
271
+ });
272
+ });
273
+ }
274
+
275
+ return content;
276
+ }
277
+
278
+ createKeyValuePair(key, value) {
279
+ const pair = document.createElement('div');
280
+ pair.className = 'flex flex-wrap rounded-md p-3 hover:bg-gray-50/50 transition-colors duration-150 gap-2';
281
+
282
+ const keyElement = document.createElement('div');
283
+ keyElement.className = 'w-full sm:w-1/4 font-medium text-gray-700';
284
+ keyElement.textContent = this.formatKey(key);
285
+
286
+ const valueElement = document.createElement('div');
287
+ valueElement.className = 'w-full sm:w-3/4 text-gray-900 break-words';
288
+ valueElement.innerHTML = this.formatValue(value);
289
+
290
+ pair.appendChild(keyElement);
291
+ pair.appendChild(valueElement);
292
+
293
+ return pair;
294
+ }
295
+
296
+ formatEventType(type) {
297
+ if (!type) return 'Unknown Event';
298
+ return type
299
+ .split('_')
300
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
301
+ .join(' ');
302
+ }
303
+
304
+ formatKey(key) {
305
+ if (!key) return '';
306
+ return key
307
+ .split('_')
308
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
309
+ .join(' ');
310
+ }
311
+
312
+ formatValue(value) {
313
+ // Handle null or undefined values
314
+ if (value === null || value === undefined) {
315
+ return `<span class="text-gray-500 italic">null</span>`;
316
+ }
317
+
318
+ // Handle complex objects
319
+ if (typeof value === 'object') {
320
+ return this.formatComplexObject(value);
321
+ }
322
+
323
+ // Convert value to string to ensure consistent handling
324
+ value = String(value);
325
+
326
+ // Long text or multi-line content: use pre-formatted block with syntax highlighting
327
+ if (value.includes('\n') || value.length > 100) {
328
+ return `
329
+ <div class="bg-gray-50 p-3 rounded-md border border-gray-200 overflow-x-auto">
330
+ <pre class="whitespace-pre-wrap break-words text-sm">
331
+ <code class="language-plaintext">${this.escapeHtml(value)}</code>
332
+ </pre>
333
+ </div>
334
+ `;
335
+ }
336
+
337
+ // Markdown content with code blocks or headers
338
+ if (value.includes('```') || value.includes('#')) {
339
+ // Use marked with advanced options for better rendering
340
+ const markedOptions = {
341
+ breaks: true,
342
+ gfm: true,
343
+ highlight: (code, lang) => {
344
+ try {
345
+ return hljs.highlightAuto(code, [lang]).value;
346
+ } catch {
347
+ return code;
348
+ }
349
+ }
350
+ };
351
+ marked.setOptions(markedOptions);
352
+ return `
353
+ <div class="markdown-content bg-white p-2 rounded-md">
354
+ ${marked.parse(value)}
355
+ </div>
356
+ `;
357
+ }
358
+
359
+ // URL detection with improved styling
360
+ if (value.match(/^https?:\/\/[^\s]+$/)) {
361
+ return `
362
+ <a href="${this.escapeHtml(value)}"
363
+ target="_blank"
364
+ rel="noopener noreferrer"
365
+ class="text-blue-600 hover:text-blue-800 hover:underline flex items-center">
366
+ <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
367
+ <path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
368
+ <path d="M5 5a2 2 0 00-2 2v8a2 2 0 110 4v-3a1 1 0 10-2 0v3a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L5 9.586V7a1 1 0 00-2 0v5a1 1 0 001 1h5v-4a1 1 0 00-1-1H5z"></path>
369
+ </svg>
370
+ ${this.escapeHtml(value)}
371
+ </a>
372
+ `;
373
+ }
374
+
375
+ // Default text rendering with safe HTML escaping
376
+ return `<span class="text-gray-800">${this.escapeHtml(value)}</span>`;
377
+ }
378
+
379
+ formatComplexObject(obj) {
380
+ if (Array.isArray(obj)) {
381
+ return `
382
+ <div class="bg-gray-50 p-3 rounded-md border border-gray-200">
383
+ <div class="flex flex-wrap items-center justify-between mb-2 gap-2">
384
+ <span class="text-sm font-medium text-gray-500">Array</span>
385
+ <span class="text-xs bg-gray-200 px-2 py-1 rounded-full">${obj.length} items</span>
386
+ </div>
387
+ <div class="space-y-2 divide-y divide-gray-100">
388
+ ${obj.map((item, index) => `
389
+ <div class="flex flex-wrap items-start pt-2 first:pt-0 gap-2">
390
+ <span class="text-xs font-mono bg-gray-200 px-1.5 py-0.5 rounded shrink-0">${index}</span>
391
+ <div class="flex-1 min-w-0 break-words">${this.formatValue(item)}</div>
392
+ </div>
393
+ `).join('')}
394
+ </div>
395
+ </div>
396
+ `;
397
+ }
398
+
399
+ const entries = Object.entries(obj);
400
+ if (entries.length === 0) {
401
+ return '<span class="text-gray-500 italic">empty object</span>';
402
+ }
403
+
404
+ return `
405
+ <div class="bg-gray-50 p-3 rounded-md border border-gray-200">
406
+ <div class="flex flex-wrap items-center justify-between mb-2 gap-2">
407
+ <span class="text-sm font-medium text-gray-500">Object</span>
408
+ <span class="text-xs bg-gray-200 px-2 py-1 rounded-full">${entries.length} properties</span>
409
+ </div>
410
+ <div class="space-y-2 divide-y divide-gray-100">
411
+ ${entries.map(([key, val]) => `
412
+ <div class="flex flex-wrap items-start pt-2 first:pt-0 gap-2">
413
+ <span class="text-xs font-medium text-gray-700 font-mono bg-gray-200 px-1.5 py-0.5 rounded shrink-0">${this.escapeHtml(key)}</span>
414
+ <div class="flex-1 min-w-0 break-words">${this.formatValue(val)}</div>
415
+ </div>
416
+ `).join('')}
417
+ </div>
418
+ </div>
419
+ `;
420
+ }
421
+
422
+ escapeHtml(unsafe) {
423
+ return unsafe
424
+ .replace(/&/g, "&amp;")
425
+ .replace(/</g, "&lt;")
426
+ .replace(/>/g, "&gt;")
427
+ .replace(/"/g, "&quot;")
428
+ .replace(/'/g, "&#039;");
429
+ }
430
+ }