Selaa lähdekoodia

AI生成课程(包括类型、封面、小节配图、配音脚本),

liyanbo 3 kuukautta sitten
vanhempi
sitoutus
7777dad9c1

+ 16 - 0
src/api/questions.js

@@ -99,4 +99,20 @@ export function VisionThink (data){
     method: 'post',
     data
   })
+}
+
+export function textToSpeech (data){
+  return axios({
+    url: "bjdxWeb/ai/textToSpeech",
+    method: 'post',
+    data
+  })
+}
+
+export function aIGenerateCourse (data){
+  return axios({
+    url: "bjdxWeb/course/aIGenerateCourse",
+    method: 'post',
+    data
+  })
 }

+ 684 - 72
src/components/study/SelfDirectedLearning.vue

@@ -2,7 +2,7 @@
 <template>
   <div class="self-directed-learning">
     <div class="title-section">
-      <h3>和AI一起学你想学!</h3>
+      <h3>和AI一起学你想学!</h3>
     </div>
     <div class="input-section">
       <!-- 发送消息输入框 -->
@@ -15,8 +15,8 @@
             @keyup.enter.exact="doSendMessage(prompt.trim())"
           ></textarea>
           <div class="dropdowns-container">
-            <el-dropdown 
-              v-for="dropdown in dropdowns" 
+            <el-dropdown
+              v-for="dropdown in dropdowns"
               :key="dropdown.key"
               v-model="dropdown.value"
               @command="(command) => handleSelect(dropdown.key, command)"
@@ -80,21 +80,687 @@
       </div>
     </div>
   </div>
+
+  <el-divider></el-divider>
 </template>
 
 <script setup>
-import {ref, onMounted, computed} from "vue";
+import {ref, onMounted, computed, reactive, defineEmits} from "vue";
+
+// 定义事件
+const emit = defineEmits(['refreshData']);
 import { Top, Search, User,Memo,ArrowUpBold,ArrowDownBold } from '@element-plus/icons-vue'
+import {
+  aIGenerateCourse,
+  AiImageStatusEnum,
+  CreateDialogue,
+  CreatePainting,
+  PaintingGetMys,
+  sendChatMessageStream, textToSpeech
+} from "@/api/questions.js";
+import {Message} from "@/utils/message/Message.js";
 
 // 发送消息输入框
-const prompt = ref(""); // prompt
+const prompt = ref("给我一首李白的静夜思"); // prompt
+
+// 对话相关
+const activeConversationId = ref(null) // 选中的对话编号
+const conversationInAbortController = ref() // 对话进行中 abort 控制器(控制 stream 对话)
+const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
+const enableContext = ref(true) // 是否开启上下文
+
+// 接收 Stream 消息
+const receiveMessageFullText = ref('')
+const scriptDataTemp = ref(null)
+
+// 生成状态
+const isGenerating = ref(false)// 是否正在生成脚本
+const isGeneratingImages = ref(false)// 是否正在生成图片
+const isGeneratingVoiceovers = ref(false)// 是否正在生成语音
+const generatingImageUrl = ref('')
+
+// 图片生成相关状态
+const inProgressImageMap = ref({}) // 监听的图片映射,key 为 image 编号,value 为 { sectionIndex, type: 'image'|'audio' }
+const inProgressTimer = ref(null) // 生成中的图片定时器,轮询生成进展
+
+// 小节列表
+const lessonList = ref([
+  {id: '1', name: '课程引入'},
+  {id: '2', name: '知识讲解'},
+  {id: '3', name: '课程总结'}
+])
+// 数字人列表
+const digitalHumans = ref([
+  { id: 18, name: '李白', description: '你是诗仙李白' },
+  { id: 10, name: '小智', description: '你是智能机器人小智' }
+])
+//主讲人、助讲人
+const selectedMainTeacher = ref('李白')
+const selectedAssistants = ref(['小智'])
+
+//脚本数据
+const scriptData = reactive(
+    {
+      courseName: '', // 课程名称
+      coverImage: { // 封面图
+        prompt: '',
+        url: '',
+        generating: false
+      },
+      lessons: [ // 课程小节
+        {
+          lessonName: '课程引入', // 小节课程名称
+          sections: [ // 环节
+            {
+              sectionName: '环节一', // 环节名称
+              backgroundImage: {
+                prompt: '',
+                url: '',
+                generating: false
+              },
+              backgroundAudio: {
+                type: '',
+                url: ''
+              },
+              dialogues: [
+                {
+                  type: 'digital',
+                  roleName: '',
+                  content: '',
+                  voiceoverUrl: '',
+                  generatingVoiceover: false
+                },
+                {
+                  type: 'user',
+                  roleName: '',
+                  content: ''
+                }
+              ]
+            }
+          ]
+        },
+        {
+          lessonName: '知识讲解', // 小节课程名称
+          sections: [ // 环节
+            {
+              sectionName: '环节一', // 环节名称
+              backgroundImage: {
+                prompt: '',
+                url: '',
+                generating: false
+              },
+              backgroundAudio: {
+                type: '',
+                url: ''
+              },
+              dialogues: [
+                {
+                  type: 'digital',
+                  roleName: '',
+                  content: '',
+                  voiceoverUrl: '',
+                  generatingVoiceover: false
+                },
+                {
+                  type: 'user',
+                  roleName: '',
+                  content: ''
+                }
+              ]
+            }
+          ]
+        },
+        {
+          lessonName: '课程总结', // 小节课程名称
+          sections: [ // 环节
+            {
+              sectionName: '环节一', // 环节名称
+              backgroundImage: {
+                prompt: '',
+                url: '',
+                generating: false
+              },
+              backgroundAudio: {
+                type: '',
+                url: ''
+              },
+              dialogues: [
+                {
+                  type: 'digital',
+                  roleName: '',
+                  content: '',
+                  voiceoverUrl: '',
+                  generatingVoiceover: false
+                },
+                {
+                  type: 'user',
+                  roleName: '',
+                  content: ''
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+)
+
+// 生成脚本
+const generateScript = async () => {
+  try {
+    isGenerating.value = true
+
+    const role = digitalHumans.value.find((r) => r.name === selectedMainTeacher.value)
+    let content =
+        prompt.value +
+        '(主讲人:' + role.name +
+        ',主讲人角色定位:' + role.description +
+        ';助讲有:'
+    let zhujiang = []
+
+    selectedAssistants.value.forEach((rName) => {
+      const assistantRole = digitalHumans.value.find((r) => r.name === rName)
+      zhujiang.push(assistantRole.name + '[' + assistantRole.description + ']')
+    })
+    content += zhujiang.join(',') + ')'
+
+    // 1. 创建对话
+    await createAiRoleIdConversation(13)
+
+    //2、生成课程信息
+    content += "注意:我需要让你生成"+lessonList.value.length+"节课程,";
+    for (let i = 1; i <= lessonList.value.length; i++) {
+      content+="第"+i+"节课程是"+lessonList.value[i-1].name+";"
+    }
+
+    //3、生成课程小节
+    progressSteps.value[0].visible = true;
+    progressSteps.value[0].active = true;
+    let courseLessons = [];//存储课程小节信息
+    scriptData.lessons = []
+    for (let i = 0; i < lessonList.value.length; i++) {
+      let lesson = lessonList.value[i]
+      progressSteps.value[0].next = "生成课程小节【" + lesson.name + "】..."
+      content += "现在开始生成第"+(i+1)+"节课:" + lesson.name
+      let lessonInfo = {courseName: lesson.name, courseLabel: lesson.id};
+      await doSendMessageStream(activeConversationId.value, content, lessonInfo)
+      console.log("生成课程小节信息:", lessonInfo)
+      courseLessons.push(lessonInfo)
+    }
+    scriptData.lessons = courseLessons;
+    progressSteps.value[0].completed = true;
+    progressSteps.value[0].active = false;
+
+    content = "最后请给我围绕着这几节课的课程封面图描述词(coverImage)和课程名称(courseName)。"
+    progressSteps.value[1].visible = true;
+    progressSteps.value[1].active = true;
+    let courseInfo = {}//存储课程信息
+    await doSendMessageStream(activeConversationId.value, content, courseInfo)
+    console.log("生成课程信息:", courseInfo)
+    scriptData.courseName = courseInfo.courseName
+    scriptData.coverImage.prompt = courseInfo.coverImage
+    progressSteps.value[1].completed = true;
+    progressSteps.value[1].active = false;
+
+    //4、生成全部图片
+    progressSteps.value[2].visible = true;
+    progressSteps.value[2].active = true;
+    //取courseInfo.coverImage、取courseLessons[*].sections[*].backgroundImage课程小节背景图描述词;生成图片
+    await generateAllImages()
+    progressSteps.value[2].completed = true;
+    progressSteps.value[2].active = false;
+
+    //5、生成全部配音
+    progressSteps.value[3].visible = true;
+    progressSteps.value[3].active = true;
+    //取courseLessons[*].sections[*].dialogues[*].voiceoverUrl课程小节课程内对话文本生成配音
+    await generateAllVoiceovers()
+    progressSteps.value[3].completed = true;
+    progressSteps.value[3].active = false;
+
+    localStorage.setItem('scriptData', JSON.stringify(scriptData))
+    console.log("【最终数据:】", scriptData)
+
+    // 调用完成方法,封装数据并存储到后台
+    await completeGenerate()
+
+  } catch (error) {
+    console.error('生成脚本失败:', error)
+  } finally {
+    isGenerating.value = false
+  }
+}
+
+// 完成生成,封装数据并存储到后台
+const completeGenerate = async () => {
+  try {
+    console.log('开始封装数据并存储到后台...')
+
+    const courses = scriptData.lessons.map(lesson => ({
+      courseName: lesson.courseName,
+      courseLabel: lesson.courseLabel,
+      courseContent: JSON.stringify(lesson)
+    }))
+
+    // 封装数据
+    const dataToStore = {
+      grade: localStorage.getItem('selectedGradeId'),
+      courseName: scriptData.courseName,
+      coverImage: scriptData.coverImage.url,
+      courseList: courses
+    }
+
+    console.log('封装的数据:', dataToStore)
+
+    // 调用后端API将文本转成语音
+    const res = await aIGenerateCourse(dataToStore)
+    if (res.code !== 0) {
+      console.error('生成课程失败:', res.msg)
+      Message().error('生成课程失败:' + res.msg)
+      return
+    }
+    // 所有步骤完成后,3秒后隐藏容器
+    setTimeout(() => {
+      showNewContainer.value = false;
+      // 通知父组件刷新数据
+      emit('refreshData');
+    }, 2000);
+  } catch (error) {
+    console.error('存储数据失败:', error)
+  }
+}
+
+/** 新建聊天对话 */
+const createAiRoleIdConversation = async (roleId) => {
+  // 智能问答
+  await CreateDialogue({ roleId: roleId })
+      .then((res) => {
+        console.log("创建会话:", res.data);
+        activeConversationId.value = res.data;
+      })
+      .catch((error) => {
+        console.error("请求出错:", error);
+      });
+}
+
+/** 调取数字人生成脚本 */
+const doSendMessageStream = async (conversationId, content, outputContent) => {
+  conversationInAbortController.value = new AbortController()
+  conversationInProgress.value = true
+  receiveMessageFullText.value = '' // 清空之前的文本
+
+  try {
+    let isFirstChunk = true
+    await sendChatMessageStream(
+        conversationId,
+        content,
+        undefined, // contentAnswer 参数
+        conversationInAbortController.value,
+        enableContext.value,
+        async (res) => {
+          const { code, data, msg } = JSON.parse(res.data)
+          if (code !== 0) {
+            console.error(`对话异常! ${msg}`)
+            return
+          }
+
+          if (data.eventType === 'TEXT') {
+            if (data.receive?.content === '') {
+              return
+            }
+
+            if (isFirstChunk) {
+              isFirstChunk = false
+            }
+
+            receiveMessageFullText.value += data.receive.content
+            console.log('数据加载中..')
+            try {
+              const parsedData = JSON.parse(receiveMessageFullText.value)
+              // 将解析后的数据填充到outputContent对象中
+              console.log('zuizhon..', parsedData)
+              Object.assign(outputContent, parsedData)
+              // updateScriptData(parsedData)
+            } catch (e) {
+              // 解析失败,说明数据还不完整,继续等待
+            }
+          }
+        },
+        (error) => {
+          console.error(`对话异常! ${error}`)
+          throw error
+        },
+        () => {
+          try {
+            if (receiveMessageFullText.value) {
+              console.log('最终数据:', receiveMessageFullText.value)
+              const parsedData = JSON.parse(receiveMessageFullText.value)
+              console.log('最终数据json:', parsedData)
+              // 将解析后的数据填充到outputContent对象中
+              // updateScriptData(parsedData)
+              console.log('zuizhon..', parsedData)
+              Object.assign(outputContent, parsedData)
+            }
+          } catch (e) {
+            console.error('最终数据解析失败:', e)
+
+            // 清洗规则:移除```json、```、** 等非JSON标记,只保留中间的JSON内容
+            try {
+              let cleanJsonStr = receiveMessageFullText.value
+              const parsedData = JSON.parse(
+                  cleanJsonStr
+                      .replace(/^```json\s*/, '')
+                      .replace(/\s*```$/, '')
+                      .replace(/\*\*/g, '')
+                      .trim()
+              )
+              console.log('最终清洗后数据json:', parsedData)
+              // 将解析后的数据填充到outputContent对象中
+              console.log('zuizhon..', parsedData)
+              Object.assign(outputContent, parsedData)
+              // updateScriptData(parsedData)
+            } catch (cleanError) {
+              console.error('清洗后数据解析失败:', cleanError)
+            }
+          }
+        }
+    )
+  } catch (error) {
+    console.error('发送消息流失败:', error)
+  } finally {
+    conversationInProgress.value = false
+  }
+}
+
+// 生成所有背景图
+const generateAllImages = async () => {
+  return new Promise(async (resolve, reject) => {
+    isGeneratingImages.value = true
+    try {
+      // 确保scriptData和coverImage存在
+      if (!scriptData.coverImage) {
+        scriptData.coverImage = { prompt: '', url: '', generating: false }
+      }
+
+      // 生成封面图
+      if (scriptData.coverImage.prompt && !scriptData.coverImage.url) {
+        await generateCoverImage()
+      }
+
+      // 生成所有环节的背景图
+      if (scriptData.lessons && scriptData.lessons.length > 0) {
+        for (let lessonIndex = 0; lessonIndex < scriptData.lessons.length; lessonIndex++) {
+          const lesson = scriptData.lessons[lessonIndex]
+          if (lesson && lesson.sections) {
+            for (let sectionIndex = 0; sectionIndex < lesson.sections.length; sectionIndex++) {
+              const section = lesson.sections[sectionIndex]
+              // 确保section和backgroundImage存在
+              if (section) {
+                if (!section.backgroundImage) {
+                  section.backgroundImage = { prompt: '', url: '', generating: false }
+                }
+                // 只处理没有背景图URL的环节
+                if (section.backgroundImage.prompt && !section.backgroundImage.url) {
+                  await generateImage(lessonIndex, sectionIndex)
+                }
+              }
+            }
+          }
+        }
+      }
+
+      // 检查是否有图片正在生成
+      if (Object.keys(inProgressImageMap.value).length === 0) {
+        // 没有图片需要生成,直接resolve
+        isGeneratingImages.value = false
+        resolve()
+      } else {
+        // 启动图片轮询
+        startImagePolling()
+
+        // 检查是否有图片正在生成
+        const checkImagesComplete = setInterval(() => {
+          if (Object.keys(inProgressImageMap.value).length === 0) {
+            isGeneratingImages.value = false
+            clearInterval(checkImagesComplete)
+            stopImagePolling()
+            resolve()
+          }
+        }, 1000)
+      }
+    } catch (error) {
+      console.error('生成所有背景图失败:', error)
+      isGeneratingImages.value = false
+      reject(error)
+    }
+  })
+}
+
+// 生成封面图
+const generateCoverImage = async () => {
+  scriptData.coverImage.generating = true
+  try {
+    const response = await CreatePainting({
+      "modelId": 56,
+      "prompt": scriptData.coverImage.prompt,
+      "width": 1024,
+      "height": 1024
+    })
+    console.log("生成封面图", response)
+    inProgressImageMap.value[response.data] = {
+      type: 'cover'
+    }
+  } catch (error) {
+    console.error('生成封面图失败:', error)
+    scriptData.coverImage.generating = false
+  }
+}
+
+// 生成图片
+const generateImage = async (lessonIndex, sectionIndex) => {
+  const lesson = scriptData.lessons[lessonIndex]
+  if (!lesson) return
+
+  const section = lesson.sections[sectionIndex]
+  if (!section) return
+
+  const media = section.backgroundImage
+
+  media.generating = true
+
+  try {
+    const res = await CreatePainting({
+      "modelId": 56,
+      "prompt": media.prompt,
+      "width": 1024,
+      "height": 1024
+    })
+    console.log("生成图片", res)
+    inProgressImageMap.value[res.data] = {
+      lessonIndex: lessonIndex,
+      sectionIndex: sectionIndex,
+      type: 'image'
+    }
+  } catch (error) {
+    console.error('生成图片失败:', error);
+    media.generating = false
+  }
+}
+
+// 启动图片轮询
+const startImagePolling = () => {
+  if (inProgressTimer.value) {
+    clearInterval(inProgressTimer.value)
+  }
+  inProgressTimer.value = setInterval(refreshWatchImages, 3000)
+}
+
+// 停止图片轮询
+const stopImagePolling = () => {
+  if (inProgressTimer.value) {
+    clearInterval(inProgressTimer.value)
+    inProgressTimer.value = null
+  }
+}
+
+// 轮询生成中的图片列表
+const refreshWatchImages = async () => {
+  const imageIds = Object.keys(inProgressImageMap.value).map(Number)
+  if (imageIds.length === 0) {
+    return
+  }
+
+  try {
+    const list = await PaintingGetMys(imageIds)
+    const newWatchImages = {}
+    // 遍历所有正在生成的图片
+    Object.keys(inProgressImageMap.value).forEach((key) => {
+      const imageId = Number(key)
+      const info = inProgressImageMap.value[key]
+      // 查找对应的图片状态
+      const image = list.data.find(item => item.id === imageId)
+      if (image) {
+        if (image.status === AiImageStatusEnum.IN_PROGRESS) {
+          // 图片正在生成中,保留在map中
+          newWatchImages[key] = info
+        } else if (image.status === AiImageStatusEnum.SUCCESS && image.picUrl) {
+          // 图片生成成功
+          if (info.type === 'cover') {
+            // 处理封面图
+            scriptData.coverImage.url = image.picUrl
+            scriptData.coverImage.generating = false
+          } else {
+            // 处理课程小节背景图
+            const lesson = scriptData.lessons[info.lessonIndex]
+            if (lesson) {
+              const section = lesson.sections[info.sectionIndex]
+              if (section) {
+                section.backgroundImage.url = image.picUrl
+                section.backgroundImage.generating = false
+              }
+            }
+          }
+          // 更新生成中的图片预览
+          generatingImageUrl.value = image.picUrl
+
+          localStorage.setItem('scriptData', JSON.stringify(scriptData))
+          console.log("【最终数据,生成图片成功】", scriptData)
+        } else if (image.status === AiImageStatusEnum.FAIL) {
+          // 图片生成失败
+          if (info.type === 'cover') {
+            // 处理封面图失败
+            scriptData.coverImage.generating = false
+          } else {
+            // 处理课程小节背景图失败
+            const lesson = scriptData.lessons[info.lessonIndex]
+            if (lesson) {
+              const section = lesson.sections[info.sectionIndex]
+              if (section) {
+                section.backgroundImage.generating = false
+              }
+            }
+          }
+        }
+      } else {
+        // 图片不存在于返回列表中,可能已经处理完成或超时,从map中移除
+        if (info.type === 'cover') {
+          scriptData.coverImage.generating = false
+        } else {
+          const lesson = scriptData.lessons[info.lessonIndex]
+          if (lesson) {
+            const section = lesson.sections[info.sectionIndex]
+            if (section) {
+              section.backgroundImage.generating = false
+            }
+          }
+        }
+      }
+    })
+    inProgressImageMap.value = newWatchImages
+  } catch (error) {
+    console.error('轮询图片状态失败:', error)
+  }
+}
+
+// 生成所有语音
+const generateAllVoiceovers = async () => {
+  isGeneratingVoiceovers.value = true
+  try {
+    // 确保scriptData和lessons存在
+    if (scriptData.lessons && scriptData.lessons.length > 0) {
+      for (let lessonIndex = 0; lessonIndex < scriptData.lessons.length; lessonIndex++) {
+        const lesson = scriptData.lessons[lessonIndex]
+        if (lesson && lesson.sections) {
+          for (let sectionIndex = 0; sectionIndex < lesson.sections.length; sectionIndex++) {
+            const section = lesson.sections[sectionIndex]
+            if (section && section.dialogues) {
+              for (let dialogueIndex = 0; dialogueIndex < section.dialogues.length; dialogueIndex++) {
+                const dialogue = section.dialogues[dialogueIndex]
+                // 只处理没有语音URL且有内容的对话
+                if (dialogue && !dialogue.url && dialogue.content) {
+                  await generateVoiceover(lessonIndex, sectionIndex, dialogueIndex)
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  } catch (error) {
+    console.error('生成所有语音失败:', error)
+  } finally {
+    isGeneratingVoiceovers.value = false
+  }
+}
+
+// 文本转语音API调用
+// 生成单个语音
+const generateVoiceover = async (lessonIndex, sectionIndex, dialogueIndex) => {
+  const lesson = scriptData.lessons[lessonIndex]
+  if (!lesson) return
+
+  const section = lesson.sections[sectionIndex]
+  if (!section) return
+
+  const dialogue = section.dialogues[dialogueIndex]
+
+  // 只处理数字人对话和提问
+  if (dialogue.type === 'user') {
+    return
+  }
+
+  dialogue.generatingVoiceover = true
+
+  let role = digitalHumans.value.find((r) => r.name === dialogue.roleName)
+  if (!role) {
+    console.error('未找到角色:', dialogue.roleName)
+    dialogue.generatingVoiceover = false
+    return
+  }
+  try {
+    // 调用后端API将文本转成语音
+    const response = await textToSpeech({
+      roleId: role.id,
+      content: dialogue.content
+    })
+
+    // 将返回的URL赋值给对话
+    dialogue.voiceoverUrl = response.data
+    localStorage.setItem('scriptData', JSON.stringify(scriptData))
+    localStorage.setItem('【最终数据,生成语音成功】', scriptData)
+  } catch (error) {
+    console.error('生成语音失败:', error)
+  } finally {
+    dialogue.generatingVoiceover = false
+  }
+}
 
 // 控制生成进度显示状态
 const showNewContainer = ref(false);
 
 // 生成进度步骤
 const progressSteps = ref([
-  { text: '生成课程信息...', completed: false, active: false, visible: false },
+  { text: '生成课程脚本...', completed: false, active: false, visible: false },
   { text: '生成课程小节...', completed: false, active: false, visible: false },
   { text: '生成背景图...', completed: false, active: false, visible: false },
   { text: '生成配音...', completed: false, active: false, visible: false }
@@ -104,12 +770,11 @@ const progressSteps = ref([
 const dropdowns = ref([
   {
     key: 'course',
-    value: '诗词',
+    value: '生成主题',
     visible: false,
     icon: Search,
     options: [
-      { label: '数学课', value: '数学课' },
-      { label: '英语课', value: '英语课' }
+      { label: '诗词课', value: '诗词课' }
     ]
   },
   {
@@ -118,7 +783,7 @@ const dropdowns = ref([
     visible: false,
     icon: User,
     options: [
-      { label: '张老师', value: '张老师' },
+      { label: '李白', value: '李白' },
       { label: '李老师', value: '李老师' }
     ]
   },
@@ -128,16 +793,7 @@ const dropdowns = ref([
     visible: false,
     icon: Memo,
     options: [
-      { label: '小王', value: '小王' },
-      { label: '小李', value: '小李' },
-      { label: '小王', value: '小王' },
-      { label: '小李', value: '小李' },
-      { label: '小王', value: '小王' },
-      { label: '小李', value: '小李' },
-      { label: '小王', value: '小王' },
-      { label: '小李', value: '小李' },
-      { label: '小王', value: '小王' },
-      { label: '小李', value: '小李' }
+      { label: '小智', value: '小智' },
     ]
   }
 ]);
@@ -166,12 +822,12 @@ const handleSelect = (key, command) => {
   const dropdown = dropdowns.value.find(item => item.key === key);
   if (dropdown && dropdownConfig[key]) {
     const config = dropdownConfig[key];
-    
+
     // 生成新内容,仅当选择的值与默认值不同时添加
-    const newContent = command === config.default 
-      ? config.prefix 
+    const newContent = command === config.default
+      ? config.prefix
       : `${config.prefix}${command}`;
-    
+
     // 检查输入框中是否已存在该类型的内容
     if (config.regex.test(prompt.value)) {
       // 替换已存在的内容
@@ -211,7 +867,7 @@ const handleSendByKeydown = async (event) => {
   }
 };
 
-/** 真正执行【发送】消息操作 */
+/** 生成操作 */
 const doSendMessage = async (content) => {
   // 校验
   if (content.length < 1) {
@@ -219,55 +875,11 @@ const doSendMessage = async (content) => {
     return;
   }
 
-  // 清空输入框
-  prompt.value = "";
-
-  // 显示new-container
   showNewContainer.value = true;
 
-  // 开始模拟生成进度
-  simulateProgress();
-};
+  // 调用生成脚本方法
+  await generateScript();
 
-// 模拟生成进度
-const simulateProgress = () => {
-  // 重置所有步骤
-  progressSteps.value.forEach(step => {
-    step.completed = false;
-    step.active = false;
-    step.visible = false;
-  });
-
-  // 逐个处理步骤
-  let currentStep = 0;
-  
-  const processStep = () => {
-    if (currentStep < progressSteps.value.length) {
-      // 显示当前步骤
-      progressSteps.value[currentStep].visible = true;
-      // 激活当前步骤
-      progressSteps.value[currentStep].active = true;
-      
-      // 模拟步骤完成
-      setTimeout(() => {
-        // 标记当前步骤为完成
-        progressSteps.value[currentStep].completed = true;
-        progressSteps.value[currentStep].active = false;
-        
-        // 处理下一步
-        currentStep++;
-        processStep();
-      }, 1500); // 每个步骤持续1.5秒
-    } else {
-      // 所有步骤完成后,3秒后隐藏容器
-      setTimeout(() => {
-        showNewContainer.value = false;
-      }, 2000);
-    }
-  };
-  
-  // 开始处理第一个步骤
-  processStep();
 };
 
 // 计算属性:返回可见的进度步骤
@@ -393,7 +1005,7 @@ onMounted(() => {
 }
 
 .dropdowns-container .el-button {
-  width: rpx(50);
+  width: rpx(60);
   height: rpx(18);
   background-color: #f1f0ff;
   border: rpx(1) solid #a7a4ed;

+ 11 - 2
src/views/AIPage/AIGeneralCourse.vue

@@ -119,7 +119,7 @@
 
       <div class="box-2">
         <!-- 自主学习组件,只在AI自主学习时显示 -->
-        <SelfDirectedLearning v-if="currentOpenedMenu === 'selfstudy'" />
+        <SelfDirectedLearning v-if="currentOpenedMenu === 'selfstudy'" @refreshData="refreshData" />
         
         <div
           class="small-box"
@@ -189,7 +189,7 @@ const menuConfig = [
   { id: 'general', title: 'AI通识课', type: '1', isPractical: false, isAICourse: false },
   { id: 'practical', title: 'AI实操课', type: '2', isPractical: true, isAICourse: false },
   { id: 'ai', title: 'AI场景教学', type: '3', isPractical: false, isAICourse: true },
-  { id: 'selfstudy', title: 'AI自主学习', type: '1', isPractical: false, isAICourse: true }
+  { id: 'selfstudy', title: 'AI自主学习', type: '4', isPractical: false, isAICourse: true }
 ]
 // 当前选中的菜单索引
 const currentActiveIndex = ref('general-1')
@@ -222,6 +222,15 @@ const saveActiveState = (menuId, index) => {
   localStorage.setItem(activeIndexKey, index)
 }
 
+//刷新AI生成课数据
+const refreshData = async () => {
+
+  const res = await ClassOutline(localStorage.getItem('selectedGradeId'), menuConfig[3].type)
+  if (res.code === 0) {
+    courseData.value[menuConfig[3].id] = processCourseData(res.data)
+  }
+}
+
 // 统一函数来获取课程大纲数据 通识课/实操课/AI生成课
 const fetchClassOutline = async (classId) => {
   try {