3 Commits

Author SHA1 Message Date
7fede0212b feat: 后台添加大模型配置管理页面
- 新增 /admin/llm_config 页面
- 支持配置API地址、Key、模型名称、参数
- 支持测试连接和恢复默认配置
- 配置保存到数据库,翻译服务动态读取
- 所有后台页面侧边栏添加入口
2026-04-10 18:42:20 +08:00
801dd1e29b fix: 首页加载时检查日期并重置daily_count 2026-04-10 18:29:37 +08:00
e5ba13980f fix: 修复时区问题和翻译状态更新问题
- 使用上海时区(UTC+8)判断每日次数重置,而不是UTC时间
- 翻译任务完成后更新数据库Translation记录的状态和进度
- 传入translation_id和app到TranslationTask以支持数据库状态同步
2026-04-10 18:26:57 +08:00
18 changed files with 399 additions and 11 deletions

108
admin.py
View File

@@ -661,4 +661,110 @@ def api_user_add_package(user_id):
'id': user_package.id,
'name': user_package.package_name,
'remaining': user_package.remaining_count
}})
}})
# ==================== LLM大模型配置 ====================
@admin_bp.route('/llm_config')
@admin_required
def llm_config():
"""LLM配置页面"""
from config import LLM_CONFIG
# 从数据库获取配置,如果没有则使用默认值
config = {
'api_base': DynamicConfig.get('llm_api_base', LLM_CONFIG.get('api_base')),
'api_key': DynamicConfig.get('llm_api_key', LLM_CONFIG.get('api_key')),
'model': DynamicConfig.get('llm_model', LLM_CONFIG.get('model')),
'max_tokens': DynamicConfig.get('llm_max_tokens', LLM_CONFIG.get('max_tokens')),
'chunk_size': DynamicConfig.get('llm_chunk_size', LLM_CONFIG.get('chunk_size')),
'timeout': DynamicConfig.get('llm_timeout', LLM_CONFIG.get('timeout')),
}
return render_template('admin/llm_config.html', config=config)
@admin_bp.route('/llm_config/save', methods=['POST'])
@admin_required
def save_llm_config():
"""保存LLM配置"""
data = request.json
DynamicConfig.set('llm_api_base', data.get('api_base'), category='llm', user_id=session.get('user_id'))
DynamicConfig.set('llm_api_key', data.get('api_key'), category='llm', user_id=session.get('user_id'))
DynamicConfig.set('llm_model', data.get('model'), category='llm', user_id=session.get('user_id'))
DynamicConfig.set('llm_max_tokens', data.get('max_tokens'), category='llm', value_type='int', user_id=session.get('user_id'))
DynamicConfig.set('llm_chunk_size', data.get('chunk_size'), category='llm', value_type='int', user_id=session.get('user_id'))
DynamicConfig.set('llm_timeout', data.get('timeout'), category='llm', value_type='int', user_id=session.get('user_id'))
# 记录日志
log = OperationLog(
user_id=session.get('user_id'),
username='admin',
action='update_llm_config',
detail='更新大模型配置'
)
db.session.add(log)
db.session.commit()
return jsonify({'success': True})
@admin_bp.route('/llm_config/test', methods=['POST'])
@admin_required
def test_llm_connection():
"""测试LLM连接"""
data = request.json
try:
from openai import OpenAI
client = OpenAI(
api_key=data.get('api_key', 'sk-test'),
base_url=data.get('api_base'),
)
# 发送简单测试请求
response = client.chat.completions.create(
model=data.get('model'),
messages=[{"role": "user", "content": "Hello"}],
max_tokens=10,
timeout=10,
)
return jsonify({
'success': True,
'model': data.get('model'),
'response': response.choices[0].message.content[:50] if response.choices else 'OK'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@admin_bp.route('/llm_config/reset', methods=['POST'])
@admin_required
def reset_llm_config():
"""恢复默认LLM配置"""
from config import LLM_CONFIG
# 删除数据库中的LLM配置
DynamicConfig.query.filter_by(category='llm').delete()
db.session.commit()
return jsonify({'success': True})
# ==================== 获取当前LLM配置供其他模块使用 ====================
def get_llm_config():
"""获取当前LLM配置"""
from config import LLM_CONFIG
return {
'api_base': DynamicConfig.get('llm_api_base', LLM_CONFIG.get('api_base')),
'api_key': DynamicConfig.get('llm_api_key', LLM_CONFIG.get('api_key')),
'model': DynamicConfig.get('llm_model', LLM_CONFIG.get('model')),
'max_tokens': DynamicConfig.get('llm_max_tokens', LLM_CONFIG.get('max_tokens')),
'chunk_size': DynamicConfig.get('llm_chunk_size', LLM_CONFIG.get('chunk_size')),
'timeout': DynamicConfig.get('llm_timeout', LLM_CONFIG.get('timeout')),
}

39
app.py
View File

@@ -93,11 +93,29 @@ def index():
"""首页"""
user = get_current_user()
if user:
# 检查日期并重置计数(使用上海时区)
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
today = datetime.now(shanghai_tz).date()
if user.last_translate_date != today:
user.daily_count = 0
user.last_translate_date = today
db.session.commit()
limits = USER_LIMITS.get(user.user_type, USER_LIMITS['free'])
daily_remaining = limits['daily_translations'] - user.daily_count if limits['daily_translations'] > 0 else '无限'
max_pages = limits['max_pages'] if limits['max_pages'] > 0 else '无限'
else:
guest = get_or_create_guest()
# 检查日期并重置访客计数
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
today = datetime.now(shanghai_tz).date()
if guest.last_translate_date != today:
guest.daily_count = 0
guest.last_translate_date = today
db.session.commit()
limits = USER_LIMITS['guest']
daily_remaining = limits['daily_translations'] - guest.daily_count
max_pages = limits['max_pages']
@@ -229,13 +247,8 @@ def upload_pdf():
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, f"{filename}_translated.md")
# 创建异步翻译任务
# 创建异步翻译任务先不创建等translation_id生成后
task_id = str(uuid.uuid4())
TranslationTask.create_task(
task_id, upload_path, output_path,
{'LLM_CONFIG': LLM_CONFIG},
instruction
)
# 创建翻译记录
translation = Translation(
@@ -252,6 +265,20 @@ def upload_pdf():
)
db.session.add(translation)
# 预先提交获取 translation_id
if not from_cache:
db.session.flush() # 获取 ID 但不提交完整事务
# 创建异步翻译任务(需要翻译时)
if not from_cache:
TranslationTask.create_task(
task_id, upload_path, output_path,
{'LLM_CONFIG': LLM_CONFIG},
instruction,
translation_id=translation.id,
app=app
)
# 更新用户/访客计数
if user:
user.increment_count()

View File

@@ -67,8 +67,10 @@ class User(db.Model):
if max_pages > 0 and pages > max_pages:
return False, f"PDF页数超出限制最大{max_pages}页)"
# 检查每日次数限制
today = datetime.utcnow().date()
# 检查每日次数限制 - 使用上海时间UTC+8
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
today = datetime.now(shanghai_tz).date()
if self.last_translate_date != today:
self.daily_count = 0
self.last_translate_date = today
@@ -81,7 +83,9 @@ class User(db.Model):
def increment_count(self):
"""增加翻译计数"""
today = datetime.utcnow().date()
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
today = datetime.now(shanghai_tz).date()
if self.last_translate_date != today:
self.daily_count = 0
self.last_translate_date = today

View File

@@ -255,7 +255,7 @@ class TranslationTask:
lock = threading.Lock()
@classmethod
def create_task(cls, task_id, pdf_path, output_path, config, instruction=None):
def create_task(cls, task_id, pdf_path, output_path, config, instruction=None, translation_id=None, app=None):
"""创建翻译任务"""
task = {
'id': task_id,
@@ -266,6 +266,7 @@ class TranslationTask:
'error': None,
'started_at': None,
'completed_at': None,
'translation_id': translation_id,
}
with cls.lock:
@@ -273,14 +274,39 @@ class TranslationTask:
# 启动翻译线程
def run_translation():
# 动态获取LLM配置
if app:
with app.app_context():
from admin import get_llm_config
llm_config = get_llm_config()
config['LLM_CONFIG'] = llm_config
service = TranslationService(config)
task['status'] = 'processing'
task['started_at'] = datetime.now().isoformat()
# 更新数据库状态为 processing
if app and translation_id:
with app.app_context():
from models import db, Translation
trans = Translation.query.get(translation_id)
if trans:
trans.status = 'processing'
db.session.commit()
def progress_callback(progress, total, message):
with cls.lock:
task['progress'] = progress
task['message'] = message
# 更新数据库进度
if app and translation_id:
with app.app_context():
from models import db, Translation
trans = Translation.query.get(translation_id)
if trans:
trans.progress = progress
db.session.commit()
try:
result = service.translate_pdf(
@@ -291,11 +317,32 @@ class TranslationTask:
task['message'] = '翻译完成'
task['completed_at'] = datetime.now().isoformat()
task['result'] = result
# 更新数据库状态为 completed
if app and translation_id:
with app.app_context():
from models import db, Translation
trans = Translation.query.get(translation_id)
if trans:
trans.status = 'completed'
trans.progress = 100
trans.completed_at = datetime.now()
db.session.commit()
except Exception as e:
task['status'] = 'failed'
task['error'] = str(e)
task['message'] = f'翻译失败: {e}'
# 更新数据库状态为 failed
if app and translation_id:
with app.app_context():
from models import db, Translation
trans = Translation.query.get(translation_id)
if trans:
trans.status = 'failed'
trans.error_message = str(e)
db.session.commit()
thread = threading.Thread(target=run_translation)
thread.start()

View File

@@ -25,6 +25,7 @@
<li class="nav-item"><a class="nav-link active" 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" 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">

View File

@@ -93,6 +93,11 @@
<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" href="{{ url_for('admin.settings') }}">
<i class="bi bi-sliders"></i> 系统配置

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>大模型配置 - 后台管理</title>
<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 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.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-cpu"></i> 大模型接口配置</h6>
</div>
<div class="card-body">
<form id="llmConfigForm">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">API 地址</label>
<input type="text" class="form-control" name="api_base" value="{{ config.api_base }}" placeholder="http://localhost:1234/v1">
<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 }}" 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 }}" placeholder="qwen/qwen3.5-35b">
<small class="text-muted">使用的模型ID</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">最大输出Token</label>
<input type="number" class="form-control" name="max_tokens" value="{{ config.max_tokens }}" placeholder="8000">
<small class="text-muted">每次翻译最大输出长度</small>
</div>
<div class="mb-3">
<label class="form-label">分块大小</label>
<input type="number" class="form-control" name="chunk_size" value="{{ config.chunk_size }}" placeholder="2000">
<small class="text-muted">PDF文本分块大小</small>
</div>
<div class="mb-3">
<label class="form-label">超时时间(秒)</label>
<input type="number" class="form-control" name="timeout" value="{{ config.timeout }}" placeholder="180">
<small class="text-muted">API请求超时时间</small>
</div>
</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="testConnection()"><i class="bi bi-plug"></i> 测试连接</button>
<button type="button" class="btn btn-outline-warning" onclick="resetToDefault()"><i class="bi bi-arrow-counterclockwise"></i> 恢复默认</button>
</div>
</form>
<div id="testResult" class="mt-3" style="display:none;"></div>
</div>
</div>
<div class="card mt-3">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-info-circle"></i> 常用模型配置参考</h6></div>
<div class="card-body">
<table class="table table-sm">
<thead class="table-light">
<tr><th>服务商</th><th>API地址</th><th>模型示例</th></tr>
</thead>
<tbody>
<tr><td>本地LM Studio</td><td>http://localhost:1234/v1</td><td>根据加载的模型</td></tr>
<tr><td>OpenAI</td><td>https://api.openai.com/v1</td><td>gpt-4, gpt-3.5-turbo</td></tr>
<tr><td>DeepSeek</td><td>https://api.deepseek.com/v1</td><td>deepseek-chat</td></tr>
<tr><td>阿里百炼</td><td>https://dashscope.aliyuncs.com/compatible-mode/v1</td><td>qwen-turbo, qwen-plus</td></tr>
<tr><td>SiliconFlow</td><td>https://api.siliconflow.cn/v1</td><td>Qwen/Qwen2.5-72B-Instruct</td></tr>
</tbody>
</table>
</div>
</div>
</main>
<script>
document.getElementById('llmConfigForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = {};
formData.forEach((value, key) => {
if (key === 'max_tokens' || key === 'chunk_size' || key === 'timeout') {
data[key] = parseInt(value) || 0;
} else {
data[key] = value;
}
});
fetch('/admin/llm_config/save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => {
if (res.success) {
alert('配置已保存!');
} else {
alert('保存失败: ' + res.error);
}
})
.catch(err => alert('请求失败: ' + err));
});
function testConnection() {
const formData = new FormData(document.getElementById('llmConfigForm'));
const data = {};
formData.forEach((value, key) => {
if (key === 'max_tokens' || key === 'chunk_size' || key === 'timeout') {
data[key] = parseInt(value) || 0;
} else {
data[key] = value;
}
});
const resultDiv = document.getElementById('testResult');
resultDiv.style.display = 'block';
resultDiv.innerHTML = '<div class="alert alert-info">正在测试连接...</div>';
fetch('/admin/llm_config/test', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => {
if (res.success) {
resultDiv.innerHTML = '<div class="alert alert-success"><i class="bi bi-check-circle"></i> 连接成功!模型: ' + 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>';
});
}
function resetToDefault() {
if (confirm('确定恢复默认配置吗?')) {
fetch('/admin/llm_config/reset', {method: 'POST'})
.then(r => r.json())
.then(res => {
if (res.success) {
location.reload();
} else {
alert('恢复失败: ' + res.error);
}
});
}
}
</script>
</body>
</html>

View File

@@ -25,6 +25,7 @@
<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 active" 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" 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">

View File

@@ -31,6 +31,7 @@
<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" 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.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">

View File

@@ -26,6 +26,7 @@
<li class="nav-item"><a class="nav-link active" 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" 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.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">

View File

@@ -30,6 +30,7 @@
<li class="nav-item"><a class="nav-link active" 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" 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.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">

View File

@@ -25,6 +25,7 @@
<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.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">

View File

@@ -26,6 +26,7 @@
<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 active" 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" 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">

View File

@@ -26,6 +26,7 @@
<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" 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">

View File

@@ -26,6 +26,7 @@
<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" 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">

View File

@@ -27,6 +27,7 @@
<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" 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">

View File

@@ -26,6 +26,7 @@
<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" 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.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">

View File

@@ -28,6 +28,7 @@
<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" 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">