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

@@ -1686,20 +1686,30 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
> 架构变更2026-06-21编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react。数据结构从 v1blocks 数组)升级到 v2nodes + edges 节点图),旧数据通过 `migrateV1ToV2()` 自动迁移。
> 架构变更2026-06-23数据结构从 v2 升级到 v3课文锚点画布格式。正文节点textbook_content作为画布中央核心教学节点围绕正文组织。新增锚点系统range/point 两种锚定方式、占位符注入算法、11 种定制节点类型。旧数据通过 `migrateV2ToV3()` 自动迁移。
**数据结构**
- v1已废弃仅向后兼容读取`{ version: 1, blocks: Block[] }`
- v2当前`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }`
- `LessonPlanNode``Block` + `position: { x, y }`(画布坐标)
- `LessonPlanEdge``{ id, source, target, sourceHandle?, targetHandle? }`(节点间连线
- v2已废弃,仅向后兼容读取`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }`
- v3当前`{ version: 3, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }`
- `TextbookContentNode`:正文节点(`draggable: false`,画布中央,可缩放
- `LessonPlanNode`:教学节点(`Block` + `position`,可拖动可连线)
- `NodeAnchor`:锚点(`{ id, nodeId, type: "range"|"point", start, end?, textPreview?, invalid? }`
- `AnchorEdge`:锚点连线(教学节点 → 正文节点,默认 10% 透明度)
- `FlowEdge`:流程连线(教学节点 → 教学节点)
**导出函数**
- Data-access`data-access.ts``getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2`v1→v2 迁移blocks 数组转换为 nodes + 线性 edges/ `normalizeDocument`(规范化:确保 content 为 v2 格式,兼容旧数据)
- Data-access`data-access.ts``getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument`v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton`v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById`
- Lib`lib/document-migration.ts``defaultDataForType` / `migrateV1ToV2` / `migrateV2ToV3` / `normalizeDocument` / `buildInitialContent` / `buildDefaultSkeleton` / `isTextbookContentNode` / `isAnchorEdge` / `getAnchorsForNode` / `getActiveAnchorIds` / `getAnchorEdges`
- Lib`lib/anchor-injector.ts``markdownToPlainText` / `injectPlaceholders` / `parseAnchoredText` / `toCircledNumber` / `getNextPointIndex` / `relocateAnchors` / `getAnchorColor`
- Lib`lib/node-summary.ts``getNodeSummary` / `getTextbookContentSummary` / `getNodeColor` / `NODE_COLORS`
- Lib`lib/rf-mappers.ts``toRfNodes`(支持 textbook_content 节点)/ `toRfEdges`(区分 anchor/flow 边透明度)/ `fromRfEdges`
- Data-access-versions`data-access-versions.ts``getLessonPlanVersions` / `createLessonPlanVersion` / `getVersionContent` / `revertToVersion` / `pruneAutoVersions`
- Data-access-templates`data-access-templates.ts``getLessonPlanTemplates` / `saveAsTemplate` / `deletePersonalTemplate`
- Data-access-knowledge`data-access-knowledge.ts``getLessonPlansByKnowledgePoint` / `getLessonPlansByQuestion`
- Publish-service`publish-service.ts``publishLessonPlanHomework`
- AI-suggest`ai-suggest.ts``suggestKnowledgePoints`
- Actions`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction`
- Actions`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction` / `getTextbooksForPickerAction` / `getChaptersForPickerAction`
**依赖关系**
- 依赖:`shared/*``@/auth``shared/lib/ai``@xyflow/react`(节点图编辑器)、`textbooks`(只读章节/知识点树)、`questions`(创建/查询题目)、`exams`(创建 exam 草稿)、`homework`(创建作业下发)、`classes`(查询教师班级)、`files`(附件)
@@ -1729,13 +1739,14 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**文件清单**
| 文件 | 职责 |
|------|------|
| `types.ts` | 类型定义(含 v1/v2 文档类型、LessonPlanNode、LessonPlanEdge |
| `types.ts` | 类型定义(含 v1/v2/v3 文档类型、TextbookContentNode、LessonPlanNode、NodeAnchor、AnchorEdge、FlowEdge、11 种 BlockData 接口 |
| `constants.ts` | 常量定义 |
| `schema.ts` | Zod 验证 |
| `lib/document-migration.ts` | **纯函数**v1→v2 迁移migrateV1ToV2/ 规范化normalizeDocument/ 初始内容构建buildInitialContent,使用类型守卫 isV1Document/isV2Document 替代 as 断言 |
| `lib/node-summary.ts` | **纯函数**getNodeSummary接受翻译函数注入支持 i18n+ NODE_COLORS + getNodeColor |
| `lib/rf-mappers.ts` | **纯函数**toRfNodes/toRfEdges/fromRfEdgesLessonPlanNode/Edge ↔ React Flow Node/Edge 映射) |
| `config/block-registry.tsx` | **配置驱动**BLOCK_REGISTRY 注册表 + getBlockComponent/isRichTextBlocknode-edit-panel 通过配置渲染 Block |
| `lib/document-migration.ts` | **纯函数**v1→v2migrateV1ToV2/ v2→v3migrateV2ToV3/ 规范化normalizeDocument,兼容 v1/v2/v3/ 初始内容buildInitialContent/ 默认骨架buildDefaultSkeleton10 节点 + 正文节点)/ defaultDataForType / 工具函数isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges |
| `lib/anchor-injector.ts` | **纯函数**锚点注入算法markdownToPlainText/injectPlaceholders/parseAnchoredText/toCircledNumber/getNextPointIndex/relocateAnchors/getAnchorColor |
| `lib/node-summary.ts` | **纯函数**getNodeSummary支持 11 种节点类型)+ getTextbookContentSummary + NODE_COLORS + getNodeColor |
| `lib/rf-mappers.ts` | **纯函数**toRfNodes支持 textbook_content 节点 + 锚点回调)/ toRfEdges区分 anchor/flow 边透明度)/ fromRfEdges |
| `config/block-registry.tsx` | **配置驱动**BLOCK_REGISTRY 注册表 + BlockRendererswitch 渲染 11 种定制节点 + textbook_content |
| `providers/lesson-plan-provider.tsx` | **Provider + ContextP1-5/P1-7/P2-4/V2-6**LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置TEACHER/ADMIN/STUDENT/PARENT、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTrackerhooksuseLessonPlanContextSafe返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafeV2-6 新增,返回 noopTracker 不抛错) |
| `services/default-data-service.ts` | **默认数据服务实现**createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现,测试可替换为 mock |
| `data-access.ts` | 课案 CRUD + 模板查询migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容buildScopeCondition 按 scope 类型精确过滤 P0-3V2-1抛出 `LessonPlanDataError` 错误码V2-3mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫) |
@@ -1749,14 +1760,15 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes通过对方 data-access 调用无直查跨模块表V2-1抛出 `PublishServiceError` 错误码V2-3显式字段映射替代 `as unknown as` |
| `ai-suggest.ts` | AI 知识点建议服务 |
| `seed-templates.ts` | 模板种子数据 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook基于 zustand支持 nodes/edges 操作addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook基于 zustand支持 nodes/edges/anchors 操作addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode + 锚点操作 addAnchor/removeAnchor/updateAnchor + 正文节点操作 updateTextbookContent/getTextbookContentNode实时拖动 |
| `components/lesson-plan-list.tsx` | 课案列表i18n 已接入) |
| `components/lesson-plan-card.tsx` | 课案卡片i18n 已接入V2-6duplicate/archive 调用 tracker.track |
| `components/lesson-plan-filters.tsx` | 课案筛选器i18n 已接入V2-53 个表单元素 label htmlFor 关联) |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPaneli18n 已接入V2-6handleManualSave 调用 tracker.track |
| `components/node-editor.tsx` | **节点图画布**React Flow使用 lib/rf-mappers + lib/node-summary 纯函数i18n 已接入V2-4MiniMap 复用 getNodeColorV2-5role=application + 键盘导航配置) |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(配置驱动渲染 Block通过 getBlockComponent + LessonPlanErrorBoundary 包裹i18n 已接入) |
| `components/nodes/lesson-node.tsx` | **自定义节点组件**(使用 lib/node-summary 的 getNodeSummary/getNodeColori18n 已接入) |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPaneli18n 已接入V2-6handleManualSave 调用 tracker.trackV3顶部工具栏显示教材/章节标题指示器 |
| `components/node-editor.tsx` | **节点图画布**React Flow使用 lib/rf-mappers + lib/node-summary 纯函数i18n 已接入V2-4MiniMap 复用 getNodeColorV2-5role=application + 键盘导航配置V3注册 textbook_content 节点类型 + 锚点回调 + 实时拖动 |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(配置驱动渲染 Block通过 BlockRenderer + LessonPlanErrorBoundary 包裹i18n 已接入V3处理 textbook_content 节点,教学节点类型收窄 |
| `components/nodes/lesson-node.tsx` | **自定义教学节点组件**(使用 lib/node-summary 的 getNodeSummary/getNodeColori18n 已接入) |
| `components/nodes/textbook-content-node.tsx` | **正文节点组件**V3 新增ReactMarkdown 渲染正文 + 锚点注入 + 文本选择range 锚定)+ 点击位置point 锚定)+ 缩放控制 + 锚点浮动菜单 |
| `components/lesson-plan-error-boundary.tsx` | **错误边界**LessonPlanErrorBoundary 类组件,支持 fallback 和 onError 回调 |
| `components/lesson-plan-skeleton.tsx` | **骨架屏**VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton |
| `components/block-renderer.tsx` | ⚠️ @deprecated Block 渲染器(已被 NodeEditor 替代,保留向后兼容) |
@@ -1804,12 +1816,15 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| teaching_head | `grade_managed` | 查看所管年级学生的错题分析 |
**核心算法SM-2 间隔重复(简化版)**
- 独立纯函数模块:[sm2-algorithm.ts](file:///e:/Desktop/CICD/src/modules/error-book/sm2-algorithm.ts)(可替换为其他算法如 FSRS
- 4 级评级:`again`(重来)/ `hard`(困难)/ `good`(良好)/ `easy`(简单)
- 初始间隔1/2/4/7 天
- 间隔增长:`again` 重置为 1 天;`hard` ×1.2`good` ×1.5`easy` ×2
- 掌握度0-5 级,`again` -1`hard` ±0`good` +1`easy` +2
- 已掌握判定:掌握度 ≥5 或连续答对 ≥3 次
- 复习时间:次日早上 9 点
- 单元测试:[sm2-algorithm.test.ts](file:///e:/Desktop/CICD/src/modules/error-book/sm2-algorithm.test.ts)39 个测试用例,全部通过)
- 导出函数:`calculateNewInterval` / `calculateNewMastery` / `deriveStatus` / `calculateNextReviewAt` / `calculateNewCorrectStreak` / `calculateSm2Result`
**自动采集机制**
- `collectFromExamSubmission`:从考试提交记录中筛选得分 < 满分的题目,去重后批量插入
@@ -1820,7 +1835,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~180 | 9 个 Server Actions全部使用 `requirePermission()` + `ActionState<T>` |
| `data-access.ts` | ~960 | 16 个数据访问函数 + SM-2 算法实现 + 自动采集逻辑 |
| `data-access.ts` | ~960 | 16 个数据访问函数 + 自动采集逻辑SM-2 算法已提取到独立模块) |
| `sm2-algorithm.ts` | ~180 | SM-2 间隔重复算法(独立纯函数模块,可替换,支持时间注入测试) |
| `sm2-algorithm.test.ts` | ~280 | SM-2 算法单元测试39 个测试用例,覆盖所有函数和边界条件) |
| `schema.ts` | ~60 | 4 个 Zod 验证 schema |
| `types.ts` | ~120 | 6 个类型定义 + 状态映射常量 + 错误标签常量 |
| `components/error-book-stats-cards.tsx` | ~80 | 5 个统计卡片(总数/待学习/学习中/已掌握/待复习) |
@@ -1857,6 +1874,85 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
---
## 2.29 aiAI 模块)— ✅ 新增
**职责**:统一 AI 能力封装,为备课、错题集、试卷、改题等业务模块提供 AI 服务。
**架构定位**
- 位于 `modules/` 层,通过 `shared/lib/ai` 调用底层 AI SDK
- 通过 `AiClientProvider`React Context向客户端组件注入 Server Action 引用
- 业务模块不直接 import `ai/actions`,仅通过 Context 消费
**核心导出**
| 类型 | 名称 | 文件 | 说明 |
|------|------|------|------|
| **Server Actions** | `aiChatAction` | `modules/ai/actions.ts` | AI 对话权限AI_CHAT |
| **Server Actions** | `suggestSimilarQuestionsAction` | `modules/ai/actions.ts` | 相似题推荐权限AI_CHAT + ERROR_BOOK_READ |
| **Server Actions** | `suggestGradingAction` | `modules/ai/actions.ts` | AI 辅助批改权限AI_CHAT + HOMEWORK_GRADE |
| **Server Actions** | `generateLessonContentAction` | `modules/ai/actions.ts` | 备课内容生成权限AI_CHAT + LESSON_PLAN_READ |
| **Server Actions** | `generateQuestionVariantAction` | `modules/ai/actions.ts` | 题目变体生成权限AI_CHAT + EXAM_AI_GENERATE |
| **Server Actions** | `analyzeWeaknessAction` | `modules/ai/actions.ts` | 薄弱点分析权限AI_CHAT + ERROR_BOOK_READ |
| **Service** | `AiService` | `modules/ai/types.ts` | 服务端 AI 服务接口chat/suggestSimilarQuestions/suggestGrading/generateLessonContent/generateQuestionVariant/analyzeWeakness |
| **Service** | `AiClientService` | `modules/ai/types.ts` | 客户端 AI 服务接口Server Action 引用集合) |
| **Provider** | `AiClientProvider` | `modules/ai/context/ai-client-provider.tsx` | React Context Provider注入 AiClientService |
| **Hook** | `useAiClient` | `modules/ai/context/ai-client-provider.tsx` | 消费 AiClientService必须在 Provider 内使用) |
| **Hook** | `useAiClientOptional` | `modules/ai/context/ai-client-provider.tsx` | 可选消费 AiClientService未注入返回 null |
| **Component** | `AiGradingAssist` | `modules/ai/components/ai-grading-assist.tsx` | AI 批改辅助(主观题预评分 + 反馈建议) |
| **Component** | `AiErrorBookAnalysis` | `modules/ai/components/ai-error-book-analysis.tsx` | 错题本 AI 分析(相似题 + 薄弱点) |
| **Component** | `AiLessonContentGenerator` | `modules/ai/components/ai-lesson-content-generator.tsx` | 备课内容生成器(活动/评估/讨论题/素材) |
| **Component** | `AiQuestionVariantGenerator` | `modules/ai/components/ai-question-variant-generator.tsx` | 题目变体生成器(同知识点/不同难度/不同题型) |
| **Component** | `AiChatPanel` | `modules/ai/components/ai-chat-panel.tsx` | AI 对话面板 |
| **Component** | `AiErrorBoundary` | `modules/ai/components/ai-error-boundary.tsx` | AI 功能错误边界 |
| **Component** | `AiSuggestionSkeleton` | `modules/ai/components/ai-skeleton.tsx` | AI 建议加载骨架屏 |
| **Component** | `AiProviderSelector` | `modules/ai/components/ai-provider-selector.tsx` | AI 服务商选择器 |
**集成点**
| 业务模块 | 集成组件 | 页面 | 说明 |
|---------|---------|------|------|
| homework | `AiGradingAssist` | `teacher/homework/submissions/[submissionId]` | 主观题辅助评分 |
| error-book | `AiErrorBookAnalysis` | `student/error-book` | 相似题推荐 + 薄弱点分析 |
| lesson-preparation | `AiLessonContentGenerator` | `teacher/lesson-plans/[planId]/edit` | 备课内容生成 |
| exams | `AiQuestionVariantGenerator` | `teacher/exams/[id]/build` | 题目变体生成 |
**依赖关系**
- `modules/ai``shared/lib/ai`AI SDK 封装)
- `modules/ai``shared/lib/auth-guard`(权限校验)
- `modules/ai``shared/types/permissions`(权限常量)
- `modules/ai``shared/types/action-state`(返回值类型)
- 业务模块 → `modules/ai/context/ai-client-provider`(通过 Context 注入)
- 业务模块 → `modules/ai/components/*`(组合 AI 组件)
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `modules/ai/types.ts` | 194 | 类型定义AiService/AiClientService/业务场景类型) |
| `modules/ai/schema.ts` | - | Zod 验证 schema |
| `modules/ai/actions.ts` | 244 | 6 个 Server Actions含权限校验 |
| `modules/ai/services/ai-service.ts` | - | DefaultAiService 实现 |
| `modules/ai/services/prompt-templates.ts` | - | 6 个系统提示词模板 |
| `modules/ai/services/usage-tracker.ts` | - | AI 使用量埋点 |
| `modules/ai/context/ai-client-provider.tsx` | 59 | React Context Provider + Hooks |
| `modules/ai/components/ai-grading-assist.tsx` | 173 | AI 批改辅助组件 |
| `modules/ai/components/ai-error-book-analysis.tsx` | 246 | 错题本 AI 分析组件 |
| `modules/ai/components/ai-lesson-content-generator.tsx` | - | 备课内容生成器 |
| `modules/ai/components/ai-question-variant-generator.tsx` | - | 题目变体生成器 |
| `modules/ai/components/ai-chat-panel.tsx` | - | AI 对话面板 |
| `modules/ai/components/ai-error-boundary.tsx` | - | AI 错误边界 |
| `modules/ai/components/ai-skeleton.tsx` | - | AI 骨架屏 |
| `modules/ai/components/ai-provider-selector.tsx` | - | 服务商选择器 |
| `modules/ai/hooks/use-ai-chat.ts` | - | AI 对话 Hook |
| `modules/ai/hooks/use-ai-suggestion.ts` | - | AI 建议 Hook |
**i18n**
- 翻译文件:`shared/i18n/messages/{locale}/ai.json`
- 命名空间:`ai`
- 包含chat/provider/suggestion/grading/errorBook/lessonPrep/exam/error/capability
---
# 第三部分:已知架构问题和技术债
## 3.1 P0 严重问题(必须立即修复)