feat(lesson-preparation): major update with AI features, schedules, and new components

- Add actions-schedules.ts and data-access-schedules.ts for schedule management

- Add AI differentiation, AI feedback, consistency check dialogs

- Add attachment-picker, curriculum-heatmap, print-view, version-diff-view

- Add lesson-plan-mobile-view and schedule-dialog components

- Add lib: ai-differentiation, ai-feedback, auto-layout, consistency-check,

  curriculum-coverage, export, version-diff

- Add history-slice hook for version history

- Update existing components, hooks, providers, services, types

- Add teacher lesson-plans heatmap and library pages
This commit is contained in:
SpecialX
2026-07-04 10:22:10 +08:00
parent 41fe8d8903
commit 25dca843be
45 changed files with 5295 additions and 210 deletions

View File

@@ -0,0 +1,135 @@
/**
* V5-17 A1/A2AI 反馈闭环 + 解释性展示。
*
* 调用 AI 对课案文档进行教学评一致性反馈,返回结构化建议。
* 反馈包含:
* - strengths课案优点
* - improvements改进建议
* - alignment教学评一致性评估
* - differentiation差异化教学建议
*
* 每条建议附带 reason解释性展示帮助教师理解 AI 判断依据。
*/
import "server-only";
import { env } from "@/env.mjs";
import { createAiChatCompletion } from "@/shared/lib/ai";
import { isRecord } from "@/shared/lib/type-guards";
import { z } from "zod";
import type { LessonPlanDocument, LessonPlanNode } from "../types";
/** AI 反馈单条建议 */
export interface AiFeedbackItem {
/** i18n 键后缀feedback.* 命名空间下) */
category: "strengths" | "improvements" | "alignment" | "differentiation";
/** 建议标题 */
title: string;
/** 解释性理由A2解释性展示 */
reason: string;
/** 关联节点 ID如适用 */
nodeId?: string;
}
/** AI 反馈结果 */
export interface AiFeedbackResult {
items: AiFeedbackItem[];
/** 整体评分0-100 */
overallScore: number;
/** 摘要 */
summary: string;
}
const FeedbackItemSchema = z.object({
category: z.enum(["strengths", "improvements", "alignment", "differentiation"]),
title: z.string().min(1),
reason: z.string(),
nodeId: z.string().optional(),
});
const FeedbackResultSchema = z.object({
items: z.array(FeedbackItemSchema),
overallScore: z.number().min(0).max(100),
summary: z.string(),
});
const AI_FEEDBACK_PROMPT_TEMPLATE = `你是资深教学设计专家。请对以下课案文档进行教学评一致性评估,给出结构化反馈。
课案文档JSON
---
{doc}
---
请从四个维度评估:
1. strengths课案优点
2. improvements改进建议
3. alignment教学评一致性目标-教学-评价是否对齐)
4. differentiation差异化教学建议
返回 JSON 对象,含:
- items数组每项含 category维度/title建议标题/reason解释性理由说明为何给出此建议/nodeId关联节点 ID可选
- overallScore整体评分 0-100
- summary一句话摘要
注意reason 字段必须解释判断依据,帮助教师理解。`;
/** 安全提取节点文本用于 AI prompt */
function extractNodeText(node: LessonPlanNode): string {
const data = node.data as unknown;
if (!isRecord(data)) return "";
const html = typeof data.html === "string" ? data.html : "";
const sourceText = typeof data.sourceText === "string" ? data.sourceText : "";
return html || sourceText || "";
}
/**
* 调用 AI 对课案文档生成结构化反馈。
*
* @param doc 课案文档
* @returns AI 反馈结果AI 不可用时返回空结果
*/
export async function generateLessonPlanFeedback(
doc: LessonPlanDocument,
): Promise<AiFeedbackResult> {
// 提取教学节点摘要(排除正文节点,控制 token 用量)
const teachingNodes = doc.nodes.filter(
(n): n is LessonPlanNode => n.type !== "textbook_content",
);
if (teachingNodes.length === 0) {
return { items: [], overallScore: 0, summary: "" };
}
const docSummary = teachingNodes.slice(0, 20).map((n) => ({
id: n.id,
type: n.type,
title: n.title,
stage: n.stage,
differentiation: n.differentiation,
text: extractNodeText(n).slice(0, 200),
}));
const prompt = AI_FEEDBACK_PROMPT_TEMPLATE.replace(
"{doc}",
JSON.stringify(docSummary),
);
try {
const { content } = await createAiChatCompletion({
messages: [{ role: "user", content: prompt }],
model: env.AI_MODEL ?? "gpt-4o-mini",
temperature: 0.4,
});
// 从返回内容中提取 JSON 对象
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (!jsonMatch) return { items: [], overallScore: 0, summary: "" };
const parsed: unknown = JSON.parse(jsonMatch[0]);
const validated = FeedbackResultSchema.safeParse(parsed);
if (!validated.success) {
return { items: [], overallScore: 0, summary: "" };
}
return validated.data;
} catch {
return { items: [], overallScore: 0, summary: "" };
}
}