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,350 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Conversations - {{ app_name }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block extra_head %}
|
|
6
|
+
<style>
|
|
7
|
+
.list-group-item .flex-grow-1:hover {
|
|
8
|
+
background-color: rgba(0, 123, 255, 0.05);
|
|
9
|
+
border-radius: 0.25rem;
|
|
10
|
+
}
|
|
11
|
+
.list-group-item {
|
|
12
|
+
transition: background-color 0.2s ease;
|
|
13
|
+
}
|
|
14
|
+
</style>
|
|
15
|
+
{% endblock %}
|
|
16
|
+
|
|
17
|
+
{% block content %}
|
|
18
|
+
<div class="row">
|
|
19
|
+
<div class="col-12">
|
|
20
|
+
<h2 class="mb-4">
|
|
21
|
+
<i class="bi bi-list-ul"></i> Conversations
|
|
22
|
+
<button class="btn btn-primary float-end" onclick="showNewConversationModal()">
|
|
23
|
+
<i class="bi bi-plus-circle-fill"></i> New Conversation
|
|
24
|
+
</button>
|
|
25
|
+
</h2>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- Conversations List -->
|
|
30
|
+
<div class="row">
|
|
31
|
+
<div class="col-12">
|
|
32
|
+
<div class="card">
|
|
33
|
+
<div class="card-body" id="conversations-list">
|
|
34
|
+
<div class="text-center">
|
|
35
|
+
<div class="spinner-border text-primary" role="status">
|
|
36
|
+
<span class="visually-hidden">Loading...</span>
|
|
37
|
+
</div>
|
|
38
|
+
<p class="mt-2">Loading conversations...</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- Delete Confirmation Modal -->
|
|
46
|
+
<div class="modal fade" id="deleteConversationModal" tabindex="-1">
|
|
47
|
+
<div class="modal-dialog">
|
|
48
|
+
<div class="modal-content">
|
|
49
|
+
<div class="modal-header">
|
|
50
|
+
<h5 class="modal-title"><i class="bi bi-exclamation-triangle-fill text-danger"></i> Delete Conversation</h5>
|
|
51
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="modal-body">
|
|
54
|
+
<p>Are you sure you want to delete this conversation?</p>
|
|
55
|
+
<p class="mb-0"><strong id="delete-conversation-name"></strong></p>
|
|
56
|
+
<p class="text-danger small mt-2">
|
|
57
|
+
<i class="bi bi-exclamation-circle"></i> This action cannot be undone. All messages and conversation history will be permanently deleted.
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="modal-footer">
|
|
61
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
62
|
+
Cancel
|
|
63
|
+
</button>
|
|
64
|
+
<button type="button" class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete()">
|
|
65
|
+
<i class="bi bi-trash-fill"></i> Delete Conversation
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- New Conversation Modal -->
|
|
73
|
+
<div class="modal fade" id="newConversationModal" tabindex="-1">
|
|
74
|
+
<div class="modal-dialog modal-lg">
|
|
75
|
+
<div class="modal-content">
|
|
76
|
+
<div class="modal-header">
|
|
77
|
+
<h5 class="modal-title"><i class="bi bi-plus-circle-fill"></i> New Conversation</h5>
|
|
78
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="modal-body">
|
|
81
|
+
<form id="new-conversation-form">
|
|
82
|
+
<div class="mb-3">
|
|
83
|
+
<label for="conversation-name" class="form-label">Conversation Name</label>
|
|
84
|
+
<input
|
|
85
|
+
type="text"
|
|
86
|
+
class="form-control"
|
|
87
|
+
id="conversation-name"
|
|
88
|
+
name="name"
|
|
89
|
+
placeholder="Enter a name for this conversation"
|
|
90
|
+
required
|
|
91
|
+
>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="mb-3">
|
|
95
|
+
<label for="model-select" class="form-label">Model</label>
|
|
96
|
+
<select class="form-select" id="model-select" name="model_id" required>
|
|
97
|
+
<option value="">Loading models...</option>
|
|
98
|
+
</select>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="mb-3">
|
|
102
|
+
<label for="instructions" class="form-label">
|
|
103
|
+
Instructions / System Prompt
|
|
104
|
+
<span class="text-muted">(Optional)</span>
|
|
105
|
+
</label>
|
|
106
|
+
<textarea
|
|
107
|
+
class="form-control"
|
|
108
|
+
id="instructions"
|
|
109
|
+
name="instructions"
|
|
110
|
+
rows="4"
|
|
111
|
+
placeholder="Enter system instructions for this conversation..."
|
|
112
|
+
></textarea>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="mb-3">
|
|
116
|
+
<label for="file-attachments" class="form-label">
|
|
117
|
+
File Attachments
|
|
118
|
+
<span class="text-muted">(Optional)</span>
|
|
119
|
+
</label>
|
|
120
|
+
<input
|
|
121
|
+
class="form-control"
|
|
122
|
+
type="file"
|
|
123
|
+
id="file-attachments"
|
|
124
|
+
name="files"
|
|
125
|
+
multiple
|
|
126
|
+
>
|
|
127
|
+
<div class="form-text">
|
|
128
|
+
Supported: txt, md, json, yaml, xml, csv, log, py, js, ts, java, cpp, c, h
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="modal-footer">
|
|
134
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
135
|
+
Cancel
|
|
136
|
+
</button>
|
|
137
|
+
<button type="button" class="btn btn-primary" onclick="createConversation()">
|
|
138
|
+
<i class="bi bi-check-circle-fill"></i> Create Conversation
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
{% endblock %}
|
|
145
|
+
|
|
146
|
+
{% block extra_scripts %}
|
|
147
|
+
<script>
|
|
148
|
+
// Load conversations on page load
|
|
149
|
+
window.addEventListener('load', () => {
|
|
150
|
+
loadConversations();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Load conversations
|
|
154
|
+
async function loadConversations() {
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch('/api/conversations');
|
|
157
|
+
const conversations = await response.json();
|
|
158
|
+
|
|
159
|
+
if (conversations.length === 0) {
|
|
160
|
+
document.getElementById('conversations-list').innerHTML = `
|
|
161
|
+
<div class="text-center text-muted">
|
|
162
|
+
<i class="bi bi-inbox" style="font-size: 3rem;"></i>
|
|
163
|
+
<p class="mt-3">No conversations yet</p>
|
|
164
|
+
<button class="btn btn-primary" onclick="showNewConversationModal()">
|
|
165
|
+
<i class="bi bi-plus-circle-fill"></i> Create Your First Conversation
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
`;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let html = '<div class="list-group">';
|
|
173
|
+
conversations.forEach(conv => {
|
|
174
|
+
const createdDate = new Date(conv.created_at).toLocaleString();
|
|
175
|
+
const lastMessageDate = conv.last_message_at ?
|
|
176
|
+
new Date(conv.last_message_at).toLocaleString() :
|
|
177
|
+
'No messages yet';
|
|
178
|
+
|
|
179
|
+
html += `
|
|
180
|
+
<div class="list-group-item">
|
|
181
|
+
<div class="d-flex w-100 justify-content-between align-items-start">
|
|
182
|
+
<div class="flex-grow-1" style="cursor: pointer;" onclick="window.location.href='/chat/${conv.id}'">
|
|
183
|
+
<h6 class="mb-1">
|
|
184
|
+
<i class="bi bi-chat-dots"></i> ${conv.name}
|
|
185
|
+
</h6>
|
|
186
|
+
<p class="mb-1 small">
|
|
187
|
+
<strong>Model:</strong> <code>${conv.model_id}</code>
|
|
188
|
+
</p>
|
|
189
|
+
<small class="text-muted">
|
|
190
|
+
Created: ${createdDate} | Last message: ${lastMessageDate}
|
|
191
|
+
</small>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="d-flex flex-column align-items-end ms-3">
|
|
194
|
+
<small class="text-muted mb-2">${conv.message_count} messages</small>
|
|
195
|
+
<button class="btn btn-sm btn-outline-danger" onclick="deleteConversation(${conv.id}, '${conv.name.replace(/'/g, "\\'")}')">
|
|
196
|
+
<i class="bi bi-trash"></i> Delete
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
`;
|
|
202
|
+
});
|
|
203
|
+
html += '</div>';
|
|
204
|
+
|
|
205
|
+
document.getElementById('conversations-list').innerHTML = html;
|
|
206
|
+
|
|
207
|
+
} catch (error) {
|
|
208
|
+
document.getElementById('conversations-list').innerHTML = `
|
|
209
|
+
<div class="alert alert-danger">
|
|
210
|
+
<i class="bi bi-exclamation-triangle-fill"></i> Failed to load conversations
|
|
211
|
+
</div>
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Show new conversation modal
|
|
217
|
+
async function showNewConversationModal() {
|
|
218
|
+
// Load available models
|
|
219
|
+
await loadModels();
|
|
220
|
+
|
|
221
|
+
const modal = new bootstrap.Modal(document.getElementById('newConversationModal'));
|
|
222
|
+
modal.show();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Load available models
|
|
226
|
+
async function loadModels() {
|
|
227
|
+
try {
|
|
228
|
+
const response = await fetch('/api/models');
|
|
229
|
+
const models = await response.json();
|
|
230
|
+
|
|
231
|
+
const select = document.getElementById('model-select');
|
|
232
|
+
select.innerHTML = '<option value="">Select a model...</option>';
|
|
233
|
+
|
|
234
|
+
models.forEach(model => {
|
|
235
|
+
const option = document.createElement('option');
|
|
236
|
+
option.value = model.id;
|
|
237
|
+
|
|
238
|
+
// Show service provider and optionally model maker
|
|
239
|
+
let displayText = model.name;
|
|
240
|
+
if (model.model_maker && model.provider !== model.model_maker) {
|
|
241
|
+
// Show both: "Model Name [Model Maker via Service Provider]"
|
|
242
|
+
displayText += ` [${model.model_maker} via ${model.provider}]`;
|
|
243
|
+
} else {
|
|
244
|
+
// Show service only: "Model Name [Service Provider]"
|
|
245
|
+
displayText += ` [${model.provider}]`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
option.textContent = displayText;
|
|
249
|
+
select.appendChild(option);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
document.getElementById('model-select').innerHTML = `
|
|
254
|
+
<option value="">Failed to load models</option>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Create conversation
|
|
260
|
+
async function createConversation() {
|
|
261
|
+
const form = document.getElementById('new-conversation-form');
|
|
262
|
+
const formData = new FormData(form);
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
// Disable button
|
|
266
|
+
event.target.disabled = true;
|
|
267
|
+
event.target.innerHTML = '<i class="bi bi-hourglass-split"></i> Creating...';
|
|
268
|
+
|
|
269
|
+
const response = await fetch('/api/conversations', {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
body: formData
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
throw new Error('Failed to create conversation');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const conversation = await response.json();
|
|
279
|
+
|
|
280
|
+
// Close modal
|
|
281
|
+
bootstrap.Modal.getInstance(document.getElementById('newConversationModal')).hide();
|
|
282
|
+
|
|
283
|
+
// Redirect to chat
|
|
284
|
+
window.location.href = `/chat/${conversation.id}`;
|
|
285
|
+
|
|
286
|
+
} catch (error) {
|
|
287
|
+
alert('Failed to create conversation: ' + error.message);
|
|
288
|
+
|
|
289
|
+
// Re-enable button
|
|
290
|
+
event.target.disabled = false;
|
|
291
|
+
event.target.innerHTML = '<i class="bi bi-check-circle-fill"></i> Create Conversation';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Global variable to store conversation to delete
|
|
296
|
+
let conversationToDelete = null;
|
|
297
|
+
|
|
298
|
+
// Show delete confirmation modal
|
|
299
|
+
function deleteConversation(conversationId, conversationName) {
|
|
300
|
+
conversationToDelete = conversationId;
|
|
301
|
+
document.getElementById('delete-conversation-name').textContent = conversationName;
|
|
302
|
+
|
|
303
|
+
const modal = new bootstrap.Modal(document.getElementById('deleteConversationModal'));
|
|
304
|
+
modal.show();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Confirm and execute deletion
|
|
308
|
+
async function confirmDelete() {
|
|
309
|
+
if (!conversationToDelete) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const deleteBtn = document.getElementById('confirm-delete-btn');
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
// Disable button
|
|
317
|
+
deleteBtn.disabled = true;
|
|
318
|
+
deleteBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> Deleting...';
|
|
319
|
+
|
|
320
|
+
const response = await fetch(`/api/conversations/${conversationToDelete}`, {
|
|
321
|
+
method: 'DELETE'
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
throw new Error('Failed to delete conversation');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Close modal
|
|
329
|
+
bootstrap.Modal.getInstance(document.getElementById('deleteConversationModal')).hide();
|
|
330
|
+
|
|
331
|
+
// Reset button state
|
|
332
|
+
deleteBtn.disabled = false;
|
|
333
|
+
deleteBtn.innerHTML = '<i class="bi bi-trash-fill"></i> Delete Conversation';
|
|
334
|
+
|
|
335
|
+
// Clear conversation to delete
|
|
336
|
+
conversationToDelete = null;
|
|
337
|
+
|
|
338
|
+
// Reload conversations list
|
|
339
|
+
await loadConversations();
|
|
340
|
+
|
|
341
|
+
} catch (error) {
|
|
342
|
+
alert('Failed to delete conversation: ' + error.message);
|
|
343
|
+
|
|
344
|
+
// Re-enable button
|
|
345
|
+
deleteBtn.disabled = false;
|
|
346
|
+
deleteBtn.innerHTML = '<i class="bi bi-trash-fill"></i> Delete Conversation';
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
</script>
|
|
350
|
+
{% endblock %}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Thank You - {{ app_name }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="row justify-content-center">
|
|
7
|
+
<div class="col-md-8 col-lg-6">
|
|
8
|
+
<div class="card mt-5 border-success">
|
|
9
|
+
<div class="card-body text-center py-5">
|
|
10
|
+
<div class="mb-4">
|
|
11
|
+
<i class="bi bi-check-circle-fill text-success" style="font-size: 4rem;"></i>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<h2 class="card-title mb-4">
|
|
15
|
+
{% if shutdown %}
|
|
16
|
+
<i class="bi bi-power"></i> Shutting Down
|
|
17
|
+
{% else %}
|
|
18
|
+
<i class="bi bi-wave"></i> Session Ended
|
|
19
|
+
{% endif %}
|
|
20
|
+
</h2>
|
|
21
|
+
|
|
22
|
+
<p class="lead mb-4">
|
|
23
|
+
Thank you for using Spark!
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
{% if shutdown %}
|
|
27
|
+
<div class="alert alert-info">
|
|
28
|
+
<i class="bi bi-info-circle-fill"></i> The web server is shutting down...
|
|
29
|
+
<div class="mt-2">
|
|
30
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
31
|
+
<span class="visually-hidden">Shutting down...</span>
|
|
32
|
+
</div>
|
|
33
|
+
<span class="ms-2">Please wait</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<p class="text-muted small mt-4">
|
|
38
|
+
You can close this browser window. The CLI will return to the command prompt.
|
|
39
|
+
</p>
|
|
40
|
+
{% else %}
|
|
41
|
+
<div class="alert alert-success">
|
|
42
|
+
<i class="bi bi-check-circle-fill"></i> Your session has been logged out successfully
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<p class="text-muted small mt-4">
|
|
46
|
+
The web server is still running. You can close this window or
|
|
47
|
+
<a href="/login" class="alert-link">log in again</a>.
|
|
48
|
+
</p>
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
51
|
+
<hr class="my-4">
|
|
52
|
+
|
|
53
|
+
<div class="text-muted small">
|
|
54
|
+
<i class="bi bi-chat-dots-fill"></i> SPARK
|
|
55
|
+
<br>
|
|
56
|
+
<span class="text-muted" style="font-size: 0.85em;">
|
|
57
|
+
Secure Personal AI Research Kit
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
{% endblock %}
|
|
65
|
+
|
|
66
|
+
{% block extra_scripts %}
|
|
67
|
+
{% if shutdown %}
|
|
68
|
+
<script>
|
|
69
|
+
// Trigger server shutdown after a short delay
|
|
70
|
+
setTimeout(function() {
|
|
71
|
+
fetch('/api/shutdown', { method: 'POST' })
|
|
72
|
+
.then(() => {
|
|
73
|
+
console.log('Shutdown request sent');
|
|
74
|
+
})
|
|
75
|
+
.catch((error) => {
|
|
76
|
+
console.error('Shutdown request failed:', error);
|
|
77
|
+
});
|
|
78
|
+
}, 1000);
|
|
79
|
+
</script>
|
|
80
|
+
{% endif %}
|
|
81
|
+
{% endblock %}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Login - {{ app_name }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="row justify-content-center">
|
|
7
|
+
<div class="col-md-6 col-lg-4">
|
|
8
|
+
<div class="card mt-5">
|
|
9
|
+
<div class="card-body">
|
|
10
|
+
<h3 class="card-title text-center mb-4">
|
|
11
|
+
<i class="bi bi-shield-lock-fill"></i> Authentication Required
|
|
12
|
+
</h3>
|
|
13
|
+
|
|
14
|
+
{% if error %}
|
|
15
|
+
<div class="alert alert-danger" role="alert">
|
|
16
|
+
<i class="bi bi-exclamation-triangle-fill"></i> {{ error }}
|
|
17
|
+
</div>
|
|
18
|
+
{% endif %}
|
|
19
|
+
|
|
20
|
+
<p class="text-muted text-center mb-4">
|
|
21
|
+
Enter the authentication code displayed in the CLI to access the web interface.
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<form method="POST" action="/login">
|
|
25
|
+
<div class="mb-3">
|
|
26
|
+
<label for="code" class="form-label">Authentication Code</label>
|
|
27
|
+
<input
|
|
28
|
+
type="text"
|
|
29
|
+
class="form-control form-control-lg text-center"
|
|
30
|
+
id="code"
|
|
31
|
+
name="code"
|
|
32
|
+
placeholder="Enter code"
|
|
33
|
+
required
|
|
34
|
+
autofocus
|
|
35
|
+
style="letter-spacing: 0.2em; font-family: monospace;"
|
|
36
|
+
value="{{ code }}"
|
|
37
|
+
>
|
|
38
|
+
<div class="form-text">
|
|
39
|
+
<i class="bi bi-info-circle"></i> This code can only be used once
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="d-grid">
|
|
44
|
+
<button type="submit" class="btn btn-primary btn-lg">
|
|
45
|
+
<i class="bi bi-key-fill"></i> Authenticate
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
</form>
|
|
49
|
+
|
|
50
|
+
<hr class="my-4">
|
|
51
|
+
|
|
52
|
+
<div class="text-center text-muted small">
|
|
53
|
+
<i class="bi bi-shield-check"></i> Localhost only - Single session
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="text-center mt-3 text-muted small">
|
|
59
|
+
<i class="bi bi-info-circle"></i> The authentication code is displayed when you start the web interface from the CLI
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
{% endblock %}
|
|
64
|
+
|
|
65
|
+
{% block extra_scripts %}
|
|
66
|
+
<script>
|
|
67
|
+
// Auto-focus on code input
|
|
68
|
+
document.getElementById('code').focus();
|
|
69
|
+
|
|
70
|
+
// Auto-capitalise input
|
|
71
|
+
document.getElementById('code').addEventListener('input', function(e) {
|
|
72
|
+
e.target.value = e.target.value.toUpperCase();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Auto-submit if code is pre-filled from URL
|
|
76
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
77
|
+
const codeInput = document.getElementById('code');
|
|
78
|
+
const form = codeInput.closest('form');
|
|
79
|
+
|
|
80
|
+
// Check if code is pre-filled and non-empty
|
|
81
|
+
if (codeInput.value && codeInput.value.trim() !== '') {
|
|
82
|
+
console.log('Code pre-filled from URL, auto-submitting form');
|
|
83
|
+
// Add a small delay to let the user see the page loaded
|
|
84
|
+
setTimeout(function() {
|
|
85
|
+
form.submit();
|
|
86
|
+
}, 500);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
{% endblock %}
|