v0.1.3-rc.1

This commit is contained in:
wisdgod
2024-12-30 23:29:37 +08:00
parent ea1acb555f
commit 5505ccc6cb
41 changed files with 9626 additions and 1215 deletions

226
static/config.html Normal file
View File

@@ -0,0 +1,226 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>配置管理</title>
<!-- 引入共享样式 -->
<link rel="stylesheet" href="/static/shared-styles.css">
<script src="/static/shared.js"></script>
</head>
<body>
<h1>配置管理</h1>
<div class="container">
<div class="form-group">
<label>路径:</label>
<select id="path">
<option value="/">根路径 (/)</option>
<option value="/logs">日志页面 (/logs)</option>
<option value="/config">配置页面 (/config)</option>
<option value="/tokeninfo">Token 信息页面 (/tokeninfo)</option>
<option value="/static/shared-styles.css">共享样式 (/static/shared-styles.css)</option>
<option value="/static/shared.js">共享脚本 (/static/shared.js)</option>
</select>
</div>
<div class="form-group">
<label>内容类型:</label>
<select id="content_type">
<option value="default">默认行为</option>
<option value="text">纯文本</option>
<option value="html">HTML</option>
</select>
</div>
<div class="form-group">
<label>内容:</label>
<textarea id="content"></textarea>
</div>
<div class="form-group">
<label>流第一个块检查:</label>
<select id="enable_stream_check">
<option value="">保持不变</option>
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
<div class="form-group">
<label>包含停止流:</label>
<select id="include_stop_stream">
<option value="">保持不变</option>
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
<div class="form-group">
<label>图片处理能力:</label>
<select id="vision_ability">
<option value="">保持不变</option>
<option value="disabled">禁用</option>
<option value="base64-only">仅 Base64</option>
<option value="base64-http">Base64 + HTTP</option>
</select>
</div>
<div class="form-group">
<label>慢速池:</label>
<select id="enable_slow_pool">
<option value="">保持不变</option>
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
<div class="form-group">
<label>允许所有Claude模型:</label>
<select id="enable_all_claude">
<option value="">保持不变</option>
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
<div class="form-group">
<label>认证令牌:</label>
<input type="password" id="authToken">
</div>
<div class="button-group">
<button onclick="updateConfig('get')">获取配置</button>
<button onclick="updateConfig('update')">更新配置</button>
<button onclick="updateConfig('reset')" class="secondary">重置配置</button>
</div>
</div>
<div id="result" class="message"></div>
<script>
async function fetchConfig() {
const path = document.getElementById('path').value;
const data = await makeAuthenticatedRequest('/config', {
body: JSON.stringify({ action: 'get', path })
});
if (data) {
let content = '';
// 获取当前路径的页面内容
const pageContent = data.data.page_content;
// 如果是 default 类型,需要从路径获取内容
if (pageContent?.type === 'default') {
// 直接从路径获取内容
const response = await fetch(path);
content = await response.text();
} else if (pageContent?.type === 'text' || pageContent?.type === 'html') {
content = pageContent.content;
}
// 更新表单
document.getElementById('content').value = content || '';
document.getElementById('content_type').value = pageContent?.type || 'default';
let visionValue = data.data.vision_ability || '';
// 标准化 vision_ability 的值
switch (visionValue) {
case 'none':
visionValue = 'disabled';
break;
case 'base64':
visionValue = 'base64-only';
break;
case 'all':
visionValue = 'base64-http';
break;
}
document.getElementById('enable_stream_check').value =
parseStringFromBoolean(data.data.enable_stream_check, '');
document.getElementById('include_stop_stream').value =
parseStringFromBoolean(data.data.include_stop_stream, '');
document.getElementById('vision_ability').value = visionValue;
document.getElementById('enable_slow_pool').value =
parseStringFromBoolean(data.data.enable_slow_pool, '');
document.getElementById('enable_all_claude').value =
parseStringFromBoolean(data.data.enable_all_claude, '');
}
}
async function updateConfig(action) {
if (action === 'get') {
await fetchConfig();
return;
}
const contentType = document.getElementById('content_type').value;
const content = document.getElementById('content').value;
// 根据内容类型构造 content 对象
let contentObj = { type: 'default' };
if (action === 'update' && contentType !== 'default') {
contentObj = {
type: contentType,
content: content
};
}
const data = {
action,
path: document.getElementById('path').value,
...(contentObj && { content: contentObj }),
...(document.getElementById('enable_stream_check').value && {
enable_stream_check: parseBooleanFromString(document.getElementById('enable_stream_check').value)
}),
...(document.getElementById('include_stop_stream').value && {
include_stop_stream: parseBooleanFromString(document.getElementById('include_stop_stream').value)
}),
...(document.getElementById('vision_ability').value && {
vision_ability: document.getElementById('vision_ability').value
}),
...(document.getElementById('enable_slow_pool').value && {
enable_slow_pool: parseBooleanFromString(document.getElementById('enable_slow_pool').value)
}),
...(document.getElementById('enable_all_claude').value && {
enable_all_claude: parseBooleanFromString(document.getElementById('enable_all_claude').value)
})
};
const result = await makeAuthenticatedRequest('/config', {
body: JSON.stringify(data)
});
if (result) {
showMessage('result', result.message, false);
if (action === 'update' || action === 'reset') {
await fetchConfig();
}
}
}
function showSuccess(message) {
showMessage('result', message, false);
}
function showError(message) {
showMessage('result', message, true);
}
// 添加按钮事件监听
document.getElementById('path').addEventListener('change', fetchConfig);
// 更新内容类型变更处理
document.getElementById('content_type').addEventListener('change', function () {
const textarea = document.getElementById('content');
textarea.disabled = this.value === 'default';
});
// 初始化 token 处理
initializeTokenHandling('authToken');
</script>
</body>
</html>

382
static/logs.html Normal file
View File

@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>请求日志查看</title>
<!-- 引入共享样式 -->
<link rel="stylesheet" href="/static/shared-styles.css">
<script src="/static/shared.js"></script>
<style>
/* 日志页面特定样式 */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: var(--spacing);
}
.stat-card {
background: var(--card-background);
padding: 15px;
border-radius: var(--border-radius);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.stat-card h4 {
margin: 0 0 8px 0;
color: var(--primary-color);
}
.stat-value {
font-size: 24px;
font-weight: 500;
}
.refresh-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.auto-refresh {
display: flex;
align-items: center;
gap: 8px;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
overflow-y: auto;
}
.modal-content {
background-color: var(--card-background);
margin: 5% auto;
padding: 20px;
border-radius: var(--border-radius);
width: 90%;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
}
.close {
float: right;
cursor: pointer;
font-size: 28px;
}
.info-button {
background: var(--primary-color);
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
}
.message-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.message-table th,
.message-table td {
border: 1px solid var(--border-color);
padding: 12px;
vertical-align: top;
}
.message-table td {
word-break: break-word;
}
.message-table td:nth-child(2) {
max-width: 600px;
}
.message-table td:first-child {
width: 80px;
white-space: nowrap;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.modal-header h3 {
margin: 0;
}
.close {
font-size: 24px;
font-weight: bold;
cursor: pointer;
padding: 5px 10px;
}
.close:hover {
color: var(--primary-color);
}
.usage-progress-container {
margin-top: 20px;
width: 100%;
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
overflow: hidden;
}
.usage-progress-bar {
height: 100%;
width: 0%;
transition: width 0.3s ease;
background: linear-gradient(to right,
#4caf50 0%,
/* 绿色 */
#8bc34a 25%,
/* 浅绿 */
#ffeb3b 50%,
/* 黄色 */
#ff9800 75%,
/* 橙色 */
#f44336 100%
/* 红色 */
);
}
</style>
</head>
<body>
<h1>请求日志查看</h1>
<div class="container">
<div class="form-group">
<label>认证令牌:</label>
<input type="password" id="authToken" placeholder="输入 AUTH_TOKEN">
</div>
<div class="refresh-container">
<div class="button-group">
<button onclick="fetchLogs()">刷新日志</button>
</div>
<div class="auto-refresh">
<input type="checkbox" id="autoRefresh" checked>
<label for="autoRefresh">自动刷新 (60秒)</label>
</div>
</div>
</div>
<div class="container">
<div class="stats-grid">
<div class="stat-card">
<h4>总请求数</h4>
<div id="totalRequests" class="stat-value">-</div>
</div>
<div class="stat-card">
<h4>活跃请求数</h4>
<div id="activeRequests" class="stat-value">-</div>
</div>
<div class="stat-card">
<h4>最后更新</h4>
<div id="lastUpdate" class="stat-value">-</div>
</div>
</div>
<div class="table-container">
<table id="logsTable">
<thead>
<tr>
<th>时间</th>
<th>模型</th>
<th>Token信息</th>
<th>Prompt</th>
<th>流式响应</th>
<th>状态</th>
<th>错误信息</th>
</tr>
</thead>
<tbody id="logsBody"></tbody>
</table>
</div>
</div>
<div id="message"></div>
<!-- 添加弹窗组件 -->
<div id="tokenModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h3>Token 详细信息</h3>
<table>
<tr>
<td>Token:</td>
<td id="modalToken"></td>
</tr>
<tr>
<td>校验和:</td>
<td id="modalChecksum"></td>
</tr>
<tr>
<td>别名:</td>
<td id="modalAlias"></td>
</tr>
<tr>
<td>使用情况:</td>
<td id="modalUsage"></td>
</tr>
</table>
<!-- 添加进度条容器 -->
<div class="usage-progress-container">
<div id="modalUsageBar" class="usage-progress-bar"></div>
</div>
</div>
</div>
<div id="promptModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>对话内容</h3>
<span class="close">&times;</span>
</div>
<div id="promptContent"></div>
</div>
</div>
<script>
let refreshInterval;
function updateStats(data) {
document.getElementById('totalRequests').textContent = data.total;
document.getElementById('activeRequests').textContent = data.active || 0;
document.getElementById('lastUpdate').textContent =
new Date(data.timestamp).toLocaleTimeString();
}
function showTokenModal(tokenInfo) {
const modal = document.getElementById('tokenModal');
document.getElementById('modalToken').textContent = tokenInfo.token || '-';
document.getElementById('modalChecksum').textContent = tokenInfo.checksum || '-';
document.getElementById('modalAlias').textContent = tokenInfo.alias || '-';
// 获取进度条容器
const progressContainer = document.querySelector('.usage-progress-container');
// 处理使用情况和进度条
if (tokenInfo.usage) {
const current = tokenInfo.usage.fast_requests;
const max = tokenInfo.usage.max_fast_requests;
const percentage = (current / max * 100).toFixed(1);
document.getElementById('modalUsage').textContent =
`${current}/${max} (${percentage}%)`;
// 显示进度条容器
progressContainer.style.display = 'block';
// 更新进度条
const progressBar = document.getElementById('modalUsageBar');
progressBar.style.width = `${percentage}%`;
} else {
document.getElementById('modalUsage').textContent = '-';
// 隐藏进度条容器
progressContainer.style.display = 'none';
}
modal.style.display = 'block';
}
function updateTable(data) {
const tbody = document.getElementById('logsBody');
updateStats(data);
tbody.innerHTML = data.logs.map(log => `
<tr>
<td>${new Date(log.timestamp).toLocaleString()}</td>
<td>${log.model}</td>
<td>
<button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>
查看详情
</button>
</td>
<td>
${log.prompt ?
`<button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">
查看对话
</button>` :
'-'
}
</td>
<td>${log.stream ? '是' : '否'}</td>
<td>${log.status}</td>
<td>${log.error || '-'}</td>
</tr>
`).join('');
}
async function fetchLogs() {
const data = await makeAuthenticatedRequest('/logs');
if (data) {
updateTable(data);
showGlobalMessage('日志获取成功');
}
}
// 自动刷新控制
document.getElementById('autoRefresh').addEventListener('change', function (e) {
if (e.target.checked) {
refreshInterval = setInterval(fetchLogs, 60000);
} else {
clearInterval(refreshInterval);
}
});
// 页面加载完成后自动获取日志
document.addEventListener('DOMContentLoaded', () => {
const authToken = getAuthToken();
if (authToken) {
document.getElementById('authToken').value = authToken;
fetchLogs();
}
// 启动自动刷新
refreshInterval = setInterval(fetchLogs, 60000);
});
// 初始化 token 处理
initializeTokenHandling('authToken');
// 添加清理逻辑
window.addEventListener('beforeunload', () => {
if (refreshInterval) {
clearInterval(refreshInterval);
}
});
// 添加模态框关闭逻辑
document.querySelectorAll('.modal .close').forEach(closeBtn => {
closeBtn.onclick = function () {
this.closest('.modal').style.display = 'none';
}
});
window.onclick = function (event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>

169
static/shared-styles.css Normal file
View File

@@ -0,0 +1,169 @@
:root {
--primary-color: #2196F3;
--primary-dark: #1976D2;
--success-color: #4CAF50;
--error-color: #F44336;
--background-color: #F5F5F5;
--card-background: #FFFFFF;
--border-radius: 8px;
--spacing: 20px;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing);
background: var(--background-color);
color: #333;
line-height: 1.6;
}
.container {
background: var(--card-background);
padding: var(--spacing);
border-radius: var(--border-radius);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: var(--spacing);
}
h1,
h2,
h3 {
color: #1a1a1a;
margin-top: 0;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
input[type="text"],
input[type="password"],
select,
textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s;
}
input[type="text"]:focus,
input[type="password"]:focus,
select:focus,
textarea:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
}
textarea {
min-height: 150px;
font-family: monospace;
resize: vertical;
}
.button-group {
display: flex;
gap: 10px;
margin: var(--spacing) 0;
}
button {
background: var(--primary-color);
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s, transform 0.1s;
}
button:hover {
background: var(--primary-dark);
}
button:active {
transform: translateY(1px);
}
button.secondary {
background: #757575;
}
button.secondary:hover {
background: #616161;
}
.message {
padding: 12px;
border-radius: var(--border-radius);
margin: 10px 0;
}
.success {
background: #E8F5E9;
color: #2E7D32;
border: 1px solid #A5D6A7;
}
.error {
background: #FFEBEE;
color: #C62828;
border: 1px solid #FFCDD2;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: var(--spacing);
background: var(--card-background);
border-radius: var(--border-radius);
overflow: hidden;
}
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background: var(--primary-color);
color: white;
font-weight: 500;
}
tr:nth-child(even) {
background: #f8f9fa;
}
tr:hover {
background: #f1f3f4;
}
/* 响应式设计 */
@media (max-width: 768px) {
body {
padding: 10px;
}
.button-group {
flex-direction: column;
}
table {
display: block;
overflow-x: auto;
}
}

252
static/shared.js Normal file
View File

@@ -0,0 +1,252 @@
// Token 管理功能
function saveAuthToken(token) {
const expiryTime = new Date().getTime() + (24 * 60 * 60 * 1000); // 24小时后过期
localStorage.setItem('authToken', token);
localStorage.setItem('authTokenExpiry', expiryTime);
}
function getAuthToken() {
const token = localStorage.getItem('authToken');
const expiry = localStorage.getItem('authTokenExpiry');
if (!token || !expiry) {
return null;
}
if (new Date().getTime() > parseInt(expiry)) {
localStorage.removeItem('authToken');
localStorage.removeItem('authTokenExpiry');
return null;
}
return token;
}
// 消息显示功能
function showMessage(elementId, text, isError = false) {
const msg = document.getElementById(elementId);
msg.className = `message ${isError ? 'error' : 'success'}`;
msg.textContent = text;
}
function showGlobalMessage(text, isError = false) {
showMessage('message', text, isError);
}
// Token 输入框自动填充和事件绑定
function initializeTokenHandling(inputId) {
document.addEventListener('DOMContentLoaded', () => {
const authToken = getAuthToken();
if (authToken) {
document.getElementById(inputId).value = authToken;
}
});
document.getElementById(inputId).addEventListener('change', (e) => {
if (e.target.value) {
saveAuthToken(e.target.value);
} else {
localStorage.removeItem('authToken');
localStorage.removeItem('authTokenExpiry');
}
});
}
// API 请求通用处理
async function makeAuthenticatedRequest(url, options = {}) {
const tokenId = options.tokenId || 'authToken';
const token = document.getElementById(tokenId).value;
if (!token) {
showGlobalMessage('请输入 AUTH_TOKEN', true);
return null;
}
const defaultOptions = {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
};
try {
const response = await fetch(url, { ...defaultOptions, ...options });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
showGlobalMessage(`请求失败: ${error.message}`, true);
return null;
}
}
/**
* 从字符串解析布尔值
* @param {string} str - 要解析的字符串
* @param {boolean|null} defaultValue - 解析失败时的默认值
* @returns {boolean|null} 解析结果,如果无法解析则返回默认值
*/
function parseBooleanFromString(str, defaultValue = null) {
if (typeof str !== 'string') {
return defaultValue;
}
const lowercaseStr = str.toLowerCase().trim();
if (lowercaseStr === 'true' || lowercaseStr === '1') {
return true;
} else if (lowercaseStr === 'false' || lowercaseStr === '0') {
return false;
} else {
return defaultValue;
}
}
/**
* 将布尔值转换为字符串
* @param {boolean|undefined|null} value - 要转换的布尔值
* @param {string} defaultValue - 转换失败时的默认值
* @returns {string} 转换结果,如果输入无效则返回默认值
*/
function parseStringFromBoolean(value, defaultValue = null) {
if (typeof value !== 'boolean') {
return defaultValue;
}
return value ? 'true' : 'false';
}
/**
* 解析对话内容
* @param {string} promptStr - 原始prompt字符串
* @returns {Array<{role: string, content: string}>} 解析后的对话数组
*/
function parsePrompt(promptStr) {
if (!promptStr) return [];
const messages = [];
const lines = promptStr.split('\n');
let currentRole = '';
let currentContent = '';
const roleMap = {
'BEGIN_SYSTEM': 'system',
'BEGIN_USER': 'user',
'BEGIN_ASSISTANT': 'assistant'
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// 检查是否是角色标记行
let foundRole = false;
for (const [marker, role] of Object.entries(roleMap)) {
if (line.includes(marker)) {
// 保存之前的消息(如果有)
if (currentRole && currentContent.trim()) {
messages.push({
role: currentRole,
content: currentContent.trim()
});
}
// 设置新角色
currentRole = role;
currentContent = '';
foundRole = true;
break;
}
}
// 如果不是角色标记行且不是END标记行则添加到当前内容
if (!foundRole && !line.includes('END_')) {
currentContent += line + '\n';
}
}
// 添加最后一条消息
if (currentRole && currentContent.trim()) {
messages.push({
role: currentRole,
content: currentContent.trim()
});
}
return messages;
}
/**
* 格式化对话内容为HTML表格
* @param {Array<{role: string, content: string}>} messages - 对话消息数组
* @returns {string} HTML表格字符串
*/
function formatPromptToTable(messages) {
if (!messages || messages.length === 0) {
return '<p>无对话内容</p>';
}
const roleLabels = {
'system': '系统',
'user': '用户',
'assistant': '助手'
};
function escapeHtml(content) {
// 先转义HTML特殊字符
const escaped = content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
// 将HTML标签文本用引号包裹使其更易读
// return escaped.replace(/&lt;(\/?[^>]+)&gt;/g, '"<$1>"');
return escaped;
}
return `
<table class="message-table">
<thead>
<tr>
<th>角色</th>
<th>内容</th>
</tr>
</thead>
<tbody>
${messages.map(msg => `
<tr>
<td>${roleLabels[msg.role] || msg.role}</td>
<td>${escapeHtml(msg.content).replace(/\n/g, '<br>')}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
/**
* 安全地显示prompt对话框
* @param {string} promptStr - 原始prompt字符串
*/
function showPromptModal(promptStr) {
try {
const modal = document.getElementById('promptModal');
const content = document.getElementById('promptContent');
if (!modal || !content) {
console.error('Modal elements not found');
return;
}
const messages = parsePrompt(promptStr);
content.innerHTML = formatPromptToTable(messages);
modal.style.display = 'block';
} catch (e) {
console.error('显示prompt对话框失败:', e);
console.error('原始prompt:', promptStr);
}
}

View File

@@ -5,70 +5,36 @@
<meta charset="UTF-8">
<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>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 20px auto;
padding: 0 20px;
.token-container {
display: grid;
gap: var(--spacing);
}
.container {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
.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);
}
.button-group {
display: flex;
gap: 10px;
margin: 10px 0;
}
textarea {
width: 100%;
min-height: 150px;
margin: 10px 0;
font-family: monospace;
resize: vertical;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
.shortcuts {
margin-top: var(--spacing);
padding: 12px;
background: #f8f9fa;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #45a049;
}
button.secondary {
background: #607D8B;
}
button.secondary:hover {
background: #546E7A;
}
.error {
color: red;
margin: 10px 0;
}
.success {
color: green;
margin: 10px 0;
}
#authToken {
width: 100%;
padding: 8px;
margin: 10px 0;
kbd {
background: #eee;
border-radius: 3px;
border: 1px solid #b4b4b4;
padding: 1px 4px;
font-size: 12px;
}
</style>
</head>
@@ -77,97 +43,70 @@
<h1>Token 信息管理</h1>
<div class="container">
<h2>认证</h2>
<input type="password" id="authToken" placeholder="输入 AUTH_TOKEN">
<div class="form-group">
<label>认证令牌:</label>
<input type="password" id="authToken" placeholder="输入 AUTH_TOKEN">
</div>
</div>
<div class="container">
<h2>Token 配置</h2>
<div class="button-group">
<button onclick="getTokenInfo()">获取当前配置</button>
<button onclick="updateTokenInfo()" class="secondary">保存更改</button>
<div class="token-container">
<div class="token-section">
<h3>Token 配置</h3>
<div class="button-group">
<button onclick="getTokenInfo()">获取当前配置</button>
<button onclick="updateTokenInfo()" class="secondary">保存更改</button>
</div>
<div class="form-group">
<label>Token 文件内容:</label>
<textarea id="tokens" placeholder="每行一个 token"></textarea>
</div>
<div class="form-group">
<label>Token List 文件内容:</label>
<textarea id="tokenList" placeholder="token,checksum 格式,每行一对"></textarea>
</div>
<div class="shortcuts">
快捷键: <kbd>Ctrl</kbd> + <kbd>S</kbd> 保存更改
</div>
</div>
<h3>Token 文件内容</h3>
<textarea id="tokens" placeholder="每行一个 token"></textarea>
<h3>Token List 文件内容</h3>
<textarea id="tokenList" placeholder="token,checksum 格式,每行一对"></textarea>
</div>
<div id="message"></div>
<script>
function showMessage(text, isError = false) {
const msg = document.getElementById('message');
msg.className = isError ? 'error' : 'success';
msg.textContent = text;
showGlobalMessage(text, isError);
}
async function getTokenInfo() {
const authToken = document.getElementById('authToken').value;
if (!authToken) {
showMessage('请输入 AUTH_TOKEN', true);
return;
}
try {
const response = await fetch('/get-tokeninfo', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const data = await makeAuthenticatedRequest('/get-tokeninfo');
if (data) {
document.getElementById('tokens').value = data.tokens;
document.getElementById('tokenList').value = data.token_list;
showMessage('配置获取成功');
} catch (error) {
showMessage(`获取失败: ${error.message}`, true);
showGlobalMessage('配置获取成功');
}
}
async function updateTokenInfo() {
const authToken = document.getElementById('authToken').value;
const tokens = document.getElementById('tokens').value;
const tokenList = document.getElementById('tokenList').value;
if (!authToken) {
showMessage('请输入 AUTH_TOKEN', true);
return;
}
if (!tokens) {
showMessage('Token 文件内容不能为空', true);
showGlobalMessage('Token 文件内容不能为空', true);
return;
}
try {
const response = await fetch('/update-tokeninfo', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
tokens: tokens,
token_list: tokenList || undefined
})
});
const data = await makeAuthenticatedRequest('/update-tokeninfo', {
body: JSON.stringify({
tokens: tokens,
token_list: tokenList || undefined
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
showMessage(`更新成功: ${data.message}`);
} catch (error) {
showMessage(`更新失败: ${error.message}`, true);
if (data) {
showGlobalMessage(`更新成功: ${data.message}`);
}
}
@@ -178,6 +117,9 @@
updateTokenInfo();
}
});
// 初始化 token 处理
initializeTokenHandling('authToken');
</script>
</body>