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,983 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Main Menu - {{ app_name }}{% endblock %}
4
+
5
+ {% block extra_styles %}
6
+ <style>
7
+ .provider-badge {
8
+ font-size: 0.9rem;
9
+ padding: 0.5em 1em;
10
+ }
11
+ .provider-card {
12
+ border-left: 4px solid;
13
+ }
14
+ .provider-card.aws { border-left-color: #ff9900; }
15
+ .provider-card.anthropic { border-left-color: #d4a574; }
16
+ .provider-card.ollama { border-left-color: #0ea5e9; }
17
+ .provider-card.none { border-left-color: #6c757d; }
18
+
19
+ .status-indicator {
20
+ width: 10px;
21
+ height: 10px;
22
+ border-radius: 50%;
23
+ display: inline-block;
24
+ margin-right: 0.5rem;
25
+ }
26
+ .status-indicator.active { background-color: #28a745; }
27
+ .status-indicator.inactive { background-color: #6c757d; }
28
+ .status-indicator.error { background-color: #dc3545; }
29
+
30
+ .quick-action-btn {
31
+ padding: 1rem 1.5rem;
32
+ font-size: 1.1rem;
33
+ transition: transform 0.1s ease;
34
+ }
35
+ .quick-action-btn:hover {
36
+ transform: translateY(-2px);
37
+ }
38
+
39
+ .info-label {
40
+ font-size: 0.8rem;
41
+ color: #888;
42
+ text-transform: uppercase;
43
+ letter-spacing: 0.5px;
44
+ margin-bottom: 0.25rem;
45
+ }
46
+ .info-value {
47
+ font-family: 'Monaco', 'Menlo', monospace;
48
+ font-size: 0.9rem;
49
+ word-break: break-all;
50
+ }
51
+
52
+ .nav-tabs .nav-link {
53
+ border: none;
54
+ border-bottom: 3px solid transparent;
55
+ color: #888;
56
+ }
57
+ .nav-tabs .nav-link:hover {
58
+ border-bottom-color: #444;
59
+ color: #ccc;
60
+ }
61
+ .nav-tabs .nav-link.active {
62
+ background: transparent;
63
+ border-bottom-color: #0d6efd;
64
+ color: #fff;
65
+ }
66
+
67
+ .integration-card {
68
+ transition: box-shadow 0.2s ease;
69
+ }
70
+ .integration-card:hover {
71
+ box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.3);
72
+ }
73
+
74
+ /* Provider icons in Overview tile */
75
+ .provider-icon-tile {
76
+ display: flex;
77
+ flex-direction: column;
78
+ align-items: center;
79
+ padding: 1rem;
80
+ border-radius: 0.5rem;
81
+ background: rgba(255,255,255,0.05);
82
+ transition: all 0.2s ease;
83
+ position: relative;
84
+ min-width: 100px;
85
+ }
86
+ .provider-icon-tile .provider-icon {
87
+ font-size: 2.5rem;
88
+ margin-bottom: 0.5rem;
89
+ }
90
+ .provider-icon-tile .provider-name {
91
+ font-size: 0.85rem;
92
+ font-weight: 500;
93
+ }
94
+ .provider-icon-tile .provider-status-dot {
95
+ position: absolute;
96
+ top: 0.5rem;
97
+ right: 0.5rem;
98
+ width: 12px;
99
+ height: 12px;
100
+ border-radius: 50%;
101
+ border: 2px solid #1a1a1a;
102
+ }
103
+ .provider-icon-tile .provider-status-dot.available { background-color: #28a745; }
104
+ .provider-icon-tile .provider-status-dot.unavailable { background-color: #6c757d; }
105
+ .provider-icon-tile.available { opacity: 1; }
106
+ .provider-icon-tile.unavailable { opacity: 0.5; }
107
+
108
+ /* Provider icon colours */
109
+ .provider-icon.bedrock { color: #ff9900; }
110
+ .provider-icon.anthropic { color: #d4a574; }
111
+ .provider-icon.ollama { color: #0ea5e9; }
112
+
113
+ /* Provider selector buttons in LLMs tab */
114
+ .provider-selector {
115
+ display: flex;
116
+ gap: 1rem;
117
+ margin-bottom: 1.5rem;
118
+ padding: 1rem;
119
+ background: rgba(255,255,255,0.03);
120
+ border-radius: 0.5rem;
121
+ }
122
+ .provider-selector-btn {
123
+ display: flex;
124
+ flex-direction: column;
125
+ align-items: center;
126
+ padding: 1rem 1.5rem;
127
+ border: 2px solid transparent;
128
+ border-radius: 0.5rem;
129
+ background: rgba(255,255,255,0.05);
130
+ cursor: pointer;
131
+ transition: all 0.2s ease;
132
+ min-width: 120px;
133
+ }
134
+ .provider-selector-btn:hover {
135
+ background: rgba(255,255,255,0.1);
136
+ }
137
+ .provider-selector-btn.active {
138
+ border-color: #0d6efd;
139
+ background: rgba(13,110,253,0.15);
140
+ }
141
+ .provider-selector-btn.unavailable {
142
+ opacity: 0.4;
143
+ cursor: not-allowed;
144
+ }
145
+ .provider-selector-btn .selector-icon {
146
+ font-size: 2rem;
147
+ margin-bottom: 0.5rem;
148
+ }
149
+ .provider-selector-btn .selector-name {
150
+ font-size: 0.9rem;
151
+ font-weight: 500;
152
+ }
153
+ .provider-selector-btn .selector-status {
154
+ font-size: 0.75rem;
155
+ margin-top: 0.25rem;
156
+ }
157
+ </style>
158
+ {% endblock %}
159
+
160
+ {% block content %}
161
+ <div class="row mb-4">
162
+ <div class="col-12">
163
+ <h2 class="mb-0"><i class="bi bi-house-fill"></i> Main Menu</h2>
164
+ <small class="text-muted">SPARK v{{ app_version }} - Secure Personal AI Research Kit</small>
165
+ </div>
166
+ </div>
167
+
168
+ <!-- Tab Navigation -->
169
+ <ul class="nav nav-tabs mb-4" id="mainTabs" role="tablist">
170
+ <li class="nav-item" role="presentation">
171
+ <button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview" type="button" role="tab">
172
+ <i class="bi bi-speedometer2"></i> Overview
173
+ </button>
174
+ </li>
175
+ <li class="nav-item" role="presentation">
176
+ <button class="nav-link" id="llms-tab" data-bs-toggle="tab" data-bs-target="#llms" type="button" role="tab">
177
+ <i class="bi bi-cpu"></i> LLMs
178
+ </button>
179
+ </li>
180
+ <li class="nav-item" role="presentation">
181
+ <button class="nav-link" id="integrations-tab" data-bs-toggle="tab" data-bs-target="#integrations" type="button" role="tab">
182
+ <i class="bi bi-plug"></i> Integrations
183
+ </button>
184
+ </li>
185
+ <li class="nav-item" role="presentation">
186
+ <button class="nav-link" id="system-tab" data-bs-toggle="tab" data-bs-target="#system" type="button" role="tab">
187
+ <i class="bi bi-gear"></i> System
188
+ </button>
189
+ </li>
190
+ </ul>
191
+
192
+ <!-- Tab Content -->
193
+ <div class="tab-content" id="mainTabContent">
194
+
195
+ <!-- Overview Tab -->
196
+ <div class="tab-pane fade show active" id="overview" role="tabpanel">
197
+ <div class="row">
198
+ <!-- LLM Providers Status -->
199
+ <div class="col-md-6 mb-4">
200
+ <div class="card">
201
+ <div class="card-body">
202
+ <h5 class="card-title mb-3"><i class="bi bi-cpu"></i> LLM Providers</h5>
203
+ <div id="llm-providers-overview" class="d-flex justify-content-around flex-wrap gap-2">
204
+ <div class="text-center py-3">
205
+ <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
206
+ <span class="ms-2">Loading providers...</span>
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+
213
+ <!-- System Status Summary -->
214
+ <div class="col-md-6 mb-4">
215
+ <div class="card">
216
+ <div class="card-body">
217
+ <h5 class="card-title mb-3"><i class="bi bi-activity"></i> System Status</h5>
218
+ <div id="system-status-summary">
219
+ <div class="d-flex justify-content-between align-items-center mb-2">
220
+ <span><i class="bi bi-hdd-network"></i> MCP Servers</span>
221
+ <span id="mcp-status-badge" class="badge bg-secondary">Loading...</span>
222
+ </div>
223
+ <div class="d-flex justify-content-between align-items-center mb-2">
224
+ <span><i class="bi bi-database"></i> Database</span>
225
+ <span class="badge bg-success">{{ database_type|default('SQLite') }}</span>
226
+ </div>
227
+ <div class="d-flex justify-content-between align-items-center">
228
+ <span><i class="bi bi-shield-check"></i> Session</span>
229
+ <span class="badge bg-success">Active</span>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </div>
236
+
237
+ <!-- Quick Actions -->
238
+ <div class="row">
239
+ <div class="col-12">
240
+ <h5 class="mb-3"><i class="bi bi-lightning-fill"></i> Quick Actions</h5>
241
+ <div class="d-grid gap-3 d-md-flex">
242
+ <a href="/conversations/new" class="btn btn-primary quick-action-btn flex-fill">
243
+ <i class="bi bi-plus-circle-fill"></i> Start New Conversation
244
+ </a>
245
+ <a href="/conversations" class="btn btn-outline-primary quick-action-btn flex-fill">
246
+ <i class="bi bi-list-ul"></i> View Conversations
247
+ </a>
248
+ <button onclick="confirmQuit(event)" class="btn btn-outline-danger quick-action-btn">
249
+ <i class="bi bi-power"></i> Quit
250
+ </button>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ </div>
255
+
256
+ <!-- LLMs Tab -->
257
+ <div class="tab-pane fade" id="llms" role="tabpanel">
258
+ <!-- Provider Selector Icons -->
259
+ <div class="provider-selector" id="provider-selector">
260
+ <div class="text-center py-3 w-100">
261
+ <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
262
+ <span class="ms-2">Loading providers...</span>
263
+ </div>
264
+ </div>
265
+
266
+ <!-- Selected Provider Details -->
267
+ <div id="selected-provider-details">
268
+ <div class="text-center py-5 text-muted">
269
+ <i class="bi bi-hand-index-thumb fs-1 d-block mb-3"></i>
270
+ <p>Select a provider above to view details and available models</p>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <!-- Integrations Tab -->
276
+ <div class="tab-pane fade" id="integrations" role="tabpanel">
277
+ <div class="row">
278
+ <!-- Tool Sources List -->
279
+ <div class="col-md-4 mb-4">
280
+ <div class="card h-100">
281
+ <div class="card-header bg-dark">
282
+ <i class="bi bi-tools"></i> Tool Sources
283
+ </div>
284
+ <div class="card-body p-0" id="tool-sources-list">
285
+ <div class="text-center py-4">
286
+ <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
287
+ <span class="ms-2">Loading tools...</span>
288
+ </div>
289
+ </div>
290
+ </div>
291
+ </div>
292
+
293
+ <!-- Selected Tool Source Details -->
294
+ <div class="col-md-8 mb-4">
295
+ <div class="card h-100">
296
+ <div class="card-header bg-dark" id="tool-details-header">
297
+ <i class="bi bi-wrench"></i> Available Tools
298
+ </div>
299
+ <div class="card-body" id="tool-details-content">
300
+ <div class="text-center py-5 text-muted">
301
+ <i class="bi bi-hand-index-thumb fs-1 d-block mb-3"></i>
302
+ <p>Select a tool source from the left to view available tools</p>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+
310
+ <!-- System Tab -->
311
+ <div class="tab-pane fade" id="system" role="tabpanel">
312
+ <div class="row">
313
+ <div class="col-md-4 mb-4">
314
+ <div class="card h-100">
315
+ <div class="card-header bg-dark">
316
+ <i class="bi bi-fingerprint"></i> User Identity
317
+ </div>
318
+ <div class="card-body" id="user-identity">
319
+ <div class="text-center py-3">
320
+ <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
321
+ <span class="ms-2">Loading user identity...</span>
322
+ </div>
323
+ </div>
324
+ </div>
325
+ </div>
326
+
327
+ <div class="col-md-4 mb-4">
328
+ <div class="card h-100">
329
+ <div class="card-header bg-dark">
330
+ <i class="bi bi-database-fill"></i> Database
331
+ </div>
332
+ <div class="card-body">
333
+ <div class="mb-3">
334
+ <div class="info-label">Database Type</div>
335
+ <div class="info-value">
336
+ <span class="badge bg-info">{{ database_type|default('SQLite') }}</span>
337
+ </div>
338
+ </div>
339
+ <div class="mb-3">
340
+ <div class="info-label">Status</div>
341
+ <div class="info-value">
342
+ <span class="status-indicator active"></span> Connected
343
+ </div>
344
+ </div>
345
+ <div class="text-muted small">
346
+ <i class="bi bi-info-circle"></i>
347
+ Stores conversations, tool permissions, and preferences.
348
+ </div>
349
+ </div>
350
+ </div>
351
+ </div>
352
+
353
+ <div class="col-md-4 mb-4">
354
+ <div class="card h-100">
355
+ <div class="card-header bg-dark">
356
+ <i class="bi bi-info-circle-fill"></i> Application Info
357
+ </div>
358
+ <div class="card-body">
359
+ <div class="mb-3">
360
+ <div class="info-label">Version</div>
361
+ <div class="info-value">{{ app_version }}</div>
362
+ </div>
363
+ <div class="mb-3">
364
+ <div class="info-label">Application Name</div>
365
+ <div class="info-value">{{ app_name }}</div>
366
+ </div>
367
+ <div class="mb-3">
368
+ <div class="info-label">Interface Mode</div>
369
+ <div class="info-value">
370
+ <span class="badge bg-primary">Web UI</span>
371
+ </div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+ </div>
376
+ </div>
377
+
378
+ <!-- Additional System Actions -->
379
+ <div class="row">
380
+ <div class="col-12">
381
+ <div class="card">
382
+ <div class="card-header bg-dark">
383
+ <i class="bi bi-sliders"></i> System Actions
384
+ </div>
385
+ <div class="card-body">
386
+ <div class="d-grid gap-2 d-md-flex">
387
+ {% if cost_tracking_enabled %}
388
+ <button class="btn btn-outline-secondary" onclick="refreshCosts()">
389
+ <i class="bi bi-arrow-clockwise"></i> Refresh AWS Costs
390
+ </button>
391
+ {% endif %}
392
+ <button class="btn btn-outline-secondary" onclick="location.reload()">
393
+ <i class="bi bi-arrow-clockwise"></i> Refresh Page
394
+ </button>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ {% endblock %}
403
+
404
+ {% block extra_scripts %}
405
+ <script>
406
+ // Global state
407
+ let providerData = null;
408
+ let allProviders = [];
409
+ let selectedProviderType = null;
410
+
411
+ // Provider definitions (all possible providers)
412
+ const providerDefinitions = {
413
+ 'aws': {
414
+ name: 'AWS Bedrock',
415
+ icon: 'bi-cloud-fill',
416
+ colour: '#ff9900',
417
+ description: 'Cloud-based model inference via AWS'
418
+ },
419
+ 'anthropic': {
420
+ name: 'Anthropic',
421
+ icon: 'bi-cpu-fill',
422
+ colour: '#d4a574',
423
+ description: 'Direct API access to Claude models'
424
+ },
425
+ 'ollama': {
426
+ name: 'Ollama',
427
+ icon: 'bi-pc-display',
428
+ colour: '#0ea5e9',
429
+ description: 'Local model inference'
430
+ }
431
+ };
432
+
433
+ // Load all data
434
+ async function loadAllData() {
435
+ try {
436
+ const response = await fetch('/api/account');
437
+ if (!response.ok) throw new Error('Failed to fetch account info');
438
+ providerData = await response.json();
439
+
440
+ updateUserIdentity();
441
+ } catch (error) {
442
+ console.error('Error loading account data:', error);
443
+ }
444
+
445
+ // Load providers (includes models list)
446
+ await loadProviders();
447
+ loadMCPServers();
448
+ }
449
+
450
+ // Load all providers and update UI
451
+ async function loadProviders() {
452
+ try {
453
+ const response = await fetch('/api/providers');
454
+ if (!response.ok) throw new Error('Failed to fetch providers');
455
+ allProviders = await response.json();
456
+
457
+ // Update Overview tab - LLM Providers tile
458
+ updateLLMProvidersOverview();
459
+
460
+ // Update LLMs tab - Provider selector
461
+ updateProviderSelector();
462
+
463
+ // Auto-select first available provider
464
+ if (allProviders.length > 0) {
465
+ selectProvider(allProviders[0].type);
466
+ }
467
+
468
+ } catch (error) {
469
+ console.error('Error loading providers:', error);
470
+ document.getElementById('llm-providers-overview').innerHTML = `
471
+ <div class="alert alert-danger mb-0 w-100">
472
+ <i class="bi bi-exclamation-triangle-fill"></i> Failed to load providers
473
+ </div>
474
+ `;
475
+ }
476
+ }
477
+
478
+ // Update the LLM Providers overview tile on Overview tab
479
+ function updateLLMProvidersOverview() {
480
+ const container = document.getElementById('llm-providers-overview');
481
+
482
+ // Get configured provider types
483
+ const configuredTypes = new Set(allProviders.map(p => p.type));
484
+
485
+ let html = '';
486
+
487
+ // Show all three provider icons
488
+ Object.entries(providerDefinitions).forEach(([type, def]) => {
489
+ const isAvailable = configuredTypes.has(type);
490
+ const provider = allProviders.find(p => p.type === type);
491
+ const isConnected = provider && provider.status === 'connected';
492
+
493
+ html += `
494
+ <div class="provider-icon-tile ${isAvailable ? 'available' : 'unavailable'}">
495
+ <div class="provider-status-dot ${isAvailable && isConnected ? 'available' : 'unavailable'}"></div>
496
+ <i class="bi ${def.icon} provider-icon ${type}"></i>
497
+ <span class="provider-name">${def.name}</span>
498
+ ${isAvailable ? `
499
+ <span class="badge bg-success mt-1" style="font-size: 0.7rem;">
500
+ ${provider.models.length} model${provider.models.length !== 1 ? 's' : ''}
501
+ </span>
502
+ ` : `
503
+ <span class="badge bg-secondary mt-1" style="font-size: 0.7rem;">Not configured</span>
504
+ `}
505
+ </div>
506
+ `;
507
+ });
508
+
509
+ container.innerHTML = html;
510
+ }
511
+
512
+ // Update the provider selector on LLMs tab
513
+ function updateProviderSelector() {
514
+ const container = document.getElementById('provider-selector');
515
+
516
+ // Get configured provider types
517
+ const configuredTypes = new Set(allProviders.map(p => p.type));
518
+
519
+ let html = '';
520
+
521
+ // Show all three provider icons as selectable buttons
522
+ Object.entries(providerDefinitions).forEach(([type, def]) => {
523
+ const isAvailable = configuredTypes.has(type);
524
+ const provider = allProviders.find(p => p.type === type);
525
+ const isConnected = provider && provider.status === 'connected';
526
+
527
+ html += `
528
+ <div class="provider-selector-btn ${isAvailable ? '' : 'unavailable'}"
529
+ data-provider-type="${type}"
530
+ onclick="${isAvailable ? `selectProvider('${type}')` : ''}"
531
+ title="${isAvailable ? `View ${def.name} details` : `${def.name} is not configured`}">
532
+ <i class="bi ${def.icon} selector-icon" style="color: ${def.colour};"></i>
533
+ <span class="selector-name">${def.name}</span>
534
+ <span class="selector-status ${isAvailable && isConnected ? 'text-success' : isAvailable ? 'text-warning' : 'text-muted'}">
535
+ ${isAvailable && isConnected ? '● Connected' : isAvailable ? '● Error' : '○ Not configured'}
536
+ </span>
537
+ </div>
538
+ `;
539
+ });
540
+
541
+ container.innerHTML = html;
542
+ }
543
+
544
+ // Select a provider and show its details
545
+ function selectProvider(providerType) {
546
+ selectedProviderType = providerType;
547
+
548
+ // Update selector button active states
549
+ document.querySelectorAll('.provider-selector-btn').forEach(btn => {
550
+ btn.classList.remove('active');
551
+ if (btn.dataset.providerType === providerType) {
552
+ btn.classList.add('active');
553
+ }
554
+ });
555
+
556
+ // Find the provider data
557
+ const provider = allProviders.find(p => p.type === providerType);
558
+ const def = providerDefinitions[providerType];
559
+
560
+ if (!provider) {
561
+ document.getElementById('selected-provider-details').innerHTML = `
562
+ <div class="alert alert-warning">
563
+ <i class="bi bi-exclamation-triangle-fill"></i> Provider not found
564
+ </div>
565
+ `;
566
+ return;
567
+ }
568
+
569
+ const statusBadge = provider.status === 'connected'
570
+ ? '<span class="badge bg-success"><i class="bi bi-check-circle"></i> Connected</span>'
571
+ : '<span class="badge bg-danger"><i class="bi bi-x-circle"></i> Error</span>';
572
+
573
+ let html = `
574
+ <div class="card" style="border-left: 4px solid ${def.colour};">
575
+ <div class="card-header" style="background-color: ${def.colour}; color: ${providerType === 'aws' ? '#000' : '#fff'};">
576
+ <div class="d-flex justify-content-between align-items-center">
577
+ <span><i class="bi ${def.icon}"></i> ${provider.name}</span>
578
+ ${statusBadge}
579
+ </div>
580
+ </div>
581
+ <div class="card-body">
582
+ <div class="row">
583
+ <div class="col-md-4">
584
+ <h6 class="mb-3">Connection Details</h6>
585
+ <div class="mb-3">
586
+ <div class="info-label">Authentication</div>
587
+ <div class="info-value">
588
+ <span class="badge bg-primary">${getAuthMethodDisplay(provider.auth_method)}</span>
589
+ </div>
590
+ </div>
591
+ ${provider.region ? `
592
+ <div class="mb-3">
593
+ <div class="info-label">Region</div>
594
+ <div class="info-value">${provider.region}</div>
595
+ </div>
596
+ ` : ''}
597
+ ${provider.base_url ? `
598
+ <div class="mb-3">
599
+ <div class="info-label">Base URL</div>
600
+ <div class="info-value"><code class="small">${provider.base_url}</code></div>
601
+ </div>
602
+ ` : ''}
603
+ </div>
604
+ <div class="col-md-8">
605
+ <h6 class="mb-3">Available Models (${provider.models.length})</h6>
606
+ <div class="model-list" style="max-height: 350px; overflow-y: auto;">
607
+ ${provider.models.length > 0 ? `
608
+ <div class="list-group list-group-flush">
609
+ ${provider.models.map(model => `
610
+ <div class="list-group-item bg-transparent border-secondary py-2">
611
+ <div class="d-flex justify-content-between align-items-center">
612
+ <span>
613
+ <i class="bi bi-robot text-muted me-2"></i>
614
+ ${model.display_name}
615
+ </span>
616
+ </div>
617
+ <code class="small text-muted">${model.model_id}</code>
618
+ </div>
619
+ `).join('')}
620
+ </div>
621
+ ` : `
622
+ <div class="text-muted text-center py-4">
623
+ <i class="bi bi-info-circle fs-2 d-block mb-2"></i>
624
+ <p class="mb-0">No models available from this provider</p>
625
+ <small>Check the provider connection or configuration</small>
626
+ </div>
627
+ `}
628
+ </div>
629
+ </div>
630
+ </div>
631
+ </div>
632
+ </div>
633
+ `;
634
+
635
+ {% if cost_tracking_enabled %}
636
+ // Add costs card if this is AWS
637
+ if (providerType === 'aws') {
638
+ html += `
639
+ <div class="card mt-4" style="border-left: 4px solid #ff9900;">
640
+ <div class="card-header bg-warning text-dark">
641
+ <i class="bi bi-currency-dollar"></i> AWS Bedrock Usage Costs
642
+ </div>
643
+ <div class="card-body" id="costs-info">
644
+ <div class="text-center py-3">
645
+ <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
646
+ <span class="ms-2">Loading costs...</span>
647
+ </div>
648
+ </div>
649
+ </div>
650
+ `;
651
+ setTimeout(loadCosts, 100);
652
+ }
653
+ {% endif %}
654
+
655
+ document.getElementById('selected-provider-details').innerHTML = html;
656
+ }
657
+
658
+ // Update user identity on System tab
659
+ function updateUserIdentity() {
660
+ const container = document.getElementById('user-identity');
661
+
662
+ const userGuid = providerData?.user_guid || 'Not available';
663
+
664
+ container.innerHTML = `
665
+ <div class="mb-3">
666
+ <div class="info-label">User GUID</div>
667
+ <div class="info-value">
668
+ <code class="text-break">${userGuid}</code>
669
+ </div>
670
+ </div>
671
+ <div class="text-muted small">
672
+ <i class="bi bi-info-circle"></i>
673
+ This unique identifier is used for database isolation and multi-user support when using shared databases.
674
+ </div>
675
+ `;
676
+ }
677
+
678
+ // Get auth method display name
679
+ function getAuthMethodDisplay(method) {
680
+ const methods = {
681
+ 'sso_profile': 'SSO Profile',
682
+ 'api_keys': 'API Keys',
683
+ 'api_key': 'API Key',
684
+ 'local': 'Local',
685
+ 'iam_role': 'IAM Role',
686
+ 'default': 'Default Credentials'
687
+ };
688
+ return methods[method] || method || 'Unknown';
689
+ }
690
+
691
+ // Global state for tools
692
+ let allTools = [];
693
+ let mcpServers = [];
694
+ let selectedToolSource = null;
695
+
696
+ // Load MCP servers and tools for Integrations tab
697
+ async function loadMCPServers() {
698
+ try {
699
+ const response = await fetch('/api/mcp/servers');
700
+ mcpServers = await response.json();
701
+
702
+ // Update Overview tab MCP status badge
703
+ const badge = document.getElementById('mcp-status-badge');
704
+ if (mcpServers.length === 0) {
705
+ badge.className = 'badge bg-secondary';
706
+ badge.textContent = 'Disabled';
707
+ } else {
708
+ const connectedCount = mcpServers.filter(s => s.connected).length;
709
+ badge.className = connectedCount === mcpServers.length ? 'badge bg-success' : 'badge bg-warning';
710
+ badge.textContent = `${connectedCount}/${mcpServers.length} Connected`;
711
+ }
712
+
713
+ // Load tool sources list for Integrations tab
714
+ updateToolSourcesList();
715
+
716
+ } catch (error) {
717
+ console.error('Error loading MCP servers:', error);
718
+ document.getElementById('mcp-status-badge').className = 'badge bg-danger';
719
+ document.getElementById('mcp-status-badge').textContent = 'Error';
720
+ document.getElementById('tool-sources-list').innerHTML = `
721
+ <div class="p-3 text-center text-danger">
722
+ <i class="bi bi-exclamation-triangle-fill"></i> Failed to load tools
723
+ </div>
724
+ `;
725
+ }
726
+ }
727
+
728
+ // Update the tool sources list in Integrations tab
729
+ function updateToolSourcesList() {
730
+ const container = document.getElementById('tool-sources-list');
731
+
732
+ let html = '<div class="list-group list-group-flush">';
733
+
734
+ // Add embedded tools item
735
+ html += `
736
+ <a href="#" class="list-group-item list-group-item-action" onclick="selectToolSource('embedded', event)">
737
+ <div class="d-flex justify-content-between align-items-center">
738
+ <div>
739
+ <i class="bi bi-box-fill text-info me-2"></i>
740
+ <strong>Embedded Tools</strong>
741
+ </div>
742
+ <span class="badge bg-info">Built-in</span>
743
+ </div>
744
+ <small class="text-muted">Core application tools</small>
745
+ </a>
746
+ `;
747
+
748
+ // Add MCP servers
749
+ if (mcpServers.length > 0) {
750
+ mcpServers.forEach(server => {
751
+ const statusClass = server.connected ? 'success' : 'danger';
752
+ const statusIcon = server.connected ? 'check-circle-fill' : 'x-circle-fill';
753
+ html += `
754
+ <a href="#" class="list-group-item list-group-item-action" onclick="selectToolSource('mcp:${server.name}', event)">
755
+ <div class="d-flex justify-content-between align-items-center">
756
+ <div>
757
+ <i class="bi bi-${statusIcon} text-${statusClass} me-2"></i>
758
+ <strong>${server.name}</strong>
759
+ </div>
760
+ <span class="badge bg-secondary">${server.tool_count} tools</span>
761
+ </div>
762
+ <small class="text-muted">${server.transport} • MCP Server</small>
763
+ </a>
764
+ `;
765
+ });
766
+ } else {
767
+ html += `
768
+ <div class="list-group-item text-muted small">
769
+ <i class="bi bi-info-circle"></i> No MCP servers configured
770
+ </div>
771
+ `;
772
+ }
773
+
774
+ html += '</div>';
775
+ container.innerHTML = html;
776
+
777
+ // Auto-select embedded tools
778
+ selectToolSource('embedded');
779
+ }
780
+
781
+ // Select a tool source and show its tools
782
+ async function selectToolSource(source, event) {
783
+ if (event) event.preventDefault();
784
+ selectedToolSource = source;
785
+
786
+ // Update active state in list
787
+ document.querySelectorAll('#tool-sources-list .list-group-item').forEach(item => {
788
+ item.classList.remove('active');
789
+ });
790
+ if (event) {
791
+ event.target.closest('.list-group-item').classList.add('active');
792
+ }
793
+
794
+ const headerEl = document.getElementById('tool-details-header');
795
+ const contentEl = document.getElementById('tool-details-content');
796
+
797
+ if (source === 'embedded') {
798
+ // Show embedded tools
799
+ headerEl.innerHTML = '<i class="bi bi-box-fill"></i> Embedded Tools';
800
+ contentEl.innerHTML = `
801
+ <div class="list-group list-group-flush">
802
+ <div class="list-group-item bg-transparent border-secondary">
803
+ <div class="d-flex justify-content-between align-items-start">
804
+ <div>
805
+ <strong>execute_mcp_tool</strong>
806
+ <p class="text-muted small mb-0 mt-1">Execute a tool provided by an MCP server</p>
807
+ </div>
808
+ <span class="badge bg-info">Core</span>
809
+ </div>
810
+ </div>
811
+ <div class="list-group-item bg-transparent border-secondary">
812
+ <div class="d-flex justify-content-between align-items-start">
813
+ <div>
814
+ <strong>conversation_search</strong>
815
+ <p class="text-muted small mb-0 mt-1">Search through conversation history</p>
816
+ </div>
817
+ <span class="badge bg-info">Core</span>
818
+ </div>
819
+ </div>
820
+ <div class="list-group-item bg-transparent border-secondary">
821
+ <div class="d-flex justify-content-between align-items-start">
822
+ <div>
823
+ <strong>file_attachment</strong>
824
+ <p class="text-muted small mb-0 mt-1">Attach files to the conversation context</p>
825
+ </div>
826
+ <span class="badge bg-info">Core</span>
827
+ </div>
828
+ </div>
829
+ </div>
830
+ `;
831
+ } else if (source.startsWith('mcp:')) {
832
+ // Show MCP server tools
833
+ const serverName = source.substring(4);
834
+ const server = mcpServers.find(s => s.name === serverName);
835
+
836
+ headerEl.innerHTML = `<i class="bi bi-hdd-network-fill"></i> ${serverName} Tools`;
837
+
838
+ if (!server || !server.connected) {
839
+ contentEl.innerHTML = `
840
+ <div class="alert alert-warning">
841
+ <i class="bi bi-exclamation-triangle-fill"></i>
842
+ Server is not connected. Tools cannot be retrieved.
843
+ </div>
844
+ `;
845
+ return;
846
+ }
847
+
848
+ // Fetch tools from API
849
+ contentEl.innerHTML = `
850
+ <div class="text-center py-4">
851
+ <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
852
+ <span class="ms-2">Loading tools...</span>
853
+ </div>
854
+ `;
855
+
856
+ try {
857
+ const response = await fetch('/api/mcp/tools?server=' + encodeURIComponent(serverName));
858
+ const tools = await response.json();
859
+
860
+ if (tools.length === 0) {
861
+ contentEl.innerHTML = `
862
+ <div class="text-center py-4 text-muted">
863
+ <i class="bi bi-inbox fs-2 d-block mb-2"></i>
864
+ <p class="mb-0">No tools available from this server</p>
865
+ </div>
866
+ `;
867
+ return;
868
+ }
869
+
870
+ let html = '<div class="list-group list-group-flush" style="max-height: 400px; overflow-y: auto;">';
871
+ tools.forEach(tool => {
872
+ html += `
873
+ <div class="list-group-item bg-transparent border-secondary">
874
+ <div class="d-flex justify-content-between align-items-start">
875
+ <div>
876
+ <strong>${tool.name}</strong>
877
+ <p class="text-muted small mb-0 mt-1">${tool.description || 'No description available'}</p>
878
+ </div>
879
+ </div>
880
+ </div>
881
+ `;
882
+ });
883
+ html += '</div>';
884
+ contentEl.innerHTML = html;
885
+
886
+ } catch (error) {
887
+ console.error('Error loading tools:', error);
888
+ contentEl.innerHTML = `
889
+ <div class="alert alert-danger">
890
+ <i class="bi bi-exclamation-triangle-fill"></i>
891
+ Failed to load tools from server
892
+ </div>
893
+ `;
894
+ }
895
+ }
896
+ }
897
+
898
+ {% if cost_tracking_enabled %}
899
+ // Load cost information
900
+ async function loadCosts() {
901
+ try {
902
+ const [lastMonth, last24Hours] = await Promise.all([
903
+ fetch('/api/costs/last-month').then(r => r.json()),
904
+ fetch('/api/costs/last-24-hours').then(r => r.json())
905
+ ]);
906
+
907
+ const container = document.getElementById('costs-info');
908
+ if (!container) return;
909
+
910
+ let html = '';
911
+
912
+ // Last Month
913
+ html += `
914
+ <div class="mb-3">
915
+ <div class="info-label">Last Month</div>
916
+ <div class="fs-4 fw-bold text-success">$${lastMonth.total.toFixed(2)}</div>
917
+ `;
918
+ if (Object.keys(lastMonth.models).length > 0) {
919
+ html += '<div class="small text-muted mt-1">';
920
+ const sorted = Object.entries(lastMonth.models).sort((a, b) => b[1].cost - a[1].cost).slice(0, 3);
921
+ sorted.forEach(([model, data]) => {
922
+ html += `<div>${model}: $${data.cost.toFixed(2)}</div>`;
923
+ });
924
+ html += '</div>';
925
+ }
926
+ html += '</div>';
927
+
928
+ // Last 24 Hours
929
+ html += `
930
+ <div>
931
+ <div class="info-label">Last 24 Hours</div>
932
+ <div class="fs-5 fw-bold">$${last24Hours.total.toFixed(2)}</div>
933
+ </div>
934
+ `;
935
+
936
+ container.innerHTML = html;
937
+
938
+ } catch (error) {
939
+ console.error('Error loading costs:', error);
940
+ const container = document.getElementById('costs-info');
941
+ if (container) {
942
+ container.innerHTML = `
943
+ <div class="text-muted small text-center">
944
+ <i class="bi bi-info-circle"></i> Cost data not available
945
+ </div>
946
+ `;
947
+ }
948
+ }
949
+ }
950
+
951
+ // Refresh costs
952
+ async function refreshCosts() {
953
+ const btn = event.target.closest('button');
954
+ btn.disabled = true;
955
+ btn.innerHTML = '<i class="bi bi-arrow-clockwise spin"></i> Refreshing...';
956
+
957
+ try {
958
+ await fetch('/api/costs/refresh', { method: 'POST' });
959
+ await loadCosts();
960
+ btn.innerHTML = '<i class="bi bi-check-circle-fill text-success"></i> Refreshed';
961
+ } catch (error) {
962
+ btn.innerHTML = '<i class="bi bi-exclamation-triangle-fill text-danger"></i> Failed';
963
+ }
964
+
965
+ setTimeout(() => {
966
+ btn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> Refresh AWS Costs';
967
+ btn.disabled = false;
968
+ }, 2000);
969
+ }
970
+ {% endif %}
971
+
972
+ // Confirm quit
973
+ function confirmQuit(event) {
974
+ event.preventDefault();
975
+ if (confirm('Are you sure you want to quit? This will shut down the web server.')) {
976
+ window.location.href = '/quit';
977
+ }
978
+ }
979
+
980
+ // Load all data on page load
981
+ window.addEventListener('load', loadAllData);
982
+ </script>
983
+ {% endblock %}