mem-llm 2.0.0__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.
- mem_llm/__init__.py +98 -0
- mem_llm/api_server.py +595 -0
- mem_llm/base_llm_client.py +201 -0
- mem_llm/builtin_tools.py +311 -0
- mem_llm/cli.py +254 -0
- mem_llm/clients/__init__.py +22 -0
- mem_llm/clients/lmstudio_client.py +393 -0
- mem_llm/clients/ollama_client.py +354 -0
- mem_llm/config.yaml.example +52 -0
- mem_llm/config_from_docs.py +180 -0
- mem_llm/config_manager.py +231 -0
- mem_llm/conversation_summarizer.py +372 -0
- mem_llm/data_export_import.py +640 -0
- mem_llm/dynamic_prompt.py +298 -0
- mem_llm/knowledge_loader.py +88 -0
- mem_llm/llm_client.py +225 -0
- mem_llm/llm_client_factory.py +260 -0
- mem_llm/logger.py +129 -0
- mem_llm/mem_agent.py +1611 -0
- mem_llm/memory_db.py +612 -0
- mem_llm/memory_manager.py +321 -0
- mem_llm/memory_tools.py +253 -0
- mem_llm/prompt_security.py +304 -0
- mem_llm/response_metrics.py +221 -0
- mem_llm/retry_handler.py +193 -0
- mem_llm/thread_safe_db.py +301 -0
- mem_llm/tool_system.py +429 -0
- mem_llm/vector_store.py +278 -0
- mem_llm/web_launcher.py +129 -0
- mem_llm/web_ui/README.md +44 -0
- mem_llm/web_ui/__init__.py +7 -0
- mem_llm/web_ui/index.html +641 -0
- mem_llm/web_ui/memory.html +569 -0
- mem_llm/web_ui/metrics.html +75 -0
- mem_llm-2.0.0.dist-info/METADATA +667 -0
- mem_llm-2.0.0.dist-info/RECORD +39 -0
- mem_llm-2.0.0.dist-info/WHEEL +5 -0
- mem_llm-2.0.0.dist-info/entry_points.txt +3 -0
- mem_llm-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Mem-LLM - Memory-Enabled AI Assistant</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
16
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.container {
|
|
25
|
+
max-width: 1400px;
|
|
26
|
+
width: 100%;
|
|
27
|
+
background: white;
|
|
28
|
+
border-radius: 20px;
|
|
29
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
display: flex;
|
|
32
|
+
height: 90vh;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sidebar {
|
|
36
|
+
width: 300px;
|
|
37
|
+
background: #2c3e50;
|
|
38
|
+
color: white;
|
|
39
|
+
padding: 30px 20px;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sidebar h1 {
|
|
46
|
+
font-size: 24px;
|
|
47
|
+
margin-bottom: 10px;
|
|
48
|
+
color: #3498db;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.sidebar p {
|
|
52
|
+
font-size: 12px;
|
|
53
|
+
opacity: 0.7;
|
|
54
|
+
margin-bottom: 30px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.section {
|
|
58
|
+
margin-bottom: 25px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.section-title {
|
|
62
|
+
font-size: 11px;
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
opacity: 0.6;
|
|
65
|
+
margin-bottom: 10px;
|
|
66
|
+
letter-spacing: 1px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.input-group {
|
|
70
|
+
margin-bottom: 15px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.input-group label {
|
|
74
|
+
display: block;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
margin-bottom: 5px;
|
|
77
|
+
opacity: 0.8;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.input-group input,
|
|
81
|
+
.input-group select {
|
|
82
|
+
width: 100%;
|
|
83
|
+
padding: 10px;
|
|
84
|
+
border: none;
|
|
85
|
+
border-radius: 5px;
|
|
86
|
+
background: #34495e;
|
|
87
|
+
color: white;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.input-group select option {
|
|
92
|
+
background: #34495e;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.input-group input:focus,
|
|
96
|
+
.input-group select:focus {
|
|
97
|
+
outline: 2px solid #3498db;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.stats {
|
|
101
|
+
margin-top: auto;
|
|
102
|
+
padding-top: 20px;
|
|
103
|
+
border-top: 1px solid #34495e;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.stat-item {
|
|
107
|
+
margin-bottom: 15px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.stat-label {
|
|
111
|
+
font-size: 11px;
|
|
112
|
+
opacity: 0.6;
|
|
113
|
+
margin-bottom: 3px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.stat-value {
|
|
117
|
+
font-size: 18px;
|
|
118
|
+
font-weight: bold;
|
|
119
|
+
color: #3498db;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.main-content {
|
|
123
|
+
flex: 1;
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.chat-header {
|
|
129
|
+
padding: 20px 30px;
|
|
130
|
+
background: #f8f9fa;
|
|
131
|
+
border-bottom: 1px solid #e9ecef;
|
|
132
|
+
display: flex;
|
|
133
|
+
justify-content: space-between;
|
|
134
|
+
align-items: center;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.chat-header h2 {
|
|
138
|
+
font-size: 20px;
|
|
139
|
+
color: #2c3e50;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.status {
|
|
143
|
+
font-size: 12px;
|
|
144
|
+
color: #27ae60;
|
|
145
|
+
margin-top: 5px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.status.disconnected {
|
|
149
|
+
color: #e74c3c;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.config-info {
|
|
153
|
+
font-size: 11px;
|
|
154
|
+
color: #7f8c8d;
|
|
155
|
+
margin-top: 5px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.chat-messages {
|
|
159
|
+
flex: 1;
|
|
160
|
+
overflow-y: auto;
|
|
161
|
+
padding: 30px;
|
|
162
|
+
background: #f8f9fa;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.message {
|
|
166
|
+
display: flex;
|
|
167
|
+
margin-bottom: 20px;
|
|
168
|
+
animation: fadeIn 0.3s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@keyframes fadeIn {
|
|
172
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
173
|
+
to { opacity: 1; transform: translateY(0); }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.message.user {
|
|
177
|
+
justify-content: flex-end;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.message-content {
|
|
181
|
+
max-width: 70%;
|
|
182
|
+
padding: 15px 20px;
|
|
183
|
+
border-radius: 15px;
|
|
184
|
+
position: relative;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.message.user .message-content {
|
|
188
|
+
background: #667eea;
|
|
189
|
+
color: white;
|
|
190
|
+
border-bottom-right-radius: 5px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.message.bot .message-content {
|
|
194
|
+
background: white;
|
|
195
|
+
color: #2c3e50;
|
|
196
|
+
border-bottom-left-radius: 5px;
|
|
197
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.message-time {
|
|
201
|
+
font-size: 10px;
|
|
202
|
+
opacity: 0.6;
|
|
203
|
+
margin-top: 5px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.chat-input {
|
|
207
|
+
padding: 20px 30px;
|
|
208
|
+
background: white;
|
|
209
|
+
border-top: 1px solid #e9ecef;
|
|
210
|
+
display: flex;
|
|
211
|
+
gap: 10px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.chat-input input {
|
|
215
|
+
flex: 1;
|
|
216
|
+
padding: 15px 20px;
|
|
217
|
+
border: 2px solid #e9ecef;
|
|
218
|
+
border-radius: 25px;
|
|
219
|
+
font-size: 14px;
|
|
220
|
+
transition: border-color 0.3s;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.chat-input input:focus {
|
|
224
|
+
outline: none;
|
|
225
|
+
border-color: #667eea;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.chat-input button {
|
|
229
|
+
padding: 15px 30px;
|
|
230
|
+
background: #667eea;
|
|
231
|
+
color: white;
|
|
232
|
+
border: none;
|
|
233
|
+
border-radius: 25px;
|
|
234
|
+
font-size: 14px;
|
|
235
|
+
font-weight: bold;
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
transition: background 0.3s;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.chat-input button:hover {
|
|
241
|
+
background: #5568d3;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.chat-input button:disabled {
|
|
245
|
+
background: #95a5a6;
|
|
246
|
+
cursor: not-allowed;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.welcome-message {
|
|
250
|
+
text-align: center;
|
|
251
|
+
padding: 50px 20px;
|
|
252
|
+
color: #7f8c8d;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.welcome-message h3 {
|
|
256
|
+
font-size: 24px;
|
|
257
|
+
margin-bottom: 10px;
|
|
258
|
+
color: #2c3e50;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.welcome-message p {
|
|
262
|
+
font-size: 14px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.btn-reconnect {
|
|
266
|
+
margin-top: 10px;
|
|
267
|
+
padding: 8px 16px;
|
|
268
|
+
background: #3498db;
|
|
269
|
+
color: white;
|
|
270
|
+
border: none;
|
|
271
|
+
border-radius: 5px;
|
|
272
|
+
cursor: pointer;
|
|
273
|
+
font-size: 12px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.btn-reconnect:hover {
|
|
277
|
+
background: #2980b9;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.btn {
|
|
281
|
+
padding: 10px 20px;
|
|
282
|
+
background: #3498db;
|
|
283
|
+
color: white;
|
|
284
|
+
border: none;
|
|
285
|
+
border-radius: 5px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
text-decoration: none;
|
|
288
|
+
font-size: 14px;
|
|
289
|
+
display: inline-block;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.btn:hover {
|
|
293
|
+
background: #2980b9;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@media (max-width: 768px) {
|
|
297
|
+
.sidebar {
|
|
298
|
+
width: 200px;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.message-content {
|
|
302
|
+
max-width: 85%;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
</style>
|
|
306
|
+
</head>
|
|
307
|
+
<body>
|
|
308
|
+
<div class="container">
|
|
309
|
+
<!-- Sidebar -->
|
|
310
|
+
<div class="sidebar">
|
|
311
|
+
<div>
|
|
312
|
+
<h1>🧠 Mem-LLM</h1>
|
|
313
|
+
<p>Memory-Enabled AI Assistant v1.3.3</p>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<!-- User Settings -->
|
|
317
|
+
<div class="section">
|
|
318
|
+
<div class="section-title">👤 User</div>
|
|
319
|
+
<div class="input-group">
|
|
320
|
+
<label>User ID</label>
|
|
321
|
+
<input type="text" id="userId" value="user123" placeholder="Enter your user ID">
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<!-- Backend Settings -->
|
|
326
|
+
<div class="section">
|
|
327
|
+
<div class="section-title">🔌 Backend Settings</div>
|
|
328
|
+
|
|
329
|
+
<div class="input-group">
|
|
330
|
+
<label>Backend</label>
|
|
331
|
+
<select id="backend">
|
|
332
|
+
<option value="ollama">Ollama (Local)</option>
|
|
333
|
+
<option value="lmstudio">LM Studio (Local)</option>
|
|
334
|
+
</select>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div class="input-group">
|
|
338
|
+
<label>Model</label>
|
|
339
|
+
<input type="text" id="model" value="granite4:3b" placeholder="Model name">
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="input-group">
|
|
343
|
+
<label>API URL</label>
|
|
344
|
+
<input type="text" id="apiUrl" value="http://localhost:8000" placeholder="API Server URL">
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
<!-- Memory Settings -->
|
|
349
|
+
<div class="section">
|
|
350
|
+
<div class="section-title">💾 Memory Settings</div>
|
|
351
|
+
|
|
352
|
+
<div class="input-group">
|
|
353
|
+
<label>
|
|
354
|
+
<input type="checkbox" id="useSQL" checked style="width: auto; margin-right: 5px;">
|
|
355
|
+
Use SQL Memory
|
|
356
|
+
</label>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<div class="input-group">
|
|
360
|
+
<label>
|
|
361
|
+
<input type="checkbox" id="loadKB" checked style="width: auto; margin-right: 5px;">
|
|
362
|
+
Load Knowledge Base
|
|
363
|
+
</label>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
<!-- Stats -->
|
|
368
|
+
<div class="stats">
|
|
369
|
+
<div class="stat-item">
|
|
370
|
+
<div class="stat-label">Messages</div>
|
|
371
|
+
<div class="stat-value" id="messageCount">0</div>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="stat-item">
|
|
374
|
+
<div class="stat-label">Session Time</div>
|
|
375
|
+
<div class="stat-value" id="sessionTime">0m</div>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="stat-item">
|
|
378
|
+
<div class="stat-label">Backend</div>
|
|
379
|
+
<div class="stat-value" id="currentBackend">-</div>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<!-- Main Chat Area -->
|
|
385
|
+
<div class="main-content">
|
|
386
|
+
<div class="chat-header">
|
|
387
|
+
<div>
|
|
388
|
+
<h2>💬 Chat</h2>
|
|
389
|
+
<div class="status" id="status">● Connecting...</div>
|
|
390
|
+
<div class="config-info" id="configInfo">Configure settings and connect</div>
|
|
391
|
+
</div>
|
|
392
|
+
<div style="display: flex; gap: 10px;">
|
|
393
|
+
<a href="memory.html" class="btn">🧠 Memory</a>
|
|
394
|
+
<a href="metrics.html" class="btn">📊 Metrics</a>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
<div class="chat-messages" id="chatMessages">
|
|
399
|
+
<div class="welcome-message">
|
|
400
|
+
<h3>👋 Welcome!</h3>
|
|
401
|
+
<p>How can Mem-LLM help you today?</p>
|
|
402
|
+
<p style="margin-top: 10px; font-size: 12px;">
|
|
403
|
+
I'm an AI assistant with memory. I can remember our conversations! 🧠
|
|
404
|
+
</p>
|
|
405
|
+
<p style="margin-top: 15px; font-size: 11px; color: #95a5a6;">
|
|
406
|
+
Configure backend and model settings on the left
|
|
407
|
+
</p>
|
|
408
|
+
<button class="btn-reconnect" onclick="connect()">Connect</button>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
|
|
412
|
+
<div class="chat-input">
|
|
413
|
+
<input type="text" id="messageInput" placeholder="Type your message..." autocomplete="off">
|
|
414
|
+
<button id="sendButton">Send</button>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<script>
|
|
420
|
+
// WebSocket connection
|
|
421
|
+
let ws = null;
|
|
422
|
+
let messageCount = 0;
|
|
423
|
+
let sessionStart = Date.now();
|
|
424
|
+
|
|
425
|
+
// DOM elements
|
|
426
|
+
const chatMessages = document.getElementById('chatMessages');
|
|
427
|
+
const messageInput = document.getElementById('messageInput');
|
|
428
|
+
const sendButton = document.getElementById('sendButton');
|
|
429
|
+
const userIdInput = document.getElementById('userId');
|
|
430
|
+
const backendSelect = document.getElementById('backend');
|
|
431
|
+
const modelInput = document.getElementById('model');
|
|
432
|
+
const apiUrlInput = document.getElementById('apiUrl');
|
|
433
|
+
const useSQLCheckbox = document.getElementById('useSQL');
|
|
434
|
+
const loadKBCheckbox = document.getElementById('loadKB');
|
|
435
|
+
const statusElement = document.getElementById('status');
|
|
436
|
+
const configInfoElement = document.getElementById('configInfo');
|
|
437
|
+
const messageCountElement = document.getElementById('messageCount');
|
|
438
|
+
const sessionTimeElement = document.getElementById('sessionTime');
|
|
439
|
+
const currentBackendElement = document.getElementById('currentBackend');
|
|
440
|
+
|
|
441
|
+
// Update model when backend changes
|
|
442
|
+
backendSelect.addEventListener('change', () => {
|
|
443
|
+
const backend = backendSelect.value;
|
|
444
|
+
if (backend === 'ollama') {
|
|
445
|
+
modelInput.value = 'granite4:3b';
|
|
446
|
+
} else if (backend === 'lmstudio') {
|
|
447
|
+
modelInput.value = 'qwen/qwen3-vl-4b';
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Connect to WebSocket
|
|
452
|
+
function connect() {
|
|
453
|
+
const userId = userIdInput.value || 'user123';
|
|
454
|
+
const apiUrl = apiUrlInput.value || 'http://localhost:8000';
|
|
455
|
+
|
|
456
|
+
// Extract host from API URL
|
|
457
|
+
const url = new URL(apiUrl);
|
|
458
|
+
const wsUrl = `ws://${url.host}/ws/chat/${userId}`;
|
|
459
|
+
|
|
460
|
+
statusElement.textContent = '● Connecting...';
|
|
461
|
+
statusElement.className = 'status';
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
ws = new WebSocket(wsUrl);
|
|
465
|
+
|
|
466
|
+
ws.onopen = () => {
|
|
467
|
+
console.log('Connected to Mem-LLM');
|
|
468
|
+
statusElement.textContent = '● Connected';
|
|
469
|
+
statusElement.className = 'status';
|
|
470
|
+
|
|
471
|
+
const backend = backendSelect.value;
|
|
472
|
+
const model = modelInput.value;
|
|
473
|
+
const useSQL = useSQLCheckbox.checked;
|
|
474
|
+
const loadKB = loadKBCheckbox.checked;
|
|
475
|
+
|
|
476
|
+
currentBackendElement.textContent = backend.toUpperCase();
|
|
477
|
+
configInfoElement.textContent = `Model: ${model} | SQL: ${useSQL ? 'On' : 'Off'} | KB: ${loadKB ? 'On' : 'Off'}`;
|
|
478
|
+
|
|
479
|
+
// Send initial configuration
|
|
480
|
+
sendConfigToAPI();
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
ws.onclose = () => {
|
|
484
|
+
console.log('Disconnected from Mem-LLM');
|
|
485
|
+
statusElement.textContent = '● Disconnected';
|
|
486
|
+
statusElement.className = 'status disconnected';
|
|
487
|
+
configInfoElement.textContent = 'Click "Connect" button to reconnect';
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
ws.onerror = (error) => {
|
|
491
|
+
console.error('WebSocket error:', error);
|
|
492
|
+
statusElement.textContent = '● Error';
|
|
493
|
+
statusElement.className = 'status disconnected';
|
|
494
|
+
configInfoElement.textContent = `Connection error. Is API Server running? (${apiUrl})`;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
ws.onmessage = (event) => {
|
|
498
|
+
const data = JSON.parse(event.data);
|
|
499
|
+
handleServerMessage(data);
|
|
500
|
+
};
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.error('Connection error:', error);
|
|
503
|
+
statusElement.textContent = '● Error';
|
|
504
|
+
statusElement.className = 'status disconnected';
|
|
505
|
+
configInfoElement.textContent = `Connection error: ${error.message}`;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Send configuration to API
|
|
510
|
+
async function sendConfigToAPI() {
|
|
511
|
+
const userId = userIdInput.value || 'user123';
|
|
512
|
+
const apiUrl = apiUrlInput.value || 'http://localhost:8000';
|
|
513
|
+
const backend = backendSelect.value;
|
|
514
|
+
const model = modelInput.value;
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
const response = await fetch(`${apiUrl}/api/v1/agent/configure/${userId}`, {
|
|
518
|
+
method: 'POST',
|
|
519
|
+
headers: {
|
|
520
|
+
'Content-Type': 'application/json',
|
|
521
|
+
},
|
|
522
|
+
body: JSON.stringify({
|
|
523
|
+
model: model,
|
|
524
|
+
backend: backend,
|
|
525
|
+
base_url: backend === 'ollama' ? 'http://localhost:11434' : 'http://localhost:1234'
|
|
526
|
+
})
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
if (response.ok) {
|
|
530
|
+
console.log('Configuration sent successfully');
|
|
531
|
+
} else {
|
|
532
|
+
console.error('Configuration failed:', response.status);
|
|
533
|
+
}
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error('Configuration error:', error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Handle messages from server
|
|
540
|
+
let currentBotMessage = null;
|
|
541
|
+
|
|
542
|
+
function handleServerMessage(data) {
|
|
543
|
+
if (data.type === 'start') {
|
|
544
|
+
// Start of streaming response
|
|
545
|
+
currentBotMessage = addMessage('', 'bot');
|
|
546
|
+
} else if (data.type === 'chunk') {
|
|
547
|
+
// Streaming chunk
|
|
548
|
+
if (currentBotMessage) {
|
|
549
|
+
currentBotMessage.textContent += data.content;
|
|
550
|
+
scrollToBottom();
|
|
551
|
+
}
|
|
552
|
+
} else if (data.type === 'done') {
|
|
553
|
+
// End of streaming
|
|
554
|
+
currentBotMessage = null;
|
|
555
|
+
} else if (data.type === 'error') {
|
|
556
|
+
addMessage(`⚠️ Error: ${data.content}`, 'bot');
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Add message to chat
|
|
561
|
+
function addMessage(text, type) {
|
|
562
|
+
// Remove welcome message if exists
|
|
563
|
+
const welcomeMsg = chatMessages.querySelector('.welcome-message');
|
|
564
|
+
if (welcomeMsg) {
|
|
565
|
+
welcomeMsg.remove();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const messageDiv = document.createElement('div');
|
|
569
|
+
messageDiv.className = `message ${type}`;
|
|
570
|
+
|
|
571
|
+
const contentDiv = document.createElement('div');
|
|
572
|
+
contentDiv.className = 'message-content';
|
|
573
|
+
|
|
574
|
+
const textDiv = document.createElement('div');
|
|
575
|
+
textDiv.textContent = text;
|
|
576
|
+
contentDiv.appendChild(textDiv);
|
|
577
|
+
|
|
578
|
+
const timeDiv = document.createElement('div');
|
|
579
|
+
timeDiv.className = 'message-time';
|
|
580
|
+
timeDiv.textContent = new Date().toLocaleTimeString();
|
|
581
|
+
contentDiv.appendChild(timeDiv);
|
|
582
|
+
|
|
583
|
+
messageDiv.appendChild(contentDiv);
|
|
584
|
+
chatMessages.appendChild(messageDiv);
|
|
585
|
+
|
|
586
|
+
scrollToBottom();
|
|
587
|
+
|
|
588
|
+
return textDiv; // Return text element for streaming
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Scroll to bottom
|
|
592
|
+
function scrollToBottom() {
|
|
593
|
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Send message
|
|
597
|
+
function sendMessage() {
|
|
598
|
+
const message = messageInput.value.trim();
|
|
599
|
+
if (!message || !ws || ws.readyState !== WebSocket.OPEN) {
|
|
600
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
601
|
+
alert('Please connect first! (Click Connect button)');
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Add user message to UI
|
|
607
|
+
addMessage(message, 'user');
|
|
608
|
+
messageInput.value = '';
|
|
609
|
+
|
|
610
|
+
// Send to server
|
|
611
|
+
ws.send(JSON.stringify({
|
|
612
|
+
message: message,
|
|
613
|
+
metadata: {
|
|
614
|
+
timestamp: new Date().toISOString(),
|
|
615
|
+
backend: backendSelect.value,
|
|
616
|
+
model: modelInput.value
|
|
617
|
+
}
|
|
618
|
+
}));
|
|
619
|
+
|
|
620
|
+
// Update stats
|
|
621
|
+
messageCount++;
|
|
622
|
+
messageCountElement.textContent = messageCount;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Event listeners
|
|
626
|
+
sendButton.addEventListener('click', sendMessage);
|
|
627
|
+
messageInput.addEventListener('keypress', (e) => {
|
|
628
|
+
if (e.key === 'Enter') {
|
|
629
|
+
sendMessage();
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Update session time every minute
|
|
634
|
+
setInterval(() => {
|
|
635
|
+
const minutes = Math.floor((Date.now() - sessionStart) / 60000);
|
|
636
|
+
sessionTimeElement.textContent = `${minutes}m`;
|
|
637
|
+
}, 60000);
|
|
638
|
+
</script>
|
|
639
|
+
</body>
|
|
640
|
+
</html>
|
|
641
|
+
|