Spaces:
Sleeping
Sleeping
Commit ·
6a91b07
1
Parent(s): e8f612b
添加持续对话功能:支持每个功能模块与AI进行多轮对话和互动
Browse filesCo-authored-by: Cursor <cursoragent@cursor.com>
- api/courseware/activity_designer.py +12 -5
- api/courseware/content_generator.py +12 -5
- api/courseware/qa_optimizer.py +12 -5
- api/courseware/teaching_copilot.py +12 -5
- api/courseware/vision_builder.py +12 -5
- api/routes_courseware.py +10 -0
- api/routes_teacher.py +10 -0
- api/teacher_agent.py +50 -16
- web/src/components/TeacherChatPage.tsx +139 -68
- web/src/components/TeacherDashboard.tsx +16 -1
- web/src/lib/api.ts +10 -0
api/courseware/activity_designer.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"""
|
| 3 |
Activity & Assignment Designer:课堂活动、作业及评分标准(Rubric);从上传资料提取核心知识点。
|
| 4 |
"""
|
| 5 |
-
from typing import Optional
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL, USE_WEAVIATE
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
@@ -15,6 +15,7 @@ def design_activities_and_assignments(
|
|
| 15 |
learning_objectives: Optional[str] = None,
|
| 16 |
rag_context_override: Optional[str] = None,
|
| 17 |
max_tokens: int = 2200,
|
|
|
|
| 18 |
) -> str:
|
| 19 |
"""
|
| 20 |
设计课堂活动、作业及 Rubric;支持从上传资料提取核心知识点并与目标一致。
|
|
@@ -30,13 +31,19 @@ def design_activities_and_assignments(
|
|
| 30 |
rag_context=rag_context or "(无检索到知识库摘录,将基于通用教学设计回答。)",
|
| 31 |
ref_instruction=ref_instruction,
|
| 32 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
try:
|
| 34 |
resp = client.chat.completions.create(
|
| 35 |
model=DEFAULT_MODEL,
|
| 36 |
-
messages=
|
| 37 |
-
{"role": "system", "content": ACTIVITY_DESIGNER_SYSTEM},
|
| 38 |
-
{"role": "user", "content": user_content},
|
| 39 |
-
],
|
| 40 |
temperature=0.5,
|
| 41 |
max_tokens=max_tokens,
|
| 42 |
timeout=90,
|
|
|
|
| 2 |
"""
|
| 3 |
Activity & Assignment Designer:课堂活动、作业及评分标准(Rubric);从上传资料提取核心知识点。
|
| 4 |
"""
|
| 5 |
+
from typing import Optional, List, Tuple
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL, USE_WEAVIATE
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
|
|
| 15 |
learning_objectives: Optional[str] = None,
|
| 16 |
rag_context_override: Optional[str] = None,
|
| 17 |
max_tokens: int = 2200,
|
| 18 |
+
history: Optional[list] = None,
|
| 19 |
) -> str:
|
| 20 |
"""
|
| 21 |
设计课堂活动、作业及 Rubric;支持从上传资料提取核心知识点并与目标一致。
|
|
|
|
| 31 |
rag_context=rag_context or "(无检索到知识库摘录,将基于通用教学设计回答。)",
|
| 32 |
ref_instruction=ref_instruction,
|
| 33 |
)
|
| 34 |
+
messages = [{"role": "system", "content": ACTIVITY_DESIGNER_SYSTEM}]
|
| 35 |
+
if history:
|
| 36 |
+
for user_msg, assistant_msg in history[-10:]:
|
| 37 |
+
if user_msg:
|
| 38 |
+
messages.append({"role": "user", "content": user_msg})
|
| 39 |
+
if assistant_msg:
|
| 40 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 41 |
+
messages.append({"role": "user", "content": user_content})
|
| 42 |
+
|
| 43 |
try:
|
| 44 |
resp = client.chat.completions.create(
|
| 45 |
model=DEFAULT_MODEL,
|
| 46 |
+
messages=messages,
|
|
|
|
|
|
|
|
|
|
| 47 |
temperature=0.5,
|
| 48 |
max_tokens=max_tokens,
|
| 49 |
timeout=90,
|
api/courseware/content_generator.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"""
|
| 3 |
Content Generator:生成 Markdown 详细教案,并导出可用于生成 PPT 的结构化数据。
|
| 4 |
"""
|
| 5 |
-
from typing import Optional
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
@@ -15,6 +15,7 @@ def generate_lesson_plan_and_ppt_data(
|
|
| 15 |
duration: Optional[str] = None,
|
| 16 |
outline_points: Optional[str] = None,
|
| 17 |
max_tokens: int = 2800,
|
|
|
|
| 18 |
) -> str:
|
| 19 |
"""
|
| 20 |
生成 Markdown 详细教案 + 可用于 PPT 的结构化数据(每页 title、bullets、speaker_notes)。
|
|
@@ -29,13 +30,19 @@ def generate_lesson_plan_and_ppt_data(
|
|
| 29 |
rag_context=rag_context or "(无检索到知识库摘录。)",
|
| 30 |
ref_instruction=ref_instruction,
|
| 31 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
try:
|
| 33 |
resp = client.chat.completions.create(
|
| 34 |
model=DEFAULT_MODEL,
|
| 35 |
-
messages=
|
| 36 |
-
{"role": "system", "content": CONTENT_GENERATOR_SYSTEM},
|
| 37 |
-
{"role": "user", "content": user_content},
|
| 38 |
-
],
|
| 39 |
temperature=0.4,
|
| 40 |
max_tokens=max_tokens,
|
| 41 |
timeout=120,
|
|
|
|
| 2 |
"""
|
| 3 |
Content Generator:生成 Markdown 详细教案,并导出可用于生成 PPT 的结构化数据。
|
| 4 |
"""
|
| 5 |
+
from typing import Optional, List, Tuple
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
|
|
| 15 |
duration: Optional[str] = None,
|
| 16 |
outline_points: Optional[str] = None,
|
| 17 |
max_tokens: int = 2800,
|
| 18 |
+
history: Optional[list] = None,
|
| 19 |
) -> str:
|
| 20 |
"""
|
| 21 |
生成 Markdown 详细教案 + 可用于 PPT 的结构化数据(每页 title、bullets、speaker_notes)。
|
|
|
|
| 30 |
rag_context=rag_context or "(无检索到知识库摘录。)",
|
| 31 |
ref_instruction=ref_instruction,
|
| 32 |
)
|
| 33 |
+
messages = [{"role": "system", "content": CONTENT_GENERATOR_SYSTEM}]
|
| 34 |
+
if history:
|
| 35 |
+
for user_msg, assistant_msg in history[-10:]:
|
| 36 |
+
if user_msg:
|
| 37 |
+
messages.append({"role": "user", "content": user_msg})
|
| 38 |
+
if assistant_msg:
|
| 39 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 40 |
+
messages.append({"role": "user", "content": user_content})
|
| 41 |
+
|
| 42 |
try:
|
| 43 |
resp = client.chat.completions.create(
|
| 44 |
model=DEFAULT_MODEL,
|
| 45 |
+
messages=messages,
|
|
|
|
|
|
|
|
|
|
| 46 |
temperature=0.4,
|
| 47 |
max_tokens=max_tokens,
|
| 48 |
timeout=120,
|
api/courseware/qa_optimizer.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"""
|
| 3 |
Course QA Optimizer:基于学生答题数据(Smart Quiz)分析弱点,自动优化后续教学建议。
|
| 4 |
"""
|
| 5 |
-
from typing import Optional
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
@@ -14,6 +14,7 @@ def optimize_from_quiz_data(
|
|
| 14 |
quiz_summary: str,
|
| 15 |
course_topic: Optional[str] = None,
|
| 16 |
max_tokens: int = 1500,
|
|
|
|
| 17 |
) -> str:
|
| 18 |
"""
|
| 19 |
基于 Smart Quiz 答题数据摘要,分析薄弱点并给出后续教学优化建议。
|
|
@@ -27,13 +28,19 @@ def optimize_from_quiz_data(
|
|
| 27 |
rag_context=rag_context or "(无检索到知识库摘录。)",
|
| 28 |
ref_instruction=ref_instruction,
|
| 29 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
try:
|
| 31 |
resp = client.chat.completions.create(
|
| 32 |
model=DEFAULT_MODEL,
|
| 33 |
-
messages=
|
| 34 |
-
{"role": "system", "content": QA_OPTIMIZER_SYSTEM},
|
| 35 |
-
{"role": "user", "content": user_content},
|
| 36 |
-
],
|
| 37 |
temperature=0.4,
|
| 38 |
max_tokens=max_tokens,
|
| 39 |
timeout=90,
|
|
|
|
| 2 |
"""
|
| 3 |
Course QA Optimizer:基于学生答题数据(Smart Quiz)分析弱点,自动优化后续教学建议。
|
| 4 |
"""
|
| 5 |
+
from typing import Optional, List, Tuple
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
|
|
| 14 |
quiz_summary: str,
|
| 15 |
course_topic: Optional[str] = None,
|
| 16 |
max_tokens: int = 1500,
|
| 17 |
+
history: Optional[list] = None,
|
| 18 |
) -> str:
|
| 19 |
"""
|
| 20 |
基于 Smart Quiz 答题数据摘要,分析薄弱点并给出后续教学优化建议。
|
|
|
|
| 28 |
rag_context=rag_context or "(无检索到知识库摘录。)",
|
| 29 |
ref_instruction=ref_instruction,
|
| 30 |
)
|
| 31 |
+
messages = [{"role": "system", "content": QA_OPTIMIZER_SYSTEM}]
|
| 32 |
+
if history:
|
| 33 |
+
for user_msg, assistant_msg in history[-10:]:
|
| 34 |
+
if user_msg:
|
| 35 |
+
messages.append({"role": "user", "content": user_msg})
|
| 36 |
+
if assistant_msg:
|
| 37 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 38 |
+
messages.append({"role": "user", "content": user_content})
|
| 39 |
+
|
| 40 |
try:
|
| 41 |
resp = client.chat.completions.create(
|
| 42 |
model=DEFAULT_MODEL,
|
| 43 |
+
messages=messages,
|
|
|
|
|
|
|
|
|
|
| 44 |
temperature=0.4,
|
| 45 |
max_tokens=max_tokens,
|
| 46 |
timeout=90,
|
api/courseware/teaching_copilot.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"""
|
| 3 |
Teaching Copilot & Student Adaptation:课堂实时辅助,按学生画像(Name, Progress, Behavior)动态调整建议。
|
| 4 |
"""
|
| 5 |
-
from typing import Optional, List, Dict, Any
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
@@ -27,6 +27,7 @@ def teaching_copilot(
|
|
| 27 |
current_content: str,
|
| 28 |
student_profiles: Optional[List[Dict[str, Any]]] = None,
|
| 29 |
max_tokens: int = 1200,
|
|
|
|
| 30 |
) -> str:
|
| 31 |
"""
|
| 32 |
课堂实时辅助:根据当前授课内容与学生画像给出即时建议与个性化调整。
|
|
@@ -41,13 +42,19 @@ def teaching_copilot(
|
|
| 41 |
rag_context=rag_context or "(无检索到知识库摘录。)",
|
| 42 |
ref_instruction=ref_instruction,
|
| 43 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
try:
|
| 45 |
resp = client.chat.completions.create(
|
| 46 |
model=DEFAULT_MODEL,
|
| 47 |
-
messages=
|
| 48 |
-
{"role": "system", "content": TEACHING_COPILOT_SYSTEM},
|
| 49 |
-
{"role": "user", "content": user_content},
|
| 50 |
-
],
|
| 51 |
temperature=0.5,
|
| 52 |
max_tokens=max_tokens,
|
| 53 |
timeout=60,
|
|
|
|
| 2 |
"""
|
| 3 |
Teaching Copilot & Student Adaptation:课堂实时辅助,按学生画像(Name, Progress, Behavior)动态调整建议。
|
| 4 |
"""
|
| 5 |
+
from typing import Optional, List, Dict, Any, Tuple
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
|
|
| 27 |
current_content: str,
|
| 28 |
student_profiles: Optional[List[Dict[str, Any]]] = None,
|
| 29 |
max_tokens: int = 1200,
|
| 30 |
+
history: Optional[list] = None,
|
| 31 |
) -> str:
|
| 32 |
"""
|
| 33 |
课堂实时辅助:根据当前授课内容与学生画像给出即时建议与个性化调整。
|
|
|
|
| 42 |
rag_context=rag_context or "(无检索到知识库摘录。)",
|
| 43 |
ref_instruction=ref_instruction,
|
| 44 |
)
|
| 45 |
+
messages = [{"role": "system", "content": TEACHING_COPILOT_SYSTEM}]
|
| 46 |
+
if history:
|
| 47 |
+
for user_msg, assistant_msg in history[-10:]:
|
| 48 |
+
if user_msg:
|
| 49 |
+
messages.append({"role": "user", "content": user_msg})
|
| 50 |
+
if assistant_msg:
|
| 51 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 52 |
+
messages.append({"role": "user", "content": user_content})
|
| 53 |
+
|
| 54 |
try:
|
| 55 |
resp = client.chat.completions.create(
|
| 56 |
model=DEFAULT_MODEL,
|
| 57 |
+
messages=messages,
|
|
|
|
|
|
|
|
|
|
| 58 |
temperature=0.5,
|
| 59 |
max_tokens=max_tokens,
|
| 60 |
timeout=60,
|
api/courseware/vision_builder.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"""
|
| 3 |
Course Vision & Structure Builder:课程定位、学习目标、层级化知识树。
|
| 4 |
"""
|
| 5 |
-
from typing import Optional
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL, USE_WEAVIATE
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
@@ -14,6 +14,7 @@ def build_course_vision(
|
|
| 14 |
course_info: str,
|
| 15 |
syllabus: str,
|
| 16 |
max_tokens: int = 2000,
|
|
|
|
| 17 |
) -> str:
|
| 18 |
"""
|
| 19 |
输入:课程基本信息 + 教学大纲。
|
|
@@ -28,13 +29,19 @@ def build_course_vision(
|
|
| 28 |
rag_context=rag_context or "(无检索到知识库摘录,将基于通用课程设计经验回答。)",
|
| 29 |
ref_instruction=ref_instruction,
|
| 30 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
try:
|
| 32 |
resp = client.chat.completions.create(
|
| 33 |
model=DEFAULT_MODEL,
|
| 34 |
-
messages=
|
| 35 |
-
{"role": "system", "content": COURSE_VISION_SYSTEM},
|
| 36 |
-
{"role": "user", "content": user_content},
|
| 37 |
-
],
|
| 38 |
temperature=0.4,
|
| 39 |
max_tokens=max_tokens,
|
| 40 |
timeout=90,
|
|
|
|
| 2 |
"""
|
| 3 |
Course Vision & Structure Builder:课程定位、学习目标、层级化知识树。
|
| 4 |
"""
|
| 5 |
+
from typing import Optional, List, Tuple
|
| 6 |
|
| 7 |
from api.config import client, DEFAULT_MODEL, USE_WEAVIATE
|
| 8 |
from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
|
|
|
|
| 14 |
course_info: str,
|
| 15 |
syllabus: str,
|
| 16 |
max_tokens: int = 2000,
|
| 17 |
+
history: Optional[list] = None,
|
| 18 |
) -> str:
|
| 19 |
"""
|
| 20 |
输入:课程基本信息 + 教学大纲。
|
|
|
|
| 29 |
rag_context=rag_context or "(无检索到知识库摘录,将基于通用课程设计经验回答。)",
|
| 30 |
ref_instruction=ref_instruction,
|
| 31 |
)
|
| 32 |
+
messages = [{"role": "system", "content": COURSE_VISION_SYSTEM}]
|
| 33 |
+
if history:
|
| 34 |
+
for user_msg, assistant_msg in history[-10:]:
|
| 35 |
+
if user_msg:
|
| 36 |
+
messages.append({"role": "user", "content": user_msg})
|
| 37 |
+
if assistant_msg:
|
| 38 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 39 |
+
messages.append({"role": "user", "content": user_content})
|
| 40 |
+
|
| 41 |
try:
|
| 42 |
resp = client.chat.completions.create(
|
| 43 |
model=DEFAULT_MODEL,
|
| 44 |
+
messages=messages,
|
|
|
|
|
|
|
|
|
|
| 45 |
temperature=0.4,
|
| 46 |
max_tokens=max_tokens,
|
| 47 |
timeout=90,
|
api/routes_courseware.py
CHANGED
|
@@ -21,6 +21,7 @@ router = APIRouter(prefix="/api/courseware", tags=["courseware"])
|
|
| 21 |
class VisionRequest(BaseModel):
|
| 22 |
course_info: str = Field(..., min_length=1, description="课程基本信息")
|
| 23 |
syllabus: str = Field(..., min_length=1, description="教学大纲或要点")
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
class VisionResponse(BaseModel):
|
|
@@ -36,6 +37,7 @@ def post_vision(req: VisionRequest):
|
|
| 36 |
content = build_course_vision(
|
| 37 |
course_info=req.course_info.strip(),
|
| 38 |
syllabus=req.syllabus.strip(),
|
|
|
|
| 39 |
)
|
| 40 |
return VisionResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 41 |
except Exception as e:
|
|
@@ -47,6 +49,7 @@ class ActivitiesRequest(BaseModel):
|
|
| 47 |
topic: str = Field(..., min_length=1, description="主题/模块")
|
| 48 |
learning_objectives: Optional[str] = Field(None, description="学习目标(可选)")
|
| 49 |
rag_context_override: Optional[str] = Field(None, description="覆盖 RAG 上下文(如上传资料摘要)")
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
class ActivitiesResponse(BaseModel):
|
|
@@ -63,6 +66,7 @@ def post_activities(req: ActivitiesRequest):
|
|
| 63 |
topic=req.topic.strip(),
|
| 64 |
learning_objectives=req.learning_objectives.strip() if req.learning_objectives else None,
|
| 65 |
rag_context_override=req.rag_context_override.strip() if req.rag_context_override else None,
|
|
|
|
| 66 |
)
|
| 67 |
return ActivitiesResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 68 |
except Exception as e:
|
|
@@ -79,6 +83,7 @@ class StudentProfile(BaseModel):
|
|
| 79 |
class CopilotRequest(BaseModel):
|
| 80 |
current_content: str = Field(..., min_length=1, description="当前授课内容/问题")
|
| 81 |
student_profiles: Optional[List[StudentProfile]] = Field(None, description="学生画像 Name, Progress, Behavior")
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
class CopilotResponse(BaseModel):
|
|
@@ -95,6 +100,7 @@ def post_copilot(req: CopilotRequest):
|
|
| 95 |
content = teaching_copilot(
|
| 96 |
current_content=req.current_content.strip(),
|
| 97 |
student_profiles=profiles,
|
|
|
|
| 98 |
)
|
| 99 |
return CopilotResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 100 |
except Exception as e:
|
|
@@ -105,6 +111,7 @@ def post_copilot(req: CopilotRequest):
|
|
| 105 |
class QAOptimizeRequest(BaseModel):
|
| 106 |
quiz_summary: str = Field(..., min_length=1, description="学生答题数据摘要(Smart Quiz)")
|
| 107 |
course_topic: Optional[str] = Field(None, description="相关课程主题/章节")
|
|
|
|
| 108 |
|
| 109 |
|
| 110 |
class QAOptimizeResponse(BaseModel):
|
|
@@ -120,6 +127,7 @@ def post_qa_optimize(req: QAOptimizeRequest):
|
|
| 120 |
content = optimize_from_quiz_data(
|
| 121 |
quiz_summary=req.quiz_summary.strip(),
|
| 122 |
course_topic=req.course_topic.strip() if req.course_topic else None,
|
|
|
|
| 123 |
)
|
| 124 |
return QAOptimizeResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 125 |
except Exception as e:
|
|
@@ -131,6 +139,7 @@ class ContentRequest(BaseModel):
|
|
| 131 |
topic: str = Field(..., min_length=1, description="主题/章节")
|
| 132 |
duration: Optional[str] = Field(None, description="课时/时长建议")
|
| 133 |
outline_points: Optional[str] = Field(None, description="大纲要点")
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
class ContentResponse(BaseModel):
|
|
@@ -147,6 +156,7 @@ def post_content(req: ContentRequest):
|
|
| 147 |
topic=req.topic.strip(),
|
| 148 |
duration=req.duration.strip() if req.duration else None,
|
| 149 |
outline_points=req.outline_points.strip() if req.outline_points else None,
|
|
|
|
| 150 |
)
|
| 151 |
return ContentResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 152 |
except Exception as e:
|
|
|
|
| 21 |
class VisionRequest(BaseModel):
|
| 22 |
course_info: str = Field(..., min_length=1, description="课程基本信息")
|
| 23 |
syllabus: str = Field(..., min_length=1, description="教学大纲或要点")
|
| 24 |
+
history: Optional[list] = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 25 |
|
| 26 |
|
| 27 |
class VisionResponse(BaseModel):
|
|
|
|
| 37 |
content = build_course_vision(
|
| 38 |
course_info=req.course_info.strip(),
|
| 39 |
syllabus=req.syllabus.strip(),
|
| 40 |
+
history=req.history,
|
| 41 |
)
|
| 42 |
return VisionResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 43 |
except Exception as e:
|
|
|
|
| 49 |
topic: str = Field(..., min_length=1, description="主题/模块")
|
| 50 |
learning_objectives: Optional[str] = Field(None, description="学习目标(可选)")
|
| 51 |
rag_context_override: Optional[str] = Field(None, description="覆盖 RAG 上下文(如上传资料摘要)")
|
| 52 |
+
history: Optional[list] = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 53 |
|
| 54 |
|
| 55 |
class ActivitiesResponse(BaseModel):
|
|
|
|
| 66 |
topic=req.topic.strip(),
|
| 67 |
learning_objectives=req.learning_objectives.strip() if req.learning_objectives else None,
|
| 68 |
rag_context_override=req.rag_context_override.strip() if req.rag_context_override else None,
|
| 69 |
+
history=req.history,
|
| 70 |
)
|
| 71 |
return ActivitiesResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 72 |
except Exception as e:
|
|
|
|
| 83 |
class CopilotRequest(BaseModel):
|
| 84 |
current_content: str = Field(..., min_length=1, description="当前授课内容/问题")
|
| 85 |
student_profiles: Optional[List[StudentProfile]] = Field(None, description="学生画像 Name, Progress, Behavior")
|
| 86 |
+
history: Optional[list] = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 87 |
|
| 88 |
|
| 89 |
class CopilotResponse(BaseModel):
|
|
|
|
| 100 |
content = teaching_copilot(
|
| 101 |
current_content=req.current_content.strip(),
|
| 102 |
student_profiles=profiles,
|
| 103 |
+
history=req.history,
|
| 104 |
)
|
| 105 |
return CopilotResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 106 |
except Exception as e:
|
|
|
|
| 111 |
class QAOptimizeRequest(BaseModel):
|
| 112 |
quiz_summary: str = Field(..., min_length=1, description="学生答题数据摘要(Smart Quiz)")
|
| 113 |
course_topic: Optional[str] = Field(None, description="相关课程主题/章节")
|
| 114 |
+
history: Optional[list] = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 115 |
|
| 116 |
|
| 117 |
class QAOptimizeResponse(BaseModel):
|
|
|
|
| 127 |
content = optimize_from_quiz_data(
|
| 128 |
quiz_summary=req.quiz_summary.strip(),
|
| 129 |
course_topic=req.course_topic.strip() if req.course_topic else None,
|
| 130 |
+
history=req.history,
|
| 131 |
)
|
| 132 |
return QAOptimizeResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 133 |
except Exception as e:
|
|
|
|
| 139 |
topic: str = Field(..., min_length=1, description="主题/章节")
|
| 140 |
duration: Optional[str] = Field(None, description="课时/时长建议")
|
| 141 |
outline_points: Optional[str] = Field(None, description="大纲要点")
|
| 142 |
+
history: Optional[list] = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 143 |
|
| 144 |
|
| 145 |
class ContentResponse(BaseModel):
|
|
|
|
| 156 |
topic=req.topic.strip(),
|
| 157 |
duration=req.duration.strip() if req.duration else None,
|
| 158 |
outline_points=req.outline_points.strip() if req.outline_points else None,
|
| 159 |
+
history=req.history,
|
| 160 |
)
|
| 161 |
return ContentResponse(content=content, weaviate_used=USE_WEAVIATE)
|
| 162 |
except Exception as e:
|
api/routes_teacher.py
CHANGED
|
@@ -18,6 +18,8 @@ class CourseDescriptionRequest(BaseModel):
|
|
| 18 |
topic: str = Field(..., min_length=1, description="课程主题/名称")
|
| 19 |
outline_hint: str | None = Field(None, description="可选:大纲或要点")
|
| 20 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
|
|
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
class CourseDescriptionResponse(BaseModel):
|
|
@@ -34,6 +36,8 @@ def post_course_description(req: CourseDescriptionRequest):
|
|
| 34 |
topic=req.topic.strip(),
|
| 35 |
outline_hint=req.outline_hint.strip() if req.outline_hint else None,
|
| 36 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
|
|
|
|
|
|
| 37 |
)
|
| 38 |
return CourseDescriptionResponse(
|
| 39 |
description=desc,
|
|
@@ -48,6 +52,7 @@ class DocSuggestionRequest(BaseModel):
|
|
| 48 |
current_doc_excerpt: str | None = Field(None, description="当前已有内容片段")
|
| 49 |
doc_type: str = Field("讲义/课件", description="文档类型")
|
| 50 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
class DocSuggestionResponse(BaseModel):
|
|
@@ -65,6 +70,7 @@ def post_doc_suggestion(req: DocSuggestionRequest):
|
|
| 65 |
current_doc_excerpt=req.current_doc_excerpt.strip() if req.current_doc_excerpt else None,
|
| 66 |
doc_type=req.doc_type.strip() or "讲义/课件",
|
| 67 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
|
|
|
| 68 |
)
|
| 69 |
return DocSuggestionResponse(
|
| 70 |
suggestion=suggestion,
|
|
@@ -79,6 +85,7 @@ class AssignmentQuestionsRequest(BaseModel):
|
|
| 79 |
week_or_module: str | None = Field(None, description="周次/模块")
|
| 80 |
question_type: str = Field("混合", description="题型:选择题、简答题、开放题、混合")
|
| 81 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
class AssignmentQuestionsResponse(BaseModel):
|
|
@@ -96,6 +103,7 @@ def post_assignment_questions(req: AssignmentQuestionsRequest):
|
|
| 96 |
week_or_module=req.week_or_module.strip() if req.week_or_module else None,
|
| 97 |
question_type=req.question_type.strip() or "混合",
|
| 98 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
|
|
|
| 99 |
)
|
| 100 |
return AssignmentQuestionsResponse(
|
| 101 |
suggestion=suggestion,
|
|
@@ -109,6 +117,7 @@ class AssessmentAnalysisRequest(BaseModel):
|
|
| 109 |
assessment_summary: str = Field(..., min_length=1, description="学生表现/评估摘要")
|
| 110 |
course_topic_hint: str | None = Field(None, description="可选:相关课程主题")
|
| 111 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
class AssessmentAnalysisResponse(BaseModel):
|
|
@@ -125,6 +134,7 @@ def post_assessment_analysis(req: AssessmentAnalysisRequest):
|
|
| 125 |
assessment_summary=req.assessment_summary.strip(),
|
| 126 |
course_topic_hint=req.course_topic_hint.strip() if req.course_topic_hint else None,
|
| 127 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
|
|
|
| 128 |
)
|
| 129 |
return AssessmentAnalysisResponse(
|
| 130 |
analysis=analysis,
|
|
|
|
| 18 |
topic: str = Field(..., min_length=1, description="课程主题/名称")
|
| 19 |
outline_hint: str | None = Field(None, description="可选:大纲或要点")
|
| 20 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
| 21 |
+
history: list | None = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 22 |
+
userMessage: str | None = Field(None, description="持续对话时的用户消息(可选)")
|
| 23 |
|
| 24 |
|
| 25 |
class CourseDescriptionResponse(BaseModel):
|
|
|
|
| 36 |
topic=req.topic.strip(),
|
| 37 |
outline_hint=req.outline_hint.strip() if req.outline_hint else None,
|
| 38 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
| 39 |
+
history=req.history,
|
| 40 |
+
user_message=req.userMessage.strip() if req.userMessage else None,
|
| 41 |
)
|
| 42 |
return CourseDescriptionResponse(
|
| 43 |
description=desc,
|
|
|
|
| 52 |
current_doc_excerpt: str | None = Field(None, description="当前已有内容片段")
|
| 53 |
doc_type: str = Field("讲义/课件", description="文档类型")
|
| 54 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
| 55 |
+
history: list | None = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 56 |
|
| 57 |
|
| 58 |
class DocSuggestionResponse(BaseModel):
|
|
|
|
| 70 |
current_doc_excerpt=req.current_doc_excerpt.strip() if req.current_doc_excerpt else None,
|
| 71 |
doc_type=req.doc_type.strip() or "讲义/课件",
|
| 72 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
| 73 |
+
history=req.history,
|
| 74 |
)
|
| 75 |
return DocSuggestionResponse(
|
| 76 |
suggestion=suggestion,
|
|
|
|
| 85 |
week_or_module: str | None = Field(None, description="周次/模块")
|
| 86 |
question_type: str = Field("混合", description="题型:选择题、简答题、开放题、混合")
|
| 87 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
| 88 |
+
history: list | None = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 89 |
|
| 90 |
|
| 91 |
class AssignmentQuestionsResponse(BaseModel):
|
|
|
|
| 103 |
week_or_module=req.week_or_module.strip() if req.week_or_module else None,
|
| 104 |
question_type=req.question_type.strip() or "混合",
|
| 105 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
| 106 |
+
history=req.history,
|
| 107 |
)
|
| 108 |
return AssignmentQuestionsResponse(
|
| 109 |
suggestion=suggestion,
|
|
|
|
| 117 |
assessment_summary: str = Field(..., min_length=1, description="学生表现/评估摘要")
|
| 118 |
course_topic_hint: str | None = Field(None, description="可选:相关课程主题")
|
| 119 |
reply_language: str | None = Field(None, description="回复语言:zh=中文, en=English")
|
| 120 |
+
history: list | None = Field(None, description="对话历史:[(user_msg, assistant_msg), ...]")
|
| 121 |
|
| 122 |
|
| 123 |
class AssessmentAnalysisResponse(BaseModel):
|
|
|
|
| 134 |
assessment_summary=req.assessment_summary.strip(),
|
| 135 |
course_topic_hint=req.course_topic_hint.strip() if req.course_topic_hint else None,
|
| 136 |
reply_language=req.reply_language.strip() if req.reply_language else None,
|
| 137 |
+
history=req.history,
|
| 138 |
)
|
| 139 |
return AssessmentAnalysisResponse(
|
| 140 |
analysis=analysis,
|
api/teacher_agent.py
CHANGED
|
@@ -24,20 +24,39 @@ def _lang_instruction(reply_language: Optional[str]) -> str:
|
|
| 24 |
return ""
|
| 25 |
|
| 26 |
|
| 27 |
-
def _call_llm(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
system = TEACHER_SYSTEM
|
| 29 |
lang = _lang_instruction(reply_language)
|
| 30 |
if lang:
|
| 31 |
system = system + "\n\n" + lang
|
| 32 |
if system_extra:
|
| 33 |
system = system + "\n\n" + system_extra
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
resp = client.chat.completions.create(
|
| 36 |
model=DEFAULT_MODEL,
|
| 37 |
-
messages=
|
| 38 |
-
{"role": "system", "content": system},
|
| 39 |
-
{"role": "user", "content": user_content},
|
| 40 |
-
],
|
| 41 |
temperature=0.6,
|
| 42 |
max_tokens=max_tokens,
|
| 43 |
timeout=60,
|
|
@@ -52,20 +71,32 @@ def generate_course_description(
|
|
| 52 |
topic: str,
|
| 53 |
outline_hint: Optional[str] = None,
|
| 54 |
reply_language: Optional[str] = None,
|
|
|
|
|
|
|
| 55 |
) -> str:
|
| 56 |
"""
|
| 57 |
课程描述生成:根据课程主题(及可选大纲要点)生成一段可用于课程介绍/选课页的描述。
|
| 58 |
reply_language: "en" | "zh" | None(默认中文)
|
|
|
|
| 59 |
"""
|
| 60 |
rag = retrieve_from_weaviate(topic, top_k=6) if USE_WEAVIATE else ""
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
user
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def suggest_course_doc_content(
|
|
@@ -73,6 +104,7 @@ def suggest_course_doc_content(
|
|
| 73 |
current_doc_excerpt: Optional[str] = None,
|
| 74 |
doc_type: str = "讲义/课件",
|
| 75 |
reply_language: Optional[str] = None,
|
|
|
|
| 76 |
) -> str:
|
| 77 |
"""
|
| 78 |
课程文档内容建议:针对某一主题或现有文档片段,给出可写入讲义/课件的内容建议。
|
|
@@ -85,7 +117,7 @@ def suggest_course_doc_content(
|
|
| 85 |
if rag:
|
| 86 |
user += "**参考知识库摘录:**\n" + rag[:5000] + "\n\n"
|
| 87 |
user += "请用分点或短段落给出建议,便于教师直接采纳或改写。"
|
| 88 |
-
return _call_llm(user, reply_language=reply_language, max_tokens=1200)
|
| 89 |
|
| 90 |
|
| 91 |
def suggest_assignments_questions(
|
|
@@ -93,6 +125,7 @@ def suggest_assignments_questions(
|
|
| 93 |
week_or_module: Optional[str] = None,
|
| 94 |
question_type: str = "混合",
|
| 95 |
reply_language: Optional[str] = None,
|
|
|
|
| 96 |
) -> str:
|
| 97 |
"""
|
| 98 |
作业和题库生成建议:根据主题(及可选周次/模块)给出作业题、练习题或考试题建议。
|
|
@@ -107,13 +140,14 @@ def suggest_assignments_questions(
|
|
| 107 |
if rag:
|
| 108 |
user += "**参考知识库摘录:**\n" + rag[:5000] + "\n\n"
|
| 109 |
user += "请直接给出建议与示例题,便于教师录入题库或布置作业。"
|
| 110 |
-
return _call_llm(user, reply_language=reply_language, max_tokens=1500)
|
| 111 |
|
| 112 |
|
| 113 |
def analyze_student_assessment(
|
| 114 |
assessment_summary: str,
|
| 115 |
course_topic_hint: Optional[str] = None,
|
| 116 |
reply_language: Optional[str] = None,
|
|
|
|
| 117 |
) -> str:
|
| 118 |
"""
|
| 119 |
学生学习评估分析:根据教师提供的学生表现摘要(如作业/测验得分、常见错误、参与度等),
|
|
@@ -129,4 +163,4 @@ def analyze_student_assessment(
|
|
| 129 |
if rag:
|
| 130 |
user += "**参考知识库摘录(可选,用于对齐课程目标):**\n" + rag[:3000] + "\n\n"
|
| 131 |
user += "控制在 300–500 字。"
|
| 132 |
-
return _call_llm(user, reply_language=reply_language, max_tokens=800)
|
|
|
|
| 24 |
return ""
|
| 25 |
|
| 26 |
|
| 27 |
+
def _call_llm(
|
| 28 |
+
user_content: str,
|
| 29 |
+
system_extra: str = "",
|
| 30 |
+
reply_language: Optional[str] = None,
|
| 31 |
+
max_tokens: int = 1500,
|
| 32 |
+
history: Optional[list] = None,
|
| 33 |
+
) -> str:
|
| 34 |
+
"""
|
| 35 |
+
history: List of tuples [(user_msg, assistant_msg), ...] for conversation context
|
| 36 |
+
"""
|
| 37 |
system = TEACHER_SYSTEM
|
| 38 |
lang = _lang_instruction(reply_language)
|
| 39 |
if lang:
|
| 40 |
system = system + "\n\n" + lang
|
| 41 |
if system_extra:
|
| 42 |
system = system + "\n\n" + system_extra
|
| 43 |
+
|
| 44 |
+
messages = [{"role": "system", "content": system}]
|
| 45 |
+
|
| 46 |
+
# Add conversation history if provided
|
| 47 |
+
if history:
|
| 48 |
+
for user_msg, assistant_msg in history[-10:]: # Keep last 10 turns
|
| 49 |
+
if user_msg:
|
| 50 |
+
messages.append({"role": "user", "content": user_msg})
|
| 51 |
+
if assistant_msg:
|
| 52 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 53 |
+
|
| 54 |
+
messages.append({"role": "user", "content": user_content})
|
| 55 |
+
|
| 56 |
try:
|
| 57 |
resp = client.chat.completions.create(
|
| 58 |
model=DEFAULT_MODEL,
|
| 59 |
+
messages=messages,
|
|
|
|
|
|
|
|
|
|
| 60 |
temperature=0.6,
|
| 61 |
max_tokens=max_tokens,
|
| 62 |
timeout=60,
|
|
|
|
| 71 |
topic: str,
|
| 72 |
outline_hint: Optional[str] = None,
|
| 73 |
reply_language: Optional[str] = None,
|
| 74 |
+
history: Optional[list] = None,
|
| 75 |
+
user_message: Optional[str] = None,
|
| 76 |
) -> str:
|
| 77 |
"""
|
| 78 |
课程描述生成:根据课程主题(及可选大纲要点)生成一段可用于课程介绍/选课页的描述。
|
| 79 |
reply_language: "en" | "zh" | None(默认中文)
|
| 80 |
+
user_message: 持续对话时的用户消息(可选)
|
| 81 |
"""
|
| 82 |
rag = retrieve_from_weaviate(topic, top_k=6) if USE_WEAVIATE else ""
|
| 83 |
+
|
| 84 |
+
if user_message:
|
| 85 |
+
# 持续对话模式:直接使用用户消息
|
| 86 |
+
user = user_message
|
| 87 |
+
if rag:
|
| 88 |
+
user = f"**参考知识库摘录:**\n{rag[:4000]}\n\n---\n\n{user}"
|
| 89 |
+
else:
|
| 90 |
+
# 初始提交模式:使用表单字段
|
| 91 |
+
user = f"请根据以下信息,生成一段简洁的**课程描述**(约 150–250 字),适合放在课程介绍或选课页面。\n\n"
|
| 92 |
+
user += f"**课程主题/名称:** {topic}\n\n"
|
| 93 |
+
if outline_hint:
|
| 94 |
+
user += f"**大纲或要点(可选):**\n{outline_hint}\n\n"
|
| 95 |
+
if rag:
|
| 96 |
+
user += "**参考知识库摘录(请据此保持与现有课程内容一致):**\n" + rag[:4000] + "\n\n"
|
| 97 |
+
user += "请直接输出课程描述正文,无需重复题目。"
|
| 98 |
+
|
| 99 |
+
return _call_llm(user, reply_language=reply_language, max_tokens=800, history=history)
|
| 100 |
|
| 101 |
|
| 102 |
def suggest_course_doc_content(
|
|
|
|
| 104 |
current_doc_excerpt: Optional[str] = None,
|
| 105 |
doc_type: str = "讲义/课件",
|
| 106 |
reply_language: Optional[str] = None,
|
| 107 |
+
history: Optional[list] = None,
|
| 108 |
) -> str:
|
| 109 |
"""
|
| 110 |
课程文档内容建议:针对某一主题或现有文档片段,给出可写入讲义/课件的内容建议。
|
|
|
|
| 117 |
if rag:
|
| 118 |
user += "**参考知识库摘录:**\n" + rag[:5000] + "\n\n"
|
| 119 |
user += "请用分点或短段落给出建议,便于教师直接采纳或改写。"
|
| 120 |
+
return _call_llm(user, reply_language=reply_language, max_tokens=1200, history=history)
|
| 121 |
|
| 122 |
|
| 123 |
def suggest_assignments_questions(
|
|
|
|
| 125 |
week_or_module: Optional[str] = None,
|
| 126 |
question_type: str = "混合",
|
| 127 |
reply_language: Optional[str] = None,
|
| 128 |
+
history: Optional[list] = None,
|
| 129 |
) -> str:
|
| 130 |
"""
|
| 131 |
作业和题库生成建议:根据主题(及可选周次/模块)给出作业题、练习题或考试题建议。
|
|
|
|
| 140 |
if rag:
|
| 141 |
user += "**参考知识库摘录:**\n" + rag[:5000] + "\n\n"
|
| 142 |
user += "请直接给出建议与示例题,便于教师录入题库或布置作业。"
|
| 143 |
+
return _call_llm(user, reply_language=reply_language, max_tokens=1500, history=history)
|
| 144 |
|
| 145 |
|
| 146 |
def analyze_student_assessment(
|
| 147 |
assessment_summary: str,
|
| 148 |
course_topic_hint: Optional[str] = None,
|
| 149 |
reply_language: Optional[str] = None,
|
| 150 |
+
history: Optional[list] = None,
|
| 151 |
) -> str:
|
| 152 |
"""
|
| 153 |
学生学习评估分析:根据教师提供的学生表现摘要(如作业/测验得分、常见错误、参与度等),
|
|
|
|
| 163 |
if rag:
|
| 164 |
user += "**参考知识库摘录(可选,用于对齐课程目标):**\n" + rag[:3000] + "\n\n"
|
| 165 |
user += "控制在 300–500 字。"
|
| 166 |
+
return _call_llm(user, reply_language=reply_language, max_tokens=800, history=history)
|
web/src/components/TeacherChatPage.tsx
CHANGED
|
@@ -34,7 +34,7 @@ interface TeacherChatPageProps {
|
|
| 34 |
onBack: () => void;
|
| 35 |
userId: string;
|
| 36 |
replyLanguage?: "zh" | "en" | "auto";
|
| 37 |
-
onSubmit: (inputs: Record<string, string>, files: File[]) => Promise<string>;
|
| 38 |
}
|
| 39 |
|
| 40 |
const DOC_TYPE_MAP: Record<FileType, string> = {
|
|
@@ -60,8 +60,11 @@ export function TeacherChatPage({
|
|
| 60 |
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
| 61 |
const [isSubmitting, setIsSubmitting] = useState(false);
|
| 62 |
const [showFileTypeDialog, setShowFileTypeDialog] = useState(false);
|
|
|
|
|
|
|
| 63 |
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 64 |
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
| 65 |
|
| 66 |
useEffect(() => {
|
| 67 |
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
@@ -119,45 +122,75 @@ export function TeacherChatPage({
|
|
| 119 |
const handleSubmit = async () => {
|
| 120 |
if (isSubmitting) return;
|
| 121 |
|
| 122 |
-
// Validate required inputs
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
| 134 |
|
| 135 |
-
|
| 136 |
-
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
| 141 |
}
|
| 142 |
|
| 143 |
setIsSubmitting(true);
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
// Add user message
|
| 146 |
const userMessage: Message = {
|
| 147 |
id: Date.now().toString(),
|
| 148 |
role: "user",
|
| 149 |
-
content:
|
| 150 |
-
.filter(([_, v]) => v?.trim())
|
| 151 |
-
.map(([k, v]) => `${k}: ${v}`)
|
| 152 |
-
.join("\n"),
|
| 153 |
timestamp: new Date(),
|
| 154 |
-
files: uploadedFiles.map((uf) => ({ name: uf.file.name, type: uf.file.type })),
|
| 155 |
};
|
| 156 |
|
| 157 |
setMessages((prev) => [...prev, userMessage]);
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
try {
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
// Add assistant message
|
| 163 |
const assistantMessage: Message = {
|
|
@@ -168,6 +201,8 @@ export function TeacherChatPage({
|
|
| 168 |
};
|
| 169 |
|
| 170 |
setMessages((prev) => [...prev, assistantMessage]);
|
|
|
|
|
|
|
| 171 |
} catch (e: any) {
|
| 172 |
toast.error(e?.message || "Generation failed");
|
| 173 |
} finally {
|
|
@@ -175,6 +210,11 @@ export function TeacherChatPage({
|
|
| 175 |
}
|
| 176 |
};
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
const getInputFields = () => {
|
| 179 |
switch (featureId) {
|
| 180 |
case "course-description":
|
|
@@ -510,58 +550,89 @@ export function TeacherChatPage({
|
|
| 510 |
</div>
|
| 511 |
|
| 512 |
{/* Right Panel: Chat Messages */}
|
| 513 |
-
<div className="flex-1
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
<div className="
|
| 517 |
-
<
|
| 518 |
-
|
|
|
|
|
|
|
| 519 |
</div>
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
{messages.map((msg) => (
|
| 524 |
-
<div
|
| 525 |
-
key={msg.id}
|
| 526 |
-
className={`flex gap-3 ${msg.role === "user" ? "justify-end" : "justify-start"}`}
|
| 527 |
-
>
|
| 528 |
-
{msg.role === "assistant" && (
|
| 529 |
-
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
|
| 530 |
-
<FileText className="h-4 w-4 text-primary" />
|
| 531 |
-
</div>
|
| 532 |
-
)}
|
| 533 |
<div
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
? "bg-primary text-primary-foreground"
|
| 537 |
-
: "bg-muted text-foreground"
|
| 538 |
-
}`}
|
| 539 |
>
|
| 540 |
-
{msg.
|
| 541 |
-
<div className="
|
| 542 |
-
<
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
</div>
|
| 551 |
)}
|
| 552 |
-
<p className="whitespace-pre-wrap">{msg.content}</p>
|
| 553 |
-
<p className="text-xs opacity-70 mt-2">
|
| 554 |
-
{msg.timestamp.toLocaleTimeString()}
|
| 555 |
-
</p>
|
| 556 |
</div>
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
)}
|
| 562 |
-
</
|
| 563 |
-
|
| 564 |
-
<div ref={messagesEndRef} />
|
| 565 |
</div>
|
| 566 |
)}
|
| 567 |
</div>
|
|
|
|
| 34 |
onBack: () => void;
|
| 35 |
userId: string;
|
| 36 |
replyLanguage?: "zh" | "en" | "auto";
|
| 37 |
+
onSubmit: (inputs: Record<string, string>, files: File[], history?: Array<[string, string]>) => Promise<string>;
|
| 38 |
}
|
| 39 |
|
| 40 |
const DOC_TYPE_MAP: Record<FileType, string> = {
|
|
|
|
| 60 |
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
| 61 |
const [isSubmitting, setIsSubmitting] = useState(false);
|
| 62 |
const [showFileTypeDialog, setShowFileTypeDialog] = useState(false);
|
| 63 |
+
const [chatInput, setChatInput] = useState("");
|
| 64 |
+
const [isInitialSubmit, setIsInitialSubmit] = useState(true);
|
| 65 |
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 66 |
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 67 |
+
const chatInputRef = useRef<HTMLTextAreaElement>(null);
|
| 68 |
|
| 69 |
useEffect(() => {
|
| 70 |
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
|
|
| 122 |
const handleSubmit = async () => {
|
| 123 |
if (isSubmitting) return;
|
| 124 |
|
| 125 |
+
// Validate required inputs for initial submit
|
| 126 |
+
if (isInitialSubmit) {
|
| 127 |
+
const requiredFields: Record<FeatureId, string[]> = {
|
| 128 |
+
"course-description": ["topic"],
|
| 129 |
+
"doc-suggestion": ["topic"],
|
| 130 |
+
"assignment-questions": ["topic"],
|
| 131 |
+
"assessment-analysis": ["summary"],
|
| 132 |
+
vision: ["courseInfo", "syllabus"],
|
| 133 |
+
activities: ["topic"],
|
| 134 |
+
copilot: ["content"],
|
| 135 |
+
"qa-optimize": ["summary"],
|
| 136 |
+
content: ["topic"],
|
| 137 |
+
};
|
| 138 |
|
| 139 |
+
const required = requiredFields[featureId] || [];
|
| 140 |
+
const missing = required.filter((field) => !inputValues[field]?.trim());
|
| 141 |
|
| 142 |
+
if (missing.length > 0) {
|
| 143 |
+
toast.error(`Please fill in required fields: ${missing.join(", ")}`);
|
| 144 |
+
return;
|
| 145 |
+
}
|
| 146 |
}
|
| 147 |
|
| 148 |
setIsSubmitting(true);
|
| 149 |
|
| 150 |
+
// Build user message content
|
| 151 |
+
let userContent: string;
|
| 152 |
+
if (isInitialSubmit) {
|
| 153 |
+
// Initial submit: use form inputs
|
| 154 |
+
userContent = Object.entries(inputValues)
|
| 155 |
+
.filter(([_, v]) => v?.trim())
|
| 156 |
+
.map(([k, v]) => `${k}: ${v}`)
|
| 157 |
+
.join("\n");
|
| 158 |
+
} else {
|
| 159 |
+
// Continuous chat: use chat input
|
| 160 |
+
if (!chatInput.trim()) {
|
| 161 |
+
setIsSubmitting(false);
|
| 162 |
+
return;
|
| 163 |
+
}
|
| 164 |
+
userContent = chatInput.trim();
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
// Add user message
|
| 168 |
const userMessage: Message = {
|
| 169 |
id: Date.now().toString(),
|
| 170 |
role: "user",
|
| 171 |
+
content: userContent,
|
|
|
|
|
|
|
|
|
|
| 172 |
timestamp: new Date(),
|
| 173 |
+
files: isInitialSubmit ? uploadedFiles.map((uf) => ({ name: uf.file.name, type: uf.file.type })) : undefined,
|
| 174 |
};
|
| 175 |
|
| 176 |
setMessages((prev) => [...prev, userMessage]);
|
| 177 |
|
| 178 |
+
// Build conversation history (excluding the current message we're about to add)
|
| 179 |
+
const history: Array<[string, string]> = [];
|
| 180 |
+
for (let i = 0; i < messages.length; i++) {
|
| 181 |
+
if (messages[i].role === "user" && i + 1 < messages.length && messages[i + 1].role === "assistant") {
|
| 182 |
+
history.push([messages[i].content, messages[i + 1].content]);
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
try {
|
| 187 |
+
// For continuous chat, use chat input as the main message
|
| 188 |
+
// but keep form inputs for context
|
| 189 |
+
const inputsForApi = isInitialSubmit
|
| 190 |
+
? inputValues
|
| 191 |
+
: { ...inputValues, userMessage: chatInput.trim() };
|
| 192 |
+
|
| 193 |
+
const result = await onSubmit(inputsForApi, uploadedFiles.map((uf) => uf.file), history);
|
| 194 |
|
| 195 |
// Add assistant message
|
| 196 |
const assistantMessage: Message = {
|
|
|
|
| 201 |
};
|
| 202 |
|
| 203 |
setMessages((prev) => [...prev, assistantMessage]);
|
| 204 |
+
setIsInitialSubmit(false);
|
| 205 |
+
setChatInput("");
|
| 206 |
} catch (e: any) {
|
| 207 |
toast.error(e?.message || "Generation failed");
|
| 208 |
} finally {
|
|
|
|
| 210 |
}
|
| 211 |
};
|
| 212 |
|
| 213 |
+
const handleChatSubmit = async (e: React.FormEvent) => {
|
| 214 |
+
e.preventDefault();
|
| 215 |
+
await handleSubmit();
|
| 216 |
+
};
|
| 217 |
+
|
| 218 |
const getInputFields = () => {
|
| 219 |
switch (featureId) {
|
| 220 |
case "course-description":
|
|
|
|
| 550 |
</div>
|
| 551 |
|
| 552 |
{/* Right Panel: Chat Messages */}
|
| 553 |
+
<div className="flex-1 flex flex-col overflow-hidden">
|
| 554 |
+
<div className="flex-1 overflow-auto p-4">
|
| 555 |
+
{messages.length === 0 ? (
|
| 556 |
+
<div className="h-full flex items-center justify-center text-muted-foreground">
|
| 557 |
+
<div className="text-center">
|
| 558 |
+
<p className="text-lg mb-2">Start a conversation</p>
|
| 559 |
+
<p className="text-sm">Fill in the form and click Generate to begin</p>
|
| 560 |
+
</div>
|
| 561 |
</div>
|
| 562 |
+
) : (
|
| 563 |
+
<div className="space-y-4 max-w-3xl mx-auto">
|
| 564 |
+
{messages.map((msg) => (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
<div
|
| 566 |
+
key={msg.id}
|
| 567 |
+
className={`flex gap-3 ${msg.role === "user" ? "justify-end" : "justify-start"}`}
|
|
|
|
|
|
|
|
|
|
| 568 |
>
|
| 569 |
+
{msg.role === "assistant" && (
|
| 570 |
+
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
|
| 571 |
+
<FileText className="h-4 w-4 text-primary" />
|
| 572 |
+
</div>
|
| 573 |
+
)}
|
| 574 |
+
<div
|
| 575 |
+
className={`rounded-lg p-4 max-w-[80%] ${
|
| 576 |
+
msg.role === "user"
|
| 577 |
+
? "bg-primary text-primary-foreground"
|
| 578 |
+
: "bg-muted text-foreground"
|
| 579 |
+
}`}
|
| 580 |
+
>
|
| 581 |
+
{msg.files && msg.files.length > 0 && (
|
| 582 |
+
<div className="mb-2 pb-2 border-b border-border/50">
|
| 583 |
+
<p className="text-xs opacity-80 mb-1">Attached files:</p>
|
| 584 |
+
<div className="flex flex-wrap gap-1">
|
| 585 |
+
{msg.files.map((f, idx) => (
|
| 586 |
+
<span key={idx} className="text-xs bg-background/20 px-2 py-0.5 rounded">
|
| 587 |
+
{f.name}
|
| 588 |
+
</span>
|
| 589 |
+
))}
|
| 590 |
+
</div>
|
| 591 |
</div>
|
| 592 |
+
)}
|
| 593 |
+
<p className="whitespace-pre-wrap">{msg.content}</p>
|
| 594 |
+
<p className="text-xs opacity-70 mt-2">
|
| 595 |
+
{msg.timestamp.toLocaleTimeString()}
|
| 596 |
+
</p>
|
| 597 |
+
</div>
|
| 598 |
+
{msg.role === "user" && (
|
| 599 |
+
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
| 600 |
+
<span className="text-xs text-primary-foreground">U</span>
|
| 601 |
</div>
|
| 602 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
</div>
|
| 604 |
+
))}
|
| 605 |
+
<div ref={messagesEndRef} />
|
| 606 |
+
</div>
|
| 607 |
+
)}
|
| 608 |
+
</div>
|
| 609 |
+
|
| 610 |
+
{/* Chat Input */}
|
| 611 |
+
{messages.length > 0 && (
|
| 612 |
+
<div className="flex-shrink-0 border-t border-border p-4">
|
| 613 |
+
<form onSubmit={handleChatSubmit} className="flex gap-2 max-w-3xl mx-auto">
|
| 614 |
+
<Textarea
|
| 615 |
+
ref={chatInputRef}
|
| 616 |
+
value={chatInput}
|
| 617 |
+
onChange={(e) => setChatInput(e.target.value)}
|
| 618 |
+
placeholder="Continue the conversation..."
|
| 619 |
+
className="flex-1 min-h-[60px] max-h-[200px] resize-none"
|
| 620 |
+
disabled={isSubmitting}
|
| 621 |
+
onKeyDown={(e) => {
|
| 622 |
+
if (e.key === "Enter" && !e.shiftKey) {
|
| 623 |
+
e.preventDefault();
|
| 624 |
+
handleChatSubmit(e);
|
| 625 |
+
}
|
| 626 |
+
}}
|
| 627 |
+
/>
|
| 628 |
+
<Button type="submit" disabled={isSubmitting || !chatInput.trim()} size="lg">
|
| 629 |
+
{isSubmitting ? (
|
| 630 |
+
<Loader2 className="h-4 w-4 animate-spin" />
|
| 631 |
+
) : (
|
| 632 |
+
<Send className="h-4 w-4" />
|
| 633 |
)}
|
| 634 |
+
</Button>
|
| 635 |
+
</form>
|
|
|
|
| 636 |
</div>
|
| 637 |
)}
|
| 638 |
</div>
|
web/src/components/TeacherDashboard.tsx
CHANGED
|
@@ -128,13 +128,20 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 128 |
|
| 129 |
// Handler functions for chat page
|
| 130 |
const getSubmitHandler = (featureId: FeatureId) => {
|
| 131 |
-
return async (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
switch (featureId) {
|
| 133 |
case "course-description": {
|
| 134 |
const res = await apiTeacherCourseDescription({
|
| 135 |
topic: inputs.topic || "",
|
| 136 |
outline_hint: inputs.outline || undefined,
|
| 137 |
reply_language: replyLangParam,
|
|
|
|
|
|
|
| 138 |
});
|
| 139 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 140 |
return res.description;
|
|
@@ -145,6 +152,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 145 |
current_doc_excerpt: inputs.excerpt || undefined,
|
| 146 |
doc_type: inputs.docType || "Lecture Notes / Slides",
|
| 147 |
reply_language: replyLangParam,
|
|
|
|
| 148 |
});
|
| 149 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 150 |
return res.suggestion;
|
|
@@ -155,6 +163,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 155 |
week_or_module: inputs.week || undefined,
|
| 156 |
question_type: inputs.questionType || "Mixed",
|
| 157 |
reply_language: replyLangParam,
|
|
|
|
| 158 |
});
|
| 159 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 160 |
return res.suggestion;
|
|
@@ -164,6 +173,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 164 |
assessment_summary: inputs.summary || "",
|
| 165 |
course_topic_hint: inputs.topic || undefined,
|
| 166 |
reply_language: replyLangParam,
|
|
|
|
| 167 |
});
|
| 168 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 169 |
return res.analysis;
|
|
@@ -172,6 +182,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 172 |
const res = await apiCoursewareVision({
|
| 173 |
course_info: inputs.courseInfo || "",
|
| 174 |
syllabus: inputs.syllabus || "",
|
|
|
|
| 175 |
});
|
| 176 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 177 |
return res.content;
|
|
@@ -180,6 +191,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 180 |
const res = await apiCoursewareActivities({
|
| 181 |
topic: inputs.topic || "",
|
| 182 |
learning_objectives: inputs.objectives || undefined,
|
|
|
|
| 183 |
});
|
| 184 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 185 |
return res.content;
|
|
@@ -197,6 +209,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 197 |
const res = await apiCoursewareCopilot({
|
| 198 |
current_content: inputs.content || "",
|
| 199 |
student_profiles: profiles,
|
|
|
|
| 200 |
});
|
| 201 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 202 |
return res.content;
|
|
@@ -205,6 +218,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 205 |
const res = await apiCoursewareQAOptimize({
|
| 206 |
quiz_summary: inputs.summary || "",
|
| 207 |
course_topic: inputs.topic || undefined,
|
|
|
|
| 208 |
});
|
| 209 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 210 |
return res.content;
|
|
@@ -214,6 +228,7 @@ export function TeacherDashboard({ user, onBack }: Props) {
|
|
| 214 |
topic: inputs.topic || "",
|
| 215 |
duration: inputs.duration || undefined,
|
| 216 |
outline_points: inputs.outline || undefined,
|
|
|
|
| 217 |
});
|
| 218 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 219 |
return res.content;
|
|
|
|
| 128 |
|
| 129 |
// Handler functions for chat page
|
| 130 |
const getSubmitHandler = (featureId: FeatureId) => {
|
| 131 |
+
return async (
|
| 132 |
+
inputs: Record<string, string>,
|
| 133 |
+
files: File[],
|
| 134 |
+
history?: Array<[string, string]>
|
| 135 |
+
): Promise<string> => {
|
| 136 |
+
const historyParam = history && history.length > 0 ? history : null;
|
| 137 |
switch (featureId) {
|
| 138 |
case "course-description": {
|
| 139 |
const res = await apiTeacherCourseDescription({
|
| 140 |
topic: inputs.topic || "",
|
| 141 |
outline_hint: inputs.outline || undefined,
|
| 142 |
reply_language: replyLangParam,
|
| 143 |
+
history: historyParam,
|
| 144 |
+
userMessage: inputs.userMessage || undefined,
|
| 145 |
});
|
| 146 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 147 |
return res.description;
|
|
|
|
| 152 |
current_doc_excerpt: inputs.excerpt || undefined,
|
| 153 |
doc_type: inputs.docType || "Lecture Notes / Slides",
|
| 154 |
reply_language: replyLangParam,
|
| 155 |
+
history: historyParam,
|
| 156 |
});
|
| 157 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 158 |
return res.suggestion;
|
|
|
|
| 163 |
week_or_module: inputs.week || undefined,
|
| 164 |
question_type: inputs.questionType || "Mixed",
|
| 165 |
reply_language: replyLangParam,
|
| 166 |
+
history: historyParam,
|
| 167 |
});
|
| 168 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 169 |
return res.suggestion;
|
|
|
|
| 173 |
assessment_summary: inputs.summary || "",
|
| 174 |
course_topic_hint: inputs.topic || undefined,
|
| 175 |
reply_language: replyLangParam,
|
| 176 |
+
history: historyParam,
|
| 177 |
});
|
| 178 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 179 |
return res.analysis;
|
|
|
|
| 182 |
const res = await apiCoursewareVision({
|
| 183 |
course_info: inputs.courseInfo || "",
|
| 184 |
syllabus: inputs.syllabus || "",
|
| 185 |
+
history: historyParam,
|
| 186 |
});
|
| 187 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 188 |
return res.content;
|
|
|
|
| 191 |
const res = await apiCoursewareActivities({
|
| 192 |
topic: inputs.topic || "",
|
| 193 |
learning_objectives: inputs.objectives || undefined,
|
| 194 |
+
history: historyParam,
|
| 195 |
});
|
| 196 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 197 |
return res.content;
|
|
|
|
| 209 |
const res = await apiCoursewareCopilot({
|
| 210 |
current_content: inputs.content || "",
|
| 211 |
student_profiles: profiles,
|
| 212 |
+
history: historyParam,
|
| 213 |
});
|
| 214 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 215 |
return res.content;
|
|
|
|
| 218 |
const res = await apiCoursewareQAOptimize({
|
| 219 |
quiz_summary: inputs.summary || "",
|
| 220 |
course_topic: inputs.topic || undefined,
|
| 221 |
+
history: historyParam,
|
| 222 |
});
|
| 223 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 224 |
return res.content;
|
|
|
|
| 228 |
topic: inputs.topic || "",
|
| 229 |
duration: inputs.duration || undefined,
|
| 230 |
outline_points: inputs.outline || undefined,
|
| 231 |
+
history: historyParam,
|
| 232 |
});
|
| 233 |
if (res.weaviate_used) toast.success(getText(uiLanguage, "knowledgeBaseUsed"));
|
| 234 |
return res.content;
|
web/src/lib/api.ts
CHANGED
|
@@ -415,6 +415,8 @@ export async function apiTeacherCourseDescription(payload: {
|
|
| 415 |
topic: string;
|
| 416 |
outline_hint?: string | null;
|
| 417 |
reply_language?: string | null;
|
|
|
|
|
|
|
| 418 |
}): Promise<{ description: string; weaviate_used: boolean }> {
|
| 419 |
const base = getBaseUrl();
|
| 420 |
const res = await fetchWithTimeout(
|
|
@@ -436,6 +438,7 @@ export async function apiTeacherDocSuggestion(payload: {
|
|
| 436 |
current_doc_excerpt?: string | null;
|
| 437 |
doc_type?: string;
|
| 438 |
reply_language?: string | null;
|
|
|
|
| 439 |
}): Promise<{ suggestion: string; weaviate_used: boolean }> {
|
| 440 |
const base = getBaseUrl();
|
| 441 |
const res = await fetchWithTimeout(
|
|
@@ -457,6 +460,7 @@ export async function apiTeacherAssignmentQuestions(payload: {
|
|
| 457 |
week_or_module?: string | null;
|
| 458 |
question_type?: string;
|
| 459 |
reply_language?: string | null;
|
|
|
|
| 460 |
}): Promise<{ suggestion: string; weaviate_used: boolean }> {
|
| 461 |
const base = getBaseUrl();
|
| 462 |
const res = await fetchWithTimeout(
|
|
@@ -477,6 +481,7 @@ export async function apiTeacherAssessmentAnalysis(payload: {
|
|
| 477 |
assessment_summary: string;
|
| 478 |
course_topic_hint?: string | null;
|
| 479 |
reply_language?: string | null;
|
|
|
|
| 480 |
}): Promise<{ analysis: string; weaviate_used: boolean }> {
|
| 481 |
const base = getBaseUrl();
|
| 482 |
const res = await fetchWithTimeout(
|
|
@@ -497,6 +502,7 @@ export async function apiTeacherAssessmentAnalysis(payload: {
|
|
| 497 |
export async function apiCoursewareVision(payload: {
|
| 498 |
course_info: string;
|
| 499 |
syllabus: string;
|
|
|
|
| 500 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 501 |
const base = getBaseUrl();
|
| 502 |
const res = await fetchWithTimeout(
|
|
@@ -513,6 +519,7 @@ export async function apiCoursewareActivities(payload: {
|
|
| 513 |
topic: string;
|
| 514 |
learning_objectives?: string | null;
|
| 515 |
rag_context_override?: string | null;
|
|
|
|
| 516 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 517 |
const base = getBaseUrl();
|
| 518 |
const res = await fetchWithTimeout(
|
|
@@ -528,6 +535,7 @@ export async function apiCoursewareActivities(payload: {
|
|
| 528 |
export async function apiCoursewareCopilot(payload: {
|
| 529 |
current_content: string;
|
| 530 |
student_profiles?: Array<{ name?: string; progress?: string; behavior?: string }> | null;
|
|
|
|
| 531 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 532 |
const base = getBaseUrl();
|
| 533 |
const res = await fetchWithTimeout(
|
|
@@ -543,6 +551,7 @@ export async function apiCoursewareCopilot(payload: {
|
|
| 543 |
export async function apiCoursewareQAOptimize(payload: {
|
| 544 |
quiz_summary: string;
|
| 545 |
course_topic?: string | null;
|
|
|
|
| 546 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 547 |
const base = getBaseUrl();
|
| 548 |
const res = await fetchWithTimeout(
|
|
@@ -559,6 +568,7 @@ export async function apiCoursewareContent(payload: {
|
|
| 559 |
topic: string;
|
| 560 |
duration?: string | null;
|
| 561 |
outline_points?: string | null;
|
|
|
|
| 562 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 563 |
const base = getBaseUrl();
|
| 564 |
const res = await fetchWithTimeout(
|
|
|
|
| 415 |
topic: string;
|
| 416 |
outline_hint?: string | null;
|
| 417 |
reply_language?: string | null;
|
| 418 |
+
history?: Array<[string, string]> | null;
|
| 419 |
+
userMessage?: string | null;
|
| 420 |
}): Promise<{ description: string; weaviate_used: boolean }> {
|
| 421 |
const base = getBaseUrl();
|
| 422 |
const res = await fetchWithTimeout(
|
|
|
|
| 438 |
current_doc_excerpt?: string | null;
|
| 439 |
doc_type?: string;
|
| 440 |
reply_language?: string | null;
|
| 441 |
+
history?: Array<[string, string]> | null;
|
| 442 |
}): Promise<{ suggestion: string; weaviate_used: boolean }> {
|
| 443 |
const base = getBaseUrl();
|
| 444 |
const res = await fetchWithTimeout(
|
|
|
|
| 460 |
week_or_module?: string | null;
|
| 461 |
question_type?: string;
|
| 462 |
reply_language?: string | null;
|
| 463 |
+
history?: Array<[string, string]> | null;
|
| 464 |
}): Promise<{ suggestion: string; weaviate_used: boolean }> {
|
| 465 |
const base = getBaseUrl();
|
| 466 |
const res = await fetchWithTimeout(
|
|
|
|
| 481 |
assessment_summary: string;
|
| 482 |
course_topic_hint?: string | null;
|
| 483 |
reply_language?: string | null;
|
| 484 |
+
history?: Array<[string, string]> | null;
|
| 485 |
}): Promise<{ analysis: string; weaviate_used: boolean }> {
|
| 486 |
const base = getBaseUrl();
|
| 487 |
const res = await fetchWithTimeout(
|
|
|
|
| 502 |
export async function apiCoursewareVision(payload: {
|
| 503 |
course_info: string;
|
| 504 |
syllabus: string;
|
| 505 |
+
history?: Array<[string, string]> | null;
|
| 506 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 507 |
const base = getBaseUrl();
|
| 508 |
const res = await fetchWithTimeout(
|
|
|
|
| 519 |
topic: string;
|
| 520 |
learning_objectives?: string | null;
|
| 521 |
rag_context_override?: string | null;
|
| 522 |
+
history?: Array<[string, string]> | null;
|
| 523 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 524 |
const base = getBaseUrl();
|
| 525 |
const res = await fetchWithTimeout(
|
|
|
|
| 535 |
export async function apiCoursewareCopilot(payload: {
|
| 536 |
current_content: string;
|
| 537 |
student_profiles?: Array<{ name?: string; progress?: string; behavior?: string }> | null;
|
| 538 |
+
history?: Array<[string, string]> | null;
|
| 539 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 540 |
const base = getBaseUrl();
|
| 541 |
const res = await fetchWithTimeout(
|
|
|
|
| 551 |
export async function apiCoursewareQAOptimize(payload: {
|
| 552 |
quiz_summary: string;
|
| 553 |
course_topic?: string | null;
|
| 554 |
+
history?: Array<[string, string]> | null;
|
| 555 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 556 |
const base = getBaseUrl();
|
| 557 |
const res = await fetchWithTimeout(
|
|
|
|
| 568 |
topic: string;
|
| 569 |
duration?: string | null;
|
| 570 |
outline_points?: string | null;
|
| 571 |
+
history?: Array<[string, string]> | null;
|
| 572 |
}): Promise<{ content: string; weaviate_used: boolean }> {
|
| 573 |
const base = getBaseUrl();
|
| 574 |
const res = await fetchWithTimeout(
|