2025-09-04 16:09:11 +08:00
|
|
|
|
import json
|
|
|
|
|
import logging
|
2025-09-04 17:27:42 +08:00
|
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
import subprocess
|
|
|
|
|
import tempfile
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from fastapi.responses import FileResponse
|
2025-09-04 16:09:11 +08:00
|
|
|
|
|
2025-09-04 17:27:42 +08:00
|
|
|
|
from fastapi import APIRouter, Request, HTTPException, Depends
|
2025-09-04 16:09:11 +08:00
|
|
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
|
|
|
|
|
|
from TeacherHelper.Kit.TeacherHelper import (
|
|
|
|
|
generate_lesson_plan,
|
|
|
|
|
generate_courseware,
|
|
|
|
|
generate_homework
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 创建路由器
|
|
|
|
|
router = APIRouter(prefix="/api", tags=["教师辅助工具"])
|
|
|
|
|
|
|
|
|
|
# 配置日志
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
2025-09-04 17:27:42 +08:00
|
|
|
|
@router.api_route("/teacher/daoXueAn", methods=["GET", "POST"]) # 修改这里
|
2025-09-04 16:09:11 +08:00
|
|
|
|
async def generate_dao_xue_an(request: Request):
|
|
|
|
|
"""生成导学案接口"""
|
|
|
|
|
try:
|
2025-09-04 17:27:42 +08:00
|
|
|
|
# 获取请求参数(兼容GET和POST)
|
|
|
|
|
if request.method == "GET":
|
|
|
|
|
params = dict(request.query_params)
|
|
|
|
|
else:
|
|
|
|
|
body = await request.body()
|
|
|
|
|
params = json.loads(body) if body else {}
|
|
|
|
|
|
|
|
|
|
# 获取提示词参数
|
|
|
|
|
system_prompt = params.get("system_prompt")
|
|
|
|
|
user_prompt = params.get("user_prompt")
|
2025-09-04 16:09:11 +08:00
|
|
|
|
|
|
|
|
|
# 生成导学案
|
|
|
|
|
content_generator = await generate_lesson_plan(system_prompt, user_prompt)
|
|
|
|
|
|
|
|
|
|
# 定义SSE响应生成器
|
|
|
|
|
async def generate_sse_response():
|
|
|
|
|
try:
|
2025-09-04 17:27:42 +08:00
|
|
|
|
buffer = [] # 用于收集完整内容
|
2025-09-04 16:09:11 +08:00
|
|
|
|
yield "data: 开始生成导学案...\n\n"
|
|
|
|
|
async for chunk in content_generator:
|
2025-09-04 17:27:42 +08:00
|
|
|
|
buffer.append(chunk) # 仅收集内容,不实时发送
|
|
|
|
|
|
|
|
|
|
# 保存为Markdown并转换为Word
|
|
|
|
|
if buffer:
|
|
|
|
|
# 创建保存目录 - 修改保存路径到static下
|
|
|
|
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
save_dir = os.path.join(project_root, "static", "teacherHelpergenerated_files")
|
|
|
|
|
os.makedirs(save_dir, exist_ok=True)
|
|
|
|
|
|
2025-09-05 07:52:23 +08:00
|
|
|
|
# 获取难度参数(默认为"默认难度")
|
|
|
|
|
difficulty = params.get("difficulty", "默认难度")
|
|
|
|
|
|
2025-09-05 08:09:44 +08:00
|
|
|
|
# 难度级别中英文映射
|
|
|
|
|
difficulty_map = {
|
|
|
|
|
"basic": "基础",
|
|
|
|
|
"intermediate": "提高",
|
|
|
|
|
"advanced": "进阶",
|
|
|
|
|
"默认难度": "默认难度"
|
|
|
|
|
}
|
|
|
|
|
# 转换为中文难度名称
|
|
|
|
|
chinese_difficulty = difficulty_map.get(difficulty, difficulty)
|
|
|
|
|
|
2025-09-04 17:27:42 +08:00
|
|
|
|
# 生成唯一文件名
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
|
|
|
|
|
|
|
|
# 创建临时Markdown文件
|
|
|
|
|
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', suffix='.md', delete=False) as temp_md:
|
|
|
|
|
temp_md.write(''.join(buffer))
|
|
|
|
|
temp_md_path = temp_md.name
|
|
|
|
|
|
|
|
|
|
# 使用Pandoc转换为Word
|
2025-09-05 08:09:44 +08:00
|
|
|
|
docx_filename = f"导学案_{chinese_difficulty}_{timestamp}.docx"
|
2025-09-04 17:27:42 +08:00
|
|
|
|
docx_path = os.path.join(save_dir, docx_filename)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
yield "data: 正在转换为Word文档...\n\n"
|
2025-09-05 07:05:22 +08:00
|
|
|
|
# 恢复Pandoc转换执行代码
|
2025-09-04 17:27:42 +08:00
|
|
|
|
result = subprocess.run(
|
|
|
|
|
["pandoc", temp_md_path, "-o", docx_path],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
check=True
|
|
|
|
|
)
|
|
|
|
|
# 清理临时文件
|
|
|
|
|
os.unlink(temp_md_path)
|
2025-09-05 07:05:22 +08:00
|
|
|
|
# 添加转换完成状态提示
|
|
|
|
|
yield "data: Word文档转换完成,准备下载...\n\n"
|
2025-09-04 17:27:42 +08:00
|
|
|
|
# 修改下载链接为静态文件路径
|
|
|
|
|
yield f"data: [下载链接] /static/teacherHelpergenerated_files/{docx_filename}\n\n"
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
yield f"data: [转换失败] Pandoc错误: {e.stderr}\n\n"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
yield f"data: [转换失败] {str(e)}\n\n"
|
|
|
|
|
|
2025-09-04 16:09:11 +08:00
|
|
|
|
yield "data: [DONE]\n\n"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"生成导学案时发生异常: {str(e)}")
|
|
|
|
|
yield f"data: 生成导学案时发生异常: {str(e)}\n\n"
|
|
|
|
|
|
|
|
|
|
# 返回SSE响应
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
generate_sse_response(),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
raise HTTPException(status_code=400, detail="无效的JSON格式")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"导学案接口异常: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/teacher/jiaoAn")
|
|
|
|
|
async def generate_jiao_an(request: Request):
|
|
|
|
|
"""生成教案接口"""
|
|
|
|
|
try:
|
|
|
|
|
# 解析请求体
|
|
|
|
|
body = await request.body()
|
|
|
|
|
data = json.loads(body) if body else {}
|
|
|
|
|
|
|
|
|
|
# 获取提示词参数,如果没有提供则使用默认值
|
|
|
|
|
system_prompt = data.get("system_prompt")
|
|
|
|
|
user_prompt = data.get("user_prompt")
|
|
|
|
|
|
|
|
|
|
# 生成教案
|
|
|
|
|
content_generator = await generate_lesson_plan(system_prompt, user_prompt)
|
|
|
|
|
|
|
|
|
|
# 定义SSE响应生成器
|
|
|
|
|
async def generate_sse_response():
|
|
|
|
|
try:
|
|
|
|
|
yield "data: 开始生成教案...\n\n"
|
|
|
|
|
async for chunk in content_generator:
|
|
|
|
|
yield f"data: {chunk}\n\n"
|
|
|
|
|
yield "data: [DONE]\n\n"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"生成教案时发生异常: {str(e)}")
|
|
|
|
|
yield f"data: 生成教案时发生异常: {str(e)}\n\n"
|
|
|
|
|
|
|
|
|
|
# 返回SSE响应
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
generate_sse_response(),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
raise HTTPException(status_code=400, detail="无效的JSON格式")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"教案接口异常: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/teacher/keJian")
|
|
|
|
|
async def generate_ke_jian(request: Request):
|
|
|
|
|
"""生成课件接口"""
|
|
|
|
|
try:
|
|
|
|
|
# 解析请求体
|
|
|
|
|
body = await request.body()
|
|
|
|
|
data = json.loads(body) if body else {}
|
|
|
|
|
|
|
|
|
|
# 获取提示词参数,如果没有提供则使用默认值
|
|
|
|
|
system_prompt = data.get("system_prompt")
|
|
|
|
|
user_prompt = data.get("user_prompt")
|
|
|
|
|
|
|
|
|
|
# 生成课件
|
|
|
|
|
content_generator = await generate_courseware(system_prompt, user_prompt)
|
|
|
|
|
|
|
|
|
|
# 定义SSE响应生成器
|
|
|
|
|
async def generate_sse_response():
|
|
|
|
|
try:
|
|
|
|
|
yield "data: 开始生成课件...\n\n"
|
|
|
|
|
async for chunk in content_generator:
|
|
|
|
|
yield f"data: {chunk}\n\n"
|
|
|
|
|
yield "data: [DONE]\n\n"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"生成课件时发生异常: {str(e)}")
|
|
|
|
|
yield f"data: 生成课件时发生异常: {str(e)}\n\n"
|
|
|
|
|
|
|
|
|
|
# 返回SSE响应
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
generate_sse_response(),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
raise HTTPException(status_code=400, detail="无效的JSON格式")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"课件接口异常: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/teacher/zuoYe")
|
|
|
|
|
async def generate_zuo_ye(request: Request):
|
|
|
|
|
"""生成作业接口"""
|
|
|
|
|
try:
|
|
|
|
|
# 解析请求体
|
|
|
|
|
body = await request.body()
|
|
|
|
|
data = json.loads(body) if body else {}
|
|
|
|
|
|
|
|
|
|
# 获取提示词参数,如果没有提供则使用默认值
|
|
|
|
|
system_prompt = data.get("system_prompt")
|
|
|
|
|
user_prompt = data.get("user_prompt")
|
|
|
|
|
material_path = data.get("material_path")
|
|
|
|
|
|
|
|
|
|
# 生成作业
|
|
|
|
|
content_generator = await generate_homework(system_prompt, user_prompt, material_path)
|
|
|
|
|
|
|
|
|
|
# 定义SSE响应生成器
|
|
|
|
|
async def generate_sse_response():
|
|
|
|
|
try:
|
|
|
|
|
yield "data: 开始生成作业...\n\n"
|
|
|
|
|
async for chunk in content_generator:
|
|
|
|
|
yield f"data: {chunk}\n\n"
|
|
|
|
|
yield "data: [DONE]\n\n"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"生成作业时发生异常: {str(e)}")
|
|
|
|
|
yield f"data: 生成作业时发生异常: {str(e)}\n\n"
|
|
|
|
|
|
|
|
|
|
# 返回SSE响应
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
generate_sse_response(),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
raise HTTPException(status_code=400, detail="无效的JSON格式")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"作业接口异常: {str(e)}")
|
2025-09-04 17:27:42 +08:00
|
|
|
|
raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/teacher/download/{filename}")
|
|
|
|
|
async def download_word_file(filename: str):
|
|
|
|
|
"""下载生成的Word文件"""
|
|
|
|
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
save_dir = os.path.join(project_root, "static", "teacherHelpergenerated_files")
|
|
|
|
|
file_path = os.path.join(save_dir, filename)
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
|
raise HTTPException(status_code=404, detail="文件不存在")
|
|
|
|
|
|
|
|
|
|
return FileResponse(
|
|
|
|
|
path=file_path,
|
|
|
|
|
filename=filename,
|
|
|
|
|
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
|
|
|
)
|