@intranefr/superbackend 1.5.2 → 1.5.3
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/index.js +2 -0
- package/manage.js +745 -0
- package/package.json +4 -2
- package/src/controllers/admin.controller.js +11 -5
- package/src/controllers/adminAgents.controller.js +37 -0
- package/src/controllers/adminLlm.controller.js +19 -0
- package/src/controllers/adminMarkdowns.controller.js +157 -0
- package/src/controllers/adminScripts.controller.js +138 -0
- package/src/controllers/adminTelegram.controller.js +72 -0
- package/src/controllers/markdowns.controller.js +42 -0
- package/src/helpers/mongooseHelper.js +6 -6
- package/src/helpers/scriptBase.js +2 -2
- package/src/middleware.js +136 -29
- package/src/models/Agent.js +105 -0
- package/src/models/AgentMessage.js +82 -0
- package/src/models/Markdown.js +75 -0
- package/src/models/ScriptRun.js +8 -0
- package/src/models/TelegramBot.js +42 -0
- package/src/routes/adminAgents.routes.js +13 -0
- package/src/routes/adminLlm.routes.js +1 -0
- package/src/routes/adminMarkdowns.routes.js +16 -0
- package/src/routes/adminScripts.routes.js +4 -1
- package/src/routes/adminTelegram.routes.js +14 -0
- package/src/routes/markdowns.routes.js +16 -0
- package/src/services/agent.service.js +546 -0
- package/src/services/agentHistory.service.js +345 -0
- package/src/services/agentTools.service.js +578 -0
- package/src/services/jsonConfigs.service.js +22 -10
- package/src/services/llm.service.js +219 -6
- package/src/services/markdowns.service.js +522 -0
- package/src/services/scriptsRunner.service.js +328 -37
- package/src/services/telegram.service.js +130 -0
- package/views/admin-agents.ejs +273 -0
- package/views/admin-coolify-deploy.ejs +8 -8
- package/views/admin-dashboard.ejs +36 -5
- package/views/admin-experiments.ejs +1 -1
- package/views/admin-markdowns.ejs +905 -0
- package/views/admin-scripts.ejs +221 -4
- package/views/admin-telegram.ejs +269 -0
- package/views/partials/dashboard/nav-items.ejs +3 -0
- package/analysis-only.skill +0 -0
package/views/admin-scripts.ejs
CHANGED
|
@@ -549,11 +549,44 @@ const recentRuns = await ScriptRun.find()
|
|
|
549
549
|
</div>
|
|
550
550
|
|
|
551
551
|
<div class="mt-4 bg-white border border-gray-200 rounded-lg">
|
|
552
|
-
<div class="p-3 border-b border-gray-200
|
|
553
|
-
<div class="
|
|
554
|
-
|
|
552
|
+
<div class="p-3 border-b border-gray-200">
|
|
553
|
+
<div class="flex items-center justify-between">
|
|
554
|
+
<nav class="flex space-x-4">
|
|
555
|
+
<button class="output-tab active" data-tab="output">Output</button>
|
|
556
|
+
<button class="output-tab" data-tab="full-logs">Full Console Logs</button>
|
|
557
|
+
</nav>
|
|
558
|
+
<div class="flex items-center gap-2">
|
|
559
|
+
<button id="btn-download-logs" class="text-sm text-gray-600 hover:underline">Download</button>
|
|
560
|
+
<button id="btn-clear-output" class="text-sm text-gray-600 hover:underline">Clear</button>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
|
|
565
|
+
<!-- Output Tab (JSON Result) -->
|
|
566
|
+
<div id="output-tab-content" class="output-tab-content">
|
|
567
|
+
<pre id="output" class="p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto"></pre>
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
<!-- Full Console Logs Tab -->
|
|
571
|
+
<div id="full-logs-tab-content" class="output-tab-content hidden">
|
|
572
|
+
<!-- Search Bar -->
|
|
573
|
+
<div class="p-3 border-b border-gray-200">
|
|
574
|
+
<div class="flex items-center gap-2">
|
|
575
|
+
<input type="text" id="log-search" placeholder="Search logs..." class="flex-1 px-2 py-1 border rounded text-sm">
|
|
576
|
+
<select id="log-filter" class="px-2 py-1 border rounded text-sm">
|
|
577
|
+
<option value="all">All Logs</option>
|
|
578
|
+
<option value="stdout">Stdout</option>
|
|
579
|
+
<option value="stderr">Stderr</option>
|
|
580
|
+
</select>
|
|
581
|
+
<button id="btn-auto-scroll" class="px-2 py-1 bg-gray-100 rounded text-sm">Auto-scroll</button>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<!-- Full Logs Display -->
|
|
586
|
+
<div class="relative">
|
|
587
|
+
<pre id="full-logs-content" class="p-3 text-xs font-mono whitespace-pre-wrap" style="height: 60vh; overflow: auto;"></pre>
|
|
588
|
+
</div>
|
|
555
589
|
</div>
|
|
556
|
-
<pre id="output" class="p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto"></pre>
|
|
557
590
|
</div>
|
|
558
591
|
</div>
|
|
559
592
|
</div>
|
|
@@ -571,6 +604,8 @@ const recentRuns = await ScriptRun.find()
|
|
|
571
604
|
es: null,
|
|
572
605
|
};
|
|
573
606
|
|
|
607
|
+
let currentRunId = null;
|
|
608
|
+
|
|
574
609
|
function qs(id) {
|
|
575
610
|
return document.getElementById(id);
|
|
576
611
|
}
|
|
@@ -772,8 +807,12 @@ const recentRuns = await ScriptRun.find()
|
|
|
772
807
|
<div class="text-xs text-gray-500 font-mono">${String(r._id || '').slice(0, 10)} · ${String(r.createdAt || '').replace(/</g,'<')}</div>
|
|
773
808
|
`;
|
|
774
809
|
btn.addEventListener('click', () => {
|
|
810
|
+
currentRunId = r._id; // Set the current run ID when clicking on a previous run
|
|
775
811
|
setOutput('');
|
|
776
812
|
if (r.outputTail) setOutput(r.outputTail, true);
|
|
813
|
+
|
|
814
|
+
// Load programmatic output for the selected run
|
|
815
|
+
loadProgrammaticOutput(r._id);
|
|
777
816
|
});
|
|
778
817
|
list.appendChild(btn);
|
|
779
818
|
});
|
|
@@ -894,8 +933,16 @@ const recentRuns = await ScriptRun.find()
|
|
|
894
933
|
|
|
895
934
|
setOutput('');
|
|
896
935
|
const res = await api('/api/admin/scripts/' + encodeURIComponent(state.selectedId) + '/run', { method: 'POST' });
|
|
936
|
+
currentRunId = res.runId; // Set the current run ID
|
|
897
937
|
startSse(res.runId);
|
|
898
938
|
await loadRuns();
|
|
939
|
+
|
|
940
|
+
// Load programmatic output after script starts
|
|
941
|
+
setTimeout(() => {
|
|
942
|
+
if (currentRunId) {
|
|
943
|
+
loadProgrammaticOutput(currentRunId);
|
|
944
|
+
}
|
|
945
|
+
}, 1000); // Wait a bit for the script to start producing output
|
|
899
946
|
}
|
|
900
947
|
|
|
901
948
|
qs('btn-refresh').addEventListener('click', loadScripts);
|
|
@@ -1080,6 +1127,176 @@ const recentRuns = await ScriptRun.find()
|
|
|
1080
1127
|
});
|
|
1081
1128
|
}
|
|
1082
1129
|
|
|
1130
|
+
// Tab switching functionality
|
|
1131
|
+
function switchOutputTab(tabName) {
|
|
1132
|
+
// Hide all tab contents
|
|
1133
|
+
document.querySelectorAll('.output-tab-content').forEach(content => {
|
|
1134
|
+
content.classList.add('hidden');
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Remove active class from all tabs
|
|
1138
|
+
document.querySelectorAll('.output-tab').forEach(tab => {
|
|
1139
|
+
tab.classList.remove('active', 'border-blue-500', 'text-blue-600');
|
|
1140
|
+
tab.classList.add('border-transparent', 'text-gray-500');
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// Show selected tab content
|
|
1144
|
+
document.getElementById(`${tabName}-tab-content`).classList.remove('hidden');
|
|
1145
|
+
|
|
1146
|
+
// Activate selected tab
|
|
1147
|
+
const activeTab = document.querySelector(`[data-tab="${tabName}"]`);
|
|
1148
|
+
activeTab.classList.add('active', 'border-blue-500', 'text-blue-600');
|
|
1149
|
+
activeTab.classList.remove('border-transparent', 'text-gray-500');
|
|
1150
|
+
|
|
1151
|
+
// Load full logs if switching to full-logs tab
|
|
1152
|
+
if (tabName === 'full-logs' && currentRunId) {
|
|
1153
|
+
loadFullLogs(currentRunId);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Load programmatic output if switching to output tab
|
|
1157
|
+
if (tabName === 'output' && currentRunId) {
|
|
1158
|
+
loadProgrammaticOutput(currentRunId);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Load programmatic output from API
|
|
1163
|
+
async function loadProgrammaticOutput(runId) {
|
|
1164
|
+
try {
|
|
1165
|
+
const response = await fetch(`${window.BASE_URL}/api/admin/scripts/runs/${runId}/programmatic-output`);
|
|
1166
|
+
const data = await response.json();
|
|
1167
|
+
|
|
1168
|
+
displayProgrammaticOutput(data);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
console.error('Failed to load programmatic output:', error);
|
|
1171
|
+
document.getElementById('output').textContent = 'Error loading programmatic output';
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Display programmatic output with smart formatting
|
|
1176
|
+
function displayProgrammaticOutput(data) {
|
|
1177
|
+
const outputElement = document.getElementById('output');
|
|
1178
|
+
|
|
1179
|
+
if (data.isJson && data.parsedResult) {
|
|
1180
|
+
// Format JSON for clean display
|
|
1181
|
+
outputElement.textContent = JSON.stringify(data.parsedResult, null, 2);
|
|
1182
|
+
outputElement.className = 'p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto json-output';
|
|
1183
|
+
} else {
|
|
1184
|
+
// Display as plain text
|
|
1185
|
+
outputElement.textContent = data.programmaticOutput;
|
|
1186
|
+
outputElement.className = 'p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto';
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Load full logs from API
|
|
1191
|
+
async function loadFullLogs(runId) {
|
|
1192
|
+
try {
|
|
1193
|
+
const response = await fetch(`${window.BASE_URL}/api/admin/scripts/runs/${runId}/full-output`);
|
|
1194
|
+
const data = await response.json();
|
|
1195
|
+
|
|
1196
|
+
if (data.fullOutput) {
|
|
1197
|
+
displayFullLogs(data.fullOutput);
|
|
1198
|
+
} else {
|
|
1199
|
+
document.getElementById('full-logs-content').textContent = 'No full logs available';
|
|
1200
|
+
}
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
console.error('Failed to load full logs:', error);
|
|
1203
|
+
document.getElementById('full-logs-content').textContent = 'Error loading full logs';
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Display full logs with filtering
|
|
1208
|
+
function displayFullLogs(logs) {
|
|
1209
|
+
const content = document.getElementById('full-logs-content');
|
|
1210
|
+
const searchTerm = document.getElementById('log-search').value.toLowerCase();
|
|
1211
|
+
const filterType = document.getElementById('log-filter').value;
|
|
1212
|
+
|
|
1213
|
+
let filteredLogs = logs;
|
|
1214
|
+
|
|
1215
|
+
// Apply search filter
|
|
1216
|
+
if (searchTerm) {
|
|
1217
|
+
filteredLogs = logs.split('\n').filter(line =>
|
|
1218
|
+
line.toLowerCase().includes(searchTerm)
|
|
1219
|
+
).join('\n');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Apply type filter
|
|
1223
|
+
if (filterType !== 'all') {
|
|
1224
|
+
const lines = filteredLogs.split('\n');
|
|
1225
|
+
filteredLogs = lines.filter(line => {
|
|
1226
|
+
// Simple heuristic to determine log type
|
|
1227
|
+
if (filterType === 'stderr') {
|
|
1228
|
+
return line.includes('❌') || line.includes('🚨') || line.includes('Error') || line.includes('error');
|
|
1229
|
+
}
|
|
1230
|
+
return true; // stdout
|
|
1231
|
+
}).join('\n');
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
content.textContent = filteredLogs || 'No logs match the current filters';
|
|
1235
|
+
|
|
1236
|
+
// Auto-scroll if enabled
|
|
1237
|
+
if (document.getElementById('btn-auto-scroll').classList.contains('bg-blue-500')) {
|
|
1238
|
+
content.scrollTop = content.scrollHeight;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Download logs functionality
|
|
1243
|
+
async function downloadLogs() {
|
|
1244
|
+
if (!currentRunId) {
|
|
1245
|
+
alert('No script run selected');
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
try {
|
|
1250
|
+
const response = await fetch(`${window.BASE_URL}/api/admin/scripts/runs/${currentRunId}/download`);
|
|
1251
|
+
const blob = await response.blob();
|
|
1252
|
+
|
|
1253
|
+
const url = window.URL.createObjectURL(blob);
|
|
1254
|
+
const a = document.createElement('a');
|
|
1255
|
+
a.href = url;
|
|
1256
|
+
a.download = response.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'script-output.txt';
|
|
1257
|
+
document.body.appendChild(a);
|
|
1258
|
+
a.click();
|
|
1259
|
+
document.body.removeChild(a);
|
|
1260
|
+
window.URL.revokeObjectURL(url);
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
console.error('Failed to download logs:', error);
|
|
1263
|
+
alert('Failed to download logs');
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// Add event listeners for new functionality
|
|
1268
|
+
document.querySelectorAll('.output-tab').forEach(tab => {
|
|
1269
|
+
tab.addEventListener('click', () => {
|
|
1270
|
+
switchOutputTab(tab.dataset.tab);
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
document.getElementById('btn-download-logs').addEventListener('click', downloadLogs);
|
|
1275
|
+
|
|
1276
|
+
document.getElementById('log-search').addEventListener('input', () => {
|
|
1277
|
+
if (document.getElementById('full-logs-tab-content').classList.contains('hidden') === false) {
|
|
1278
|
+
const content = document.getElementById('full-logs-content');
|
|
1279
|
+
const currentLogs = content.dataset.originalLogs || content.textContent;
|
|
1280
|
+
content.dataset.originalLogs = currentLogs;
|
|
1281
|
+
displayFullLogs(currentLogs);
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
document.getElementById('log-filter').addEventListener('change', () => {
|
|
1286
|
+
if (document.getElementById('full-logs-tab-content').classList.contains('hidden') === false) {
|
|
1287
|
+
const content = document.getElementById('full-logs-content');
|
|
1288
|
+
const currentLogs = content.dataset.originalLogs || content.textContent;
|
|
1289
|
+
content.dataset.originalLogs = currentLogs;
|
|
1290
|
+
displayFullLogs(currentLogs);
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
document.getElementById('btn-auto-scroll').addEventListener('click', function() {
|
|
1295
|
+
this.classList.toggle('bg-blue-500');
|
|
1296
|
+
this.classList.toggle('bg-gray-100');
|
|
1297
|
+
this.textContent = this.classList.contains('bg-blue-500') ? 'Auto-scroll ON' : 'Auto-scroll';
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1083
1300
|
(async function init() {
|
|
1084
1301
|
clearForm();
|
|
1085
1302
|
normalizeRunnerOptions();
|
|
@@ -0,0 +1,269 @@
|
|
|
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>Telegram Bots - SuperBackend</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="bg-gray-50 min-h-screen">
|
|
12
|
+
<div id="app" class="p-6 max-w-6xl mx-auto" v-cloak>
|
|
13
|
+
<div class="flex justify-between items-center mb-8">
|
|
14
|
+
<div>
|
|
15
|
+
<h1 class="text-2xl font-bold text-gray-800">Telegram Bots</h1>
|
|
16
|
+
<p class="text-gray-500">Manage your Telegram bot integrations</p>
|
|
17
|
+
</div>
|
|
18
|
+
<button @click="openCreateModal" class="bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-blue-700 transition">
|
|
19
|
+
<i class="ti ti-plus"></i>
|
|
20
|
+
Add New Bot
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Bot List -->
|
|
25
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
26
|
+
<div v-for="bot in bots" :key="bot._id" class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
|
27
|
+
<div class="p-6">
|
|
28
|
+
<div class="flex justify-between items-start mb-4">
|
|
29
|
+
<div class="flex items-center gap-3">
|
|
30
|
+
<div :class="['w-12 h-12 rounded-full flex items-center justify-center text-2xl', bot.status === 'running' ? 'bg-green-100 text-green-600' : 'bg-gray-100 text-gray-400']">
|
|
31
|
+
<i class="ti ti-brand-telegram"></i>
|
|
32
|
+
</div>
|
|
33
|
+
<div>
|
|
34
|
+
<h3 class="font-bold text-gray-800">{{ bot.name }}</h3>
|
|
35
|
+
<span :class="['text-xs px-2 py-0.5 rounded-full', statusBadgeClass(bot.status)]">
|
|
36
|
+
{{ bot.status }}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="flex gap-2">
|
|
41
|
+
<button @click="toggleBot(bot)" :title="bot.isActive ? 'Stop' : 'Start'" :class="['p-2 rounded-lg border transition', bot.isActive ? 'bg-red-50 text-red-600 border-red-100 hover:bg-red-100' : 'bg-green-50 text-green-600 border-green-100 hover:bg-green-100']">
|
|
42
|
+
<i :class="['ti', bot.isActive ? 'ti-player-stop' : 'ti-player-play']"></i>
|
|
43
|
+
</button>
|
|
44
|
+
<button @click="editBot(bot)" class="p-2 text-gray-500 hover:bg-gray-100 rounded-lg border border-gray-200 transition">
|
|
45
|
+
<i class="ti ti-edit"></i>
|
|
46
|
+
</button>
|
|
47
|
+
<button @click="confirmDelete(bot)" class="p-2 text-red-500 hover:bg-red-50 rounded-lg border border-red-100 transition">
|
|
48
|
+
<i class="ti ti-trash"></i>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="space-y-3 text-sm">
|
|
54
|
+
<div class="flex justify-between">
|
|
55
|
+
<span class="text-gray-500">Agent:</span>
|
|
56
|
+
<span :class="['font-medium', !bot.defaultAgentId ? 'text-amber-600' : '']">
|
|
57
|
+
{{ bot.defaultAgentId?.name || 'Unassigned' }}
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="flex justify-between">
|
|
61
|
+
<span class="text-gray-500">Allowed Users:</span>
|
|
62
|
+
<span class="font-medium">{{ bot.allowedUserIds?.length || 'Any' }}</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div v-if="bot.lastError" class="mt-4 p-3 bg-red-50 text-red-700 rounded-lg border border-red-100 break-all">
|
|
65
|
+
<p class="font-bold mb-1">Last Error:</p>
|
|
66
|
+
{{ bot.lastError }}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- Empty State -->
|
|
74
|
+
<div v-if="bots.length === 0" class="bg-white rounded-xl border-2 border-dashed border-gray-200 p-12 text-center">
|
|
75
|
+
<i class="ti ti-brand-telegram text-6xl text-gray-200 mb-4 inline-block"></i>
|
|
76
|
+
<h3 class="text-lg font-medium text-gray-800">No bots configured</h3>
|
|
77
|
+
<p class="text-gray-500 mb-6">Start by adding your first Telegram bot</p>
|
|
78
|
+
<button @click="openCreateModal" class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition">
|
|
79
|
+
Add Bot
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- Modal -->
|
|
84
|
+
<div v-if="showModal" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
85
|
+
<div class="bg-white rounded-xl shadow-xl w-full max-w-md overflow-hidden">
|
|
86
|
+
<div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center">
|
|
87
|
+
<h3 class="font-bold text-gray-800">{{ editingBot ? 'Edit Bot' : 'Add New Bot' }}</h3>
|
|
88
|
+
<button @click="showModal = false" class="text-gray-400 hover:text-gray-600">
|
|
89
|
+
<i class="ti ti-x"></i>
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
<form @submit.prevent="saveBot" class="p-6 space-y-4">
|
|
93
|
+
<div>
|
|
94
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Friendly Name</label>
|
|
95
|
+
<input v-model="formData.name" type="text" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="My Support Bot">
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Bot Token</label>
|
|
99
|
+
<input v-model="formData.token" type="password" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="123456:ABC-DEF...">
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Default Agent <span class="text-xs text-gray-400 font-normal">(Optional)</span></label>
|
|
103
|
+
<select v-model="formData.defaultAgentId" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
104
|
+
<option value="">No Agent (Idle)</option>
|
|
105
|
+
<option v-for="agent in agents" :key="agent._id" :value="agent._id">{{ agent.name }}</option>
|
|
106
|
+
</select>
|
|
107
|
+
</div>
|
|
108
|
+
<div>
|
|
109
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Allowed Telegram User IDs (comma separated)</label>
|
|
110
|
+
<input v-model="allowedUsersInput" type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="123456, 789012">
|
|
111
|
+
<p class="text-xs text-gray-400 mt-1">Leave empty to allow any user (be careful!)</p>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="flex items-center gap-2 pt-2">
|
|
114
|
+
<input v-model="formData.isActive" type="checkbox" id="isActive" class="w-4 h-4 text-blue-600 rounded">
|
|
115
|
+
<label for="isActive" class="text-sm font-medium text-gray-700">Enable bot on save</label>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div class="flex gap-3 pt-4">
|
|
119
|
+
<button type="button" @click="showModal = false" class="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
|
|
120
|
+
Cancel
|
|
121
|
+
</button>
|
|
122
|
+
<button type="submit" :disabled="saving" class="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50">
|
|
123
|
+
{{ saving ? 'Saving...' : (editingBot ? 'Update' : 'Create') }}
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</form>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<script>
|
|
132
|
+
const { createApp, ref, onMounted } = Vue;
|
|
133
|
+
|
|
134
|
+
createApp({
|
|
135
|
+
setup() {
|
|
136
|
+
const bots = ref([]);
|
|
137
|
+
const agents = ref([]);
|
|
138
|
+
const showModal = ref(false);
|
|
139
|
+
const editingBot = ref(null);
|
|
140
|
+
const saving = ref(false);
|
|
141
|
+
const allowedUsersInput = ref('');
|
|
142
|
+
|
|
143
|
+
const formData = ref({
|
|
144
|
+
name: '',
|
|
145
|
+
token: '',
|
|
146
|
+
defaultAgentId: '',
|
|
147
|
+
isActive: false,
|
|
148
|
+
allowedUserIds: []
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const baseUrl = '<%= baseUrl %>';
|
|
152
|
+
|
|
153
|
+
const fetchBots = async () => {
|
|
154
|
+
const res = await fetch(`${baseUrl}/api/admin/telegram`);
|
|
155
|
+
const data = await res.json();
|
|
156
|
+
bots.value = data.items;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const fetchAgents = async () => {
|
|
160
|
+
const res = await fetch(`${baseUrl}/api/admin/agents`);
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
agents.value = data.items;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const openCreateModal = () => {
|
|
166
|
+
editingBot.value = null;
|
|
167
|
+
allowedUsersInput.value = '';
|
|
168
|
+
formData.value = {
|
|
169
|
+
name: '',
|
|
170
|
+
token: '',
|
|
171
|
+
defaultAgentId: '',
|
|
172
|
+
isActive: false,
|
|
173
|
+
allowedUserIds: []
|
|
174
|
+
};
|
|
175
|
+
showModal.value = true;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const editBot = (bot) => {
|
|
179
|
+
editingBot.value = bot;
|
|
180
|
+
allowedUsersInput.value = (bot.allowedUserIds || []).join(', ');
|
|
181
|
+
formData.value = {
|
|
182
|
+
name: bot.name,
|
|
183
|
+
token: bot.token,
|
|
184
|
+
defaultAgentId: bot.defaultAgentId?._id || bot.defaultAgentId || '',
|
|
185
|
+
isActive: bot.isActive,
|
|
186
|
+
allowedUserIds: bot.allowedUserIds || []
|
|
187
|
+
};
|
|
188
|
+
showModal.value = true;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const toggleBot = async (bot) => {
|
|
192
|
+
await fetch(`${baseUrl}/api/admin/telegram/${bot._id}/toggle`, { method: 'POST' });
|
|
193
|
+
fetchBots();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const confirmDelete = async (bot) => {
|
|
197
|
+
if (confirm(`Are you sure you want to delete ${bot.name}?`)) {
|
|
198
|
+
await fetch(`${baseUrl}/api/admin/telegram/${bot._id}`, { method: 'DELETE' });
|
|
199
|
+
fetchBots();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const saveBot = async () => {
|
|
204
|
+
saving.value = true;
|
|
205
|
+
try {
|
|
206
|
+
formData.value.allowedUserIds = allowedUsersInput.value
|
|
207
|
+
.split(',')
|
|
208
|
+
.map(s => s.trim())
|
|
209
|
+
.filter(s => s.length > 0);
|
|
210
|
+
|
|
211
|
+
const url = editingBot.value
|
|
212
|
+
? `${baseUrl}/api/admin/telegram/${editingBot.value._id}`
|
|
213
|
+
: `${baseUrl}/api/admin/telegram`;
|
|
214
|
+
|
|
215
|
+
const method = editingBot.value ? 'PUT' : 'POST';
|
|
216
|
+
|
|
217
|
+
const res = await fetch(url, {
|
|
218
|
+
method,
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify(formData.value)
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (res.ok) {
|
|
224
|
+
showModal.value = false;
|
|
225
|
+
fetchBots();
|
|
226
|
+
} else {
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
alert(data.error || 'Failed to save bot');
|
|
229
|
+
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
alert(err.message);
|
|
232
|
+
} finally {
|
|
233
|
+
saving.value = false;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const statusBadgeClass = (status) => {
|
|
238
|
+
switch (status) {
|
|
239
|
+
case 'running': return 'bg-green-100 text-green-700';
|
|
240
|
+
case 'error': return 'bg-red-100 text-red-700';
|
|
241
|
+
default: return 'bg-gray-100 text-gray-700';
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
onMounted(() => {
|
|
246
|
+
fetchBots();
|
|
247
|
+
fetchAgents();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
bots,
|
|
252
|
+
agents,
|
|
253
|
+
showModal,
|
|
254
|
+
editingBot,
|
|
255
|
+
formData,
|
|
256
|
+
allowedUsersInput,
|
|
257
|
+
saving,
|
|
258
|
+
openCreateModal,
|
|
259
|
+
editBot,
|
|
260
|
+
toggleBot,
|
|
261
|
+
confirmDelete,
|
|
262
|
+
saveBot,
|
|
263
|
+
statusBadgeClass
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}).mount('#app');
|
|
267
|
+
</script>
|
|
268
|
+
</body>
|
|
269
|
+
</html>
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
{ id: 'i18n', label: 'I18n Entries', path: adminPath + '/i18n', icon: 'ti-language' },
|
|
25
25
|
{ id: 'locales', label: 'I18n Locales', path: adminPath + '/i18n/locales', icon: 'ti-world' },
|
|
26
26
|
{ id: 'json', label: 'JSON Configs', path: adminPath + '/json-configs', icon: 'ti-braces' },
|
|
27
|
+
{ id: 'markdowns', label: 'Markdowns', path: adminPath + '/markdowns', icon: 'ti-file-description' },
|
|
27
28
|
{ id: 'seo', label: 'SEO Config', path: adminPath + '/seo-config', icon: 'ti-search' },
|
|
28
29
|
{ id: 'assets', label: 'Assets', path: adminPath + '/assets', icon: 'ti-photo' },
|
|
29
30
|
{ id: 'file-manager', label: 'File Manager', path: adminPath + '/file-manager', icon: 'ti-folder' },
|
|
@@ -70,6 +71,8 @@
|
|
|
70
71
|
{
|
|
71
72
|
title: 'Automation',
|
|
72
73
|
items: [
|
|
74
|
+
{ id: 'agents', label: 'AI Agents', path: adminPath + '/agents', icon: 'ti-robot' },
|
|
75
|
+
{ id: 'telegram', label: 'Telegram Bots', path: adminPath + '/telegram', icon: 'ti-brand-telegram' },
|
|
73
76
|
{ id: 'workflows', label: 'Workflows', path: adminPath + '/workflows/all', icon: 'ti-robot' },
|
|
74
77
|
{ id: 'scripts', label: 'Scripts', path: adminPath + '/scripts', icon: 'ti-terminal-2' },
|
|
75
78
|
{ id: 'crons', label: 'Crons', path: adminPath + '/crons', icon: 'ti-clock' },
|
package/analysis-only.skill
DELETED
|
Binary file
|