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:
@@ -1686,20 +1686,30 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
|
||||
> 架构变更(2026-06-21):编辑器从列表式(BlockRenderer + @dnd-kit)升级为节点图式(NodeEditor + @xyflow/react)。数据结构从 v1(blocks 数组)升级到 v2(nodes + 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/fromRfEdges(LessonPlanNode/Edge ↔ React Flow Node/Edge 映射) |
|
||||
| `config/block-registry.tsx` | **配置驱动**:BLOCK_REGISTRY 注册表 + getBlockComponent/isRichTextBlock,node-edit-panel 通过配置渲染 Block |
|
||||
| `lib/document-migration.ts` | **纯函数**:v1→v2(migrateV1ToV2)/ v2→v3(migrateV2ToV3)/ 规范化(normalizeDocument,兼容 v1/v2/v3)/ 初始内容(buildInitialContent)/ 默认骨架(buildDefaultSkeleton,10 节点 + 正文节点)/ 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 注册表 + BlockRenderer(switch 渲染 11 种定制节点 + textbook_content) |
|
||||
| `providers/lesson-plan-provider.tsx` | **Provider + Context(P1-5/P1-7/P2-4/V2-6)**:LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置(TEACHER/ADMIN/STUDENT/PARENT)、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker;hooks:useLessonPlanContextSafe(返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe(V2-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-3;V2-1:抛出 `LessonPlanDataError` 错误码;V2-3:mapRowToLessonPlan/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-6:duplicate/archive 调用 tracker.track) |
|
||||
| `components/lesson-plan-filters.tsx` | 课案筛选器(i18n 已接入;V2-5:3 个表单元素 label htmlFor 关联) |
|
||||
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel,i18n 已接入;V2-6:handleManualSave 调用 tracker.track) |
|
||||
| `components/node-editor.tsx` | **节点图画布**(React Flow,使用 lib/rf-mappers + lib/node-summary 纯函数,i18n 已接入;V2-4:MiniMap 复用 getNodeColor;V2-5:role=application + 键盘导航配置) |
|
||||
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(配置驱动渲染 Block,通过 getBlockComponent + LessonPlanErrorBoundary 包裹,i18n 已接入) |
|
||||
| `components/nodes/lesson-node.tsx` | **自定义节点组件**(使用 lib/node-summary 的 getNodeSummary/getNodeColor,i18n 已接入) |
|
||||
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel,i18n 已接入;V2-6:handleManualSave 调用 tracker.track;V3:顶部工具栏显示教材/章节标题指示器) |
|
||||
| `components/node-editor.tsx` | **节点图画布**(React Flow,使用 lib/rf-mappers + lib/node-summary 纯函数,i18n 已接入;V2-4:MiniMap 复用 getNodeColor;V2-5:role=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/getNodeColor,i18n 已接入) |
|
||||
| `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 ai(AI 模块)— ✅ 新增
|
||||
|
||||
**职责**:统一 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 严重问题(必须立即修复)
|
||||
|
||||
@@ -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 组件"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
452
docs/architecture/audit/ai-module-audit-report.md
Normal file
452
docs/architecture/audit/ai-module-audit-report.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# AI 模块审计报告
|
||||
|
||||
> 审计范围:项目中所有与 AI(人工智能)相关的代码,包括底层 SDK 封装、Provider 管理、各业务模块(备课、错题集、试卷、改题等)中的 AI 集成点。
|
||||
> 审计日期:2026-06-23
|
||||
> 审计依据:`docs/architecture/004_architecture_impact_map.md`、`docs/architecture/005_architecture_data.json`、`docs/standards/coding-standards.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、现有实现概要
|
||||
|
||||
### 1.1 文件分布
|
||||
|
||||
AI 相关代码当前**未形成独立模块**,而是分散在 5 个不同位置:
|
||||
|
||||
| 位置 | 文件 | 行数 | 职责 |
|
||||
|------|------|------|------|
|
||||
| `src/shared/lib/ai/` | `api-key-crypto.ts` | 28 | AES-256-GCM 加密 API Key |
|
||||
| `src/shared/lib/ai/` | `client.ts` | 58 | OpenAI SDK 封装,创建 chat completion |
|
||||
| `src/shared/lib/ai/` | `errors.ts` | 8 | 错误消息格式化 |
|
||||
| `src/shared/lib/ai/` | `payload-parser.ts` | 78 | 请求负载解析与 Zod 守卫 |
|
||||
| `src/shared/lib/ai/` | `provider-config.ts` | 61 | 从 `ai_providers` 表查询 Provider 配置 |
|
||||
| `src/shared/lib/ai/` | `index.ts` | 5 | 聚合导出 |
|
||||
| `src/shared/lib/ai.ts` | — | 9 | 向后兼容重导出 |
|
||||
| `src/app/api/ai/chat/` | `route.ts` | 42 | AI 聊天 REST API 端点 |
|
||||
| `src/modules/exams/ai-pipeline/` | `parse.ts` | 426 | Zod schema、JSON 提取修复、提示词 |
|
||||
| `src/modules/exams/ai-pipeline/` | `request.ts` | 306 | AI 请求构造与发送 |
|
||||
| `src/modules/exams/ai-pipeline/` | `structure.ts` | 209 | 结构生成与预览/草稿转换 |
|
||||
| `src/modules/exams/ai-pipeline/` | `index.ts` | 172 | 高层编排 |
|
||||
| `src/modules/lesson-preparation/` | `actions-ai.ts` | 44 | 知识点推荐 Server Action |
|
||||
| `src/modules/lesson-preparation/` | `ai-suggest.ts` | 65 | 知识点推荐 AI 逻辑 |
|
||||
| `src/modules/settings/` | `actions.ts`(部分) | ~183 | AI Provider CRUD Action |
|
||||
| `src/modules/settings/` | `data-access.ts`(部分) | — | `ai_providers` 表查询 |
|
||||
| `src/modules/exams/components/` | `exam-ai-generator.tsx` | 224 | AI 出题 UI 组件 |
|
||||
|
||||
### 1.2 数据流
|
||||
|
||||
```
|
||||
前端组件 (exam-ai-generator.tsx)
|
||||
└─▶ Server Action (exams/actions.ts: createAiExamAction)
|
||||
└─▶ ai-pipeline.generateAiCreateDraftFromSource()
|
||||
├─▶ requestAiExamStructureDraft() → createAiChatCompletion()
|
||||
│ └─▶ OpenAI SDK + db.query.aiProviders
|
||||
└─▶ parseQuestionDetail() → createAiChatCompletion()
|
||||
|
||||
前端组件 (lesson-preparation hooks)
|
||||
└─▶ suggestKnowledgePointsAction()
|
||||
└─▶ ai-suggest.suggestKnowledgePoints()
|
||||
├─▶ textbooks/data-access.getKnowledgePointsByTextbookId() [跨模块]
|
||||
└─▶ createAiChatCompletion()
|
||||
|
||||
前端组件 (settings)
|
||||
└─▶ upsertAiProviderAction() / testAiProviderAction()
|
||||
└─▶ settings/data-access (ai_providers 表)
|
||||
```
|
||||
|
||||
### 1.3 架构图记录情况
|
||||
|
||||
- `005_architecture_data.json` 中 `modules` 节点**未将 AI 列为独立模块**。
|
||||
- 仅在 `dbTables.aiProviders` 中记录 `usedBy: ["settings", "ai"]`,但 `ai` 并非真实存在的模块。
|
||||
- `shared` 模块下记录了 `lib/ai/*` 工具函数(`createAiChatCompletion`、`parseAiChatPayload` 等)。
|
||||
- `exams` 模块下记录了 `ai-pipeline` 子目录的导出函数。
|
||||
- `lessonPreparation` 模块下记录了 `suggestKnowledgePointsAction`。
|
||||
- **结论:架构图对 AI 模块的记录不完整,未反映 AI 作为横切关注点的全貌,也未记录 `app/api/ai/chat/route.ts` 端点。**
|
||||
|
||||
### 1.4 权限点
|
||||
|
||||
| 权限常量 | 值 | 用途 |
|
||||
|----------|----|------|
|
||||
| `AI_CHAT` | `ai:chat` | 使用 AI 聊天 |
|
||||
| `AI_CONFIGURE` | `ai:configure` | 配置 AI Provider |
|
||||
| `EXAM_AI_GENERATE` | `exam:ai_generate` | AI 出题 |
|
||||
|
||||
---
|
||||
|
||||
## 二、现存问题与原因分析
|
||||
|
||||
### 2.1 架构分层问题
|
||||
|
||||
#### 问题 2.1.1:AI 未形成独立模块,逻辑分散在 5 处
|
||||
|
||||
- **位置**:`shared/lib/ai/`、`app/api/ai/chat/`、`modules/exams/ai-pipeline/`、`modules/lesson-preparation/ai-suggest.ts`、`modules/settings/`
|
||||
- **原因**:AI 能力是按业务需求逐步添加的,每次新增场景都在调用方就地实现,未抽象为独立模块。
|
||||
- **后果**:AI 逻辑无法统一治理(限流、监控、成本控制、Prompt 版本管理);新增 AI 场景需要重复编写请求构造与错误处理;测试时无法 Mock AI 层。
|
||||
- **违反规则**:`项目规则 → 架构分层规则 → 模块标准结构`(AI 应作为 `modules/ai/` 独立模块存在)。
|
||||
|
||||
#### 问题 2.1.2:AI 聊天使用 REST API 路由而非 Server Action
|
||||
|
||||
- **位置**:[route.ts](file:///e:/Desktop/CICD/src/app/api/ai/chat/route.ts)
|
||||
- **原因**:早期实现选择了 REST 路由,未遵循项目 Server Action 统一规范。
|
||||
- **后果**:与项目其他数据操作风格不一致;无法复用 `ActionState<T>` 返回类型与 `useActionMutation` Hook;权限校验绕过了 `requirePermission()` 体系。
|
||||
- **违反规则**:`项目规则 → Server Action 规范`(所有数据操作应通过 Server Action,返回 `ActionState<T>`)。
|
||||
|
||||
#### 问题 2.1.3:`lesson-preparation/ai-suggest.ts` 跨模块直接依赖
|
||||
|
||||
- **位置**:[ai-suggest.ts](file:///e:/Desktop/CICD/src/modules/lesson-preparation/ai-suggest.ts#L6-L8)
|
||||
- **现状**:直接 `import { getKnowledgePointsByTextbookId, getKnowledgePointsByChapterId } from "@/modules/textbooks/data-access"`。
|
||||
- **判定**:模块间通过对方 data-access 通信**符合规则**,但 AI 推荐逻辑本身应属于 AI 模块,而非备课模块。当前 `ai-suggest.ts` 混合了"AI 调用"与"知识点候选获取"两个职责。
|
||||
- **后果**:若其他模块也需要"基于文本推荐知识点",无法复用。
|
||||
- **违反规则**:`项目规则 → 架构分层规则`(职责划分不清)。
|
||||
|
||||
### 2.2 权限问题
|
||||
|
||||
#### 问题 2.2.1:AI 聊天端点缺少 `requirePermission()` 校验
|
||||
|
||||
- **位置**:[route.ts:15-18](file:///e:/Desktop/CICD/src/app/api/ai/chat/route.ts#L15-L18)
|
||||
- **现状**:仅检查 `session?.user?.id` 是否存在,**未调用 `requirePermission(Permissions.AI_CHAT)`**。
|
||||
- **后果**:任何已登录用户(包括学生)都能无限制调用 AI 聊天,绕过了角色权限体系;无法按角色限制 AI 使用场景。
|
||||
- **违反规则**:`项目规则 → Server Action 规范 → 每个 Action 必须调用 requirePermission()`;`项目规则 → 安全规范`。
|
||||
|
||||
#### 问题 2.2.2:AI 出题管线内部无权限二次校验
|
||||
|
||||
- **位置**:`exams/ai-pipeline/index.ts` 的 `generateAiCreateDraftFromSource`
|
||||
- **现状**:依赖调用方 Action 校验权限,管线本身不校验。
|
||||
- **后果**:若未来有新调用方忘记校验,将导致越权调用 AI。
|
||||
- **违反规则**:`项目规则 → 安全规范 → Server Action 二次校验`。
|
||||
|
||||
### 2.3 国际化问题
|
||||
|
||||
#### 问题 2.3.1:`exam-ai-generator.tsx` 大量硬编码文本
|
||||
|
||||
- **位置**:[exam-ai-generator.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/exam-ai-generator.tsx)
|
||||
- **硬编码中文**:第 118 行"新建配置"、第 164 行"加入后台队列(运行 ${...}/3,排队 ${...})"、第 167 行"立即预览"/"Generating..."、第 192 行"后台生成记录"、第 202-207 行"排队中"/"生成中"/"已完成"/"失败:..."、第 211 行"打开预览"。
|
||||
- **硬编码英文**:第 92 行"AI Generation"、第 93-95 行描述、第 104 行"AI Provider"、第 122-124 行对话框标题、第 144 行"Loading providers..."/"Select provider"、第 156 行描述、第 175 行"Source Exam Text"、第 178 行 placeholder、第 184 行描述。
|
||||
- **后果**:无法切换语言;违反 i18n 就绪要求。
|
||||
- **违反规则**:`项目规则 → 所有用户可见文本必须适配 i18n`。
|
||||
|
||||
#### 问题 2.3.2:AI 管线内部硬编码中文错误消息
|
||||
|
||||
- **位置**:[request.ts:152](file:///e:/Desktop/CICD/src/modules/exams/ai-pipeline/request.ts#L152) "请先粘贴试卷文本"、第 172 行"试卷文本校验失败,请重试"、第 177 行"识别为乱码或混乱文本..."。
|
||||
- **后果**:错误消息无法国际化。
|
||||
- **违反规则**:`项目规则 → i18n`。
|
||||
|
||||
#### 问题 2.3.3:无独立 `ai.json` 翻译文件
|
||||
|
||||
- **现状**:AI 相关翻译散落在 `settings.json`(Provider 管理)和 `lesson-preparation.json`(`error.aiSuggest`),无统一命名空间。
|
||||
- **后果**:AI 文本难以维护与查找。
|
||||
|
||||
### 2.4 类型安全问题
|
||||
|
||||
#### 问题 2.4.1:`ai-suggest.ts` 使用 `as` 断言
|
||||
|
||||
- **位置**:[ai-suggest.ts:54](file:///e:/Desktop/CICD/src/modules/lesson-preparation/ai-suggest.ts#L54)
|
||||
- **代码**:`JSON.parse(jsonMatch[0]) as { id: string; name: string; reason: string }[]`
|
||||
- **后果**:AI 返回的 JSON 结构不可信,直接断言可能导致运行时错误。
|
||||
- **违反规则**:`项目规则 → TypeScript 规则 → 禁止 as 断言`。
|
||||
|
||||
#### 问题 2.4.2:`actions-ai.ts` 双重断言
|
||||
|
||||
- **位置**:[actions-ai.ts:34](file:///e:/Desktop/CICD/src/modules/lesson-preparation/actions-ai.ts#L34)
|
||||
- **代码**:`parsed.data.doc as unknown as LessonPlanDocument`
|
||||
- **后果**:绕过类型系统,不安全。
|
||||
- **违反规则**:`项目规则 → TypeScript 规则 → 禁止 as 断言`。
|
||||
|
||||
### 2.5 错误处理问题
|
||||
|
||||
#### 问题 2.5.1:`ai-suggest.ts` 静默吞掉错误
|
||||
|
||||
- **位置**:[ai-suggest.ts:50-64](file:///e:/Desktop/CICD/src/modules/lesson-preparation/ai-suggest.ts#L50-L64)
|
||||
- **现状**:`try { JSON.parse(...) } catch { return [] }` — JSON 解析失败时静默返回空数组。
|
||||
- **后果**:教师无法区分"AI 未推荐任何知识点"与"AI 返回格式错误";无法排查问题。
|
||||
- **违反规则**:`项目规则 → 错误处理`。
|
||||
|
||||
#### 问题 2.5.2:无 AI 专用 Error Boundary
|
||||
|
||||
- **现状**:AI 组件(如 `exam-ai-generator`)未用 Error Boundary 包裹。
|
||||
- **后果**:AI 调用失败可能导致整个页面崩溃。
|
||||
- **违反规则**:审计要求 → 每个独立数据区块必须用 React Error Boundary 包裹。
|
||||
|
||||
#### 问题 2.5.3:无 Suspense/骨架屏
|
||||
|
||||
- **现状**:AI 异步操作仅用 `loading` 布尔值切换按钮文字,无骨架屏。
|
||||
- **后果**:用户体验差,无法感知加载进度。
|
||||
|
||||
### 2.6 可复用性问题
|
||||
|
||||
#### 问题 2.6.1:无可复用 AI 组件
|
||||
|
||||
- **现状**:
|
||||
- AI Provider 选择器硬编码在 `exam-ai-generator.tsx` 内部,无法在其他模块复用。
|
||||
- 无通用 AI 聊天面板组件。
|
||||
- 无通用 AI 建议加载器组件。
|
||||
- 无通用 AI 结果预览组件。
|
||||
- **后果**:每个需要 AI 的模块都要从零实现 UI。
|
||||
- **违反规则**:审计要求 → 最大化复用。
|
||||
|
||||
#### 问题 2.6.2:无 AI 服务接口抽象
|
||||
|
||||
- **现状**:所有模块直接 `import { createAiChatCompletion } from "@/shared/lib/ai"`。
|
||||
- **后果**:无法 Mock AI 服务进行单测;无法切换 AI 实现(如本地 mock、不同 SDK)。
|
||||
- **违反规则**:审计要求 → 完全解耦、可测试性。
|
||||
|
||||
### 2.7 功能缺失问题
|
||||
|
||||
#### 问题 2.7.1:错题集无 AI 集成
|
||||
|
||||
- **现状**:`error-book` 模块仅有 SM2 间隔复习算法,无 AI 能力。
|
||||
- **缺失功能**:
|
||||
- AI 相似题推荐(根据错题生成同类练习)
|
||||
- AI 薄弱点分析(根据错题分布分析学生薄弱知识点)
|
||||
- AI 解题思路生成(为错题生成分步骤解析)
|
||||
- AI 复习计划建议(基于错题掌握度智能调整复习节奏)
|
||||
- **后果**:错题本仅是静态记录,无法发挥 AI 的个性化学习价值。
|
||||
|
||||
#### 问题 2.7.2:改题(作业批改)无 AI 集成
|
||||
|
||||
- **现状**:`homework-grading-view.tsx` 仅支持手动评分与自动判分(选择题),无 AI 辅助。
|
||||
- **缺失功能**:
|
||||
- AI 辅助批改主观题(简答题/论述题)
|
||||
- AI 生成评分反馈建议
|
||||
- AI 批改一致性校验(检测人工评分偏差)
|
||||
- **后果**:教师批改主观题负担重,效率低。
|
||||
|
||||
#### 问题 2.7.3:备课 AI 能力单一
|
||||
|
||||
- **现状**:`lesson-preparation` 仅有"知识点推荐"一个 AI 功能。
|
||||
- **缺失功能**:
|
||||
- AI 生成教学活动设计
|
||||
- AI 生成课堂提问
|
||||
- AI 生成形成性评估
|
||||
- AI 生成差异化教学建议
|
||||
- **后果**:AI 价值未充分释放。
|
||||
|
||||
#### 问题 2.7.4:试卷 AI 无题目变体与智能组卷
|
||||
|
||||
- **现状**:`exams/ai-pipeline` 仅支持"从文本解析生成试卷"。
|
||||
- **缺失功能**:
|
||||
- AI 生成题目变体(基于已有题目生成同知识点不同表述的变体)
|
||||
- AI 智能组卷(根据知识点覆盖、难度分布自动组卷)
|
||||
- AI 难度分析(预测题目难度)
|
||||
- **后果**:AI 出题场景受限。
|
||||
|
||||
### 2.8 性能与监控问题
|
||||
|
||||
#### 问题 2.8.1:无流式响应
|
||||
|
||||
- **现状**:所有 AI 调用等待完整响应才返回。
|
||||
- **后果**:长文本生成时用户体验差(等待 10-30 秒)。
|
||||
- **违反规则**:审计要求 → 性能:支持流式渲染。
|
||||
|
||||
#### 问题 2.8.2:无 AI 使用监控
|
||||
|
||||
- **现状**:无 AI 调用埋点、无成本统计、无延迟监控、无错误率监控。
|
||||
- **后果**:无法优化 AI 使用策略,无法发现异常调用。
|
||||
- **违反规则**:审计要求 → 监控:预留关键操作埋点接口。
|
||||
|
||||
### 2.9 可访问性问题
|
||||
|
||||
#### 问题 2.9.1:AI 组件缺少 ARIA 属性
|
||||
|
||||
- **位置**:`exam-ai-generator.tsx` 的后台任务列表无 `aria-live`,屏幕阅读器无法感知状态变化。
|
||||
- **违反规则**:审计要求 → a11y:ARIA 属性。
|
||||
|
||||
---
|
||||
|
||||
## 三、行业差距对比
|
||||
|
||||
### 3.1 与优秀 K12 产品的差距
|
||||
|
||||
| 能力 | 行业主流做法 | 当前状态 | 差距影响 |
|
||||
|------|-------------|---------|---------|
|
||||
| **AI 助手入口** | 全局悬浮按钮/侧边栏,可从任何页面唤起 AI 助手 | 无全局入口,仅嵌入特定页面 | 用户无法在需要时随时获取 AI 帮助 |
|
||||
| **上下文感知** | AI 助手自动感知当前页面上下文(如正在批改的作业) | 无上下文感知 | AI 建议不精准,需用户手动输入上下文 |
|
||||
| **流式输出** | AI 回复逐字流式显示 | 等待完整响应 | 长文本等待体验差 |
|
||||
| **错题 AI 推荐** | 根据错题自动生成同类练习题,支持"再练一题" | 无此功能 | 学生无法针对性巩固薄弱点 |
|
||||
| **AI 辅助批改** | 主观题 AI 预评分 + 教师确认 | 无此功能 | 教师批改负担重 |
|
||||
| **学习路径推荐** | AI 根据错题与掌握度生成个性化学习路径 | 无此功能 | 缺少个性化学习引导 |
|
||||
| **AI 内容安全** | 学生侧 AI 输出经过内容过滤 | 无过滤机制 | 学生可能接触不当内容 |
|
||||
| **AI 使用历史** | 用户可查看自己的 AI 对话历史 | 无此功能 | 无法回顾 AI 建议结果 |
|
||||
| **多 Provider 对比** | 同一 Prompt 可对比不同模型输出 | 仅支持选择单一 Provider | 无法评估最优模型 |
|
||||
| **Prompt 版本管理** | Prompt 模板可配置化、版本化 | Prompt 硬编码在代码中 | 调整 Prompt 需改代码发版 |
|
||||
|
||||
### 3.2 多角色体验差距
|
||||
|
||||
| 角色 | 期望的 AI 能力 | 当前状态 |
|
||||
|------|---------------|---------|
|
||||
| **教师** | 备课内容生成、出题辅助、批改辅助、学情分析 | 仅有知识点推荐 + 试卷解析 |
|
||||
| **学生** | 错题相似题推荐、解题思路、学习路径 | 无任何 AI 能力 |
|
||||
| **家长** | 子女学情 AI 摘要、辅导建议 | 无任何 AI 能力 |
|
||||
| **管理员** | AI 使用统计、成本监控 | 无任何 AI 能力 |
|
||||
|
||||
---
|
||||
|
||||
## 四、改进优先级建议
|
||||
|
||||
### P0(紧急,影响安全与基础架构)
|
||||
|
||||
| 编号 | 问题 | 改进方向 |
|
||||
|------|------|---------|
|
||||
| P0-1 | AI 聊天端点缺少权限校验 | 改造为 Server Action,添加 `requirePermission(AI_CHAT)` |
|
||||
| P0-2 | AI 未形成独立模块 | 创建 `src/modules/ai/`,将分散的 AI 逻辑统一收口 |
|
||||
| P0-3 | `exam-ai-generator.tsx` 硬编码文本 | 提取 i18n 键,创建 `ai.json` 翻译文件 |
|
||||
| P0-4 | AI 管线硬编码错误消息 | 通过 Server Action 层返回 i18n 错误键 |
|
||||
| P0-5 | `ai-suggest.ts` 使用 `as` 断言 | 用 Zod schema 校验 AI 返回 |
|
||||
|
||||
### P1(重要,影响功能完整性与可维护性)
|
||||
|
||||
| 编号 | 问题 | 改进方向 |
|
||||
|------|------|---------|
|
||||
| P1-1 | 无 AI 服务接口抽象 | 定义 `AiService` 接口,通过 React Context 注入 |
|
||||
| P1-2 | 无可复用 AI 组件 | 抽象 `AiChatPanel`、`AiProviderSelector`、`AiSuggestionCard`、`AiErrorBoundary` |
|
||||
| P1-3 | 无 AI Error Boundary | 创建 `AiErrorBoundary` 包裹所有 AI 区块 |
|
||||
| P1-4 | 错题集无 AI 集成 | 新增相似题推荐、薄弱点分析 Server Action |
|
||||
| P1-5 | 改题无 AI 集成 | 新增 AI 辅助批改 Action |
|
||||
| P1-6 | 无 AI 使用监控 | 预留 `trackAiUsage()` 埋点接口 |
|
||||
| P1-7 | 备课 AI 能力单一 | 新增内容生成、活动建议 Action |
|
||||
|
||||
### P2(优化,提升体验与扩展性)
|
||||
|
||||
| 编号 | 问题 | 改进方向 |
|
||||
|------|------|---------|
|
||||
| P2-1 | 无流式响应 | 支持 SSE 流式输出 |
|
||||
| P2-2 | 无 AI 对话历史 | 持久化用户 AI 对话记录 |
|
||||
| P2-3 | Prompt 硬编码 | 抽取为可配置 Prompt 模板 |
|
||||
| P2-4 | 试卷 AI 无变体生成 | 新增题目变体生成 Action |
|
||||
| P2-5 | 无多 Provider 对比 | 支持并行调用多 Provider 对比 |
|
||||
| P2-6 | 无内容安全过滤 | 学生侧 AI 输出添加内容过滤 |
|
||||
| P2-7 | 架构图未记录 AI 模块 | 同步更新 004/005 文档 |
|
||||
|
||||
---
|
||||
|
||||
## 五、架构图同步说明
|
||||
|
||||
本次审计发现架构图存在以下遗漏与不一致,需在实现后同步更新:
|
||||
|
||||
### 5.1 需新增的节点
|
||||
|
||||
| 文档 | 节点路径 | 内容 |
|
||||
|------|---------|------|
|
||||
| `005_architecture_data.json` | `modules.ai` | 新增 AI 模块定义:path、description、exports(AiService 接口、Actions、组件) |
|
||||
| `005_architecture_data.json` | `modules.ai.exports.functions` | `createAiChatAction`、`suggestSimilarQuestionsAction`、`suggestGradingAction`、`generateLessonContentAction`、`generateQuestionVariantAction` |
|
||||
| `005_architecture_data.json` | `modules.ai.exports.components` | `AiChatPanel`、`AiProviderSelector`、`AiSuggestionCard`、`AiErrorBoundary` |
|
||||
| `005_architecture_data.json` | `modules.ai.exports.hooks` | `useAiChat`、`useAiSuggestion` |
|
||||
| `005_architecture_data.json` | `dependencyMatrix.ai` | ai → shared、ai → settings(data-access);exams/lesson-preparation/error-book/homework → ai |
|
||||
| `004_architecture_impact_map.md` | 模块清单 | 新增"AI 模块"章节 |
|
||||
| `004_architecture_impact_map.md` | 文件清单 | 新增 `modules/ai/` 下所有文件 |
|
||||
|
||||
### 5.2 需修改的节点
|
||||
|
||||
| 文档 | 节点 | 修改内容 |
|
||||
|------|------|---------|
|
||||
| `005_architecture_data.json` | `dbTables.aiProviders.usedBy` | 从 `["settings", "ai"]` 改为 `["ai"]`(AI 模块收口后由 AI 模块负责) |
|
||||
| `005_architecture_data.json` | `modules.shared.exports` | 标注 `lib/ai/*` 为"底层 SDK 封装,业务层应调用 `modules/ai`" |
|
||||
| `005_architecture_data.json` | `modules.exams.ai-pipeline` | 标注依赖关系变更为"通过 ai 模块服务调用" |
|
||||
| `005_architecture_data.json` | `routes` | 移除 `app/api/ai/chat/route.ts`(改造为 Server Action 后删除) |
|
||||
| `004_architecture_impact_map.md` | 调用链路图 | 更新 AI 调用链路:业务模块 → ai/actions → ai/services → shared/lib/ai |
|
||||
|
||||
### 5.3 需删除的节点
|
||||
|
||||
| 文档 | 节点 | 原因 |
|
||||
|------|------|------|
|
||||
| `005_architecture_data.json` | `routes./api/ai/chat` | 改造为 Server Action 后该 REST 路由删除 |
|
||||
|
||||
---
|
||||
|
||||
## 六、重构方案设计(概要)
|
||||
|
||||
> 详细实现见代码提交,此处仅列出设计要点。
|
||||
|
||||
### 6.1 模块结构
|
||||
|
||||
```
|
||||
src/modules/ai/
|
||||
├─ types.ts # AiService 接口、AiChatMessage、AiSuggestion 等类型
|
||||
├─ schema.ts # Zod 校验(chat、suggest、grading 等)
|
||||
├─ data-access.ts # ai_providers 表查询(从 settings 迁移)
|
||||
├─ services/
|
||||
│ ├─ ai-service.ts # AiService 接口实现(封装 createAiChatCompletion)
|
||||
│ ├─ prompt-templates.ts # 可配置 Prompt 模板
|
||||
│ └─ usage-tracker.ts # AI 使用埋点
|
||||
├─ actions.ts # Server Actions(chat、suggestSimilar、suggestGrading、generateLessonContent)
|
||||
├─ context/
|
||||
│ └─ ai-provider.tsx # React Context + Provider(依赖注入 AiService)
|
||||
├─ components/
|
||||
│ ├─ ai-chat-panel.tsx # 通用 AI 聊天面板(支持流式)
|
||||
│ ├─ ai-provider-selector.tsx # Provider 选择器(复用)
|
||||
│ ├─ ai-suggestion-card.tsx # 建议卡片
|
||||
│ ├─ ai-error-boundary.tsx # AI 专用 Error Boundary
|
||||
│ └─ ai-skeleton.tsx # AI 加载骨架屏
|
||||
└─ hooks/
|
||||
├─ use-ai-chat.ts # AI 聊天 Hook
|
||||
└─ use-ai-suggestion.ts # AI 建议 Hook
|
||||
```
|
||||
|
||||
### 6.2 依赖注入
|
||||
|
||||
```typescript
|
||||
// types.ts
|
||||
export interface AiService {
|
||||
chat(messages: AiChatMessage[], options?: AiChatOptions): Promise<AiChatResult>
|
||||
suggestSimilarQuestions(input: SimilarQuestionInput): Promise<SimilarQuestionResult[]>
|
||||
suggestGrading(input: GradingInput): Promise<GradingSuggestion>
|
||||
generateLessonContent(input: LessonContentInput): Promise<LessonContentResult>
|
||||
}
|
||||
|
||||
// context/ai-provider.tsx
|
||||
const AiContext = createContext<AiService | null>(null)
|
||||
export function AiServiceProvider({ children, service }: { children: ReactNode; service: AiService }) { ... }
|
||||
export function useAiService(): AiService { ... }
|
||||
```
|
||||
|
||||
### 6.3 i18n 结构
|
||||
|
||||
```json
|
||||
// ai.json
|
||||
{
|
||||
"chat": {
|
||||
"title": "AI Assistant",
|
||||
"placeholder": "Ask anything...",
|
||||
"sending": "Sending...",
|
||||
"error": "AI request failed"
|
||||
},
|
||||
"provider": {
|
||||
"selector": { "label": "AI Provider", "placeholder": "Select provider" },
|
||||
"manage": { "label": "Manage", "title": "AI Provider Settings" }
|
||||
},
|
||||
"suggestion": {
|
||||
"loading": "AI is thinking...",
|
||||
"empty": "No suggestions",
|
||||
"retry": "Retry"
|
||||
},
|
||||
"errorBook": {
|
||||
"similarQuestions": "Similar Questions",
|
||||
"weaknessAnalysis": "Weakness Analysis"
|
||||
},
|
||||
"grading": {
|
||||
"aiSuggest": "AI Grading Suggestion",
|
||||
"applyScore": "Apply Score",
|
||||
"applyFeedback": "Apply Feedback"
|
||||
},
|
||||
"lessonPrep": {
|
||||
"generateContent": "Generate Content",
|
||||
"generateActivity": "Suggest Activity"
|
||||
},
|
||||
"exam": {
|
||||
"generate": "Generate",
|
||||
"queue": "Add to Queue",
|
||||
"preview": "Preview"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 配置驱动
|
||||
|
||||
```typescript
|
||||
// 角色配置决定可用 AI 能力
|
||||
const AI_CAPABILITY_CONFIG: Record<Role, AiCapability[]> = {
|
||||
admin: ["chat", "usage-stats"],
|
||||
teacher: ["chat", "exam-generate", "grading-assist", "lesson-content", "question-variant"],
|
||||
student: ["chat", "similar-question", "study-path"],
|
||||
parent: ["chat", "child-summary"],
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user