|
|
@@ -1,4 +1,4 @@
|
|
|
-<template>
|
|
|
+<template>
|
|
|
<div class="dialog-content-wrapper">
|
|
|
<!-- 遮罩层 -->
|
|
|
<div v-if="showMask" class="mask-layer" ref="maskLayer">
|
|
|
@@ -149,9 +149,16 @@ const props = defineProps({
|
|
|
backText: {
|
|
|
type: String,
|
|
|
default: '返回课程'
|
|
|
+ },
|
|
|
+ // 是否是最后一节课
|
|
|
+ isLastCourse: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+const emit = defineEmits(['dialogueEnded'])
|
|
|
+
|
|
|
// 对话相关状态
|
|
|
// 当前章节索引
|
|
|
const currentSectionIndex = ref(0)
|
|
|
@@ -261,15 +268,8 @@ const parseMarkdown = (content) => {
|
|
|
// 格式化诗词内容,在逗号和句号后添加换行
|
|
|
const formatPoemContent = (content) => {
|
|
|
if (!content) return ''
|
|
|
- // 移除markdown格式符号,保留文字
|
|
|
- let plainText = content.replace(/[\*#`\[\]\(\)]/g, '')
|
|
|
- // 在逗号和句号后添加换行符(保留标点符号)
|
|
|
- let formatted = plainText.replace(/([。!?;、])/g, '$1<br/>')
|
|
|
-
|
|
|
- // 写死的额外数据
|
|
|
- // const extraData = '<p>' + formatted + '</p>'
|
|
|
-
|
|
|
- return formatted
|
|
|
+ const plainText = content.replace(/[\*#`\[\]\(\)]/g, '')
|
|
|
+ return plainText.replace(/([。!?;、])/g, '$1<br/>')
|
|
|
}
|
|
|
|
|
|
const getCharacterSide = () => {
|
|
|
@@ -320,19 +320,33 @@ const playDialogueAudio = (isAutoPlay = false) => {
|
|
|
dialogueAudio.value.pause()
|
|
|
dialogueAudio.value.currentTime = 0
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果是诗词类型,不播放音频
|
|
|
if (currentDialogue.value?.type === 'poem') {
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 播放当前对话的语音
|
|
|
if (currentDialogue.value?.voiceoverUrl) {
|
|
|
const audio = new Audio(currentDialogue.value.voiceoverUrl)
|
|
|
dialogueAudio.value = audio
|
|
|
-
|
|
|
+
|
|
|
// 音频结束事件
|
|
|
audio.onended = () => {
|
|
|
+ // 检查是否是最后一个对话
|
|
|
+ if (isAtLastDialogue()) {
|
|
|
+ // 如果是用户输入类型,不立即触发dialogueEnded,等待AI回答完成
|
|
|
+ if (currentDialogue.value?.type === 'user') {
|
|
|
+ console.log('用户输入类型,等待AI回答完成后再提示');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 语音播报完成且是最后一个对话,触发事件通知父组件
|
|
|
+ console.log('普通对话语音播放完成,已到达最后一个对话,触发 dialogueEnded 事件');
|
|
|
+ emit('dialogueEnded', props.isLastCourse);
|
|
|
+ isPlaying.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// 如果是自动播放状态,继续播放下一条
|
|
|
if (isAutoPlay && isPlaying.value) {
|
|
|
setTimeout(() => {
|
|
|
@@ -344,10 +358,21 @@ const playDialogueAudio = (isAutoPlay = false) => {
|
|
|
}, 100)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 播放音频
|
|
|
audio.play().catch(e => {
|
|
|
console.error('对话语音播放失败:', e)
|
|
|
+ // 播放失败时,检查是否是最后一个对话
|
|
|
+ if (isAtLastDialogue()) {
|
|
|
+ // 如果是用户输入类型,不立即触发dialogueEnded,等待AI回答完成
|
|
|
+ if (currentDialogue.value?.type === 'user') {
|
|
|
+ console.log('用户输入类型,等待AI回答完成后再提示');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ emit('dialogueEnded', props.isLastCourse);
|
|
|
+ isPlaying.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
// 播放失败时,2秒后跳转
|
|
|
if (isAutoPlay && isPlaying.value) {
|
|
|
setTimeout(() => {
|
|
|
@@ -360,6 +385,17 @@ const playDialogueAudio = (isAutoPlay = false) => {
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
+ // 检查是否是最后一个对话
|
|
|
+ if (isAtLastDialogue()) {
|
|
|
+ // 如果是用户输入类型,不立即触发dialogueEnded,等待AI回答完成
|
|
|
+ if (currentDialogue.value?.type === 'user') {
|
|
|
+ console.log('用户输入类型,等待AI回答完成后再提示');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ emit('dialogueEnded', props.isLastCourse);
|
|
|
+ isPlaying.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
// 如果没有语音文件,2秒后跳转
|
|
|
if (isAutoPlay && isPlaying.value && currentDialogue.value?.type !== 'user') {
|
|
|
setTimeout(() => {
|
|
|
@@ -403,7 +439,11 @@ const playSequence = () => {
|
|
|
// 自动切换到下一条对话
|
|
|
setTimeout(() => {
|
|
|
if (!playNext(true)) {
|
|
|
- // 播放完毕
|
|
|
+ // 播放完毕,检查是否是最后一个对话
|
|
|
+ if (isAtLastDialogue()) {
|
|
|
+ console.log('诗词序列:已到达最后一个对话,触发 dialogueEnded 事件');
|
|
|
+ emit('dialogueEnded', props.isLastCourse);
|
|
|
+ }
|
|
|
isPlaying.value = false
|
|
|
stopAllAudio()
|
|
|
}
|
|
|
@@ -785,6 +825,12 @@ const doSendMessageStream = async userMessage => {
|
|
|
() => {
|
|
|
console.log(`结束对话! `)
|
|
|
stopStream()
|
|
|
+ // AI回答完成,检查是否是最后一个对话且是用户输入类型
|
|
|
+ if (isAtLastDialogue() && currentDialogue.value?.type === 'user') {
|
|
|
+ console.log('AI回答完成,触发 dialogueEnded 事件');
|
|
|
+ emit('dialogueEnded', props.isLastCourse);
|
|
|
+ isPlaying.value = false;
|
|
|
+ }
|
|
|
}
|
|
|
)
|
|
|
} catch (error) {
|
|
|
@@ -817,6 +863,16 @@ const handleAudioPlaybackComplete = () => {
|
|
|
setOnPlaybackComplete(null);
|
|
|
|
|
|
stopAllAudio();
|
|
|
+
|
|
|
+ // 检查是否是最后一个对话
|
|
|
+ if (isAtLastDialogue()) {
|
|
|
+ // 语音播报完成且是最后一个对话,触发事件通知父组件
|
|
|
+ console.log('已到达最后一个对话,触发 dialogueEnded 事件');
|
|
|
+ emit('dialogueEnded', props.isLastCourse);
|
|
|
+ isPlaying.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// 如果处于自动播放状态,继续播放下一条对话
|
|
|
if (isPlaying.value) {
|
|
|
// 恢复回调,供下一条 AI 对话使用
|
|
|
@@ -831,6 +887,22 @@ const handleAudioPlaybackComplete = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 判断是否是最后一个对话
|
|
|
+const isAtLastDialogue = () => {
|
|
|
+ if (!props.scriptData.sections || props.scriptData.sections.length === 0) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ const lastSectionIndex = props.scriptData.sections.length - 1
|
|
|
+ const lastSection = props.scriptData.sections[lastSectionIndex]
|
|
|
+ if (!lastSection.dialogues || lastSection.dialogues.length === 0) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ const lastDialogueIndex = lastSection.dialogues.length - 1
|
|
|
+
|
|
|
+ return currentSectionIndex.value === lastSectionIndex &&
|
|
|
+ currentDialogueIndex.value === lastDialogueIndex
|
|
|
+}
|
|
|
+
|
|
|
// 组件挂载时添加键盘事件监听
|
|
|
onMounted(() => {
|
|
|
window.addEventListener('keydown', handleKeydown)
|