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:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 学习路径"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user