Files
pdf-translate-web/templates/admin/llm_config.html
coder abb76bf6d3 fix: 操作按钮图标放大,更清晰可见
- btn-sm 改成 btn 正常大小
- 图标加 fs-5 类放大字体
- 切换按钮图标改成电源图标(power)更直观
- 设为默认图标改成实心星星
- 操作列宽度加宽到180px
2026-04-16 18:31:54 +08:00

336 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>大模型配置 - 后台管理</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body { background-color: #f5f5f5; }
.sidebar { position: fixed; top: 0; left: 0; height: 100vh; width: 250px; background: #343a40; padding-top: 60px; }
.sidebar .nav-link { color: #adb5bd; padding: 12px 20px; }
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); }
.main-content { margin-left: 250px; padding: 20px; }
.table th, .table td { font-size: 0.9rem; }
.status-badge { font-size: 0.75rem; }
.default-badge { font-size: 0.75rem; }
.row-default { background-color: rgba(13, 110, 253, 0.05); }
</style>
</head>
<body>
<nav class="sidebar">
<div class="position-absolute top-0 w-100 p-3 border-bottom border-secondary">
<h5 class="text-white mb-0"><i class="bi bi-gear-fill"></i> 后台管理</h5>
</div>
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.dashboard') }}"><i class="bi bi-speedometer2"></i> 数据概览</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}"><i class="bi bi-people"></i> 用户管理</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.translations') }}"><i class="bi bi-file-text"></i> 翻译记录</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.cache_list') }}"><i class="bi bi-database"></i> 缓存管理</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.packages') }}"><i class="bi bi-box-seam"></i> 数据包套餐</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.stats') }}"><i class="bi bi-bar-chart"></i> 统计报表</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.logs') }}"><i class="bi bi-list-check"></i> 操作日志</a></li>
<li class="nav-item"><a class="nav-link active" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.user_types') }}"><i class="bi bi-person-badge"></i> 用户类型</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.membership_plans') }}"><i class="bi bi-credit-card"></i> 会员套餐</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">
<a href="/" class="btn btn-outline-light btn-sm w-100"><i class="bi bi-house"></i> 返回前台</a>
</div>
</nav>
<main class="main-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="bi bi-cpu"></i> 大模型接口配置</h6>
<button class="btn btn-sm btn-primary" onclick="showAddModal()"><i class="bi bi-plus-lg"></i> 新增接口</button>
</div>
<div class="card-body">
<p class="text-muted mb-3">
<i class="bi bi-info-circle"></i>
从列表中选择一个接口设为<strong>默认</strong>,系统将使用该接口进行翻译。点击"设为默认"按钮即可切换。
</p>
<table class="table table-sm table-hover">
<thead class="table-light">
<tr>
<th style="width: 40px;">#</th>
<th>服务商</th>
<th>API地址</th>
<th>模型</th>
<th style="width: 60px;">Token</th>
<th style="width: 70px;">状态</th>
<th style="width: 70px;">默认</th>
<th style="width: 180px;">操作</th>
</tr>
</thead>
<tbody>
{% for item in llm_configs %}
<tr id="llm-row-{{ item.id }}" class="{% if item.is_default %}row-default{% endif %}">
<td>{{ item.sort_order }}</td>
<td>
<strong>{{ item.provider_name }}</strong>
{% if item.description %}<br><small class="text-muted">{{ item.description }}</small>{% endif %}
</td>
<td><code style="font-size: 0.8rem;">{{ item.api_base }}</code></td>
<td><code style="font-size: 0.8rem;">{{ item.model or '默认' }}</code></td>
<td>{{ item.max_tokens }}</td>
<td>
{% if item.is_active %}
<span class="badge bg-success status-badge">启用</span>
{% else %}
<span class="badge bg-secondary status-badge">禁用</span>
{% endif %}
</td>
<td>
{% if item.is_default %}
<span class="badge bg-primary default-badge"><i class="bi bi-star-fill"></i> 默认</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
<div class="d-flex gap-1">
<button class="btn btn-outline-primary" onclick="testLLM({{ item.id }})" title="测试连接"><i class="bi bi-plug fs-5"></i></button>
<button class="btn btn-outline-secondary" onclick="showEditModal({{ item.id }})" title="编辑"><i class="bi bi-pencil fs-5"></i></button>
{% if not item.is_default %}
<button class="btn btn-outline-success" onclick="setDefault({{ item.id }})" title="设为默认"><i class="bi bi-star-fill fs-5"></i></button>
{% endif %}
<button class="btn btn-outline-warning" onclick="toggleLLM({{ item.id }})" title="启用/禁用"><i class="bi bi-power fs-5"></i></button>
{% if not item.is_default %}
<button class="btn btn-outline-danger" onclick="deleteLLM({{ item.id }})" title="删除"><i class="bi bi-trash fs-5"></i></button>
{% endif %}
</div>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted py-3">
<i class="bi bi-cloud-slash" style="font-size: 1.5rem;"></i><br>
暂无大模型配置,点击右上角"新增接口"添加
</td></tr>
{% endfor %}
</tbody>
</table>
<div id="testResult" class="mt-2" style="display:none;"></div>
</div>
</div>
</main>
<!-- 新增/编辑模态框 -->
<div class="modal fade" id="llmModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title" id="modalTitle">新增大模型接口</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="llmForm">
<input type="hidden" id="llm_id" name="id">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">服务商名称 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="provider_name" name="provider_name" required placeholder="如: OpenAI">
</div>
<div class="mb-3">
<label class="form-label">API地址 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="api_base" name="api_base" required placeholder="https://api.openai.com/v1">
</div>
<div class="mb-3">
<label class="form-label">API Key</label>
<input type="text" class="form-control" id="api_key" name="api_key" placeholder="sk-xxx可选">
</div>
<div class="mb-3">
<label class="form-label">默认模型</label>
<input type="text" class="form-control" id="model" name="model" placeholder="gpt-4">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">最大输出Token</label>
<input type="number" class="form-control" id="max_tokens" name="max_tokens" value="8000">
</div>
<div class="mb-3">
<label class="form-label">分块大小</label>
<input type="number" class="form-control" id="chunk_size" name="chunk_size" value="2000">
</div>
<div class="mb-3">
<label class="form-label">超时时间(秒)</label>
<input type="number" class="form-control" id="timeout" name="timeout" value="180">
</div>
<div class="mb-3">
<label class="form-label">备注</label>
<input type="text" class="form-control" id="description" name="description">
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<label class="form-label">排序</label>
<input type="number" class="form-control" id="sort_order" name="sort_order" value="0">
</div>
<div class="col-6">
<label class="form-label">状态</label>
<select class="form-select" id="is_active" name="is_active">
<option value="true">启用</option>
<option value="false">禁用</option>
</select>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" onclick="testFormConnection()"><i class="bi bi-plug"></i> 测试连接</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="saveLLM()"><i class="bi bi-check-lg"></i> 保存</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const llmData = {{ llm_configs | tojson }};
const modal = new bootstrap.Modal(document.getElementById('llmModal'));
function showAddModal() {
document.getElementById('modalTitle').textContent = '新增大模型接口';
document.getElementById('llm_id').value = '';
document.getElementById('provider_name').value = '';
document.getElementById('api_base').value = '';
document.getElementById('api_key').value = '';
document.getElementById('model').value = '';
document.getElementById('max_tokens').value = '8000';
document.getElementById('chunk_size').value = '2000';
document.getElementById('timeout').value = '180';
document.getElementById('description').value = '';
document.getElementById('sort_order').value = '0';
document.getElementById('is_active').value = 'true';
modal.show();
}
function showEditModal(id) {
const item = llmData.find(b => b.id === id);
if (!item) return;
document.getElementById('modalTitle').textContent = '编辑大模型接口';
document.getElementById('llm_id').value = id;
document.getElementById('provider_name').value = item.provider_name;
document.getElementById('api_base').value = item.api_base;
document.getElementById('api_key').value = item.api_key || '';
document.getElementById('model').value = item.model || '';
document.getElementById('max_tokens').value = item.max_tokens;
document.getElementById('chunk_size').value = item.chunk_size;
document.getElementById('timeout').value = item.timeout;
document.getElementById('description').value = item.description || '';
document.getElementById('sort_order').value = item.sort_order;
document.getElementById('is_active').value = item.is_active ? 'true' : 'false';
modal.show();
}
function saveLLM() {
const id = document.getElementById('llm_id').value;
const data = {
provider_name: document.getElementById('provider_name').value,
api_base: document.getElementById('api_base').value,
api_key: document.getElementById('api_key').value,
model: document.getElementById('model').value,
max_tokens: parseInt(document.getElementById('max_tokens').value) || 8000,
chunk_size: parseInt(document.getElementById('chunk_size').value) || 2000,
timeout: parseInt(document.getElementById('timeout').value) || 180,
description: document.getElementById('description').value,
sort_order: parseInt(document.getElementById('sort_order').value) || 0,
is_active: document.getElementById('is_active').value === 'true'
};
const url = id ? `/admin/backup-llm/${id}/edit` : '/admin/backup-llm/add';
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => {
if (res.success) {
modal.hide();
location.reload();
} else {
alert('保存失败: ' + (res.error || '未知错误'));
}
})
.catch(err => alert('请求失败: ' + err));
}
function testLLM(id) {
const div = document.getElementById('testResult');
div.style.display = 'block';
div.innerHTML = '<div class="alert alert-info"><i class="bi bi-hourglass-split"></i> 测试中...</div>';
fetch(`/admin/backup-llm/${id}/test`, {method: 'POST'})
.then(r => r.json())
.then(res => {
div.innerHTML = res.success
? `<div class="alert alert-success"><i class="bi bi-check-circle"></i> ${res.provider} 连接成功!模型: ${res.model}</div>`
: `<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 连接失败: ${res.error}</div>`;
})
.catch(err => div.innerHTML = `<div class="alert alert-danger">请求失败: ${err}</div>`);
}
function testFormConnection() {
const data = {
api_base: document.getElementById('api_base').value,
api_key: document.getElementById('api_key').value,
model: document.getElementById('model').value
};
const div = document.getElementById('testResult');
div.style.display = 'block';
div.innerHTML = '<div class="alert alert-info"><i class="bi bi-hourglass-split"></i> 测试中...</div>';
fetch('/admin/llm_config/test', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => {
div.innerHTML = res.success
? `<div class="alert alert-success"><i class="bi bi-check-circle"></i> 连接成功!</div>`
: `<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 连接失败: ${res.error}</div>`;
})
.catch(err => div.innerHTML = `<div class="alert alert-danger">请求失败: ${err}</div>`);
}
function setDefault(id) {
fetch(`/admin/backup-llm/${id}/set-default`, {method: 'POST'})
.then(r => r.json())
.then(res => {
if (res.success) location.reload();
else alert('设置失败');
});
}
function toggleLLM(id) {
fetch(`/admin/backup-llm/${id}/toggle`, {method: 'POST'})
.then(r => r.json())
.then(res => res.success ? location.reload() : alert('操作失败'));
}
function deleteLLM(id) {
if (confirm('确定删除此接口?')) {
fetch(`/admin/backup-llm/${id}/delete`, {method: 'POST'})
.then(r => r.json())
.then(res => {
if (res.success) document.getElementById(`llm-row-${id}`).remove();
else alert('删除失败');
});
}
}
</script>
</body>
</html>