更新:models/routes/services/templates/docs
This commit is contained in:
+130
-42
@@ -13,7 +13,7 @@ from flask import (
|
||||
session,
|
||||
)
|
||||
from app.routes import main_bp
|
||||
from app.models import db, Student, PracticePlan, StudentProblem
|
||||
from app.models import db, Student, PracticePlan, StudentProblem, StudentGoal
|
||||
from app.services.plan_generator import generate_practice_plan, generate_ai_report
|
||||
from app.services.pdf_generator import generate_pdf
|
||||
from app.routes.auth import login_required_json, admin_required
|
||||
@@ -44,8 +44,10 @@ def get_all_plans():
|
||||
- template_id: 模板ID
|
||||
- is_typical: 是否典型 (true/false)
|
||||
- student_name: 学员姓名(模糊匹配)
|
||||
- mine: true/false(我的学员的方案)
|
||||
"""
|
||||
import json as json_module
|
||||
from app.models import Class
|
||||
|
||||
query = PracticePlan.query
|
||||
|
||||
@@ -84,6 +86,13 @@ def get_all_plans():
|
||||
)
|
||||
)
|
||||
|
||||
# 我的学员筛选(所在班级的老师是当前用户)
|
||||
mine = request.args.get('mine')
|
||||
if mine and mine.lower() == 'true':
|
||||
user_id = session.get('user_id')
|
||||
if user_id:
|
||||
query = query.join(Student).join(Class).filter(Class.teacher_id == user_id)
|
||||
|
||||
plans = query.order_by(PracticePlan.created_at.desc()).all()
|
||||
return jsonify([p.to_dict() for p in plans])
|
||||
|
||||
@@ -153,6 +162,10 @@ def generate_plan():
|
||||
student = Student.query.get_or_404(student_id)
|
||||
problems = student.problems.all()
|
||||
|
||||
# 获取学员的目标(只获取未完成的)
|
||||
goals = StudentGoal.query.filter_by(student_id=student_id).all()
|
||||
goal_data = [g.to_dict() for g in goals]
|
||||
|
||||
if not problems:
|
||||
return jsonify({"error": "请先记录学员的问题"}), 400
|
||||
|
||||
@@ -207,6 +220,7 @@ def generate_plan():
|
||||
student_name=student.name,
|
||||
problems=problem_data,
|
||||
practice_time=practice_time,
|
||||
goals=goal_data,
|
||||
)
|
||||
|
||||
yield sse_format(
|
||||
@@ -254,22 +268,27 @@ def generate_plan():
|
||||
)
|
||||
|
||||
# 先用dry_run模式获取提示词并显示给用户
|
||||
prompt, _, error = generate_ai_report(
|
||||
prompt, _, error, extra_info = generate_ai_report(
|
||||
student_name=student.name,
|
||||
wechat_nickname=student.wechat_nickname or "",
|
||||
problems=problem_data,
|
||||
practice_time=practice_time,
|
||||
time_config=time_config,
|
||||
template_id=template_id,
|
||||
dry_run=True
|
||||
dry_run=True,
|
||||
goals=goal_data
|
||||
)
|
||||
|
||||
# 发送提示词给前端显示
|
||||
# 发送提示词给前端显示(只发长度,不发完整内容避免SSE缓冲问题)
|
||||
prompt_length = len(prompt) if prompt else 0
|
||||
yield sse_format({
|
||||
"step": "ai_prompt",
|
||||
"message": "发送给AI的提示词:",
|
||||
"message": "AI提示词已生成",
|
||||
"progress": 60,
|
||||
"detail": prompt if prompt else "无"
|
||||
"prompt_length": prompt_length,
|
||||
"student_problems_length": extra_info.get("student_problems_length", 0) if extra_info else 0,
|
||||
"problems_length": extra_info.get("problems_length", 0) if extra_info else 0,
|
||||
"student_goals_length": extra_info.get("student_goals_length", 0) if extra_info else 0,
|
||||
})
|
||||
|
||||
if error:
|
||||
@@ -288,14 +307,15 @@ def generate_plan():
|
||||
)
|
||||
|
||||
# 真正调用API生成报告
|
||||
_, ai_report, error = generate_ai_report(
|
||||
_, ai_report, error, _ = generate_ai_report(
|
||||
student_name=student.name,
|
||||
wechat_nickname=student.wechat_nickname or "",
|
||||
problems=problem_data,
|
||||
practice_time=practice_time,
|
||||
time_config=time_config,
|
||||
template_id=template_id,
|
||||
dry_run=False
|
||||
dry_run=False,
|
||||
goals=goal_data
|
||||
)
|
||||
|
||||
if error:
|
||||
@@ -375,6 +395,8 @@ def generate_plan():
|
||||
"plan_id": plan.id if plan and hasattr(plan, 'id') else None,
|
||||
"content": plan_content,
|
||||
"ai_report": ai_report,
|
||||
"prompt_length": prompt_length,
|
||||
"ai_report_length": len(ai_report) if ai_report else 0,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -411,6 +433,7 @@ def get_plan(plan_id):
|
||||
"student_id": plan.student_id,
|
||||
"student_name": plan.student.name if plan.student else "",
|
||||
"created_at": plan.created_at.strftime("%Y-%m-%d %H:%M"),
|
||||
"is_typical": plan.is_typical,
|
||||
"content": content,
|
||||
}
|
||||
)
|
||||
@@ -423,14 +446,20 @@ def export_pdf(plan_id):
|
||||
plan = PracticePlan.query.get_or_404(plan_id)
|
||||
content = json.loads(plan.content)
|
||||
student_name = plan.student.name if plan.student else "未知学员"
|
||||
template_id = request.args.get('template_id', type=int)
|
||||
|
||||
# 尝试使用数据库中的报告模板
|
||||
# 获取报告模板
|
||||
report_template = None
|
||||
try:
|
||||
from app.models import Template
|
||||
tmpl = Template.query.filter_by(type="report").first()
|
||||
if tmpl:
|
||||
report_template = tmpl.content
|
||||
if template_id:
|
||||
tmpl = Template.query.get(template_id)
|
||||
if tmpl and tmpl.type == "report":
|
||||
report_template = tmpl.content
|
||||
else:
|
||||
tmpl = Template.query.filter_by(type="report").order_by(Template.sort_order.asc()).first()
|
||||
if tmpl:
|
||||
report_template = tmpl.content
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -442,6 +471,15 @@ def export_pdf(plan_id):
|
||||
rendered_report = rendered_report.replace("{practice_time}", content.get('practice_time', 'N/A'))
|
||||
rendered_report = rendered_report.replace("{total_minutes}", str(content.get('total_daily_minutes', 0)))
|
||||
rendered_report = rendered_report.replace("{generated_at}", content.get('generated_at', ''))
|
||||
# 获取当前用户姓名
|
||||
from app.models import User
|
||||
user_id = session.get('user_id')
|
||||
user_name = '未知'
|
||||
if user_id:
|
||||
user = User.query.get(user_id)
|
||||
if user and user.name:
|
||||
user_name = user.name
|
||||
rendered_report = rendered_report.replace("{generated_by}", user_name)
|
||||
|
||||
if content.get('ai_report'):
|
||||
rendered_report = rendered_report.replace("{ai_report}", content['ai_report'])
|
||||
@@ -453,11 +491,6 @@ def export_pdf(plan_id):
|
||||
problem_tags += f"- **{problem.get('name', '')}** ({problem.get('severity', '')})\n"
|
||||
rendered_report = rendered_report.replace("{problem_tags}", problem_tags or "(无)")
|
||||
|
||||
schedule_table = "| 阶段 | 时长 | 内容 | 目的 |\n|------|------|------|------|\n"
|
||||
for item in content.get('daily_schedule', []):
|
||||
schedule_table += f"| {item.get('phase', '')} | {item.get('duration', '')} | {item.get('content', '')} | {item.get('purpose', '')} |\n"
|
||||
rendered_report = rendered_report.replace("{schedule_table}", schedule_table)
|
||||
|
||||
pdf_path = generate_pdf(
|
||||
plan_id=plan_id,
|
||||
student_name=student_name,
|
||||
@@ -504,6 +537,16 @@ def export_md(plan_id):
|
||||
rendered = rendered.replace("{total_minutes}", str(content.get('total_daily_minutes', 0)))
|
||||
rendered = rendered.replace("{generated_at}", content.get('generated_at', ''))
|
||||
|
||||
# 获取当前用户姓名
|
||||
from app.models import User
|
||||
user_id = session.get('user_id')
|
||||
user_name = '未知'
|
||||
if user_id:
|
||||
user = User.query.get(user_id)
|
||||
if user and user.name:
|
||||
user_name = user.name
|
||||
rendered = rendered.replace("{generated_by}", user_name)
|
||||
|
||||
# AI报告
|
||||
if content.get('ai_report'):
|
||||
rendered = rendered.replace("{ai_report}", content['ai_report'])
|
||||
@@ -516,15 +559,9 @@ def export_md(plan_id):
|
||||
problem_tags += f"- **{problem.get('name', '')}** ({problem.get('severity', '')})\n"
|
||||
rendered = rendered.replace("{problem_tags}", problem_tags or "(无)")
|
||||
|
||||
# 每日计划表格
|
||||
schedule_table = "| 阶段 | 时长 | 内容 | 目的 |\n|------|------|------|------|\n"
|
||||
for item in content.get('daily_schedule', []):
|
||||
schedule_table += f"| {item.get('phase', '')} | {item.get('duration', '')} | {item.get('content', '')} | {item.get('purpose', '')} |\n"
|
||||
rendered = rendered.replace("{schedule_table}", schedule_table)
|
||||
|
||||
md_content = rendered
|
||||
else:
|
||||
# 兜底:生成完整内容
|
||||
# 兜底:生成完整内容(AI报告已包含所有内容)
|
||||
md_lines = [
|
||||
f"# {student_name} - 个性化练习方案\n",
|
||||
f"**练习时间**: {content.get('practice_time', 'N/A')} (共{content.get('total_daily_minutes', 0)}分钟)\n",
|
||||
@@ -533,25 +570,7 @@ def export_md(plan_id):
|
||||
]
|
||||
|
||||
if content.get('ai_report'):
|
||||
md_lines.append("## 📝 AI个性化报告\n")
|
||||
md_lines.append(content['ai_report'])
|
||||
md_lines.append("\n---\n")
|
||||
|
||||
md_lines.append("## 🔍 问题诊断\n")
|
||||
for problem in content.get('problems', []):
|
||||
md_lines.append(f"- **{problem.get('name', '')}** ({problem.get('severity', '')})\n")
|
||||
md_lines.append("\n")
|
||||
|
||||
if content.get('problem_details'):
|
||||
md_lines.append("## 📚 问题详解\n")
|
||||
for detail in content.get('problem_details', []):
|
||||
md_lines.append(f"### {detail.get('name', '')} ({detail.get('severity', '')})\n")
|
||||
md_lines.append(f"{detail.get('content', '')}\n\n")
|
||||
|
||||
md_lines.append("## 📅 每日练习计划\n")
|
||||
md_lines.append("| 阶段 | 时长 | 内容 | 目的 |\n|------|------|------|------|\n")
|
||||
for item in content.get('daily_schedule', []):
|
||||
md_lines.append(f"| {item.get('phase', '')} | {item.get('duration', '')} | {item.get('content', '')} | {item.get('purpose', '')} |\n")
|
||||
|
||||
md_content = ''.join(md_lines)
|
||||
|
||||
@@ -602,3 +621,72 @@ def update_plan_content(plan_id):
|
||||
plan.content = data["content"]
|
||||
db.session.commit()
|
||||
return jsonify({"message": "保存成功"})
|
||||
|
||||
|
||||
@main_bp.route("/api/generate-plan/preview", methods=["POST"])
|
||||
@login_required_json
|
||||
def generate_plan_preview():
|
||||
"""预览AI提示词 - 返回完整提示词内容"""
|
||||
data = request.get_json()
|
||||
student_id = data.get("student_id")
|
||||
template_id = data.get("template_id") # AI提示词模板ID
|
||||
|
||||
student = Student.query.get_or_404(student_id)
|
||||
problems = student.problems.all()
|
||||
|
||||
if not problems:
|
||||
return jsonify({"error": "请先记录学员的问题"}), 400
|
||||
|
||||
# 获取学员的目标
|
||||
goals = StudentGoal.query.filter_by(student_id=student_id).all()
|
||||
goal_data = [g.to_dict() for g in goals]
|
||||
|
||||
# 学员的统一练习时间
|
||||
practice_time = student.practice_time or "30-60分钟"
|
||||
|
||||
problem_data = []
|
||||
for p in problems:
|
||||
problem_obj = p.problem
|
||||
if problem_obj:
|
||||
problem_data.append(
|
||||
{
|
||||
"problem_id": problem_obj.id,
|
||||
"problem_name": problem_obj.name,
|
||||
"problem_no": problem_obj.no,
|
||||
"severity": p.severity,
|
||||
"level": p.level,
|
||||
"content": problem_obj.content or "",
|
||||
}
|
||||
)
|
||||
|
||||
time_mapping = {
|
||||
"15分钟": {"total": 15, "basic": 10, "tech": 2, "piece": 3},
|
||||
"30分钟": {"total": 30, "basic": 15, "tech": 5, "piece": 10},
|
||||
"45分钟": {"total": 45, "basic": 15, "tech": 10, "piece": 20},
|
||||
"60分钟": {"total": 60, "basic": 20, "tech": 15, "piece": 25},
|
||||
"90分钟": {"total": 90, "basic": 25, "tech": 25, "piece": 40},
|
||||
"120分钟": {"total": 120, "basic": 30, "tech": 35, "piece": 55},
|
||||
"150分钟以上": {"total": 150, "basic": 35, "tech": 45, "piece": 70},
|
||||
}
|
||||
time_config = time_mapping.get(practice_time, time_mapping["30分钟"])
|
||||
|
||||
# 调用生成器获取提示词(dry_run模式)
|
||||
from app.services.plan_generator import generate_ai_report
|
||||
prompt, _, _, extra_info = generate_ai_report(
|
||||
student_name=student.name,
|
||||
wechat_nickname=student.wechat_nickname or "",
|
||||
problems=problem_data,
|
||||
practice_time=practice_time,
|
||||
time_config=time_config,
|
||||
template_id=template_id,
|
||||
dry_run=True,
|
||||
goals=goal_data
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"prompt": prompt,
|
||||
"prompt_length": len(prompt) if prompt else 0,
|
||||
"student_problems_length": extra_info.get("student_problems_length", 0) if extra_info else 0,
|
||||
"problems_length": extra_info.get("problems_length", 0) if extra_info else 0,
|
||||
"student_goals_length": extra_info.get("student_goals_length", 0) if extra_info else 0,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user