Files
piano-plan/app/routes/settings.py
T

369 lines
11 KiB
Python
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.
# 问题配置路由 - 完整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)})