Spaces:
Sleeping
Sleeping
Commit ·
98148dc
1
Parent(s): b642ebc
Switch AI courseware APIs to snake_case schema
Browse files- api/routes_courseware_ai.py +211 -162
- docs/AI_COURSEWARE_API_USAGE.md +64 -57
- web/src/lib/api.ts +90 -79
api/routes_courseware_ai.py
CHANGED
|
@@ -14,7 +14,7 @@ AI Courseware AI 接口 (Plan / Prepare / Reflect / Improve)。
|
|
| 14 |
|
| 15 |
import json
|
| 16 |
import time
|
| 17 |
-
from typing import List, Optional, Any
|
| 18 |
|
| 19 |
from fastapi import APIRouter, HTTPException
|
| 20 |
from pydantic import BaseModel, Field
|
|
@@ -112,31 +112,32 @@ def _call_openai_json(
|
|
| 112 |
|
| 113 |
|
| 114 |
class SyllabusContext(BaseModel):
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
|
| 121 |
|
| 122 |
class SyllabusGenerateRequest(BaseModel):
|
| 123 |
-
|
| 124 |
context: SyllabusContext
|
|
|
|
| 125 |
|
| 126 |
|
| 127 |
class WeekSyllabus(BaseModel):
|
| 128 |
-
|
| 129 |
title: str
|
| 130 |
-
|
| 131 |
topics: List[str]
|
| 132 |
|
| 133 |
|
| 134 |
class SyllabusGenerateData(BaseModel):
|
| 135 |
-
|
| 136 |
assessment: str | None = None
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
syllabus: List[WeekSyllabus]
|
| 141 |
|
| 142 |
|
|
@@ -149,37 +150,42 @@ class SyllabusGenerateResponse(BaseModel):
|
|
| 149 |
def generate_syllabus(req: SyllabusGenerateRequest):
|
| 150 |
"""Phase 1 (Plan):根据课程元数据生成 Weeks/Modules 大纲草案。"""
|
| 151 |
ctx = req.context
|
|
|
|
| 152 |
sys_prompt = (
|
| 153 |
"You are an instructional designer. "
|
| 154 |
"Given course metadata, generate a weekly syllabus and key syllabus header fields as JSON. "
|
| 155 |
-
"Output ONLY a JSON object
|
| 156 |
-
"'
|
|
|
|
| 157 |
)
|
| 158 |
user_prompt = f"""
|
| 159 |
-
Course name: {ctx.
|
| 160 |
-
Learning outcome: {ctx.
|
| 161 |
-
Student level: {ctx.
|
| 162 |
-
Teaching focus: {ctx.
|
| 163 |
-
Total weeks/modules: {ctx.
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
Return JSON:
|
| 166 |
{{
|
| 167 |
-
"
|
| 168 |
"assessment": "Assessment breakdown text...",
|
| 169 |
-
"
|
| 170 |
-
"
|
| 171 |
-
"
|
| 172 |
"syllabus": [
|
| 173 |
{{
|
| 174 |
-
"
|
| 175 |
"title": "string",
|
| 176 |
-
"
|
| 177 |
"topics": ["string", "..."]
|
| 178 |
}},
|
| 179 |
...
|
| 180 |
]
|
| 181 |
}}
|
| 182 |
-
Make sure the array length equals
|
| 183 |
"""
|
| 184 |
|
| 185 |
data_dict, meta = _call_openai_json("syllabus.generate", sys_prompt, user_prompt, temperature=0.4)
|
|
@@ -195,27 +201,27 @@ Make sure the array length equals courseLength ({ctx.courseLength}) and weekNumb
|
|
| 195 |
|
| 196 |
|
| 197 |
class SyllabusLockedSections(BaseModel):
|
| 198 |
-
|
| 199 |
assessment: Optional[str] = None
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
|
|
|
| 203 |
|
| 204 |
|
| 205 |
class SyllabusUnlockedSections(BaseModel):
|
| 206 |
-
|
| 207 |
-
syllabus: List[dict] = Field(default_factory=list)
|
| 208 |
|
| 209 |
|
| 210 |
class CurrentSyllabus(BaseModel):
|
| 211 |
-
|
| 212 |
-
|
| 213 |
|
| 214 |
|
| 215 |
class SyllabusPartialRequest(BaseModel):
|
| 216 |
-
|
| 217 |
prompt: str
|
| 218 |
-
|
| 219 |
context: SyllabusContext
|
| 220 |
|
| 221 |
|
|
@@ -236,8 +242,8 @@ class SyllabusPartialResponse(BaseModel):
|
|
| 236 |
@router.post("/syllabus/regenerate-partial", response_model=SyllabusPartialResponse)
|
| 237 |
def regenerate_partial_syllabus(req: SyllabusPartialRequest):
|
| 238 |
"""Phase 1 (Plan):在锁定部分大纲后,根据 Prompt 重写未锁定部分(主要是 syllabus 数组)。"""
|
| 239 |
-
locked_sections = req.
|
| 240 |
-
unlocked_sections = req.
|
| 241 |
ctx = req.context
|
| 242 |
|
| 243 |
sys_prompt = (
|
|
@@ -252,11 +258,11 @@ User prompt:
|
|
| 252 |
{req.prompt}
|
| 253 |
|
| 254 |
Course context:
|
| 255 |
-
-
|
| 256 |
-
-
|
| 257 |
-
-
|
| 258 |
-
-
|
| 259 |
-
-
|
| 260 |
|
| 261 |
Locked sections (MUST remain unchanged, only for your reference):
|
| 262 |
{json.dumps(locked_sections, ensure_ascii=False)}
|
|
@@ -270,9 +276,9 @@ Return JSON:
|
|
| 270 |
"proposed_draft": {{
|
| 271 |
"syllabus": [
|
| 272 |
{{
|
| 273 |
-
"
|
| 274 |
"title": "string",
|
| 275 |
-
"
|
| 276 |
"topics": ["string", "..."]
|
| 277 |
}}
|
| 278 |
]
|
|
@@ -305,26 +311,26 @@ Return JSON:
|
|
| 305 |
|
| 306 |
class ModuleContext(BaseModel):
|
| 307 |
title: str
|
| 308 |
-
|
| 309 |
topics: List[str]
|
| 310 |
-
|
| 311 |
|
| 312 |
|
| 313 |
class FlowGenerateRequest(BaseModel):
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
|
| 318 |
|
| 319 |
class LessonStep(BaseModel):
|
| 320 |
type: str
|
| 321 |
title: str
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
|
| 329 |
|
| 330 |
class FlowGenerateData(BaseModel):
|
|
@@ -339,8 +345,8 @@ class FlowGenerateResponse(BaseModel):
|
|
| 339 |
@router.post("/flow/generate", response_model=FlowGenerateResponse)
|
| 340 |
def generate_lesson_flow(req: FlowGenerateRequest):
|
| 341 |
"""Phase 2 (Prepare):为单个 Module 生成完整教学步骤列表。"""
|
| 342 |
-
mc = req.
|
| 343 |
-
extra_system = "\n".join(req.
|
| 344 |
sys_prompt = (
|
| 345 |
"You are an AI courseware copilot. "
|
| 346 |
"Generate a detailed lesson flow for one module as JSON. "
|
|
@@ -348,9 +354,9 @@ def generate_lesson_flow(req: FlowGenerateRequest):
|
|
| 348 |
)
|
| 349 |
user_prompt = f"""
|
| 350 |
Module title: {mc.title}
|
| 351 |
-
Learning objectives: {mc.
|
| 352 |
Topics: {mc.topics}
|
| 353 |
-
Duration (minutes): {mc.
|
| 354 |
|
| 355 |
Additional long-term system prompts (optional, may be empty):
|
| 356 |
{extra_system}
|
|
@@ -361,17 +367,17 @@ Return JSON:
|
|
| 361 |
{{
|
| 362 |
"type": "Teacher Explanation | Interactive Quiz | Group Discussion | ...",
|
| 363 |
"title": "string",
|
| 364 |
-
"
|
| 365 |
-
"
|
| 366 |
-
"
|
| 367 |
-
"
|
| 368 |
-
"
|
| 369 |
-
"
|
| 370 |
}},
|
| 371 |
...
|
| 372 |
]
|
| 373 |
}}
|
| 374 |
-
The total sum of
|
| 375 |
"""
|
| 376 |
|
| 377 |
data_dict, meta = _call_openai_json("flow.generate", sys_prompt, user_prompt, temperature=0.5)
|
|
@@ -393,30 +399,30 @@ class SimpleStep(BaseModel):
|
|
| 393 |
|
| 394 |
|
| 395 |
class CurrentFlow(BaseModel):
|
| 396 |
-
|
| 397 |
-
|
| 398 |
|
| 399 |
|
| 400 |
class FlowPartialRequest(BaseModel):
|
| 401 |
-
|
| 402 |
prompt: str
|
| 403 |
-
|
| 404 |
|
| 405 |
|
| 406 |
class CopilotProposedStep(BaseModel):
|
| 407 |
type: str
|
| 408 |
title: str
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
|
| 416 |
|
| 417 |
class FlowPartialData(BaseModel):
|
| 418 |
explanation: str
|
| 419 |
-
|
| 420 |
|
| 421 |
|
| 422 |
class FlowPartialResponse(BaseModel):
|
|
@@ -427,8 +433,8 @@ class FlowPartialResponse(BaseModel):
|
|
| 427 |
@router.post("/flow/regenerate-partial", response_model=FlowPartialResponse)
|
| 428 |
def regenerate_partial_flow(req: FlowPartialRequest):
|
| 429 |
"""Phase 2 (Prepare):在锁定部分步骤后,根据 Prompt 重写剩余步骤。"""
|
| 430 |
-
locked = [s.model_dump() for s in req.
|
| 431 |
-
unlocked = [s.model_dump() for s in req.
|
| 432 |
sys_prompt = (
|
| 433 |
"You are an AI copilot for lesson flow editing. "
|
| 434 |
"Given lockedSteps and unlockedSteps plus a user prompt, "
|
|
@@ -446,16 +452,16 @@ unlockedSteps: {unlocked}
|
|
| 446 |
Return JSON:
|
| 447 |
{{
|
| 448 |
"explanation": "short natural language explanation for the teacher UI bubble",
|
| 449 |
-
"
|
| 450 |
{{
|
| 451 |
"type": "Teacher Explanation | Interactive Quiz | Group Discussion | ...",
|
| 452 |
"title": "string",
|
| 453 |
-
"
|
| 454 |
-
"
|
| 455 |
-
"
|
| 456 |
-
"
|
| 457 |
-
"
|
| 458 |
-
"
|
| 459 |
}}
|
| 460 |
]
|
| 461 |
}}
|
|
@@ -464,10 +470,10 @@ Return JSON:
|
|
| 464 |
data_dict, meta = _call_openai_json(
|
| 465 |
"flow.regenerate-partial", sys_prompt, user_prompt, temperature=0.5
|
| 466 |
)
|
| 467 |
-
if "explanation" not in data_dict or "
|
| 468 |
raise HTTPException(
|
| 469 |
status_code=422,
|
| 470 |
-
detail="INVALID_GENERATION: 'explanation' or '
|
| 471 |
)
|
| 472 |
|
| 473 |
return FlowPartialResponse(data=FlowPartialData(**data_dict), meta=meta)
|
|
@@ -479,17 +485,19 @@ Return JSON:
|
|
| 479 |
|
| 480 |
|
| 481 |
class PlanDetailRequest(BaseModel):
|
| 482 |
-
|
| 483 |
-
|
| 484 |
|
| 485 |
|
| 486 |
class LessonSection(BaseModel):
|
| 487 |
section_id: str
|
| 488 |
type: str
|
|
|
|
| 489 |
content: str
|
| 490 |
|
| 491 |
|
| 492 |
class PlanDetailData(BaseModel):
|
|
|
|
| 493 |
sections: List[LessonSection]
|
| 494 |
|
| 495 |
|
|
@@ -508,19 +516,22 @@ def generate_plan_detail(req: PlanDetailRequest):
|
|
| 508 |
)
|
| 509 |
user_prompt = f"""
|
| 510 |
Finalized steps (JSON array, opaque to you but describes lesson flow):
|
| 511 |
-
{json.dumps(req.
|
| 512 |
|
| 513 |
Return JSON:
|
| 514 |
{{
|
|
|
|
| 515 |
"sections": [
|
| 516 |
{{
|
| 517 |
-
"section_id": "
|
| 518 |
-
"type": "
|
|
|
|
| 519 |
"content": "By the end of this lesson, students will be able to..."
|
| 520 |
}},
|
| 521 |
{{
|
| 522 |
-
"section_id": "
|
| 523 |
-
"type": "
|
|
|
|
| 524 |
"content": "### 1. Topic heading\\nDetailed explanation..."
|
| 525 |
}}
|
| 526 |
]
|
|
@@ -556,54 +567,65 @@ class TeachAnnotation(BaseModel):
|
|
| 556 |
|
| 557 |
dimension: str # e.g. "TIME" | "DIFFICULTY" | "ENGAGEMENT"
|
| 558 |
level: str # e.g. "LIMITED" | "ADEQUATE" | "STRONG"
|
| 559 |
-
|
| 560 |
feedback: Optional[str] = None
|
| 561 |
|
| 562 |
|
| 563 |
class QuizAggregations(BaseModel):
|
| 564 |
-
|
| 565 |
-
|
| 566 |
|
| 567 |
|
| 568 |
class ReflectionRequest(BaseModel):
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
|
| 574 |
|
| 575 |
-
class
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
|
|
|
| 579 |
|
| 580 |
|
| 581 |
-
class
|
| 582 |
status: str
|
| 583 |
-
summary:
|
|
|
|
| 584 |
|
| 585 |
|
| 586 |
-
class
|
| 587 |
-
|
| 588 |
-
|
|
|
|
|
|
|
| 589 |
|
| 590 |
|
| 591 |
-
class
|
| 592 |
-
|
| 593 |
-
|
|
|
|
| 594 |
|
| 595 |
|
| 596 |
class NextLessonSuggestion(BaseModel):
|
| 597 |
-
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
|
| 600 |
|
| 601 |
class ReflectionData(BaseModel):
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
misconceptions: ReflectionMisconception
|
| 606 |
-
nextLessonSuggestions: List[NextLessonSuggestion]
|
| 607 |
|
| 608 |
|
| 609 |
class ReflectionResponse(BaseModel):
|
|
@@ -614,13 +636,15 @@ class ReflectionResponse(BaseModel):
|
|
| 614 |
@router.post("/reflection/generate", response_model=ReflectionResponse)
|
| 615 |
def generate_reflection(req: ReflectionRequest):
|
| 616 |
"""Phase 4 (Reflect):基于反馈与测验数据生成反思看板。"""
|
| 617 |
-
lesson_steps = [s.model_dump() for s in req.
|
| 618 |
-
annotations = [a.model_dump() for a in req.
|
| 619 |
-
qa = req.
|
| 620 |
sys_prompt = (
|
| 621 |
"You are an AI teaching analytics assistant. "
|
| 622 |
-
"Given lesson steps, teacher annotations, and quiz aggregations, generate a reflection dashboard as JSON "
|
| 623 |
-
"
|
|
|
|
|
|
|
| 624 |
)
|
| 625 |
user_prompt = f"""
|
| 626 |
Lesson steps:
|
|
@@ -634,34 +658,59 @@ Quiz aggregations:
|
|
| 634 |
|
| 635 |
Return JSON:
|
| 636 |
{{
|
| 637 |
-
"
|
| 638 |
-
|
| 639 |
-
"
|
| 640 |
-
"
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
}}
|
| 659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
}}
|
| 661 |
"""
|
| 662 |
|
| 663 |
data_dict, meta = _call_openai_json("reflection.generate", sys_prompt, user_prompt, temperature=0.4)
|
| 664 |
-
required = ["
|
| 665 |
if any(k not in data_dict for k in required):
|
| 666 |
raise HTTPException(
|
| 667 |
status_code=422,
|
|
@@ -677,18 +726,18 @@ Return JSON:
|
|
| 677 |
|
| 678 |
|
| 679 |
class ImprovementRequest(BaseModel):
|
| 680 |
-
|
| 681 |
-
|
| 682 |
|
| 683 |
|
| 684 |
class ImprovementProposal(BaseModel):
|
| 685 |
title: str
|
| 686 |
priority: str
|
| 687 |
-
|
| 688 |
evidence: Optional[str] = None
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
|
| 693 |
|
| 694 |
class ImprovementData(BaseModel):
|
|
@@ -703,7 +752,7 @@ class ImprovementResponse(BaseModel):
|
|
| 703 |
@router.post("/improvement/generate", response_model=ImprovementResponse)
|
| 704 |
def generate_improvement(req: ImprovementRequest):
|
| 705 |
"""Phase 5 (Improve):基于过往反思报告生成跨期改进提案。"""
|
| 706 |
-
reports = json.dumps(req.
|
| 707 |
sys_prompt = (
|
| 708 |
"You are an AI curriculum improvement assistant. "
|
| 709 |
"Given past reflection reports, generate high-level improvement proposals as JSON."
|
|
@@ -718,11 +767,11 @@ Return JSON:
|
|
| 718 |
{{
|
| 719 |
"title": "Students struggle with gradient descent optimization",
|
| 720 |
"priority": "High Priority | Medium Priority | Low Priority",
|
| 721 |
-
"
|
| 722 |
"evidence": "45% reported difficulty...",
|
| 723 |
-
"
|
| 724 |
-
"
|
| 725 |
-
"
|
| 726 |
}}
|
| 727 |
]
|
| 728 |
}}
|
|
|
|
| 14 |
|
| 15 |
import json
|
| 16 |
import time
|
| 17 |
+
from typing import List, Optional, Any, Dict
|
| 18 |
|
| 19 |
from fastapi import APIRouter, HTTPException
|
| 20 |
from pydantic import BaseModel, Field
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
class SyllabusContext(BaseModel):
|
| 115 |
+
course_name: str
|
| 116 |
+
learning_outcome: str
|
| 117 |
+
student_level: str
|
| 118 |
+
teaching_focus: str
|
| 119 |
+
course_length: int = Field(..., ge=1, le=52)
|
| 120 |
|
| 121 |
|
| 122 |
class SyllabusGenerateRequest(BaseModel):
|
| 123 |
+
request_id: str
|
| 124 |
context: SyllabusContext
|
| 125 |
+
system_prompts: Optional[List[str]] = None
|
| 126 |
|
| 127 |
|
| 128 |
class WeekSyllabus(BaseModel):
|
| 129 |
+
week_number: int
|
| 130 |
title: str
|
| 131 |
+
learning_objectives: List[str]
|
| 132 |
topics: List[str]
|
| 133 |
|
| 134 |
|
| 135 |
class SyllabusGenerateData(BaseModel):
|
| 136 |
+
course_overview: str | None = None
|
| 137 |
assessment: str | None = None
|
| 138 |
+
teaching_approach: str | None = None
|
| 139 |
+
workload_expectation: str | None = None
|
| 140 |
+
academic_integrity_policy: str | None = None
|
| 141 |
syllabus: List[WeekSyllabus]
|
| 142 |
|
| 143 |
|
|
|
|
| 150 |
def generate_syllabus(req: SyllabusGenerateRequest):
|
| 151 |
"""Phase 1 (Plan):根据课程元数据生成 Weeks/Modules 大纲草案。"""
|
| 152 |
ctx = req.context
|
| 153 |
+
extra_system = "\n".join(req.system_prompts or [])
|
| 154 |
sys_prompt = (
|
| 155 |
"You are an instructional designer. "
|
| 156 |
"Given course metadata, generate a weekly syllabus and key syllabus header fields as JSON. "
|
| 157 |
+
"Output ONLY a JSON object using snake_case keys: "
|
| 158 |
+
"'course_overview', 'assessment', 'teaching_approach', "
|
| 159 |
+
"'workload_expectation', 'academic_integrity_policy', and 'syllabus'."
|
| 160 |
)
|
| 161 |
user_prompt = f"""
|
| 162 |
+
Course name: {ctx.course_name}
|
| 163 |
+
Learning outcome: {ctx.learning_outcome}
|
| 164 |
+
Student level: {ctx.student_level}
|
| 165 |
+
Teaching focus: {ctx.teaching_focus}
|
| 166 |
+
Total weeks/modules: {ctx.course_length}
|
| 167 |
+
|
| 168 |
+
Additional long-term system prompts (optional, may be empty):
|
| 169 |
+
{extra_system}
|
| 170 |
|
| 171 |
Return JSON:
|
| 172 |
{{
|
| 173 |
+
"course_overview": "One short paragraph course overview",
|
| 174 |
"assessment": "Assessment breakdown text...",
|
| 175 |
+
"teaching_approach": "How the course will be taught...",
|
| 176 |
+
"workload_expectation": "Expected weekly workload...",
|
| 177 |
+
"academic_integrity_policy": "Short academic integrity policy...",
|
| 178 |
"syllabus": [
|
| 179 |
{{
|
| 180 |
+
"week_number": 1,
|
| 181 |
"title": "string",
|
| 182 |
+
"learning_objectives": ["string", "..."],
|
| 183 |
"topics": ["string", "..."]
|
| 184 |
}},
|
| 185 |
...
|
| 186 |
]
|
| 187 |
}}
|
| 188 |
+
Make sure the array length equals course_length ({ctx.course_length}) and week_number starts from 1.
|
| 189 |
"""
|
| 190 |
|
| 191 |
data_dict, meta = _call_openai_json("syllabus.generate", sys_prompt, user_prompt, temperature=0.4)
|
|
|
|
| 201 |
|
| 202 |
|
| 203 |
class SyllabusLockedSections(BaseModel):
|
| 204 |
+
course_overview: Optional[str] = None
|
| 205 |
assessment: Optional[str] = None
|
| 206 |
+
teaching_approach: Optional[str] = None
|
| 207 |
+
workload_expectation: Optional[str] = None
|
| 208 |
+
academic_integrity_policy: Optional[str] = None
|
| 209 |
+
syllabus: Optional[List[Dict[str, Any]]] = None
|
| 210 |
|
| 211 |
|
| 212 |
class SyllabusUnlockedSections(BaseModel):
|
| 213 |
+
syllabus: List[Dict[str, Any]] = Field(default_factory=list)
|
|
|
|
| 214 |
|
| 215 |
|
| 216 |
class CurrentSyllabus(BaseModel):
|
| 217 |
+
locked_sections: SyllabusLockedSections
|
| 218 |
+
unlocked_sections: SyllabusUnlockedSections
|
| 219 |
|
| 220 |
|
| 221 |
class SyllabusPartialRequest(BaseModel):
|
| 222 |
+
request_id: str
|
| 223 |
prompt: str
|
| 224 |
+
current_syllabus: CurrentSyllabus
|
| 225 |
context: SyllabusContext
|
| 226 |
|
| 227 |
|
|
|
|
| 242 |
@router.post("/syllabus/regenerate-partial", response_model=SyllabusPartialResponse)
|
| 243 |
def regenerate_partial_syllabus(req: SyllabusPartialRequest):
|
| 244 |
"""Phase 1 (Plan):在锁定部分大纲后,根据 Prompt 重写未锁定部分(主要是 syllabus 数组)。"""
|
| 245 |
+
locked_sections = req.current_syllabus.locked_sections.model_dump()
|
| 246 |
+
unlocked_sections = req.current_syllabus.unlocked_sections.model_dump()
|
| 247 |
ctx = req.context
|
| 248 |
|
| 249 |
sys_prompt = (
|
|
|
|
| 258 |
{req.prompt}
|
| 259 |
|
| 260 |
Course context:
|
| 261 |
+
- course_name: {ctx.course_name}
|
| 262 |
+
- learning_outcome: {ctx.learning_outcome}
|
| 263 |
+
- student_level: {ctx.student_level}
|
| 264 |
+
- teaching_focus: {ctx.teaching_focus}
|
| 265 |
+
- course_length: {ctx.course_length}
|
| 266 |
|
| 267 |
Locked sections (MUST remain unchanged, only for your reference):
|
| 268 |
{json.dumps(locked_sections, ensure_ascii=False)}
|
|
|
|
| 276 |
"proposed_draft": {{
|
| 277 |
"syllabus": [
|
| 278 |
{{
|
| 279 |
+
"week_number": 1,
|
| 280 |
"title": "string",
|
| 281 |
+
"learning_objectives": ["string", "..."],
|
| 282 |
"topics": ["string", "..."]
|
| 283 |
}}
|
| 284 |
]
|
|
|
|
| 311 |
|
| 312 |
class ModuleContext(BaseModel):
|
| 313 |
title: str
|
| 314 |
+
learning_objectives: List[str]
|
| 315 |
topics: List[str]
|
| 316 |
+
duration_minutes: int = Field(..., ge=10, le=600)
|
| 317 |
|
| 318 |
|
| 319 |
class FlowGenerateRequest(BaseModel):
|
| 320 |
+
request_id: str
|
| 321 |
+
module_context: ModuleContext
|
| 322 |
+
system_prompts: Optional[List[str]] = None
|
| 323 |
|
| 324 |
|
| 325 |
class LessonStep(BaseModel):
|
| 326 |
type: str
|
| 327 |
title: str
|
| 328 |
+
estimated_duration: int = Field(..., ge=1)
|
| 329 |
+
ai_understanding: str
|
| 330 |
+
teacher_activity: Optional[str] = None
|
| 331 |
+
student_activity: Optional[str] = None
|
| 332 |
+
guiding_questions: Optional[str] = None
|
| 333 |
+
required_materials: Optional[str] = None
|
| 334 |
|
| 335 |
|
| 336 |
class FlowGenerateData(BaseModel):
|
|
|
|
| 345 |
@router.post("/flow/generate", response_model=FlowGenerateResponse)
|
| 346 |
def generate_lesson_flow(req: FlowGenerateRequest):
|
| 347 |
"""Phase 2 (Prepare):为单个 Module 生成完整教学步骤列表。"""
|
| 348 |
+
mc = req.module_context
|
| 349 |
+
extra_system = "\n".join(req.system_prompts or [])
|
| 350 |
sys_prompt = (
|
| 351 |
"You are an AI courseware copilot. "
|
| 352 |
"Generate a detailed lesson flow for one module as JSON. "
|
|
|
|
| 354 |
)
|
| 355 |
user_prompt = f"""
|
| 356 |
Module title: {mc.title}
|
| 357 |
+
Learning objectives: {mc.learning_objectives}
|
| 358 |
Topics: {mc.topics}
|
| 359 |
+
Duration (minutes): {mc.duration_minutes}
|
| 360 |
|
| 361 |
Additional long-term system prompts (optional, may be empty):
|
| 362 |
{extra_system}
|
|
|
|
| 367 |
{{
|
| 368 |
"type": "Teacher Explanation | Interactive Quiz | Group Discussion | ...",
|
| 369 |
"title": "string",
|
| 370 |
+
"estimated_duration": 10,
|
| 371 |
+
"ai_understanding": "short explanation of why this step exists and what it should achieve",
|
| 372 |
+
"teacher_activity": "what the teacher should concretely do/say in this step",
|
| 373 |
+
"student_activity": "what students are expected to do",
|
| 374 |
+
"guiding_questions": "- question 1\\n- question 2",
|
| 375 |
+
"required_materials": "Projector, handouts, whiteboard, ..."
|
| 376 |
}},
|
| 377 |
...
|
| 378 |
]
|
| 379 |
}}
|
| 380 |
+
The total sum of estimated_duration should be roughly close to duration_minutes ({mc.duration_minutes}).
|
| 381 |
"""
|
| 382 |
|
| 383 |
data_dict, meta = _call_openai_json("flow.generate", sys_prompt, user_prompt, temperature=0.5)
|
|
|
|
| 399 |
|
| 400 |
|
| 401 |
class CurrentFlow(BaseModel):
|
| 402 |
+
locked_steps: List[SimpleStep]
|
| 403 |
+
unlocked_steps: List[SimpleStep]
|
| 404 |
|
| 405 |
|
| 406 |
class FlowPartialRequest(BaseModel):
|
| 407 |
+
request_id: str
|
| 408 |
prompt: str
|
| 409 |
+
current_flow: CurrentFlow
|
| 410 |
|
| 411 |
|
| 412 |
class CopilotProposedStep(BaseModel):
|
| 413 |
type: str
|
| 414 |
title: str
|
| 415 |
+
estimated_duration: int
|
| 416 |
+
ai_understanding: str
|
| 417 |
+
teacher_activity: Optional[str] = None
|
| 418 |
+
student_activity: Optional[str] = None
|
| 419 |
+
guiding_questions: Optional[str] = None
|
| 420 |
+
required_materials: Optional[str] = None
|
| 421 |
|
| 422 |
|
| 423 |
class FlowPartialData(BaseModel):
|
| 424 |
explanation: str
|
| 425 |
+
proposed_steps: List[CopilotProposedStep]
|
| 426 |
|
| 427 |
|
| 428 |
class FlowPartialResponse(BaseModel):
|
|
|
|
| 433 |
@router.post("/flow/regenerate-partial", response_model=FlowPartialResponse)
|
| 434 |
def regenerate_partial_flow(req: FlowPartialRequest):
|
| 435 |
"""Phase 2 (Prepare):在锁定部分步骤后,根据 Prompt 重写剩余步骤。"""
|
| 436 |
+
locked = [s.model_dump() for s in req.current_flow.locked_steps]
|
| 437 |
+
unlocked = [s.model_dump() for s in req.current_flow.unlocked_steps]
|
| 438 |
sys_prompt = (
|
| 439 |
"You are an AI copilot for lesson flow editing. "
|
| 440 |
"Given lockedSteps and unlockedSteps plus a user prompt, "
|
|
|
|
| 452 |
Return JSON:
|
| 453 |
{{
|
| 454 |
"explanation": "short natural language explanation for the teacher UI bubble",
|
| 455 |
+
"proposed_steps": [
|
| 456 |
{{
|
| 457 |
"type": "Teacher Explanation | Interactive Quiz | Group Discussion | ...",
|
| 458 |
"title": "string",
|
| 459 |
+
"estimated_duration": 10,
|
| 460 |
+
"ai_understanding": "short explanation of this step",
|
| 461 |
+
"teacher_activity": "what the teacher should concretely do/say in this step",
|
| 462 |
+
"student_activity": "what students are expected to do",
|
| 463 |
+
"guiding_questions": "- question 1\\n- question 2",
|
| 464 |
+
"required_materials": "Projector, handouts, whiteboard, ..."
|
| 465 |
}}
|
| 466 |
]
|
| 467 |
}}
|
|
|
|
| 470 |
data_dict, meta = _call_openai_json(
|
| 471 |
"flow.regenerate-partial", sys_prompt, user_prompt, temperature=0.5
|
| 472 |
)
|
| 473 |
+
if "explanation" not in data_dict or "proposed_steps" not in data_dict:
|
| 474 |
raise HTTPException(
|
| 475 |
status_code=422,
|
| 476 |
+
detail="INVALID_GENERATION: 'explanation' or 'proposed_steps' missing in JSON.",
|
| 477 |
)
|
| 478 |
|
| 479 |
return FlowPartialResponse(data=FlowPartialData(**data_dict), meta=meta)
|
|
|
|
| 485 |
|
| 486 |
|
| 487 |
class PlanDetailRequest(BaseModel):
|
| 488 |
+
request_id: str
|
| 489 |
+
finalized_steps: List[Any]
|
| 490 |
|
| 491 |
|
| 492 |
class LessonSection(BaseModel):
|
| 493 |
section_id: str
|
| 494 |
type: str
|
| 495 |
+
title: str
|
| 496 |
content: str
|
| 497 |
|
| 498 |
|
| 499 |
class PlanDetailData(BaseModel):
|
| 500 |
+
course_name: Optional[str] = None
|
| 501 |
sections: List[LessonSection]
|
| 502 |
|
| 503 |
|
|
|
|
| 516 |
)
|
| 517 |
user_prompt = f"""
|
| 518 |
Finalized steps (JSON array, opaque to you but describes lesson flow):
|
| 519 |
+
{json.dumps(req.finalized_steps, ensure_ascii=False)}
|
| 520 |
|
| 521 |
Return JSON:
|
| 522 |
{{
|
| 523 |
+
"course_name": "string",
|
| 524 |
"sections": [
|
| 525 |
{{
|
| 526 |
+
"section_id": "learning_objectives",
|
| 527 |
+
"type": "Learning Objectives",
|
| 528 |
+
"title": "Learning Objectives",
|
| 529 |
"content": "By the end of this lesson, students will be able to..."
|
| 530 |
}},
|
| 531 |
{{
|
| 532 |
+
"section_id": "lesson_activities",
|
| 533 |
+
"type": "Lesson Activities",
|
| 534 |
+
"title": "Lesson Activities",
|
| 535 |
"content": "### 1. Topic heading\\nDetailed explanation..."
|
| 536 |
}}
|
| 537 |
]
|
|
|
|
| 567 |
|
| 568 |
dimension: str # e.g. "TIME" | "DIFFICULTY" | "ENGAGEMENT"
|
| 569 |
level: str # e.g. "LIMITED" | "ADEQUATE" | "STRONG"
|
| 570 |
+
selected_text: Optional[str] = None
|
| 571 |
feedback: Optional[str] = None
|
| 572 |
|
| 573 |
|
| 574 |
class QuizAggregations(BaseModel):
|
| 575 |
+
average_score: Optional[float] = None
|
| 576 |
+
lowest_topic: Optional[str] = None
|
| 577 |
|
| 578 |
|
| 579 |
class ReflectionRequest(BaseModel):
|
| 580 |
+
request_id: str
|
| 581 |
+
lesson_steps: List[LessonStepForReflection] = Field(default_factory=list)
|
| 582 |
+
teach_annotations: List[TeachAnnotation] = Field(default_factory=list)
|
| 583 |
+
quiz_aggregations: Optional[QuizAggregations] = None
|
| 584 |
|
| 585 |
|
| 586 |
+
class ReflectionDetails(BaseModel):
|
| 587 |
+
description: Optional[str] = None
|
| 588 |
+
bullet_points: Optional[List[str]] = None
|
| 589 |
+
challenging_topics: Optional[List[str]] = None
|
| 590 |
+
issues: Optional[List[str]] = None
|
| 591 |
|
| 592 |
|
| 593 |
+
class ReflectionField(BaseModel):
|
| 594 |
status: str
|
| 595 |
+
summary: str
|
| 596 |
+
details: ReflectionDetails
|
| 597 |
|
| 598 |
|
| 599 |
+
class UpperSectionFields(BaseModel):
|
| 600 |
+
understanding: ReflectionField
|
| 601 |
+
engagement: ReflectionField
|
| 602 |
+
difficulty: ReflectionField
|
| 603 |
+
misconceptions: ReflectionField
|
| 604 |
|
| 605 |
|
| 606 |
+
class UpperSection(BaseModel):
|
| 607 |
+
description: Optional[str] = None
|
| 608 |
+
required: bool = True
|
| 609 |
+
fields: UpperSectionFields
|
| 610 |
|
| 611 |
|
| 612 |
class NextLessonSuggestion(BaseModel):
|
| 613 |
+
text: str
|
| 614 |
+
deep_link_type: Optional[str] = None
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
class LowerSection(BaseModel):
|
| 618 |
+
summary: str
|
| 619 |
+
description: Optional[str] = None
|
| 620 |
+
overall_summary: Optional[str] = None
|
| 621 |
+
strengths: List[str]
|
| 622 |
+
next_lesson_suggestions: List[NextLessonSuggestion]
|
| 623 |
|
| 624 |
|
| 625 |
class ReflectionData(BaseModel):
|
| 626 |
+
overall_status: str
|
| 627 |
+
upper_section: UpperSection
|
| 628 |
+
lower_section: LowerSection
|
|
|
|
|
|
|
| 629 |
|
| 630 |
|
| 631 |
class ReflectionResponse(BaseModel):
|
|
|
|
| 636 |
@router.post("/reflection/generate", response_model=ReflectionResponse)
|
| 637 |
def generate_reflection(req: ReflectionRequest):
|
| 638 |
"""Phase 4 (Reflect):基于反馈与测验数据生成反思看板。"""
|
| 639 |
+
lesson_steps = [s.model_dump() for s in req.lesson_steps]
|
| 640 |
+
annotations = [a.model_dump() for a in req.teach_annotations]
|
| 641 |
+
qa = req.quiz_aggregations.model_dump() if req.quiz_aggregations else {}
|
| 642 |
sys_prompt = (
|
| 643 |
"You are an AI teaching analytics assistant. "
|
| 644 |
+
"Given lesson steps, teacher annotations, and quiz aggregations, generate a reflection dashboard as JSON. "
|
| 645 |
+
"Use snake_case keys and match this structure: overall_status, upper_section(fields: understanding/engagement/"
|
| 646 |
+
"difficulty/misconceptions), and lower_section(summary/strengths/next_lesson_suggestions). "
|
| 647 |
+
"Output ONLY the JSON object that matches the schema."
|
| 648 |
)
|
| 649 |
user_prompt = f"""
|
| 650 |
Lesson steps:
|
|
|
|
| 658 |
|
| 659 |
Return JSON:
|
| 660 |
{{
|
| 661 |
+
"overall_status": "Overall: Progressing",
|
| 662 |
+
"upper_section": {{
|
| 663 |
+
"description": "One short sentence describing the upper section purpose",
|
| 664 |
+
"required": true,
|
| 665 |
+
"fields": {{
|
| 666 |
+
"understanding": {{
|
| 667 |
+
"status": "Low | Medium | High",
|
| 668 |
+
"summary": "Comprehension of core concepts",
|
| 669 |
+
"details": {{
|
| 670 |
+
"description": "A short paragraph",
|
| 671 |
+
"bullet_points": ["..."]
|
| 672 |
+
}}
|
| 673 |
+
}},
|
| 674 |
+
"engagement": {{
|
| 675 |
+
"status": "Low | Medium | High",
|
| 676 |
+
"summary": "Student participation and motivation",
|
| 677 |
+
"details": {{
|
| 678 |
+
"description": "A short paragraph",
|
| 679 |
+
"bullet_points": ["..."]
|
| 680 |
+
}}
|
| 681 |
+
}},
|
| 682 |
+
"difficulty": {{
|
| 683 |
+
"status": "Low | Medium | High",
|
| 684 |
+
"summary": "Challenging topics and areas",
|
| 685 |
+
"details": {{
|
| 686 |
+
"description": "A short paragraph",
|
| 687 |
+
"challenging_topics": ["Topic A (45% struggle)", "Topic B (38% struggle)"]
|
| 688 |
+
}}
|
| 689 |
+
}},
|
| 690 |
+
"misconceptions": {{
|
| 691 |
+
"status": "Low | Medium | High",
|
| 692 |
+
"summary": "Common misunderstandings identified",
|
| 693 |
+
"details": {{
|
| 694 |
+
"description": "A short paragraph",
|
| 695 |
+
"issues": ["Misconception A — evidence ...", "Misconception B — evidence ..."]
|
| 696 |
+
}}
|
| 697 |
+
}}
|
| 698 |
}}
|
| 699 |
+
}},
|
| 700 |
+
"lower_section": {{
|
| 701 |
+
"summary": "Lesson 1 Summary",
|
| 702 |
+
"description": "AI-generated overall assessment and recommendations",
|
| 703 |
+
"overall_summary": "One paragraph overall summary",
|
| 704 |
+
"strengths": ["..."],
|
| 705 |
+
"next_lesson_suggestions": [
|
| 706 |
+
{{ "text": "Add visual demos ...", "deep_link_type": "PREPARE_FLOW_EDIT" }}
|
| 707 |
+
]
|
| 708 |
+
}}
|
| 709 |
}}
|
| 710 |
"""
|
| 711 |
|
| 712 |
data_dict, meta = _call_openai_json("reflection.generate", sys_prompt, user_prompt, temperature=0.4)
|
| 713 |
+
required = ["overall_status", "upper_section", "lower_section"]
|
| 714 |
if any(k not in data_dict for k in required):
|
| 715 |
raise HTTPException(
|
| 716 |
status_code=422,
|
|
|
|
| 726 |
|
| 727 |
|
| 728 |
class ImprovementRequest(BaseModel):
|
| 729 |
+
request_id: str
|
| 730 |
+
reflection_reports: List[Any]
|
| 731 |
|
| 732 |
|
| 733 |
class ImprovementProposal(BaseModel):
|
| 734 |
title: str
|
| 735 |
priority: str
|
| 736 |
+
affected_weeks: Optional[str] = None
|
| 737 |
evidence: Optional[str] = None
|
| 738 |
+
root_cause: Optional[str] = None
|
| 739 |
+
proposed_solution: Optional[str] = None
|
| 740 |
+
expected_impact: Optional[str] = None
|
| 741 |
|
| 742 |
|
| 743 |
class ImprovementData(BaseModel):
|
|
|
|
| 752 |
@router.post("/improvement/generate", response_model=ImprovementResponse)
|
| 753 |
def generate_improvement(req: ImprovementRequest):
|
| 754 |
"""Phase 5 (Improve):基于过往反思报告生成跨期改进提案。"""
|
| 755 |
+
reports = json.dumps(req.reflection_reports, ensure_ascii=False)
|
| 756 |
sys_prompt = (
|
| 757 |
"You are an AI curriculum improvement assistant. "
|
| 758 |
"Given past reflection reports, generate high-level improvement proposals as JSON."
|
|
|
|
| 767 |
{{
|
| 768 |
"title": "Students struggle with gradient descent optimization",
|
| 769 |
"priority": "High Priority | Medium Priority | Low Priority",
|
| 770 |
+
"affected_weeks": "Week 2, 3",
|
| 771 |
"evidence": "45% reported difficulty...",
|
| 772 |
+
"root_cause": "Introduced too quickly without visual math foundation.",
|
| 773 |
+
"proposed_solution": "Split gradient descent into two lessons: (1) Intuition (2) Math.",
|
| 774 |
+
"expected_impact": "Increase scores by 15-20% and reduce frustration."
|
| 775 |
}}
|
| 776 |
]
|
| 777 |
}}
|
docs/AI_COURSEWARE_API_USAGE.md
CHANGED
|
@@ -80,7 +80,7 @@
|
|
| 80 |
|
| 81 |
前端已经在 `web/src/lib/api.ts` 中提供了 6 个结构化接口的封装函数,可直接在 React 组件中调用。
|
| 82 |
|
| 83 |
-
#### 3.1 生成大纲草案 `apiAiSyllabusGenerate`
|
| 84 |
|
| 85 |
```ts
|
| 86 |
import {
|
|
@@ -90,27 +90,27 @@ import {
|
|
| 90 |
} from "@/lib/api";
|
| 91 |
|
| 92 |
const req: AiSyllabusGenerateReq = {
|
| 93 |
-
|
| 94 |
context: {
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
},
|
| 101 |
};
|
| 102 |
|
| 103 |
const resp: AiSyllabusGenerateResp = await apiAiSyllabusGenerate(req);
|
| 104 |
-
// resp.data.
|
| 105 |
// resp.data.assessment -> 考核方式与比例说明
|
| 106 |
-
// resp.data.
|
| 107 |
-
// resp.data.
|
| 108 |
-
// resp.data.
|
| 109 |
// resp.data.syllabus -> Week 级别数组
|
| 110 |
// resp.meta -> 模型与性能信息
|
| 111 |
```
|
| 112 |
|
| 113 |
-
#### 3.2 局部重构大纲草案 `apiAiSyllabusRegeneratePartial`
|
| 114 |
|
| 115 |
```ts
|
| 116 |
import {
|
|
@@ -120,17 +120,17 @@ import {
|
|
| 120 |
} from "@/lib/api";
|
| 121 |
|
| 122 |
const req: AiSyllabusPartialReq = {
|
| 123 |
-
|
| 124 |
prompt: "Increase the difficulty and add more details on Transformer.",
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
assessment: "Weekly Programming Assignments...",
|
| 129 |
},
|
| 130 |
-
|
| 131 |
syllabus: [
|
| 132 |
{
|
| 133 |
-
|
| 134 |
title: "Introduction to Generative AI",
|
| 135 |
topics: ["History of AI"],
|
| 136 |
},
|
|
@@ -138,11 +138,11 @@ const req: AiSyllabusPartialReq = {
|
|
| 138 |
},
|
| 139 |
},
|
| 140 |
context: {
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
},
|
| 147 |
};
|
| 148 |
|
|
@@ -151,7 +151,7 @@ const resp: AiSyllabusPartialResp = await apiAiSyllabusRegeneratePartial(req);
|
|
| 151 |
// resp.data.proposed_draft.syllabus -> 用于替换未锁定 syllabus 的新大纲
|
| 152 |
```
|
| 153 |
|
| 154 |
-
#### 3.3 生成单个模块的教学流程 `apiAiFlowGenerate`
|
| 155 |
|
| 156 |
```ts
|
| 157 |
import {
|
|
@@ -161,26 +161,26 @@ import {
|
|
| 161 |
} from "@/lib/api";
|
| 162 |
|
| 163 |
const req: AiFlowGenerateReq = {
|
| 164 |
-
|
| 165 |
-
|
| 166 |
title: "Introduction to Generative AI",
|
| 167 |
-
|
| 168 |
topics: ["History of AI", "Transformer Architecture"],
|
| 169 |
-
|
| 170 |
},
|
| 171 |
-
|
| 172 |
};
|
| 173 |
|
| 174 |
const resp: AiFlowGenerateResp = await apiAiFlowGenerate(req);
|
| 175 |
-
// resp.data.steps[i].
|
| 176 |
-
// resp.data.steps[i].
|
| 177 |
-
// resp.data.steps[i].
|
| 178 |
-
// resp.data.steps[i].
|
| 179 |
-
// resp.data.steps[i].
|
| 180 |
-
// resp.data.steps[i].
|
| 181 |
```
|
| 182 |
|
| 183 |
-
#### 3.4 局部重构教学流程 `apiAiFlowRegeneratePartial`
|
| 184 |
|
| 185 |
```ts
|
| 186 |
import {
|
|
@@ -190,20 +190,20 @@ import {
|
|
| 190 |
} from "@/lib/api";
|
| 191 |
|
| 192 |
const req: AiFlowPartialReq = {
|
| 193 |
-
|
| 194 |
prompt: "Split the 30-min explanation into two shorter parts with a quiz in between.",
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
},
|
| 199 |
};
|
| 200 |
|
| 201 |
const resp: AiFlowPartialResp = await apiAiFlowRegeneratePartial(req);
|
| 202 |
// resp.data.explanation -> Copilot 气泡文案
|
| 203 |
-
// resp.data.
|
| 204 |
```
|
| 205 |
|
| 206 |
-
#### 3.5 教案长文 `apiAiPlanDetailGenerate`
|
| 207 |
|
| 208 |
```ts
|
| 209 |
import {
|
|
@@ -213,15 +213,16 @@ import {
|
|
| 213 |
} from "@/lib/api";
|
| 214 |
|
| 215 |
const req: AiPlanDetailReq = {
|
| 216 |
-
|
| 217 |
-
|
| 218 |
};
|
| 219 |
|
| 220 |
const resp: AiPlanDetailResp = await apiAiPlanDetailGenerate(req);
|
| 221 |
-
// resp.data.
|
|
|
|
| 222 |
```
|
| 223 |
|
| 224 |
-
#### 3.6 反思看板 `apiAiReflectionGenerate`
|
| 225 |
|
| 226 |
```ts
|
| 227 |
import {
|
|
@@ -231,25 +232,31 @@ import {
|
|
| 231 |
} from "@/lib/api";
|
| 232 |
|
| 233 |
const req: AiReflectionReq = {
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
| 236 |
{
|
| 237 |
-
|
| 238 |
-
|
|
|
|
| 239 |
feedback: "Students were confused by the math",
|
| 240 |
},
|
| 241 |
],
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
},
|
| 246 |
};
|
| 247 |
|
| 248 |
const resp: AiReflectionResp = await apiAiReflectionGenerate(req);
|
| 249 |
-
// resp.data.
|
|
|
|
|
|
|
| 250 |
```
|
| 251 |
|
| 252 |
-
#### 3.7 改进提案 `apiAiImprovementGenerate`
|
| 253 |
|
| 254 |
```ts
|
| 255 |
import {
|
|
@@ -259,8 +266,8 @@ import {
|
|
| 259 |
} from "@/lib/api";
|
| 260 |
|
| 261 |
const req: AiImprovementReq = {
|
| 262 |
-
|
| 263 |
-
|
| 264 |
};
|
| 265 |
|
| 266 |
const resp: AiImprovementResp = await apiAiImprovementGenerate(req);
|
|
|
|
| 80 |
|
| 81 |
前端已经在 `web/src/lib/api.ts` 中提供了 6 个结构化接口的封装函数,可直接在 React 组件中调用。
|
| 82 |
|
| 83 |
+
#### 3.1 生成大纲草案 `apiAiSyllabusGenerate`(snake_case)
|
| 84 |
|
| 85 |
```ts
|
| 86 |
import {
|
|
|
|
| 90 |
} from "@/lib/api";
|
| 91 |
|
| 92 |
const req: AiSyllabusGenerateReq = {
|
| 93 |
+
request_id: "req_cw_101",
|
| 94 |
context: {
|
| 95 |
+
course_name: "IST 345 Building Generative AI Application",
|
| 96 |
+
learning_outcome: "Understand LLMs and build a RAG application",
|
| 97 |
+
student_level: "BEGINNER",
|
| 98 |
+
teaching_focus: "PRACTICE_ORIENTED",
|
| 99 |
+
course_length: 4,
|
| 100 |
},
|
| 101 |
};
|
| 102 |
|
| 103 |
const resp: AiSyllabusGenerateResp = await apiAiSyllabusGenerate(req);
|
| 104 |
+
// resp.data.course_overview -> 课程整体简介(段落)
|
| 105 |
// resp.data.assessment -> 考核方式与比例说明
|
| 106 |
+
// resp.data.teaching_approach -> 教学方式描述
|
| 107 |
+
// resp.data.workload_expectation -> 学生每周学习负担预期
|
| 108 |
+
// resp.data.academic_integrity_policy -> 学术诚信说明(可直接挪用到 Syllabus)
|
| 109 |
// resp.data.syllabus -> Week 级别数组
|
| 110 |
// resp.meta -> 模型与性能信息
|
| 111 |
```
|
| 112 |
|
| 113 |
+
#### 3.2 局部重构大纲草案 `apiAiSyllabusRegeneratePartial`(snake_case)
|
| 114 |
|
| 115 |
```ts
|
| 116 |
import {
|
|
|
|
| 120 |
} from "@/lib/api";
|
| 121 |
|
| 122 |
const req: AiSyllabusPartialReq = {
|
| 123 |
+
request_id: "req_cw_101b",
|
| 124 |
prompt: "Increase the difficulty and add more details on Transformer.",
|
| 125 |
+
current_syllabus: {
|
| 126 |
+
locked_sections: {
|
| 127 |
+
course_overview: "This course provides...",
|
| 128 |
assessment: "Weekly Programming Assignments...",
|
| 129 |
},
|
| 130 |
+
unlocked_sections: {
|
| 131 |
syllabus: [
|
| 132 |
{
|
| 133 |
+
week_number: 1,
|
| 134 |
title: "Introduction to Generative AI",
|
| 135 |
topics: ["History of AI"],
|
| 136 |
},
|
|
|
|
| 138 |
},
|
| 139 |
},
|
| 140 |
context: {
|
| 141 |
+
course_name: "IST 345 Building Generative AI Application",
|
| 142 |
+
learning_outcome: "Understand LLMs and build a RAG application",
|
| 143 |
+
student_level: "BEGINNER",
|
| 144 |
+
teaching_focus: "PRACTICE_ORIENTED",
|
| 145 |
+
course_length: 4,
|
| 146 |
},
|
| 147 |
};
|
| 148 |
|
|
|
|
| 151 |
// resp.data.proposed_draft.syllabus -> 用于替换未锁定 syllabus 的新大纲
|
| 152 |
```
|
| 153 |
|
| 154 |
+
#### 3.3 生成单个模块的教学流程 `apiAiFlowGenerate`(snake_case)
|
| 155 |
|
| 156 |
```ts
|
| 157 |
import {
|
|
|
|
| 161 |
} from "@/lib/api";
|
| 162 |
|
| 163 |
const req: AiFlowGenerateReq = {
|
| 164 |
+
request_id: "req_cw_102",
|
| 165 |
+
module_context: {
|
| 166 |
title: "Introduction to Generative AI",
|
| 167 |
+
learning_objectives: ["Understand basic LLM concepts"],
|
| 168 |
topics: ["History of AI", "Transformer Architecture"],
|
| 169 |
+
duration_minutes: 90,
|
| 170 |
},
|
| 171 |
+
system_prompts: ["(可选) 来自 Improve 阶段的长期提示"],
|
| 172 |
};
|
| 173 |
|
| 174 |
const resp: AiFlowGenerateResp = await apiAiFlowGenerate(req);
|
| 175 |
+
// resp.data.steps[i].estimated_duration -> 单步预计时长(分钟)
|
| 176 |
+
// resp.data.steps[i].ai_understanding -> AI 对该步骤教学意图的摘要
|
| 177 |
+
// resp.data.steps[i].teacher_activity -> 教师在该步骤的具体行为/讲解要点
|
| 178 |
+
// resp.data.steps[i].student_activity -> 学生在该步骤预期进行的活动
|
| 179 |
+
// resp.data.steps[i].guiding_questions -> 建议的引导提问(Markdown 文本)
|
| 180 |
+
// resp.data.steps[i].required_materials -> 所���材料/设备清单
|
| 181 |
```
|
| 182 |
|
| 183 |
+
#### 3.4 局部重构教学流程 `apiAiFlowRegeneratePartial`(snake_case)
|
| 184 |
|
| 185 |
```ts
|
| 186 |
import {
|
|
|
|
| 190 |
} from "@/lib/api";
|
| 191 |
|
| 192 |
const req: AiFlowPartialReq = {
|
| 193 |
+
request_id: "req_cw_103",
|
| 194 |
prompt: "Split the 30-min explanation into two shorter parts with a quiz in between.",
|
| 195 |
+
current_flow: {
|
| 196 |
+
locked_steps: [{ id: "step_1", title: "Welcome", duration: 15 }],
|
| 197 |
+
unlocked_steps: [{ id: "step_2", title: "Heavy Explanation", duration: 30 }],
|
| 198 |
},
|
| 199 |
};
|
| 200 |
|
| 201 |
const resp: AiFlowPartialResp = await apiAiFlowRegeneratePartial(req);
|
| 202 |
// resp.data.explanation -> Copilot 气泡文案
|
| 203 |
+
// resp.data.proposed_steps -> 用于替换 unlocked_steps 的新步骤
|
| 204 |
```
|
| 205 |
|
| 206 |
+
#### 3.5 教案长文 `apiAiPlanDetailGenerate`(snake_case)
|
| 207 |
|
| 208 |
```ts
|
| 209 |
import {
|
|
|
|
| 213 |
} from "@/lib/api";
|
| 214 |
|
| 215 |
const req: AiPlanDetailReq = {
|
| 216 |
+
request_id: "req_cw_104",
|
| 217 |
+
finalized_steps: stepsArray, // 来自 Flow 阶段确定的完整 steps
|
| 218 |
};
|
| 219 |
|
| 220 |
const resp: AiPlanDetailResp = await apiAiPlanDetailGenerate(req);
|
| 221 |
+
// resp.data.course_name -> 课程名(可选)
|
| 222 |
+
// resp.data.sections -> 多个 section(含 section_id/type/title/content),content 支持 Markdown
|
| 223 |
```
|
| 224 |
|
| 225 |
+
#### 3.6 反思看板 `apiAiReflectionGenerate`(snake_case + 新结构)
|
| 226 |
|
| 227 |
```ts
|
| 228 |
import {
|
|
|
|
| 232 |
} from "@/lib/api";
|
| 233 |
|
| 234 |
const req: AiReflectionReq = {
|
| 235 |
+
request_id: "req_cw_105",
|
| 236 |
+
lesson_steps: [
|
| 237 |
+
{ step_number: 1, title: "Intro", type: "LECTURE", duration: 15 },
|
| 238 |
+
],
|
| 239 |
+
teach_annotations: [
|
| 240 |
{
|
| 241 |
+
dimension: "TIME",
|
| 242 |
+
level: "LIMITED",
|
| 243 |
+
selected_text: "Gradient Descent",
|
| 244 |
feedback: "Students were confused by the math",
|
| 245 |
},
|
| 246 |
],
|
| 247 |
+
quiz_aggregations: {
|
| 248 |
+
average_score: 72,
|
| 249 |
+
lowest_topic: "Matrix Operations",
|
| 250 |
},
|
| 251 |
};
|
| 252 |
|
| 253 |
const resp: AiReflectionResp = await apiAiReflectionGenerate(req);
|
| 254 |
+
// resp.data.overall_status
|
| 255 |
+
// resp.data.upper_section.fields.understanding / engagement / difficulty / misconceptions
|
| 256 |
+
// resp.data.lower_section.strengths / next_lesson_suggestions
|
| 257 |
```
|
| 258 |
|
| 259 |
+
#### 3.7 改进提案 `apiAiImprovementGenerate`(snake_case)
|
| 260 |
|
| 261 |
```ts
|
| 262 |
import {
|
|
|
|
| 266 |
} from "@/lib/api";
|
| 267 |
|
| 268 |
const req: AiImprovementReq = {
|
| 269 |
+
request_id: "req_cw_106",
|
| 270 |
+
reflection_reports: previousReflectionReports, // 来自 2.6 的历史数据数组
|
| 271 |
};
|
| 272 |
|
| 273 |
const resp: AiImprovementResp = await apiAiImprovementGenerate(req);
|
web/src/lib/api.ts
CHANGED
|
@@ -594,33 +594,33 @@ export type AiMeta = {
|
|
| 594 |
|
| 595 |
// 2.1 Generate Syllabus Preview
|
| 596 |
export type AiSyllabusContext = {
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
};
|
| 603 |
|
| 604 |
export type AiSyllabusGenerateReq = {
|
| 605 |
-
|
| 606 |
context: AiSyllabusContext;
|
| 607 |
-
|
| 608 |
};
|
| 609 |
|
| 610 |
export type AiWeekSyllabus = {
|
| 611 |
-
|
| 612 |
title: string;
|
| 613 |
-
|
| 614 |
topics: string[];
|
| 615 |
};
|
| 616 |
|
| 617 |
export type AiSyllabusGenerateResp = {
|
| 618 |
data: {
|
| 619 |
-
|
| 620 |
assessment?: string | null;
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
syllabus: AiWeekSyllabus[];
|
| 625 |
};
|
| 626 |
meta: AiMeta;
|
|
@@ -646,11 +646,12 @@ export async function apiAiSyllabusGenerate(
|
|
| 646 |
|
| 647 |
// 2.2 Regenerate Partial Syllabus
|
| 648 |
export type AiSyllabusLockedSections = {
|
| 649 |
-
|
| 650 |
assessment?: string | null;
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
|
|
|
| 654 |
};
|
| 655 |
|
| 656 |
export type AiSyllabusUnlockedSections = {
|
|
@@ -658,14 +659,14 @@ export type AiSyllabusUnlockedSections = {
|
|
| 658 |
};
|
| 659 |
|
| 660 |
export type AiCurrentSyllabus = {
|
| 661 |
-
|
| 662 |
-
|
| 663 |
};
|
| 664 |
|
| 665 |
export type AiSyllabusPartialReq = {
|
| 666 |
-
|
| 667 |
prompt: string;
|
| 668 |
-
|
| 669 |
context: AiSyllabusContext;
|
| 670 |
};
|
| 671 |
|
|
@@ -700,26 +701,26 @@ export async function apiAiSyllabusRegeneratePartial(
|
|
| 700 |
// 2.3 Generate Lesson Flow
|
| 701 |
export type AiModuleContext = {
|
| 702 |
title: string;
|
| 703 |
-
|
| 704 |
topics: string[];
|
| 705 |
-
|
| 706 |
};
|
| 707 |
|
| 708 |
export type AiFlowGenerateReq = {
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
};
|
| 713 |
|
| 714 |
export type AiLessonStep = {
|
| 715 |
type: string;
|
| 716 |
title: string;
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
};
|
| 724 |
|
| 725 |
export type AiFlowGenerateResp = {
|
|
@@ -753,31 +754,31 @@ export type AiSimpleStep = {
|
|
| 753 |
};
|
| 754 |
|
| 755 |
export type AiCurrentFlow = {
|
| 756 |
-
|
| 757 |
-
|
| 758 |
};
|
| 759 |
|
| 760 |
export type AiFlowPartialReq = {
|
| 761 |
-
|
| 762 |
prompt: string;
|
| 763 |
-
|
| 764 |
};
|
| 765 |
|
| 766 |
export type AiCopilotProposedStep = {
|
| 767 |
type: string;
|
| 768 |
title: string;
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
};
|
| 776 |
|
| 777 |
export type AiFlowPartialResp = {
|
| 778 |
data: {
|
| 779 |
explanation: string;
|
| 780 |
-
|
| 781 |
};
|
| 782 |
meta: AiMeta;
|
| 783 |
};
|
|
@@ -802,18 +803,19 @@ export async function apiAiFlowRegeneratePartial(
|
|
| 802 |
|
| 803 |
// 2.4 Generate/Polish Lesson Plan Detail
|
| 804 |
export type AiPlanDetailReq = {
|
| 805 |
-
|
| 806 |
-
|
| 807 |
};
|
| 808 |
|
| 809 |
export type AiLessonSection = {
|
| 810 |
section_id: string;
|
| 811 |
type: string;
|
|
|
|
| 812 |
content: string;
|
| 813 |
};
|
| 814 |
|
| 815 |
export type AiPlanDetailResp = {
|
| 816 |
-
data: { sections: AiLessonSection[] };
|
| 817 |
meta: AiMeta;
|
| 818 |
};
|
| 819 |
|
|
@@ -839,18 +841,18 @@ export async function apiAiPlanDetailGenerate(
|
|
| 839 |
export type AiTeachAnnotation = {
|
| 840 |
dimension: string;
|
| 841 |
level: string;
|
| 842 |
-
|
| 843 |
feedback?: string | null;
|
| 844 |
};
|
| 845 |
|
| 846 |
export type AiQuizAggregations = {
|
| 847 |
-
|
| 848 |
-
|
| 849 |
};
|
| 850 |
|
| 851 |
export type AiReflectionReq = {
|
| 852 |
-
|
| 853 |
-
|
| 854 |
step_number: number;
|
| 855 |
title: string;
|
| 856 |
type: string;
|
|
@@ -859,43 +861,52 @@ export type AiReflectionReq = {
|
|
| 859 |
student_activity?: string | null;
|
| 860 |
guiding_questions?: string | null;
|
| 861 |
}[];
|
| 862 |
-
|
| 863 |
-
|
| 864 |
};
|
| 865 |
|
| 866 |
-
export type
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
|
|
|
| 870 |
};
|
| 871 |
|
| 872 |
-
export type
|
| 873 |
status: string;
|
| 874 |
-
summary
|
|
|
|
| 875 |
};
|
| 876 |
|
| 877 |
-
export type
|
| 878 |
-
|
| 879 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
};
|
| 881 |
|
| 882 |
-
export type
|
| 883 |
-
|
| 884 |
-
|
| 885 |
};
|
| 886 |
|
| 887 |
-
export type
|
| 888 |
-
|
| 889 |
-
|
|
|
|
|
|
|
|
|
|
| 890 |
};
|
| 891 |
|
| 892 |
export type AiReflectionResp = {
|
| 893 |
data: {
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
misconceptions: AiReflectionMisconception;
|
| 898 |
-
nextLessonSuggestions: AiNextLessonSuggestion[];
|
| 899 |
};
|
| 900 |
meta: AiMeta;
|
| 901 |
};
|
|
@@ -920,18 +931,18 @@ export async function apiAiReflectionGenerate(
|
|
| 920 |
|
| 921 |
// 2.6 Generate Improvement Proposals
|
| 922 |
export type AiImprovementReq = {
|
| 923 |
-
|
| 924 |
-
|
| 925 |
};
|
| 926 |
|
| 927 |
export type AiImprovementProposal = {
|
| 928 |
title: string;
|
| 929 |
priority: string;
|
| 930 |
-
|
| 931 |
evidence?: string | null;
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
};
|
| 936 |
|
| 937 |
export type AiImprovementResp = {
|
|
|
|
| 594 |
|
| 595 |
// 2.1 Generate Syllabus Preview
|
| 596 |
export type AiSyllabusContext = {
|
| 597 |
+
course_name: string;
|
| 598 |
+
learning_outcome: string;
|
| 599 |
+
student_level: string;
|
| 600 |
+
teaching_focus: string;
|
| 601 |
+
course_length: number;
|
| 602 |
};
|
| 603 |
|
| 604 |
export type AiSyllabusGenerateReq = {
|
| 605 |
+
request_id: string;
|
| 606 |
context: AiSyllabusContext;
|
| 607 |
+
system_prompts?: string[] | null;
|
| 608 |
};
|
| 609 |
|
| 610 |
export type AiWeekSyllabus = {
|
| 611 |
+
week_number: number;
|
| 612 |
title: string;
|
| 613 |
+
learning_objectives: string[];
|
| 614 |
topics: string[];
|
| 615 |
};
|
| 616 |
|
| 617 |
export type AiSyllabusGenerateResp = {
|
| 618 |
data: {
|
| 619 |
+
course_overview?: string | null;
|
| 620 |
assessment?: string | null;
|
| 621 |
+
teaching_approach?: string | null;
|
| 622 |
+
workload_expectation?: string | null;
|
| 623 |
+
academic_integrity_policy?: string | null;
|
| 624 |
syllabus: AiWeekSyllabus[];
|
| 625 |
};
|
| 626 |
meta: AiMeta;
|
|
|
|
| 646 |
|
| 647 |
// 2.2 Regenerate Partial Syllabus
|
| 648 |
export type AiSyllabusLockedSections = {
|
| 649 |
+
course_overview?: string | null;
|
| 650 |
assessment?: string | null;
|
| 651 |
+
teaching_approach?: string | null;
|
| 652 |
+
workload_expectation?: string | null;
|
| 653 |
+
academic_integrity_policy?: string | null;
|
| 654 |
+
syllabus?: Partial<AiWeekSyllabus>[] | null;
|
| 655 |
};
|
| 656 |
|
| 657 |
export type AiSyllabusUnlockedSections = {
|
|
|
|
| 659 |
};
|
| 660 |
|
| 661 |
export type AiCurrentSyllabus = {
|
| 662 |
+
locked_sections: AiSyllabusLockedSections;
|
| 663 |
+
unlocked_sections: AiSyllabusUnlockedSections;
|
| 664 |
};
|
| 665 |
|
| 666 |
export type AiSyllabusPartialReq = {
|
| 667 |
+
request_id: string;
|
| 668 |
prompt: string;
|
| 669 |
+
current_syllabus: AiCurrentSyllabus;
|
| 670 |
context: AiSyllabusContext;
|
| 671 |
};
|
| 672 |
|
|
|
|
| 701 |
// 2.3 Generate Lesson Flow
|
| 702 |
export type AiModuleContext = {
|
| 703 |
title: string;
|
| 704 |
+
learning_objectives: string[];
|
| 705 |
topics: string[];
|
| 706 |
+
duration_minutes: number;
|
| 707 |
};
|
| 708 |
|
| 709 |
export type AiFlowGenerateReq = {
|
| 710 |
+
request_id: string;
|
| 711 |
+
module_context: AiModuleContext;
|
| 712 |
+
system_prompts?: string[] | null;
|
| 713 |
};
|
| 714 |
|
| 715 |
export type AiLessonStep = {
|
| 716 |
type: string;
|
| 717 |
title: string;
|
| 718 |
+
estimated_duration: number;
|
| 719 |
+
ai_understanding: string;
|
| 720 |
+
teacher_activity?: string | null;
|
| 721 |
+
student_activity?: string | null;
|
| 722 |
+
guiding_questions?: string | null;
|
| 723 |
+
required_materials?: string | null;
|
| 724 |
};
|
| 725 |
|
| 726 |
export type AiFlowGenerateResp = {
|
|
|
|
| 754 |
};
|
| 755 |
|
| 756 |
export type AiCurrentFlow = {
|
| 757 |
+
locked_steps: AiSimpleStep[];
|
| 758 |
+
unlocked_steps: AiSimpleStep[];
|
| 759 |
};
|
| 760 |
|
| 761 |
export type AiFlowPartialReq = {
|
| 762 |
+
request_id: string;
|
| 763 |
prompt: string;
|
| 764 |
+
current_flow: AiCurrentFlow;
|
| 765 |
};
|
| 766 |
|
| 767 |
export type AiCopilotProposedStep = {
|
| 768 |
type: string;
|
| 769 |
title: string;
|
| 770 |
+
estimated_duration: number;
|
| 771 |
+
ai_understanding: string;
|
| 772 |
+
teacher_activity?: string | null;
|
| 773 |
+
student_activity?: string | null;
|
| 774 |
+
guiding_questions?: string | null;
|
| 775 |
+
required_materials?: string | null;
|
| 776 |
};
|
| 777 |
|
| 778 |
export type AiFlowPartialResp = {
|
| 779 |
data: {
|
| 780 |
explanation: string;
|
| 781 |
+
proposed_steps: AiCopilotProposedStep[];
|
| 782 |
};
|
| 783 |
meta: AiMeta;
|
| 784 |
};
|
|
|
|
| 803 |
|
| 804 |
// 2.4 Generate/Polish Lesson Plan Detail
|
| 805 |
export type AiPlanDetailReq = {
|
| 806 |
+
request_id: string;
|
| 807 |
+
finalized_steps: any[];
|
| 808 |
};
|
| 809 |
|
| 810 |
export type AiLessonSection = {
|
| 811 |
section_id: string;
|
| 812 |
type: string;
|
| 813 |
+
title: string;
|
| 814 |
content: string;
|
| 815 |
};
|
| 816 |
|
| 817 |
export type AiPlanDetailResp = {
|
| 818 |
+
data: { course_name?: string | null; sections: AiLessonSection[] };
|
| 819 |
meta: AiMeta;
|
| 820 |
};
|
| 821 |
|
|
|
|
| 841 |
export type AiTeachAnnotation = {
|
| 842 |
dimension: string;
|
| 843 |
level: string;
|
| 844 |
+
selected_text?: string | null;
|
| 845 |
feedback?: string | null;
|
| 846 |
};
|
| 847 |
|
| 848 |
export type AiQuizAggregations = {
|
| 849 |
+
average_score?: number | null;
|
| 850 |
+
lowest_topic?: string | null;
|
| 851 |
};
|
| 852 |
|
| 853 |
export type AiReflectionReq = {
|
| 854 |
+
request_id: string;
|
| 855 |
+
lesson_steps?: {
|
| 856 |
step_number: number;
|
| 857 |
title: string;
|
| 858 |
type: string;
|
|
|
|
| 861 |
student_activity?: string | null;
|
| 862 |
guiding_questions?: string | null;
|
| 863 |
}[];
|
| 864 |
+
teach_annotations: AiTeachAnnotation[];
|
| 865 |
+
quiz_aggregations?: AiQuizAggregations | null;
|
| 866 |
};
|
| 867 |
|
| 868 |
+
export type AiReflectionDetails = {
|
| 869 |
+
description?: string | null;
|
| 870 |
+
bullet_points?: string[] | null;
|
| 871 |
+
challenging_topics?: string[] | null;
|
| 872 |
+
issues?: string[] | null;
|
| 873 |
};
|
| 874 |
|
| 875 |
+
export type AiReflectionField = {
|
| 876 |
status: string;
|
| 877 |
+
summary: string;
|
| 878 |
+
details: AiReflectionDetails;
|
| 879 |
};
|
| 880 |
|
| 881 |
+
export type AiUpperSection = {
|
| 882 |
+
description?: string | null;
|
| 883 |
+
required: boolean;
|
| 884 |
+
fields: {
|
| 885 |
+
understanding: AiReflectionField;
|
| 886 |
+
engagement: AiReflectionField;
|
| 887 |
+
difficulty: AiReflectionField;
|
| 888 |
+
misconceptions: AiReflectionField;
|
| 889 |
+
};
|
| 890 |
};
|
| 891 |
|
| 892 |
+
export type AiNextLessonSuggestion = {
|
| 893 |
+
text: string;
|
| 894 |
+
deep_link_type?: string | null;
|
| 895 |
};
|
| 896 |
|
| 897 |
+
export type AiLowerSection = {
|
| 898 |
+
summary: string;
|
| 899 |
+
description?: string | null;
|
| 900 |
+
overall_summary?: string | null;
|
| 901 |
+
strengths: string[];
|
| 902 |
+
next_lesson_suggestions: AiNextLessonSuggestion[];
|
| 903 |
};
|
| 904 |
|
| 905 |
export type AiReflectionResp = {
|
| 906 |
data: {
|
| 907 |
+
overall_status: string;
|
| 908 |
+
upper_section: AiUpperSection;
|
| 909 |
+
lower_section: AiLowerSection;
|
|
|
|
|
|
|
| 910 |
};
|
| 911 |
meta: AiMeta;
|
| 912 |
};
|
|
|
|
| 931 |
|
| 932 |
// 2.6 Generate Improvement Proposals
|
| 933 |
export type AiImprovementReq = {
|
| 934 |
+
request_id: string;
|
| 935 |
+
reflection_reports: any[];
|
| 936 |
};
|
| 937 |
|
| 938 |
export type AiImprovementProposal = {
|
| 939 |
title: string;
|
| 940 |
priority: string;
|
| 941 |
+
affected_weeks?: string | null;
|
| 942 |
evidence?: string | null;
|
| 943 |
+
root_cause?: string | null;
|
| 944 |
+
proposed_solution?: string | null;
|
| 945 |
+
expected_impact?: string | null;
|
| 946 |
};
|
| 947 |
|
| 948 |
export type AiImprovementResp = {
|