feat(lesson-preparation): add AI evaluation, analytics, attachments, calendar, comments, review, substitutes, formative, and version diff

- Add actions-ai-evaluation, actions-analytics, actions-attachments, actions-calendar, actions-comments, actions-formative, actions-questions, actions-review, actions-substitutes

- Add corresponding data-access layers for each new action module

- Add calendar-view, curriculum-map-view, version-diff-viewer components

- Add editor-slice, selection-slice, version-slice hooks for state management

- Add document-diff and scope-check lib utilities

- Add default-question-service and external-questions-bridge services
This commit is contained in:
SpecialX
2026-07-03 10:25:21 +08:00
parent a16f09d3c3
commit 20023e13fd
75 changed files with 5131 additions and 1186 deletions

View File

@@ -0,0 +1,120 @@
/**
* M8 AI 评估 - Server Actions
*/
"use server";
import { getTranslations } from "next-intl/server";
import { z } from "zod";
import {
getEvaluationsByPlanId,
getLatestEvaluation,
createEvaluation,
deleteEvaluation,
evaluateDocument,
} from "./data-access-ai-evaluation";
import type { AiEvaluation } from "./data-access-ai-evaluation";
import type { ActionState } from "@/shared/types/action-state";
import { getAuthContext, requirePermission } from "@/shared/lib/auth-guard";
import { handleActionError } from "@/shared/lib/action-utils";
import { Permissions } from "@/shared/types/permissions";
import { getLessonPlanById } from "./data-access";
import { getStandardsByPlanId } from "../standards/data-access";
import { normalizeDocument } from "./lib/document-migration";
const createEvaluationSchema = z.object({
planId: z.string().min(1),
versionNo: z.number().int().positive(),
});
/**
* 查询课案的所有 AI 评估
*/
export async function getLessonPlanEvaluationsAction(
planId: string,
): Promise<ActionState<{ items: AiEvaluation[] }>> {
try {
await requirePermission(Permissions.LESSON_PLAN_READ);
const items = await getEvaluationsByPlanId(planId);
return { success: true, data: { items } };
} catch (e) {
return handleActionError(e);
}
}
/**
* 查询最新评估
*/
export async function getLatestEvaluationAction(
planId: string,
): Promise<ActionState<{ evaluation: AiEvaluation | null }>> {
try {
await requirePermission(Permissions.LESSON_PLAN_READ);
const evaluation = await getLatestEvaluation(planId);
return { success: true, data: { evaluation } };
} catch (e) {
return handleActionError(e);
}
}
/**
* 触发 AI 评估(当前为本地规则评估,未来可替换为远程 AI API
*/
export async function evaluateLessonPlanAction(
input: Record<string, unknown>,
): Promise<ActionState<{ evaluation: AiEvaluation }>> {
try {
await requirePermission(Permissions.LESSON_PLAN_READ);
const t = await getTranslations("lessonPreparation");
const parseResult = createEvaluationSchema.safeParse(input);
if (!parseResult.success) {
return { success: false, message: t("error.invalidInput") };
}
const { planId, versionNo } = parseResult.data;
const auth = await getAuthContext();
if (!auth.userId) {
return { success: false, message: t("error.unauthorized") };
}
const plan = await getLessonPlanById(planId, auth.userId);
if (!plan) {
return { success: false, message: t("error.notFound") };
}
const doc = normalizeDocument(plan.content as unknown);
const standards = await getStandardsByPlanId(planId);
const result = evaluateDocument(doc, {
standardsLinkedCount: standards.length,
hasInteractiveItems: false, // TODO: 查询 M5 formative items
});
const evaluation = await createEvaluation(
{
planId,
versionNo,
overallScore: result.overallScore,
dimensionScores: result.dimensionScores,
suggestions: result.suggestions,
},
auth.userId,
);
return { success: true, data: { evaluation } };
} catch (e) {
return handleActionError(e);
}
}
/**
* 删除评估
*/
export async function deleteEvaluationAction(
id: string,
): Promise<ActionState<null>> {
try {
await requirePermission(Permissions.LESSON_PLAN_UPDATE);
await deleteEvaluation(id);
return { success: true, data: null };
} catch (e) {
return handleActionError(e);
}
}