feat(ai): 新增 AI 模块并集成至备课/错题集/试卷/改题四大业务场景

- 新增 src/modules/ai 独立模块,遵循三层架构(actions → services → shared/lib/ai)
- 通过 AiClientProvider + useAiClient 实现 React Context 依赖注入,业务组件零直接 import
- 6 个 Server Actions 均调用 requirePermission() 权限校验,返回 ActionState<T>
- withAiTracking 统一埋点,覆盖 chat/similar_question/grading_assist/lesson_content/question_variant/weakness_analysis
- 集成场景:作业批改 AiGradingAssist、错题集 AiErrorBookAnalysis、备课 AiLessonContentGenerator、试卷 AiQuestionVariantGenerator
- 全量 i18n(en/zh-CN ai.json),Error Boundary + Skeleton 边界处理
- 同步架构图 004/005,新增审计报告 ai-module-audit-report.md
This commit is contained in:
SpecialX
2026-06-23 00:52:39 +08:00
parent ec87cd9efa
commit 21c5eba96c
40 changed files with 4885 additions and 169 deletions

View File

@@ -13100,7 +13100,7 @@
},
"lesson_preparation": {
"path": "src/modules/lesson-preparation",
"description": "教师备课模块:基于教材章节创建课案(节点图编辑器 React Flowv2 nodes+edges 数据结构),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react旧 v1 数据通过 migrateV1ToV2() 自动迁移。V2 审计修复Server Actions i18n + 错误码模式、SYSTEM_TEMPLATES i18n 化、as unknown as 类型断言清零、a11y 深度修复、Tracker 埋点接入",
"description": "教师备课模块v3 锚点画布以课文正文为核心主体textbook_content 节点置于画布中央draggable=false 可缩放),教学目标/重难点等 11 种定制节点围绕正文组织。两种锚定机制range选择一段文本关联节点未选中 opacity:0+ point插入带圈数字占位符未选中 opacity:0.3)。锚点连线默认 10% 透明度,选中时完整显示。数据模型 v3LessonPlanDocument 含 textbookContentNodeId、nodes、edges、anchors 数组。anchor-injector 实现 Markdown 纯文本偏移映射与占位符注入。实时拖动onNodeDrag 替代 onNodeDragStop。v1→v2→v3 链式迁移。强制选定教材+章节才能备课template-picker 四步流程)。保留模板、版本管理、知识点标注、题目创建/拉取、作业发布能力",
"exports": {
"dataAccess": [
{
@@ -13141,17 +13141,97 @@
{
"name": "buildInitialContent",
"file": "lib/document-migration.tsdata-access.ts re-export",
"purpose": "基于模板构建初始课案内容v2 nodes+edges"
"purpose": "基于模板构建初始课案内容v3 nodes+edges+anchors+textbookContentNodeId"
},
{
"name": "migrateV1ToV2",
"file": "lib/document-migration.tsdata-access.ts re-export",
"purpose": "v1→v2 迁移:将旧 blocks 数组转换为 nodes + 线性 edges节点按网格布局使用类型守卫 isV1Document/isV2Document 替代 as 断言"
},
{
"name": "migrateV2ToV3",
"file": "lib/document-migration.tsdata-access.ts re-export",
"purpose": "v2→v3 迁移:注入 textbook_content 节点draggable=false、初始化 anchors 数组、设置 textbookContentNodeId可选传入 chapterId/chapterContent 填充正文"
},
{
"name": "normalizeDocument",
"file": "lib/document-migration.tsdata-access.ts re-export",
"purpose": "规范化:确保 content 为 v2 格式,兼容旧 v1 数据(自动调用 migrateV1ToV2"
"purpose": "规范化:确保 content 为 v3 格式,兼容旧 v1/v2 数据(自动调用 migrateV1ToV2 → migrateV2ToV3 链式迁移"
},
{
"name": "buildDefaultSkeleton",
"file": "lib/document-migration.ts",
"purpose": "v3 默认骨架:生成 1 个 textbook_content 节点(画布中央)+ 10 个教学节点objective/key_point/import/new_teaching/consolidation/summary/homework/blackboard/text_study/reflection+ 默认 flow edges"
},
{
"name": "defaultDataForType",
"file": "lib/document-migration.ts",
"purpose": "按 BlockType 返回初始 data11 种定制节点 + rich_text供 addNode/buildDefaultSkeleton 使用"
},
{
"name": "isTextbookContentNode",
"file": "lib/document-migration.ts",
"purpose": "类型守卫:判断节点是否为 textbook_content 类型"
},
{
"name": "isAnchorEdge",
"file": "lib/document-migration.ts",
"purpose": "类型守卫:判断边是否为 anchor 锚点连线vs flow 流程连线)"
},
{
"name": "getAnchorsForNode",
"file": "lib/document-migration.ts",
"purpose": "按 nodeId 过滤 anchors 数组"
},
{
"name": "getActiveAnchorIds",
"file": "lib/document-migration.ts",
"purpose": "获取选中节点关联的 anchor ID 集合(用于正文渲染时高亮)"
},
{
"name": "getAnchorEdges",
"file": "lib/document-migration.ts",
"purpose": "获取锚点连线列表(用于 rf-mappers 区分透明度策略)"
},
{
"name": "markdownToPlainText",
"file": "lib/anchor-injector.ts",
"purpose": "Markdown 转纯文本去除语法标记用于建立纯文本偏移↔Markdown 偏移映射"
},
{
"name": "injectPlaceholders",
"file": "lib/anchor-injector.ts",
"purpose": "将 anchors 注入 Markdown 正文range 锚点包裹为 [[anchor:id]]...[[/anchor:id]]point 锚点插入 ①②③ 带圈数字"
},
{
"name": "parseAnchoredText",
"file": "lib/anchor-injector.ts",
"purpose": "解析注入后的 Markdown 为段落数组text/anchor-range/anchor-point供 textbook-content-node 自定义渲染"
},
{
"name": "getNextPointIndex",
"file": "lib/anchor-injector.ts",
"purpose": "计算下一个 point 锚点的带圈数字序号"
},
{
"name": "toCircledNumber",
"file": "lib/anchor-injector.ts",
"purpose": "数字转带圈数字字符1→①, 2→②, ..."
},
{
"name": "relocateAnchors",
"file": "lib/anchor-injector.ts",
"purpose": "正文变更后按文本相似度重定位锚点偏移量"
},
{
"name": "getTextbooksForPicker",
"file": "data-access.ts",
"purpose": "获取教材列表id+title供 template-picker 选择教材"
},
{
"name": "getChaptersForPicker",
"file": "data-access.ts",
"purpose": "按 textbookId 获取章节树(含 children 嵌套),供 template-picker 选择章节"
},
{
"name": "getLessonPlanVersions",
@@ -13304,6 +13384,18 @@
"permission": "LESSON_PLAN_READ",
"file": "actions-kp.ts",
"purpose": "获取知识点选项(委托 textbooks data-access"
},
{
"name": "getTextbooksForPickerAction",
"permission": "LESSON_PLAN_READ",
"file": "actions.ts",
"purpose": "获取教材列表(供 template-picker 选择教材)"
},
{
"name": "getChaptersForPickerAction",
"permission": "LESSON_PLAN_READ",
"file": "actions.ts",
"purpose": "按 textbookId 获取章节树(供 template-picker 选择章节)"
}
]
},
@@ -13322,6 +13414,7 @@
"constants.ts",
"schema.ts",
"lib/document-migration.ts",
"lib/anchor-injector.ts",
"lib/node-summary.ts",
"lib/rf-mappers.ts",
"config/block-registry.tsx",
@@ -13346,6 +13439,7 @@
"components/node-editor.tsx",
"components/node-edit-panel.tsx",
"components/nodes/lesson-node.tsx",
"components/nodes/textbook-content-node.tsx",
"components/lesson-plan-error-boundary.tsx",
"components/lesson-plan-skeleton.tsx",
"components/block-renderer.tsx",
@@ -13358,7 +13452,14 @@
"components/blocks/rich-text-block.tsx",
"components/blocks/text-study-block.tsx",
"components/blocks/exercise-block.tsx",
"components/blocks/reflection-block.tsx"
"components/blocks/reflection-block.tsx",
"components/blocks/objective-block.tsx",
"components/blocks/key-point-block.tsx",
"components/blocks/import-block.tsx",
"components/blocks/new-teaching-block.tsx",
"components/blocks/summary-block.tsx",
"components/blocks/homework-block.tsx",
"components/blocks/blackboard-block.tsx"
],
"i18n": {
"namespace": "lessonPreparation",
@@ -15082,6 +15183,106 @@
"actions.getQuestionsAction"
]
}
},
"ai": {
"dependsOn": [
"shared"
],
"uses": {
"shared": [
"db",
"auth-guard.requirePermission",
"lib.ai.createAiChatCompletion",
"types.permissions",
"types.action-state",
"lib.track-event.trackEvent",
"i18n.messages"
]
},
"exports": {
"actions": [
"aiChatAction",
"suggestSimilarQuestionsAction",
"suggestGradingAction",
"generateLessonContentAction",
"generateQuestionVariantAction",
"analyzeWeaknessAction"
],
"types": [
"AiService",
"AiClientService",
"AiChatMessage",
"AiChatResult",
"SimilarQuestionInput",
"SimilarQuestionResult",
"GradingInput",
"GradingSuggestion",
"LessonContentInput",
"LessonContentResult",
"QuestionVariantInput",
"QuestionVariantResult",
"WeaknessAnalysisInput",
"WeaknessAnalysisResult",
"AiCapability"
],
"components": [
"AiGradingAssist",
"AiErrorBookAnalysis",
"AiLessonContentGenerator",
"AiQuestionVariantGenerator",
"AiChatPanel",
"AiErrorBoundary",
"AiSuggestionSkeleton",
"AiProviderSelector"
],
"hooks": [
"useAiClient",
"useAiClientOptional",
"useAiChat",
"useAiSuggestion"
],
"providers": [
"AiClientProvider"
],
"services": [
"DefaultAiService",
"createAiService",
"safeAiCall"
]
},
"integrations": {
"homework": {
"component": "AiGradingAssist",
"page": "teacher/homework/submissions/[submissionId]",
"capability": "grading-assist"
},
"error-book": {
"component": "AiErrorBookAnalysis",
"page": "student/error-book",
"capability": "similar-question, weakness-analysis"
},
"lesson-preparation": {
"component": "AiLessonContentGenerator",
"page": "teacher/lesson-plans/[planId]/edit",
"capability": "lesson-content"
},
"exams": {
"component": "AiQuestionVariantGenerator",
"page": "teacher/exams/[id]/build",
"capability": "question-variant"
}
},
"permissions": [
"ai:chat",
"ai:configure"
],
"i18n": {
"namespace": "ai",
"files": [
"shared/i18n/messages/zh-CN/ai.json",
"shared/i18n/messages/en/ai.json"
]
}
}
},
"moduleDependencyGraph": {
@@ -15112,7 +15313,8 @@
"diagnostic",
"elective",
"onboarding",
"error-book"
"error-book",
"ai"
],
"edges": [
{
@@ -15372,6 +15574,36 @@
"to": "questions",
"type": "data-access",
"description": "通过 questions/actions.getQuestionsAction 查询题库(手动添加错题时)"
},
{
"from": "ai",
"to": "shared",
"type": "normal",
"description": "使用 lib/ai.createAiChatCompletion、auth-guard.requirePermission、types.permissions、types.action-state"
},
{
"from": "homework",
"to": "ai",
"type": "context",
"description": "通过 AiClientProvider 注入 AiClientService使用 AiGradingAssist 组件"
},
{
"from": "error-book",
"to": "ai",
"type": "context",
"description": "通过 AiClientProvider 注入 AiClientService使用 AiErrorBookAnalysis 组件"
},
{
"from": "lesson-preparation",
"to": "ai",
"type": "context",
"description": "通过 AiClientProvider 注入 AiClientService使用 AiLessonContentGenerator 组件"
},
{
"from": "exams",
"to": "ai",
"type": "context",
"description": "通过 AiClientProvider 注入 AiClientService使用 AiQuestionVariantGenerator 组件"
}
]
},