@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.
Files changed (41) hide show
  1. package/index.js +2 -0
  2. package/manage.js +745 -0
  3. package/package.json +4 -2
  4. package/src/controllers/admin.controller.js +11 -5
  5. package/src/controllers/adminAgents.controller.js +37 -0
  6. package/src/controllers/adminLlm.controller.js +19 -0
  7. package/src/controllers/adminMarkdowns.controller.js +157 -0
  8. package/src/controllers/adminScripts.controller.js +138 -0
  9. package/src/controllers/adminTelegram.controller.js +72 -0
  10. package/src/controllers/markdowns.controller.js +42 -0
  11. package/src/helpers/mongooseHelper.js +6 -6
  12. package/src/helpers/scriptBase.js +2 -2
  13. package/src/middleware.js +136 -29
  14. package/src/models/Agent.js +105 -0
  15. package/src/models/AgentMessage.js +82 -0
  16. package/src/models/Markdown.js +75 -0
  17. package/src/models/ScriptRun.js +8 -0
  18. package/src/models/TelegramBot.js +42 -0
  19. package/src/routes/adminAgents.routes.js +13 -0
  20. package/src/routes/adminLlm.routes.js +1 -0
  21. package/src/routes/adminMarkdowns.routes.js +16 -0
  22. package/src/routes/adminScripts.routes.js +4 -1
  23. package/src/routes/adminTelegram.routes.js +14 -0
  24. package/src/routes/markdowns.routes.js +16 -0
  25. package/src/services/agent.service.js +546 -0
  26. package/src/services/agentHistory.service.js +345 -0
  27. package/src/services/agentTools.service.js +578 -0
  28. package/src/services/jsonConfigs.service.js +22 -10
  29. package/src/services/llm.service.js +219 -6
  30. package/src/services/markdowns.service.js +522 -0
  31. package/src/services/scriptsRunner.service.js +328 -37
  32. package/src/services/telegram.service.js +130 -0
  33. package/views/admin-agents.ejs +273 -0
  34. package/views/admin-coolify-deploy.ejs +8 -8
  35. package/views/admin-dashboard.ejs +36 -5
  36. package/views/admin-experiments.ejs +1 -1
  37. package/views/admin-markdowns.ejs +905 -0
  38. package/views/admin-scripts.ejs +221 -4
  39. package/views/admin-telegram.ejs +269 -0
  40. package/views/partials/dashboard/nav-items.ejs +3 -0
  41. package/analysis-only.skill +0 -0
@@ -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 flex items-center justify-between">
553
- <div class="text-sm font-medium text-gray-800">Output</div>
554
- <button id="btn-clear-output" class="text-sm text-gray-600 hover:underline">Clear</button>
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,'&lt;')}</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' },
Binary file