feat(ai): provider OpenAI-compatible + DeepSeek, AI Telegram, self-healing dengan toggle (port upstream)

- telegramAiService/adminAiTools: dukung openai, deepseek, groq, openrouter, custom (base URL + key + model)
- selfHealingService: billing vs isolir, konsistensi secret PPPoE, sesi RADIUS stale, IP ACS kosong
- mikrotikService: getActivePppoeUsers, isolateCustomerPppoe, unisolateCustomerPppoe (upstream memanggil fungsi yang tidak ada)
- cron 30 menit hanya jalan bila ai_self_healing_enabled=true (default off) + scan brute force MikroTik tiap 2 menit
- Pengaturan: kartu AI & Self-Healing (provider, key/model, toggle, tombol Diagnosa/Perbaiki)
- bot Telegram: pesan non-command dari admin dijawab AI
This commit is contained in:
oxpoe
2026-11-05 01:48:50 +07:00
parent 1c40e6a0e8
commit 4003149046
9 changed files with 2022 additions and 4 deletions

View File

@@ -4760,6 +4760,12 @@ router.post('/settings', requireAdminSession, express.urlencoded({ extended: tru
if (newSettings.voucher_enabled === 'true') newSettings.voucher_enabled = true;
else if (newSettings.voucher_enabled === 'false') newSettings.voucher_enabled = false;
newSettings.ai_provider = String(newSettings.ai_provider || 'deepseek').trim().toLowerCase();
newSettings.ai_self_healing_enabled = newSettings.ai_self_healing_enabled === 'true' || newSettings.ai_self_healing_enabled === 'on';
for (const k of ['deepseek_api_key','openai_api_key','groq_api_key','openrouter_api_key','custom_ai_api_key','custom_ai_base_url','custom_ai_model','deepseek_model','openai_model','groq_model','openrouter_model']) {
if (newSettings[k] !== undefined) newSettings[k] = String(newSettings[k]).trim();
}
if (newSettings.qris_static_enabled === 'true') newSettings.qris_static_enabled = true;
else if (newSettings.qris_static_enabled === 'false') newSettings.qris_static_enabled = false;
@@ -5228,6 +5234,26 @@ router.get('/api/inventory/serials/:itemId', requireAdminSession, (req, res) =>
}
});
router.get('/api/self-heal/diagnose', requireAdminSession, restrictToAdmin, async (req, res) => {
try {
const svc = require('../services/selfHealingService');
const result = await svc.diagnose();
res.json({ success: true, ...result });
} catch (e) {
res.status(500).json({ success: false, message: e.message });
}
});
router.post('/api/self-heal/run', requireAdminSession, restrictToAdmin, async (req, res) => {
try {
const svc = require('../services/selfHealingService');
const result = await svc.autoRepair(req.session.adminUser || 'Web Admin');
res.json({ success: true, ...result });
} catch (e) {
res.status(500).json({ success: false, message: e.message });
}
});
router.get('/api/soundbox/poll', requireAdminSession, (req, res) => {
try {
const soundboxSvc = require('../services/soundboxService');