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

@@ -27,6 +27,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/components/ui/
import { gradeHomeworkSubmissionAction } from "../actions"
import { formatDate } from "@/shared/lib/utils"
import { QuestionRenderer } from "./question-renderer"
import { AiGradingAssist } from "@/modules/ai/components/ai-grading-assist"
import {
applyAutoGrades as applyAutoGradesUtil,
extractAnswerValue,
@@ -136,16 +137,21 @@ export function HomeworkGradingView({
formData.set("submissionId", submissionId)
formData.set("answersJson", JSON.stringify(payload))
const result = await gradeHomeworkSubmissionAction(null, formData)
try {
const result = await gradeHomeworkSubmissionAction(null, formData)
if (result.success) {
toast.success(t("homework.grade.gradesSaved"))
// Optionally redirect or stay
router.refresh()
} else {
toast.error(result.message || t("homework.grade.gradesSaveFailed"))
if (result.success) {
toast.success(t("homework.grade.gradesSaved"))
// Optionally redirect or stay
router.refresh()
} else {
toast.error(result.message || t("homework.grade.gradesSaveFailed"))
}
} catch {
toast.error(t("homework.grade.gradesSaveFailed"))
} finally {
setIsSubmitting(false)
}
setIsSubmitting(false)
}
const handleScrollToQuestion = (id: string) => {
@@ -337,6 +343,22 @@ export function HomeworkGradingView({
/>
</div>
)}
{/* AI Grading Assist (subjective questions only) */}
{!isAutoGradable(ans) && (
<AiGradingAssist
questionText={extractQuestionText(ans.questionContent)}
questionType={ans.questionType}
studentAnswer={formatStudentAnswer(ans.studentAnswer)}
correctAnswer={getTextCorrectAnswers(ans.questionContent).join(" / ")}
maxScore={ans.maxScore}
onApplyScore={(score) => handleManualScoreChange(ans.id, String(score))}
onApplyFeedback={(feedback) => {
handleFeedbackChange(ans.id, feedback)
setShowFeedbackByAnswerId((prev) => ({ ...prev, [ans.id]: true }))
}}
/>
)}
</CardFooter>
</Card>
)
@@ -520,3 +542,21 @@ const formatStudentAnswer = (studentAnswer: unknown): string => {
if (v == null) return "—"
return JSON.stringify(v)
}
/**
* 从题目内容中提取纯文本(用于 AI 批改输入)
*
* 优先使用 `text` 字段;若不存在则回退到 JSON 字符串,
* 保证 AI 服务能拿到可读的题目描述。
*/
const extractQuestionText = (content: QuestionContent | null): string => {
if (!content) return ""
if (typeof content.text === "string" && content.text.trim().length > 0) {
return content.text
}
try {
return JSON.stringify(content)
} catch {
return ""
}
}