/* Search Hub — 管理面板 */ (function () { 'use strict'; let currentEditSource = null; loadSources(); loadStatus(); loadApiDocs(); loadUsage(); // ===== 搜索源 ===== function loadSources() { fetch('/api/sources') .then(function (r) { return r.json(); }) .then(function (data) { var sources = data.sources || []; var html = ''; sources.forEach(function (s) { var badge = s.available ? '可用' : '不可用'; var keyInfo = s.needs_api_key ? (s.available ? ' | 密钥已配置' : ' | 需配置密钥') : ' | 无需密钥'; var editBtn = s.needs_api_key ? '' : ''; html += '
'; html += '
'; html += '
' + escapeHtml(s.display_name || s.name) + '
'; html += '
' + badge + editBtn + '
'; html += '
'; html += '
' + s.name + keyInfo + '
'; html += '
'; }); document.getElementById('sources-list').innerHTML = html; }); } // ===== 服务状态 ===== function loadStatus() { fetch('/api/status') .then(function (r) { return r.json(); }) .then(function (data) { document.getElementById('hub-status').textContent = data.status === 'ok' ? '运行中' : '异常'; document.getElementById('hub-version').textContent = data.version || '-'; document.getElementById('hub-history').textContent = data.history_count || 0; }); } // ===== API 文档 ===== function loadApiDocs() { fetch('/api/docs') .then(function (r) { return r.json(); }) .then(function (data) { var endpoints = data.endpoints || []; var html = ''; endpoints.forEach(function (ep) { var methodClass = ep.method === 'GET' ? 'method-get' : 'method-post'; html += '
'; html += ' ' + ep.method + ''; html += ' ' + ep.path + ''; html += '
' + escapeHtml(ep.desc) + '
'; if (ep.example) { html += ' '; html += '
' + escapeHtml(ep.example) + '
'; } html += '
'; }); document.getElementById('api-docs-list').innerHTML = html; }); } // ===== 服务用量(异步加载,不阻塞页面) ===== function loadUsage() { var el = document.getElementById('usage-list'); fetch('/api/admin/usage') .then(function (r) { return r.json(); }) .then(function (data) { var html = ''; // Tavily(优先显示 account 级别用量) var t = data.tavily || {}; if (t.available) { var usage = t.key && t.key.limit > 0 ? t.key.usage : (t.account ? t.account.search_usage : 0); var limit = t.key && t.key.limit > 0 ? t.key.limit : (t.account ? t.account.plan_limit : 0); var remaining = t.key && t.key.limit > 0 ? t.key.remaining : (limit - usage); var pct = limit > 0 ? Math.round(usage / limit * 100) : 0; html += '
'; html += ' Tavily'; html += '
已用: ' + usage + ' / ' + limit; html += ' 剩余: ' + remaining + '
'; if (t.account) { html += '
账户搜索: ' + t.account.search_usage + ' / ' + t.account.plan_limit; if (t.account.paygo_usage > 0) html += ' | 按量: ' + t.account.paygo_usage; html += '
'; } html += '
'; } else { html += '
Tavily
' + (t.error || '未配置') + '
'; } // 百度 VDB var b = data.baidu_vdb || {}; if (b.available) { html += '
'; html += ' 百度 VDB'; html += '
免费额度: ' + b.freeQuota + '
'; html += '
'; } else { html += '
百度 VDB
' + (b.error || '未配置') + '
'; } el.innerHTML = html; }) .catch(function () { el.innerHTML = '
用量查询暂不可用
'; }); } window.toggleExample = function (btn) { var example = btn.nextElementSibling; if (example) { example.classList.toggle('visible'); btn.textContent = example.classList.contains('visible') ? '收起' : '显示示例'; } }; // ===== 编辑弹窗 ===== window.openEditModal = function (name) { currentEditSource = name; var modal = document.getElementById('edit-modal'); var title = document.getElementById('edit-modal-title'); var body = document.getElementById('edit-modal-body'); title.textContent = '加载中...'; body.innerHTML = '
加载中...
'; modal.style.display = 'flex'; fetch('/api/admin/sources/' + name) .then(function (r) { return r.json(); }) .then(function (data) { title.textContent = '编辑: ' + (data.display_name || name); var html = ''; if (data.fields.length === 0) { html = '
该搜索源无需配置
'; } else { data.fields.forEach(function (f) { html += '
'; html += ' '; if (f.type === 'password') { html += ' '; if (f.has_value) { html += '
当前: ' + escapeHtml(f.value) + '
'; } } else { html += ' '; } html += '
'; }); } body.innerHTML = html; }) .catch(function (err) { body.innerHTML = '
加载失败: ' + err.message + '
'; }); }; window.closeEditModal = function () { document.getElementById('edit-modal').style.display = 'none'; currentEditSource = null; }; window.saveConfig = function () { if (!currentEditSource) return; var body = document.getElementById('edit-modal-body'); var inputs = body.querySelectorAll('input[id^="cfg-"]'); var data = {}; inputs.forEach(function (input) { var key = input.id.replace('cfg-', ''); var val = input.value.trim(); if (val) data[key] = val; }); if (Object.keys(data).length === 0) { alert('请至少填写一个字段'); return; } var saveBtn = document.getElementById('save-btn'); saveBtn.disabled = true; saveBtn.textContent = '保存中...'; fetch('/api/admin/sources/' + currentEditSource, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) .then(function (r) { return r.json(); }) .then(function (result) { saveBtn.disabled = false; saveBtn.textContent = '保存'; if (result.success) { closeEditModal(); loadSources(); loadStatus(); } else { alert('保存失败: ' + (result.error || '未知错误')); } }) .catch(function (err) { saveBtn.disabled = false; saveBtn.textContent = '保存'; alert('请求失败: ' + err.message); }); }; // ===== 辅助 ===== function escapeHtml(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } })();