369 lines
11 KiB
Python
369 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.config import load_api_config, save_api_config
|
||
from app.routes.auth import login_required_json, admin_required
|
||
|
||
|
||
@main_bp.route("/settings")
|
||
@login_required_json
|
||
def settings():
|
||
"""问题配置页面 - 所有登录用户可访问"""
|
||
return render_template("settings.html")
|
||
|
||
|
||
@main_bp.route("/api-settings")
|
||
@admin_required
|
||
def api_settings_page():
|
||
"""API设置页面 - 仅管理员"""
|
||
return render_template("api_settings.html")
|
||
|
||
|
||
# ==================== 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", methods=["POST"])
|
||
@admin_required
|
||
def update_api_config():
|
||
"""更新API配置"""
|
||
data = request.get_json()
|
||
|
||
# 验证必填字段
|
||
if not data.get("api_key"):
|
||
return jsonify({"error": "API Key不能为空"}), 400
|
||
|
||
# 根据provider设置默认endpoint
|
||
provider = data.get("provider", "volcengine")
|
||
default_endpoints = {
|
||
"minimax": "https://api.minimaxi.com/anthropic/v1",
|
||
"volcengine": "https://ark.cn-beijing.volces.com/api/coding/v3",
|
||
"deepseek": "https://api.deepseek.com/v1",
|
||
"openrouter": "https://openrouter.ai/api/v1",
|
||
}
|
||
default_endpoint = default_endpoints.get(provider, "https://ark.cn-beijing.volces.com/api/coding/v3")
|
||
|
||
# 保存配置
|
||
config = {
|
||
"provider": provider,
|
||
"api_key": data.get("api_key", ""),
|
||
"base_url": data.get("base_url", default_endpoint),
|
||
"model": data.get("model", "doubao-seed-2.0-pro"),
|
||
"temperature": float(data.get("temperature", 0.7)),
|
||
"prompt_template": data.get("prompt_template", ""),
|
||
}
|
||
|
||
save_api_config(config, current_app.config)
|
||
return jsonify({"message": "配置保存成功"})
|
||
|
||
|
||
@main_bp.route("/api/problems", methods=["GET"])
|
||
@login_required_json
|
||
def get_problems():
|
||
"""获取问题列表(从文件夹动态读取)"""
|
||
problems_dir = current_app.config.get("PROBLEMS_DIR")
|
||
|
||
problems = []
|
||
if problems_dir and os.path.exists(problems_dir):
|
||
for filename in sorted(os.listdir(problems_dir)):
|
||
if (
|
||
filename.endswith(".md")
|
||
and not filename.startswith("模板")
|
||
and not filename.startswith("针对性练习建议")
|
||
):
|
||
name = filename.replace(".md", "")
|
||
if "汇总" in name:
|
||
continue
|
||
|
||
parts = name.split("_", 1)
|
||
if len(parts) == 2:
|
||
problem_id = parts[0]
|
||
problem_name = parts[1]
|
||
|
||
category = "技术类"
|
||
filepath = os.path.join(problems_dir, filename)
|
||
try:
|
||
with open(filepath, "r", encoding="utf-8") as f:
|
||
content = f.read()
|
||
if "认知类" in content:
|
||
category = "认知类"
|
||
elif "节奏类" in content:
|
||
category = "节奏类"
|
||
elif "表现类" in content:
|
||
category = "表现类"
|
||
elif "习惯类" in content:
|
||
category = "习惯类"
|
||
elif "综合类" in content:
|
||
category = "综合类"
|
||
except:
|
||
pass
|
||
|
||
try:
|
||
mtime = os.path.getmtime(filepath)
|
||
except:
|
||
mtime = 0
|
||
|
||
problems.append(
|
||
{
|
||
"id": problem_id,
|
||
"name": problem_name,
|
||
"category": category,
|
||
"file": filename,
|
||
"mtime": mtime,
|
||
}
|
||
)
|
||
|
||
return jsonify(sorted(problems, key=lambda x: x["id"]))
|
||
|
||
|
||
@main_bp.route("/api/problems/<problem_id>", methods=["GET"])
|
||
@login_required_json
|
||
def get_problem_detail(problem_id):
|
||
"""获取单个问题的详细信息"""
|
||
problems_dir = current_app.config.get("PROBLEMS_DIR")
|
||
|
||
if problems_dir and os.path.exists(problems_dir):
|
||
for filename in os.listdir(problems_dir):
|
||
if filename.startswith(f"{problem_id}_") and filename.endswith(".md"):
|
||
filepath = os.path.join(problems_dir, filename)
|
||
with open(filepath, "r", encoding="utf-8") as f:
|
||
content = f.read()
|
||
return jsonify(
|
||
{"id": problem_id, "filename": filename, "content": content}
|
||
)
|
||
|
||
return jsonify({"error": "问题不存在"}), 404
|
||
|
||
|
||
@main_bp.route("/api/problems", methods=["POST"])
|
||
@admin_required
|
||
def create_problem():
|
||
"""创建新问题 - 仅管理员"""
|
||
"""创建新问题"""
|
||
data = request.get_json()
|
||
problem_id = data.get("id", "").strip()
|
||
problem_name = data.get("name", "").strip()
|
||
category = data.get("category", "技术类")
|
||
|
||
if not problem_id or not problem_name:
|
||
return jsonify({"error": "ID和名称不能为空"}), 400
|
||
|
||
# 格式化ID
|
||
problem_id = problem_id.zfill(2)
|
||
|
||
problems_dir = current_app.config.get("PROBLEMS_DIR")
|
||
filename = f"{problem_id}_{problem_name}.md"
|
||
filepath = os.path.join(problems_dir, filename)
|
||
|
||
if os.path.exists(filepath):
|
||
return jsonify({"error": "问题已存在"}), 400
|
||
|
||
# 生成默认内容
|
||
content = f"""# {problem_name}
|
||
|
||
> 所属系列:钢琴学习常见问题针对性练习建议
|
||
> 配合目标体系使用,针对性补齐短板
|
||
|
||
---
|
||
|
||
## 问题表现
|
||
|
||
- 请描述具体表现症状
|
||
|
||
## 原因分析
|
||
|
||
- 可能的原因1
|
||
- 可能的原因2
|
||
|
||
## 针对性练习方案
|
||
|
||
### 日常基础练习
|
||
|
||
| 练习名称 | 时长 | 频率 | 目的 |
|
||
|---------|------|------|------|
|
||
| 练习1 | 10分钟 | 每天 | 目的1 |
|
||
| 练习2 | 5分钟 | 每天 | 目的2 |
|
||
|
||
### 具体操作
|
||
|
||
```
|
||
练习1:练习名称
|
||
- 步骤1
|
||
- 步骤2
|
||
- 步骤3
|
||
```
|
||
|
||
## 练习提醒
|
||
|
||
### ⚠️ 禁忌
|
||
|
||
- 禁忌1
|
||
- 禁忌2
|
||
|
||
### ✓ 正确做法
|
||
|
||
- 正确做法1
|
||
- 正确做法2
|
||
|
||
## 评估标准
|
||
|
||
| 等级 | 标准 |
|
||
|------|------|
|
||
| 入门 | 达到的标准 |
|
||
| 进阶 | 达到的标准 |
|
||
| 熟练 | 达到的标准 |
|
||
| 精通 | 达到的标准 |
|
||
|
||
---
|
||
|
||
> **版本**:V1.0
|
||
> **创建时间**:{datetime.now().strftime("%Y-%m-%d")}
|
||
> **适用场景**:成人钢琴集体课学员个性化辅导"""
|
||
|
||
with open(filepath, "w", encoding="utf-8") as f:
|
||
f.write(content)
|
||
|
||
return jsonify({"message": "创建成功", "id": problem_id, "name": problem_name})
|
||
|
||
|
||
@main_bp.route("/api/problems/<problem_id>", methods=["PUT"])
|
||
@login_required_json
|
||
def update_problem(problem_id):
|
||
"""更新问题"""
|
||
data = request.get_json()
|
||
new_name = data.get("name", "").strip()
|
||
new_content = data.get("content", "").strip()
|
||
|
||
if not new_name:
|
||
return jsonify({"error": "名称不能为空"}), 400
|
||
|
||
problems_dir = current_app.config.get("PROBLEMS_DIR")
|
||
|
||
# 找到旧文件
|
||
old_filename = None
|
||
for f in os.listdir(problems_dir):
|
||
if f.startswith(f"{problem_id}_") and f.endswith(".md"):
|
||
old_filename = f
|
||
break
|
||
|
||
if not old_filename:
|
||
return jsonify({"error": "问题不存在"}), 404
|
||
|
||
old_filepath = os.path.join(problems_dir, old_filename)
|
||
new_filename = f"{problem_id}_{new_name}.md"
|
||
new_filepath = os.path.join(problems_dir, new_filename)
|
||
|
||
# 如果名称改变,需要重命名文件
|
||
if old_filename != new_filename:
|
||
if os.path.exists(new_filepath):
|
||
return jsonify({"error": "同名问题已存在"}), 400
|
||
os.rename(old_filepath, new_filepath)
|
||
filepath = new_filepath
|
||
else:
|
||
filepath = old_filepath
|
||
|
||
# 更新内容
|
||
with open(filepath, "w", encoding="utf-8") as f:
|
||
f.write(new_content)
|
||
|
||
return jsonify({"message": "更新成功"})
|
||
|
||
|
||
@main_bp.route("/api/problems/<problem_id>", methods=["DELETE"])
|
||
@admin_required
|
||
def delete_problem(problem_id):
|
||
"""删除问题"""
|
||
problems_dir = current_app.config.get("PROBLEMS_DIR")
|
||
|
||
# 找到文件
|
||
filename = None
|
||
for f in os.listdir(problems_dir):
|
||
if f.startswith(f"{problem_id}_") and f.endswith(".md"):
|
||
filename = f
|
||
break
|
||
|
||
if not filename:
|
||
return jsonify({"error": "问题不存在"}), 404
|
||
|
||
filepath = os.path.join(problems_dir, filename)
|
||
|
||
# 移动到备份目录
|
||
trash_dir = os.path.join(problems_dir, "bk")
|
||
os.makedirs(trash_dir, exist_ok=True)
|
||
|
||
import time
|
||
|
||
backup_name = f"{filename.replace('.md', '')}_{int(time.time())}.md"
|
||
shutil.move(filepath, os.path.join(trash_dir, backup_name))
|
||
|
||
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)})
|