dtSpark 1.0.4__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 (96) hide show
  1. dtSpark/__init__.py +0 -0
  2. dtSpark/_description.txt +1 -0
  3. dtSpark/_full_name.txt +1 -0
  4. dtSpark/_licence.txt +21 -0
  5. dtSpark/_metadata.yaml +6 -0
  6. dtSpark/_name.txt +1 -0
  7. dtSpark/_version.txt +1 -0
  8. dtSpark/aws/__init__.py +7 -0
  9. dtSpark/aws/authentication.py +296 -0
  10. dtSpark/aws/bedrock.py +578 -0
  11. dtSpark/aws/costs.py +318 -0
  12. dtSpark/aws/pricing.py +580 -0
  13. dtSpark/cli_interface.py +2645 -0
  14. dtSpark/conversation_manager.py +3050 -0
  15. dtSpark/core/__init__.py +12 -0
  16. dtSpark/core/application.py +3355 -0
  17. dtSpark/core/context_compaction.py +735 -0
  18. dtSpark/daemon/__init__.py +104 -0
  19. dtSpark/daemon/__main__.py +10 -0
  20. dtSpark/daemon/action_monitor.py +213 -0
  21. dtSpark/daemon/daemon_app.py +730 -0
  22. dtSpark/daemon/daemon_manager.py +289 -0
  23. dtSpark/daemon/execution_coordinator.py +194 -0
  24. dtSpark/daemon/pid_file.py +169 -0
  25. dtSpark/database/__init__.py +482 -0
  26. dtSpark/database/autonomous_actions.py +1191 -0
  27. dtSpark/database/backends.py +329 -0
  28. dtSpark/database/connection.py +122 -0
  29. dtSpark/database/conversations.py +520 -0
  30. dtSpark/database/credential_prompt.py +218 -0
  31. dtSpark/database/files.py +205 -0
  32. dtSpark/database/mcp_ops.py +355 -0
  33. dtSpark/database/messages.py +161 -0
  34. dtSpark/database/schema.py +673 -0
  35. dtSpark/database/tool_permissions.py +186 -0
  36. dtSpark/database/usage.py +167 -0
  37. dtSpark/files/__init__.py +4 -0
  38. dtSpark/files/manager.py +322 -0
  39. dtSpark/launch.py +39 -0
  40. dtSpark/limits/__init__.py +10 -0
  41. dtSpark/limits/costs.py +296 -0
  42. dtSpark/limits/tokens.py +342 -0
  43. dtSpark/llm/__init__.py +17 -0
  44. dtSpark/llm/anthropic_direct.py +446 -0
  45. dtSpark/llm/base.py +146 -0
  46. dtSpark/llm/context_limits.py +438 -0
  47. dtSpark/llm/manager.py +177 -0
  48. dtSpark/llm/ollama.py +578 -0
  49. dtSpark/mcp_integration/__init__.py +5 -0
  50. dtSpark/mcp_integration/manager.py +653 -0
  51. dtSpark/mcp_integration/tool_selector.py +225 -0
  52. dtSpark/resources/config.yaml.template +631 -0
  53. dtSpark/safety/__init__.py +22 -0
  54. dtSpark/safety/llm_service.py +111 -0
  55. dtSpark/safety/patterns.py +229 -0
  56. dtSpark/safety/prompt_inspector.py +442 -0
  57. dtSpark/safety/violation_logger.py +346 -0
  58. dtSpark/scheduler/__init__.py +20 -0
  59. dtSpark/scheduler/creation_tools.py +599 -0
  60. dtSpark/scheduler/execution_queue.py +159 -0
  61. dtSpark/scheduler/executor.py +1152 -0
  62. dtSpark/scheduler/manager.py +395 -0
  63. dtSpark/tools/__init__.py +4 -0
  64. dtSpark/tools/builtin.py +833 -0
  65. dtSpark/web/__init__.py +20 -0
  66. dtSpark/web/auth.py +152 -0
  67. dtSpark/web/dependencies.py +37 -0
  68. dtSpark/web/endpoints/__init__.py +17 -0
  69. dtSpark/web/endpoints/autonomous_actions.py +1125 -0
  70. dtSpark/web/endpoints/chat.py +621 -0
  71. dtSpark/web/endpoints/conversations.py +353 -0
  72. dtSpark/web/endpoints/main_menu.py +547 -0
  73. dtSpark/web/endpoints/streaming.py +421 -0
  74. dtSpark/web/server.py +578 -0
  75. dtSpark/web/session.py +167 -0
  76. dtSpark/web/ssl_utils.py +195 -0
  77. dtSpark/web/static/css/dark-theme.css +427 -0
  78. dtSpark/web/static/js/actions.js +1101 -0
  79. dtSpark/web/static/js/chat.js +614 -0
  80. dtSpark/web/static/js/main.js +496 -0
  81. dtSpark/web/static/js/sse-client.js +242 -0
  82. dtSpark/web/templates/actions.html +408 -0
  83. dtSpark/web/templates/base.html +93 -0
  84. dtSpark/web/templates/chat.html +814 -0
  85. dtSpark/web/templates/conversations.html +350 -0
  86. dtSpark/web/templates/goodbye.html +81 -0
  87. dtSpark/web/templates/login.html +90 -0
  88. dtSpark/web/templates/main_menu.html +983 -0
  89. dtSpark/web/templates/new_conversation.html +191 -0
  90. dtSpark/web/web_interface.py +137 -0
  91. dtspark-1.0.4.dist-info/METADATA +187 -0
  92. dtspark-1.0.4.dist-info/RECORD +96 -0
  93. dtspark-1.0.4.dist-info/WHEEL +5 -0
  94. dtspark-1.0.4.dist-info/entry_points.txt +3 -0
  95. dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
  96. dtspark-1.0.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,496 @@
1
+ /**
2
+ * Main JavaScript utilities for Spark web interface
3
+ *
4
+ * Provides common functions used across all pages
5
+ */
6
+
7
+ // =============================================================================
8
+ // MARKDOWN, CODE HIGHLIGHTING, AND MERMAID CONFIGURATION
9
+ // =============================================================================
10
+
11
+ /**
12
+ * Initialise Mermaid.js with dark theme
13
+ */
14
+ if (typeof mermaid !== 'undefined') {
15
+ mermaid.initialize({
16
+ startOnLoad: false, // We'll manually trigger rendering
17
+ theme: 'dark',
18
+ themeVariables: {
19
+ primaryColor: '#3b82f6',
20
+ primaryTextColor: '#e0e0e0',
21
+ primaryBorderColor: '#404040',
22
+ lineColor: '#606060',
23
+ secondaryColor: '#1e3a5f',
24
+ tertiaryColor: '#2a2a2a',
25
+ background: '#1a1a1a',
26
+ mainBkg: '#2a2a2a',
27
+ nodeBorder: '#404040',
28
+ clusterBkg: '#2a2a2a',
29
+ clusterBorder: '#404040',
30
+ titleColor: '#e0e0e0',
31
+ edgeLabelBackground: '#2a2a2a',
32
+ },
33
+ flowchart: {
34
+ useMaxWidth: true,
35
+ htmlLabels: true,
36
+ curve: 'basis'
37
+ },
38
+ sequence: {
39
+ useMaxWidth: true,
40
+ diagramMarginX: 50,
41
+ diagramMarginY: 10,
42
+ actorMargin: 50,
43
+ width: 150,
44
+ height: 65,
45
+ boxMargin: 10,
46
+ boxTextMargin: 5,
47
+ noteMargin: 10,
48
+ messageMargin: 35
49
+ }
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Configure Marked.js with highlight.js for code blocks
55
+ */
56
+ if (typeof marked !== 'undefined') {
57
+ // Create custom renderer for code blocks
58
+ const renderer = new marked.Renderer();
59
+
60
+ // Override code block rendering
61
+ // Note: marked.js v5+ passes a token object instead of separate parameters
62
+ renderer.code = function(tokenOrCode, language) {
63
+ // Handle both old API (code, language) and new API (token object)
64
+ let code, lang;
65
+ if (typeof tokenOrCode === 'object' && tokenOrCode !== null) {
66
+ // New marked.js API (v5+): receives token object
67
+ code = tokenOrCode.text || '';
68
+ lang = tokenOrCode.lang || '';
69
+ } else {
70
+ // Old marked.js API: receives separate parameters
71
+ code = tokenOrCode || '';
72
+ lang = language || '';
73
+ }
74
+
75
+ // Handle mermaid diagrams
76
+ if (lang === 'mermaid') {
77
+ const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
78
+ return `<div class="mermaid-container"><pre class="mermaid" id="${id}">${escapeHtmlForMermaid(code)}</pre></div>`;
79
+ }
80
+
81
+ // Use highlight.js for other code blocks
82
+ if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) {
83
+ try {
84
+ const highlighted = hljs.highlight(code, { language: lang, ignoreIllegals: true }).value;
85
+ return `<pre><code class="hljs language-${lang}">${highlighted}</code></pre>`;
86
+ } catch (e) {
87
+ console.warn('Highlight.js error:', e);
88
+ }
89
+ }
90
+
91
+ // Fallback: auto-detect language or plain text
92
+ if (typeof hljs !== 'undefined' && code) {
93
+ try {
94
+ const highlighted = hljs.highlightAuto(code).value;
95
+ return `<pre><code class="hljs">${highlighted}</code></pre>`;
96
+ } catch (e) {
97
+ console.warn('Highlight.js auto-detect error:', e);
98
+ }
99
+ }
100
+
101
+ // Final fallback: plain code block
102
+ return `<pre><code>${escapeHtml(code)}</code></pre>`;
103
+ };
104
+
105
+ // Configure marked options
106
+ marked.setOptions({
107
+ renderer: renderer,
108
+ gfm: true, // GitHub Flavoured Markdown
109
+ breaks: true, // Convert \n to <br>
110
+ pedantic: false,
111
+ smartLists: true,
112
+ smartypants: false,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Escape HTML for mermaid (preserve diagram syntax)
118
+ * @param {string} text - Text to escape
119
+ * @returns {string} Escaped text safe for mermaid
120
+ */
121
+ function escapeHtmlForMermaid(text) {
122
+ return text
123
+ .replace(/&/g, '&amp;')
124
+ .replace(/</g, '&lt;')
125
+ .replace(/>/g, '&gt;');
126
+ }
127
+
128
+ /**
129
+ * Copy SVG element to clipboard as PNG image
130
+ * @param {SVGElement} svgElement - The SVG element to copy
131
+ * @param {HTMLElement} button - The button element (for feedback)
132
+ */
133
+ async function copySvgToClipboard(svgElement, button) {
134
+ try {
135
+ // Get SVG dimensions
136
+ const svgRect = svgElement.getBoundingClientRect();
137
+ const width = Math.ceil(svgRect.width) || 800;
138
+ const height = Math.ceil(svgRect.height) || 600;
139
+
140
+ // Clone SVG and prepare for rendering
141
+ const svgClone = svgElement.cloneNode(true);
142
+
143
+ // Set explicit dimensions and viewBox
144
+ svgClone.setAttribute('width', width);
145
+ svgClone.setAttribute('height', height);
146
+ if (!svgClone.getAttribute('viewBox')) {
147
+ svgClone.setAttribute('viewBox', `0 0 ${width} ${height}`);
148
+ }
149
+
150
+ // Add xmlns if missing (required for data URL)
151
+ if (!svgClone.getAttribute('xmlns')) {
152
+ svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
153
+ }
154
+
155
+ // Add dark background for better visibility
156
+ const bgRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
157
+ bgRect.setAttribute('width', '100%');
158
+ bgRect.setAttribute('height', '100%');
159
+ bgRect.setAttribute('fill', '#1a1a1a');
160
+ svgClone.insertBefore(bgRect, svgClone.firstChild);
161
+
162
+ // Serialise SVG to string
163
+ const serializer = new XMLSerializer();
164
+ let svgString = serializer.serializeToString(svgClone);
165
+
166
+ // Encode SVG as base64 data URL (avoids tainted canvas issues)
167
+ const base64Svg = btoa(unescape(encodeURIComponent(svgString)));
168
+ const dataUrl = 'data:image/svg+xml;base64,' + base64Svg;
169
+
170
+ // Create canvas and draw image
171
+ const canvas = document.createElement('canvas');
172
+ const ctx = canvas.getContext('2d');
173
+ const img = new Image();
174
+
175
+ // Set up promise-based loading
176
+ await new Promise((resolve, reject) => {
177
+ img.onload = () => {
178
+ // Set canvas size with scale for better quality
179
+ const scale = 2;
180
+ canvas.width = width * scale;
181
+ canvas.height = height * scale;
182
+ ctx.scale(scale, scale);
183
+
184
+ // Draw image on canvas
185
+ ctx.drawImage(img, 0, 0, width, height);
186
+ resolve();
187
+ };
188
+
189
+ img.onerror = (e) => {
190
+ reject(new Error('Failed to load SVG image'));
191
+ };
192
+
193
+ img.src = dataUrl;
194
+ });
195
+
196
+ // Convert to blob and copy to clipboard
197
+ const blob = await new Promise((resolve) => {
198
+ canvas.toBlob(resolve, 'image/png');
199
+ });
200
+
201
+ if (!blob) {
202
+ throw new Error('Failed to create image blob');
203
+ }
204
+
205
+ try {
206
+ await navigator.clipboard.write([
207
+ new ClipboardItem({ 'image/png': blob })
208
+ ]);
209
+
210
+ // Show success feedback
211
+ if (button) {
212
+ const icon = button.querySelector('i');
213
+ if (icon) {
214
+ icon.className = 'bi bi-check-circle-fill text-success';
215
+ setTimeout(() => {
216
+ icon.className = 'bi bi-clipboard';
217
+ }, 2000);
218
+ }
219
+ }
220
+ showToast('Diagram copied to clipboard', 'success');
221
+ } catch (clipboardError) {
222
+ console.error('Clipboard write failed:', clipboardError);
223
+ // Fallback: offer download
224
+ downloadDiagramAsPng(canvas, 'diagram.png');
225
+ showToast('Clipboard not available - downloading image instead', 'info');
226
+ }
227
+
228
+ } catch (e) {
229
+ console.error('Error copying diagram:', e);
230
+ showToast('Failed to copy diagram: ' + e.message, 'error');
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Download canvas as PNG file (fallback when clipboard not available)
236
+ * @param {HTMLCanvasElement} canvas - Canvas element
237
+ * @param {string} filename - Download filename
238
+ */
239
+ function downloadDiagramAsPng(canvas, filename) {
240
+ const link = document.createElement('a');
241
+ link.download = filename;
242
+ link.href = canvas.toDataURL('image/png');
243
+ link.click();
244
+ }
245
+
246
+ /**
247
+ * Render all mermaid diagrams in a container
248
+ * @param {HTMLElement} container - Container element to search for mermaid blocks
249
+ */
250
+ async function renderMermaidDiagrams(container) {
251
+ if (typeof mermaid === 'undefined') return;
252
+
253
+ const mermaidBlocks = container.querySelectorAll('pre.mermaid');
254
+ if (mermaidBlocks.length === 0) return;
255
+
256
+ for (const block of mermaidBlocks) {
257
+ try {
258
+ const id = block.id || 'mermaid-' + Math.random().toString(36).substr(2, 9);
259
+ const code = block.textContent;
260
+
261
+ // Render the diagram
262
+ const { svg } = await mermaid.render(id + '-svg', code);
263
+
264
+ // Create wrapper with copy button
265
+ const wrapper = document.createElement('div');
266
+ wrapper.className = 'mermaid-diagram';
267
+
268
+ // Add copy button
269
+ const copyBtn = document.createElement('button');
270
+ copyBtn.className = 'diagram-copy-btn btn btn-sm';
271
+ copyBtn.title = 'Copy diagram to clipboard';
272
+ copyBtn.innerHTML = '<i class="bi bi-clipboard"></i>';
273
+
274
+ // Add SVG content
275
+ const svgContainer = document.createElement('div');
276
+ svgContainer.className = 'mermaid-svg-container';
277
+ svgContainer.innerHTML = svg;
278
+
279
+ wrapper.appendChild(copyBtn);
280
+ wrapper.appendChild(svgContainer);
281
+
282
+ // Add click handler for copy button
283
+ copyBtn.onclick = () => {
284
+ const svgElement = svgContainer.querySelector('svg');
285
+ if (svgElement) {
286
+ copySvgToClipboard(svgElement, copyBtn);
287
+ }
288
+ };
289
+
290
+ block.parentNode.replaceChild(wrapper, block);
291
+ } catch (e) {
292
+ console.error('Mermaid rendering error:', e);
293
+ // Show error message in the block
294
+ block.classList.add('mermaid-error');
295
+ block.innerHTML = `<span class="text-danger">Diagram rendering error: ${escapeHtml(e.message || 'Unknown error')}</span>\n\n${block.textContent}`;
296
+ }
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Parse markdown and render with syntax highlighting and mermaid support
302
+ * @param {string} content - Markdown content to parse
303
+ * @param {HTMLElement} targetElement - Element to render into
304
+ */
305
+ async function renderMarkdown(content, targetElement) {
306
+ if (typeof marked === 'undefined') {
307
+ targetElement.textContent = content;
308
+ return;
309
+ }
310
+
311
+ // Parse markdown
312
+ targetElement.innerHTML = marked.parse(content);
313
+
314
+ // Render mermaid diagrams
315
+ await renderMermaidDiagrams(targetElement);
316
+ }
317
+
318
+ // =============================================================================
319
+ // GENERAL UTILITIES
320
+ // =============================================================================
321
+
322
+ /**
323
+ * Format a timestamp to local date/time string
324
+ * @param {string|Date} timestamp - The timestamp to format
325
+ * @returns {string} Formatted date/time string
326
+ */
327
+ function formatTimestamp(timestamp) {
328
+ const date = new Date(timestamp);
329
+ return date.toLocaleString();
330
+ }
331
+
332
+ /**
333
+ * Format a number with thousands separators
334
+ * @param {number} num - The number to format
335
+ * @returns {string} Formatted number string
336
+ */
337
+ function formatNumber(num) {
338
+ return num.toLocaleString();
339
+ }
340
+
341
+ /**
342
+ * Truncate text to a maximum length
343
+ * @param {string} text - The text to truncate
344
+ * @param {number} maxLength - Maximum length
345
+ * @returns {string} Truncated text with ellipsis if needed
346
+ */
347
+ function truncateText(text, maxLength = 100) {
348
+ if (text.length <= maxLength) {
349
+ return text;
350
+ }
351
+ return text.substring(0, maxLength) + '...';
352
+ }
353
+
354
+ /**
355
+ * Show a toast notification
356
+ * @param {string} message - The message to display
357
+ * @param {string} type - Toast type (success, error, warning, info)
358
+ */
359
+ function showToast(message, type = 'info') {
360
+ // Create toast container if it doesn't exist
361
+ let container = document.getElementById('toast-container');
362
+ if (!container) {
363
+ container = document.createElement('div');
364
+ container.id = 'toast-container';
365
+ container.className = 'position-fixed bottom-0 end-0 p-3';
366
+ container.style.zIndex = '11';
367
+ document.body.appendChild(container);
368
+ }
369
+
370
+ // Create toast
371
+ const toast = document.createElement('div');
372
+ toast.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0`;
373
+ toast.setAttribute('role', 'alert');
374
+ toast.setAttribute('aria-live', 'assertive');
375
+ toast.setAttribute('aria-atomic', 'true');
376
+
377
+ toast.innerHTML = `
378
+ <div class="d-flex">
379
+ <div class="toast-body">
380
+ ${message}
381
+ </div>
382
+ <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
383
+ </div>
384
+ `;
385
+
386
+ container.appendChild(toast);
387
+
388
+ const bsToast = new bootstrap.Toast(toast);
389
+ bsToast.show();
390
+
391
+ // Remove toast element after it's hidden
392
+ toast.addEventListener('hidden.bs.toast', () => {
393
+ toast.remove();
394
+ });
395
+ }
396
+
397
+ /**
398
+ * Escape HTML to prevent XSS
399
+ * @param {string} text - Text to escape
400
+ * @returns {string} Escaped text
401
+ */
402
+ function escapeHtml(text) {
403
+ const div = document.createElement('div');
404
+ div.textContent = text;
405
+ return div.innerHTML;
406
+ }
407
+
408
+ /**
409
+ * Copy text to clipboard
410
+ * @param {string} text - Text to copy
411
+ */
412
+ async function copyToClipboard(text) {
413
+ try {
414
+ await navigator.clipboard.writeText(text);
415
+ showToast('Copied to clipboard', 'success');
416
+ } catch (err) {
417
+ showToast('Failed to copy to clipboard', 'error');
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Download text as a file
423
+ * @param {string} content - File content
424
+ * @param {string} filename - File name
425
+ * @param {string} mimeType - MIME type
426
+ */
427
+ function downloadFile(content, filename, mimeType = 'text/plain') {
428
+ const blob = new Blob([content], { type: mimeType });
429
+ const url = URL.createObjectURL(blob);
430
+ const link = document.createElement('a');
431
+ link.href = url;
432
+ link.download = filename;
433
+ document.body.appendChild(link);
434
+ link.click();
435
+ document.body.removeChild(link);
436
+ URL.revokeObjectURL(url);
437
+ }
438
+
439
+ /**
440
+ * Make an API request with error handling
441
+ * @param {string} url - API endpoint URL
442
+ * @param {object} options - Fetch options
443
+ * @returns {Promise<any>} Response data
444
+ */
445
+ async function apiRequest(url, options = {}) {
446
+ try {
447
+ const response = await fetch(url, {
448
+ ...options,
449
+ headers: {
450
+ 'Content-Type': 'application/json',
451
+ ...options.headers,
452
+ },
453
+ });
454
+
455
+ if (!response.ok) {
456
+ const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
457
+ throw new Error(error.detail || `HTTP ${response.status}`);
458
+ }
459
+
460
+ return await response.json();
461
+ } catch (error) {
462
+ console.error('API request failed:', error);
463
+ throw error;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Debounce function to limit function call frequency
469
+ * @param {Function} func - Function to debounce
470
+ * @param {number} wait - Wait time in milliseconds
471
+ * @returns {Function} Debounced function
472
+ */
473
+ function debounce(func, wait) {
474
+ let timeout;
475
+ return function executedFunction(...args) {
476
+ const later = () => {
477
+ clearTimeout(timeout);
478
+ func(...args);
479
+ };
480
+ clearTimeout(timeout);
481
+ timeout = setTimeout(later, wait);
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Format file size to human-readable string
487
+ * @param {number} bytes - File size in bytes
488
+ * @returns {string} Formatted file size
489
+ */
490
+ function formatFileSize(bytes) {
491
+ if (bytes === 0) return '0 Bytes';
492
+ const k = 1024;
493
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
494
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
495
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
496
+ }