diff --git a/admin.py b/admin.py index 05dd594..31f2d15 100644 --- a/admin.py +++ b/admin.py @@ -10,7 +10,7 @@ import json from models import (db, User, Translation, TranslationCache, GuestTranslation, SystemConfig, OperationLog, DataPackage, UserPackage, DynamicConfig, - UserTypeConfig, MembershipPlanConfig) + UserTypeConfig, MembershipPlanConfig, BackupLLMConfig) from config import USER_LIMITS, MEMBERSHIP_PLANS admin_bp = Blueprint('admin', __name__, url_prefix='/admin') @@ -1217,4 +1217,206 @@ def get_membership_plan(plan_key): def get_all_membership_plans(): """获取所有会员套餐配置""" plans = MembershipPlanConfig.query.filter_by(is_active=True).order_by(MembershipPlanConfig.sort_order).all() - return [p.to_dict() for p in plans] \ No newline at end of file + return [p.to_dict() for p in plans] + + +# ==================== 备用大模型接口管理 ==================== +@admin_bp.route('/backup-llm') +@admin_required +def backup_llm_list(): + """备用大模型接口列表""" + configs = BackupLLMConfig.query.order_by(BackupLLMConfig.sort_order).all() + + # 如果数据库中没有数据,初始化默认配置 + if not configs: + init_default_backup_llm() + configs = BackupLLMConfig.query.order_by(BackupLLMConfig.sort_order).all() + + return render_template('admin/backup_llm.html', configs=configs) + + +@admin_bp.route('/backup-llm/add', methods=['GET', 'POST']) +@admin_required +def add_backup_llm(): + """添加备用大模型接口""" + if request.method == 'POST': + data = request.json if request.is_json else request.form + + config = BackupLLMConfig( + provider_name=data.get('provider_name'), + api_base=data.get('api_base'), + api_key=data.get('api_key'), + model=data.get('model'), + is_active=data.get('is_active', True) if isinstance(data.get('is_active'), bool) else data.get('is_active') == 'true', + sort_order=int(data.get('sort_order', 0)), + description=data.get('description'), + ) + + db.session.add(config) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='add_backup_llm', + target=config.provider_name, + detail=json.dumps(config.to_dict(), ensure_ascii=False) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'config': config.to_dict()}) + flash('备用大模型接口已添加', 'success') + return redirect(url_for('admin.backup_llm_list')) + + return render_template('admin/backup_llm_form.html', config=None) + + +@admin_bp.route('/backup-llm//edit', methods=['GET', 'POST']) +@admin_required +def edit_backup_llm(config_id): + """编辑备用大模型接口""" + config = BackupLLMConfig.query.get_or_404(config_id) + + if request.method == 'POST': + data = request.json if request.is_json else request.form + + config.provider_name = data.get('provider_name', config.provider_name) + config.api_base = data.get('api_base', config.api_base) + config.api_key = data.get('api_key', config.api_key) + config.model = data.get('model', config.model) + config.is_active = data.get('is_active', True) if isinstance(data.get('is_active'), bool) else data.get('is_active') == 'true' + config.sort_order = int(data.get('sort_order', config.sort_order)) + config.description = data.get('description', config.description) + + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='edit_backup_llm', + target=config.provider_name, + detail=json.dumps(config.to_dict(), ensure_ascii=False) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'config': config.to_dict()}) + flash('备用大模型接口已更新', 'success') + return redirect(url_for('admin.backup_llm_list')) + + return render_template('admin/backup_llm_form.html', config=config) + + +@admin_bp.route('/backup-llm//delete', methods=['POST']) +@admin_required +def delete_backup_llm(config_id): + """删除备用大模型接口""" + config = BackupLLMConfig.query.get_or_404(config_id) + + provider_name = config.provider_name + db.session.delete(config) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='delete_backup_llm', + target=provider_name + ) + db.session.add(log) + db.session.commit() + + return jsonify({'success': True}) + + +@admin_bp.route('/backup-llm//toggle', methods=['POST']) +@admin_required +def toggle_backup_llm(config_id): + """切换备用大模型接口状态""" + config = BackupLLMConfig.query.get_or_404(config_id) + config.is_active = not config.is_active + db.session.commit() + + return jsonify({'success': True, 'is_active': config.is_active}) + + +@admin_bp.route('/backup-llm//test', methods=['POST']) +@admin_required +def test_backup_llm(config_id): + """测试备用大模型接口""" + config = BackupLLMConfig.query.get_or_404(config_id) + + try: + from openai import OpenAI + + client = OpenAI( + api_key=config.api_key or 'sk-test', + base_url=config.api_base, + ) + + model = config.model or 'default' + + # 发送简单测试请求 + response = client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": "Hello"}], + max_tokens=10, + timeout=10, + ) + + return jsonify({ + 'success': True, + 'provider': config.provider_name, + 'model': 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('/backup-llm/init', methods=['POST']) +@admin_required +def init_backup_llm(): + """初始化默认备用大模型""" + init_default_backup_llm() + return jsonify({'success': True}) + + +def init_default_backup_llm(): + """初始化默认备用大模型接口配置""" + defaults = [ + ('本地LM Studio', 'http://localhost:1234/v1', None, None, 0), + ('OpenAI', 'https://api.openai.com/v1', None, 'gpt-4', 1), + ('DeepSeek', 'https://api.deepseek.com/v1', None, 'deepseek-chat', 2), + ('阿里百炼', 'https://dashscope.aliyuncs.com/compatible-mode/v1', None, 'qwen-turbo', 3), + ('SiliconFlow', 'https://api.siliconflow.cn/v1', None, 'Qwen/Qwen2.5-72B-Instruct', 4), + ] + + for provider_name, api_base, api_key, model, sort_order in defaults: + existing = BackupLLMConfig.query.filter_by(provider_name=provider_name).first() + if not existing: + config = BackupLLMConfig( + provider_name=provider_name, + api_base=api_base, + api_key=api_key, + model=model, + is_active=True, + sort_order=sort_order, + description=f'{provider_name} 默认接口', + ) + db.session.add(config) + + db.session.commit() + + +def get_backup_llm_configs(): + """获取所有备用大模型配置(供其他模块使用)""" + configs = BackupLLMConfig.query.filter_by(is_active=True).order_by(BackupLLMConfig.sort_order).all() + return [c.to_dict() for c in configs] \ No newline at end of file diff --git a/models.py b/models.py index 5faa544..721b7e6 100644 --- a/models.py +++ b/models.py @@ -869,6 +869,44 @@ class EmailNotification(db.Model): } +# ==================== 备用大模型接口配置 ==================== +class BackupLLMConfig(db.Model): + """备用大模型接口配置""" + __tablename__ = 'backup_llm_config' + + id = db.Column(db.Integer, primary_key=True) + + # 服务商信息 + provider_name = db.Column(db.String(100), nullable=False) # 服务商名称: OpenAI, DeepSeek, 阿里百炼, etc. + api_base = db.Column(db.String(255), nullable=False) # API地址 + api_key = db.Column(db.String(255), nullable=True) # API Key(可选) + model = db.Column(db.String(100), nullable=True) # 默认模型 + + # 状态 + is_active = db.Column(db.Boolean, default=True) # 是否启用 + sort_order = db.Column(db.Integer, default=0) # 排序 + + # 备注 + description = db.Column(db.String(255), nullable=True) # 备注 + + # 时间 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def to_dict(self): + return { + 'id': self.id, + 'provider_name': self.provider_name, + 'api_base': self.api_base, + 'api_key': self.api_key, + 'model': self.model, + 'is_active': self.is_active, + 'sort_order': self.sort_order, + 'description': self.description, + 'created_at': self.created_at.isoformat() if self.created_at else None, + } + + # ==================== 邮件模板配置 ==================== class EmailTemplateConfig(db.Model): """邮件模板配置""" diff --git a/templates/admin/backup_llm.html b/templates/admin/backup_llm.html new file mode 100644 index 0000000..33fcde8 --- /dev/null +++ b/templates/admin/backup_llm.html @@ -0,0 +1,173 @@ + + + + + 备用大模型接口 - 后台管理 + + + + + + + + +
+
+
备用大模型接口
+
+ 新增接口 + +
+
+ +
+
+ + + + + + + + + + + + + {% for config in configs %} + + + + + + + + + {% endfor %} + +
#服务商API地址模型状态操作
{{ config.sort_order }} + {{ config.provider_name }} + {% if config.description %} +
{{ config.description }} + {% endif %} +
{{ config.api_base }}{{ config.model or '默认' }} + {% if config.is_active %} + 启用 + {% else %} + 禁用 + {% endif %} + + + + + + + +
+ + {% if not configs %} +
+ +

暂无备用大模型配置

+ +
+ {% endif %} +
+
+ + +
+ + + + \ No newline at end of file diff --git a/templates/admin/backup_llm_form.html b/templates/admin/backup_llm_form.html new file mode 100644 index 0000000..fe00c0c --- /dev/null +++ b/templates/admin/backup_llm_form.html @@ -0,0 +1,188 @@ + + + + + {{ config ? '编辑' : '添加' }}备用大模型接口 - 后台管理 + + + + + + + + +
+
+
+
{{ config ? '编辑备用大模型接口' : '添加备用大模型接口' }}
+
+
+
+
+ + + 显示名称,方便识别 +
+ +
+ + + LLM服务的API endpoint +
+ +
+ + + 如果不需要可留空 +
+ +
+ + + 推荐使用的模型ID +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + + 返回 +
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/llm_config.html b/templates/admin/llm_config.html index 98256ac..b7c5ce1 100644 --- a/templates/admin/llm_config.html +++ b/templates/admin/llm_config.html @@ -90,20 +90,17 @@
-
常用模型配置参考
+
+
备用大模型接口
+ + 管理备用接口 + +
- - - - - - - - - - - -
服务商API地址模型示例
本地LM Studiohttp://localhost:1234/v1根据加载的模型
OpenAIhttps://api.openai.com/v1gpt-4, gpt-3.5-turbo
DeepSeekhttps://api.deepseek.com/v1deepseek-chat
阿里百炼https://dashscope.aliyuncs.com/compatible-mode/v1qwen-turbo, qwen-plus
SiliconFlowhttps://api.siliconflow.cn/v1Qwen/Qwen2.5-72B-Instruct
+

备用大模型接口用于主接口不可用时切换备用服务。支持手动新增、编辑、测试连接。

+ + 查看所有备用接口 +