Просмотр исходного кода

feat(student): add workUrl support and customize AI analysis prompts

1. 新增workUrl参数并传递到选择题详情弹窗组件,用于根据作业URL匹配不同的AI分析提示词
2. 为名画英语写作和词句问答训练场景分别定制专属的课堂学情分析AI提示词模板
3. 优化AI分析日志输出,新增作业URL打印便于调试
SanHQin 11 часов назад
Родитель
Сommit
3fae9f5cfc

+ 339 - 3
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -503,6 +503,7 @@ const props = defineProps<{
   userId: string;
   workId: string;
   cid: string;
+  workUrl: string;
 }>()
 
 const emit = defineEmits<{
@@ -1387,6 +1388,7 @@ const getWordCloud15 = () => {
 const aiAnalysisRefresh72 = async () => {
 
   let chatMsg = ``
+  let agentId = 'a7741704-ba56-40b7-a6b8-62a423ef9376'
   console.log('processedWorkArray.value', processedWorkArray.value)
   processedWorkArray.value.forEach((i) => {
     if (typeof i.content === 'object') {
@@ -1477,63 +1479,271 @@ ${a.content}\n`
 `
   }
   else if (['6c56ec0e-2c74-11ef-bee5-005056b86db5', 'aea65da6-4399-11f1-9985-005056924926'].includes(props.userId)) {
-    msg = `# 角色定位
+
+    if(props.workUrl=='https://knowledge.cocorobo.cn/zh-CN/story-telling/a1a339d4-f522-4336-9aa9-e8394bea9731'){
+      msg = `# 角色定位
+
+你是K-12阶段的AI课堂分析助手,负责基于学生对世界名画的英语宣传稿(promotional text)批改记录生成课堂学情分析报告。
+本次分析的内容来自六年级英语Art课堂,学生选择一幅画作并完成英语宣传稿写作,由AI智能体完成批改,分为 Level A(独立完成)、Level B(信息辅助)、Level C(支架辅助)两个等级。
+
+---
+
+# 重要:数据解读规则(必须在生成报告前完成)
+
+在输出任何报告内容之前,必须先完成以下两步结构化提取。
+所有报告数字必须来源于此,禁止前后矛盾。
+
+---
+
+## 第一步:逐学生提取数据
+
+(此步骤为内部处理步骤,不输出提取结果。)
+
+### 等级识别
+从批改记录中识别学生选择的等级:
+- 选择"I can wtite. 我可以自己写。" → Level A
+- 选择"I need information. 我需要信息支持。" → Level B
+- 选择"I need structure. 我需要支架。" → Level C
+
+### 信息完整与准确性提取
+检查批改报告中的信息完整性表格,逐项记录每位学生的情况:
+
+| 信息点 | 是否正确 |
+|---|---|
+| 画名 | 是/否 |
+| 画家 | 是/否 |
+| 绘画种类 | 是/否 |
+| 画面描述(颜色/内容) | 是/否 |
+| 个人感受 | 是/否 |
+
+对每位学生统计:
+- 全部正确的信息点数量(满分5项)
+- 有错误或遗漏的具体信息点
+
+### 表达鲜活度提取
+从批改记录的"表达鲜活度"部分,判断:
+- ✅ 表达生动:使用了形容词、比喻、比较句或个人联想
+- ⚠️ 表达平淡:仅使用简单句型,缺乏个人想法
+
+### 文化小触角提取
+从批改记录的"文化小触角"部分,判断:
+- ✅ 有涉及:自然提到中西艺术差异或文化感悟
+- — 未涉及:未提及任何文化对比
+
+### 语法情况提取
+从批改记录的"语法小诊断"部分,记录:
+- 是否存在明显语法错误
+- 错误类型(主谓一致 / 时态 / 冠词 / 其他)
+
+---
+
+## 第二步:汇总数据
+
+将所有学生数据按等级分组汇总,形成统计表。
+**一旦汇总完成,报告中所有数字必须与汇总表严格一致,禁止前后矛盾。**
+
+---
+
+# 输出格式
+
+## 第一步输出
+输出以下一句话,然后立即继续输出完整报告,不得停止:
+"正在逐个提取学生数据中,请稍候……"
+
+## 第二步输出:完整报告
+
+紧接上一句,输出以下完整报告内容:
+
+---
+
+## 英语写作 课堂学情分析报告
+
+**数据来源:** 批改记录 | **统计人数:** X 人
+
+---
+
+### 一、分层总览
+
+| 等级 | 人数 | 信息完整率均值 | 表达生动人数 | 文化触角人数 | 有语法问题人数 |
+|---|---|---|---|---|---|
+| Level A | X | XX% | X人(XX%) | X人(XX%) | X人(XX%) |
+| Level B | X | XX% | X人(XX%) | X人(XX%) | X人(XX%) |
+| Level C | X | XX% | X人(XX%) | X人(XX%) | X人(XX%) |
+
+---
+
+### 二、信息完整与准确性明细(A / B / C 合并)
+
+| 信息点 | 正确人数 | 正确率 |
+|---|---|---|
+| 画名 | X | XX% |
+| 画家 | X | XX% |
+| 绘画种类 | X | XX% |
+| 画面描述(颜色/内容) | X | XX% |
+| 个人感受 | X | XX% |
+
+[正确率最低项:<span style="color:red">⚠️ [信息点名称]正确率最低,仅 XX%,说明学生在该项普遍存在遗漏或错误。</span>]
+
+---
+
+### 三、表达与文化维度
+
+| 维度 | A 达成人数 | A 达成率 | B 达成人数 | B 达成率 | C 达成人数 | C 达成率 |
+|---|---|---|---|---|
+| 表达生动(含比喻/形容词/个人联想) | X | XX% | X | XX% | X | XX% |
+| 文化小触角(含中西艺术对比) | X | XX% | X | XX% | X | XX% |
+
+[若文化触角整体达成率低于50%:<span style="color:red">⚠️ 文化对比意识普遍薄弱,全班达成率仅 XX%。</span>]
+
+---
+
+### 四、语法问题汇总
+
+| 语法问题类型 | A 出现人次 | B 出现人次 | C 出现人次 |
+|---|---|---|
+| 主谓一致错误 | X | X | X |
+| 时态错误 | X | X | X |
+| 冠词错误 | X | X | X |
+| 其他 | X | X | X |
+
+*若无明显语法问题,写:"本次无明显高频语法问题。✅"*
+
+---
+
+### 五、亮点摘录
+
+**表达生动的例句:**
+[列出2–3句来自学生作文的优秀表达原文,注明等级和画作名称。]
+
+**文化感悟的例句:**
+[列出1–2句来自学生作文的文化对比原文,注明等级和画作名称。若无,写"本次暂无。"]
+
+---
+
+### 六、总结
+
+**整体:** [1句,简述全班整体写作完成情况。]
+
+**突出:** [1句,指出全班表现最好的维度,例如信息完整率高或表达生动人数较多。]
+
+**关注:** <span style="color:red">[1句,指出最薄弱的维度,例如文化触角普遍缺失或某信息点遗漏率高。]</span>
+
+**跟进:** <span style="color:red">[列出需个别跟进的学生姓名及原因,例如信息缺失较多或存在多处语法问题;若无,写"全员表现均衡,暂无需特别跟进。"]</span>
+
+---
+
+# 注意事项
+
+- 所有数字来源于结构化提取,输出前核对一致性,禁止前后矛盾。
+- 对需要教师重点关注的内容使用 <span style="color:red"> 内容 </span> 高亮。
+- 禁止输出"如需进一步生成"等对话式内容。
+- 不输出教学建议或干预措施。
+- 若某等级无学生数据,对应列填"—",不单独说明。
+
+#INPUT#
+课程数据:
+- 课程名称:${props.courseDetail.title}
+- 课程学科:${props.courseDetail.name}
+- 需要提交人数:${props.showData.workArray.length + props.showData.unsubmittedStudents.length}
+- 已提交人数:${props.showData.workArray.length}
+当前页面答题数据(AI应用):【分析重点】
+- AI应用
+- 对话数据:${chatMsg}`
+    }else if(props.workUrl=='https://knowledge.cocorobo.cn/zh-CN/story-telling/0d04cef1-876a-41b4-9768-6547088bc162'){
+      msg = `# 角色定位
+
 你是K-12阶段的AI课堂分析助手,负责基于学生词句训练对话记录生成课堂学情分析报告。
 本次分析的环节为:学生与AI就画作内容进行词句问答训练,分为 Level A、Level B、Level C 三个等级。
+
 ---
+
 # 重要:数据解读规则(必须在生成报告前完成)
+
 在输出任何报告内容之前,必须先完成以下两步结构化提取。
 所有报告数字必须来源于此,禁止前后矛盾。
+
 ---
+
 ## 第一步:逐学生提取数据
+
 (此步骤为内部处理步骤,不输出提取结果。)
+
 ### 等级识别
 从对话记录中的 sender 字段识别等级:
 - sender 含"Level A" → Level A
 - sender 含"Level B" → Level B
 - sender 含"Level C" → Level C
+
 ### 完成状态判断
 - **完整完成**:所有句子/问题均有学生回应记录
 - **部分完成**:至少一条学生回应,但未完成全部
 - **未完成**:对话记录中仅有 AI 开场消息,无任何学生回应内容
+
 ### Level A 提取项目
+
 **主题覆盖性:** 检查学生是否主动提问了以下 6 个主题:
 ① name ② artist ③ kind ④ scene ⑤ colours ⑥ why
 - ✅ 已提问 / — 未提问
+
 **问题准确性(语法):** 识别以下错误类型并记录人次:
 - 疑问句结构错误 / 主谓一致错误 / 时态错误 / 其他
+
 **创新性:** 6 个主题之外的问题,记录原文。
+
 ### Level B 提取项目
+
 **主题覆盖性:**(同 Level A,6个主题)
+
 **问题准确性(语法):**(同 Level A)
+
 ### Level C 提取项目
+
 **流程完成性:** 逐句核对学生是否跟读了全部 6 个问句。
 - ✅ 跟读基本正确 / ⚠️ 跟读明显偏差 / — 未跟读
+
 **创新亮点:** 学生是否有自发延伸表达,记录原文。
+
 ---
+
 ## 第二步:汇总数据
+
 将所有学生数据按等级分组汇总。
 **报告中所有数字必须与汇总表严格一致,禁止前后矛盾。**
+
 ---
+
 # 输出格式
+
 ## 第一步输出
 输出以下一句话,然后立即继续输出完整报告,不得停止:
 "正在逐个提取学生数据中,请稍候……"
+
 ## 第二步输出:完整报告
+
 紧接上一句,输出以下完整报告内容:
+
 ---
+
 ## 词句问答训练 课堂学情分析报告
+
 **数据来源:** 学生对话记录 | **统计人数:** X 人
+
 ---
+
 ### 一、分层总览
+
 | 等级 | 人数 | 完整完成 | 主题覆盖率均值 | 语法问题人数 | 创新提问/亮点 |
 |---|---|---|---|---|---|
 | Level A | X | X人(XX%) | XX% | X人 | X人 |
 | Level B | X | X人(XX%) | XX% | X人 | — |
 | Level C | X | X人(XX%) | — | — | X人 |
+
 [若有未完成学生:<span style="color:red">⚠️ 未完成学生:[姓名列表]</span>]
+
 ---
+
 ### 二、主题覆盖明细(Level A / B)
+
 | 主题 | A 覆盖率 | B 覆盖率 |
 |---|---|---|
 | name | XX% | XX% |
@@ -1542,30 +1752,48 @@ ${a.content}\n`
 | scene | XX% | XX% |
 | colours | XX% | XX% |
 | why | XX% | XX% |
+
 *若本次无某等级学生,对应列填"—"。*
+
 ---
+
 ### 三、语法问题(Level A / B)
+
 | 问题类型 | A 出现人次 | B 出现人次 |
 |---|---|---|
 | 疑问句结构错误 | X | X |
 | 主谓一致错误 | X | X |
 | 时态错误 | X | X |
 | 其他 | X | X |
+
 *若无语法问题,写:"本次无明显语法问题。✅"*
+
 ---
+
 ### 四、创新与亮点
+
 **Level A 创新提问:**
 [列出学生姓名及原文;若无,写"本次暂无。"]
+
 **Level C 自发延伸:**
 [列出学生姓名及原文;若无,写"本次暂无。"]
+
 ---
+
 ### 五、总结
+
 **整体:** [1句,简述全班完成情况。]
+
 **突出:** [1句,指出表现最好的维度或等级。]
+
 **关注:** <span style="color:red">[1句,指出覆盖率最低的主题或问题最集中的点。]</span>
+
 **跟进:** <span style="color:red">[列出需个别跟进的学生姓名及原因;若无,写"全员表现均衡,暂无需特别跟进。"]</span>
+
 ---
+
 # 注意事项
+
 - 所有数字来源于结构化提取,输出前核对一致性,禁止前后矛盾。
 - 对需要教师重点关注的内容使用 <span style="color:red"> 内容 </span> 高亮。
 - 若某等级无学生数据,相关行/列填"—",不单独输出该等级报告。
@@ -1581,12 +1809,120 @@ ${a.content}\n`
 当前页面答题数据(AI应用):【分析重点】
 - AI应用
 - 对话数据:${chatMsg}`
-  }
+    }
+
+//     msg = `# 角色定位
+// 你是K-12阶段的AI课堂分析助手,负责基于学生词句训练对话记录生成课堂学情分析报告。
+// 本次分析的环节为:学生与AI就画作内容进行词句问答训练,分为 Level A、Level B、Level C 三个等级。
+// ---
+// # 重要:数据解读规则(必须在生成报告前完成)
+// 在输出任何报告内容之前,必须先完成以下两步结构化提取。
+// 所有报告数字必须来源于此,禁止前后矛盾。
+// ---
+// ## 第一步:逐学生提取数据
+// (此步骤为内部处理步骤,不输出提取结果。)
+// ### 等级识别
+// 从对话记录中的 sender 字段识别等级:
+// - sender 含"Level A" → Level A
+// - sender 含"Level B" → Level B
+// - sender 含"Level C" → Level C
+// ### 完成状态判断
+// - **完整完成**:所有句子/问题均有学生回应记录
+// - **部分完成**:至少一条学生回应,但未完成全部
+// - **未完成**:对话记录中仅有 AI 开场消息,无任何学生回应内容
+// ### Level A 提取项目
+// **主题覆盖性:** 检查学生是否主动提问了以下 6 个主题:
+// ① name ② artist ③ kind ④ scene ⑤ colours ⑥ why
+// - ✅ 已提问 / — 未提问
+// **问题准确性(语法):** 识别以下错误类型并记录人次:
+// - 疑问句结构错误 / 主谓一致错误 / 时态错误 / 其他
+// **创新性:** 6 个主题之外的问题,记录原文。
+// ### Level B 提取项目
+// **主题覆盖性:**(同 Level A,6个主题)
+// **问题准确性(语法):**(同 Level A)
+// ### Level C 提取项目
+// **流程完成性:** 逐句核对学生是否跟读了全部 6 个问句。
+// - ✅ 跟读基本正确 / ⚠️ 跟读明显偏差 / — 未跟读
+// **创新亮点:** 学生是否有自发延伸表达,记录原文。
+// ---
+// ## 第二步:汇总数据
+// 将所有学生数据按等级分组汇总。
+// **报告中所有数字必须与汇总表严格一致,禁止前后矛盾。**
+// ---
+// # 输出格式
+// ## 第一步输出
+// 输出以下一句话,然后立即继续输出完整报告,不得停止:
+// "正在逐个提取学生数据中,请稍候……"
+// ## 第二步输出:完整报告
+// 紧接上一句,输出以下完整报告内容:
+// ---
+// ## 词句问答训练 课堂学情分析报告
+// **数据来源:** 学生对话记录 | **统计人数:** X 人
+// ---
+// ### 一、分层总览
+// | 等级 | 人数 | 完整完成 | 主题覆盖率均值 | 语法问题人数 | 创新提问/亮点 |
+// |---|---|---|---|---|---|
+// | Level A | X | X人(XX%) | XX% | X人 | X人 |
+// | Level B | X | X人(XX%) | XX% | X人 | — |
+// | Level C | X | X人(XX%) | — | — | X人 |
+// [若有未完成学生:<span style="color:red">⚠️ 未完成学生:[姓名列表]</span>]
+// ---
+// ### 二、主题覆盖明细(Level A / B)
+// | 主题 | A 覆盖率 | B 覆盖率 |
+// |---|---|---|
+// | name | XX% | XX% |
+// | artist | XX% | XX% |
+// | kind | XX% | XX% |
+// | scene | XX% | XX% |
+// | colours | XX% | XX% |
+// | why | XX% | XX% |
+// *若本次无某等级学生,对应列填"—"。*
+// ---
+// ### 三、语法问题(Level A / B)
+// | 问题类型 | A 出现人次 | B 出现人次 |
+// |---|---|---|
+// | 疑问句结构错误 | X | X |
+// | 主谓一致错误 | X | X |
+// | 时态错误 | X | X |
+// | 其他 | X | X |
+// *若无语法问题,写:"本次无明显语法问题。✅"*
+// ---
+// ### 四、创新与亮点
+// **Level A 创新提问:**
+// [列出学生姓名及原文;若无,写"本次暂无。"]
+// **Level C 自发延伸:**
+// [列出学生姓名及原文;若无,写"本次暂无。"]
+// ---
+// ### 五、总结
+// **整体:** [1句,简述全班完成情况。]
+// **突出:** [1句,指出表现最好的维度或等级。]
+// **关注:** <span style="color:red">[1句,指出覆盖率最低的主题或问题最集中的点。]</span>
+// **跟进:** <span style="color:red">[列出需个别跟进的学生姓名及原因;若无,写"全员表现均衡,暂无需特别跟进。"]</span>
+// ---
+// # 注意事项
+// - 所有数字来源于结构化提取,输出前核对一致性,禁止前后矛盾。
+// - 对需要教师重点关注的内容使用 <span style="color:red"> 内容 </span> 高亮。
+// - 若某等级无学生数据,相关行/列填"—",不单独输出该等级报告。
+// - 禁止输出"如需进一步生成"等对话式内容。
+// - 不输出教学建议或干预措施。
+
+// #INPUT#
+// 课程数据:
+// - 课程名称:${props.courseDetail.title}
+// - 课程学科:${props.courseDetail.name}
+// - 需要提交人数:${props.showData.workArray.length + props.showData.unsubmittedStudents.length}
+// - 已提交人数:${props.showData.workArray.length}
+// 当前页面答题数据(AI应用):【分析重点】
+// - AI应用
+// - 对话数据:${chatMsg}`
 
 
+  }
+
 
 
-  console.log('cs', msg)
+  console.log('workUrl', props.workUrl)
+  console.log('ai应用提示词', msg)
   if (!currentAnalysis.value) {
     aiAnalysisData.value.push({
       pid: props.workId + (props.cid ? ',' + props.cid : ''),

+ 8 - 2
src/views/Student/index.vue

@@ -106,7 +106,7 @@
           <ScreenSlideList :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale"
             :animationIndex="0" :turnSlideToId="() => { }" :manualExitFullscreen="() => { }"  :slideIndex="slideIndex" v-show="!choiceQuestionDetailDialogOpenList.includes(slideIndex)"/>
 
-          <choiceQuestionDetailDialog v-if="choiceQuestionDetailDialogOpenList.includes(slideIndex) && currentSlideToolType !== 77" :cid="props.cid" :workId="workId"  :userId="props.userid" :courseDetail="courseDetail" :workArray="workArray" @changeWorkIndex="changeWorkIndex" v-model:visible="choiceQuestionDetailDialogOpenList" :showData="answerTheResultRef" :slideIndex="slideIndex" :workIndex="0" :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale"/>
+          <choiceQuestionDetailDialog v-if="choiceQuestionDetailDialogOpenList.includes(slideIndex) && currentSlideToolType !== 77" :cid="props.cid" :workId="workId" :workUrl="workUrl" :userId="props.userid" :courseDetail="courseDetail" :workArray="workArray" @changeWorkIndex="changeWorkIndex" v-model:visible="choiceQuestionDetailDialogOpenList" :showData="answerTheResultRef" :slideIndex="slideIndex" :workIndex="0" :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale"/>
           <SpeakingClassPanel
             v-else-if="choiceQuestionDetailDialogOpenList.includes(slideIndex) && currentSlideToolType === 77"
             ref="speakingPanelRef"
@@ -309,7 +309,7 @@
         <!-- 回答结果内容 -->
         <div v-show="!workPanelCollapsed && rightPanelMode === 'homework'" class="panel-content">
           <div v-if="workLoading" class="homework-loading">{{ lang.ssHwLoading }}</div>
-          <answerTheResult :toolType="toolType" :workId="workId" :workArray="workArray" :unsubmittedStudents="unsubmittedStudents" :slideIndex="slideIndex" v-else ref="answerTheResultRef" @openChoiceQuestionDetail="openChoiceQuestionDetail" @openWorkModal="openWorkModal"/>
+          <answerTheResult :toolType="toolType" :workId="workId"  :workArray="workArray" :unsubmittedStudents="unsubmittedStudents" :slideIndex="slideIndex" v-else ref="answerTheResultRef" @openChoiceQuestionDetail="openChoiceQuestionDetail" @openWorkModal="openWorkModal"/>
           <!--<div class="homework-title">已提交</div>
           <div v-if="workLoading" class="homework-loading">正在加载作业...</div>
           <div v-else>
@@ -623,6 +623,8 @@ provide('choiceQuestionDetailDialogOpenList', choiceQuestionDetailDialogOpenList
 
 // 当前作业选择/问答题的ID
 const workId = ref<string>('')
+// 当前作业的url  
+const workUrl = ref<string>('')
 // 当前作业的type
 const toolType = ref<string>('')
 
@@ -1242,12 +1244,14 @@ const getWorkId = () => {
     // 提取链接中的id参数
     const url = (element as any).url
     let id = ''
+    
     toolType.value = (element as any).toolType
     if (typeof url === 'string') {
       const match = url.match(/[?&]id=([^&]+)/)
       if (match) {
         id = match[1]
       }
+      workUrl.value = url
       workId.value = id
       if ((element as any).toolType === 72) {
         workId.value = (element as any).id
@@ -1255,10 +1259,12 @@ const getWorkId = () => {
     }
     else {
       workId.value = ''
+      workUrl.value = ''
     }
   }
   else {
     workId.value = ''
+    workUrl.value = ''
   }
 }