cursor / static /tokens.html
ronghua's picture
up
3c2af29
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/x-icon" href="data:image/x-icon;,">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Token 信息管理</title>
<!-- 引入共享样式 -->
<link rel="stylesheet" href="/static/shared-styles.css">
<script src="/static/shared.js"></script>
<style>
.token-container {
display: grid;
gap: var(--spacing);
}
.token-section {
background: var(--card-background);
padding: var(--spacing);
border-radius: var(--border-radius);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.shortcuts {
margin-top: var(--spacing);
padding: 12px;
background: var(--disabled-bg);
border-radius: 4px;
font-size: 14px;
color: var(--text-secondary);
}
kbd {
background: var(--card-background);
border-radius: 3px;
border: 1px solid var(--border-color);
padding: 1px 4px;
font-size: 12px;
color: var(--text-primary);
}
.token-table {
width: 100%;
border-collapse: collapse;
margin: 8px 0;
background: var(--card-background);
}
.token-table th,
.token-table td {
padding: 8px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.token-table th {
background: var(--disabled-bg);
font-weight: 500;
color: var(--text-primary);
}
.token-list-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.token-list-header button {
padding: 4px 12px;
font-size: 14px;
}
/* Token表格样式优化 */
.token-table td {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-primary);
}
.token-table tr:hover {
background: var(--primary-color-alpha);
}
.token-table tr:hover td {
white-space: normal;
word-break: break-all;
}
/* 操作按钮样式 */
.action-cell {
width: 160px;
/* 增加宽度以容纳两个按钮 */
text-align: center !important;
}
.action-cell button {
padding: 2px 8px;
font-size: 12px;
white-space: nowrap;
margin: 0 2px;
/* 添加按钮间距 */
}
/* 提示框样式 */
.modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--card-background);
padding: 20px;
border-radius: var(--border-radius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
width: 90%;
max-width: 500px;
color: var(--text-primary);
}
.modal-backdrop {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.modal-header {
margin-bottom: 15px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.modal-header h3 {
margin: 0;
color: var(--text-primary);
}
.modal-footer {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid var(--border-color);
text-align: right;
}
/* 复选框容器样式 */
.checkbox-container {
margin: 8px 0;
}
.checkbox-container label {
display: inline;
margin-left: 8px;
color: var(--text-primary);
}
/* 帮助文本样式 */
.help-text {
color: var(--text-secondary);
}
@media (prefers-color-scheme: dark) {
.modal {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.token-table tr:hover {
background: rgba(144, 202, 249, 0.1);
/* --primary-color in dark mode */
}
}
/* Key结果样式 */
.key-result {
background: var(--card-background);
padding: var(--spacing);
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
margin-top: var(--spacing);
position: relative;
cursor: pointer;
transition: all var(--transition-fast);
}
.key-result:hover {
background: var(--primary-color-alpha);
border-color: var(--primary-color);
}
.key-result:active {
transform: translateY(1px);
}
.key-content {
overflow-x: auto;
white-space: nowrap;
scrollbar-width: thin;
/* Firefox */
-ms-overflow-style: none;
/* IE and Edge */
}
/* Webkit浏览器的滚动条样式 */
.key-content::-webkit-scrollbar {
height: 6px;
}
.key-content::-webkit-scrollbar-track {
background: var(--disabled-bg);
border-radius: 3px;
}
.key-content::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
.key-content::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
@media (prefers-color-scheme: dark) {
.key-content::-webkit-scrollbar-track {
background: var(--card-background);
}
.key-content::-webkit-scrollbar-thumb {
background: var(--text-secondary);
}
.key-content::-webkit-scrollbar-thumb:hover {
background: var(--text-primary);
}
}
.model-list {
max-height: 150px;
overflow-y: auto;
padding: 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
margin-top: 8px;
background: var(--card-background);
}
.model-item {
display: flex;
align-items: center;
margin-bottom: 4px;
}
.model-item input[type="checkbox"] {
margin-right: 8px;
}
/* 确认对话框的额外样式 */
#confirmModal {
max-width: 400px;
}
#confirmModal .modal-content {
margin: 20px 0;
text-align: center;
}
#confirmModal .modal-footer button {
min-width: 80px;
margin-left: 10px;
}
</style>
</head>
<body>
<h1>Token 信息管理</h1>
<div class="container">
<div class="form-group">
<label>认证令牌:</label>
<input type="password" id="authToken" placeholder="输入 AUTH_TOKEN">
</div>
</div>
<div class="token-container">
<div class="token-section">
<h3>Token 管理</h3>
<div class="button-group">
<button onclick="getTokenInfo()">获取当前配置</button>
<button onclick="reloadTokens()" class="secondary">重载Token</button>
<button onclick="addTokens()" class="secondary">添加Token</button>
<button onclick="deleteTokens()" class="danger">删除Token</button>
</div>
<div class="form-group">
<label>Token 操作:</label>
<textarea id="tokenInput" placeholder="每行一个 token"></textarea>
<div class="help-text">添加模式: 输入要添加的token,每行一个
删除模式: 输入要删除的token,每行一个</div>
</div>
<div class="form-group">
<div class="token-list-header">
<label>当前Token列表:</label>
<button onclick="copyTokenList()" class="secondary">复制列表</button>
</div>
<table class="token-table">
<thead>
<tr>
<th>Token</th>
<th>Checksum</th>
<th>邮箱</th>
<th>会员类型</th>
<th>Premium用量</th>
<th>试用剩余</th>
<th class="action-cell">操作</th>
</tr>
</thead>
<tbody id="tokenTableBody">
</tbody>
</table>
</div>
<div class="shortcuts">
快捷键: <kbd>Ctrl</kbd> + <kbd>Enter</kbd> 执行当前操作
</div>
</div>
</div>
<div id="message"></div>
<!-- 动态key生成对话框 -->
<div class="modal-backdrop" id="keyModal-backdrop" onclick="closeKeyModal()"></div>
<div class="modal" id="keyModal">
<div class="modal-header">
<h3>生成动态Key</h3>
</div>
<div class="form-group">
<label>图片处理能力:</label>
<select id="disableVision">
<option value="">跟随全局</option>
<option value="true">禁用</option>
<option value="false">启用</option>
</select>
</div>
<div class="form-group">
<label>慢速池:</label>
<select id="enableSlowPool">
<option value="">跟随全局</option>
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
<div class="form-group">
<label>使用量检查模型规则:</label>
<select id="usageCheckType" onchange="toggleModelList()">
<option value="">跟随全局</option>
<option value="default">默认</option>
<option value="disabled">禁用</option>
<option value="all">所有</option>
<option value="custom">自定义</option>
</select>
<div id="modelListContainer" class="model-list" style="display: none;">
<!-- 模型列表将通过 JavaScript 动态填充 -->
</div>
</div>
<div class="form-group">
<label>包含网络引用:</label>
<select id="includeWebReferences">
<option value="">跟随全局</option>
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
<div class="key-result" id="keyResult" style="display: none;" onclick="copyGeneratedKey()">
<div class="key-content" id="keyContent"></div>
</div>
<div class="modal-footer">
<button onclick="closeKeyModal()" class="secondary">取消</button>
<button onclick="generateKey()" class="primary">生成</button>
</div>
</div>
<!-- 添加确认对话框 -->
<div class="modal-backdrop" id="confirmModal-backdrop"></div>
<div class="modal" id="confirmModal">
<div class="modal-header">
<h3>确认删除</h3>
</div>
<div class="modal-content">
<p>确定要删除这个token吗?</p>
</div>
<div class="modal-footer">
<button onclick="closeConfirmModal()" class="secondary">取消</button>
<button onclick="confirmDelete()" class="danger">删除</button>
</div>
</div>
<script>
async function getTokenInfo() {
const data = await makeAuthenticatedRequest('/tokens/get');
if (data) {
const tableBody = document.getElementById('tokenTableBody');
tableBody.innerHTML = data.tokens.map(t => {
const profile = t.profile || {};
const user = profile.user || {};
const stripe = profile.stripe || {};
const usage = profile.usage || {};
const premium = usage.premium || {};
return `<tr><td title="${t.token}">${t.token}</td><td title="${t.checksum}">${t.checksum}</td><td>${user.email || '-'}</td><td>${formatMembershipType(stripe.membership_type)}</td><td>${premium.requests || 0}/${premium.max_requests || '∞'}</td><td>${stripe.days_remaining_on_trial > 0 ? `${stripe.days_remaining_on_trial}天` : '-'}</td><td class="action-cell"><button onclick="showKeyModal('${t.token}','${t.checksum}')" class="secondary">生成Key</button><button onclick="deleteToken('${t.token}')" class="danger">删除</button></td></tr>`;
}).join('');
showGlobalMessage('配置获取成功');
}
}
function copyTokenList() {
const tableBody = document.getElementById('tokenTableBody');
const rows = tableBody.getElementsByTagName('tr');
const tokenList = Array.from(rows).map(row => {
const token = row.cells[0].textContent;
const checksum = row.cells[1].textContent;
return `${token},${checksum}`;
}).join('\n');
navigator.clipboard.writeText(tokenList).then(() => {
showGlobalMessage('Token列表已复制到剪贴板');
}).catch(err => {
showGlobalMessage('复制失败: ' + err, true);
});
}
async function reloadTokens() {
const data = await makeAuthenticatedRequest('/tokens/reload');
if (data) {
showGlobalMessage(`Token重载成功: ${data.message}`);
getTokenInfo(); // 刷新当前配置
}
}
async function addTokens() {
const tokensInput = document.getElementById('tokenInput').value;
if (!tokensInput) {
showGlobalMessage('请输入要添加的Token', true);
return;
}
// 处理输入的tokens,跳过空行和注释,解析token和checksum
const tokenList = tokensInput.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
.map(line => {
const parts = line.includes(',') ? line.split(',') : [line];
return {
token: parts[0].trim(),
checksum: parts[1]?.trim() || null
};
});
if (tokenList.length === 0) {
showGlobalMessage('没有有效的Token输入', true);
return;
}
const data = await makeAuthenticatedRequest('/tokens/add', {
body: JSON.stringify(tokenList)
});
if (data) {
showGlobalMessage(`添加成功: ${data.message}`);
document.getElementById('tokenInput').value = '';
getTokenInfo(); // 刷新当前配置
}
}
async function deleteTokens() {
const tokensToDelete = document.getElementById('tokenInput').value;
if (!tokensToDelete) {
showGlobalMessage('请输入要删除的Token', true);
return;
}
const tokens = tokensToDelete.trim().split('\n').filter(t => t);
const data = await makeAuthenticatedRequest('/tokens/delete', {
body: JSON.stringify({
tokens: tokens,
expectation: 'detailed'
})
});
if (data) {
let message = '删除操作完成\n';
if (data.failed_tokens?.length) {
message += `\n未找到的Token: ${data.failed_tokens.join('\n')}`;
}
if (data.updated_tokens?.length) {
message += `\n剩余Token: ${data.updated_tokens.join('\n')}`;
}
showGlobalMessage(message, timeout = 30000);
document.getElementById('tokenInput').value = '';
getTokenInfo();
}
}
// 添加删除单个token的函数
let tokenToDelete = null;
function showConfirmModal(token) {
tokenToDelete = token;
const modal = document.getElementById('confirmModal');
const backdrop = document.getElementById('confirmModal-backdrop');
modal.style.display = 'block';
backdrop.style.display = 'block';
}
function closeConfirmModal() {
const modal = document.getElementById('confirmModal');
const backdrop = document.getElementById('confirmModal-backdrop');
modal.style.display = 'none';
backdrop.style.display = 'none';
tokenToDelete = null;
}
async function confirmDelete() {
if (!tokenToDelete) return;
const data = await makeAuthenticatedRequest('/tokens/delete', {
body: JSON.stringify({
tokens: [tokenToDelete],
expectation: 'detailed'
})
});
if (data) {
showGlobalMessage('Token删除成功');
getTokenInfo();
}
closeConfirmModal();
}
// 修改deleteToken函数
function deleteToken(token) {
showConfirmModal(token);
}
// 动态key相关函数
let availableModels = [];
let currentToken = '';
let currentChecksum = '';
async function getModels() {
try {
const response = await fetch('/v1/models');
const data = await response.json();
availableModels = data.data.map(model => model.id);
updateModelList();
} catch (error) {
showGlobalMessage('获取模型列表失败', true);
}
}
function updateModelList() {
const container = document.getElementById('modelListContainer');
container.innerHTML = availableModels.map(model => `<div class="model-item"><input type="checkbox" id="model_${model}" value="${model}"><label for="model_${model}">${model}</label></div>`).join('');
}
function toggleModelList() {
const type = document.getElementById('usageCheckType').value;
const container = document.getElementById('modelListContainer');
container.style.display = type === 'custom' ? 'block' : 'none';
}
function showKeyModal(token, checksum) {
currentToken = token;
currentChecksum = checksum;
const modal = document.getElementById('keyModal');
const backdrop = document.getElementById('keyModal-backdrop');
modal.style.display = 'block';
backdrop.style.display = 'block';
document.getElementById('keyResult').style.display = 'none';
// 添加点击事件处理
modal.addEventListener('click', function (event) {
event.stopPropagation(); // 防止点击modal内部时触发backdrop的点击事件
});
// 重置所有选项
document.getElementById('disableVision').value = '';
document.getElementById('enableSlowPool').value = '';
document.getElementById('usageCheckType').value = '';
document.getElementById('modelListContainer').style.display = 'none';
document.getElementById('includeWebReferences').value = '';
}
function closeKeyModal() {
document.getElementById('keyModal').style.display = 'none';
document.getElementById('keyModal-backdrop').style.display = 'none';
}
function parseBooleanFromString(value, defaultValue) {
if (value === '') return defaultValue;
return value === 'true';
}
async function generateKey() {
const type = document.getElementById('usageCheckType').value;
let modelIds = '';
if (type === 'custom') {
modelIds = Array.from(document.querySelectorAll('#modelListContainer input:checked'))
.map(input => input.value)
.join(',');
}
const payload = {
auth_token: `${currentToken},${currentChecksum}`,
disable_vision: parseBooleanFromString(document.getElementById('disableVision').value, undefined),
enable_slow_pool: parseBooleanFromString(document.getElementById('enableSlowPool').value, undefined),
usage_check_models: type ? {
type: type,
model_ids: type === 'custom' ? modelIds : undefined
} : undefined,
include_web_references: parseBooleanFromString(document.getElementById('includeWebReferences').value, undefined)
};
const data = await makeAuthenticatedRequest('/build-key', {
body: JSON.stringify(payload)
});
if (data && data.key) {
const keyResult = document.getElementById('keyResult');
const keyContent = document.getElementById('keyContent');
keyContent.textContent = data.key;
keyResult.style.display = 'block';
showGlobalMessage('动态Key已生成,点击复制');
}
}
function copyGeneratedKey(event) {
// 防止触发滚动条点击事件
if (event && event.target.classList.contains('key-content') && event.offsetX > event.target.clientWidth) {
return;
}
const keyContent = document.getElementById('keyContent').textContent;
navigator.clipboard.writeText(keyContent).then(() => {
showGlobalMessage('Key已复制到剪贴板');
}).catch(() => {
showGlobalMessage('复制失败', true);
});
}
// 快捷键支持
document.addEventListener('keydown', function (e) {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
const activeElement = document.activeElement;
if (activeElement.id === 'tokenInput') {
// 根据当前焦点确定操作
const action = document.querySelector('.button-group button.active');
if (action) {
action.click();
}
}
}
});
// 初始化 token 处理
initializeTokenHandling('authToken');
// 页面加载完成后获取当前配置和模型列表
document.addEventListener('DOMContentLoaded', () => {
getModels();
getTokenInfo();
});
</script>
</body>
</html>