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,814 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Chat - {{ conversation_name }} - {{ app_name }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col-12">
|
|
8
|
+
<h2 class="mb-3">
|
|
9
|
+
<i class="bi bi-chat-dots-fill"></i> {{ conversation_name }}
|
|
10
|
+
<button class="btn btn-sm btn-outline-secondary float-end" data-bs-toggle="modal" data-bs-target="#infoModal">
|
|
11
|
+
<i class="bi bi-info-circle"></i> Info
|
|
12
|
+
</button>
|
|
13
|
+
</h2>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Chat Messages Area -->
|
|
18
|
+
<div class="row">
|
|
19
|
+
<div class="col-12">
|
|
20
|
+
<div class="card mb-3" style="height: 60vh;">
|
|
21
|
+
<div class="card-body overflow-auto" id="chat-messages">
|
|
22
|
+
<!-- Messages will be appended here -->
|
|
23
|
+
<div class="text-center text-muted">
|
|
24
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
25
|
+
<span class="visually-hidden">Loading...</span>
|
|
26
|
+
</div>
|
|
27
|
+
<span class="ms-2">Loading chat history...</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Chat Input Area -->
|
|
35
|
+
<div class="row">
|
|
36
|
+
<div class="col-12">
|
|
37
|
+
<div class="card">
|
|
38
|
+
<div class="card-body">
|
|
39
|
+
<form id="chat-form">
|
|
40
|
+
<div class="mb-3">
|
|
41
|
+
<label for="message-input" class="form-label">Your Message</label>
|
|
42
|
+
<textarea
|
|
43
|
+
class="form-control"
|
|
44
|
+
id="message-input"
|
|
45
|
+
rows="3"
|
|
46
|
+
placeholder="Type your message here..."
|
|
47
|
+
required
|
|
48
|
+
></textarea>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
51
|
+
<div class="btn-group" role="group">
|
|
52
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="showCommandMenu()">
|
|
53
|
+
<i class="bi bi-terminal"></i> Commands
|
|
54
|
+
</button>
|
|
55
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="attachFiles()">
|
|
56
|
+
<i class="bi bi-paperclip"></i> Attach
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
<button type="submit" class="btn btn-primary" id="send-btn">
|
|
60
|
+
<i class="bi bi-send-fill"></i> Send
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</form>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Info Modal -->
|
|
70
|
+
<div class="modal fade" id="infoModal" tabindex="-1">
|
|
71
|
+
<div class="modal-dialog modal-lg">
|
|
72
|
+
<div class="modal-content">
|
|
73
|
+
<div class="modal-header">
|
|
74
|
+
<h5 class="modal-title"><i class="bi bi-info-circle"></i> Conversation Information</h5>
|
|
75
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="modal-body" id="info-content">
|
|
78
|
+
<div class="text-center">
|
|
79
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
80
|
+
<span class="visually-hidden">Loading...</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
// Load conversation info when modal is shown
|
|
90
|
+
document.getElementById('infoModal').addEventListener('show.bs.modal', function() {
|
|
91
|
+
loadConversationInfo();
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<!-- Command Menu Modal -->
|
|
96
|
+
<div class="modal fade" id="commandModal" tabindex="-1">
|
|
97
|
+
<div class="modal-dialog">
|
|
98
|
+
<div class="modal-content">
|
|
99
|
+
<div class="modal-header">
|
|
100
|
+
<h5 class="modal-title"><i class="bi bi-terminal"></i> Commands</h5>
|
|
101
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="modal-body">
|
|
104
|
+
<div class="list-group">
|
|
105
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('history')">
|
|
106
|
+
<i class="bi bi-clock-history"></i> View History
|
|
107
|
+
</button>
|
|
108
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('info')">
|
|
109
|
+
<i class="bi bi-info-circle"></i> Show Info
|
|
110
|
+
</button>
|
|
111
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('changemodel')">
|
|
112
|
+
<i class="bi bi-arrow-repeat"></i> Change Model
|
|
113
|
+
</button>
|
|
114
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('export')">
|
|
115
|
+
<i class="bi bi-download"></i> Export Conversation
|
|
116
|
+
</button>
|
|
117
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('mcpaudit')">
|
|
118
|
+
<i class="bi bi-shield-check"></i> MCP Audit Log
|
|
119
|
+
</button>
|
|
120
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('mcpservers')">
|
|
121
|
+
<i class="bi bi-hdd-network"></i> MCP Server Management
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
{% endblock %}
|
|
129
|
+
|
|
130
|
+
{% block extra_scripts %}
|
|
131
|
+
<script src="/static/js/chat.js"></script>
|
|
132
|
+
<script src="/static/js/sse-client.js"></script>
|
|
133
|
+
<script>
|
|
134
|
+
// Initialise chat with conversation ID
|
|
135
|
+
const conversationId = {{ conversation_id }};
|
|
136
|
+
|
|
137
|
+
// Load chat history on page load
|
|
138
|
+
window.addEventListener('load', () => {
|
|
139
|
+
loadChatHistory(conversationId);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Handle chat form submission
|
|
143
|
+
document.getElementById('chat-form').addEventListener('submit', async (e) => {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
|
|
146
|
+
const messageInput = document.getElementById('message-input');
|
|
147
|
+
const message = messageInput.value.trim();
|
|
148
|
+
|
|
149
|
+
if (!message) return;
|
|
150
|
+
|
|
151
|
+
// Clear input
|
|
152
|
+
messageInput.value = '';
|
|
153
|
+
|
|
154
|
+
// Disable send button
|
|
155
|
+
const sendBtn = document.getElementById('send-btn');
|
|
156
|
+
sendBtn.disabled = true;
|
|
157
|
+
sendBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> Sending...';
|
|
158
|
+
|
|
159
|
+
// Add user message to chat
|
|
160
|
+
appendMessage('user', message);
|
|
161
|
+
|
|
162
|
+
// Send message via SSE
|
|
163
|
+
await sendMessageWithSSE(conversationId, message);
|
|
164
|
+
|
|
165
|
+
// Re-enable send button
|
|
166
|
+
sendBtn.disabled = false;
|
|
167
|
+
sendBtn.innerHTML = '<i class="bi bi-send-fill"></i> Send';
|
|
168
|
+
|
|
169
|
+
// Focus on input
|
|
170
|
+
messageInput.focus();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Show command menu
|
|
174
|
+
function showCommandMenu() {
|
|
175
|
+
const modal = new bootstrap.Modal(document.getElementById('commandModal'));
|
|
176
|
+
modal.show();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Execute command
|
|
180
|
+
async function executeCommand(command) {
|
|
181
|
+
// Close modal
|
|
182
|
+
bootstrap.Modal.getInstance(document.getElementById('commandModal')).hide();
|
|
183
|
+
|
|
184
|
+
// Execute command based on type
|
|
185
|
+
switch (command) {
|
|
186
|
+
case 'info':
|
|
187
|
+
await loadConversationInfo();
|
|
188
|
+
const infoModal = new bootstrap.Modal(document.getElementById('infoModal'));
|
|
189
|
+
infoModal.show();
|
|
190
|
+
break;
|
|
191
|
+
case 'history':
|
|
192
|
+
await loadChatHistory(conversationId);
|
|
193
|
+
break;
|
|
194
|
+
case 'export':
|
|
195
|
+
await showExportModal();
|
|
196
|
+
break;
|
|
197
|
+
case 'changemodel':
|
|
198
|
+
await showChangeModelModal();
|
|
199
|
+
break;
|
|
200
|
+
case 'mcpaudit':
|
|
201
|
+
await showMCPAuditModal();
|
|
202
|
+
break;
|
|
203
|
+
case 'mcpservers':
|
|
204
|
+
await showMCPServersModal();
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
alert(`Command '${command}' not yet implemented in web UI`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Export conversation
|
|
212
|
+
async function showExportModal() {
|
|
213
|
+
const modalHTML = `
|
|
214
|
+
<div class="modal fade" id="exportModal" tabindex="-1">
|
|
215
|
+
<div class="modal-dialog">
|
|
216
|
+
<div class="modal-content">
|
|
217
|
+
<div class="modal-header">
|
|
218
|
+
<h5 class="modal-title"><i class="bi bi-download"></i> Export Conversation</h5>
|
|
219
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="modal-body">
|
|
222
|
+
<div class="mb-3">
|
|
223
|
+
<label class="form-label">Export Format</label>
|
|
224
|
+
<select class="form-select" id="export-format">
|
|
225
|
+
<option value="markdown">Markdown (.md)</option>
|
|
226
|
+
<option value="html">HTML (.html)</option>
|
|
227
|
+
<option value="csv">CSV (.csv)</option>
|
|
228
|
+
</select>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="form-check">
|
|
231
|
+
<input class="form-check-input" type="checkbox" id="export-include-tools" checked>
|
|
232
|
+
<label class="form-check-label" for="export-include-tools">
|
|
233
|
+
Include tool call details
|
|
234
|
+
</label>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="modal-footer">
|
|
238
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
239
|
+
<button type="button" class="btn btn-primary" onclick="performExport()">
|
|
240
|
+
<i class="bi bi-download"></i> Export
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
// Remove old modal if exists
|
|
249
|
+
const oldModal = document.getElementById('exportModal');
|
|
250
|
+
if (oldModal) oldModal.remove();
|
|
251
|
+
|
|
252
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
253
|
+
const modal = new bootstrap.Modal(document.getElementById('exportModal'));
|
|
254
|
+
modal.show();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function performExport() {
|
|
258
|
+
const format = document.getElementById('export-format').value;
|
|
259
|
+
const includeTools = document.getElementById('export-include-tools').checked;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const formData = new FormData();
|
|
263
|
+
formData.append('format', format);
|
|
264
|
+
formData.append('include_tools', includeTools);
|
|
265
|
+
|
|
266
|
+
const response = await fetch(`/api/chat/${conversationId}/command/export`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
body: formData
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
|
|
273
|
+
if (data.status === 'success') {
|
|
274
|
+
// Download file
|
|
275
|
+
const blob = new Blob([data.content], { type: 'text/plain' });
|
|
276
|
+
const url = window.URL.createObjectURL(blob);
|
|
277
|
+
const a = document.createElement('a');
|
|
278
|
+
a.href = url;
|
|
279
|
+
a.download = `conversation_${conversationId}.${data.format === 'markdown' ? 'md' : data.format}`;
|
|
280
|
+
document.body.appendChild(a);
|
|
281
|
+
a.click();
|
|
282
|
+
window.URL.revokeObjectURL(url);
|
|
283
|
+
a.remove();
|
|
284
|
+
|
|
285
|
+
// Close modal
|
|
286
|
+
bootstrap.Modal.getInstance(document.getElementById('exportModal')).hide();
|
|
287
|
+
showToast('Conversation exported successfully', 'success');
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
showToast('Failed to export conversation', 'error');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Change model
|
|
295
|
+
async function showChangeModelModal() {
|
|
296
|
+
const modalHTML = `
|
|
297
|
+
<div class="modal fade" id="changeModelModal" tabindex="-1">
|
|
298
|
+
<div class="modal-dialog">
|
|
299
|
+
<div class="modal-content">
|
|
300
|
+
<div class="modal-header">
|
|
301
|
+
<h5 class="modal-title"><i class="bi bi-arrow-repeat"></i> Change Model</h5>
|
|
302
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="modal-body">
|
|
305
|
+
<div class="mb-3">
|
|
306
|
+
<label class="form-label">Select New Model</label>
|
|
307
|
+
<select class="form-select" id="new-model-select">
|
|
308
|
+
<option value="">Loading models...</option>
|
|
309
|
+
</select>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="modal-footer">
|
|
313
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
314
|
+
<button type="button" class="btn btn-primary" onclick="performChangeModel()">
|
|
315
|
+
<i class="bi bi-check-circle-fill"></i> Change Model
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
`;
|
|
322
|
+
|
|
323
|
+
// Remove old modal if exists
|
|
324
|
+
const oldModal = document.getElementById('changeModelModal');
|
|
325
|
+
if (oldModal) oldModal.remove();
|
|
326
|
+
|
|
327
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
328
|
+
|
|
329
|
+
// Load models
|
|
330
|
+
try {
|
|
331
|
+
const response = await fetch('/api/models');
|
|
332
|
+
const models = await response.json();
|
|
333
|
+
|
|
334
|
+
const select = document.getElementById('new-model-select');
|
|
335
|
+
select.innerHTML = '<option value="">Select a model...</option>';
|
|
336
|
+
|
|
337
|
+
models.forEach(model => {
|
|
338
|
+
const option = document.createElement('option');
|
|
339
|
+
option.value = model.id;
|
|
340
|
+
|
|
341
|
+
// Show service provider and optionally model maker
|
|
342
|
+
let displayText = model.name;
|
|
343
|
+
if (model.model_maker && model.provider !== model.model_maker) {
|
|
344
|
+
// Show both: "Model Name [Model Maker via Service Provider]"
|
|
345
|
+
displayText += ` [${model.model_maker} via ${model.provider}]`;
|
|
346
|
+
} else {
|
|
347
|
+
// Show service only: "Model Name [Service Provider]"
|
|
348
|
+
displayText += ` [${model.provider}]`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
option.textContent = displayText;
|
|
352
|
+
select.appendChild(option);
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
document.getElementById('new-model-select').innerHTML = '<option value="">Failed to load models</option>';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const modal = new bootstrap.Modal(document.getElementById('changeModelModal'));
|
|
359
|
+
modal.show();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function performChangeModel() {
|
|
363
|
+
const modelId = document.getElementById('new-model-select').value;
|
|
364
|
+
|
|
365
|
+
if (!modelId) {
|
|
366
|
+
showToast('Please select a model', 'error');
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const formData = new FormData();
|
|
372
|
+
formData.append('model_id', modelId);
|
|
373
|
+
|
|
374
|
+
const response = await fetch(`/api/chat/${conversationId}/command/changemodel`, {
|
|
375
|
+
method: 'POST',
|
|
376
|
+
body: formData
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const data = await response.json();
|
|
380
|
+
|
|
381
|
+
if (data.status === 'success') {
|
|
382
|
+
bootstrap.Modal.getInstance(document.getElementById('changeModelModal')).hide();
|
|
383
|
+
showToast('Model changed successfully', 'success');
|
|
384
|
+
}
|
|
385
|
+
} catch (error) {
|
|
386
|
+
showToast('Failed to change model', 'error');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// MCP Audit Log
|
|
391
|
+
async function showMCPAuditModal() {
|
|
392
|
+
const modalHTML = `
|
|
393
|
+
<div class="modal fade" id="mcpAuditModal" tabindex="-1">
|
|
394
|
+
<div class="modal-dialog modal-xl">
|
|
395
|
+
<div class="modal-content">
|
|
396
|
+
<div class="modal-header">
|
|
397
|
+
<h5 class="modal-title"><i class="bi bi-shield-check"></i> MCP Audit Log</h5>
|
|
398
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="modal-body" id="mcp-audit-content">
|
|
401
|
+
<div class="text-center">
|
|
402
|
+
<div class="spinner-border spinner-border-sm" role="status"></div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
`;
|
|
409
|
+
|
|
410
|
+
// Remove old modal if exists
|
|
411
|
+
const oldModal = document.getElementById('mcpAuditModal');
|
|
412
|
+
if (oldModal) oldModal.remove();
|
|
413
|
+
|
|
414
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
415
|
+
|
|
416
|
+
// Load audit log
|
|
417
|
+
try {
|
|
418
|
+
const response = await fetch(`/api/chat/${conversationId}/command/mcpaudit`);
|
|
419
|
+
const data = await response.json();
|
|
420
|
+
|
|
421
|
+
const transactions = data.data.transactions;
|
|
422
|
+
|
|
423
|
+
let html = '';
|
|
424
|
+
if (transactions.length === 0) {
|
|
425
|
+
html = '<p class="text-muted">No MCP transactions recorded</p>';
|
|
426
|
+
} else {
|
|
427
|
+
html = `
|
|
428
|
+
<div class="table-responsive">
|
|
429
|
+
<table class="table table-sm table-striped">
|
|
430
|
+
<thead>
|
|
431
|
+
<tr>
|
|
432
|
+
<th>Time</th>
|
|
433
|
+
<th>Tool</th>
|
|
434
|
+
<th>Server</th>
|
|
435
|
+
<th>Status</th>
|
|
436
|
+
<th>Exec Time</th>
|
|
437
|
+
</tr>
|
|
438
|
+
</thead>
|
|
439
|
+
<tbody>
|
|
440
|
+
`;
|
|
441
|
+
|
|
442
|
+
transactions.forEach(tx => {
|
|
443
|
+
const timestamp = new Date(tx.transaction_timestamp).toLocaleString();
|
|
444
|
+
const status = tx.is_error ? '<span class="badge bg-danger">Error</span>' : '<span class="badge bg-success">Success</span>';
|
|
445
|
+
|
|
446
|
+
html += `
|
|
447
|
+
<tr>
|
|
448
|
+
<td class="small">${timestamp}</td>
|
|
449
|
+
<td><code class="small">${escapeHtml(tx.tool_name)}</code></td>
|
|
450
|
+
<td class="small">${escapeHtml(tx.tool_server)}</td>
|
|
451
|
+
<td>${status}</td>
|
|
452
|
+
<td class="small">${tx.execution_time_ms}ms</td>
|
|
453
|
+
</tr>
|
|
454
|
+
`;
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
html += `
|
|
458
|
+
</tbody>
|
|
459
|
+
</table>
|
|
460
|
+
</div>
|
|
461
|
+
`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
document.getElementById('mcp-audit-content').innerHTML = html;
|
|
465
|
+
} catch (error) {
|
|
466
|
+
document.getElementById('mcp-audit-content').innerHTML = `
|
|
467
|
+
<div class="alert alert-danger">Failed to load MCP audit log</div>
|
|
468
|
+
`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const modal = new bootstrap.Modal(document.getElementById('mcpAuditModal'));
|
|
472
|
+
modal.show();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// MCP Server Management
|
|
476
|
+
async function showMCPServersModal() {
|
|
477
|
+
const modalHTML = `
|
|
478
|
+
<div class="modal fade" id="mcpServersModal" tabindex="-1">
|
|
479
|
+
<div class="modal-dialog modal-lg">
|
|
480
|
+
<div class="modal-content">
|
|
481
|
+
<div class="modal-header">
|
|
482
|
+
<h5 class="modal-title"><i class="bi bi-hdd-network"></i> MCP Server Management</h5>
|
|
483
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="modal-body" id="mcp-servers-content">
|
|
486
|
+
<div class="text-center">
|
|
487
|
+
<div class="spinner-border spinner-border-sm" role="status"></div>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
`;
|
|
494
|
+
|
|
495
|
+
// Remove old modal if exists
|
|
496
|
+
const oldModal = document.getElementById('mcpServersModal');
|
|
497
|
+
if (oldModal) oldModal.remove();
|
|
498
|
+
|
|
499
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
500
|
+
|
|
501
|
+
// Load server states
|
|
502
|
+
try {
|
|
503
|
+
const response = await fetch(`/api/chat/${conversationId}/command/mcpservers`);
|
|
504
|
+
const data = await response.json();
|
|
505
|
+
|
|
506
|
+
const servers = data.data.servers;
|
|
507
|
+
|
|
508
|
+
let html = '';
|
|
509
|
+
if (servers.length === 0) {
|
|
510
|
+
html = '<p class="text-muted">No MCP servers configured</p>';
|
|
511
|
+
} else {
|
|
512
|
+
html = '<div class="list-group">';
|
|
513
|
+
|
|
514
|
+
servers.forEach(server => {
|
|
515
|
+
const enabled = server.enabled;
|
|
516
|
+
const badge = enabled ?
|
|
517
|
+
'<span class="badge bg-success">Enabled</span>' :
|
|
518
|
+
'<span class="badge bg-secondary">Disabled</span>';
|
|
519
|
+
|
|
520
|
+
html += `
|
|
521
|
+
<div class="list-group-item">
|
|
522
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
523
|
+
<div>
|
|
524
|
+
<h6 class="mb-1">${escapeHtml(server.server_name)}</h6>
|
|
525
|
+
${badge}
|
|
526
|
+
</div>
|
|
527
|
+
<div class="form-check form-switch">
|
|
528
|
+
<input
|
|
529
|
+
class="form-check-input"
|
|
530
|
+
type="checkbox"
|
|
531
|
+
id="server-${escapeHtml(server.server_name)}"
|
|
532
|
+
${enabled ? 'checked' : ''}
|
|
533
|
+
onchange="toggleMCPServer('${escapeHtml(server.server_name)}', this.checked)"
|
|
534
|
+
>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
`;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
html += '</div>';
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
document.getElementById('mcp-servers-content').innerHTML = html;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
document.getElementById('mcp-servers-content').innerHTML = `
|
|
547
|
+
<div class="alert alert-danger">Failed to load MCP servers</div>
|
|
548
|
+
`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const modal = new bootstrap.Modal(document.getElementById('mcpServersModal'));
|
|
552
|
+
modal.show();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function toggleMCPServer(serverName, enabled) {
|
|
556
|
+
try {
|
|
557
|
+
const formData = new FormData();
|
|
558
|
+
formData.append('server_name', serverName);
|
|
559
|
+
formData.append('enabled', enabled);
|
|
560
|
+
|
|
561
|
+
const response = await fetch(`/api/chat/${conversationId}/command/mcpservers/toggle`, {
|
|
562
|
+
method: 'POST',
|
|
563
|
+
body: formData
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const data = await response.json();
|
|
567
|
+
|
|
568
|
+
if (data.status === 'success') {
|
|
569
|
+
showToast(`Server ${serverName} ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
|
570
|
+
}
|
|
571
|
+
} catch (error) {
|
|
572
|
+
showToast('Failed to toggle server', 'error');
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Load conversation info
|
|
577
|
+
async function loadConversationInfo() {
|
|
578
|
+
try {
|
|
579
|
+
document.getElementById('info-content').innerHTML = `
|
|
580
|
+
<div class="text-center">
|
|
581
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
582
|
+
<span class="visually-hidden">Loading...</span>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
`;
|
|
586
|
+
|
|
587
|
+
const response = await fetch(`/api/chat/${conversationId}/command/info`, {
|
|
588
|
+
method: 'POST'
|
|
589
|
+
});
|
|
590
|
+
const data = await response.json();
|
|
591
|
+
|
|
592
|
+
const conv = data.data.conversation;
|
|
593
|
+
const modelUsage = data.data.model_usage;
|
|
594
|
+
const rollupSettings = data.data.rollup_settings;
|
|
595
|
+
|
|
596
|
+
let infoHTML = `
|
|
597
|
+
<div class="mb-3">
|
|
598
|
+
<h6>Conversation Details</h6>
|
|
599
|
+
<ul class="list-unstyled small">
|
|
600
|
+
<li><strong>Name:</strong> ${conv.name}</li>
|
|
601
|
+
<li><strong>Model:</strong> <code>${conv.model_id}</code></li>
|
|
602
|
+
<li><strong>Created:</strong> ${new Date(conv.created_at).toLocaleString()}</li>
|
|
603
|
+
<li><strong>Total Tokens:</strong> ${conv.total_tokens.toLocaleString()}</li>
|
|
604
|
+
</ul>
|
|
605
|
+
</div>
|
|
606
|
+
`;
|
|
607
|
+
|
|
608
|
+
if (modelUsage && modelUsage.length > 0) {
|
|
609
|
+
infoHTML += `
|
|
610
|
+
<div class="mb-3">
|
|
611
|
+
<h6>Model Usage</h6>
|
|
612
|
+
<div class="table-responsive">
|
|
613
|
+
<table class="table table-sm">
|
|
614
|
+
<thead>
|
|
615
|
+
<tr>
|
|
616
|
+
<th>Model</th>
|
|
617
|
+
<th>Input</th>
|
|
618
|
+
<th>Output</th>
|
|
619
|
+
<th>Total</th>
|
|
620
|
+
</tr>
|
|
621
|
+
</thead>
|
|
622
|
+
<tbody>
|
|
623
|
+
`;
|
|
624
|
+
|
|
625
|
+
modelUsage.forEach(usage => {
|
|
626
|
+
infoHTML += `
|
|
627
|
+
<tr>
|
|
628
|
+
<td><code class="small">${usage.model_id}</code></td>
|
|
629
|
+
<td>${usage.input_tokens.toLocaleString()}</td>
|
|
630
|
+
<td>${usage.output_tokens.toLocaleString()}</td>
|
|
631
|
+
<td>${usage.total_tokens.toLocaleString()}</td>
|
|
632
|
+
</tr>
|
|
633
|
+
`;
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
infoHTML += `
|
|
637
|
+
</tbody>
|
|
638
|
+
</table>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Add rollup/compaction settings
|
|
645
|
+
if (rollupSettings && !rollupSettings.error) {
|
|
646
|
+
const usagePercent = rollupSettings.context_usage_percent || 0;
|
|
647
|
+
const compactionThresholdPercent = (rollupSettings.compaction_threshold * 100).toFixed(0);
|
|
648
|
+
const emergencyThresholdPercent = (rollupSettings.emergency_threshold * 100).toFixed(0);
|
|
649
|
+
|
|
650
|
+
// Determine status colour based on usage
|
|
651
|
+
let statusClass = 'text-success';
|
|
652
|
+
let statusText = 'Normal';
|
|
653
|
+
if (usagePercent >= rollupSettings.emergency_threshold * 100) {
|
|
654
|
+
statusClass = 'text-danger';
|
|
655
|
+
statusText = 'Emergency';
|
|
656
|
+
} else if (usagePercent >= rollupSettings.compaction_threshold * 100) {
|
|
657
|
+
statusClass = 'text-warning';
|
|
658
|
+
statusText = 'Compaction Due';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
infoHTML += `
|
|
662
|
+
<div class="mb-3">
|
|
663
|
+
<h6>Context & Rollup Settings</h6>
|
|
664
|
+
<div class="small">
|
|
665
|
+
<div class="mb-2">
|
|
666
|
+
<strong>Context Usage:</strong>
|
|
667
|
+
<span class="${statusClass}">${usagePercent}%</span>
|
|
668
|
+
<span class="text-muted">(${rollupSettings.current_tokens?.toLocaleString() || 0} / ${rollupSettings.context_window?.toLocaleString() || 0} tokens)</span>
|
|
669
|
+
<span class="badge ${statusClass === 'text-success' ? 'bg-success' : statusClass === 'text-warning' ? 'bg-warning text-dark' : 'bg-danger'}">${statusText}</span>
|
|
670
|
+
</div>
|
|
671
|
+
<div class="progress mb-2" style="height: 8px;">
|
|
672
|
+
<div class="progress-bar ${usagePercent >= rollupSettings.emergency_threshold * 100 ? 'bg-danger' : usagePercent >= rollupSettings.compaction_threshold * 100 ? 'bg-warning' : 'bg-success'}"
|
|
673
|
+
role="progressbar"
|
|
674
|
+
style="width: ${Math.min(usagePercent, 100)}%">
|
|
675
|
+
</div>
|
|
676
|
+
<div class="position-absolute" style="left: ${compactionThresholdPercent}%; width: 2px; height: 8px; background-color: orange; top: 0;"></div>
|
|
677
|
+
<div class="position-absolute" style="left: ${emergencyThresholdPercent}%; width: 2px; height: 8px; background-color: red; top: 0;"></div>
|
|
678
|
+
</div>
|
|
679
|
+
<ul class="list-unstyled mb-0">
|
|
680
|
+
<li><strong>Context Window:</strong> ${rollupSettings.context_window?.toLocaleString() || 'Unknown'} tokens</li>
|
|
681
|
+
<li><strong>Max Output:</strong> ${rollupSettings.max_output?.toLocaleString() || 'Unknown'} tokens</li>
|
|
682
|
+
<li><strong>Compaction Threshold:</strong> ${compactionThresholdPercent}% (${rollupSettings.compaction_trigger_tokens?.toLocaleString() || 0} tokens)</li>
|
|
683
|
+
<li><strong>Emergency Threshold:</strong> ${emergencyThresholdPercent}% (${rollupSettings.emergency_trigger_tokens?.toLocaleString() || 0} tokens)</li>
|
|
684
|
+
<li><strong>Provider:</strong> ${rollupSettings.provider || 'Unknown'}</li>
|
|
685
|
+
</ul>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
`;
|
|
689
|
+
} else if (rollupSettings && rollupSettings.error) {
|
|
690
|
+
infoHTML += `
|
|
691
|
+
<div class="mb-3">
|
|
692
|
+
<h6>Context & Rollup Settings</h6>
|
|
693
|
+
<p class="text-muted small">Unable to load rollup settings: ${rollupSettings.error}</p>
|
|
694
|
+
</div>
|
|
695
|
+
`;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
document.getElementById('info-content').innerHTML = infoHTML;
|
|
699
|
+
|
|
700
|
+
} catch (error) {
|
|
701
|
+
document.getElementById('info-content').innerHTML = `
|
|
702
|
+
<div class="alert alert-danger">
|
|
703
|
+
<i class="bi bi-exclamation-triangle-fill"></i> Failed to load conversation info
|
|
704
|
+
</div>
|
|
705
|
+
`;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Attach files
|
|
710
|
+
function showAttachFilesModal() {
|
|
711
|
+
const modalHTML = `
|
|
712
|
+
<div class="modal fade" id="attachFilesModal" tabindex="-1">
|
|
713
|
+
<div class="modal-dialog">
|
|
714
|
+
<div class="modal-content">
|
|
715
|
+
<div class="modal-header">
|
|
716
|
+
<h5 class="modal-title"><i class="bi bi-paperclip"></i> Attach Files</h5>
|
|
717
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="modal-body">
|
|
720
|
+
<div class="mb-3">
|
|
721
|
+
<label class="form-label">Select files to attach</label>
|
|
722
|
+
<input type="file" class="form-control" id="attach-files-input" multiple>
|
|
723
|
+
<div class="form-text">
|
|
724
|
+
Supported formats: txt, md, json, yaml, yml, xml, csv, log, py, js, ts, java, cpp, c, h
|
|
725
|
+
</div>
|
|
726
|
+
</div>
|
|
727
|
+
<div id="attach-files-list" class="mb-3"></div>
|
|
728
|
+
</div>
|
|
729
|
+
<div class="modal-footer">
|
|
730
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
731
|
+
<button type="button" class="btn btn-primary" onclick="performAttachFiles()">
|
|
732
|
+
<i class="bi bi-paperclip"></i> Attach Files
|
|
733
|
+
</button>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
`;
|
|
739
|
+
|
|
740
|
+
// Remove old modal if exists
|
|
741
|
+
const oldModal = document.getElementById('attachFilesModal');
|
|
742
|
+
if (oldModal) oldModal.remove();
|
|
743
|
+
|
|
744
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
745
|
+
|
|
746
|
+
// Add file selection change listener
|
|
747
|
+
const fileInput = document.getElementById('attach-files-input');
|
|
748
|
+
fileInput.addEventListener('change', function() {
|
|
749
|
+
const filesList = document.getElementById('attach-files-list');
|
|
750
|
+
if (this.files.length > 0) {
|
|
751
|
+
let html = '<div class="alert alert-info"><strong>Selected files:</strong><ul class="mb-0 mt-2">';
|
|
752
|
+
for (let i = 0; i < this.files.length; i++) {
|
|
753
|
+
html += `<li>${this.files[i].name} (${(this.files[i].size / 1024).toFixed(2)} KB)</li>`;
|
|
754
|
+
}
|
|
755
|
+
html += '</ul></div>';
|
|
756
|
+
filesList.innerHTML = html;
|
|
757
|
+
} else {
|
|
758
|
+
filesList.innerHTML = '';
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
const modal = new bootstrap.Modal(document.getElementById('attachFilesModal'));
|
|
763
|
+
modal.show();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async function performAttachFiles() {
|
|
767
|
+
const fileInput = document.getElementById('attach-files-input');
|
|
768
|
+
|
|
769
|
+
if (!fileInput.files || fileInput.files.length === 0) {
|
|
770
|
+
showToast('Please select at least one file', 'error');
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
try {
|
|
775
|
+
const formData = new FormData();
|
|
776
|
+
|
|
777
|
+
// Add all selected files
|
|
778
|
+
for (let i = 0; i < fileInput.files.length; i++) {
|
|
779
|
+
formData.append('files', fileInput.files[i]);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const response = await fetch(`/api/chat/${conversationId}/command/attach`, {
|
|
783
|
+
method: 'POST',
|
|
784
|
+
body: formData
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
const data = await response.json();
|
|
788
|
+
|
|
789
|
+
if (data.status === 'success' || data.status === 'partial') {
|
|
790
|
+
// Close modal
|
|
791
|
+
bootstrap.Modal.getInstance(document.getElementById('attachFilesModal')).hide();
|
|
792
|
+
|
|
793
|
+
// Show success message
|
|
794
|
+
const message = data.status === 'success'
|
|
795
|
+
? `Successfully attached ${data.data.files.length} file(s)`
|
|
796
|
+
: `${data.message} - Some files may have failed`;
|
|
797
|
+
showToast(message, data.status === 'success' ? 'success' : 'warning');
|
|
798
|
+
|
|
799
|
+
// Reload conversation info to show updated file list
|
|
800
|
+
await loadConversationInfo();
|
|
801
|
+
} else {
|
|
802
|
+
showToast(data.message || 'Failed to attach files', 'error');
|
|
803
|
+
}
|
|
804
|
+
} catch (error) {
|
|
805
|
+
console.error('Error attaching files:', error);
|
|
806
|
+
showToast('Failed to attach files', 'error');
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function attachFiles() {
|
|
811
|
+
showAttachFilesModal();
|
|
812
|
+
}
|
|
813
|
+
</script>
|
|
814
|
+
{% endblock %}
|