mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-12-24 13:38:01 +08:00
修复了一些问题
This commit is contained in:
@@ -140,7 +140,7 @@
|
||||
<div id="usageProgressContainer" class="progress-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="message" class="message"></div>
|
||||
<div id="message"></div>
|
||||
|
||||
<footer class="footer">
|
||||
<div id="version"></div>
|
||||
@@ -198,11 +198,16 @@
|
||||
|
||||
// 获取模型列表
|
||||
async function getModels() {
|
||||
const modelList = document.getElementById('modelList');
|
||||
const suffix = document.getElementById('customSuffix').checked ?
|
||||
document.getElementById('suffixInput').value : '';
|
||||
try {
|
||||
const modelList = document.getElementById('modelList');
|
||||
const suffix = document.getElementById('customSuffix').checked ?
|
||||
document.getElementById('suffixInput').value : '';
|
||||
|
||||
modelList.value = globalModels.map(model => model + suffix).join(',');
|
||||
modelList.value = globalModels.map(model => model + suffix).join(',');
|
||||
showGlobalMessage('模型列表已更新');
|
||||
} catch (error) {
|
||||
showGlobalMessage('获取模型列表失败', true);
|
||||
}
|
||||
}
|
||||
|
||||
// 复制模型列表
|
||||
@@ -234,7 +239,7 @@
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
throw new Error(`请求失败 (${response.status})`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
@@ -261,9 +266,13 @@
|
||||
calibrationCache.set(token, {
|
||||
user_id: result.user_id,
|
||||
create_at: result.create_at,
|
||||
checksum_time: calibResult.checksum_time
|
||||
checksum_time: result.checksum_time
|
||||
});
|
||||
updateUsageDisplay(null, calibrationCache.get(token));
|
||||
|
||||
// 显示基本校准信息
|
||||
const container = document.getElementById('userInfoContainer');
|
||||
container.style.display = 'block';
|
||||
updateUsageDisplay({ user: null }, calibrationCache.get(token));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,15 +284,13 @@
|
||||
showGlobalMessage('请输入 Token', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有校准缓存,先进行校准
|
||||
if (!calibrationCache.has(token)) {
|
||||
const calibResult = await makeTokenRequest('/basic-calibration', token);
|
||||
if (calibResult && calibResult.status !== 'error') {
|
||||
calibrationCache.set(token, {
|
||||
user_id: calibResult.user_id,
|
||||
create_at: calibResult.create_at,
|
||||
checksum_time: calibResult.checksum_time
|
||||
});
|
||||
showGlobalMessage('正在进行 Token 校准...');
|
||||
await calibrateToken();
|
||||
if (!calibrationCache.has(token)) {
|
||||
return; // 如果校准失败,直接返回
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +299,7 @@
|
||||
const container = document.getElementById('userInfoContainer');
|
||||
container.style.display = 'block';
|
||||
updateUsageDisplay(result, calibrationCache.get(token));
|
||||
showGlobalMessage('用户信息获取成功');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,11 +358,16 @@
|
||||
});
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
startStatusCheck();
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
await startStatusCheck();
|
||||
showGlobalMessage('系统初始化完成');
|
||||
|
||||
// 监听后缀输入变化
|
||||
document.getElementById('suffixInput').addEventListener('input', getModels);
|
||||
// 监听后缀输入变化
|
||||
document.getElementById('suffixInput').addEventListener('input', getModels);
|
||||
} catch (error) {
|
||||
showGlobalMessage('系统初始化失败', true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
253
static/build_key.html
Normal file
253
static/build_key.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<!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>Key 构建</title>
|
||||
<!-- 引入共享样式 -->
|
||||
<link rel="stylesheet" href="/static/shared-styles.css">
|
||||
<script src="/static/shared.js"></script>
|
||||
<style>
|
||||
.key-result {
|
||||
word-break: break-all;
|
||||
background: var(--card-background);
|
||||
padding: var(--spacing);
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-top: var(--spacing);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
padding: 4px 8px;
|
||||
font-size: 14px;
|
||||
background: var(--primary-color-alpha);
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--primary-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Key 构建</h1>
|
||||
|
||||
<div class="container">
|
||||
<div class="form-group">
|
||||
<label>服务认证令牌:</label>
|
||||
<input type="password" id="authToken" placeholder="输入服务认证令牌">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>数据认证令牌:</label>
|
||||
<input type="password" id="dataToken" placeholder="输入数据认证令牌">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>流第一个块检查:</label>
|
||||
<select id="enableStreamCheck">
|
||||
<option value="">跟随全局</option>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>包含停止流:</label>
|
||||
<select id="includeStopStream">
|
||||
<option value="">跟随全局</option>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
</select>
|
||||
</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="button-group">
|
||||
<button onclick="buildKey()">构建 Key</button>
|
||||
<button onclick="clearForm()" class="secondary">清空表单</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="keyResult" class="key-result" style="display: none;">
|
||||
<button class="copy-button" onclick="copyKey()">复制</button>
|
||||
<div id="keyContent"></div>
|
||||
</div>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<script>
|
||||
let availableModels = [];
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
async function buildKey() {
|
||||
const authToken = document.getElementById('authToken').value;
|
||||
const dataToken = document.getElementById('dataToken').value;
|
||||
|
||||
if (!authToken) {
|
||||
showGlobalMessage('请输入服务认证令牌', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dataToken) {
|
||||
showGlobalMessage('请输入数据认证令牌', true);
|
||||
return;
|
||||
}
|
||||
|
||||
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 data = {
|
||||
auth_token: dataToken,
|
||||
enable_stream_check: parseBooleanFromString(document.getElementById('enableStreamCheck').value, undefined),
|
||||
include_stop_stream: parseBooleanFromString(document.getElementById('includeStopStream').value, undefined),
|
||||
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
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeAuthenticatedRequest('/build-key', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response) {
|
||||
const keyResult = document.getElementById('keyResult');
|
||||
const keyContent = document.getElementById('keyContent');
|
||||
keyContent.textContent = response.key || response.error;
|
||||
keyResult.style.display = 'block';
|
||||
showGlobalMessage(response.key ? 'Key 构建成功' : '构建失败: ' + response.error, !response.key);
|
||||
}
|
||||
} catch (error) {
|
||||
showGlobalMessage('请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function copyKey() {
|
||||
const keyContent = document.getElementById('keyContent').textContent;
|
||||
navigator.clipboard.writeText(keyContent).then(() => {
|
||||
showGlobalMessage('Key 已复制到剪贴板');
|
||||
}).catch(() => {
|
||||
showGlobalMessage('复制失败', true);
|
||||
});
|
||||
}
|
||||
|
||||
function clearForm() {
|
||||
document.getElementById('authToken').value = '';
|
||||
document.getElementById('dataToken').value = '';
|
||||
document.getElementById('enableStreamCheck').value = '';
|
||||
document.getElementById('includeStopStream').value = '';
|
||||
document.getElementById('disableVision').value = '';
|
||||
document.getElementById('enableSlowPool').value = '';
|
||||
document.getElementById('usageCheckType').value = 'default';
|
||||
document.getElementById('modelListContainer').style.display = 'none';
|
||||
document.getElementById('keyResult').style.display = 'none';
|
||||
showGlobalMessage('表单已清空');
|
||||
}
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
getModels();
|
||||
const authToken = getAuthToken();
|
||||
if (authToken) {
|
||||
document.getElementById('authToken').value = authToken;
|
||||
fetchLogs();
|
||||
}
|
||||
});
|
||||
initializeTokenHandling('authToken');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -21,12 +21,13 @@
|
||||
<option value="/">根路径 (/)</option>
|
||||
<option value="/logs">日志页面 (/logs)</option>
|
||||
<option value="/config">配置页面 (/config)</option>
|
||||
<option value="/tokeninfo">Token 信息页面 (/tokeninfo)</option>
|
||||
<option value="/tokens">Token 管理页面 (/tokens)</option>
|
||||
<option value="/static/shared-styles.css">共享样式 (/static/shared-styles.css)</option>
|
||||
<option value="/static/shared.js">共享脚本 (/static/shared.js)</option>
|
||||
<option value="/about">关于页面 (/about)</option>
|
||||
<option value="/readme">ReadMe文档 (/readme)</option>
|
||||
<option value="/api">api调用 (/api)</option>
|
||||
<option value="/build-key">构建动态 Key (/build-key)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -92,14 +93,40 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label>使用量检查模型规则:</label>
|
||||
<select id="check_usage_models_type">
|
||||
<select id="usage_check_models_type">
|
||||
<option value="">保持不变</option>
|
||||
<option value="none">禁用</option>
|
||||
<option value="default">默认</option>
|
||||
<option value="all">所有</option>
|
||||
<option value="list">自定义列表</option>
|
||||
</select>
|
||||
<input type="text" id="check_usage_models_list" placeholder="模型列表,以逗号分隔" style="display: none;">
|
||||
<input type="text" id="usage_check_models_list" placeholder="模型列表,以逗号分隔" style="display: none;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>是否允许动态配置Key:</label>
|
||||
<select id="enable_dynamic_key">
|
||||
<option value="">保持不变</option>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>代理设置:</label>
|
||||
<select id="proxies_type" onchange="handleProxiesTypeChange()">
|
||||
<option value="">保持不变</option>
|
||||
<option value="no">不使用代理</option>
|
||||
<option value="system">使用系统代理</option>
|
||||
<option value="list">自定义代理列表</option>
|
||||
</select>
|
||||
<input type="text" id="proxies_list" placeholder="代理地址列表,以逗号分隔 (例如: http://127.0.0.1:7890)"
|
||||
style="display: none;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>共享令牌(空表示禁用):</label>
|
||||
<input type="text" id="shareToken">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -114,127 +141,175 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="result" class="message"></div>
|
||||
<div id="message"></div>
|
||||
|
||||
<script>
|
||||
async function fetchConfig() {
|
||||
const path = document.getElementById('path').value;
|
||||
const data = await makeAuthenticatedRequest('/config', {
|
||||
body: JSON.stringify({ action: 'get', path })
|
||||
});
|
||||
try {
|
||||
const path = document.getElementById('path').value;
|
||||
const data = await makeAuthenticatedRequest('/config', {
|
||||
body: JSON.stringify({ action: 'get', path })
|
||||
});
|
||||
|
||||
if (data) {
|
||||
let content = '';
|
||||
if (data) {
|
||||
let content = '';
|
||||
|
||||
// 获取当前路径的页面内容
|
||||
const pageContent = data.data.page_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;
|
||||
// 如果是 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, '');
|
||||
document.getElementById('usage_check_models_type').value = data.data.usage_check_models?.type || '';
|
||||
document.getElementById('usage_check_models_list').value = data.data.usage_check_models?.type === 'list' ? data.data.usage_check_models?.content || '' : document.getElementById('usage_check_models_list').value;
|
||||
document.getElementById('enable_dynamic_key').value =
|
||||
parseStringFromBoolean(data.data.enable_dynamic_key, '');
|
||||
|
||||
// 处理代理设置
|
||||
const proxies = data.data.proxies || '';
|
||||
let proxiesType = '';
|
||||
let proxiesList = '';
|
||||
|
||||
if (proxies === '') {
|
||||
proxiesType = 'no';
|
||||
} else if (proxies === 'system') {
|
||||
proxiesType = 'system';
|
||||
} else {
|
||||
proxiesType = 'list';
|
||||
proxiesList = proxies;
|
||||
}
|
||||
|
||||
document.getElementById('proxies_type').value = proxiesType;
|
||||
document.getElementById('proxies_list').value = proxiesList;
|
||||
handleProxiesTypeChange();
|
||||
|
||||
document.getElementById('shareToken').value = data.data.share_token || '';
|
||||
|
||||
// 添加获取配置成功提示
|
||||
showGlobalMessage(`成功获取 ${path} 的配置`, false);
|
||||
}
|
||||
|
||||
// 更新表单
|
||||
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, '');
|
||||
document.getElementById('check_usage_models_type').value = data.data.check_usage_models?.type || '';
|
||||
document.getElementById('check_usage_models_list').value = data.data.check_usage_models?.type === 'list' ? data.data.check_usage_models?.content || '' : document.getElementById('check_usage_models_list').value;
|
||||
} catch (error) {
|
||||
showGlobalMessage(error.message || '获取配置失败', true);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}),
|
||||
...(document.getElementById('check_usage_models_type').value && {
|
||||
check_usage_models: {
|
||||
type: document.getElementById('check_usage_models_type').value,
|
||||
...(document.getElementById('check_usage_models_type').value === 'list' && {
|
||||
content: document.getElementById('check_usage_models_list').value
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const result = await makeAuthenticatedRequest('/config', {
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (result) {
|
||||
showMessage('result', result.message, false);
|
||||
if (action === 'update' || action === 'reset') {
|
||||
try {
|
||||
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 shareToken = document.getElementById('shareToken').value.trim();
|
||||
|
||||
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)
|
||||
}),
|
||||
...(document.getElementById('usage_check_models_type').value && {
|
||||
usage_check_models: {
|
||||
type: document.getElementById('usage_check_models_type').value,
|
||||
...(document.getElementById('usage_check_models_type').value === 'list' && {
|
||||
content: document.getElementById('usage_check_models_list').value
|
||||
})
|
||||
}
|
||||
}),
|
||||
...(document.getElementById('enable_dynamic_key').value && {
|
||||
enable_dynamic_key: parseBooleanFromString(document.getElementById('enable_dynamic_key').value)
|
||||
}),
|
||||
...(document.getElementById('proxies_type').value && {
|
||||
proxies: (() => {
|
||||
const type = document.getElementById('proxies_type').value;
|
||||
switch (type) {
|
||||
case 'no':
|
||||
return '';
|
||||
case 'system':
|
||||
return 'system';
|
||||
case 'list':
|
||||
return document.getElementById('proxies_list').value;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
})()
|
||||
}),
|
||||
...(shareToken && {
|
||||
share_token: shareToken
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await makeAuthenticatedRequest('/config', {
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (result) {
|
||||
showGlobalMessage(result.message, false);
|
||||
if (action === 'update' || action === 'reset') {
|
||||
await fetchConfig();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showGlobalMessage(error.message || '操作失败', true);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
showMessage('result', message, false);
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
showMessage('result', message, true);
|
||||
}
|
||||
|
||||
// 添加按钮事件监听
|
||||
document.getElementById('path').addEventListener('change', fetchConfig);
|
||||
|
||||
@@ -248,10 +323,27 @@
|
||||
initializeTokenHandling('authToken');
|
||||
|
||||
// 添加使用量检查模型类型变更处理
|
||||
document.getElementById('check_usage_models_type').addEventListener('change', function() {
|
||||
const input = document.getElementById('check_usage_models_list');
|
||||
document.getElementById('usage_check_models_type').addEventListener('change', function () {
|
||||
const input = document.getElementById('usage_check_models_list');
|
||||
input.style.display = this.value === 'list' ? 'inline-block' : 'none';
|
||||
});
|
||||
|
||||
// 添加代理类型变更处理函数
|
||||
function handleProxiesTypeChange() {
|
||||
const type = document.getElementById('proxies_type').value;
|
||||
const list = document.getElementById('proxies_list');
|
||||
list.style.display = type === 'list' ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
// 页面加载完成后自动获取配置
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
await fetchConfig();
|
||||
showGlobalMessage('页面加载完成', false);
|
||||
} catch (error) {
|
||||
showGlobalMessage('初始化配置加载失败', true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -354,6 +354,28 @@
|
||||
#logsTable tr:hover td {
|
||||
background-color: var(--hover-color, rgba(0, 0, 0, 0.02));
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.danger-button {
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--error-color-alpha);
|
||||
color: var(--error-color);
|
||||
border: 1px solid var(--error-color);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.danger-button:hover {
|
||||
background: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -423,7 +445,10 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Token 详细信息</h3>
|
||||
<span class="close">×</span>
|
||||
<div class="modal-actions">
|
||||
<button class="danger-button" id="deleteTokenBtn">删除此Token</button>
|
||||
<span class="close">×</span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="message-table">
|
||||
<tr>
|
||||
@@ -543,6 +568,42 @@
|
||||
|
||||
function showTokenModal(tokenInfo) {
|
||||
const modal = document.getElementById('tokenModal');
|
||||
const deleteBtn = document.getElementById('deleteTokenBtn');
|
||||
|
||||
// 存储当前token用于删除操作
|
||||
const currentToken = tokenInfo.token;
|
||||
|
||||
// 更新删除按钮点击事件
|
||||
deleteBtn.onclick = async () => {
|
||||
if (!currentToken) {
|
||||
showGlobalMessage('无效的Token', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('确定要删除此Token吗?此操作不可撤销。')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await makeAuthenticatedRequest('/tokens/delete', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tokens: [currentToken],
|
||||
expectation: 'failed_tokens'
|
||||
})
|
||||
});
|
||||
|
||||
if (data) {
|
||||
modal.style.display = 'none';
|
||||
let message = 'Token删除成功';
|
||||
if (data.failed_tokens?.length) {
|
||||
message = 'Token删除失败:未找到该Token';
|
||||
}
|
||||
showGlobalMessage(message);
|
||||
// 刷新日志列表
|
||||
fetchLogs();
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('modalToken').textContent = tokenInfo.token || '-';
|
||||
document.getElementById('modalChecksum').textContent = tokenInfo.checksum || '-';
|
||||
|
||||
@@ -634,7 +695,7 @@
|
||||
const tbody = document.getElementById('logsBody');
|
||||
updateStats(data);
|
||||
|
||||
tbody.innerHTML = data.logs.map(log => `<tr><td>${log.id}</td><td>${new Date(log.timestamp).toLocaleString()}</td><td>${log.model}</td><td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td><td>${log.prompt ?`<div class="token-info-tooltip prompt-preview"><button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">查看对话<div class="tooltip-content">${formatPromptPreview(log.prompt)}</div></button></div>` :'-'}</td><td>${formatTiming(log.timing.total, log.timing.first)}</td><td>${log.stream ? '是' : '否'}</td><td>${log.status}</td><td>${log.error || '-'}</td></tr>`).join('');
|
||||
tbody.innerHTML = data.logs.map(log => `<tr><td>${log.id}</td><td>${new Date(log.timestamp).toLocaleString()}</td><td>${log.model}</td><td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td><td>${log.prompt ? `<div class="token-info-tooltip prompt-preview"><button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">查看对话<div class="tooltip-content">${formatPromptPreview(log.prompt)}</div></button></div>` : '-'}</td><td>${formatTiming(log.timing.total, log.timing.first)}</td><td>${log.stream ? '是' : '否'}</td><td>${log.status}</td><td>${log.error || '-'}</td></tr>`).join('');
|
||||
}
|
||||
|
||||
function formatTiming(total, first) {
|
||||
|
||||
@@ -1,651 +0,0 @@
|
||||
<h1>cursor-api</h1>
|
||||
|
||||
<h2>说明</h2>
|
||||
|
||||
<ul>
|
||||
<li>当前版本已稳定,若发现响应出现缺字漏字,与本程序无关。</li>
|
||||
<li>若发现首字慢,与本程序无关。</li>
|
||||
<li>若发现响应出现乱码,也与本程序无关。</li>
|
||||
<li>属于官方的问题,请不要像作者反馈。</li>
|
||||
<li>本程序拥有堪比客户端原本的速度,甚至可能更快。</li>
|
||||
<li>本程序的性能是非常厉害的。</li>
|
||||
<li>根据本项目开源协议,Fork的项目不能以作者的名义进行任何形式的宣传、推广或声明。</li>
|
||||
</ul>
|
||||
|
||||
<h2>获取key</h2>
|
||||
|
||||
<ol>
|
||||
<li>访问 <a href="https://www.cursor.com">www.cursor.com</a> 并完成注册登录</li>
|
||||
<li>在浏览器中打开开发者工具(F12)</li>
|
||||
<li>在 Application-Cookies 中查找名为 <code>WorkosCursorSessionToken</code> 的条目,并复制其第三个字段。请注意,%3A%3A 是 :: 的 URL 编码形式,cookie
|
||||
的值使用冒号 (:) 进行分隔。</li>
|
||||
</ol>
|
||||
|
||||
<h2>配置说明</h2>
|
||||
|
||||
<h3>环境变量</h3>
|
||||
|
||||
<ul>
|
||||
<li><code>PORT</code>: 服务器端口号(默认:3000)</li>
|
||||
<li><code>AUTH_TOKEN</code>: 认证令牌(必须,用于API认证)</li>
|
||||
<li><code>ROUTE_PREFIX</code>: 路由前缀(可选)</li>
|
||||
<li><code>TOKEN_FILE</code>: token文件路径(默认:.token)</li>
|
||||
<li><code>TOKEN_LIST_FILE</code>: token列表文件路径(默认:.token-list)</li>
|
||||
</ul>
|
||||
|
||||
<p>更多请查看 <code>/env-example</code></p>
|
||||
|
||||
<h3>Token文件格式</h3>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<p><code>.token</code> 文件:每行一个token,支持以下格式:</p>
|
||||
|
||||
<pre><code># 这是注释
|
||||
token1
|
||||
# alias与标签的作用差不多
|
||||
alias::token2
|
||||
</code></pre>
|
||||
|
||||
<p>alias 可以是任意值,用于区分不同的 token,更方便管理,WorkosCursorSessionToken 是相同格式<br>
|
||||
该文件将自动向.token-list文件中追加token,同时自动生成checksum</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p><code>.token-list</code> 文件:每行为token和checksum的对应关系:</p>
|
||||
|
||||
<pre><code># 这里的#表示这行在下次读取要删除
|
||||
token1,checksum1
|
||||
# alias被舍弃,会自动删除最后一个:或%3A的后一位前的所有内容
|
||||
token2,checksum2
|
||||
</code></pre>
|
||||
|
||||
<p>该文件可以被自动管理,但用户仅可在确认自己拥有修改能力时修改,一般仅有以下情况需要手动修改:</p>
|
||||
|
||||
<ul>
|
||||
<li>需要删除某个 token</li>
|
||||
<li>需要使用已有 checksum 来对应某一个 token</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>模型列表</h3>
|
||||
|
||||
<p>写死了,后续也不会会支持自定义模型列表</p>
|
||||
|
||||
<pre><code>claude-3.5-sonnet
|
||||
gpt-4
|
||||
gpt-4o
|
||||
claude-3-opus
|
||||
cursor-fast
|
||||
cursor-small
|
||||
gpt-3.5-turbo
|
||||
gpt-4-turbo-2024-04-09
|
||||
gpt-4o-128k
|
||||
gemini-1.5-flash-500k
|
||||
claude-3-haiku-200k
|
||||
claude-3-5-sonnet-200k
|
||||
claude-3-5-sonnet-20241022
|
||||
gpt-4o-mini
|
||||
o1-mini
|
||||
o1-preview
|
||||
o1
|
||||
claude-3.5-haiku
|
||||
gemini-exp-1206
|
||||
gemini-2.0-flash-thinking-exp
|
||||
gemini-2.0-flash-exp
|
||||
</code></pre>
|
||||
|
||||
<h1>接口说明</h1>
|
||||
|
||||
<h2>基础对话</h2>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/v1/chat/completions</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: Bearer Token
|
||||
<ol>
|
||||
<li>使用环境变量 <code>AUTH_TOKEN</code> 进行认证</li>
|
||||
<li>使用 <code>.token</code> 文件中的令牌列表进行轮询认证</li>
|
||||
<li>在v0.1.3-rc.3支持直接使用 token,checksum 进行认证,但未提供配置关闭</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>请求格式</h3>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"model": "string",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system" | "user" | "assistant", // 也可以是 "developer" | "human" | "ai"
|
||||
"content": "string" | [
|
||||
{
|
||||
"type": "text" | "image_url",
|
||||
"text": "string",
|
||||
"image_url": {
|
||||
"url": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stream": boolean
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>响应格式</h3>
|
||||
|
||||
<p>如果 <code>stream</code> 为 <code>false</code>:</p>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"id": "string",
|
||||
"object": "chat.completion",
|
||||
"created": number,
|
||||
"model": "string",
|
||||
"choices": [
|
||||
{
|
||||
"index": number,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "string"
|
||||
},
|
||||
"finish_reason": "stop" | "length"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"total_tokens": 0
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>不进行 tokens 计算主要是担心性能问题。</p>
|
||||
|
||||
<p>如果 <code>stream</code> 为 <code>true</code>:</p>
|
||||
|
||||
<pre><code>data: {"id":"string","object":"chat.completion.chunk","created":number,"model":"string","choices":[{"index":number,"delta":{"role":"assistant","content":"string"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"string","object":"chat.completion.chunk","created":number,"model":"string","choices":[{"index":number,"delta":{"content":"string"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"string","object":"chat.completion.chunk","created":number,"model":"string","choices":[{"index":number,"delta":{},"finish_reason":"stop"}]}
|
||||
|
||||
data: [DONE]
|
||||
</code></pre>
|
||||
|
||||
<h2>Token管理接口</h2>
|
||||
|
||||
<h3>简易Token信息管理页面</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/tokeninfo</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式: HTML页面</li>
|
||||
<li>功能: 获取 .token 和 .token-list 文件内容,并允许用户方便地使用 API 修改文件内容</li>
|
||||
</ul>
|
||||
|
||||
<h3>更新Token信息 (GET)</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/update-tokeninfo</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>认证方式: 不需要</li>
|
||||
<li>功能: 重新加载tokens并更新应用状态</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"status": "success",
|
||||
"message": "Token list has been reloaded"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>更新Token信息 (POST)</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/update-tokeninfo</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: Bearer Token</li>
|
||||
<li>请求格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"tokens": "string",
|
||||
"token_list": "string"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<ul>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"status": "success",
|
||||
"token_file": "string",
|
||||
"token_list_file": "string",
|
||||
"tokens_count": number,
|
||||
"message": "Token files have been updated and reloaded"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>获取Token信息</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/get-tokeninfo</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: Bearer Token</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"status": "success",
|
||||
"token_file": "string",
|
||||
"token_list_file": "string",
|
||||
"tokens": "string",
|
||||
"tokens_count": number,
|
||||
"token_list": "string"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>配置管理接口</h2>
|
||||
|
||||
<h3>配置页面</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/config</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式: HTML页面</li>
|
||||
<li>功能: 提供配置管理界面,可以修改页面内容和系统配置</li>
|
||||
</ul>
|
||||
|
||||
<h3>更新配置</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/config</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: Bearer Token</li>
|
||||
<li>请求格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"action": "get" | "update" | "reset",
|
||||
"path": "string",
|
||||
"content": {
|
||||
"type": "default" | "text" | "html",
|
||||
"content": "string"
|
||||
},
|
||||
"enable_stream_check": boolean,
|
||||
"include_stop_stream": boolean,
|
||||
"vision_ability": "none" | "base64" | "all", // "disabled" | "base64-only" | "base64-http"
|
||||
"enable_slow_pool": boolean,
|
||||
"enable_all_claude": boolean,
|
||||
"check_usage_models": {
|
||||
"type": "none" | "default" | "all" | "list",
|
||||
"content": "string"
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<ul>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"status": "success",
|
||||
"message": "string",
|
||||
"data": {
|
||||
"page_content": {
|
||||
"type": "default" | "text" | "html", // 对于js和css后两者是一样的
|
||||
"content": "string"
|
||||
},
|
||||
"enable_stream_check": boolean,
|
||||
"include_stop_stream": boolean,
|
||||
"vision_ability": "none" | "base64" | "all",
|
||||
"enable_slow_pool": boolean,
|
||||
"enable_all_claude": boolean,
|
||||
"check_usage_models": {
|
||||
"type": "none" | "default" | "all" | "list",
|
||||
"content": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>注意:<code>check_usage_models</code> 字段的默认值为:</p>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"type": "default",
|
||||
"content": "claude-3-5-sonnet-20241022,claude-3.5-sonnet,gemini-exp-1206,gpt-4,gpt-4-turbo-2024-04-09,gpt-4o,claude-3.5-haiku,gpt-4o-128k,gemini-1.5-flash-500k,claude-3-haiku-200k,claude-3-5-sonnet-200k"
|
||||
}</code></pre>
|
||||
|
||||
<p>这些模型将默认进行使用量检查。您可以通过配置接口修改此设置。</p>
|
||||
|
||||
<p>路径修改注意:选择类型再修改文本,否则选择默认时内容的修改无效,在更新配置后自动被覆盖导致内容丢失,自行改进。</p>
|
||||
|
||||
<h2>静态资源接口</h2>
|
||||
|
||||
<h3>获取共享样式</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/static/shared-styles.css</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式: CSS文件</li>
|
||||
<li>功能: 获取共享样式表</li>
|
||||
</ul>
|
||||
|
||||
<h3>获取共享脚本</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/static/shared.js</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式: JavaScript文件</li>
|
||||
<li>功能: 获取共享JavaScript代码</li>
|
||||
</ul>
|
||||
|
||||
<h3>环境变量示例</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/env-example</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式: 文本文件</li>
|
||||
<li>功能: 获取环境变量配置示例</li>
|
||||
</ul>
|
||||
|
||||
<h2>其他接口</h2>
|
||||
|
||||
<h3>获取模型列表</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/v1/models</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "string",
|
||||
"object": "model",
|
||||
"created": number,
|
||||
"owned_by": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>获取一个随机hash</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/get-hash</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-plaintext">string</code></pre>
|
||||
|
||||
<h3>获取或修复checksum</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/get-checksum</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>请求参数:
|
||||
<ul>
|
||||
<li><code>checksum</code>: 可选,用于修复的旧版本生成的checksum,也可只传入前8个字符;可用来自动刷新时间戳头</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-plaintext">string</code></pre>
|
||||
|
||||
<p>说明:</p>
|
||||
<ul>
|
||||
<li>如果不提供<code>checksum</code>参数,将生成一个新的随机checksum</li>
|
||||
<li>如果提供<code>checksum</code>参数,将尝试修复旧版本的checksum以适配v0.1.3-rc.3之后的版本使用,修复失败会返回新的checksum;若输入的checksum本来就有效,则返回更新tsheader后的checksum</li>
|
||||
</ul>
|
||||
|
||||
<h3>获取当前的tsheader</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/get-tsheader</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-plaintext">string</code></pre>
|
||||
|
||||
<h3>健康检查接口</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/health</code> 或 <code>/</code>(重定向)</li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>认证方式: Bearer Token(可选)</li>
|
||||
<li>响应格式: 根据配置返回不同的内容类型(默认、文本或HTML),默认JSON</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"status": "success",
|
||||
"version": "string",
|
||||
"uptime": number,
|
||||
"stats": {
|
||||
"started": "string",
|
||||
"total_requests": number,
|
||||
"active_requests": number,
|
||||
"system": {
|
||||
"memory": {
|
||||
"rss": number
|
||||
},
|
||||
"cpu": {
|
||||
"usage": number
|
||||
}
|
||||
}
|
||||
},
|
||||
"models": ["string"],
|
||||
"endpoints": ["string"]
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>注意:<code>stats</code> 字段仅在请求头中包含正确的 <code>AUTH_TOKEN</code> 时才会返回。否则,该字段将被省略。</p>
|
||||
|
||||
<h3>获取日志接口</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/logs</code></li>
|
||||
<li>请求方法: GET</li>
|
||||
<li>响应格式: 根据配置返回不同的内容类型(默认、文本或HTML)</li>
|
||||
</ul>
|
||||
|
||||
<h3>获取日志数据</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/logs</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: Bearer Token</li>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"total": number,
|
||||
"logs": [
|
||||
{
|
||||
"id": number,
|
||||
"timestamp": "string",
|
||||
"model": "string",
|
||||
"token_info": {
|
||||
"token": "string",
|
||||
"checksum": "string",
|
||||
"profile": {
|
||||
"usage": {
|
||||
"premium": {
|
||||
"requests": number,
|
||||
"requests_total": number,
|
||||
"tokens": number,
|
||||
"max_requests": number,
|
||||
"max_tokens": number
|
||||
},
|
||||
"standard": {
|
||||
"requests": number,
|
||||
"requests_total": number,
|
||||
"tokens": number,
|
||||
"max_requests": number,
|
||||
"max_tokens": number
|
||||
},
|
||||
"unknown": {
|
||||
"requests": number,
|
||||
"requests_total": number,
|
||||
"tokens": number,
|
||||
"max_requests": number,
|
||||
"max_tokens": number
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"id": "string",
|
||||
"updated_at": "string"
|
||||
},
|
||||
"stripe": {
|
||||
"membership_type": "free" | "free_trial" | "pro" | "enterprise",
|
||||
"payment_id": "string",
|
||||
"days_remaining_on_trial": number
|
||||
}
|
||||
}
|
||||
},
|
||||
"prompt": "string",
|
||||
"timing": {
|
||||
"total": number,
|
||||
"first": number
|
||||
},
|
||||
"stream": boolean,
|
||||
"status": "string",
|
||||
"error": "string"
|
||||
}
|
||||
],
|
||||
"timestamp": "string",
|
||||
"status": "success"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>获取用户信息</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/userinfo</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: 请求体中包含token</li>
|
||||
<li>请求格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"token": "string"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<ul>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"usage": {
|
||||
"premium": {
|
||||
"requests": number,
|
||||
"requests_total": number,
|
||||
"tokens": number,
|
||||
"max_requests": number,
|
||||
"max_tokens": number
|
||||
},
|
||||
"standard": {
|
||||
"requests": number,
|
||||
"requests_total": number,
|
||||
"tokens": number,
|
||||
"max_requests": number,
|
||||
"max_tokens": number
|
||||
},
|
||||
"unknown": {
|
||||
"requests": number,
|
||||
"requests_total": number,
|
||||
"tokens": number,
|
||||
"max_requests": number,
|
||||
"max_tokens": number
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"id": "string",
|
||||
"updated_at": "string"
|
||||
},
|
||||
"stripe": {
|
||||
"membership_type": "free" | "free_trial" | "pro" | "enterprise",
|
||||
"payment_id": "string",
|
||||
"days_remaining_on_trial": number
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>如果发生错误,响应格式为:</p>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"error": "string"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>基础校准</h3>
|
||||
|
||||
<ul>
|
||||
<li>接口地址: <code>/basic-calibration</code></li>
|
||||
<li>请求方法: POST</li>
|
||||
<li>认证方式: 请求体中包含token</li>
|
||||
<li>请求格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"token": "string"
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<ul>
|
||||
<li>响应格式:</li>
|
||||
</ul>
|
||||
|
||||
<pre><code class="language-json">{
|
||||
"status": "success" | "error",
|
||||
"message": "string",
|
||||
"user_id": "string",
|
||||
"create_at": "string",
|
||||
"checksum_time": number
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>注意: <code>user_id</code>, <code>create_at</code>, 和 <code>checksum_time</code> 字段在校验失败时可能不存在。</p>
|
||||
|
||||
<h2>偷偷写在最后的话</h2>
|
||||
|
||||
<p>虽然作者觉得<del>骗</del>收点钱合理,但不强求,要是<strong>主动自愿</strong>发我我肯定收(因为真有人这么做,虽然不是赞助),赞助很合理吧</p>
|
||||
|
||||
<p>不是<strong>主动自愿</strong>就算了,不是很缺,给了会很感动罢了。</p>
|
||||
|
||||
<p>虽然不是很建议你赞助,但如果你赞助了,大概可以:</p>
|
||||
|
||||
<ul>
|
||||
<li>测试版更新</li>
|
||||
<li>要求功能</li>
|
||||
<li>问题更快解决</li>
|
||||
</ul>
|
||||
|
||||
<p>即使如此,我也保留可以拒绝赞助和拒绝要求的权利。</p>
|
||||
|
||||
<p>求赞助还是有点不要脸了,接下来是吐槽:</p>
|
||||
|
||||
<p>辛辛苦苦做这个也不知道是为了谁,好累。其实还有很多功能可以做,比如直接传token支持配置(其实这个要专门做一个页面),这个作为rc.4的计划之一吧。</p>
|
||||
|
||||
<p>主要没想做用户管理,所以不存在是否接入LinuxDo的问题。虽然那个半成品公益版做好了就是了。</p>
|
||||
|
||||
<p>就说这么多,没啥可说的,不管那么多,做就完了。<span>[doge]</span> 自己想象吧。</p>
|
||||
|
||||
<p>为什么一直说要跑路呢?主要是有时Cursor的Claude太假了,堪比gpt-4o-mini,我对比发现真没啥差别,比以前差远了,无力了,所以不太想做了。我也感觉很奇怪。</p>
|
||||
|
||||
<p>查询额度会在一开始检测导致和完成时的额度有些差别,但是懒得改了,反正差别不大,对话也没响应内容,恰好完成了统一。</p>
|
||||
|
||||
<p>有人说少个二维码来着,还是算了。如果觉得好用,给点支持。其实没啥大不了的,没兴趣就不做了。不想那么多了。</p>
|
||||
@@ -117,7 +117,7 @@ input[type="checkbox"] {
|
||||
appearance: auto;
|
||||
}
|
||||
|
||||
input[type="checkbox"] + label {
|
||||
input[type="checkbox"]+label {
|
||||
cursor: pointer;
|
||||
color: var(--text-primary);
|
||||
user-select: none;
|
||||
@@ -217,7 +217,39 @@ button:disabled {
|
||||
|
||||
/* 次要按钮样式 */
|
||||
button.secondary {
|
||||
background: var(--text-secondary);
|
||||
background: transparent;
|
||||
border: 1px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
button.secondary:hover {
|
||||
background: var(--primary-color-alpha);
|
||||
border-color: var(--primary-dark);
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background: var(--error-color);
|
||||
border: none;
|
||||
}
|
||||
|
||||
button.danger:hover {
|
||||
background: #d32f2f;
|
||||
/* 深红色 */
|
||||
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.2);
|
||||
}
|
||||
|
||||
/* 激活状态的按钮 */
|
||||
button.active {
|
||||
background: var(--primary-dark);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
button.secondary.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
/* 按钮组 */
|
||||
@@ -227,22 +259,93 @@ button.secondary {
|
||||
margin: var(--spacing) 0;
|
||||
}
|
||||
|
||||
/* 消息提示 */
|
||||
/* 按钮组中的按钮间距调整 */
|
||||
.button-group button {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
/* 消息容器 - 固定在顶部中间 */
|
||||
.message-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
/* 允许点击穿透 */
|
||||
}
|
||||
|
||||
/* 单个消息样式 */
|
||||
.message {
|
||||
padding: 12px;
|
||||
border-radius: var(--border-radius);
|
||||
margin: 10px 0;
|
||||
border: 1px solid transparent;
|
||||
padding: 12px 20px;
|
||||
border-radius: 4px;
|
||||
background: var(--card-background);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
margin-bottom: 10px;
|
||||
pointer-events: auto;
|
||||
/* 允许消息本身可以交互 */
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
animation: messageIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.success {
|
||||
background: var(--success-color);
|
||||
color: #fff;
|
||||
.message.success {
|
||||
background: #f0f9eb;
|
||||
border: 1px solid #e1f3d8;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: var(--error-color);
|
||||
color: #fff;
|
||||
.message.error {
|
||||
background: #fef0f0;
|
||||
border: 1px solid #fde2e2;
|
||||
}
|
||||
|
||||
@keyframes messageIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes messageOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.message {
|
||||
background: #2c2c2c;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background: #294929;
|
||||
border-color: #1c321c;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: #4d2c2c;
|
||||
border-color: #321c1c;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
|
||||
@@ -24,19 +24,50 @@ function getAuthToken() {
|
||||
|
||||
// 消息显示功能
|
||||
function showMessage(elementId, text, isError = false) {
|
||||
const msg = document.getElementById(elementId);
|
||||
msg.className = `message ${isError ? 'error' : 'success'}`;
|
||||
msg.textContent = text;
|
||||
let msg = document.getElementById(elementId);
|
||||
|
||||
// 如果消息元素不存在,创建一个新的
|
||||
if (!msg) {
|
||||
msg = document.createElement('div');
|
||||
msg.id = elementId;
|
||||
document.body.appendChild(msg);
|
||||
}
|
||||
|
||||
msg.className = `floating-message ${isError ? 'error' : 'success'}`;
|
||||
msg.innerHTML = text.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
function showGlobalMessage(text, isError = false) {
|
||||
showMessage('message', text, isError);
|
||||
// 3秒后自动清除消息
|
||||
// 确保消息容器存在
|
||||
function ensureMessageContainer() {
|
||||
let container = document.querySelector('.message-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.className = 'message-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
function showGlobalMessage(text, isError = false, timeout = 3000) {
|
||||
const container = ensureMessageContainer();
|
||||
|
||||
const msgElement = document.createElement('div');
|
||||
msgElement.className = `message ${isError ? 'error' : 'success'}`;
|
||||
msgElement.textContent = text;
|
||||
|
||||
container.appendChild(msgElement);
|
||||
|
||||
// 设置淡出动画和移除
|
||||
setTimeout(() => {
|
||||
const msg = document.getElementById('message');
|
||||
msg.textContent = '';
|
||||
msg.className = 'message';
|
||||
}, 3000);
|
||||
msgElement.style.animation = 'messageOut 0.3s ease-in-out';
|
||||
setTimeout(() => {
|
||||
msgElement.remove();
|
||||
// 如果容器为空,也移除容器
|
||||
if (container.children.length === 0) {
|
||||
container.remove();
|
||||
}
|
||||
}, 300);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// Token 输入框自动填充和事件绑定
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<!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: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
padding: 1px 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</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="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>
|
||||
</div>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<script>
|
||||
function showMessage(text, isError = false) {
|
||||
showGlobalMessage(text, isError);
|
||||
}
|
||||
|
||||
async function getTokenInfo() {
|
||||
const data = await makeAuthenticatedRequest('/get-tokeninfo');
|
||||
if (data) {
|
||||
document.getElementById('tokens').value = data.tokens;
|
||||
document.getElementById('tokenList').value = data.token_list;
|
||||
showGlobalMessage('配置获取成功');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTokenInfo() {
|
||||
const tokens = document.getElementById('tokens').value;
|
||||
const tokenList = document.getElementById('tokenList').value;
|
||||
|
||||
if (!tokens) {
|
||||
showGlobalMessage('Token 文件内容不能为空', true);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await makeAuthenticatedRequest('/update-tokeninfo', {
|
||||
body: JSON.stringify({
|
||||
tokens: tokens,
|
||||
token_list: tokenList || undefined
|
||||
})
|
||||
});
|
||||
|
||||
if (data) {
|
||||
showGlobalMessage(`更新成功: ${data.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 快捷键支持
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.ctrlKey && e.key === 's') {
|
||||
e.preventDefault();
|
||||
updateTokenInfo();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化 token 处理
|
||||
initializeTokenHandling('authToken');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
603
static/tokens.html
Normal file
603
static/tokens.html
Normal file
@@ -0,0 +1,603 @@
|
||||
<!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: 100px;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.action-cell button {
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 提示框样式 */
|
||||
.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;
|
||||
}
|
||||
</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="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 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="enableStreamCheck">
|
||||
<option value="">跟随全局</option>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>包含停止流:</label>
|
||||
<select id="includeStopStream">
|
||||
<option value="">跟随全局</option>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
</select>
|
||||
</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="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>
|
||||
|
||||
<script>
|
||||
async function getTokenInfo() {
|
||||
const data = await makeAuthenticatedRequest('/tokens/get');
|
||||
if (data) {
|
||||
const tableBody = document.getElementById('tokenTableBody');
|
||||
tableBody.innerHTML = data.tokens.map(t => `<tr><td title="${t.token}">${t.token}</td><td title="${t.checksum}">${t.checksum}</td><td class="action-cell"><button onclick="showKeyModal('${t.token}','${t.checksum}')" class="secondary">生成Key</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 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();
|
||||
}
|
||||
}
|
||||
|
||||
// 动态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('enableStreamCheck').value = '';
|
||||
document.getElementById('includeStopStream').value = '';
|
||||
document.getElementById('disableVision').value = '';
|
||||
document.getElementById('enableSlowPool').value = '';
|
||||
document.getElementById('usageCheckType').value = '';
|
||||
document.getElementById('modelListContainer').style.display = 'none';
|
||||
}
|
||||
|
||||
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}`,
|
||||
enable_stream_check: parseBooleanFromString(document.getElementById('enableStreamCheck').value, undefined),
|
||||
include_stop_stream: parseBooleanFromString(document.getElementById('includeStopStream').value, undefined),
|
||||
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
|
||||
};
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user