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:
@@ -13100,7 +13100,7 @@
|
||||
},
|
||||
"lesson_preparation": {
|
||||
"path": "src/modules/lesson-preparation",
|
||||
"description": "教师备课模块:基于教材章节创建课案(节点图编辑器 React Flow,v2 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% 透明度,选中时完整显示。数据模型 v3:LessonPlanDocument 含 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.ts(data-access.ts re-export)",
|
||||
"purpose": "基于模板构建初始课案内容(v2 nodes+edges)"
|
||||
"purpose": "基于模板构建初始课案内容(v3 nodes+edges+anchors+textbookContentNodeId)"
|
||||
},
|
||||
{
|
||||
"name": "migrateV1ToV2",
|
||||
"file": "lib/document-migration.ts(data-access.ts re-export)",
|
||||
"purpose": "v1→v2 迁移:将旧 blocks 数组转换为 nodes + 线性 edges(节点按网格布局),使用类型守卫 isV1Document/isV2Document 替代 as 断言"
|
||||
},
|
||||
{
|
||||
"name": "migrateV2ToV3",
|
||||
"file": "lib/document-migration.ts(data-access.ts re-export)",
|
||||
"purpose": "v2→v3 迁移:注入 textbook_content 节点(draggable=false)、初始化 anchors 数组、设置 textbookContentNodeId;可选传入 chapterId/chapterContent 填充正文"
|
||||
},
|
||||
{
|
||||
"name": "normalizeDocument",
|
||||
"file": "lib/document-migration.ts(data-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 返回初始 data(11 种定制节点 + 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 组件"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user