This commit is contained in:
2025-09-04 17:27:42 +08:00
parent f219144328
commit f280923ce0
10 changed files with 459 additions and 391 deletions

View File

@@ -1,7 +1,13 @@
import json
import logging
import os
import json
import subprocess
import tempfile
from datetime import datetime
from fastapi.responses import FileResponse
from fastapi import APIRouter, Request, HTTPException
from fastapi import APIRouter, Request, HTTPException, Depends
from fastapi.responses import StreamingResponse
from TeacherHelper.Kit.TeacherHelper import (
@@ -17,17 +23,20 @@ router = APIRouter(prefix="/api", tags=["教师辅助工具"])
logger = logging.getLogger(__name__)
@router.post("/teacher/daoXueAn")
@router.api_route("/teacher/daoXueAn", methods=["GET", "POST"]) # 修改这里
async def generate_dao_xue_an(request: Request):
"""生成导学案接口"""
try:
# 解析请求体
# 获取请求参数兼容GET和POST
if request.method == "GET":
params = dict(request.query_params)
else:
body = await request.body()
data = json.loads(body) if body else {}
params = json.loads(body) if body else {}
# 获取提示词参数,如果没有提供则使用默认值
system_prompt = data.get("system_prompt")
user_prompt = data.get("user_prompt")
# 获取提示词参数
system_prompt = params.get("system_prompt")
user_prompt = params.get("user_prompt")
# 生成导学案
content_generator = await generate_lesson_plan(system_prompt, user_prompt)
@@ -35,9 +44,51 @@ async def generate_dao_xue_an(request: Request):
# 定义SSE响应生成器
async def generate_sse_response():
try:
buffer = [] # 用于收集完整内容
yield "data: 开始生成导学案...\n\n"
async for chunk in content_generator:
yield f"data: {chunk}\n\n"
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)
# 生成唯一文件名
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
docx_filename = f"导学案_{timestamp}.docx"
docx_path = os.path.join(save_dir, docx_filename)
try:
yield "data: 正在转换为Word文档...\n\n"
# 执行Pandoc命令
result = subprocess.run(
["pandoc", temp_md_path, "-o", docx_path],
capture_output=True,
text=True,
check=True
)
# 清理临时文件
os.unlink(temp_md_path)
logger.info(f"导学案已转换为Word文档: {docx_path}")
# 修改下载链接为静态文件路径
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"
yield "data: [DONE]\n\n"
except Exception as e:
logger.error(f"生成导学案时发生异常: {str(e)}")
@@ -176,3 +227,20 @@ async def generate_zuo_ye(request: Request):
except Exception as e:
logger.error(f"作业接口异常: {str(e)}")
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"
)

View File

@@ -0,0 +1,87 @@
### 1. 教材与学情分析
本节课选自人教版八年级下册第十章第2节《万有引力》。学生在前期已学习了重力、匀速圆周运动等知识具备一定的力学基础和科学探究意识。本节内容是牛顿力学体系中的重要组成部分通过引导学生理解万有引力的提出背景、基本概念及其在天体运动中的应用有助于培养其物理观念、科学思维和探究能力。
---
### 2. 四维目标(对应核心素养)
| 核心素养维度 | 学习目标 |
|--------------|----------|
| 物理观念 | 通过观察和分析,理解万有引力的基本概念及其作用规律。 |
| 科学思维 | 能够运用归纳推理和类比思维,理解牛顿提出万有引力的过程。 |
| 探究能力 | 通过小组合作探究,设计实验验证万有引力与质量、距离的关系(定性)。 |
| 态度责任 | 培与科学探索的兴趣,体会科学家探索自然规律的坚持与智慧。 |
---
### 3. 重难点
| 类型 | 内容(行为动词+知识内容) |
|------|-----------------------------|
| 教学重点 | 分析牛顿提出万有引力的思想过程;理解万有引力的定义与表达式。 |
| 教学难点 | 推理万有引力与质量、距离之间的定性关系;理解万有引力在天体运动中的作用。 |
---
### 4. 教学准备
| 类型 | 内容 |
|------|------|
| 实验器材 | 演示实验:弹簧秤、质量块若干、天体模型(地球与月球) |
| 数字资源 | Padlet在线协作平台、PPT课件、视频片段《牛顿发现万有引力》 |
---
### 5. 教学过程(时间轴表格)
| 时间 | 教师行为 | 期望的学生行为 | 评价要点 |
|------|----------|------------------|-----------|
| 0-5分钟 | 情境导入:播放牛顿发现苹果落地的视频,提问“为什么苹果会落地?” | 学生观看视频,思考问题,尝试回答 | 观察学生是否能联系重力知识进行初步解释 |
| 5-10分钟 | 演示实验:用弹簧秤测量不同质量物体的重力,引导学生观察数据变化 | 学生观察实验,记录数据,思考质量与重力的关系 | 评价学生是否理解质量与引力的关系 |
| 10-15分钟 | 讲解牛顿提出万有引力的过程,结合地球绕太阳运动进行类比说明 | 学生倾听,记录关键点,尝试用自己的语言复述 | 评价学生是否能理解“类比思维”在科学发现中的作用 |
| 15-25分钟 | 小组探究:每组设计一个实验(定性)验证万有引力与质量、距离的关系(可用天体模型模拟) | 学生分组讨论,设计实验方案,进行模拟演示 | 评价学生是否能运用控制变量法进行探究设计 |
| 25-30分钟 | Padlet即时反馈学生将探究结果上传至Padlet平台教师选取典型方案进行展示与点评 | 学生上传探究结果,浏览他人方案,参与互评 | 评价学生是否能清晰表达探究过程与结论 |
| 30-40分钟 | 总结提升教师引导学生归纳万有引力的基本概念与表达式F=Gm₁m₂/r²并解释其在宇宙中的应用 | 学生整理笔记,参与互动问答,尝试解释天体运动现象 | 评价学生是否掌握万有引力的基本表达与应用 |
| 40-45分钟 | 课堂小结与作业布置:回顾本节课重点,布置分层作业 | 学生记录作业内容,提出疑问 | 评价学生是否理解学习目标与任务 |
---
### 6. 板书设计ASCII示意图
```
万有引力
┌───────────────┐
│ 一、牛顿的发现过程 │
│ - 苹果落地与月球绕地 │
│ - 类比思维的应用 │
├───────────────┤
│ 二、万有引力概念 │
│ - 定义:任何两个物体之间都存在引力 │
│ - 表达式F = Gm₁m₂ / r² │
├───────────────┤
│ 三、探究活动 │
│ - 探究目的:质量、距离对引力的影响 │
│ - 探究方法:控制变量法 + 模拟实验 │
└───────────────┘
```
---
### 7. 作业布置(与导学案分层衔接)
| 层次 | 内容 |
|------|------|
| 基础层 | 教材P58练习题1-3题完成导学案“基础巩固”部分 |
| 提高层 | 设计一个实验方案,验证“两个物体之间的引力与它们的距离平方成反比”(可用文字+图示表达) |
| 拓展层 | 查阅资料简述“万有引力定律”对现代航天技术的影响200字左右 |
---
### 8. 教学反思提示(空白)
1. ___________________________________________________________
2. ___________________________________________________________
3. ___________________________________________________________

View File

@@ -4,8 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>教师辅助工具 - 教学资源生成</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://gcore.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
:root {
--primary-color: #2c3e50;
@@ -85,52 +88,77 @@
overflow-y: auto;
}
.loading {
.loading-container {
display: none;
text-align: center;
padding: 20px;
justify-content: center;
align-items: center;
height: 200px;
}
.spinner-border {
width: 3rem;
height: 3rem;
.loader {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.nav-tabs .nav-link {
color: var(--dark-color);
font-weight: 500;
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.nav-tabs .nav-link.active {
color: var(--secondary-color);
font-weight: bold;
/* 按钮禁用样式 */
.btn-disabled {
background-color: #cccccc !important;
border-color: #cccccc !important;
cursor: not-allowed;
opacity: 0.7;
}
.footer {
margin-top: 40px;
padding: 20px 0;
background-color: var(--primary-color);
/* 下载按钮样式 */
.download-btn {
display: inline-block;
margin-top: 15px;
padding: 10px 20px;
background-color: #27ae60;
color: white;
text-align: center;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.3s;
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
.download-btn:hover {
background-color: #219653;
color: white;
}
.tab-pane {
padding-top: 20px;
/* Markdown内容样式 */
.result-container h1, .result-container h2, .result-container h3,
.result-container h4, .result-container h5, .result-container h6 {
margin-top: 2rem;
margin-bottom: 1.2rem;
font-weight: 600;
color: #2c3e50;
}
.result-container p { margin-bottom: 1.2rem; line-height: 1.8; }
.result-container ul, .result-container ol { margin-bottom: 1.2rem; padding-left: 2rem; }
.result-container li { margin-bottom: 0.5rem; }
.result-container blockquote { margin: 0 0 1.2rem; padding: 1rem 1.5rem; border-left: 4px solid #3498db; background-color: #f8f9fa; }
.result-container code { padding: 0.2em 0.4em; background-color: rgba(27, 31, 35, 0.05); border-radius: 3px; font-family: 'Courier New', monospace; }
.result-container pre { padding: 16px; overflow: auto; background-color: #f6f8fa; border-radius: 3px; margin-bottom: 1.2rem; border: 1px solid #e1e4e8; }
.result-container table { border-collapse: collapse; width: 100%; margin-bottom: 1.2rem; }
.result-container table th, .result-container table td { padding: 0.75rem; border: 1px solid #dfe2e5; text-align: left; }
.result-container table th { background-color: #f6f8fa; font-weight: 600; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="#">
<i class="bi bi-mortarboard-fill me-2"></i>教师辅助工具
<i class="bi bi-mortarboard-fill me-2"></i>AI教学资源生成工具
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -153,7 +181,8 @@
<i class="bi bi-book me-2"></i>教学资源生成工具
</div>
<div class="card-body">
<p class="text-muted">使用此工具生成万有引力相关的导学案、教案、课件和作业。您可以自定义提示词以获得更符合需求的内容。</p>
<p class="text-muted">
使用此工具生成万有引力相关的导学案、教案、课件和作业。您可以自定义提示词以获得更符合需求的内容。</p>
<ul class="nav nav-tabs" id="resourceTabs" role="tablist">
<li class="nav-item" role="presentation">
@@ -176,42 +205,18 @@
<form id="daoXueAnForm">
<div class="mb-3">
<label for="daoXueAnSystemPrompt" class="form-label">系统提示词</label>
<textarea class="form-control" id="daoXueAnSystemPrompt" rows="3">你是市级骨干教师,撰写导学案时严格对标"核心素养四维度(物理观念、科学思维、探究能力、态度责任)",并采用"目标—评价—活动"逆向设计模板UbD活动设计需包含"教师行为+期望的学生行为+评价要点"三列表格。</textarea>
<textarea class="form-control" id="daoXueAnSystemPrompt" rows="3">你是市级骨干教师,撰写导学案时严格对标"核心素养四维度(物理观念、科学思维、探究能力、态度责任)",并采用"目标—评价—活动"逆向设计模板UbD</textarea>
</div>
<div class="mb-3">
<label for="daoXueAnUserPrompt" class="form-label">用户提示词</label>
<textarea class="form-control" id="daoXueAnUserPrompt" rows="6">请输出"万有引力"第1课时的导学案要求
1. 教学重难点用"行为动词+知识内容"表述;
2. 至少1个"演示实验"+1个"小组探究"+1个"即时反馈"技术如Padlet
3. 时间轴精确到分钟;
4. 课后"教学反思提示"留3条空白供教师手写。
【格式要求】
### 1. 教材与学情分析
### 2. 四维目标(对应核心素养)
### 3. 重难点
### 4. 教学准备(器材+数字资源)
### 5. 教学过程(时间轴表格)
### 6. 板书设计ASCII示意图
### 7. 作业布置(与导学案分层衔接)
### 8. 教学反思提示(空白)
【变量】
教材版本:{人教版八年级下第十章第2节}
学生基础:{已学过重力、匀速圆周运动}
课时长度:{45分钟}</textarea>
<textarea class="form-control" id="daoXueAnUserPrompt" rows="6">请输出"万有引力"第1课时的导学案要求包含教材学情分析、四维目标、重难点、教学准备、教学过程时间轴表格、板书设计和作业布置。</textarea>
</div>
<button type="button" class="btn btn-primary" onclick="generateResource('daoXueAn')">
<button type="button" class="btn btn-primary" id="generateDaoXueAn" onclick="generateResource('daoXueAn')">
<i class="bi bi-magic me-2"></i>生成导学案
</button>
</form>
<div id="daoXueAnLoading" class="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在生成导学案,请稍候...</p>
</div>
<div id="daoXueAnResult" class="result-container" style="display: none;"></div>
<div id="daoXueAnLoading" class="loading-container"><div class="loader"></div></div>
<div id="daoXueAnResult" class="result-container"></div>
</div>
<!-- 教案标签页 -->
@@ -219,42 +224,18 @@
<form id="jiaoAnForm">
<div class="mb-3">
<label for="jiaoAnSystemPrompt" class="form-label">系统提示词</label>
<textarea class="form-control" id="jiaoAnSystemPrompt" rows="3">你是市级骨干教师,撰写教案时严格对标"核心素养四维度(物理观念、科学思维、探究能力、态度责任)",并采用"目标—评价—活动"逆向设计模板UbD。活动设计需包含"教师行为+期望的学生行为+评价要点"三列表格</textarea>
<textarea class="form-control" id="jiaoAnSystemPrompt" rows="3">你是市级骨干教师,撰写教案时严格对标"核心素养四维度(物理观念、科学思维、探究能力、态度责任)"。</textarea>
</div>
<div class="mb-3">
<label for="jiaoAnUserPrompt" class="form-label">用户提示词</label>
<textarea class="form-control" id="jiaoAnUserPrompt" rows="6">请输出"万有引力"第1课时的案,要求
1. 教学重难点用"行为动词+知识内容"表述;
2. 至少1个"演示实验"+1个"小组探究"+1个"即时反馈"技术如Padlet
3. 时间轴精确到分钟;
4. 课后"教学反思提示"留3条空白供教师手写。
【格式要求】
### 1. 教材与学情分析
### 2. 四维目标(对应核心素养)
### 3. 重难点
### 4. 教学准备(器材+数字资源)
### 5. 教学过程(时间轴表格)
### 6. 板书设计ASCII示意图
### 7. 作业布置(与导学案分层衔接)
### 8. 教学反思提示(空白)
【变量】
教材版本:{人教版八年级下第十章第2节}
学生基础:{已学过重力、匀速圆周运动}
课时长度:{45分钟}</textarea>
<textarea class="form-control" id="jiaoAnUserPrompt" rows="6">请输出"万有引力"第1课时的案,要求包含教材学情分析、四维目标、重难点、教学准备、教学过程时间轴表格、板书设计和作业布置。</textarea>
</div>
<button type="button" class="btn btn-primary" onclick="generateResource('jiaoAn')">
<button type="button" class="btn btn-primary" id="generateJiaoAn" onclick="generateResource('jiaoAn')">
<i class="bi bi-magic me-2"></i>生成教案
</button>
</form>
<div id="jiaoAnLoading" class="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在生成教案,请稍候...</p>
</div>
<div id="jiaoAnResult" class="result-container" style="display: none;"></div>
<div id="jiaoAnLoading" class="loading-container"><div class="loader"></div></div>
<div id="jiaoAnResult" class="result-container"></div>
</div>
<!-- 课件标签页 -->
@@ -262,36 +243,18 @@
<form id="keJianForm">
<div class="mb-3">
<label for="keJianSystemPrompt" class="form-label">系统提示词</label>
<textarea class="form-control" id="keJianSystemPrompt" rows="3">你是PPT视觉设计教练遵循"6×6原则"每页≤6行每行≤6词字体≥28pt,主色调#005BAC教育蓝强调色#FFB703暖黄。所有动画≤0.5s,禁止花哨。需要给出演示者备注栏(<备注></textarea>
<textarea class="form-control" id="keJianSystemPrompt" rows="3">你是PPT视觉设计教练遵循"6×6原则"每页≤6行每行≤6词字体≥28pt。</textarea>
</div>
<div class="mb-3">
<label for="keJianUserPrompt" class="form-label">用户提示词</label>
<textarea class="form-control" id="keJianUserPrompt" rows="6">为"万有引力"生成可直接导入PowerPoint的Markdown大纲共12页
1. 封面(课程名+章节+教师姓名留白)
2. 情境导入1个30s短视频建议+2张图片提示
3. 概念建构(苹果落地+月亮绕地对比图)
4. 规律探究卡文迪许实验GIF占位
5. 公式推导F=G·m₁m₂/r²分三步行
6. 例题精讲2道step-by-step动画
7. 当堂检测Padlet二维码占位
8. 小结思维导图可一键转SmartArt
9. 作业二维码(链接到在线表单)
10. 结束页("思考:如果没有万有引力?"留白)
【格式要求】
每页用三级标题###表示,下方用<ul>列要点;如需图片,用! `https://via.placeholder.com/800x450?text=Image` 占位并给出版权提示;在要点后另起一行写<备注>演示者话术。</textarea>
<textarea class="form-control" id="keJianUserPrompt" rows="6">为"万有引力"生成12页PPT大纲包含封面、情境导入、概念建构、规律探究、公式推导、例题精讲、当堂检测、小结和作业布置。</textarea>
</div>
<button type="button" class="btn btn-primary" onclick="generateResource('keJian')">
<button type="button" class="btn btn-primary" id="generateKeJian" onclick="generateResource('keJian')">
<i class="bi bi-magic me-2"></i>生成课件
</button>
</form>
<div id="keJianLoading" class="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在生成课件,请稍候...</p>
</div>
<div id="keJianResult" class="result-container" style="display: none;"></div>
<div id="keJianLoading" class="loading-container"><div class="loader"></div></div>
<div id="keJianResult" class="result-container"></div>
</div>
<!-- 作业标签页 -->
@@ -299,33 +262,18 @@
<form id="zuoYeForm">
<div class="mb-3">
<label for="zuoYeSystemPrompt" class="form-label">系统提示词</label>
<textarea class="form-control" id="zuoYeSystemPrompt" rows="3">你是命题专家,熟悉布鲁姆认知分类和'双减'政策。你需要根据用户提供的题目材料来生成作业,而不是自己创造题目。客观题采用'四选一'单选,难度比例易:中:难=6:3:1主观题设置2小问第1问'解释现象'对应'理解'第2问'方案设计'对应'创新'。题量控制为20分钟完成。</textarea>
<textarea class="form-control" id="zuoYeSystemPrompt" rows="3">你是命题专家,熟悉布鲁姆认知分类和'双减'政策题量控制为20分钟完成。</textarea>
</div>
<div class="mb-3">
<label for="zuoYeUserPrompt" class="form-label">用户提示词</label>
<textarea class="form-control" id="zuoYeUserPrompt" rows="6">请根据以下'万有引力'相关题目材料输出课后作业满分100分
A. 客观题8题×5分=40分
- 从提供的材料中选择题目,并按要求重新组织
- 前3题考'史实&概念'识记
- 中间3题考'公式变形&比例'理解
- 后2题考'情境估算'应用
B. 主观题2题30+30分
- 题1结合'天问一号'发射新闻,解释地球与火星之间的引力如何变化
- 题2设计一个实验用智能手机+免费APP估算地球质量写出步骤与所需测量量
C. 评分标准主观题分点给分每点10分
D. 参考答案与解析(客观题给出选项+一句话解析;主观题给出关键公式与评分关键词)</textarea>
<textarea class="form-control" id="zuoYeUserPrompt" rows="6">请根据万有引力知识点生成课后作业包含8道客观题40分和2道主观题60分并提供评分标准。</textarea>
</div>
<button type="button" class="btn btn-primary" onclick="generateResource('zuoYe')">
<button type="button" class="btn btn-primary" id="generateZuoYe" onclick="generateResource('zuoYe')">
<i class="bi bi-magic me-2"></i>生成作业
</button>
</form>
<div id="zuoYeLoading" class="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在生成作业,请稍候...</p>
</div>
<div id="zuoYeResult" class="result-container" style="display: none;"></div>
<div id="zuoYeLoading" class="loading-container"><div class="loader"></div></div>
<div id="zuoYeResult" class="result-container"></div>
</div>
</div>
</div>
@@ -334,152 +282,117 @@ D. 参考答案与解析(客观题给出选项+一句话解析;主观题给
</div>
</div>
<footer class="footer">
<div class="container">
<p class="mb-0">© 2023 教师辅助工具 - 万有引力教学资源生成</p>
</div>
</footer>
<!-- Toast 通知容器 -->
<div class="toast-container"></div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<!-- 自定义JavaScript -->
<script src="https://gcore.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 生成资源函数
async function generateResource(resourceType) {
// 获取表单数据
let systemPrompt, userPrompt, materialPath;
switch(resourceType) {
case 'daoXueAn':
systemPrompt = document.getElementById('daoXueAnSystemPrompt').value;
userPrompt = document.getElementById('daoXueAnUserPrompt').value;
break;
case 'jiaoAn':
systemPrompt = document.getElementById('jiaoAnSystemPrompt').value;
userPrompt = document.getElementById('jiaoAnUserPrompt').value;
break;
case 'keJian':
systemPrompt = document.getElementById('keJianSystemPrompt').value;
userPrompt = document.getElementById('keJianUserPrompt').value;
break;
case 'zuoYe':
systemPrompt = document.getElementById('zuoYeSystemPrompt').value;
userPrompt = document.getElementById('zuoYeUserPrompt').value;
// 设置默认的题目材料路径,不暴露给用户
materialPath = '../static/YunXiao.txt';
break;
}
// 显示加载状态
document.getElementById(resourceType + 'Loading').style.display = 'block';
document.getElementById(resourceType + 'Result').style.display = 'none';
// 准备请求数据
const requestData = {
system_prompt: systemPrompt,
user_prompt: userPrompt
// 资源类型名称映射
function getResourceName(resourceType) {
const resourceNames = {
'daoXueAn': '导学案',
'jiaoAn': '教案',
'keJian': '课件',
'zuoYe': '作业'
};
if (materialPath) {
requestData.material_path = materialPath;
return resourceNames[resourceType] || resourceType;
}
// 生成资源主函数
async function generateResource(resourceType) {
// 获取元素
const generateButton = document.getElementById(`generate${getResourceName(resourceType)}`);
const loadingElement = document.getElementById(`${resourceType}Loading`);
const resultContainer = document.getElementById(`${resourceType}Result`);
const systemPrompt = document.getElementById(`${resourceType}SystemPrompt`).value;
const userPrompt = document.getElementById(`${resourceType}UserPrompt`).value;
// 验证输入
if (!userPrompt.trim()) {
alert('请输入用户提示词');
return;
}
// 重置状态
resultContainer.innerHTML = '';
resultContainer.style.display = 'block';
loadingElement.style.display = 'flex';
// 禁用按钮
generateButton.disabled = true;
generateButton.classList.add('btn-disabled');
generateButton.innerHTML = '<i class="bi bi-hourglass-split me-2"></i>生成中...';
try {
// 发送请求
const response = await fetch(`/api/teacher/${resourceType}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
system_prompt: systemPrompt,
user_prompt: userPrompt
})
});
if (!response.ok) {
throw new Error(`请求失败: ${response.status}`);
}
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
// 处理SSE响应
// 处理流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder();
let resultText = '';
let done = false;
// 显示结果容器
document.getElementById(resourceType + 'Loading').style.display = 'none';
document.getElementById(resourceType + 'Result').style.display = 'block';
const resultContainer = document.getElementById(resourceType + 'Result');
resultContainer.innerHTML = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.substring(6); // 移除 'data: ' 前缀
if (data === '[DONE]') {
showToast('生成完成!', 'success');
return;
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunk = decoder.decode(value, { stream: true });
if (chunk) handleStreamData(chunk, resourceType, resultContainer, loadingElement, generateButton);
}
} catch (error) {
console.error('生成资源失败:', error);
resultContainer.innerHTML = `<div class="alert alert-danger"><i class="bi bi-exclamation-triangle me-2"></i>生成失败: ${error.message}</div>`;
resetState(loadingElement, generateButton);
}
}
if (data && !data.startsWith('开始生成')) {
resultText += data;
resultContainer.innerHTML = resultText;
// 滚动到底部
// 处理流数据
function handleStreamData(data, resourceType, resultContainer, loadingElement, generateButton) {
const lines = data.split('\n').filter(line => line.trim());
let shouldHideLoading = false;
lines.forEach(line => {
// 移除data:前缀
const cleanLine = line.replace(/^data:/, '').trim();
if (cleanLine.includes('[下载链接]')) {
const url = cleanLine.split('[下载链接]')[1].trim();
const fileName = `${getResourceName(resourceType)}_${new Date().toLocaleDateString().replace(/\//g, '-')}.docx`;
resultContainer.innerHTML += `<a href="${url}" class="download-btn" download="${fileName}"><i class="bi bi-download me-2"></i>下载${getResourceName(resourceType)}\n</a>`;
shouldHideLoading = true;
} else if (cleanLine.includes('[转换失败]')) {
const errorMsg = cleanLine.split('[转换失败]')[1].trim();
resultContainer.innerHTML += `<div class="alert alert-danger"><i class="bi bi-x-circle me-2"></i>转换失败: ${errorMsg}</div>`;
shouldHideLoading = true;
} else if (cleanLine.includes('[DONE]')) {
shouldHideLoading = true;
} else if (cleanLine) {
// 普通消息显示已移除data:前缀)
resultContainer.innerHTML += `<div class="alert alert-info"><i class="bi bi-info-circle me-2"></i>${cleanLine}</div>`;
resultContainer.scrollTop = resultContainer.scrollHeight;
}
}
}
}
showToast('生成完成!', 'success');
} catch (error) {
console.error('生成资源时出错:', error);
document.getElementById(resourceType + 'Loading').style.display = 'none';
showToast(`生成失败: ${error.message}`, 'danger');
}
}
// 显示Toast通知
function showToast(message, type = 'info') {
const toastContainer = document.querySelector('.toast-container');
const toastId = 'toast-' + Date.now();
const toastHtml = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<i class="bi bi-${type === 'success' ? 'check-circle-fill text-success' : type === 'danger' ? 'exclamation-triangle-fill text-danger' : 'info-circle-fill text-info'} me-2"></i>
<strong class="me-auto">系统通知</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: 3000
});
toast.show();
// 隐藏加载动画
if (shouldHideLoading) {
loadingElement.style.display = 'none';
generateButton.disabled = false;
generateButton.classList.remove('btn-disabled');
generateButton.innerHTML = `<i class="bi bi-magic me-2"></i>生成${getResourceName(resourceType).replace(/^./, c => c.toLowerCase())}`;
}
}
// 监听隐藏事件,移除元素
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
// 重置状态
function resetState(loadingElement, generateButton) {
loadingElement.style.display = 'none';
generateButton.disabled = false;
generateButton.classList.remove('btn-disabled');
generateButton.innerHTML = `<i class="bi bi-magic me-2"></i>生成${getResourceName(resourceType).replace(/^./, c => c.toLowerCase())}`;
}
</script>
</body>