dtSpark 1.1.0a3__py3-none-any.whl → 1.1.0a6__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 (54) hide show
  1. dtSpark/_version.txt +1 -1
  2. dtSpark/aws/authentication.py +1 -1
  3. dtSpark/aws/bedrock.py +238 -239
  4. dtSpark/aws/costs.py +9 -5
  5. dtSpark/aws/pricing.py +25 -21
  6. dtSpark/cli_interface.py +69 -62
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +112 -91
  9. dtSpark/core/context_compaction.py +241 -226
  10. dtSpark/daemon/__init__.py +36 -22
  11. dtSpark/daemon/action_monitor.py +46 -17
  12. dtSpark/daemon/daemon_app.py +126 -104
  13. dtSpark/daemon/daemon_manager.py +59 -23
  14. dtSpark/daemon/pid_file.py +3 -2
  15. dtSpark/database/autonomous_actions.py +3 -0
  16. dtSpark/database/credential_prompt.py +52 -54
  17. dtSpark/files/manager.py +6 -12
  18. dtSpark/limits/__init__.py +1 -1
  19. dtSpark/limits/tokens.py +2 -2
  20. dtSpark/llm/anthropic_direct.py +246 -141
  21. dtSpark/llm/ollama.py +3 -1
  22. dtSpark/mcp_integration/manager.py +4 -4
  23. dtSpark/mcp_integration/tool_selector.py +83 -77
  24. dtSpark/resources/config.yaml.template +10 -0
  25. dtSpark/safety/patterns.py +45 -46
  26. dtSpark/safety/prompt_inspector.py +8 -1
  27. dtSpark/scheduler/creation_tools.py +273 -181
  28. dtSpark/scheduler/executor.py +503 -221
  29. dtSpark/tools/builtin.py +70 -53
  30. dtSpark/web/endpoints/autonomous_actions.py +12 -9
  31. dtSpark/web/endpoints/chat.py +8 -6
  32. dtSpark/web/endpoints/conversations.py +11 -9
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +65 -5
  36. dtSpark/web/ssl_utils.py +3 -3
  37. dtSpark/web/static/css/dark-theme.css +8 -29
  38. dtSpark/web/static/js/chat.js +6 -8
  39. dtSpark/web/static/js/main.js +8 -8
  40. dtSpark/web/static/js/sse-client.js +130 -122
  41. dtSpark/web/templates/actions.html +5 -5
  42. dtSpark/web/templates/base.html +13 -0
  43. dtSpark/web/templates/chat.html +10 -10
  44. dtSpark/web/templates/conversations.html +2 -2
  45. dtSpark/web/templates/goodbye.html +2 -2
  46. dtSpark/web/templates/main_menu.html +17 -17
  47. dtSpark/web/web_interface.py +2 -2
  48. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/METADATA +9 -2
  49. dtspark-1.1.0a6.dist-info/RECORD +96 -0
  50. dtspark-1.1.0a3.dist-info/RECORD +0 -96
  51. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/WHEEL +0 -0
  52. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/entry_points.txt +0 -0
  53. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/licenses/LICENSE +0 -0
  54. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/top_level.txt +0 -0
@@ -17,151 +17,159 @@ async function sendMessageWithSSE(conversationId, message) {
17
17
  let streamingMessageElement = null;
18
18
  let accumulatedContent = '';
19
19
 
20
- try {
21
- // Create EventSource for SSE
22
- const encodedMessage = encodeURIComponent(message);
23
- const eventSource = new EventSource(
24
- `/api/stream/chat?message=${encodedMessage}&conversation_id=${conversationId}`
25
- );
26
-
27
- // Handle different event types
28
- eventSource.addEventListener('status', (event) => {
29
- const data = JSON.parse(event.data);
30
- console.log('Status:', data);
31
-
32
- // Update typing indicator with status
33
- if (typingIndicator && typingIndicator.parentNode) {
34
- const statusText = data.message || '';
35
- typingIndicator.querySelector('.visually-hidden').nextSibling.textContent = statusText;
36
- }
37
- });
20
+ return new Promise((resolve) => {
21
+ try {
22
+ // Create EventSource for SSE
23
+ const encodedMessage = encodeURIComponent(message);
24
+ const eventSource = new EventSource(
25
+ `/api/stream/chat?message=${encodedMessage}&conversation_id=${conversationId}`
26
+ );
27
+
28
+ // Handle different event types
29
+ eventSource.addEventListener('status', (event) => {
30
+ const data = JSON.parse(event.data);
31
+ console.log('Status:', data);
32
+
33
+ // Update typing indicator with status
34
+ if (typingIndicator && typingIndicator.parentNode) {
35
+ const statusText = data.message || '';
36
+ typingIndicator.querySelector('.visually-hidden').nextSibling.textContent = statusText;
37
+ }
38
+ });
38
39
 
39
- eventSource.addEventListener('response', (event) => {
40
- const data = JSON.parse(event.data);
41
- console.log('Received response event:', data);
40
+ eventSource.addEventListener('response', (event) => {
41
+ const data = JSON.parse(event.data);
42
+ console.log('Received response event:', data);
42
43
 
43
- // Hide typing indicator on first response
44
- if (typingIndicator && typingIndicator.parentNode) {
45
- console.log('Hiding typing indicator');
46
- hideTypingIndicator();
47
- typingIndicator = null; // Clear reference after hiding
48
- }
44
+ // Hide typing indicator on first response
45
+ if (typingIndicator && typingIndicator.parentNode) {
46
+ console.log('Hiding typing indicator');
47
+ hideTypingIndicator();
48
+ typingIndicator = null; // Clear reference after hiding
49
+ }
49
50
 
50
- if (data.type === 'text') {
51
- if (data.final) {
52
- // Final response - this is the main assistant response after tool execution
53
- console.log('Final response received, content length:', data.content ? data.content.length : 0);
54
- accumulatedContent += data.content;
55
- console.log('Accumulated content:', accumulatedContent);
56
-
57
- // Ensure we have content before displaying
58
- if (accumulatedContent.trim().length > 0) {
59
- streamingMessageElement = updateStreamingMessage(
60
- accumulatedContent,
61
- streamingMessageElement
62
- );
63
- console.log('Updated streaming message element:', streamingMessageElement);
51
+ if (data.type === 'text') {
52
+ if (data.final) {
53
+ // Final response - this is the main assistant response after tool execution
54
+ console.log('Final response received, content length:', data.content ? data.content.length : 0);
55
+ accumulatedContent += data.content;
56
+ console.log('Accumulated content:', accumulatedContent);
57
+
58
+ // Ensure we have content before displaying
59
+ if (accumulatedContent.trim().length > 0) {
60
+ streamingMessageElement = updateStreamingMessage(
61
+ accumulatedContent,
62
+ streamingMessageElement
63
+ );
64
+ console.log('Updated streaming message element:', streamingMessageElement);
65
+ } else {
66
+ console.warn('No content to display');
67
+ }
68
+ eventSource.close();
69
+ resolve();
64
70
  } else {
65
- console.warn('No content to display');
71
+ // Non-final response - text that appears with tool calls
72
+ // Display immediately as a separate message
73
+ console.log('Non-final response, appending:', data.content);
74
+ appendMessage('assistant', data.content);
66
75
  }
67
- eventSource.close();
68
- } else {
69
- // Non-final response - text that appears with tool calls
70
- // Display immediately as a separate message
71
- console.log('Non-final response, appending:', data.content);
72
- appendMessage('assistant', data.content);
73
76
  }
74
- }
75
- });
76
-
77
- eventSource.addEventListener('tool_start', (event) => {
78
- const data = JSON.parse(event.data);
79
- appendToolCall(data.tool_name, data.input);
80
- });
81
-
82
- eventSource.addEventListener('tool_complete', (event) => {
83
- const data = JSON.parse(event.data);
84
- // Display tool result
85
- appendToolResult(data.tool_use_id, { content: data.content });
86
- });
77
+ });
78
+
79
+ eventSource.addEventListener('tool_start', (event) => {
80
+ const data = JSON.parse(event.data);
81
+ appendToolCall(data.tool_name, data.input);
82
+ });
83
+
84
+ eventSource.addEventListener('tool_complete', (event) => {
85
+ const data = JSON.parse(event.data);
86
+ // Display tool result
87
+ appendToolResult(data.tool_use_id, { content: data.content });
88
+ });
89
+
90
+ eventSource.addEventListener('tool_error', (event) => {
91
+ const data = JSON.parse(event.data);
92
+ appendToolResult(data.tool_name, { error: data.error });
93
+ });
94
+
95
+ eventSource.addEventListener('permission_request', (event) => {
96
+ const data = JSON.parse(event.data);
97
+ console.log('Permission request received:', data);
98
+
99
+ // Show permission modal/dialog
100
+ showToolPermissionDialog(data.request_id, data.tool_name, data.tool_description);
101
+ });
102
+
103
+ eventSource.addEventListener('progress', (event) => {
104
+ const data = JSON.parse(event.data);
105
+ // Update progress (if we want to show a progress bar)
106
+ console.log('Progress:', data);
107
+ });
108
+
109
+ eventSource.addEventListener('complete', (event) => {
110
+ // Stream complete
111
+ console.log('Stream complete');
112
+ eventSource.close();
87
113
 
88
- eventSource.addEventListener('tool_error', (event) => {
89
- const data = JSON.parse(event.data);
90
- appendToolResult(data.tool_name, { error: data.error });
91
- });
114
+ // Hide typing indicator if still visible
115
+ if (typingIndicator && typingIndicator.parentNode) {
116
+ console.log('Hiding typing indicator on complete');
117
+ hideTypingIndicator();
118
+ typingIndicator = null;
119
+ }
92
120
 
93
- eventSource.addEventListener('permission_request', (event) => {
94
- const data = JSON.parse(event.data);
95
- console.log('Permission request received:', data);
121
+ resolve();
122
+ });
96
123
 
97
- // Show permission modal/dialog
98
- showToolPermissionDialog(data.request_id, data.tool_name, data.tool_description);
99
- });
124
+ eventSource.addEventListener('error', (event) => {
125
+ console.error('SSE error:', event);
126
+ const data = JSON.parse(event.data);
100
127
 
101
- eventSource.addEventListener('progress', (event) => {
102
- const data = JSON.parse(event.data);
103
- // Update progress (if we want to show a progress bar)
104
- console.log('Progress:', data);
105
- });
128
+ // Hide typing indicator
129
+ if (typingIndicator && typingIndicator.parentNode) {
130
+ console.log('Hiding typing indicator on error');
131
+ hideTypingIndicator();
132
+ typingIndicator = null;
133
+ }
106
134
 
107
- eventSource.addEventListener('complete', (event) => {
108
- // Stream complete
109
- console.log('Stream complete');
110
- eventSource.close();
135
+ // Show error message
136
+ appendMessage('system', `Error: ${data.message}`);
111
137
 
112
- // Hide typing indicator if still visible
113
- if (typingIndicator && typingIndicator.parentNode) {
114
- console.log('Hiding typing indicator on complete');
115
- hideTypingIndicator();
116
- typingIndicator = null;
117
- }
118
- });
138
+ eventSource.close();
139
+ resolve();
140
+ });
119
141
 
120
- eventSource.addEventListener('error', (event) => {
121
- console.error('SSE error:', event);
122
- const data = JSON.parse(event.data);
142
+ eventSource.onerror = (error) => {
143
+ console.error('EventSource failed:', error);
123
144
 
124
- // Hide typing indicator
125
- if (typingIndicator && typingIndicator.parentNode) {
126
- console.log('Hiding typing indicator on error');
127
- hideTypingIndicator();
128
- typingIndicator = null;
129
- }
145
+ // Hide typing indicator
146
+ if (typingIndicator && typingIndicator.parentNode) {
147
+ console.log('Hiding typing indicator on connection error');
148
+ hideTypingIndicator();
149
+ typingIndicator = null;
150
+ }
130
151
 
131
- // Show error message
132
- appendMessage('system', `Error: ${data.message}`);
152
+ // If we haven't received any content, show error
153
+ if (!streamingMessageElement) {
154
+ appendMessage('system', 'Connection error. Please try again.');
155
+ }
133
156
 
134
- eventSource.close();
135
- });
157
+ eventSource.close();
158
+ resolve();
159
+ };
136
160
 
137
- eventSource.onerror = (error) => {
138
- console.error('EventSource failed:', error);
161
+ } catch (error) {
162
+ console.error('Error sending message:', error);
139
163
 
140
164
  // Hide typing indicator
141
- if (typingIndicator && typingIndicator.parentNode) {
142
- console.log('Hiding typing indicator on connection error');
165
+ if (typingIndicator) {
143
166
  hideTypingIndicator();
144
- typingIndicator = null;
145
- }
146
-
147
- // If we haven't received any content, show error
148
- if (!streamingMessageElement) {
149
- appendMessage('system', 'Connection error. Please try again.');
150
167
  }
151
168
 
152
- eventSource.close();
153
- };
154
-
155
- } catch (error) {
156
- console.error('Error sending message:', error);
157
-
158
- // Hide typing indicator
159
- if (typingIndicator) {
160
- hideTypingIndicator();
169
+ showToast('Failed to send message', 'error');
170
+ resolve();
161
171
  }
162
-
163
- showToast('Failed to send message', 'error');
164
- }
172
+ });
165
173
  }
166
174
 
167
175
  /**
@@ -53,7 +53,7 @@
53
53
  <tbody id="actions-tbody">
54
54
  <tr>
55
55
  <td colspan="9" class="text-center text-muted py-4">
56
- <div class="spinner-border spinner-border-sm" role="status"></div>
56
+ <output class="spinner-border spinner-border-sm"></output>
57
57
  Loading actions...
58
58
  </td>
59
59
  </tr>
@@ -85,7 +85,7 @@
85
85
  <tbody id="runs-tbody">
86
86
  <tr>
87
87
  <td colspan="7" class="text-center text-muted py-4">
88
- <div class="spinner-border spinner-border-sm" role="status"></div>
88
+ <output class="spinner-border spinner-border-sm"></output>
89
89
  Loading recent runs...
90
90
  </td>
91
91
  </tr>
@@ -172,7 +172,7 @@
172
172
 
173
173
  <!-- Tool Permissions -->
174
174
  <div class="mb-3">
175
- <label class="form-label">Tool Permissions</label>
175
+ <span class="form-label d-block">Tool Permissions</span>
176
176
  <div class="card">
177
177
  <div class="card-body" style="max-height: 200px; overflow-y: auto;">
178
178
  <div id="toolPermissions">
@@ -374,14 +374,14 @@
374
374
  <div class="alert alert-success mb-0">
375
375
  <i class="bi bi-check-circle-fill"></i>
376
376
  Action created successfully!
377
- <a href="#" id="viewCreatedAction" onclick="viewCreatedAction(); return false;">View action details</a>
377
+ <button type="button" id="viewCreatedAction" class="btn btn-link p-0 align-baseline" onclick="viewCreatedAction();">View action details</button>
378
378
  </div>
379
379
  </div>
380
380
  </div>
381
381
 
382
382
  <!-- Loading Indicator -->
383
383
  <div id="aiCreateLoading" class="d-none text-center py-4">
384
- <div class="spinner-border text-primary" role="status"></div>
384
+ <output class="spinner-border text-primary"></output>
385
385
  <p class="mt-2 text-muted">AI is thinking...</p>
386
386
  </div>
387
387
  </div>
@@ -53,11 +53,13 @@
53
53
  <i class="bi bi-plus-circle"></i> New
54
54
  </a>
55
55
  </li>
56
+ {% if actions_enabled %}
56
57
  <li class="nav-item">
57
58
  <a class="nav-link" href="/actions">
58
59
  <i class="bi bi-robot"></i> Actions
59
60
  </a>
60
61
  </li>
62
+ {% endif %}
61
63
  </ul>
62
64
  <ul class="navbar-nav">
63
65
  <li class="nav-item">
@@ -89,5 +91,16 @@
89
91
  <script src="/static/js/main.js"></script>
90
92
 
91
93
  {% block extra_scripts %}{% endblock %}
94
+
95
+ {% if heartbeat_enabled %}
96
+ <!-- Browser heartbeat - auto-shutdown when browser closes -->
97
+ <script>
98
+ setInterval(() => {
99
+ fetch('/api/heartbeat', { method: 'POST' }).catch(() => {});
100
+ }, {{ heartbeat_interval_ms }});
101
+ // Send initial heartbeat immediately
102
+ fetch('/api/heartbeat', { method: 'POST' }).catch(() => {});
103
+ </script>
104
+ {% endif %}
92
105
  </body>
93
106
  </html>
@@ -21,9 +21,9 @@
21
21
  <div class="card-body overflow-auto" id="chat-messages">
22
22
  <!-- Messages will be appended here -->
23
23
  <div class="text-center text-muted">
24
- <div class="spinner-border spinner-border-sm" role="status">
24
+ <output class="spinner-border spinner-border-sm">
25
25
  <span class="visually-hidden">Loading...</span>
26
- </div>
26
+ </output>
27
27
  <span class="ms-2">Loading chat history...</span>
28
28
  </div>
29
29
  </div>
@@ -48,14 +48,14 @@
48
48
  ></textarea>
49
49
  </div>
50
50
  <div class="d-flex justify-content-between align-items-center">
51
- <div class="btn-group" role="group">
51
+ <fieldset class="btn-group">
52
52
  <button type="button" class="btn btn-outline-secondary btn-sm" onclick="showCommandMenu()">
53
53
  <i class="bi bi-terminal"></i> Commands
54
54
  </button>
55
55
  <button type="button" class="btn btn-outline-secondary btn-sm" onclick="attachFiles()">
56
56
  <i class="bi bi-paperclip"></i> Attach
57
57
  </button>
58
- </div>
58
+ </fieldset>
59
59
  <button type="submit" class="btn btn-primary" id="send-btn">
60
60
  <i class="bi bi-send-fill"></i> Send
61
61
  </button>
@@ -76,9 +76,9 @@
76
76
  </div>
77
77
  <div class="modal-body" id="info-content">
78
78
  <div class="text-center">
79
- <div class="spinner-border spinner-border-sm" role="status">
79
+ <output class="spinner-border spinner-border-sm">
80
80
  <span class="visually-hidden">Loading...</span>
81
- </div>
81
+ </output>
82
82
  </div>
83
83
  </div>
84
84
  </div>
@@ -432,7 +432,7 @@ async function showMCPAuditModal() {
432
432
  </div>
433
433
  <div class="modal-body" id="mcp-audit-content">
434
434
  <div class="text-center">
435
- <div class="spinner-border spinner-border-sm" role="status"></div>
435
+ <output class="spinner-border spinner-border-sm"></output>
436
436
  </div>
437
437
  </div>
438
438
  </div>
@@ -517,7 +517,7 @@ async function showMCPServersModal() {
517
517
  </div>
518
518
  <div class="modal-body" id="mcp-servers-content">
519
519
  <div class="text-center">
520
- <div class="spinner-border spinner-border-sm" role="status"></div>
520
+ <output class="spinner-border spinner-border-sm"></output>
521
521
  </div>
522
522
  </div>
523
523
  </div>
@@ -611,9 +611,9 @@ async function loadConversationInfo() {
611
611
  try {
612
612
  document.getElementById('info-content').innerHTML = `
613
613
  <div class="text-center">
614
- <div class="spinner-border spinner-border-sm" role="status">
614
+ <output class="spinner-border spinner-border-sm">
615
615
  <span class="visually-hidden">Loading...</span>
616
- </div>
616
+ </output>
617
617
  </div>
618
618
  `;
619
619
 
@@ -32,9 +32,9 @@
32
32
  <div class="card">
33
33
  <div class="card-body" id="conversations-list">
34
34
  <div class="text-center">
35
- <div class="spinner-border text-primary" role="status">
35
+ <output class="spinner-border text-primary">
36
36
  <span class="visually-hidden">Loading...</span>
37
- </div>
37
+ </output>
38
38
  <p class="mt-2">Loading conversations...</p>
39
39
  </div>
40
40
  </div>
@@ -27,9 +27,9 @@
27
27
  <div class="alert alert-info">
28
28
  <i class="bi bi-info-circle-fill"></i> The web server is shutting down...
29
29
  <div class="mt-2">
30
- <div class="spinner-border spinner-border-sm" role="status">
30
+ <output class="spinner-border spinner-border-sm">
31
31
  <span class="visually-hidden">Shutting down...</span>
32
- </div>
32
+ </output>
33
33
  <span class="ms-2">Please wait</span>
34
34
  </div>
35
35
  </div>
@@ -166,28 +166,28 @@
166
166
  </div>
167
167
 
168
168
  <!-- Tab Navigation -->
169
- <ul class="nav nav-tabs mb-4" id="mainTabs" role="tablist">
170
- <li class="nav-item" role="presentation">
169
+ <div class="nav nav-tabs mb-4" id="mainTabs" role="tablist">
170
+ <div class="nav-item">
171
171
  <button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview" type="button" role="tab">
172
172
  <i class="bi bi-speedometer2"></i> Overview
173
173
  </button>
174
- </li>
175
- <li class="nav-item" role="presentation">
174
+ </div>
175
+ <div class="nav-item">
176
176
  <button class="nav-link" id="llms-tab" data-bs-toggle="tab" data-bs-target="#llms" type="button" role="tab">
177
177
  <i class="bi bi-cpu"></i> LLMs
178
178
  </button>
179
- </li>
180
- <li class="nav-item" role="presentation">
179
+ </div>
180
+ <div class="nav-item">
181
181
  <button class="nav-link" id="integrations-tab" data-bs-toggle="tab" data-bs-target="#integrations" type="button" role="tab">
182
182
  <i class="bi bi-plug"></i> Integrations
183
183
  </button>
184
- </li>
185
- <li class="nav-item" role="presentation">
184
+ </div>
185
+ <div class="nav-item">
186
186
  <button class="nav-link" id="system-tab" data-bs-toggle="tab" data-bs-target="#system" type="button" role="tab">
187
187
  <i class="bi bi-gear"></i> System
188
188
  </button>
189
- </li>
190
- </ul>
189
+ </div>
190
+ </div>
191
191
 
192
192
  <!-- Tab Content -->
193
193
  <div class="tab-content" id="mainTabContent">
@@ -202,7 +202,7 @@
202
202
  <h5 class="card-title mb-3"><i class="bi bi-cpu"></i> LLM Providers</h5>
203
203
  <div id="llm-providers-overview" class="d-flex justify-content-around flex-wrap gap-2">
204
204
  <div class="text-center py-3">
205
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
205
+ <output class="spinner-border spinner-border-sm text-primary"></output>
206
206
  <span class="ms-2">Loading providers...</span>
207
207
  </div>
208
208
  </div>
@@ -258,7 +258,7 @@
258
258
  <!-- Provider Selector Icons -->
259
259
  <div class="provider-selector" id="provider-selector">
260
260
  <div class="text-center py-3 w-100">
261
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
261
+ <output class="spinner-border spinner-border-sm text-primary"></output>
262
262
  <span class="ms-2">Loading providers...</span>
263
263
  </div>
264
264
  </div>
@@ -283,7 +283,7 @@
283
283
  </div>
284
284
  <div class="card-body p-0" id="tool-sources-list">
285
285
  <div class="text-center py-4">
286
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
286
+ <output class="spinner-border spinner-border-sm text-primary"></output>
287
287
  <span class="ms-2">Loading tools...</span>
288
288
  </div>
289
289
  </div>
@@ -317,7 +317,7 @@
317
317
  </div>
318
318
  <div class="card-body" id="user-identity">
319
319
  <div class="text-center py-3">
320
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
320
+ <output class="spinner-border spinner-border-sm text-primary"></output>
321
321
  <span class="ms-2">Loading user identity...</span>
322
322
  </div>
323
323
  </div>
@@ -642,7 +642,7 @@ function selectProvider(providerType) {
642
642
  </div>
643
643
  <div class="card-body" id="costs-info">
644
644
  <div class="text-center py-3">
645
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
645
+ <output class="spinner-border spinner-border-sm text-primary"></output>
646
646
  <span class="ms-2">Loading costs...</span>
647
647
  </div>
648
648
  </div>
@@ -799,7 +799,7 @@ async function selectToolSource(source, event) {
799
799
  headerEl.innerHTML = '<i class="bi bi-box-fill"></i> Embedded Tools';
800
800
  contentEl.innerHTML = `
801
801
  <div class="text-center py-4">
802
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
802
+ <output class="spinner-border spinner-border-sm text-primary"></output>
803
803
  <span class="ms-2">Loading embedded tools...</span>
804
804
  </div>
805
805
  `;
@@ -890,7 +890,7 @@ async function selectToolSource(source, event) {
890
890
  // Fetch tools from API
891
891
  contentEl.innerHTML = `
892
892
  <div class="text-center py-4">
893
- <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
893
+ <output class="spinner-border spinner-border-sm text-primary"></output>
894
894
  <span class="ms-2">Loading tools...</span>
895
895
  </div>
896
896
  `;
@@ -124,7 +124,7 @@ class WebInterface:
124
124
  True if response was accepted, False if request not found
125
125
  """
126
126
  if request_id not in self._permission_requests:
127
- logger.warning(f"Permission response submitted for unknown request: {request_id}")
127
+ logger.warning("Permission response submitted for unknown request")
128
128
  return False
129
129
 
130
130
  # Store the response
@@ -133,5 +133,5 @@ class WebInterface:
133
133
  # Update request status
134
134
  self._permission_requests[request_id]['status'] = 'responded'
135
135
 
136
- logger.info(f"Permission response submitted for {request_id}: {response}")
136
+ logger.info("Permission response submitted and recorded")
137
137
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtSpark
3
- Version: 1.1.0a3
3
+ Version: 1.1.0a6
4
4
  Summary: Secure Personal AI Research Kit - Multi-provider LLM CLI/Web interface with MCP tool integration
5
5
  Home-page: https://github.com/digital-thought/dtSpark
6
6
  Author: Matthew Westwood-Hill
@@ -42,7 +42,7 @@ Requires-Dist: httpx>=0.24.0
42
42
  Requires-Dist: aiohttp>=3.8.0
43
43
  Requires-Dist: mcp>=0.9.0
44
44
  Requires-Dist: pyyaml>=6.0
45
- Requires-Dist: dtPyAppFramework>=4.0.3
45
+ Requires-Dist: dtPyAppFramework>=4.1.2
46
46
  Requires-Dist: tiktoken>=0.5.0
47
47
  Requires-Dist: ollama>=0.2.0
48
48
  Requires-Dist: cryptography>=41.0.0
@@ -83,6 +83,13 @@ Dynamic: requires-python
83
83
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
84
84
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
85
85
 
86
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Digital-Thought_dtSpark&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Digital-Thought_dtSpark)
87
+ [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Digital-Thought_dtSpark&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=Digital-Thought_dtSpark)
88
+ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=Digital-Thought_dtSpark&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=Digital-Thought_dtSpark)
89
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Digital-Thought_dtSpark&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=Digital-Thought_dtSpark)
90
+ [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=Digital-Thought_dtSpark&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=Digital-Thought_dtSpark)
91
+ [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Digital-Thought_dtSpark&metric=bugs)](https://sonarcloud.io/summary/new_code?id=Digital-Thought_dtSpark)
92
+
86
93
  **Spark** is a powerful, multi-provider LLM interface for conversational AI with integrated tool support. It supports AWS Bedrock, Anthropic Direct API, and Ollama local models through both CLI and Web interfaces.
87
94
 
88
95
  ## Key Features