Quellcode durchsuchen

1、修复重新加载后的小节内容减少资源浪费
2、修复重新加载后的图像进度条异常

liyanbo vor 1 Monat
Ursprung
Commit
e7ddf97acd
1 geänderte Dateien mit 130 neuen und 35 gelöschten Zeilen
  1. 130 35
      src/components/study/SelfDirectedLearning.vue

+ 130 - 35
src/components/study/SelfDirectedLearning.vue

@@ -378,11 +378,15 @@ const hasPendingTask = computed(() => {
   }
 })
 
+// 当前正在生成的小节索引(用于恢复时从正确位置继续)
+const currentLessonIndex = ref(0)
+
 // 保存任务状态到缓存(使用统一的键并绑定用户ID)
 const saveTaskState = (currentStep) => {
   const taskState = {
     isRestoring: true,
     currentStep: currentStep,
+    currentLessonIndex: currentLessonIndex.value,
     progressSteps: progressSteps.value,
     progressCounters: progressCounters.value,
     prompt: prompt.value,
@@ -448,6 +452,11 @@ const restoreTaskState = () => {
       Object.assign(scriptData, parsedData)
     }
 
+    // 恢复当前小节索引
+    if (parsedState.currentLessonIndex !== undefined) {
+      currentLessonIndex.value = parsedState.currentLessonIndex
+    }
+
     // 恢复用户输入
     if (parsedState.prompt) {
       prompt.value = parsedState.prompt
@@ -466,9 +475,35 @@ const restoreTaskState = () => {
       selectedAssistants.value = [assistantDropdown.value.value]
     }
 
-    // 恢复图片映射
+    // 恢复图片映射(只保留还未完成的图片)
     if (parsedState.inProgressImageMap) {
-      inProgressImageMap.value = parsedState.inProgressImageMap
+      const savedImageMap = parsedState.inProgressImageMap
+      const validImageMap = {}
+      
+      // 过滤掉已经完成的图片(有url的图片不需要再轮询)
+      Object.keys(savedImageMap).forEach(key => {
+        const info = savedImageMap[key]
+        if (info.type === 'cover') {
+          if (!scriptData.coverImage || !scriptData.coverImage.url) {
+            validImageMap[key] = info
+          }
+        } else {
+          const lesson = scriptData.lessons[info.lessonIndex]
+          if (lesson && lesson.sections && lesson.sections[info.sectionIndex]) {
+            const section = lesson.sections[info.sectionIndex]
+            if (!section.backgroundImage || !section.backgroundImage.url) {
+              validImageMap[key] = info
+            }
+          }
+        }
+      })
+      
+      inProgressImageMap.value = validImageMap
+      
+      // 恢复后立即检查一次图片状态,确保进度准确
+      if (Object.keys(inProgressImageMap.value).length > 0) {
+        refreshWatchImages()
+      }
     }
 
     showNewContainer.value = true
@@ -524,6 +559,12 @@ const cancelGenerate = (showMessage = true) => {
   }, 500)
 }
 
+// 当前步骤(响应式,用于保存和恢复)
+const currentStep = ref(0)
+
+// 组件是否已卸载(用于防止流式回调在组件卸载后继续执行)
+const isUnmounted = ref(false)
+
 /**
  * 生成脚本主函数
  * @param {boolean} isResume - 是否为恢复任务(从缓存中恢复)
@@ -534,7 +575,7 @@ const generateScript = async (isResume = false) => {
     isGenerating.value = true
     isCancelled.value = false
 
-    let currentStep = 0
+    currentStep.value = 0
 
     // 如果是恢复任务,从缓存中获取当前步骤
     if (isResume) {
@@ -542,7 +583,7 @@ const generateScript = async (isResume = false) => {
       if (taskState) {
         try {
           const parsedState = JSON.parse(taskState)
-          currentStep = parsedState.currentStep || 0
+          currentStep.value = parsedState.currentStep || 0
         } catch (error) {
           console.error('获取当前步骤失败:', error)
         }
@@ -554,8 +595,8 @@ const generateScript = async (isResume = false) => {
       // 检查是否已取消
       if (isCancelled.value) return
 
-      currentStep = 0
-      saveTaskState(currentStep)
+      currentStep.value = 0
+      saveTaskState(currentStep.value)
 
       try {
         // 创建会话
@@ -592,8 +633,8 @@ const generateScript = async (isResume = false) => {
       // 检查是否已取消
       if (isCancelled.value) return
 
-      currentStep = 1
-      saveTaskState(currentStep)
+      currentStep.value = 1
+      saveTaskState(currentStep.value)
 
       try {
         // 设置进度状态
@@ -623,11 +664,15 @@ const generateScript = async (isResume = false) => {
         // 构建基础提示词(不含课程列表)
         const baseContent = `${prompt.value}(主讲人:${role.name},主讲人角色定位:${role.description};助讲有:${assistants})`
 
-        let courseLessons = [];
+        // 如果是恢复任务,从已保存的脚本数据中获取已完成的课程
+        let courseLessons = isResume && scriptData.lessons.length > 0 ? [...scriptData.lessons] : [];
         scriptData.lessons = []
 
-        // 循环生成每节课程内容
-        for (let i = 0; i < lessonList.value.length; i++) {
+        // 循环生成每节课程内容(从保存的索引位置开始)
+        for (let i = currentLessonIndex.value; i < lessonList.value.length; i++) {
+          // 更新当前小节索引
+          currentLessonIndex.value = i
+
           // 检查是否已取消
           if (isCancelled.value) return
 
@@ -649,10 +694,12 @@ const generateScript = async (isResume = false) => {
           console.log(`生成第${i+1}节课程【${lesson.name}】成功:`, lessonInfo)
           courseLessons.push(lessonInfo)
 
-          // 保存当前进度
-          saveTaskState(currentStep)
+          // 保存当前进度(包含小节索引)
+          saveTaskState(currentStep.value)
         }
 
+        // 重置小节索引为0,准备下一次生成
+        currentLessonIndex.value = 0
         scriptData.lessons = courseLessons;
         progressSteps.value[1].completed = true;
         progressSteps.value[1].active = false;
@@ -667,13 +714,18 @@ const generateScript = async (isResume = false) => {
       // 检查是否已取消
       if (isCancelled.value) return
 
-      currentStep = 2
-      saveTaskState(currentStep)
+      currentStep.value = 2
+      
+      // 重置图片进度为0%,避免恢复后显示旧进度
+      progressSteps.value[2].progress = 0
+      progressCounters.value.images.current = 0
+      
+      saveTaskState(currentStep.value)
 
       try {
         progressSteps.value[2].visible = true;
         progressSteps.value[2].active = true;
-        await generateAllImages()
+        await generateAllImages(isResume)
 
         // 验证图片生成结果
         if (scriptData.coverImage && scriptData.coverImage.prompt && !scriptData.coverImage.url) {
@@ -693,8 +745,8 @@ const generateScript = async (isResume = false) => {
       // 检查是否已取消
       if (isCancelled.value) return
 
-      currentStep = 3
-      saveTaskState(currentStep)
+      currentStep.value = 3
+      saveTaskState(currentStep.value)
 
       try {
         progressSteps.value[3].visible = true;
@@ -839,13 +891,13 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
         conversationInAbortController.value,
         enableContext.value,
         async (res) => {
-          if (isCancelled.value) return
+          // 检查组件是否已卸载或已取消
+          if (isUnmounted.value || isCancelled.value) return
 
           try {
             const { code, data, msg } = JSON.parse(res.data)
             if (code !== 0) {
               console.error(`对话异常! ${msg}`)
-              // API返回错误码,抛出异常终止生成
               throw new Error(`对话异常:${msg}`)
             }
 
@@ -864,21 +916,23 @@ 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) => {
-          console.error(`对话异常! ${error}`)
-          throw error
+          if (!isUnmounted.value) {
+            console.error(`对话异常! ${error}`)
+            throw error
+          }
         },
         () => {
+          // 检查组件是否已卸载
+          if (isUnmounted.value) return
+
           try {
             if (receiveMessageFullText.value) {
               console.log('最终数据:', receiveMessageFullText.value)
@@ -916,18 +970,27 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
 /**
  * 生成所有背景图
  * 包括封面图和所有环节的背景图
+ * @param {boolean} isResume - 是否为恢复任务
  * @returns {Promise}
  */
-const generateAllImages = async () => {
+const generateAllImages = async (isResume = false) => {
   return new Promise(async (resolve, reject) => {
     isGeneratingImages.value = true
     try {
       // ========== 计算总图片数 ==========
+      // 如果是恢复任务,保留已有的轮询列表;否则清空重新开始
+      if (!isResume) {
+        inProgressImageMap.value = {}
+      }
+      
+      // 统计需要生成的图片总数(有prompt且无url的图片)
       progressCounters.value.images.total = 0
+      
       // 统计封面图
       if (scriptData.coverImage && scriptData.coverImage.prompt && !scriptData.coverImage.url) {
         progressCounters.value.images.total++
       }
+      
       // 统计所有环节的背景图
       if (scriptData.lessons && scriptData.lessons.length > 0) {
         for (let lessonIndex = 0; lessonIndex < scriptData.lessons.length; lessonIndex++) {
@@ -942,7 +1005,17 @@ const generateAllImages = async () => {
           }
         }
       }
-      progressCounters.value.images.current = 0
+      
+      // 计算已完成的图片数(总数 - 正在生成的数量)
+      progressCounters.value.images.current = progressCounters.value.images.total - Object.keys(inProgressImageMap.value).length
+      
+      // 更新进度显示
+      if (progressCounters.value.images.total > 0) {
+        const progress = Math.round((progressCounters.value.images.current / progressCounters.value.images.total) * 100)
+        progressSteps.value[2].progress = Math.min(progress, 100)
+      } else {
+        progressSteps.value[2].progress = 100
+      }
 
       // 初始化封面图对象
       if (!scriptData.coverImage) {
@@ -951,7 +1024,15 @@ const generateAllImages = async () => {
 
       // ========== 生成封面图(如果尚未生成) ==========
       if (scriptData.coverImage.prompt && !scriptData.coverImage.url) {
-        await generateCoverImage()
+        // 在恢复模式下,检查封面图是否已经在生成中
+        const coverAlreadyGenerating = Object.keys(inProgressImageMap.value).some(
+          key => inProgressImageMap.value[key].type === 'cover'
+        )
+        if (!isResume || !coverAlreadyGenerating) {
+          await generateCoverImage()
+        } else {
+          console.log('封面图已在生成中,跳过重复生成')
+        }
       }
 
       // ========== 生成所有环节的背景图 ==========
@@ -974,7 +1055,17 @@ const generateAllImages = async () => {
                 }
                 // 如果有prompt但没有url,则生成图片
                 if (section.backgroundImage.prompt && !section.backgroundImage.url) {
-                  await generateImage(lessonIndex, sectionIndex)
+                  // 在恢复模式下,检查该图片是否已经在生成中
+                  const imageAlreadyGenerating = Object.keys(inProgressImageMap.value).some(
+                    key => inProgressImageMap.value[key].type === 'image' &&
+                           inProgressImageMap.value[key].lessonIndex === lessonIndex &&
+                           inProgressImageMap.value[key].sectionIndex === sectionIndex
+                  )
+                  if (!isResume || !imageAlreadyGenerating) {
+                    await generateImage(lessonIndex, sectionIndex)
+                  } else {
+                    console.log(`背景图[${lessonIndex}-${sectionIndex}]已在生成中,跳过重复生成`)
+                  }
                 }
               }
             }
@@ -1222,13 +1313,14 @@ const refreshWatchImages = async () => {
     })
     inProgressImageMap.value = newWatchImages
 
-    // 累计完成数 = 之前完成的 + 本次新完成的
-    progressCounters.value.images.current += completedCount
+    // 计算当前已完成的图片数量(重新统计,避免刷新后累加导致超过100%)
+    const completedImages = progressCounters.value.images.total - Object.keys(inProgressImageMap.value).length
+    progressCounters.value.images.current = completedImages
     
-    // 确保进度百分比只增不减,避免进度条重置
+    // 更新进度百分比
     if (progressCounters.value.images.total > 0) {
-      const newProgress = Math.round((progressCounters.value.images.current / progressCounters.value.images.total) * 100)
-      progressSteps.value[2].progress = Math.max(progressSteps.value[2].progress, newProgress)
+      const newProgress = Math.round((completedImages / progressCounters.value.images.total) * 100)
+      progressSteps.value[2].progress = Math.min(newProgress, 100)
     }
 
     saveTaskState(2)
@@ -1647,6 +1739,9 @@ onMounted(async () => {
  * 清理所有资源,避免内存泄漏
  */
 onUnmounted(() => {
+  // 标记组件已卸载
+  isUnmounted.value = true
+
   // 停止图片轮询定时器
   stopImagePolling()
 
@@ -1657,7 +1752,7 @@ onUnmounted(() => {
 
   // 如果正在生成中,保存当前状态以便下次恢复
   if (isGenerating.value) {
-    saveTaskState(0)
+    saveTaskState(currentStep.value)
   }
 });
 </script>