Files
pdf-translate-web/templates/admin/backup_llm_form.html
coder 8ef9df65d2 feat: 备用大模型接口管理功能
- 新增 BackupLLMConfig 数据模型存储备用大模型配置
- 支持手动新增、编辑、删除备用大模型接口
- 支持测试连接功能
- 大模型配置页面静态表格改为动态管理的备用接口链接
- 默认初始化5个常用大模型服务商配置
2026-04-16 14:36:13 +08:00

188 lines
9.8 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>{{ config ? '编辑' : '添加' }}备用大模型接口 - 后台管理</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; }
</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.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" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
<li class="nav-item"><a class="nav-link active" href="{{ url_for('admin.backup_llm_list') }}"><i class="bi bi-cloud"></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">
<h6 class="mb-0"><i class="bi bi-cloud-plus"></i> {{ config ? '编辑备用大模型接口' : '添加备用大模型接口' }}</h6>
</div>
<div class="card-body">
<form id="configForm" onsubmit="return saveConfig(event)">
<div class="mb-3">
<label class="form-label">服务商名称 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="provider_name"
value="{{ config.provider_name if config else '' }}"
placeholder="例如: OpenAI, DeepSeek, 阿里百炼" required>
<small class="text-muted">显示名称,方便识别</small>
</div>
<div class="mb-3">
<label class="form-label">API地址 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="api_base"
value="{{ config.api_base if config else '' }}"
placeholder="https://api.openai.com/v1" required>
<small class="text-muted">LLM服务的API endpoint</small>
</div>
<div class="mb-3">
<label class="form-label">API Key</label>
<input type="text" class="form-control" name="api_key"
value="{{ config.api_key if config else '' }}"
placeholder="sk-xxx可选">
<small class="text-muted">如果不需要可留空</small>
</div>
<div class="mb-3">
<label class="form-label">默认模型</label>
<input type="text" class="form-control" name="model"
value="{{ config.model if config else '' }}"
placeholder="gpt-4, deepseek-chat">
<small class="text-muted">推荐使用的模型ID</small>
</div>
<div class="mb-3">
<label class="form-label">备注</label>
<input type="text" class="form-control" name="description"
value="{{ config.description if config else '' }}"
placeholder="接口说明">
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">排序</label>
<input type="number" class="form-control" name="sort_order"
value="{{ config.sort_order if config else 0 }}">
</div>
<div class="col-md-6">
<label class="form-label">状态</label>
<select class="form-select" name="is_active">
<option value="true" {% if not config or config.is_active %}selected{% endif %}>启用</option>
<option value="false" {% if config and not config.is_active %}selected{% endif %}>禁用</option>
</select>
</div>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg"></i> 保存</button>
<button type="button" class="btn btn-outline-secondary" onclick="testConfig()"><i class="bi bi-plug"></i> 测试连接</button>
<a href="{{ url_for('admin.backup_llm_list') }}" class="btn btn-outline-secondary"><i class="bi bi-arrow-left"></i> 返回</a>
</div>
</form>
<div id="testResult" class="mt-3" style="display:none;"></div>
</div>
</div>
</main>
<script>
function saveConfig(e) {
e.preventDefault();
const formData = new FormData(document.getElementById('configForm'));
const data = {};
formData.forEach((value, key) => {
if (key === 'sort_order') {
data[key] = parseInt(value) || 0;
} else if (key === 'is_active') {
data[key] = value === 'true';
} else {
data[key] = value;
}
});
{% if config %}
const url = `/admin/backup-llm/{{ config.id }}/edit`;
{% else %}
const url = '/admin/backup-llm/add';
{% endif %}
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => {
if (res.success) {
alert('保存成功!');
window.location.href = '/admin/backup-llm';
} else {
alert('保存失败: ' + (res.error || '未知错误'));
}
})
.catch(err => alert('请求失败: ' + err));
return false;
}
function testConfig() {
const formData = new FormData(document.getElementById('configForm'));
const resultDiv = document.getElementById('testResult');
resultDiv.style.display = 'block';
resultDiv.innerHTML = '<div class="alert alert-info"><i class="bi bi-hourglass-split"></i> 正在测试连接...</div>';
// 先临时保存,然后测试
{% if config %}
// 已有配置,直接测试
fetch(`/admin/backup-llm/{{ config.id }}/test`, {method: 'POST'})
{% else %}
// 新配置,需要先创建
const data = {};
formData.forEach((value, key) => {
if (key === 'sort_order') data[key] = parseInt(value) || 0;
else if (key === 'is_active') data[key] = value === 'true';
else data[key] = value;
});
// 直接用输入的数据测试(不保存)
resultDiv.innerHTML = '<div class="alert alert-warning"><i class="bi bi-info-circle"></i> 新配置请先保存后再测试</div>';
return;
{% endif %}
.then(r => r.json())
.then(res => {
if (res.success) {
resultDiv.innerHTML = `<div class="alert alert-success"><i class="bi bi-check-circle"></i> ${res.provider} 连接成功!模型: ${res.model}</div>`;
} else {
resultDiv.innerHTML = `<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 连接失败: ${res.error}</div>`;
}
})
.catch(err => {
resultDiv.innerHTML = `<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 请求失败: ${err}</div>`;
});
}
</script>
</body>
</html>