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.
- dtSpark/__init__.py +0 -0
- dtSpark/_description.txt +1 -0
- dtSpark/_full_name.txt +1 -0
- dtSpark/_licence.txt +21 -0
- dtSpark/_metadata.yaml +6 -0
- dtSpark/_name.txt +1 -0
- dtSpark/_version.txt +1 -0
- dtSpark/aws/__init__.py +7 -0
- dtSpark/aws/authentication.py +296 -0
- dtSpark/aws/bedrock.py +578 -0
- dtSpark/aws/costs.py +318 -0
- dtSpark/aws/pricing.py +580 -0
- dtSpark/cli_interface.py +2645 -0
- dtSpark/conversation_manager.py +3050 -0
- dtSpark/core/__init__.py +12 -0
- dtSpark/core/application.py +3355 -0
- dtSpark/core/context_compaction.py +735 -0
- dtSpark/daemon/__init__.py +104 -0
- dtSpark/daemon/__main__.py +10 -0
- dtSpark/daemon/action_monitor.py +213 -0
- dtSpark/daemon/daemon_app.py +730 -0
- dtSpark/daemon/daemon_manager.py +289 -0
- dtSpark/daemon/execution_coordinator.py +194 -0
- dtSpark/daemon/pid_file.py +169 -0
- dtSpark/database/__init__.py +482 -0
- dtSpark/database/autonomous_actions.py +1191 -0
- dtSpark/database/backends.py +329 -0
- dtSpark/database/connection.py +122 -0
- dtSpark/database/conversations.py +520 -0
- dtSpark/database/credential_prompt.py +218 -0
- dtSpark/database/files.py +205 -0
- dtSpark/database/mcp_ops.py +355 -0
- dtSpark/database/messages.py +161 -0
- dtSpark/database/schema.py +673 -0
- dtSpark/database/tool_permissions.py +186 -0
- dtSpark/database/usage.py +167 -0
- dtSpark/files/__init__.py +4 -0
- dtSpark/files/manager.py +322 -0
- dtSpark/launch.py +39 -0
- dtSpark/limits/__init__.py +10 -0
- dtSpark/limits/costs.py +296 -0
- dtSpark/limits/tokens.py +342 -0
- dtSpark/llm/__init__.py +17 -0
- dtSpark/llm/anthropic_direct.py +446 -0
- dtSpark/llm/base.py +146 -0
- dtSpark/llm/context_limits.py +438 -0
- dtSpark/llm/manager.py +177 -0
- dtSpark/llm/ollama.py +578 -0
- dtSpark/mcp_integration/__init__.py +5 -0
- dtSpark/mcp_integration/manager.py +653 -0
- dtSpark/mcp_integration/tool_selector.py +225 -0
- dtSpark/resources/config.yaml.template +631 -0
- dtSpark/safety/__init__.py +22 -0
- dtSpark/safety/llm_service.py +111 -0
- dtSpark/safety/patterns.py +229 -0
- dtSpark/safety/prompt_inspector.py +442 -0
- dtSpark/safety/violation_logger.py +346 -0
- dtSpark/scheduler/__init__.py +20 -0
- dtSpark/scheduler/creation_tools.py +599 -0
- dtSpark/scheduler/execution_queue.py +159 -0
- dtSpark/scheduler/executor.py +1152 -0
- dtSpark/scheduler/manager.py +395 -0
- dtSpark/tools/__init__.py +4 -0
- dtSpark/tools/builtin.py +833 -0
- dtSpark/web/__init__.py +20 -0
- dtSpark/web/auth.py +152 -0
- dtSpark/web/dependencies.py +37 -0
- dtSpark/web/endpoints/__init__.py +17 -0
- dtSpark/web/endpoints/autonomous_actions.py +1125 -0
- dtSpark/web/endpoints/chat.py +621 -0
- dtSpark/web/endpoints/conversations.py +353 -0
- dtSpark/web/endpoints/main_menu.py +547 -0
- dtSpark/web/endpoints/streaming.py +421 -0
- dtSpark/web/server.py +578 -0
- dtSpark/web/session.py +167 -0
- dtSpark/web/ssl_utils.py +195 -0
- dtSpark/web/static/css/dark-theme.css +427 -0
- dtSpark/web/static/js/actions.js +1101 -0
- dtSpark/web/static/js/chat.js +614 -0
- dtSpark/web/static/js/main.js +496 -0
- dtSpark/web/static/js/sse-client.js +242 -0
- dtSpark/web/templates/actions.html +408 -0
- dtSpark/web/templates/base.html +93 -0
- dtSpark/web/templates/chat.html +814 -0
- dtSpark/web/templates/conversations.html +350 -0
- dtSpark/web/templates/goodbye.html +81 -0
- dtSpark/web/templates/login.html +90 -0
- dtSpark/web/templates/main_menu.html +983 -0
- dtSpark/web/templates/new_conversation.html +191 -0
- dtSpark/web/web_interface.py +137 -0
- dtspark-1.0.4.dist-info/METADATA +187 -0
- dtspark-1.0.4.dist-info/RECORD +96 -0
- dtspark-1.0.4.dist-info/WHEEL +5 -0
- dtspark-1.0.4.dist-info/entry_points.txt +3 -0
- dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
- 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 %}
|