Spaces:
Sleeping
Sleeping
| const { | |
| User, Student, Score, AttendanceModel, ClassModel, | |
| Course, ConfigModel | |
| } = require('./models'); | |
| /** | |
| * 格式化当前日期 | |
| */ | |
| const getCurrentDateInfo = () => { | |
| const now = new Date(); | |
| const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; | |
| return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${days[now.getDay()]}`; | |
| }; | |
| /** | |
| * 辅助函数:解析学年前缀 | |
| * 例如 "2023-2024学年 第二学期" -> "2023-2024学年" | |
| */ | |
| const getSchoolYearPrefix = (semester) => { | |
| if (!semester) return null; | |
| const match = semester.match(/^(.+?学年)/); | |
| return match ? match[1] : null; | |
| }; | |
| /** | |
| * 构建学生画像上下文 (学生视角) | |
| */ | |
| async function buildStudentContext(username, schoolId) { | |
| const student = await Student.findOne({ | |
| $or: [{ studentNo: username }, { name: username }], | |
| schoolId | |
| }); | |
| if (!student) return "无法找到该学生的详细档案。"; | |
| // 获取当前学期配置 | |
| const config = await ConfigModel.findOne({ key: 'main' }); | |
| const currentSemester = config ? config.semester : null; | |
| // 逻辑:尝试拉取本学年的所有数据 (第一学期 + 第二学期) | |
| const schoolYearPrefix = getSchoolYearPrefix(currentSemester); | |
| const scoreQuery = { | |
| studentNo: student.studentNo, | |
| schoolId | |
| }; | |
| if (schoolYearPrefix) { | |
| // 匹配该学年开头的所有学期 | |
| scoreQuery.semester = { $regex: new RegExp('^' + schoolYearPrefix) }; | |
| } else if (currentSemester) { | |
| scoreQuery.semester = currentSemester; | |
| } | |
| // 获取成绩 (稍微放宽限制以容纳整年数据) | |
| const recentScores = await Score.find(scoreQuery).sort({ semester: -1, _id: -1 }).limit(100); | |
| // 获取考勤概况 | |
| const attendanceStats = await AttendanceModel.aggregate([ | |
| { $match: { studentId: student._id.toString() } }, | |
| { $group: { _id: "$status", count: { $sum: 1 } } } | |
| ]); | |
| const absentCount = attendanceStats.find(a => a._id === 'Absent')?.count || 0; | |
| const leaveCount = attendanceStats.find(a => a._id === 'Leave')?.count || 0; | |
| let prompt = ` | |
| ### 当前用户身份:学生 (个人视图) | |
| - **姓名**: ${student.name} | |
| - **班级**: ${student.className} | |
| - **学号**: ${student.studentNo} | |
| - **积分(小红花)**: ${student.flowerBalance} 🌺 | |
| - **当前学期**: ${currentSemester || '全部'} | |
| ### 个人学习数据 | |
| `; | |
| if (recentScores.length > 0) { | |
| // 格式化输出,带上学期标识 | |
| const scoreList = recentScores.map(s => { | |
| const semShort = s.semester ? s.semester.replace(schoolYearPrefix || '', '').trim() : ''; | |
| return `${s.courseName}: ${s.score} (${semShort} ${s.examName||s.type})`; | |
| }).join('\n'); | |
| prompt += `#### 成绩记录 (本学年):\n${scoreList}\n`; | |
| } else { | |
| prompt += `- **近期成绩**: 暂无记录\n`; | |
| } | |
| if (absentCount > 0 || leaveCount > 0) { | |
| prompt += `- **考勤**: 缺勤 ${absentCount} 次,请假 ${leaveCount} 次。\n`; | |
| } else { | |
| prompt += `- **考勤**: 全勤。\n`; | |
| } | |
| return prompt; | |
| } | |
| /** | |
| * 构建教师画像上下文 (严格权限版 + 全量数据) | |
| */ | |
| async function buildTeacherContext(username, schoolId) { | |
| const user = await User.findOne({ username, schoolId }); | |
| if (!user) return "无法找到该教师档案。"; | |
| const homeroomClass = user.homeroomClass; | |
| // 1. 查找任教课程信息 (确定科任权限) | |
| const courses = await Course.find({ | |
| schoolId, | |
| $or: [{ teacherId: user._id }, { teacherName: user.trueName || user.username }] | |
| }); | |
| // 2. 确定有权限的班级列表 | |
| const authorizedClasses = new Set(); | |
| const teachingSubjectsMap = {}; // Map<ClassName, Set<SubjectName>> | |
| // 班主任权限:拥有该班级所有数据权限 | |
| if (homeroomClass) { | |
| authorizedClasses.add(homeroomClass); | |
| } | |
| // 科任权限:拥有特定班级的特定科目权限 | |
| courses.forEach(c => { | |
| if (c.className) { | |
| authorizedClasses.add(c.className); | |
| if (!teachingSubjectsMap[c.className]) teachingSubjectsMap[c.className] = new Set(); | |
| teachingSubjectsMap[c.className].add(c.courseName); | |
| } | |
| }); | |
| const classList = Array.from(authorizedClasses); | |
| if (classList.length === 0) { | |
| return `### 当前用户身份:教师 (${user.trueName})\n目前系统显示您未绑定任何班级。请告知用户去“班级管理”或“课程安排”进行绑定。`; | |
| } | |
| // 3. 全量拉取相关班级的学生 | |
| const students = await Student.find({ | |
| schoolId, | |
| className: { $in: classList }, | |
| status: 'Enrolled' | |
| }).sort({ seatNo: 1, studentNo: 1 }); // 按座号排序 | |
| if (students.length === 0) { | |
| return `### 当前用户身份:教师\n管理班级: [${classList.join(', ')}]\n但系统未在这些班级找到学生档案。`; | |
| } | |
| const studentNos = students.map(s => s.studentNo); | |
| // 4. 拉取这些学生的成绩 (限制为本学年:包含第一学期和第二学期) | |
| const config = await ConfigModel.findOne({ key: 'main' }); | |
| const currentSemester = config ? config.semester : null; | |
| const schoolYearPrefix = getSchoolYearPrefix(currentSemester); | |
| const scoreQuery = { | |
| schoolId, | |
| studentNo: { $in: studentNos }, | |
| status: 'Normal' | |
| }; | |
| // 关键修复:使用正则匹配整个学年 (例如 "2024-2025学年") | |
| if (schoolYearPrefix) { | |
| scoreQuery.semester = { $regex: new RegExp('^' + schoolYearPrefix) }; | |
| } else if (currentSemester) { | |
| // 如果无法解析年份,回退到当前学期 | |
| scoreQuery.semester = currentSemester; | |
| } | |
| const allScores = await Score.find(scoreQuery); | |
| // 5. 构建 Prompt | |
| let prompt = ` | |
| ### 当前用户身份:教师 (${user.trueName || username}) | |
| ### 权限范围 (严格遵守) | |
| 你只能回答下列班级的数据。如果用户询问其他班级(例如用户只教一年级,却问四年级),请礼貌拒绝,说明权限不足。 | |
| 管理班级: [${classList.join(', ')}] | |
| 数据范围: ${schoolYearPrefix ? schoolYearPrefix + " (全学年)" : (currentSemester || '所有历史')} | |
| ### 详细班级数据 | |
| `; | |
| for (const cls of classList) { | |
| const isClassHomeroom = cls === homeroomClass; | |
| const subjects = teachingSubjectsMap[cls] ? Array.from(teachingSubjectsMap[cls]) : []; | |
| const roleText = isClassHomeroom ? "班主任 (全科权限)" : `任课老师 (科目: ${subjects.join(', ')})`; | |
| prompt += `\n#### 🏫 ${cls} [${roleText}]\n`; | |
| const clsStudents = students.filter(s => s.className === cls); | |
| if (clsStudents.length === 0) { | |
| prompt += "(暂无学生)\n"; | |
| continue; | |
| } | |
| prompt += `学生总数: ${clsStudents.length}人\n名单及成绩 (格式: [学期]科目:分数):\n`; | |
| for (const s of clsStudents) { | |
| let sScores = allScores.filter(sc => sc.studentNo === s.studentNo); | |
| // 如果不是班主任,仅展示自己教的科目成绩 | |
| if (!isClassHomeroom && subjects.length > 0) { | |
| sScores = sScores.filter(sc => subjects.includes(sc.courseName)); | |
| } | |
| // 格式: 张三(01号): [一]语文:90, [二]语文:85... | |
| const scoreStr = sScores.length > 0 | |
| ? sScores.map(sc => { | |
| // 简化学期显示,例如 "第一学期" -> "一", "第二学期" -> "二" | |
| let semLabel = ""; | |
| if (sc.semester && schoolYearPrefix) { | |
| if (sc.semester.includes("第一")) semLabel = "[上]"; | |
| else if (sc.semester.includes("第二")) semLabel = "[下]"; | |
| } | |
| return `${semLabel}${sc.courseName}:${sc.score}`; | |
| }).join(', ') | |
| : (isClassHomeroom ? "暂无本学年成绩" : "无本科目成绩"); | |
| prompt += `- ${s.name} (${s.seatNo ? s.seatNo+'号' : '无座号'}): ${scoreStr}\n`; | |
| } | |
| } | |
| return prompt; | |
| } | |
| /** | |
| * 构建管理员/校长画像上下文 | |
| */ | |
| async function buildAdminContext(role, schoolId) { | |
| return `### 当前用户身份:${role === 'PRINCIPAL' ? '校长' : '超级管理员'}\n你拥有最高权限,但请注意保护隐私。`; | |
| } | |
| /** | |
| * 主入口 | |
| */ | |
| async function buildUserContext(username, role, schoolId) { | |
| try { | |
| const dateStr = getCurrentDateInfo(); | |
| let roleContext = ""; | |
| if (role === 'STUDENT') { | |
| roleContext = await buildStudentContext(username, schoolId); | |
| } else if (role === 'TEACHER') { | |
| roleContext = await buildTeacherContext(username, schoolId); | |
| } else if (role === 'ADMIN' || role === 'PRINCIPAL') { | |
| roleContext = await buildAdminContext(role, schoolId); | |
| } | |
| return ` | |
| --- | |
| 【系统注入上下文】 | |
| 当前时间: ${dateStr} | |
| 以下是当前用户权限范围内的 **全量真实数据**。 | |
| ${roleContext} | |
| 【AI 行为准则】 | |
| 1. **角色设定**: 你是学校的AI智能助手。你的职责是协助查询校内信息,同时也乐于回答通用的百科/日常问题。 | |
| 2. **数据查询规则**: | |
| - 当用户询问 **学校内部数据** (如: 学生成绩、考勤、班级名单、老师信息) 时,**必须且只能** 使用上述提供的【系统注入上下文】。 | |
| - 如果用户询问的 **校内数据** 不在上下文中 (例如: 用户只教一年级,却问三年级的数据),请明确告知“无权限查看该数据”。 | |
| 3. **通用问答规则**: | |
| - 当用户询问 **非校内数据** (如: 天气、历史、写代码、翻译、闲聊) 时,请忽略权限限制,利用你的通用知识库或联网搜索功能正常回答。不要因为上下文中没有天气数据就拒绝回答。 | |
| 4. **联网搜索**: 如果用户开启了联网搜索,积极搜索最新信息回答通用问题。 | |
| --- | |
| `; | |
| } catch (e) { | |
| console.error("Context build failed:", e); | |
| return ""; | |
| } | |
| } | |
| module.exports = { buildUserContext }; | |