natureco-cli 1.0.14 → 1.0.15
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.
- package/README.md +17 -0
- package/bin/natureco.js +42 -0
- package/package.json +1 -1
- package/src/commands/dashboard.js +687 -0
- package/src/commands/discord.js +159 -0
- package/src/commands/doctor.js +345 -0
- package/src/commands/setup.js +259 -0
- package/src/commands/slack.js +164 -0
- package/src/commands/whatsapp.js +190 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { exec } = require('child_process');
|
|
6
|
+
const { getConfig, CONFIG_DIR } = require('../utils/config');
|
|
7
|
+
|
|
8
|
+
const PORT = 3848;
|
|
9
|
+
const PID_FILE = path.join(CONFIG_DIR, 'dashboard.pid');
|
|
10
|
+
|
|
11
|
+
async function dashboard(action) {
|
|
12
|
+
if (!action || action === 'start') {
|
|
13
|
+
return startDashboard();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (action === 'stop') {
|
|
17
|
+
return stopDashboard();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (action === 'status') {
|
|
21
|
+
return statusDashboard();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(chalk.red('\n❌ Unknown action\n'));
|
|
25
|
+
console.log(chalk.gray('Available actions: start, stop, status\n'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function startDashboard() {
|
|
30
|
+
// Check if already running
|
|
31
|
+
if (fs.existsSync(PID_FILE)) {
|
|
32
|
+
const pid = fs.readFileSync(PID_FILE, 'utf-8').trim();
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 0);
|
|
35
|
+
console.log(chalk.yellow('\n⚠️ Dashboard is already running\n'));
|
|
36
|
+
console.log(chalk.cyan('URL:'), chalk.white(`http://localhost:${PORT}`));
|
|
37
|
+
console.log(chalk.gray('\nStop with: natureco dashboard stop\n'));
|
|
38
|
+
return;
|
|
39
|
+
} catch {
|
|
40
|
+
// Process not running, remove stale PID file
|
|
41
|
+
fs.unlinkSync(PID_FILE);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const config = getConfig();
|
|
46
|
+
|
|
47
|
+
if (!config.apiKey) {
|
|
48
|
+
console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(chalk.cyan('🚀 Starting NatureCo Dashboard...\n'));
|
|
53
|
+
|
|
54
|
+
const server = http.createServer((req, res) => {
|
|
55
|
+
// CORS headers
|
|
56
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
57
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
58
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
59
|
+
|
|
60
|
+
if (req.method === 'OPTIONS') {
|
|
61
|
+
res.writeHead(200);
|
|
62
|
+
res.end();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (req.url === '/' || req.url === '/index.html') {
|
|
67
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
68
|
+
res.end(getHTML(config));
|
|
69
|
+
} else if (req.url === '/config') {
|
|
70
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
71
|
+
res.end(JSON.stringify({
|
|
72
|
+
apiKey: config.apiKey,
|
|
73
|
+
defaultBot: config.defaultBot,
|
|
74
|
+
defaultBotId: config.defaultBotId,
|
|
75
|
+
}));
|
|
76
|
+
} else {
|
|
77
|
+
res.writeHead(404);
|
|
78
|
+
res.end('Not Found');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
server.listen(PORT, () => {
|
|
83
|
+
// Save PID
|
|
84
|
+
fs.writeFileSync(PID_FILE, process.pid.toString());
|
|
85
|
+
|
|
86
|
+
console.log(chalk.green('✅ Dashboard started!\n'));
|
|
87
|
+
console.log(chalk.cyan('URL:'), chalk.white(`http://localhost:${PORT}`));
|
|
88
|
+
console.log(chalk.gray('\nOpening browser...\n'));
|
|
89
|
+
|
|
90
|
+
// Open browser
|
|
91
|
+
const url = `http://localhost:${PORT}`;
|
|
92
|
+
const platform = process.platform;
|
|
93
|
+
|
|
94
|
+
if (platform === 'darwin') {
|
|
95
|
+
exec(`open ${url}`);
|
|
96
|
+
} else if (platform === 'win32') {
|
|
97
|
+
exec(`start ${url}`);
|
|
98
|
+
} else {
|
|
99
|
+
exec(`xdg-open ${url}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(chalk.gray('Press Ctrl+C to stop the server\n'));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Handle shutdown
|
|
106
|
+
process.on('SIGINT', () => {
|
|
107
|
+
console.log(chalk.yellow('\n\n⏳ Shutting down dashboard...\n'));
|
|
108
|
+
server.close(() => {
|
|
109
|
+
if (fs.existsSync(PID_FILE)) {
|
|
110
|
+
fs.unlinkSync(PID_FILE);
|
|
111
|
+
}
|
|
112
|
+
console.log(chalk.green('✅ Dashboard stopped\n'));
|
|
113
|
+
process.exit(0);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function stopDashboard() {
|
|
119
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
120
|
+
console.log(chalk.gray('\n⚠️ Dashboard is not running\n'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const pid = fs.readFileSync(PID_FILE, 'utf-8').trim();
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
process.kill(pid, 'SIGTERM');
|
|
128
|
+
fs.unlinkSync(PID_FILE);
|
|
129
|
+
console.log(chalk.green('\n✅ Dashboard stopped\n'));
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.log(chalk.red(`\n❌ Failed to stop dashboard: ${err.message}\n`));
|
|
132
|
+
// Remove stale PID file
|
|
133
|
+
if (fs.existsSync(PID_FILE)) {
|
|
134
|
+
fs.unlinkSync(PID_FILE);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function statusDashboard() {
|
|
140
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
141
|
+
console.log(chalk.gray('\n⚠️ Dashboard is not running\n'));
|
|
142
|
+
console.log(chalk.gray('Start with: natureco dashboard start\n'));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const pid = fs.readFileSync(PID_FILE, 'utf-8').trim();
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
process.kill(pid, 0);
|
|
150
|
+
console.log(chalk.green('\n✅ Dashboard is running\n'));
|
|
151
|
+
console.log(chalk.cyan('PID:'), chalk.white(pid));
|
|
152
|
+
console.log(chalk.cyan('URL:'), chalk.white(`http://localhost:${PORT}`));
|
|
153
|
+
console.log(chalk.gray('\nStop with: natureco dashboard stop\n'));
|
|
154
|
+
} catch {
|
|
155
|
+
console.log(chalk.gray('\n⚠️ Dashboard is not running (stale PID file)\n'));
|
|
156
|
+
fs.unlinkSync(PID_FILE);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getHTML(config) {
|
|
161
|
+
return `<!DOCTYPE html>
|
|
162
|
+
<html lang="en">
|
|
163
|
+
<head>
|
|
164
|
+
<meta charset="UTF-8">
|
|
165
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
166
|
+
<title>NatureCo Dashboard</title>
|
|
167
|
+
<style>
|
|
168
|
+
* {
|
|
169
|
+
margin: 0;
|
|
170
|
+
padding: 0;
|
|
171
|
+
box-sizing: border-box;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
body {
|
|
175
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
176
|
+
background: #0d1117;
|
|
177
|
+
color: #c9d1d9;
|
|
178
|
+
height: 100vh;
|
|
179
|
+
overflow: hidden;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.container {
|
|
183
|
+
display: flex;
|
|
184
|
+
height: 100vh;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.sidebar {
|
|
188
|
+
width: 280px;
|
|
189
|
+
background: #161b22;
|
|
190
|
+
border-right: 1px solid #30363d;
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-direction: column;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.sidebar-header {
|
|
196
|
+
padding: 20px;
|
|
197
|
+
border-bottom: 1px solid #30363d;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.logo {
|
|
201
|
+
font-size: 20px;
|
|
202
|
+
font-weight: bold;
|
|
203
|
+
color: #00E676;
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: 8px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.sidebar-content {
|
|
210
|
+
flex: 1;
|
|
211
|
+
overflow-y: auto;
|
|
212
|
+
padding: 16px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.section {
|
|
216
|
+
margin-bottom: 24px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.section-title {
|
|
220
|
+
font-size: 12px;
|
|
221
|
+
font-weight: 600;
|
|
222
|
+
color: #8b949e;
|
|
223
|
+
text-transform: uppercase;
|
|
224
|
+
margin-bottom: 8px;
|
|
225
|
+
letter-spacing: 0.5px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.bot-item, .item {
|
|
229
|
+
padding: 10px 12px;
|
|
230
|
+
background: #0d1117;
|
|
231
|
+
border: 1px solid #30363d;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
margin-bottom: 8px;
|
|
234
|
+
cursor: pointer;
|
|
235
|
+
transition: all 0.2s;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.bot-item:hover, .item:hover {
|
|
239
|
+
background: #161b22;
|
|
240
|
+
border-color: #00E676;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.bot-item.active {
|
|
244
|
+
background: #00E676;
|
|
245
|
+
color: #0d1117;
|
|
246
|
+
border-color: #00E676;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.bot-name {
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
font-size: 14px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.bot-id {
|
|
255
|
+
font-size: 11px;
|
|
256
|
+
color: #8b949e;
|
|
257
|
+
margin-top: 4px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.bot-item.active .bot-id {
|
|
261
|
+
color: #0d1117;
|
|
262
|
+
opacity: 0.7;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.main {
|
|
266
|
+
flex: 1;
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.header {
|
|
272
|
+
padding: 20px 24px;
|
|
273
|
+
border-bottom: 1px solid #30363d;
|
|
274
|
+
background: #161b22;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.header-title {
|
|
278
|
+
font-size: 18px;
|
|
279
|
+
font-weight: 600;
|
|
280
|
+
color: #00E676;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.chat-container {
|
|
284
|
+
flex: 1;
|
|
285
|
+
display: flex;
|
|
286
|
+
flex-direction: column;
|
|
287
|
+
overflow: hidden;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.messages {
|
|
291
|
+
flex: 1;
|
|
292
|
+
overflow-y: auto;
|
|
293
|
+
padding: 24px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.message {
|
|
297
|
+
margin-bottom: 16px;
|
|
298
|
+
display: flex;
|
|
299
|
+
gap: 12px;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.message-avatar {
|
|
303
|
+
width: 32px;
|
|
304
|
+
height: 32px;
|
|
305
|
+
border-radius: 50%;
|
|
306
|
+
background: #00E676;
|
|
307
|
+
display: flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
justify-content: center;
|
|
310
|
+
font-weight: bold;
|
|
311
|
+
color: #0d1117;
|
|
312
|
+
flex-shrink: 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.message-content {
|
|
316
|
+
flex: 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.message-author {
|
|
320
|
+
font-weight: 600;
|
|
321
|
+
font-size: 14px;
|
|
322
|
+
margin-bottom: 4px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.message-text {
|
|
326
|
+
font-size: 14px;
|
|
327
|
+
line-height: 1.6;
|
|
328
|
+
white-space: pre-wrap;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.message.user .message-avatar {
|
|
332
|
+
background: #58a6ff;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.input-container {
|
|
336
|
+
padding: 16px 24px;
|
|
337
|
+
border-top: 1px solid #30363d;
|
|
338
|
+
background: #161b22;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.input-wrapper {
|
|
342
|
+
display: flex;
|
|
343
|
+
gap: 12px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.input {
|
|
347
|
+
flex: 1;
|
|
348
|
+
padding: 12px 16px;
|
|
349
|
+
background: #0d1117;
|
|
350
|
+
border: 1px solid #30363d;
|
|
351
|
+
border-radius: 6px;
|
|
352
|
+
color: #c9d1d9;
|
|
353
|
+
font-size: 14px;
|
|
354
|
+
font-family: inherit;
|
|
355
|
+
outline: none;
|
|
356
|
+
transition: border-color 0.2s;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.input:focus {
|
|
360
|
+
border-color: #00E676;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.send-btn {
|
|
364
|
+
padding: 12px 24px;
|
|
365
|
+
background: #00E676;
|
|
366
|
+
color: #0d1117;
|
|
367
|
+
border: none;
|
|
368
|
+
border-radius: 6px;
|
|
369
|
+
font-weight: 600;
|
|
370
|
+
cursor: pointer;
|
|
371
|
+
transition: all 0.2s;
|
|
372
|
+
font-size: 14px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.send-btn:hover {
|
|
376
|
+
background: #00c853;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.send-btn:disabled {
|
|
380
|
+
opacity: 0.5;
|
|
381
|
+
cursor: not-allowed;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.loading {
|
|
385
|
+
display: inline-block;
|
|
386
|
+
width: 16px;
|
|
387
|
+
height: 16px;
|
|
388
|
+
border: 2px solid #30363d;
|
|
389
|
+
border-top-color: #00E676;
|
|
390
|
+
border-radius: 50%;
|
|
391
|
+
animation: spin 0.8s linear infinite;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@keyframes spin {
|
|
395
|
+
to { transform: rotate(360deg); }
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.empty-state {
|
|
399
|
+
display: flex;
|
|
400
|
+
flex-direction: column;
|
|
401
|
+
align-items: center;
|
|
402
|
+
justify-content: center;
|
|
403
|
+
height: 100%;
|
|
404
|
+
color: #8b949e;
|
|
405
|
+
text-align: center;
|
|
406
|
+
padding: 24px;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.empty-icon {
|
|
410
|
+
font-size: 48px;
|
|
411
|
+
margin-bottom: 16px;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.empty-title {
|
|
415
|
+
font-size: 18px;
|
|
416
|
+
font-weight: 600;
|
|
417
|
+
margin-bottom: 8px;
|
|
418
|
+
color: #c9d1d9;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.empty-text {
|
|
422
|
+
font-size: 14px;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.stat {
|
|
426
|
+
display: flex;
|
|
427
|
+
align-items: center;
|
|
428
|
+
gap: 8px;
|
|
429
|
+
padding: 8px 12px;
|
|
430
|
+
background: #0d1117;
|
|
431
|
+
border: 1px solid #30363d;
|
|
432
|
+
border-radius: 6px;
|
|
433
|
+
font-size: 13px;
|
|
434
|
+
margin-bottom: 8px;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.stat-icon {
|
|
438
|
+
color: #00E676;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
::-webkit-scrollbar {
|
|
442
|
+
width: 8px;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
::-webkit-scrollbar-track {
|
|
446
|
+
background: #0d1117;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
::-webkit-scrollbar-thumb {
|
|
450
|
+
background: #30363d;
|
|
451
|
+
border-radius: 4px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
::-webkit-scrollbar-thumb:hover {
|
|
455
|
+
background: #484f58;
|
|
456
|
+
}
|
|
457
|
+
</style>
|
|
458
|
+
</head>
|
|
459
|
+
<body>
|
|
460
|
+
<div class="container">
|
|
461
|
+
<div class="sidebar">
|
|
462
|
+
<div class="sidebar-header">
|
|
463
|
+
<div class="logo">
|
|
464
|
+
🌿 NatureCo
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="sidebar-content">
|
|
468
|
+
<div class="section">
|
|
469
|
+
<div class="section-title">Bots</div>
|
|
470
|
+
<div id="bots-list">
|
|
471
|
+
<div class="loading"></div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
<div class="section">
|
|
475
|
+
<div class="section-title">Stats</div>
|
|
476
|
+
<div class="stat">
|
|
477
|
+
<span class="stat-icon">✨</span>
|
|
478
|
+
<span id="skills-count">Skills: 0</span>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="stat">
|
|
481
|
+
<span class="stat-icon">🔌</span>
|
|
482
|
+
<span id="mcp-count">MCP: 0</span>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="stat">
|
|
485
|
+
<span class="stat-icon">💬</span>
|
|
486
|
+
<span id="sessions-count">Sessions: 0</span>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<div class="main">
|
|
493
|
+
<div class="header">
|
|
494
|
+
<div class="header-title" id="current-bot">Select a bot</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<div class="chat-container">
|
|
498
|
+
<div class="messages" id="messages">
|
|
499
|
+
<div class="empty-state">
|
|
500
|
+
<div class="empty-icon">💬</div>
|
|
501
|
+
<div class="empty-title">Start a conversation</div>
|
|
502
|
+
<div class="empty-text">Select a bot and send a message</div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<div class="input-container">
|
|
507
|
+
<div class="input-wrapper">
|
|
508
|
+
<input
|
|
509
|
+
type="text"
|
|
510
|
+
class="input"
|
|
511
|
+
id="message-input"
|
|
512
|
+
placeholder="Type your message..."
|
|
513
|
+
disabled
|
|
514
|
+
/>
|
|
515
|
+
<button class="send-btn" id="send-btn" disabled>Send</button>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<script>
|
|
523
|
+
const API_BASE = 'https://api.natureco.me';
|
|
524
|
+
let apiKey = '';
|
|
525
|
+
let currentBotId = null;
|
|
526
|
+
let currentBotName = '';
|
|
527
|
+
let sessionId = null;
|
|
528
|
+
|
|
529
|
+
// Load config
|
|
530
|
+
fetch('/config')
|
|
531
|
+
.then(r => r.json())
|
|
532
|
+
.then(config => {
|
|
533
|
+
apiKey = config.apiKey;
|
|
534
|
+
loadBots();
|
|
535
|
+
loadStats();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
async function loadBots() {
|
|
539
|
+
try {
|
|
540
|
+
const response = await fetch(API_BASE + '/api/v1/bots', {
|
|
541
|
+
headers: {
|
|
542
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const data = await response.json();
|
|
547
|
+
const botsList = document.getElementById('bots-list');
|
|
548
|
+
|
|
549
|
+
if (data.bots && data.bots.length > 0) {
|
|
550
|
+
botsList.innerHTML = data.bots.map(bot => \`
|
|
551
|
+
<div class="bot-item" onclick="selectBot('\${bot.id}', '\${bot.name}')">
|
|
552
|
+
<div class="bot-name">\${bot.name}</div>
|
|
553
|
+
<div class="bot-id">\${bot.id.slice(0, 8)}...</div>
|
|
554
|
+
</div>
|
|
555
|
+
\`).join('');
|
|
556
|
+
} else {
|
|
557
|
+
botsList.innerHTML = '<div class="empty-text">No bots found</div>';
|
|
558
|
+
}
|
|
559
|
+
} catch (err) {
|
|
560
|
+
console.error('Failed to load bots:', err);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function selectBot(botId, botName) {
|
|
565
|
+
currentBotId = botId;
|
|
566
|
+
currentBotName = botName;
|
|
567
|
+
sessionId = null;
|
|
568
|
+
|
|
569
|
+
// Update UI
|
|
570
|
+
document.getElementById('current-bot').textContent = botName;
|
|
571
|
+
document.getElementById('message-input').disabled = false;
|
|
572
|
+
document.getElementById('send-btn').disabled = false;
|
|
573
|
+
|
|
574
|
+
// Clear messages
|
|
575
|
+
document.getElementById('messages').innerHTML = \`
|
|
576
|
+
<div class="empty-state">
|
|
577
|
+
<div class="empty-icon">💬</div>
|
|
578
|
+
<div class="empty-title">Start a conversation with \${botName}</div>
|
|
579
|
+
<div class="empty-text">Type a message below</div>
|
|
580
|
+
</div>
|
|
581
|
+
\`;
|
|
582
|
+
|
|
583
|
+
// Update active state
|
|
584
|
+
document.querySelectorAll('.bot-item').forEach(item => {
|
|
585
|
+
item.classList.remove('active');
|
|
586
|
+
});
|
|
587
|
+
event.target.closest('.bot-item').classList.add('active');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function sendMessage() {
|
|
591
|
+
const input = document.getElementById('message-input');
|
|
592
|
+
const message = input.value.trim();
|
|
593
|
+
|
|
594
|
+
if (!message || !currentBotId) return;
|
|
595
|
+
|
|
596
|
+
// Add user message
|
|
597
|
+
addMessage('user', 'You', message);
|
|
598
|
+
input.value = '';
|
|
599
|
+
|
|
600
|
+
// Disable input
|
|
601
|
+
input.disabled = true;
|
|
602
|
+
document.getElementById('send-btn').disabled = true;
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const response = await fetch(API_BASE + '/api/v1/chat', {
|
|
606
|
+
method: 'POST',
|
|
607
|
+
headers: {
|
|
608
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
609
|
+
'Content-Type': 'application/json',
|
|
610
|
+
},
|
|
611
|
+
body: JSON.stringify({
|
|
612
|
+
bot_id: currentBotId,
|
|
613
|
+
message: message,
|
|
614
|
+
session_id: sessionId,
|
|
615
|
+
}),
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const data = await response.json();
|
|
619
|
+
|
|
620
|
+
if (data.reply) {
|
|
621
|
+
addMessage('bot', currentBotName, data.reply);
|
|
622
|
+
sessionId = data.session_id;
|
|
623
|
+
}
|
|
624
|
+
} catch (err) {
|
|
625
|
+
addMessage('system', 'System', 'Error: ' + err.message);
|
|
626
|
+
} finally {
|
|
627
|
+
input.disabled = false;
|
|
628
|
+
document.getElementById('send-btn').disabled = false;
|
|
629
|
+
input.focus();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function addMessage(type, author, text) {
|
|
634
|
+
const messagesDiv = document.getElementById('messages');
|
|
635
|
+
|
|
636
|
+
// Remove empty state
|
|
637
|
+
const emptyState = messagesDiv.querySelector('.empty-state');
|
|
638
|
+
if (emptyState) {
|
|
639
|
+
emptyState.remove();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const messageDiv = document.createElement('div');
|
|
643
|
+
messageDiv.className = 'message ' + type;
|
|
644
|
+
|
|
645
|
+
const avatar = type === 'user' ? 'U' : type === 'bot' ? '🤖' : 'ℹ️';
|
|
646
|
+
|
|
647
|
+
messageDiv.innerHTML = \`
|
|
648
|
+
<div class="message-avatar">\${avatar}</div>
|
|
649
|
+
<div class="message-content">
|
|
650
|
+
<div class="message-author">\${author}</div>
|
|
651
|
+
<div class="message-text">\${text}</div>
|
|
652
|
+
</div>
|
|
653
|
+
\`;
|
|
654
|
+
|
|
655
|
+
messagesDiv.appendChild(messageDiv);
|
|
656
|
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function loadStats() {
|
|
660
|
+
// Load skills count
|
|
661
|
+
try {
|
|
662
|
+
const config = await fetch('/config').then(r => r.json());
|
|
663
|
+
// This would need to be implemented properly
|
|
664
|
+
document.getElementById('skills-count').textContent = 'Skills: 0';
|
|
665
|
+
document.getElementById('mcp-count').textContent = 'MCP: 0';
|
|
666
|
+
document.getElementById('sessions-count').textContent = 'Sessions: 0';
|
|
667
|
+
} catch (err) {
|
|
668
|
+
console.error('Failed to load stats:', err);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Event listeners
|
|
673
|
+
document.getElementById('send-btn').addEventListener('click', sendMessage);
|
|
674
|
+
document.getElementById('message-input').addEventListener('keypress', (e) => {
|
|
675
|
+
if (e.key === 'Enter') {
|
|
676
|
+
sendMessage();
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Make selectBot global
|
|
681
|
+
window.selectBot = selectBot;
|
|
682
|
+
</script>
|
|
683
|
+
</body>
|
|
684
|
+
</html>`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
module.exports = dashboard;
|