vproxy / src /aistudio_api /static /index.html
jlshu's picture
Upload 85 files
95f8c4b verified
Raw History Blame Contribute Delete
13.4 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>AI Studio Proxy</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body x-data="app()" x-init="init()" @click="configOpen=false">
<div class="overlay" :class="{open:sidebarOpen}" @click="sidebarOpen=false"></div>
<nav class="sidebar" :class="{open:sidebarOpen}">
<div class="sidebar-header">
<div class="logo">AI</div><span>AI Studio Proxy</span>
</div>
<div class="nav-section">
<button class="nav-item" :class="{active:view==='dashboard'}" @click="go('dashboard')">
<svg viewBox="0 0 24 24">
<rect x="3" y="3" width="18" height="18" rx="2" />
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="9" y1="21" x2="9" y2="9" />
</svg> 控制面板 </button>
<button class="nav-item" :class="{active:view==='accounts'}" @click="go('accounts')">
<svg viewBox="0 0 24 24">
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 00-3-3.87" />
<path d="M16 3.13a4 4 0 010 7.75" />
</svg> 账号管理 </button>
</div>
<div class="sidebar-footer">
<div class="dot"></div><span>系统在线</span>
<button class="logout-btn" @click="logout()" title="登出" x-show="authEnabled">
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
</button>
</div>
</nav>
<div class="main">
<div class="topbar">
<div class="topbar-left">
<button class="mh-btn" @click="sidebarOpen=true">☰</button>
<span class="topbar-title" x-text="view==='dashboard'?'控制面板':'账号管理'"></span>
</div>
<div class="topbar-right" @click.stop>
<button class="icon-btn" :class="{active:configOpen}" @click="configOpen=!configOpen" title="API Token 设置">
<svg viewBox="0 0 24 24">
<path
d="M12.22 2h-.44a2 2 0 00-2 2v.18a2 2 0 01-1 1.73l-.43.25a2 2 0 01-2 0l-.15-.08a2 2 0 00-2.73.73l-.22.38a2 2 0 00.73 2.73l.15.1a2 2 0 011 1.72v.51a2 2 0 01-1 1.74l-.15.09a2 2 0 00-.73 2.73l.22.38a2 2 0 002.73.73l.15-.08a2 2 0 012 0l.43.25a2 2 0 011 1.73V20a2 2 0 002 2h.44a2 2 0 002-2v-.18a2 2 0 011-1.73l.43-.25a2 2 0 012 0l.15.08a2 2 0 002.73-.73l.22-.39a2 2 0 00-.73-2.73l-.15-.08a2 2 0 01-1-1.74v-.5a2 2 0 011-1.74l.15-.09a2 2 0 00.73-2.73l-.22-.38a2 2 0 00-2.73-.73l-.15.08a2 2 0 01-2 0l-.43-.25a2 2 0 01-1-1.73V4a2 2 0 00-2-2z" />
<circle cx="12" cy="12" r="3" />
</svg>
</button>
<div class="cfg-dropdown" x-show="configOpen" x-cloak @click.stop>
<div class="cfg-head">API Token 设置</div>
<div class="cfg-body">
<div class="cfg-item">
<div class="cfg-info">
<div class="cfg-name">API Token</div>
<div class="cfg-desc">开启鉴权后会自动附带 Bearer Token,仅保存在当前浏览器</div>
</div>
<div class="cfg-ctrl"><input class="cfg-num" type="password" x-model="auth.token"
placeholder="留空则不发送"></div>
</div>
<div class="cfg-sep"></div>
<div class="cfg-item" style="padding-top:4px">
<button class="btn btn-danger btn-block" @click="clearCache()">清理本地缓存</button>
</div>
</div>
</div>
</div>
</div>
<!-- Dashboard -->
<div class="page-wrap" :class="{active:view==='dashboard'}">
<div class="scroll-area">
<div class="page-pad">
<div class="stat-grid">
<div class="stat-card">
<div class="slabel">当前激活账号</div>
<div class="sval" x-text="activeAccount.email||'暂无'"></div>
</div>
<div class="stat-card">
<div class="slabel">轮询模式</div>
<div class="sval" x-text="rotationMode==='round_robin'?'顺序轮询':rotationMode"></div>
</div>
<div class="stat-card">
<div class="slabel">总请求数</div>
<div class="sval" x-text="totalReqs"></div>
</div>
<div class="stat-card">
<div class="slabel">429 频率限制数</div>
<div class="sval sval-amber" x-text="totalRL"></div>
</div>
</div>
<div class="card">
<div class="card-head">模型统计</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>模型</th>
<th>请求</th>
<th>成功</th>
<th>429</th>
<th>错误</th>
<th>最后使用</th>
</tr>
</thead>
<tbody>
<template x-for="(s,n) in stats" :key="n">
<tr>
<td style="font-weight:500" x-text="n.replace('models/','')"></td>
<td x-text="s.requests||0"></td>
<td><span class="badge badge-green" x-text="s.success||0"></span></td>
<td><span class="badge" :class="(s.rate_limited||0)>0?'badge-red':'badge-gray'"
x-text="s.rate_limited||0"></span></td>
<td x-text="s.errors||0"></td>
<td style="font-size:13px;color:var(--muted)" x-text="fmtDate(s.last_used)"></td>
</tr>
</template>
<tr x-show="!Object.keys(stats).length">
<td colspan="6" style="text-align:center;color:var(--muted);padding:24px">暂无数据</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Accounts -->
<div class="page-wrap" :class="{active:view==='accounts'}">
<div class="scroll-area">
<div class="page-pad">
<div
style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;margin-bottom:24px">
<h1 style="font-size:24px;font-weight:700">账号管理</h1>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-primary" @click="cookieModal.open=true">+ 导入 Cookies</button>
</div>
</div>
<div class="card" style="margin-bottom:24px">
<div class="card-head">轮询设置</div>
<div style="padding:20px;display:flex;flex-wrap:wrap;gap:16px;align-items:end">
<div>
<div style="font-size:13px;color:var(--muted);margin-bottom:4px">模式</div>
<div class="cselect" :class="{open:openSelect==='rotMode'}" @click.stop>
<div class="cselect-btn" @click="toggleSelect('rotMode',$event)" tabindex="0" style="min-width:130px">
<span
x-text="rotCfg.mode==='round_robin'?'顺序轮询':rotCfg.mode==='lru'?'最近最少使用 (LRU)':rotCfg.mode==='least_rl'?'最小频率限制':'未知'"></span><span
class="arrow"></span></div>
<div class="cselect-menu"><template x-for="v in ['round_robin','lru','least_rl']">
<div class="cselect-opt" :class="{active:rotCfg.mode===v}" @click="rotCfg.mode=v;openSelect=null"
x-text="v==='round_robin'?'顺序轮询':v==='lru'?'最近最少使用 (LRU)':v==='least_rl'?'最小频率限制':v"></div>
</template></div>
</div>
</div>
<div>
<div style="font-size:13px;color:var(--muted);margin-bottom:4px">冷却时间 (秒)</div><input type="number"
x-model.number="rotCfg.cooldown"
style="width:80px;padding:8px;border:1px solid var(--border);border-radius:8px;font-size:14px"
min="0">
</div>
<button class="btn" @click="saveRotation()">保存设置</button>
<button class="btn" @click="forceNext()">强制切换</button>
</div>
</div>
<div class="card">
<div class="card-head"><span>账号池</span><span style="font-size:13px;color:var(--muted)"
x-text="accountRows.length+' 个账号'"></span></div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>账号 / 邮箱</th>
<th>子账号</th>
<th>请求</th>
<th>成功</th>
<th>429</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<template x-for="a in accountRows" :key="a.id">
<tr>
<td>
<div style="font-weight:500" x-text="a.email||a.name||a.id"></div>
<div style="font-size:12px;color:var(--muted);font-family:monospace" x-text="a.id"></div>
</td>
<td>
<span class="badge" style="background:rgba(59,130,246,0.15);color:#3b82f6;font-family:monospace;font-weight:600" x-text="'u/' + (a.auth_user !== undefined ? a.auth_user : '0')"></span>
</td>
<td x-text="a.requests||0"></td>
<td><span class="badge badge-green" x-text="a.success||0"></span></td>
<td><span class="badge" :class="(a.rate_limited||0)>0?'badge-red':'badge-gray'"
x-text="a.rate_limited||0"></span></td>
<td><span class="badge badge-green" x-show="a.id===activeId">激活</span><span
class="badge badge-gray" x-show="a.id!==activeId">待命</span></td>
<td><button class="btn btn-sm" x-show="a.id!==activeId"
@click="activateAccount(a.id)">手动激活</button></td>
</tr>
</template>
<tr x-show="!accountRows.length">
<td colspan="7" style="text-align:center;color:var(--muted);padding:24px">暂无账号</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Cookie Import Modal -->
<div class="modal-overlay" x-show="cookieModal.open" x-cloak @click.self="cookieModal.open=false"
x-transition.opacity>
<div class="modal" x-show="cookieModal.open" x-transition>
<div class="modal-head">
<span>导入 Cookies</span>
<button class="modal-close" @click="cookieModal.open=false">&times;</button>
</div>
<div class="modal-body">
<div class="modal-field">
<label>Cookie 数据 <span style="color:var(--muted);font-weight:400">(支持 Cookie 插件导出的 JSON 数组、Netscape 7列、或 KV 文本)</span></label>
<textarea class="modal-textarea" x-model="cookieModal.cookies"
placeholder='[{"name":"SAPISID","value":"..."},...] 或&#10;SID=xxx; SSID=xxx; HSID=xxx; ...'
rows="7"></textarea>
</div>
<div style="margin-bottom:12px;display:flex;align-items:center;gap:8px">
<input type="checkbox" id="autoProbeCheck" x-model="cookieModal.autoProbe" style="cursor:pointer;width:16px;height:16px">
<label for="autoProbeCheck" style="cursor:pointer;font-size:13px;font-weight:500">⚡ 自动无限向下探活多登录账号 (u/0, u/1, u/2... 一键批量分化导入)</label>
</div>
<div style="display:flex;gap:12px">
<div class="modal-field" style="flex:1">
<label>账号名称前缀 <span style="color:var(--muted);font-weight:400">(可选)</span></label>
<input class="modal-input" x-model="cookieModal.name" placeholder="Google Account">
</div>
<div class="modal-field" style="flex:1" x-show="!cookieModal.autoProbe">
<label>邮箱 <span style="color:var(--muted);font-weight:400">(可选)</span></label>
<input class="modal-input" x-model="cookieModal.email" placeholder="user@gmail.com">
</div>
</div>
</div>
<div class="modal-foot">
<button class="btn" @click="cookieModal.open=false">取消</button>
<button class="btn btn-primary" :disabled="cookieModal.importing" @click="importCookies()">
<span x-show="!cookieModal.importing" x-text="cookieModal.autoProbe ? '⚡ 探活并导入' : '导入'"></span>
<span x-show="cookieModal.importing">探活与导入中...</span>
</button>
</div>
</div>
</div>
<div class="toast" :class="{show:toast.show}" x-text="toast.msg"></div>
<script src="/static/app.js"></script>
<script defer src="/static/alpine.min.js"></script>
</body>
</html>