340 lines
11 KiB
Python
340 lines
11 KiB
Python
# 问题配置路由 - 完整CRUD
|
||
|
||
import os
|
||
import shutil
|
||
from datetime import datetime
|
||
from flask import request, jsonify, render_template, current_app, session, redirect
|
||
from app.routes import main_bp
|
||
from app.models import db, Problem, StudentProblem
|
||
from app.config import load_api_config, save_api_config, DEFAULT_PROVIDERS
|
||
from app.routes.auth import login_required_json, admin_required
|
||
|
||
|
||
@main_bp.route("/problems")
|
||
@login_required_json
|
||
def settings():
|
||
"""问题配置页面 - 所有登录用户可访问"""
|
||
return render_template("problems.html", active_nav="problems")
|
||
|
||
|
||
@main_bp.route("/api-settings")
|
||
@admin_required
|
||
def api_settings_page():
|
||
"""API设置页面 - 仅管理员"""
|
||
return render_template("api_settings.html", active_nav="api-settings")
|
||
|
||
|
||
# ==================== API配置接口 ====================
|
||
|
||
|
||
@main_bp.route("/api/config", methods=["GET"])
|
||
@admin_required
|
||
def get_api_config():
|
||
"""获取API配置"""
|
||
config = load_api_config(current_app.config)
|
||
# 不返回完整的api_key,只返回前5位和后5位
|
||
if config.get("api_key"):
|
||
api_key = config["api_key"]
|
||
if len(api_key) > 10:
|
||
config["api_key_preview"] = api_key[:5] + "..." + api_key[-5:]
|
||
else:
|
||
config["api_key_preview"] = "****"
|
||
return jsonify(config)
|
||
|
||
|
||
@main_bp.route("/api/config/providers", methods=["GET"])
|
||
@admin_required
|
||
def list_providers():
|
||
"""列出所有API提供商"""
|
||
config = load_api_config(current_app.config)
|
||
providers = config.get("providers", {})
|
||
result = {}
|
||
for pid, pdata in providers.items():
|
||
result[pid] = {
|
||
"name": pdata.get("name", pid),
|
||
"endpoint": pdata.get("endpoint", ""),
|
||
"models": pdata.get("models", []),
|
||
}
|
||
return jsonify(result)
|
||
|
||
|
||
@main_bp.route("/api/config/providers", methods=["POST"])
|
||
@admin_required
|
||
def add_provider():
|
||
"""新增API提供商"""
|
||
data = request.get_json()
|
||
provider_id = data.get("id", "").strip().lower()
|
||
name = data.get("name", "").strip()
|
||
endpoint = data.get("endpoint", "").strip()
|
||
models = data.get("models", [])
|
||
|
||
if not isinstance(models, list):
|
||
models = [m.strip() for m in str(models).split(",") if m.strip()]
|
||
|
||
if not provider_id or not name:
|
||
return jsonify({"error": "提供商ID和名称不能为空"}), 400
|
||
if not endpoint:
|
||
return jsonify({"error": "Endpoint不能为空"}), 400
|
||
|
||
config = load_api_config(current_app.config)
|
||
providers = config.get("providers", {})
|
||
|
||
if provider_id in providers:
|
||
return jsonify({"error": f"提供商 '{provider_id}' 已存在"}), 400
|
||
|
||
providers[provider_id] = {
|
||
"name": name,
|
||
"endpoint": endpoint,
|
||
"models": models,
|
||
}
|
||
config["providers"] = providers
|
||
save_api_config(config, current_app.config)
|
||
return jsonify({"message": "添加成功", "id": provider_id})
|
||
|
||
|
||
@main_bp.route("/api/config/providers/<provider_id>", methods=["DELETE"])
|
||
@admin_required
|
||
def delete_provider(provider_id):
|
||
"""删除API提供商"""
|
||
provider_id = provider_id.strip().lower()
|
||
config = load_api_config(current_app.config)
|
||
providers = config.get("providers", {})
|
||
|
||
if provider_id not in providers:
|
||
return jsonify({"error": f"提供商 '{provider_id}' 不存在"}), 404
|
||
|
||
# 不允许删除当前选中的 provider
|
||
if provider_id == config.get("provider"):
|
||
return jsonify({"error": "不能删除当前正在使用的提供商,请先切换到其他提供商"}), 400
|
||
|
||
# 不允许删除到只剩 0 个
|
||
if len(providers) <= 1:
|
||
return jsonify({"error": "至少保留一个提供商"}), 400
|
||
|
||
del providers[provider_id]
|
||
|
||
# 同时删除 api_keys 中对应的 key
|
||
if "api_keys" in config and provider_id in config["api_keys"]:
|
||
del config["api_keys"][provider_id]
|
||
|
||
config["providers"] = providers
|
||
save_api_config(config, current_app.config)
|
||
return jsonify({"message": "删除成功"})
|
||
|
||
|
||
@main_bp.route("/api/config/providers/<provider_id>", methods=["PUT"])
|
||
@admin_required
|
||
def update_provider(provider_id):
|
||
"""更新API提供商(名称、endpoint、模型列表)"""
|
||
provider_id = provider_id.strip().lower()
|
||
data = request.get_json()
|
||
config = load_api_config(current_app.config)
|
||
providers = config.get("providers", {})
|
||
|
||
if provider_id not in providers:
|
||
return jsonify({"error": f"提供商 '{provider_id}' 不存在"}), 404
|
||
|
||
pdata = providers[provider_id]
|
||
if "name" in data:
|
||
pdata["name"] = data["name"].strip()
|
||
if "endpoint" in data:
|
||
pdata["endpoint"] = data["endpoint"].strip()
|
||
if "models" in data:
|
||
if isinstance(data["models"], list):
|
||
pdata["models"] = [m.strip() for m in data["models"] if m.strip()]
|
||
else:
|
||
pdata["models"] = [m.strip() for m in str(data["models"]).split(",") if m.strip()]
|
||
|
||
config["providers"] = providers
|
||
save_api_config(config, current_app.config)
|
||
return jsonify({"message": "更新成功"})
|
||
|
||
|
||
@main_bp.route("/api/config", methods=["POST"])
|
||
@admin_required
|
||
def update_api_config():
|
||
"""更新API配置"""
|
||
data = request.get_json()
|
||
|
||
config = load_api_config(current_app.config)
|
||
provider = data.get("provider", config.get("provider", "volcengine"))
|
||
|
||
# api_key 留空时,尝试使用已有 key(当前 provider 的 key)
|
||
api_key = data.get("api_key", "")
|
||
if not api_key:
|
||
existing_key = config.get("api_keys", {}).get(provider, "")
|
||
if not existing_key:
|
||
return jsonify({"error": "API Key不能为空"}), 400
|
||
api_key = existing_key
|
||
providers = config.get("providers", DEFAULT_PROVIDERS)
|
||
|
||
# 从 providers 列表获取默认 endpoint 和 model
|
||
if provider in providers:
|
||
default_endpoint = providers[provider].get("endpoint", "")
|
||
models = providers[provider].get("models", [])
|
||
default_model = models[0] if models else ""
|
||
else:
|
||
default_endpoint = ""
|
||
default_model = ""
|
||
|
||
new_config = {
|
||
"provider": provider,
|
||
"api_key": api_key,
|
||
"base_url": data.get("base_url", default_endpoint),
|
||
"model": data.get("model", default_model),
|
||
"temperature": float(data.get("temperature", 0.7)),
|
||
"prompt_template": data.get("prompt_template", ""),
|
||
"watermark_text": data.get("watermark_text", ""),
|
||
}
|
||
|
||
save_api_config(new_config, current_app.config)
|
||
return jsonify({"message": "配置保存成功"})
|
||
|
||
|
||
@main_bp.route("/api/problems", methods=["GET"])
|
||
@login_required_json
|
||
def get_problems():
|
||
"""获取问题列表(从数据库读取)"""
|
||
from app.models import Problem
|
||
|
||
problems = Problem.query.order_by(Problem.no).all()
|
||
return jsonify([p.to_dict() for p in problems])
|
||
|
||
|
||
@main_bp.route("/api/problems/<int:problem_id>", methods=["GET"])
|
||
@login_required_json
|
||
def get_problem_detail(problem_id):
|
||
"""获取单个问题的详细信息"""
|
||
from app.models import Problem
|
||
|
||
problem = Problem.query.get(problem_id)
|
||
if not problem:
|
||
return jsonify({"error": "问题不存在"}), 404
|
||
|
||
return jsonify({
|
||
"id": problem.id,
|
||
"no": problem.no,
|
||
"name": problem.name,
|
||
"category": problem.category,
|
||
"content": problem.content,
|
||
})
|
||
|
||
|
||
@main_bp.route("/api/problems", methods=["POST"])
|
||
@admin_required
|
||
def create_problem():
|
||
"""创建新问题 - 仅管理员"""
|
||
from app.models import Problem
|
||
|
||
data = request.get_json()
|
||
no = data.get("no", "").strip()
|
||
name = data.get("name", "").strip()
|
||
category = data.get("category", "技术类")
|
||
content = data.get("content", "")
|
||
|
||
if not no or not name:
|
||
return jsonify({"error": "编号和名称不能为空"}), 400
|
||
|
||
# 检查编号是否已存在
|
||
existing = Problem.query.filter_by(no=no).first()
|
||
if existing:
|
||
return jsonify({"error": "该编号已存在"}), 400
|
||
|
||
# 创建问题
|
||
problem = Problem(no=no, name=name, category=category, content=content)
|
||
db.session.add(problem)
|
||
db.session.commit()
|
||
|
||
return jsonify({"message": "创建成功", "id": problem.id, "no": problem.no, "name": problem.name})
|
||
|
||
|
||
@main_bp.route("/api/problems/<int:problem_id>", methods=["PUT"])
|
||
@login_required_json
|
||
def update_problem(problem_id):
|
||
"""更新问题"""
|
||
from app.models import Problem
|
||
|
||
problem = Problem.query.get(problem_id)
|
||
if not problem:
|
||
return jsonify({"error": "问题不存在"}), 404
|
||
|
||
data = request.get_json()
|
||
if "name" in data:
|
||
problem.name = data["name"].strip()
|
||
if "category" in data:
|
||
problem.category = data["category"]
|
||
if "content" in data:
|
||
problem.content = data["content"]
|
||
|
||
db.session.commit()
|
||
return jsonify({"message": "更新成功"})
|
||
|
||
|
||
@main_bp.route("/api/problems/<int:problem_id>", methods=["DELETE"])
|
||
@admin_required
|
||
def delete_problem(problem_id):
|
||
"""删除问题"""
|
||
from app.models import Problem
|
||
|
||
problem = Problem.query.get(problem_id)
|
||
if not problem:
|
||
return jsonify({"error": "问题不存在"}), 404
|
||
|
||
# 检查是否有关联数据
|
||
from app.models import StudentProblem
|
||
if StudentProblem.query.filter_by(problem_id=problem_id).first():
|
||
return jsonify({"error": "该问题已被学员使用,无法删除"}), 400
|
||
|
||
db.session.delete(problem)
|
||
db.session.commit()
|
||
|
||
return jsonify({"message": "删除成功"})
|
||
|
||
|
||
# ==================== AI测试接口 ====================
|
||
|
||
|
||
@main_bp.route("/api/config/test", methods=["POST"])
|
||
@admin_required
|
||
def test_api_connection():
|
||
"""测试API连接"""
|
||
import requests
|
||
|
||
config = load_api_config(current_app.config)
|
||
provider = config.get("provider", "volcengine")
|
||
|
||
headers = {
|
||
"Authorization": f"Bearer {config.get('api_key', '')}",
|
||
"Content-Type": "application/json",
|
||
}
|
||
|
||
payload = {
|
||
"model": config.get("model", "doubao-seed-2.0-pro"),
|
||
"messages": [{"role": "user", "content": "你好"}],
|
||
"max_tokens": 50,
|
||
}
|
||
|
||
try:
|
||
# MiniMax 使用 Anthropic 格式的 API
|
||
if provider == "minimax":
|
||
endpoint = f"{config.get('base_url')}/messages"
|
||
payload["max_tokens"] = 50
|
||
else:
|
||
endpoint = f"{config.get('base_url')}/chat/completions"
|
||
|
||
response = requests.post(
|
||
endpoint,
|
||
headers=headers,
|
||
json=payload,
|
||
timeout=30,
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
return jsonify({"success": True, "message": "连接成功"})
|
||
else:
|
||
return jsonify(
|
||
{"success": False, "error": f"API返回错误: {response.status_code}"}
|
||
)
|
||
except Exception as e:
|
||
return jsonify({"success": False, "error": str(e)})
|