|
|
@@ -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) + ',请稍后再试!')
|
|
|
}
|
|
|
}
|
|
|
|