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:
@@ -1,12 +1,44 @@
|
||||
import type { JSX } from "react"
|
||||
import { Suspense } from "react"
|
||||
import { notFound } from "next/navigation"
|
||||
import { getLessonPlanById } from "@/modules/lesson-preparation/data-access"
|
||||
import { LessonPlanEditor } from "@/modules/lesson-preparation/components/lesson-plan-editor"
|
||||
import { getTeacherClasses } from "@/modules/classes/data-access"
|
||||
import { getTextbookById, getChaptersByTextbookId } from "@/modules/textbooks/data-access"
|
||||
import { getAuthContext } from "@/shared/lib/auth-guard"
|
||||
import { Skeleton } from "@/shared/components/ui/skeleton"
|
||||
import {
|
||||
AiClientProvider,
|
||||
type AiClientService,
|
||||
} from "@/modules/ai/context/ai-client-provider"
|
||||
import {
|
||||
aiChatAction,
|
||||
suggestSimilarQuestionsAction,
|
||||
suggestGradingAction,
|
||||
generateLessonContentAction,
|
||||
generateQuestionVariantAction,
|
||||
analyzeWeaknessAction,
|
||||
} from "@/modules/ai/actions"
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
/**
|
||||
* 构建 AI 客户端服务(Server Action 引用集合)
|
||||
*
|
||||
* 通过 React Context 注入,客户端组件不直接 import actions,
|
||||
* 遵循依赖注入模式,便于测试时替换为 mock。
|
||||
*/
|
||||
function createAiClientService(): AiClientService {
|
||||
return {
|
||||
chat: aiChatAction,
|
||||
suggestSimilarQuestions: suggestSimilarQuestionsAction,
|
||||
suggestGrading: suggestGradingAction,
|
||||
generateLessonContent: generateLessonContentAction,
|
||||
generateQuestionVariant: generateQuestionVariantAction,
|
||||
analyzeWeakness: analyzeWeaknessAction,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function EditLessonPlanPage({
|
||||
params,
|
||||
}: {
|
||||
@@ -23,16 +55,53 @@ export default async function EditLessonPlanPage({
|
||||
|
||||
const classes = teacherClasses.map((c) => ({ id: c.id, name: c.name }))
|
||||
|
||||
// 拉取教材/章节标题用于工具栏显示
|
||||
let textbookTitle: string | undefined
|
||||
let chapterTitle: string | undefined
|
||||
if (plan.textbookId) {
|
||||
const textbook = await getTextbookById(plan.textbookId)
|
||||
textbookTitle = textbook?.title
|
||||
if (plan.chapterId) {
|
||||
const chapters = await getChaptersByTextbookId(plan.textbookId)
|
||||
const findChapter = (list: typeof chapters): typeof chapters[number] | undefined => {
|
||||
for (const ch of list) {
|
||||
if (ch.id === plan.chapterId) return ch
|
||||
if (ch.children && ch.children.length > 0) {
|
||||
const found = findChapter(ch.children as typeof chapters)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
const chapter = findChapter(chapters)
|
||||
chapterTitle = chapter?.title
|
||||
}
|
||||
}
|
||||
|
||||
const aiClientService = createAiClientService()
|
||||
|
||||
return (
|
||||
<div className="h-[calc(100vh-4rem)]">
|
||||
<LessonPlanEditor
|
||||
planId={plan.id}
|
||||
initialTitle={plan.title}
|
||||
initialDoc={plan.content}
|
||||
textbookId={plan.textbookId ?? undefined}
|
||||
chapterId={plan.chapterId ?? undefined}
|
||||
classes={classes}
|
||||
/>
|
||||
</div>
|
||||
<AiClientProvider service={aiClientService}>
|
||||
<div className="h-[calc(100vh-4rem)]">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Skeleton className="h-[80%] w-[80%]" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<LessonPlanEditor
|
||||
planId={plan.id}
|
||||
initialTitle={plan.title}
|
||||
initialDoc={plan.content}
|
||||
textbookId={plan.textbookId ?? undefined}
|
||||
chapterId={plan.chapterId ?? undefined}
|
||||
textbookTitle={textbookTitle}
|
||||
chapterTitle={chapterTitle}
|
||||
classes={classes}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</AiClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user