const API = ''; let authKey = ''; let editingName = null; function togglePwd(id) { const el = document.getElementById(id); el.type = el.type === 'password' ? 'text' : 'password'; } function toast(msg, ok = true) { const area = document.getElementById('toasts'); const el = document.createElement('div'); el.className = 'toast ' + (ok ? 'toast-ok' : 'toast-err'); el.textContent = msg; area.appendChild(el); setTimeout(() => el.remove(), 3000); } async function api(path, opts = {}) { const headers = { 'Content-Type': 'application/json' }; if (authKey) headers['Authorization'] = 'Bearer ' + authKey; const res = await fetch(API + path, { ...opts, headers }); const ct = res.headers.get('content-type') || ''; if (!ct.includes('application/json')) { const text = await res.text(); if (!res.ok) throw new Error('HTTP ' + res.status + ': ' + text.substring(0, 100)); throw new Error('服务器返回了非 JSON 响应'); } const data = await res.json(); if (!res.ok) { const e = data.error; const msg = (typeof e === 'object' && e !== null) ? (e.message || JSON.stringify(e)) : (e || data.message || 'HTTP ' + res.status); throw new Error(msg); } return data; } // ─── 登录 ─────────────────────────────────────────── async function doLogin() { const key = document.getElementById('loginKey').value.trim(); if (!key) { toast('请输入密钥', false); return; } try { const r = await api('/api/admin/login', { method: 'POST', body: JSON.stringify({ key }) }); if (r.ok) { authKey = key; sessionStorage.setItem('_ak', key); document.getElementById('login').style.display = 'none'; document.getElementById('dashboard').style.display = 'block'; loadDashboard(); } } catch (e) { toast('密钥无效', false); } } function doLogout() { authKey = ''; sessionStorage.removeItem('_ak'); document.getElementById('dashboard').style.display = 'none'; document.getElementById('login').style.display = 'flex'; } // ─── 仪表盘 ───────────────────────────────────────── async function loadDashboard() { try { const s = await api('/api/admin/settings'); document.getElementById('targetUrl').value = s.proxy_target_url || ''; document.getElementById('proxyKey').value = s.proxy_api_key || ''; document.getElementById('envUrl').textContent = s.env_target_url ? '环境变量: ' + s.env_target_url : ''; document.getElementById('envKey').textContent = s.env_api_key ? '环境变量: (已配置)' : '环境变量: (未设置)'; await loadMappings(); checkHealth(); } catch (e) { toast('加载设置失败: ' + e.message, false); } } async function checkHealth() { try { const r = await fetch(API + '/health'); const d = await r.json(); const b = document.getElementById('statusBadge'); if (d.status === 'ok') { b.textContent = '已连接'; b.style.background = 'rgba(34,197,94,.15)'; b.style.color = 'var(--green)'; } else { b.textContent = '异常'; } } catch { const b = document.getElementById('statusBadge'); b.textContent = '离线'; b.style.background = 'rgba(239,68,68,.15)'; b.style.color = 'var(--red)'; } } async function saveSettings() { try { await api('/api/admin/settings', { method: 'PUT', body: JSON.stringify({ proxy_target_url: document.getElementById('targetUrl').value.trim(), proxy_api_key: document.getElementById('proxyKey').value.trim(), }), }); toast('设置已保存'); } catch (e) { toast('保存失败: ' + e.message, false); } } // ─── 模型映射 ─────────────────────────────────────── async function loadMappings() { const mappings = await api('/api/admin/mappings'); const el = document.getElementById('mappingList'); const keys = Object.keys(mappings); if (!keys.length) { el.innerHTML = '
暂无模型映射
点击「+ 添加映射」开始配置
'; return; } el.innerHTML = '
' + keys.map(name => { const m = mappings[name]; const backend = m.backend || 'auto'; const tagClass = backend === 'anthropic' ? 'tag-anthropic' : backend === 'openai' ? 'tag-openai' : 'tag-auto'; const tagLabel = backend === 'auto' ? '自动' : backend; const hasOverride = m.target_url || m.api_key; return `
${esc(name)} ${esc(m.upstream_model || name)}
${tagLabel} ${hasOverride ? '自定义地址' : ''}
`; }).join('') + '
'; } function esc(s) { return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); } // ─── 弹窗 ────────────────────────────────────────── function openAddModal() { editingName = null; document.getElementById('modalTitle').textContent = '添加模型映射'; document.getElementById('mName').value = ''; document.getElementById('mName').disabled = false; document.getElementById('mUpstream').value = ''; document.getElementById('mBackend').value = 'auto'; document.getElementById('mUrl').value = ''; document.getElementById('mKey').value = ''; document.getElementById('modal').classList.add('active'); } async function openEditModal(name) { editingName = name; document.getElementById('modalTitle').textContent = '编辑模型映射'; try { const mappings = await api('/api/admin/mappings'); const m = mappings[name]; if (!m) { toast('映射未找到', false); return; } document.getElementById('mName').value = name; document.getElementById('mName').disabled = false; document.getElementById('mUpstream').value = m.upstream_model || ''; document.getElementById('mBackend').value = m.backend || 'auto'; document.getElementById('mUrl').value = m.target_url || ''; document.getElementById('mKey').value = m.api_key || ''; document.getElementById('modal').classList.add('active'); } catch (e) { toast('错误: ' + e.message, false); } } function closeModal() { document.getElementById('modal').classList.remove('active'); editingName = null; } async function saveMapping() { const name = document.getElementById('mName').value.trim(); const upstream = document.getElementById('mUpstream').value.trim(); if (!name) { toast('请填写 Cursor 模型名', false); return; } if (!upstream) { toast('请填写上游模型名', false); return; } const payload = { name, upstream_model: upstream, backend: document.getElementById('mBackend').value, target_url: document.getElementById('mUrl').value.trim(), api_key: document.getElementById('mKey').value.trim(), }; try { if (editingName) { await api('/api/admin/mappings/' + encodeURIComponent(editingName), { method: 'PUT', body: JSON.stringify(payload), }); toast('映射已更新'); } else { await api('/api/admin/mappings', { method: 'POST', body: JSON.stringify(payload), }); toast('映射已添加'); } closeModal(); await loadMappings(); } catch (e) { toast('操作失败: ' + e.message, false); } } async function deleteMapping(name) { if (!confirm('确定要删除映射「' + name + '」吗?')) return; try { await api('/api/admin/mappings/' + encodeURIComponent(name), { method: 'DELETE' }); toast('映射已删除'); await loadMappings(); } catch (e) { toast('删除失败: ' + e.message, false); } } // ─── 初始化 ───────────────────────────────────────── (function init() { const saved = sessionStorage.getItem('_ak'); if (saved) { authKey = saved; document.getElementById('login').style.display = 'none'; document.getElementById('dashboard').style.display = 'block'; loadDashboard(); } })(); document.getElementById('modal').addEventListener('click', function(e) { if (e.target === this) closeModal(); }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeModal(); });