Эх сурвалжийг харах

1、课程小节 扩展:添加了 description 和 requirements 字段
2、生成逻辑优化:修改了提示词构建逻辑,每个小节使用独立的提示词
3、图片进度条修复:修复了进度条重置问题,使用累加计算
4、定时器清理:添加了 checkImagesCompleteTimer 变量,并在 stopImagePolling 中清理
5、错误处理增强:在 doSendMessageStream 中添加了解析失败时的异常抛出
6、错误消息统一:所有错误消息添加了"请稍后再试!"

liyanbo 1 сар өмнө
parent
commit
b84363beac

+ 79 - 30
src/components/study/SelfDirectedLearning.vue

@@ -30,7 +30,7 @@
               </template>
               <el-option
                   v-for="item in courseDropdown.options"
-                  v-if="item.show"
+                  v-show="item.show"
                   :key="item.value"
                   :label="item.label"
                   :value="item.value"
@@ -167,7 +167,7 @@ const prompt = ref(""); // prompt
 const activeConversationId = ref(null) // 选中的对话编号
 const conversationInAbortController = ref() // 对话进行中 abort 控制器(控制 stream 对话)
 const conversationInProgress = ref(false) // 对话是否正在进行中
-const enableContext = ref(true) // 是否开启上下文
+const enableContext = ref(false) // 是否开启上下文
 
 // 接收 Stream 消息
 const receiveMessageFullText = ref('')
@@ -183,6 +183,7 @@ const isCancelled = ref(false)// 是否取消生成
 // 图片生成相关状态
 const inProgressImageMap = ref({}) // 监听的图片映射
 const inProgressTimer = ref(null) // 生成中的图片定时器
+const checkImagesCompleteTimer = ref(null) // 图片完成检查定时器
 
 // 任务恢复相关 - 使用统一的缓存键并绑定用户ID
 const getTaskStateKey = () => {
@@ -193,11 +194,47 @@ const getTaskStateKey = () => {
 // 单个脚本数据的缓存键(保持原有逻辑,用于单独存储脚本数据)
 const SCRIPT_DATA_KEY = 'scriptData'
 
-// 小节列表
+// 小节列表(包含详细讲解要求,适用于所有课程类型)
 const lessonList = ref([
-  {id: '1', name: '课程引入'},
-  {id: '2', name: '知识讲解'},
-  {id: '3', name: '课程总结'}
+  {
+    id: '1', 
+    name: '课程引入',
+    description: '作为课程的开篇,建立学习背景,引发学生兴趣',
+    requirements: `
+      1. 介绍课程主题的背景知识(如相关历史、文化背景或基本概念)
+      2. 用生动有趣的方式引发学生兴趣(可以讲一个小故事或提一个有趣的问题)
+      3. 明确本节课程的学习目标和预期收获
+      4. 简单介绍课程内容的整体框架和重点
+      5. 结尾用自然的过渡语引导到正式讲解
+      要求:语言亲切自然,符合学生认知水平,篇幅适中,结构为:问候→背景介绍→学习目标→过渡语
+    `.trim()
+  },
+  {
+    id: '2', 
+    name: '知识讲解',
+    description: '深入解析课程内容,详细讲解知识点',
+    requirements: `
+      1. 详细讲解课程核心内容(如知识点、概念、原理等,确保覆盖所有关键内容)
+      2. 分析内容中的重点和难点,结合实例进行说明
+      3. 解读内容所表达的思想、情感或核心价值
+      4. 介绍关键术语的含义、用法和相关知识
+      5. 设计1-2个互动问题引导学生思考和应用知识点
+      要求:讲解详细透彻,逻辑清晰,确保学生理解每一个知识点,结构为:内容解析→重点分析→实例说明→互动提问
+    `.trim()
+  },
+  {
+    id: '3', 
+    name: '课程总结',
+    description: '回顾课程内容,总结要点,拓展延伸',
+    requirements: `
+      1. 回顾本节课的核心知识点(重点内容复述)
+      2. 概括课程的主旨思想和核心要点
+      3. 总结学习方法和要点(帮助记忆)
+      4. 联系生活实际,进行拓展延伸
+      5. 布置简单的课后思考或练习建议
+      要求:条理清晰,重点突出,语言简洁,易于记忆,篇幅适中,结构为:回顾要点→总结方法→拓展延伸→课后建议
+    `.trim()
+  }
 ])
 // 主讲人、助讲人
 const selectedMainTeacher = ref('')
@@ -583,9 +620,8 @@ const generateScript = async (isResume = false) => {
           return assistantRole ? `${assistantRole.name}[${assistantRole.description}]` : ''
         }).filter(Boolean).join(',')
 
-        // 构建生成脚本的提示词内容
-        let content = `${prompt.value}(主讲人:${role.name},主讲人角色定位:${role.description};助讲有:${assistants});`
-        content += `我需要让你生成${lessonList.value.length}节课程;${lessonList.value.map((lesson, index) => `第${index+1}节课程是${lesson.name};`).join('')}`
+        // 构建基础提示词(不含课程列表)
+        const baseContent = `${prompt.value}(主讲人:${role.name},主讲人角色定位:${role.description};助讲有:${assistants})`
 
         let courseLessons = [];
         scriptData.lessons = []
@@ -597,24 +633,20 @@ const generateScript = async (isResume = false) => {
 
           let lesson = lessonList.value[i]
           progressSteps.value[1].text = "生成课程小节【" + lesson.name + "】..."
-          let nameTemp = `现在开始生成第${i+1}节课:${lesson.name}`
-
-          // 第一节课追加内容,后续课程重新设置内容
-          if(i === 0){
-            content += nameTemp
-          }else{
-            content = nameTemp
-          }
+          
+          // 构建当前小节的专属提示词(只针对当前小节)
+          let content = `${baseContent}。这是系列课程的第${i+1}节,本节主题:【${lesson.name}】。${lesson.requirements}`
 
+          // 创建独立的输出对象,避免数据污染
           let lessonInfo = {courseName: lesson.name, courseLabel: lesson.id};
           await doSendMessageStream(activeConversationId.value, content, lessonInfo)
 
           // 检查生成结果是否有效
           if (!lessonInfo || !lessonInfo.courseName) {
-            throw new Error(`生成第${i+1}节课程失败`)
+            throw new Error(`生成第${i+1}节课程【${lesson.name}】失败`)
           }
 
-          console.log("生成课程小节信息:", lessonInfo)
+          console.log(`生成第${i+1}节课程【${lesson.name}】成功:`, lessonInfo)
           courseLessons.push(lessonInfo)
 
           // 保存当前进度
@@ -693,7 +725,7 @@ const generateScript = async (isResume = false) => {
     cancelGenerate(false)
 
     // 显示错误消息
-    Message().error('生成脚本失败:' + errorMessage)
+    Message().error('生成脚本失败:' + errorMessage + ',请稍后再试!')
   } finally {
     // 确保生成状态被重置
     isGenerating.value = false
@@ -733,7 +765,7 @@ const completeGenerate = async () => {
     const res = await aIGenerateCourse(dataToStore)
     if (res.code !== 0) {
       console.error('生成课程失败:', res.msg)
-      Message().error('生成课程失败:' + res.msg)
+      Message().error('生成课程失败:' + res.msg + ',请稍后再试!')
       return
     }
 
@@ -744,7 +776,7 @@ const completeGenerate = async () => {
     }, 2000);
   } catch (error) {
     console.error('存储数据失败:', error)
-    Message().error('存储数据失败:' + (error.message || error))
+    Message().error('存储数据失败:' + (error.message || error) + ',请稍后再试!')
   }
 }
 
@@ -813,7 +845,8 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
             const { code, data, msg } = JSON.parse(res.data)
             if (code !== 0) {
               console.error(`对话异常! ${msg}`)
-              return
+              // API返回错误码,抛出异常终止生成
+              throw new Error(`对话异常:${msg}`)
             }
 
             if (data.eventType === 'TEXT') {
@@ -831,10 +864,14 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
                 const parsedData = JSON.parse(receiveMessageFullText.value)
                 Object.assign(outputContent, parsedData)
               } catch (e) {
+                // 流式接收时解析失败可以忽略(可能数据不完整),但记录日志
+                // console.log('流式数据暂不完整,继续接收...')
               }
             }
           } catch (e) {
             console.error('消息处理异常:', e)
+            // 消息处理失败,抛出异常终止生成
+            throw new Error('消息处理失败:' + e.message)
           }
         },
         (error) => {
@@ -862,6 +899,8 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
               Object.assign(outputContent, parsedData)
             } catch (cleanError) {
               console.error('清洗后数据解析失败:', cleanError)
+              // 解析失败,抛出异常终止生成
+              throw new Error('脚本数据解析失败:无法解析服务器返回的内容')
             }
           }
         }
@@ -957,11 +996,12 @@ const generateAllImages = async () => {
         startImagePolling()
         console.log('启动图片完成检查定时器')
 
-        const checkImagesComplete = setInterval(() => {
+        checkImagesCompleteTimer.value = setInterval(() => {
           console.log('检查图片完成 - 轮询列表:', Object.keys(inProgressImageMap.value))
 
           if (isCancelled.value) {
-            clearInterval(checkImagesComplete)
+            clearInterval(checkImagesCompleteTimer.value)
+            checkImagesCompleteTimer.value = null
             stopImagePolling()
             reject(new Error('已取消'))
             return
@@ -972,7 +1012,8 @@ const generateAllImages = async () => {
             console.log('所有图片生成完成')
             progressSteps.value[2].progress = 100
             isGeneratingImages.value = false
-            clearInterval(checkImagesComplete)
+            clearInterval(checkImagesCompleteTimer.value)
+            checkImagesCompleteTimer.value = null
             stopImagePolling()
             saveTaskState(2)
             resolve()
@@ -1096,6 +1137,11 @@ const stopImagePolling = () => {
     clearInterval(inProgressTimer.value)
     inProgressTimer.value = null
   }
+  // 同时清除图片完成检查定时器
+  if (checkImagesCompleteTimer.value) {
+    clearInterval(checkImagesCompleteTimer.value)
+    checkImagesCompleteTimer.value = null
+  }
 }
 
 /**
@@ -1103,7 +1149,6 @@ const stopImagePolling = () => {
  * 查询图片状态并更新脚本数据
  */
 const refreshWatchImages = async () => {
-  console.log('refreshWatchImages 被调用')
 
   // 获取所有正在生成的图片ID
   const imageIds = Object.keys(inProgressImageMap.value)
@@ -1177,9 +1222,13 @@ const refreshWatchImages = async () => {
     })
     inProgressImageMap.value = newWatchImages
 
-    progressCounters.value.images.current = completedCount
+    // 累计完成数 = 之前完成的 + 本次新完成的
+    progressCounters.value.images.current += completedCount
+    
+    // 确保进度百分比只增不减,避免进度条重置
     if (progressCounters.value.images.total > 0) {
-      progressSteps.value[2].progress = Math.round((completedCount / progressCounters.value.images.total) * 100)
+      const newProgress = Math.round((progressCounters.value.images.current / progressCounters.value.images.total) * 100)
+      progressSteps.value[2].progress = Math.max(progressSteps.value[2].progress, newProgress)
     }
 
     saveTaskState(2)
@@ -1405,7 +1454,7 @@ const getRoleList = async () => {
     localScriptRoles.value = listAi
   } catch (error) {
     console.error('获取角色数据失败:', error)
-    Message().error('获取角色数据失败:' + (error.message || error))
+    Message().error('获取角色数据失败:' + (error.message || error) + ',请稍后再试!')
   }
 }