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

@@ -1,10 +1,11 @@
"use server";
import { getTranslations } from "next-intl/server";
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard";
import { requirePermission } from "@/shared/lib/auth-guard";
import { handleActionError } from "@/shared/lib/action-utils";
import { Permissions } from "@/shared/types/permissions";
import { suggestKnowledgePoints } from "./ai-suggest";
import { suggestKnowledgePointsSchema } from "./schema";
import { translateFieldErrors } from "./lib/i18n-errors";
import type { ActionState } from "@/shared/types/action-state";
export async function suggestKnowledgePointsAction(input: {
@@ -16,11 +17,11 @@ export async function suggestKnowledgePointsAction(input: {
suggestions: { id: string; name: string; reason: string }[];
}>
> {
const t = await getTranslations("lessonPreparation");
try {
const parsed = suggestKnowledgePointsSchema.safeParse(input);
if (!parsed.success) {
return { success: false, errors: parsed.error.flatten().fieldErrors };
const errors = await translateFieldErrors(parsed.error.flatten().fieldErrors);
return { success: false, errors };
}
// 并行校验两个权限点
@@ -29,9 +30,10 @@ export async function suggestKnowledgePointsAction(input: {
requirePermission(Permissions.AI_CHAT),
]);
// 从 unknown 安全提取 nodes 数组Zod 已校验 doc 是对象
// P1 修复:从 unknown 安全提取 nodes 数组,显式类型标注替代隐式 any
// Zod 已校验 doc 是 Record<string, unknown>,但 nodes 字段需运行时检查
const doc = parsed.data.doc;
const nodes = Array.isArray(doc.nodes) ? doc.nodes : [];
const nodes: unknown[] = Array.isArray(doc.nodes) ? doc.nodes : [];
const suggestions = await suggestKnowledgePoints(
{ nodes },
parsed.data.textbookId,
@@ -39,8 +41,7 @@ export async function suggestKnowledgePointsAction(input: {
);
return { success: true, data: { suggestions } };
} catch (e) {
if (e instanceof PermissionDeniedError)
return { success: false, message: e.message };
return { success: false, message: t("error.aiSuggest") };
// P1 修复:统一使用 handleActionError 处理错误,避免手动判断 PermissionDeniedError
return handleActionError(e);
}
}