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:
SpecialX
2026-06-23 01:34:37 +08:00
parent a60105455e
commit 4da9194a5e
27 changed files with 3522 additions and 172 deletions

View File

@@ -5,8 +5,20 @@
"inputLabel": "Message input",
"send": "Send",
"thinking": "AI is thinking...",
"streaming": "AI is typing...",
"stopGeneration": "Stop generating",
"maxReached": "Maximum messages reached",
"clear": "Clear conversation"
"clear": "Clear conversation",
"clearConfirm": "Clear all messages?",
"copy": "Copy",
"copied": "Copied!",
"suggestedPrompts": {
"title": "Try asking...",
"teacher": ["Help me grade this question", "Generate a classroom activity", "Create a quiz question"],
"student": ["Explain this concept", "Give me a practice question", "Help me study"],
"parent": ["How is my child doing?", "What should I focus on at home?"],
"admin": ["Show AI usage stats", "Which teachers use AI most?"]
}
},
"provider": {
"label": "AI Provider",
@@ -28,10 +40,13 @@
"loaded": "Suggestions loaded",
"selected": "Suggestion selected",
"select": "Select",
"difficulty": "Difficulty"
"difficulty": "Difficulty",
"practiceNow": "Practice Now",
"addAll": "Add All"
},
"grading": {
"title": "AI Grading Suggestion",
"description": "AI-powered scoring and feedback for subjective questions",
"suggestedScore": "Suggested Score",
"confidence": "Confidence",
"feedback": "Feedback",
@@ -40,7 +55,13 @@
"applyFeedback": "Apply Feedback",
"loading": "AI is grading...",
"error": "AI grading failed",
"notAvailable": "AI grading not available for this question type"
"notAvailable": "AI grading not available for this question type",
"batchTitle": "Batch AI Grading",
"batchDescription": "Generate AI suggestions for all subjective questions at once",
"batchGenerate": "Generate All Suggestions",
"batchProgress": "Processing {done}/{total}",
"currentScore": "Current Score",
"scoreDifference": "Difference"
},
"errorBook": {
"similarQuestions": "Similar Questions",
@@ -56,11 +77,18 @@
},
"lessonPrep": {
"generateContent": "Generate Content",
"description": "AI-powered teaching content generation",
"generateActivity": "Suggest Activity",
"generateAssessment": "Generate Assessment",
"generateQuestion": "Generate Discussion Question",
"loading": "Generating...",
"error": "Content generation failed"
"error": "Content generation failed",
"additionalContext": "Additional context",
"additionalContextPlaceholder": "Add any specific requirements or context...",
"insertContent": "Insert Content",
"editBeforeInsert": "Edit before insert",
"history": "Generation History",
"clearHistory": "Clear history"
},
"exam": {
"generate": "Generate",
@@ -81,7 +109,64 @@
"sourceTextPlaceholder": "Paste the full exam text to parse into questions.",
"sourceTextDesc": "AI will extract questions and structure from this text.",
"generationTitle": "AI Generation",
"generationDesc": "Paste the exam text and generate a structured preview."
"generationDesc": "Paste the exam text and generate a structured preview.",
"variantType": {
"label": "Variant type",
"same_knowledge_point": "Same knowledge point, different context",
"different_difficulty": "Different difficulty level",
"different_format": "Different question format"
},
"targetDifficulty": "Target difficulty",
"addVariant": "Add Variant"
},
"parent": {
"summary": "AI Learning Summary",
"summaryDescription": "AI-generated overview of your child's learning progress",
"generateSummary": "Generate Summary",
"weaknessHint": "Areas to focus on",
"suggestion": "Family tutoring suggestion",
"loading": "Generating summary...",
"error": "Failed to generate summary"
},
"admin": {
"usageDashboard": "AI Usage Dashboard",
"dashboardDescription": "Monitor AI usage across the school",
"totalCalls": "Total AI Calls",
"activeUsers": "Active Users",
"costEstimate": "Estimated Cost",
"topUsers": "Top Users",
"byCapability": "By Capability",
"byRole": "By Role",
"recentActivity": "Recent Activity",
"noData": "No AI usage data available",
"callsToday": "Calls today",
"callsThisWeek": "Calls this week",
"errorRate": "Error rate",
"avgDuration": "Avg duration"
},
"studyPath": {
"title": "Your Learning Path",
"description": "AI-personalized learning recommendations",
"nextSteps": "Recommended Next Steps",
"mastered": "Mastered",
"inProgress": "In Progress",
"needsWork": "Needs Work",
"generate": "Generate Learning Path",
"loading": "Generating learning path...",
"error": "Failed to generate learning path",
"startLearning": "Start Learning"
},
"widget": {
"title": "AI Assistant",
"open": "Open AI Assistant",
"close": "Close",
"contextAware": "Context-aware"
},
"safety": {
"blocked": "Your message was blocked by the safety filter. Please keep the conversation educational.",
"dailyLimit": "Daily AI usage limit reached. Please try again tomorrow.",
"studentMode": "AI is in student mode. It will guide you to find the answer.",
"contentFiltered": "Inappropriate content was filtered from the AI response."
},
"error": {
"invalidInput": "Invalid input data",
@@ -104,6 +189,8 @@
"lessonContent": "AI Lesson Content",
"questionVariant": "AI Question Variant",
"similarQuestion": "AI Similar Questions",
"weaknessAnalysis": "AI Weakness Analysis"
"weaknessAnalysis": "AI Weakness Analysis",
"childSummary": "AI Child Summary",
"studyPath": "AI Study Path"
}
}

View File

@@ -5,8 +5,20 @@
"inputLabel": "消息输入",
"send": "发送",
"thinking": "AI 正在思考...",
"streaming": "AI 正在输入...",
"stopGeneration": "停止生成",
"maxReached": "已达到最大消息数",
"clear": "清空对话"
"clear": "清空对话",
"clearConfirm": "确认清空所有消息?",
"copy": "复制",
"copied": "已复制!",
"suggestedPrompts": {
"title": "试试问我...",
"teacher": ["帮我批改这道题", "生成一个课堂活动", "创建一道测验题"],
"student": ["解释这个概念", "给我一道练习题", "帮我复习"],
"parent": ["我孩子学得怎么样?", "在家应该关注什么?"],
"admin": ["显示 AI 使用统计", "哪些老师最常使用 AI"]
}
},
"provider": {
"label": "AI 服务商",
@@ -28,10 +40,13 @@
"loaded": "建议已加载",
"selected": "已选择建议",
"select": "选择",
"difficulty": "难度"
"difficulty": "难度",
"practiceNow": "立即练习",
"addAll": "全部添加"
},
"grading": {
"title": "AI 批改建议",
"description": "AI 驱动的主观题评分与反馈",
"suggestedScore": "建议分数",
"confidence": "置信度",
"feedback": "反馈",
@@ -40,7 +55,13 @@
"applyFeedback": "应用反馈",
"loading": "AI 批改中...",
"error": "AI 批改失败",
"notAvailable": "此题型不支持 AI 批改"
"notAvailable": "此题型不支持 AI 批改",
"batchTitle": "批量 AI 批改",
"batchDescription": "一次性为所有主观题生成 AI 建议",
"batchGenerate": "生成全部建议",
"batchProgress": "处理中 {done}/{total}",
"currentScore": "当前分数",
"scoreDifference": "差值"
},
"errorBook": {
"similarQuestions": "相似题目",
@@ -56,11 +77,18 @@
},
"lessonPrep": {
"generateContent": "生成内容",
"description": "AI 驱动的教学内容生成",
"generateActivity": "建议活动",
"generateAssessment": "生成评估",
"generateQuestion": "生成讨论题",
"loading": "生成中...",
"error": "内容生成失败"
"error": "内容生成失败",
"additionalContext": "附加上下文",
"additionalContextPlaceholder": "添加特定要求或上下文信息...",
"insertContent": "插入内容",
"editBeforeInsert": "插入前编辑",
"history": "生成历史",
"clearHistory": "清空历史"
},
"exam": {
"generate": "生成",
@@ -81,7 +109,64 @@
"sourceTextPlaceholder": "粘贴试卷文本以解析为题目",
"sourceTextDesc": "AI 将从文本中提取题目和结构。",
"generationTitle": "AI 生成",
"generationDesc": "粘贴试卷文本并生成结构化预览。"
"generationDesc": "粘贴试卷文本并生成结构化预览。",
"variantType": {
"label": "变体类型",
"same_knowledge_point": "同知识点,不同情境",
"different_difficulty": "不同难度",
"different_format": "不同题型"
},
"targetDifficulty": "目标难度",
"addVariant": "添加变体"
},
"parent": {
"summary": "AI 学情摘要",
"summaryDescription": "AI 生成的子女学习进度概览",
"generateSummary": "生成摘要",
"weaknessHint": "需关注领域",
"suggestion": "家庭辅导建议",
"loading": "生成摘要中...",
"error": "生成摘要失败"
},
"admin": {
"usageDashboard": "AI 使用仪表盘",
"dashboardDescription": "监控全校 AI 使用情况",
"totalCalls": "AI 调用总数",
"activeUsers": "活跃用户",
"costEstimate": "预估成本",
"topUsers": "高频用户",
"byCapability": "按能力分类",
"byRole": "按角色分类",
"recentActivity": "最近活动",
"noData": "暂无 AI 使用数据",
"callsToday": "今日调用",
"callsThisWeek": "本周调用",
"errorRate": "错误率",
"avgDuration": "平均耗时"
},
"studyPath": {
"title": "你的学习路径",
"description": "AI 个性化学习建议",
"nextSteps": "推荐下一步",
"mastered": "已掌握",
"inProgress": "学习中",
"needsWork": "需要加强",
"generate": "生成学习路径",
"loading": "生成学习路径中...",
"error": "生成学习路径失败",
"startLearning": "开始学习"
},
"widget": {
"title": "AI 助手",
"open": "打开 AI 助手",
"close": "关闭",
"contextAware": "上下文感知"
},
"safety": {
"blocked": "您的消息被安全过滤器拦截,请保持教育性对话。",
"dailyLimit": "今日 AI 使用次数已达上限,请明天再试。",
"studentMode": "AI 处于学生模式,将引导你自主找到答案。",
"contentFiltered": "AI 回复中的不当内容已被过滤。"
},
"error": {
"invalidInput": "输入数据无效",
@@ -104,6 +189,8 @@
"lessonContent": "AI 备课内容",
"questionVariant": "AI 题目变体",
"similarQuestion": "AI 相似题",
"weaknessAnalysis": "AI 薄弱点分析"
"weaknessAnalysis": "AI 薄弱点分析",
"childSummary": "AI 子女摘要",
"studyPath": "AI 学习路径"
}
}

View File

@@ -65,3 +65,31 @@ export const createAiChatCompletion = async (input: AiChatRequest) => {
const usage = "usage" in result ? result.usage ?? null : null
return { content, usage }
}
/**
* 流式 AI 聊天补全
*
* 返回 AsyncGenerator逐 token 产出内容。
* 用于 SSE 流式响应,降低用户感知延迟。
*/
export async function* createAiChatCompletionStream(
input: AiChatRequest
): AsyncGenerator<string, void, unknown> {
const config = await getAiProviderConfig(input.providerId)
const client = await getAiClient(config)
const stream = await client.chat.completions.create({
model: config.model || input.model,
messages: input.messages,
temperature: input.temperature,
...(typeof input.maxTokens === "number" ? { max_tokens: input.maxTokens } : {}),
stream: true,
})
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta
const content = extractMessageContent(delta)
if (content) {
yield content
}
}
}

View File

@@ -1,5 +1,5 @@
export { encryptAiApiKey, decryptAiApiKey } from "./api-key-crypto"
export { createAiChatCompletion, testAiProviderById, testAiProviderConfig } from "./client"
export { createAiChatCompletion, createAiChatCompletionStream, testAiProviderById, testAiProviderConfig } from "./client"
export { getAiErrorMessage } from "./errors"
export { parseAiChatPayload, isRecord } from "./payload-parser"
export type { AiChatRequest, ChatMessage, ChatRole } from "./payload-parser"

View File

@@ -60,6 +60,7 @@ export type EventName =
| "homework.auto_save_failed"
// AI 模块监控事件
| "ai.chat"
| "ai.chat_stream"
| "ai.similar_question"
| "ai.grading_assist"
| "ai.lesson_content"