feat(ai): V2 深度增强 — SSE 流式/全局助手/内容安全/多角色覆盖
对标 Khanmigo/Duolingo Max/Squirrel AI/Century Tech 实现: - SSE 流式响应:createAiChatCompletionStream AsyncGenerator + /api/ai/chat/stream SSE 端点 + useAiChatStream hook(AbortController 停止生成 + localStorage 持久化) - Markdown 渲染:AiMarkdownRenderer(react-markdown + remark-gfm + 代码块/表格/列表 + hover 复制按钮) - 全局 AI 助手:AiAssistantWidget 浮动按钮 + Sheet 侧抽屉 + usePathname 路由推断上下文(7 类场景系统提示)+ dashboard layout 全局注入 AiClientProvider - 内容安全:content-safety.ts 多层过滤(输入/输出安全过滤 + 每日限制 student 50/teacher 200/parent 30/admin 500 + 学生苏格拉底模式),COPPA/FERPA K12 合规 - 多角色 AI 覆盖:家长端 AiChildSummary(学情摘要)+ 管理员端 AiUsageDashboard(使用监控)+ 学生端 AiStudyPath(个性化学习路径) - i18n 修复:8 处错误键引用 + zh-CN/en ai.json 全面扩展 - 架构文档 004/005 同步更新
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
||||
QUESTION_VARIANT_SYSTEM_PROMPT,
|
||||
SIMILAR_QUESTION_SYSTEM_PROMPT,
|
||||
WEAKNESS_ANALYSIS_SYSTEM_PROMPT,
|
||||
CHILD_SUMMARY_SYSTEM_PROMPT,
|
||||
STUDY_PATH_SYSTEM_PROMPT,
|
||||
} from "./prompt-templates"
|
||||
import { withAiTracking } from "./usage-tracker"
|
||||
import {
|
||||
@@ -17,6 +19,8 @@ import {
|
||||
QuestionVariantResultSchema,
|
||||
SimilarQuestionListSchema,
|
||||
WeaknessAnalysisResultSchema,
|
||||
ChildSummaryResultSchema,
|
||||
StudyPathResultSchema,
|
||||
} from "../schema"
|
||||
import type {
|
||||
AiChatMessage,
|
||||
@@ -33,6 +37,10 @@ import type {
|
||||
SimilarQuestionResult,
|
||||
WeaknessAnalysisInput,
|
||||
WeaknessAnalysisResult,
|
||||
ChildSummaryInput,
|
||||
ChildSummaryResult,
|
||||
StudyPathInput,
|
||||
StudyPathResult,
|
||||
} from "../types"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -320,6 +328,76 @@ export class DefaultAiService implements AiService {
|
||||
return { result: validated.data }
|
||||
})
|
||||
}
|
||||
|
||||
async generateChildSummary(input: ChildSummaryInput): Promise<ChildSummaryResult> {
|
||||
return withAiTracking(this.userId, "weakness_analysis", undefined, async () => {
|
||||
const userLines = [
|
||||
`Student ID: ${input.studentId}`,
|
||||
input.studentName ? `Student Name: ${input.studentName}` : "",
|
||||
input.grade ? `Grade: ${input.grade}` : "",
|
||||
input.recentGrades && input.recentGrades.length > 0
|
||||
? `Recent Grades:\n${JSON.stringify(input.recentGrades, null, 2)}`
|
||||
: "",
|
||||
input.attendanceRate !== undefined
|
||||
? `Attendance Rate: ${(input.attendanceRate * 100).toFixed(1)}%`
|
||||
: "",
|
||||
input.errorBookSummary
|
||||
? `Error Book Summary:\n${JSON.stringify(input.errorBookSummary, null, 2)}`
|
||||
: "",
|
||||
input.homeworkCompletionRate !== undefined
|
||||
? `Homework Completion Rate: ${(input.homeworkCompletionRate * 100).toFixed(1)}%`
|
||||
: "",
|
||||
].filter((line) => line.length > 0)
|
||||
const { content } = await callAi(
|
||||
buildChatMessages(CHILD_SUMMARY_SYSTEM_PROMPT, userLines.join("\n\n")),
|
||||
{ temperature: 0.4, maxTokens: 2000 }
|
||||
)
|
||||
const parsed = extractJson(content)
|
||||
const validated = ChildSummaryResultSchema.safeParse(parsed)
|
||||
if (!validated.success) {
|
||||
return {
|
||||
result: {
|
||||
overallAssessment: "Unable to generate summary at this time.",
|
||||
strengths: [],
|
||||
areasForImprovement: [],
|
||||
familyTutoringSuggestions: [],
|
||||
nextSteps: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
return { result: validated.data }
|
||||
})
|
||||
}
|
||||
|
||||
async recommendStudyPath(input: StudyPathInput): Promise<StudyPathResult> {
|
||||
return withAiTracking(this.userId, "weakness_analysis", undefined, async () => {
|
||||
const userLines = [
|
||||
`Student ID: ${input.studentId}`,
|
||||
input.subject ? `Subject: ${input.subject}` : "",
|
||||
input.currentMastery && input.currentMastery.length > 0
|
||||
? `Current Mastery:\n${JSON.stringify(input.currentMastery, null, 2)}`
|
||||
: "",
|
||||
input.learningGoal ? `Learning Goal: ${input.learningGoal}` : "",
|
||||
].filter((line) => line.length > 0)
|
||||
const { content } = await callAi(
|
||||
buildChatMessages(STUDY_PATH_SYSTEM_PROMPT, userLines.join("\n\n")),
|
||||
{ temperature: 0.5, maxTokens: 2000 }
|
||||
)
|
||||
const parsed = extractJson(content)
|
||||
const validated = StudyPathResultSchema.safeParse(parsed)
|
||||
if (!validated.success) {
|
||||
return {
|
||||
result: {
|
||||
currentLevel: "Analysis unavailable",
|
||||
learningPath: [],
|
||||
summary: "Unable to generate learning path at this time.",
|
||||
motivation: "Keep learning!",
|
||||
},
|
||||
}
|
||||
}
|
||||
return { result: validated.data }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user