Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
17 KiB
JSON
1 line
17 KiB
JSON
{"repo": "2150489918xf-a11y/ai-edu", "files": {"server/services/aiOutlineService.js": {"32ce596ff32068c7fa6561400b4f02851ea37a24": {"t": 1785746677.6746664, "n": 3895, "h": "a7837b258c0511be", "c": "function createHttpError(statusCode, code, message, details = {}) {\n const error = new Error(message);\n error.statusCode = statusCode;\n error.code = code;\n error.details = details;\n return error;\n}\n\nfunction stripMarkdownFences(value) {\n return String(value || '')\n .replace(/^```(?:json|markdown|md)?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n}\n\nfunction normalizeMessages(messages = []) {\n return Array.isArray(messages)\n ? messages\n .filter((message) => ['user', 'assistant', 'teacher', 'ai'].includes(message.role) && (message.content || message.text))\n .map((message) => ({\n role: ['assistant', 'ai'].includes(message.role) ? 'assistant' : 'user',\n content: String(message.content || message.text)\n }))\n : [];\n}\n\nfunction buildSystemPrompt() {\n return [\n 'You are an AI assistant for high-school teachers creating and revising lesson outlines.',\n 'Respond in Simplified Chinese.',\n 'You may answer naturally, explain your reasoning, or ask for missing information.',\n 'When the teacher asks you to generate, rewrite, supplement, or revise the outline, append exactly one structured block after the natural-language reply.',\n 'The block format is:',\n ':::outline-json',\n '{ ...complete JSON... }',\n ':::',\n 'Do not wrap the JSON block in Markdown fences.',\n 'The JSON root must contain version, tags, and sections.',\n 'tags may be strings or objects like { \"text\": \"力与加速度\", \"tone\": \"success\" }.',\n 'sections must contain 3 to 6 teaching sections unless the teacher explicitly asks otherwise.',\n 'Each section must contain id, phase, time, title, status, active, and cards.',\n 'Each cards item must be { \"label\": \"教师动作\", \"content\": \"具体内容\", \"tone\": \"default\" }.',\n 'tone must be one of default, focus, warning, success, muted.',\n 'At most one section may have active=true.',\n 'Keep the outline consistent with grade, subject, duration, and teaching goal.'\n ].join('\\n');\n}\n\nfunction buildUserPrompt({ course = {}, currentOutline = null }) {\n return [\n `课程标题: ${course.title || course.shortTitle || '当前课程'}`,\n `学段: ${course.grade || '未提供'}`,\n `学科: ${course.subject || '未提供'}`,\n `课时时长: ${course.duration || '未提供'}`,\n `教学目标: ${course.goal || '未提供'}`,\n '当前结构化大纲 JSON:',\n JSON.stringify(currentOutline || {}, null, 2),\n '请结合后续完整会话处理老师请求。'\n ].join('\\n');\n}\n\nexport function createAiOutlineService({ env = process.env, fetchImpl = globalThis.fetch } = {}) {\n if (!fetchImpl) {\n throw new Error('fetch implementation is required');\n }\n\n return {\n async generateOutlineReply(request = {}) {\n if (!env.DEEPSEEK_API_KEY) {\n throw createHttpError(500, 'AI_CREDENTIALS_MISSING', 'Missing DEEPSEEK_API_KEY');\n }\n\n const model = env.DEEPSEEK_MODEL || env.AI_MODEL || 'deepseek-chat';\n const baseUrl = env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com';\n const response = await fetchImpl(`${baseUrl.replace(/\\/$/, '')}/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${env.DEEPSEEK_API_KEY}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n model,\n messages: [\n { role: 'system', content: buildSystemPrompt() },\n { role: 'user', content: buildUserPrompt(request) },\n ...normalizeMessages(request.messages)\n ],\n temperature: 0.2\n })\n });\n\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n throw createHttpError(\n response.status,\n 'AI_PROVIDER_ERROR',\n payload.error?.message || 'DeepSeek request failed'\n );\n }\n\n return {\n content: stripMarkdownFences(payload.choices?.[0]?.message?.content || ''),\n provider: 'deepseek',\n model\n };\n }\n };\n}\n"}, "a06b53439145542bf46d451391237f8146b87252": {"t": 1785766760.159095, "n": 3895, "h": "a7837b258c0511be", "c": "function createHttpError(statusCode, code, message, details = {}) {\n const error = new Error(message);\n error.statusCode = statusCode;\n error.code = code;\n error.details = details;\n return error;\n}\n\nfunction stripMarkdownFences(value) {\n return String(value || '')\n .replace(/^```(?:json|markdown|md)?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n}\n\nfunction normalizeMessages(messages = []) {\n return Array.isArray(messages)\n ? messages\n .filter((message) => ['user', 'assistant', 'teacher', 'ai'].includes(message.role) && (message.content || message.text))\n .map((message) => ({\n role: ['assistant', 'ai'].includes(message.role) ? 'assistant' : 'user',\n content: String(message.content || message.text)\n }))\n : [];\n}\n\nfunction buildSystemPrompt() {\n return [\n 'You are an AI assistant for high-school teachers creating and revising lesson outlines.',\n 'Respond in Simplified Chinese.',\n 'You may answer naturally, explain your reasoning, or ask for missing information.',\n 'When the teacher asks you to generate, rewrite, supplement, or revise the outline, append exactly one structured block after the natural-language reply.',\n 'The block format is:',\n ':::outline-json',\n '{ ...complete JSON... }',\n ':::',\n 'Do not wrap the JSON block in Markdown fences.',\n 'The JSON root must contain version, tags, and sections.',\n 'tags may be strings or objects like { \"text\": \"力与加速度\", \"tone\": \"success\" }.',\n 'sections must contain 3 to 6 teaching sections unless the teacher explicitly asks otherwise.',\n 'Each section must contain id, phase, time, title, status, active, and cards.',\n 'Each cards item must be { \"label\": \"教师动作\", \"content\": \"具体内容\", \"tone\": \"default\" }.',\n 'tone must be one of default, focus, warning, success, muted.',\n 'At most one section may have active=true.',\n 'Keep the outline consistent with grade, subject, duration, and teaching goal.'\n ].join('\\n');\n}\n\nfunction buildUserPrompt({ course = {}, currentOutline = null }) {\n return [\n `课程标题: ${course.title || course.shortTitle || '当前课程'}`,\n `学段: ${course.grade || '未提供'}`,\n `学科: ${course.subject || '未提供'}`,\n `课时时长: ${course.duration || '未提供'}`,\n `教学目标: ${course.goal || '未提供'}`,\n '当前结构化大纲 JSON:',\n JSON.stringify(currentOutline || {}, null, 2),\n '请结合后续完整会话处理老师请求。'\n ].join('\\n');\n}\n\nexport function createAiOutlineService({ env = process.env, fetchImpl = globalThis.fetch } = {}) {\n if (!fetchImpl) {\n throw new Error('fetch implementation is required');\n }\n\n return {\n async generateOutlineReply(request = {}) {\n if (!env.DEEPSEEK_API_KEY) {\n throw createHttpError(500, 'AI_CREDENTIALS_MISSING', 'Missing DEEPSEEK_API_KEY');\n }\n\n const model = env.DEEPSEEK_MODEL || env.AI_MODEL || 'deepseek-chat';\n const baseUrl = env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com';\n const response = await fetchImpl(`${baseUrl.replace(/\\/$/, '')}/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${env.DEEPSEEK_API_KEY}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n model,\n messages: [\n { role: 'system', content: buildSystemPrompt() },\n { role: 'user', content: buildUserPrompt(request) },\n ...normalizeMessages(request.messages)\n ],\n temperature: 0.2\n })\n });\n\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n throw createHttpError(\n response.status,\n 'AI_PROVIDER_ERROR',\n payload.error?.message || 'DeepSeek request failed'\n );\n }\n\n return {\n content: stripMarkdownFences(payload.choices?.[0]?.message?.content || ''),\n provider: 'deepseek',\n model\n };\n }\n };\n}\n"}, "1d481c1007f6201f2833ff96ae5bc1523a3b6f6b": {"t": 1786068486.918861, "n": 3895, "h": "a7837b258c0511be", "c": "function createHttpError(statusCode, code, message, details = {}) {\n const error = new Error(message);\n error.statusCode = statusCode;\n error.code = code;\n error.details = details;\n return error;\n}\n\nfunction stripMarkdownFences(value) {\n return String(value || '')\n .replace(/^```(?:json|markdown|md)?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n}\n\nfunction normalizeMessages(messages = []) {\n return Array.isArray(messages)\n ? messages\n .filter((message) => ['user', 'assistant', 'teacher', 'ai'].includes(message.role) && (message.content || message.text))\n .map((message) => ({\n role: ['assistant', 'ai'].includes(message.role) ? 'assistant' : 'user',\n content: String(message.content || message.text)\n }))\n : [];\n}\n\nfunction buildSystemPrompt() {\n return [\n 'You are an AI assistant for high-school teachers creating and revising lesson outlines.',\n 'Respond in Simplified Chinese.',\n 'You may answer naturally, explain your reasoning, or ask for missing information.',\n 'When the teacher asks you to generate, rewrite, supplement, or revise the outline, append exactly one structured block after the natural-language reply.',\n 'The block format is:',\n ':::outline-json',\n '{ ...complete JSON... }',\n ':::',\n 'Do not wrap the JSON block in Markdown fences.',\n 'The JSON root must contain version, tags, and sections.',\n 'tags may be strings or objects like { \"text\": \"力与加速度\", \"tone\": \"success\" }.',\n 'sections must contain 3 to 6 teaching sections unless the teacher explicitly asks otherwise.',\n 'Each section must contain id, phase, time, title, status, active, and cards.',\n 'Each cards item must be { \"label\": \"教师动作\", \"content\": \"具体内容\", \"tone\": \"default\" }.',\n 'tone must be one of default, focus, warning, success, muted.',\n 'At most one section may have active=true.',\n 'Keep the outline consistent with grade, subject, duration, and teaching goal.'\n ].join('\\n');\n}\n\nfunction buildUserPrompt({ course = {}, currentOutline = null }) {\n return [\n `课程标题: ${course.title || course.shortTitle || '当前课程'}`,\n `学段: ${course.grade || '未提供'}`,\n `学科: ${course.subject || '未提供'}`,\n `课时时长: ${course.duration || '未提供'}`,\n `教学目标: ${course.goal || '未提供'}`,\n '当前结构化大纲 JSON:',\n JSON.stringify(currentOutline || {}, null, 2),\n '请结合后续完整会话处理老师请求。'\n ].join('\\n');\n}\n\nexport function createAiOutlineService({ env = process.env, fetchImpl = globalThis.fetch } = {}) {\n if (!fetchImpl) {\n throw new Error('fetch implementation is required');\n }\n\n return {\n async generateOutlineReply(request = {}) {\n if (!env.DEEPSEEK_API_KEY) {\n throw createHttpError(500, 'AI_CREDENTIALS_MISSING', 'Missing DEEPSEEK_API_KEY');\n }\n\n const model = env.DEEPSEEK_MODEL || env.AI_MODEL || 'deepseek-chat';\n const baseUrl = env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com';\n const response = await fetchImpl(`${baseUrl.replace(/\\/$/, '')}/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${env.DEEPSEEK_API_KEY}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n model,\n messages: [\n { role: 'system', content: buildSystemPrompt() },\n { role: 'user', content: buildUserPrompt(request) },\n ...normalizeMessages(request.messages)\n ],\n temperature: 0.2\n })\n });\n\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n throw createHttpError(\n response.status,\n 'AI_PROVIDER_ERROR',\n payload.error?.message || 'DeepSeek request failed'\n );\n }\n\n return {\n content: stripMarkdownFences(payload.choices?.[0]?.message?.content || ''),\n provider: 'deepseek',\n model\n };\n }\n };\n}\n"}}, "server/services/aiStudentTutorService.js": {"1d481c1007f6201f2833ff96ae5bc1523a3b6f6b": {"t": 1786068491.9456527, "n": 3647, "h": "80f216df4efa28cb", "c": "function createHttpError(statusCode, code, message, details = {}) {\n const error = new Error(message);\n error.statusCode = statusCode;\n error.code = code;\n error.details = details;\n return error;\n}\n\nfunction buildSystemPrompt() {\n return [\n '你是学生学习助手。',\n '你的目标是引导学生理解题目,而不是直接替学生报答案。',\n '先用简短问题或步骤帮助学生定位知识点,再解释解题思路。',\n '如果学生明确卡住,可以给出下一步提示;只有在学生要求核对时才说明最终结论。',\n '回答要适合高中学生阅读,语气清楚、克制、鼓励独立思考。'\n ].join('\\n');\n}\n\nfunction buildUserPrompt(request = {}) {\n const course = request.course || {};\n const question = request.question || {};\n return [\n '课程信息:',\n `课程:${course.title || '未提供'}`,\n `学科:${course.subject || '未提供'}`,\n `年级:${course.grade || '未提供'}`,\n '',\n '当前题目:',\n `题型:${question.type || '未提供'}`,\n `题干:${question.title || '未提供'}`,\n `选项:${Array.isArray(question.options) && question.options.length ? question.options.join(';') : '无'}`,\n `学生当前答案:${question.studentAnswer?.value || question.studentAnswer || '未作答'}`,\n '',\n '学生问题:',\n request.message || ''\n ].join('\\n');\n}\n\nasync function readProviderStream(response, onPayload) {\n if (!response.body?.getReader) {\n throw createHttpError(502, 'AI_STREAM_UNAVAILABLE', 'AI provider did not return a readable stream');\n }\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const frames = buffer.split(/\\n\\n/);\n buffer = frames.pop() || '';\n for (const frame of frames) {\n const lines = frame.split(/\\r?\\n/).filter((line) => line.startsWith('data:'));\n for (const line of lines) {\n const data = line.slice(5).trim();\n if (!data) continue;\n if (data === '[DONE]') return;\n try {\n onPayload(JSON.parse(data));\n } catch {\n // Ignore malformed provider frames and continue streaming.\n }\n }\n }\n }\n}\n\nexport function createAiStudentTutorService({ env = process.env, fetchImpl = globalThis.fetch } = {}) {\n if (!fetchImpl) throw new Error('fetch implementation is required');\n\n return {\n async streamChat(request = {}, handlers = {}) {\n if (!request.message || !String(request.message).trim()) {\n throw createHttpError(400, 'BAD_REQUEST', '缺少学生问题');\n }\n if (!env.DEEPSEEK_API_KEY) {\n throw createHttpError(500, 'AI_CREDENTIALS_MISSING', 'Missing DEEPSEEK_API_KEY');\n }\n\n const model = env.DEEPSEEK_MODEL || env.AI_MODEL || 'deepseek-chat';\n const baseUrl = env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com';\n const response = await fetchImpl(`${baseUrl.replace(/\\/$/, '')}/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${env.DEEPSEEK_API_KEY}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n model,\n stream: true,\n messages: [\n { role: 'system', content: buildSystemPrompt() },\n { role: 'user', content: buildUserPrompt(request) }\n ],\n temperature: 0.25\n })\n });\n\n if (!response.ok) {\n const payload = await response.json().catch(() => ({}));\n throw createHttpError(response.status, 'AI_PROVIDER_ERROR', payload.error?.message || 'AI provider request failed');\n }\n\n await readProviderStream(response, (payload) => {\n const text = payload.choices?.[0]?.delta?.content || '';\n if (text) handlers.onDelta?.(text);\n });\n handlers.onDone?.({ provider: 'deepseek', model });\n }\n };\n}\n"}}}} |