mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-12-24 13:38:01 +08:00
v0.1.3-rc.1
This commit is contained in:
226
static/config.html
Normal file
226
static/config.html
Normal 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
382
static/logs.html
Normal 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">×</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">×</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
169
static/shared-styles.css
Normal 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
252
static/shared.js
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// 将HTML标签文本用引号包裹,使其更易读
|
||||
// return escaped.replace(/<(\/?[^>]+)>/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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user