Przeglądaj źródła

feat(学生分析): 添加AI生成选择题和问答题分析功能

- 新增aiChat工具类实现与AI服务的流式和非流式交互
- 在选择题详情弹窗中添加AI分析模块,支持生成和显示分析报告
- 添加多语言支持的分析相关文本
- 引入uuid库用于生成唯一会话ID
- 实现分析数据的保存和获取功能
- 优化界面样式和交互体验
SanHQin 3 tygodni temu
rodzic
commit
dc2dd75fa2

+ 1 - 0
package.json

@@ -52,6 +52,7 @@
     "svg-pathdata": "^7.1.0",
     "tinycolor2": "^1.6.0",
     "tippy.js": "^6.3.7",
+    "uuid": "^13.0.0",
     "vue": "^3.5.17",
     "vuedraggable": "^4.1.0",
     "wangeditor": "^4.7.15",

+ 121 - 0
src/tools/aiChat.ts

@@ -0,0 +1,121 @@
+import axios from "@/services/config"
+import { v4 as uuidv4 } from 'uuid'
+import { fetchEventSource } from '@microsoft/fetch-event-source'
+
+interface ChatParams {
+  model: string;
+  temperature: number;
+  max_tokens: number;
+  top_p: number;
+  frequency_penalty: number;
+  presence_penalty: number;
+  messages: Array<{ role: string; content: string }>;
+  uid: string;
+  mind_map_question: string;
+  stream: boolean;
+  response_format: { type: string };
+}
+
+const DEFAULT_PARAMS: Omit<ChatParams, 'messages' | 'uid'> = {
+  model: "gpt-4o-2024-11-20",
+  temperature: 0,
+  max_tokens: 4096,
+  top_p: 1,
+  frequency_penalty: 0,
+  presence_penalty: 0,
+  mind_map_question: "",
+  stream: false,
+  response_format: { type: "text" }
+};
+
+export const chat_no_stream = async (msg: string): Promise<string> => {
+  const params: ChatParams = {
+    ...DEFAULT_PARAMS,
+    messages: [{ role: "user", content: msg }],
+    uid: uuidv4(),
+    stream: false
+  };
+
+  try {
+    const res = await axios.post('https://appapi.cocorobo.cn/api/common/chat', params);
+    let content = res.FunctionResponse.choices[0].message.content;
+
+    // 清理可能的 markdown 格式
+    if (content.includes('```json')) {
+      // 提取 ```json 和 ``` 之间的内容
+      const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/);
+      if (jsonMatch) {
+        content = jsonMatch[1].trim();
+      }
+    } else if (content.includes('```')) {
+      // 提取 ``` 和 ``` 之间的内容
+      const codeMatch = content.match(/```\s*([\s\S]*?)\s*```/);
+      if (codeMatch) {
+        content = codeMatch[1].trim();
+      }
+    }
+
+    return content
+  } catch (err) {
+    throw err;
+  }
+};
+
+export const chat_stream = async (
+  msg: string,
+  onMessage: (event: { type: 'message' | 'close' | 'error' | 'messageEnd'; data: string }) => void
+): Promise<void> => {
+  const params: ChatParams = {
+    ...DEFAULT_PARAMS,
+    messages: [{ role: "user", content: msg }],
+    uid: uuidv4(),
+    stream: true
+  };
+
+  const ctrl = new AbortController();
+  let content = ''
+  try {
+    await fetchEventSource('https://appapi.cocorobo.cn/api/common/chat', {
+      method: 'POST',
+      body: JSON.stringify(params),
+      signal: ctrl.signal,
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      onmessage(event) {
+        let data = JSON.parse(event.data)
+        if (data.content) {
+          if (data.content != '[DONE]') {
+            content += data.content
+            onMessage({
+              type: 'message',
+              data: content
+            });
+          }else{
+            onMessage({
+              type: 'messageEnd',
+              data: content
+            });
+          }
+
+        }
+      },
+      onclose() {
+        onMessage({
+          type: 'close',
+          data: 'SSE Connection closed'
+        });
+      },
+      onerror(err) {
+        onMessage({
+          type: 'error',
+          data: err.message || 'Unknown error'
+        });
+        // 返回 undefined 以阻止自动重连,如需重连则删除此行
+        throw err;
+      },
+    });
+  } finally {
+    ctrl.abort();
+  }
+};

+ 318 - 43
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -43,18 +43,22 @@
         }">
           <div id="echartsArea1" ref="echartsArea1"></div>
         </div>
-        <!-- <div class="c_t45_aiAnalysis">
-          <div class="c_t45_aa_header">
-            <div class="c_t45_aa_h_title">
-              <svg viewBox="0 0 1024 1024" width="200" height="200"><path d="M512 170.666667C323.477333 170.666667 170.666667 323.477333 170.666667 512s152.810667 341.333333 341.333333 341.333333 341.333333-152.810667 341.333333-341.333333S700.522667 170.666667 512 170.666667zM85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333s426.666667 191.018667 426.666667 426.666667-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512z"></path><path d="M693.013333 330.986667a42.666667 42.666667 0 0 1 10.304 43.648l-75.413333 226.282666a42.666667 42.666667 0 0 1-26.986667 26.986667l-226.282666 75.413333a42.666667 42.666667 0 0 1-53.973334-53.973333l75.434667-226.261333a42.666667 42.666667 0 0 1 26.986667-26.986667l226.282666-75.413333a42.666667 42.666667 0 0 1 43.648 10.304z m-222.72 139.306666l-41.685333 125.098667 125.077333-41.706667 41.706667-125.077333-125.077333 41.706667z"></path></svg>分析
+        <div class="aiAnalysis">
+          <div class="ai_header">
+            <div class="ai_title">
+              <svg viewBox="0 0 1024 1024" width="200" height="200"><path d="M512 170.666667C323.477333 170.666667 170.666667 323.477333 170.666667 512s152.810667 341.333333 341.333333 341.333333 341.333333-152.810667 341.333333-341.333333S700.522667 170.666667 512 170.666667zM85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333s426.666667 191.018667 426.666667 426.666667-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512z"></path><path d="M693.013333 330.986667a42.666667 42.666667 0 0 1 10.304 43.648l-75.413333 226.282666a42.666667 42.666667 0 0 1-26.986667 26.986667l-226.282666 75.413333a42.666667 42.666667 0 0 1-53.973334-53.973333l75.434667-226.261333a42.666667 42.666667 0 0 1 26.986667-26.986667l226.282666-75.413333a42.666667 42.666667 0 0 1 43.648 10.304z m-222.72 139.306666l-41.685333 125.098667 125.077333-41.706667 41.706667-125.077333-125.077333 41.706667z"></path></svg>{{ lang.ssAnalysis }}
             </div>
-            <div class="c_t45_aa_h_refresh">
-              AI生成
+            <div class="ai_refresh" :class="{'disabled': currentAnalysis && currentAnalysis.loading}" @click="aiAnalysisRefresh45()">
+              {{ lang.ssAIGenerate }}
              <svg viewBox="0 0 1024 1024" width="200" height="200"><path d="M875 483c-33.4 0-60.5 27.1-60.5 60.5v0.1C814.4 710.3 678.8 846 512 846S209.5 710.3 209.5 543.5 345.2 241 512 241c36.8 0 71.7 7.6 104.4 19.7-32 3-57.4 29.1-57.4 61.9 0 34.8 28.2 63 63 63h201.9c34.8 0 63-28.2 63-63V120c0-34.8-28.2-63-63-63s-63 28.2-63 63v81.4C691 150.5 605.2 120 512 120 278.1 120 88.5 309.6 88.5 543.5S278.1 967 512 967s423.5-189.6 423.5-423.5c0-33.4-27.1-60.5-60.5-60.5z"></path></svg>
 
             </div>
           </div>
-        </div> -->
+          <div class="ai_content" v-if="currentAnalysis">
+            {{ currentAnalysis.json }}
+          </div>
+          <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}</div>
+        </div>
         <div class="cq_changeBtn" v-if="props.showData.choiceQuestionListData.length > 1">
           <div :class="{ cq_cb_disabled: props.showData.workIndex <= 0 }" @click="changeWorkIndex(0)">
             <svg style="transform: rotate(-90deg);" viewBox="0 0 1024 1024" version="1.1" width="200" height="200">
@@ -91,6 +95,24 @@
           </div>
         </div>
 
+       <div class="aiAnalysis" style="margin-top:1rem ;">
+          <div class="ai_header">
+            <div class="ai_title">
+              <svg viewBox="0 0 1024 1024" width="200" height="200"><path d="M512 170.666667C323.477333 170.666667 170.666667 323.477333 170.666667 512s152.810667 341.333333 341.333333 341.333333 341.333333-152.810667 341.333333-341.333333S700.522667 170.666667 512 170.666667zM85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333s426.666667 191.018667 426.666667 426.666667-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512z"></path><path d="M693.013333 330.986667a42.666667 42.666667 0 0 1 10.304 43.648l-75.413333 226.282666a42.666667 42.666667 0 0 1-26.986667 26.986667l-226.282666 75.413333a42.666667 42.666667 0 0 1-53.973334-53.973333l75.434667-226.261333a42.666667 42.666667 0 0 1 26.986667-26.986667l226.282666-75.413333a42.666667 42.666667 0 0 1 43.648 10.304z m-222.72 139.306666l-41.685333 125.098667 125.077333-41.706667 41.706667-125.077333-125.077333 41.706667z"></path></svg>{{ lang.ssAnalysis }}
+            </div>
+            <div class="ai_refresh" :class="{'disabled': currentAnalysis && currentAnalysis.loading}" @click="aiAnalysisRefresh15()">
+              {{ lang.ssAIGenerate }}
+             <svg viewBox="0 0 1024 1024" width="200" height="200"><path d="M875 483c-33.4 0-60.5 27.1-60.5 60.5v0.1C814.4 710.3 678.8 846 512 846S209.5 710.3 209.5 543.5 345.2 241 512 241c36.8 0 71.7 7.6 104.4 19.7-32 3-57.4 29.1-57.4 61.9 0 34.8 28.2 63 63 63h201.9c34.8 0 63-28.2 63-63V120c0-34.8-28.2-63-63-63s-63 28.2-63 63v81.4C691 150.5 605.2 120 512 120 278.1 120 88.5 309.6 88.5 543.5S278.1 967 512 967s423.5-189.6 423.5-423.5c0-33.4-27.1-60.5-60.5-60.5z"></path></svg>
+
+            </div>
+          </div>
+          <div class="ai_content" v-if="currentAnalysis">
+            {{ currentAnalysis.json }}
+          </div>
+          <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}</div>
+        </div>
+
+
         <div class="c_t15_workDetail" v-if="lookWorkData">
           <div class="c_t15_wd_top">
             <img src="../../../assets/img/arrow_left.png" @click="lookWork('')" />
@@ -201,13 +223,15 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch, onUnmounted, nextTick } from 'vue'
+import { computed, ref, watch, onUnmounted, nextTick, onMounted } from 'vue'
 import * as echarts from 'echarts'
 import previewImageTool from '../../components/tool/previewImageTool.vue'
 import MarkdownIt from 'markdown-it'
 import useImport from '@/hooks/useImport'
 import { lang } from '@/main'
 import selectUserDialog from './selectUserDialog.vue'
+import { chat_stream } from '@/tools/aiChat'
+import axios from "@/services/config"
 const props = defineProps<{
   visible: number[];
   workIndex: number;
@@ -216,6 +240,7 @@ const props = defineProps<{
   slideIndex: number;
   showData: any;
   workArray: any[];
+  courseDetail: any;
 }>()
 
 const emit = defineEmits<{
@@ -240,6 +265,9 @@ const previewImageToolRef = ref<any>(null)
 // 选择用户组件
 const selectUserDialogRef = ref<any>(null)
 
+//ai分析数据
+const aiAnalysisData = ref<Array<any>>([])
+
 const md = new MarkdownIt()
 const { getFile } = useImport()
 
@@ -455,6 +483,7 @@ const setEchartsArea1 = () => {
   if (myChart.value) {
     const _work =
       props.showData.choiceQuestionListData[props.showData.workIndex]
+      console.log('_work', _work)
     // 修正版,处理xAxis.data内为图片对象的case,formatter始终只拿到src或自定义label,保证无[object Object]问题
     const option = {
       tooltip: {
@@ -580,7 +609,7 @@ const setEchartsArea1 = () => {
       (option.series[0].data as any[]).push(i.user.length)
     })
 
-    console.log(option)
+    // console.log(option)
     // {
     //   title: {
     //     text: 'ECharts 入门示例'
@@ -613,6 +642,26 @@ const setEchartsArea1 = () => {
   }
 }
 
+// 获取分析
+const getAnalysis = ()=>{
+  if(!props.showData.workDetail.id){
+    return
+  }
+  const params = {
+    pid:props.showData.workDetail.id,
+  }
+  axios.get('https://pbl.cocorobo.cn/api/pbl/select_pptAnalysisByPid?pid='+params.pid).then(res => {
+    const data = res[0]
+    if(data.length){
+      aiAnalysisData.value = data
+    }else{
+      aiAnalysisData.value = []
+    }
+  }).catch(err => {
+    console.log('get_pptAnalysis_err',err)
+  })
+}
+
 // 监听选择题数据变化
 watch(
   () => props.showData,
@@ -639,6 +688,7 @@ watch(
     else {
       myChart.value = null
     }
+    getAnalysis()
   },
   { immediate: true, deep: true }
 )
@@ -725,6 +775,197 @@ const viewUnsubmittedStudents = () => {
   // }
 }
 
+// ai生成选择题分析 选择题
+const aiAnalysisRefresh45 = ()=>{
+   const _work = props.showData.choiceQuestionListData[props.showData.workIndex]
+  const msg = `# CONTEXT #
+你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
+
+# OBJECTIVE #
+输出当前学生答题的分析报告:整体表现、核心发现(共性问题/误区)、教学优化(改进方向),为教师提供教学策略的建议和支持。
+
+#INPUT#
+课程数据:
+- 课程名称:${props.courseDetail.title}
+- 课程学科:${props.courseDetail.name}
+当前页面答题数据(选择题):【分析重点】
+- 选择题题目:${_work.teststitle}
+- 选项数据:${JSON.stringify(_work.choiceUser)}
+- 题目图片:${_work.timuList.lenght?_work.timuList[0].src:''}
+- 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
+
+# ANALYSIS RULES #
+1. **问题定位**:明确指出哪个知识点/概念/步骤掌握不佳
+2. **建议具体**:给出可执行的教学动作(如“增加XX实例演练”“补充XX对比图”)
+
+# RESPONSE #
+采用段落叙述,数据量化呈现,突出可操作建议,严格控制输出为80词内。采用三段式论述:
+1. 整体表现-1句话
+2. 核心发现-1-2句,指出共性问题+典型案例
+3. 改进建议-1句话,提出具体教学动作
+
+# EXAMPLES #
+样例:
+选择题正确率62%,核心概念“机器学习三步骤”(输入数据→训练模型→预测结果)掌握尚可,但“训练与预测区分”混淆率达38%;建议强化训练vs预测对比教学。`
+
+if(!currentAnalysis.value){
+  aiAnalysisData.value.push({
+    pid:props.showData.workDetail.id,
+    index:props.showData.workIndex,
+    loading:true,
+    json:'',
+    noEnd:true,
+    update_at:'',
+    create_at:"",
+  })
+}else{
+  aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).loading = true
+  aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).json = ""
+}
+
+chat_stream(msg, (event) => {
+  if (event.type === 'message') { 
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).json = event.data
+
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).loading = false
+  }else if (event.type === 'messageEnd') {
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).json = event.data
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).noEnd = false
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
+    saveAnalysis()
+  }
+}).catch(err => {
+  console.log('err',err)
+})
+}
+
+// 问答题
+const aiAnalysisRefresh15 = ()=>{
+  const msg = `# CONTEXT #
+你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
+
+# OBJECTIVE #
+输出当前学生答题的分析报告:整体表现、核心发现(共性问题/误区)、教学优化(改进方向),为教师提供教学策略的建议和支持。
+
+#INPUT#
+课程数据:
+- 课程名称:${props.courseDetail.title}
+- 课程学科:${props.courseDetail.name}
+当前页面答题数据(问答题):【分析重点】
+- 问答题题目:${props.showData.workDetail.json.answerQ}
+- 回答数据:${JSON.stringify(processedWorkArray.value.map((i)=>({user:i.name,answer:i.content.answer})))}
+- 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
+
+# ANALYSIS RULES #
+1. **问题定位**:明确指出哪个知识点/概念/步骤掌握不佳
+2. **建议具体**:给出可执行的教学动作(如“增加XX实例演练”“补充XX对比图”)
+
+# RESPONSE #
+采用段落叙述,数据量化呈现,突出可操作建议,严格控制输出为80词内。采用三段式论述:
+1. 整体表现-1句话
+2. 核心发现-1-2句,指出共性问题+典型案例
+3. 改进建议-1句话,提出具体教学动作
+
+# EXAMPLES #
+样例:
+选择题正确率62%,核心概念“机器学习三步骤”(输入数据→训练模型→预测结果)掌握尚可,但“训练与预测区分”混淆率达38%;建议强化训练vs预测对比教学。`
+if(!currentAnalysis.value){
+  aiAnalysisData.value.push({
+    pid:props.showData.workDetail.id,
+    index:props.showData.workIndex,
+    loading:true,
+    json:'',
+    noEnd:true,
+    update_at:'',
+    create_at:"",
+  })
+}else{
+  aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).loading = true
+  aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).json = ""
+}
+
+chat_stream(msg, (event) => {
+  if (event.type === 'message') { 
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).json = event.data
+
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).loading = false
+  }else if (event.type === 'messageEnd') {
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).json = event.data
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).noEnd = false
+    aiAnalysisData.value.find((item:any)=>{
+      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+    }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
+    saveAnalysis()
+  }
+}).catch(err => {
+  console.log('err',err)
+})
+}
+
+
+// 当前分析
+const currentAnalysis = computed(()=>{
+  return aiAnalysisData.value.find((item:any)=>{
+    return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+  })
+})
+
+// 保存分析
+const saveAnalysis = ()=>{
+  if(!currentAnalysis.value){
+    return
+  }
+  const params = [{
+    pid:props.showData.workDetail.id,
+    idx:props.showData.workIndex,
+    json:currentAnalysis.value.json,
+  }]
+
+  axios.post('https://pbl.cocorobo.cn/api/pbl/insert_pptAnalysis',params).then(res => {
+    if(res==1){
+      console.log("保存成功")
+    }
+  }).catch(err => {
+    console.log('insert_pptAnalysis_err',err)
+  })
+}
+
+
+
+// watch(()=>props.slideIndex,()=>{
+//   getAnalysis()
+// })
+
+// onMounted(()=>{
+//   getAnalysis()
+// })
+
 // 组件卸载时清理ECharts实例
 onUnmounted(() => {
   // 清除定时器
@@ -897,40 +1138,7 @@ onUnmounted(() => {
         }
       }
 
-      .c_t45_aiAnalysis{
-        display: flex;
-        flex-direction: column;
-        padding: 1rem;
-        border: solid 1px #F6C82B;
-        border-left-width: 4px;
-        border-radius: 1rem;
-        &>.c_t45_aa_header{
-          display: flex;
-          align-items: center;
-          justify-content: space-between;
-          gap: 1rem;
-          &>div{
-            display: flex;
-            align-items: center;
-            gap: .5rem;
-            &>svg{
-              width: 1rem;
-              height: 1rem;
-              
-            }
-          }
-          &>.c_t45_aa_h_title{
-            color: #F7CD49;
-            font-weight: 500;
-            &>svg{
-              fill: #F7CD49;
-            }
-          }
-          // &>.c_t45_aa_h_refresh{
-            
-          // }
-        }
-      }
+
     }
 
     .c_t15 {
@@ -1458,4 +1666,71 @@ onUnmounted(() => {
     }
   }
 }
+
+      .aiAnalysis{
+        width: 100%;
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        padding: 1rem;
+        border: solid 1px #F6C82B;
+        border-left-width: 4px;
+        border-radius: 1rem;
+        gap: 1rem;
+        &>.ai_header{
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          gap: 1rem;
+          &>div{
+            display: flex;
+            align-items: center;
+            gap: .5rem;
+            &>svg{
+              width: 1rem;
+              height: 1rem;
+              
+            }
+          }
+          &>.ai_title{
+            color: #F7CD49;
+            font-weight: 500;
+            &>svg{
+              fill: #F7CD49;
+              width: 1.2rem;
+              height: 1.2rem;
+            }
+          }
+          &>.ai_refresh{
+            padding: .5rem 1rem;
+            border-radius: .5rem;
+            background: #F6C82B;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            cursor: pointer;
+            color: #000;
+            font-weight: bold;
+            font-size: .8rem;
+            gap: .5rem;
+            &>svg{
+              fill: #000;
+              width: .8rem;
+              height: .8rem;
+            }
+            &.disabled{
+              cursor: not-allowed !important;
+              background: #FEF8E9 !important;
+            }
+          }
+        }
+        &>.ai_content{
+          font-size: 1rem;
+          font-weight: 500;
+        }
+        &>.ai_updateTime{
+          font-size: .8rem;
+           font-weight: 500;
+        }
+      }
 </style>

+ 1 - 1
src/views/Student/index.vue

@@ -89,7 +89,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)" :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)" :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"/>
 
 
           <div class="slide-bottom" v-if="!isFullscreen">

+ 4 - 1
src/views/lang/cn.json

@@ -723,5 +723,8 @@
   "ssAnswerCount":"回答人数",
   "ssViewUnsubmittedStudents":"点击查看未提交学生",
   "ssSelectUser":"选择{a}的成员",
-  "ssUnsubmittedStudents":"未提交学生"
+  "ssUnsubmittedStudents":"未提交学生",
+  "ssAnalysis":"分析",
+  "ssAIGenerate":"AI生成",
+  "ssUpdateTime":"最后更新"
 }

+ 4 - 1
src/views/lang/en.json

@@ -723,5 +723,8 @@
   "ssAnswerCount":"Answer count",
   "ssViewUnsubmittedStudents":"Click to view unsubmitted students",
   "ssSelectUser":"Select members of {a}",
-  "ssUnsubmittedStudents":"Unsubmitted students"
+  "ssUnsubmittedStudents":"Unsubmitted students",
+  "ssAnalysis":"Analysis",
+  "ssAIGenerate":"AI Generate",
+  "ssUpdateTime":"Last Updated"
 }

+ 4 - 1
src/views/lang/hk.json

@@ -723,5 +723,8 @@
   "ssAnswerCount":"回答人數",
   "ssViewUnsubmittedStudents":"點擊查看未提交學生",
   "ssSelectUser":"選擇{a}的成員",
-  "ssUnsubmittedStudents":"未提交學生"
+  "ssUnsubmittedStudents":"未提交學生",
+  "ssAnalysis":"分析",
+  "ssAIGenerate":"AI生成",
+  "ssUpdateTime":"最後更新"
 }