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('debugMode').value = s.debug_mode || 'off'; document.getElementById('envUrl').textContent = s.env_target_url ? '环境变量: ' + s.env_target_url : ''; document.getElementById('envKey').textContent = s.env_api_key ? '环境变量: (已配置)' : '环境变量: (未设置)'; await loadMappings(); checkHealth(); loadStats(); } catch (e) { toast('加载设置失败: ' + e.message, false); } } async function loadStats() { const el = document.getElementById('statsContent'); try { const data = await api('/api/admin/stats'); const models = data.models || {}; const keys = Object.keys(models); if (!keys.length) { el.innerHTML = '
暂无请求统计数据
'; return; } const uptime = data.uptime_seconds || 0; const h = Math.floor(uptime / 3600); const m = Math.floor((uptime % 3600) / 60); let html = '
运行时长: ' + h + '小时' + m + '分钟
'; html += ''; keys.sort((a, b) => models[b].request_count - models[a].request_count); for (const name of keys) { const s = models[name]; html += ''; } html += '
模型请求数输入 Tokens输出 Tokens总 Tokens
' + esc(name) + '' + s.request_count + '' + s.input_tokens.toLocaleString() + '' + s.output_tokens.toLocaleString() + '' + s.total_tokens.toLocaleString() + '
'; el.innerHTML = html; } catch (e) { el.innerHTML = '
加载统计失败
'; } } 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(), debug_mode: document.getElementById('debugMode').value, }), }); 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 === 'responses' ? 'tag-responses' : backend === 'openai' ? 'tag-openai' : backend === 'gemini' ? 'tag-gemini' : 'tag-auto'; const tagLabel = backend === 'auto' ? '自动' : backend === 'responses' ? 'responses' : backend; const hasOverride = m.target_url || m.api_key; const hasInstructions = !!m.custom_instructions; const hasBodyMods = m.body_modifications && Object.keys(m.body_modifications).length > 0; const hasHeaderMods = m.header_modifications && Object.keys(m.header_modifications).length > 0; return `
${esc(name)} ${esc(m.upstream_model || name)}
${tagLabel} ${hasOverride ? '自定义地址' : ''} ${hasInstructions ? '自定义指令' : ''} ${hasBodyMods ? 'Body修改' : ''} ${hasHeaderMods ? 'Header修改' : ''}
`; }).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('mInstructions').value = ''; document.getElementById('mInsPosition').value = 'prepend'; document.getElementById('mBodyMods').value = ''; document.getElementById('mHeaderMods').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('mInstructions').value = m.custom_instructions || ''; document.getElementById('mInsPosition').value = m.instructions_position || 'prepend'; document.getElementById('mBodyMods').value = m.body_modifications && Object.keys(m.body_modifications).length ? JSON.stringify(m.body_modifications, null, 2) : ''; document.getElementById('mHeaderMods').value = m.header_modifications && Object.keys(m.header_modifications).length ? JSON.stringify(m.header_modifications, null, 2) : ''; 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; } let bodyMods = {}; const bodyModsStr = document.getElementById('mBodyMods').value.trim(); if (bodyModsStr) { try { bodyMods = JSON.parse(bodyModsStr); } catch { toast('Body 修改不是有效的 JSON', false); return; } } let headerMods = {}; const headerModsStr = document.getElementById('mHeaderMods').value.trim(); if (headerModsStr) { try { headerMods = JSON.parse(headerModsStr); } catch { toast('Header 修改不是有效的 JSON', 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(), custom_instructions: document.getElementById('mInstructions').value, instructions_position: document.getElementById('mInsPosition').value, body_modifications: bodyMods, header_modifications: headerMods, }; 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(); });