|
|
@@ -484,7 +484,7 @@
|
|
|
<script lang="ts" setup>
|
|
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick, h, defineComponent } from 'vue'
|
|
|
import type { PropType } from 'vue'
|
|
|
-import type { PreviewChatMessage, BadgeAchievement, DialogueReport } from '@/types/englishSpeaking'
|
|
|
+import type { PreviewChatMessage, BadgeAchievement, DialogueReport, SessionStartInfo } from '@/types/englishSpeaking'
|
|
|
import { useDialogueEngine } from '../composables/useDialogueEngine'
|
|
|
import { useAudioRecorder } from '../composables/useAudioRecorder'
|
|
|
|
|
|
@@ -501,7 +501,7 @@ interface Props {
|
|
|
mode?: 'preview' | 'real'
|
|
|
showEnglishText?: boolean
|
|
|
showChineseText?: boolean
|
|
|
- sessionInfo?: { sessionId: string; expiresAt: string | null } | null
|
|
|
+ sessionInfo?: SessionStartInfo | null
|
|
|
}
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
@@ -737,6 +737,9 @@ function handleRetry() {
|
|
|
|
|
|
function retryAiMessage(message: PreviewChatMessage) {
|
|
|
const idx = engine.messages.value.indexOf(message)
|
|
|
+ if (idx === -1) return // message already removed (e.g., retryGreeting filtered it out)
|
|
|
+ // Invariant: only generateGreeting pushes an AI message before any student turn,
|
|
|
+ // so the first AI message is always the greeting.
|
|
|
const hasPrevStudent = engine.messages.value.slice(0, idx).some(m => m.role === 'student')
|
|
|
|
|
|
// 第一条 AI 消息(前面没有学生消息)= greeting
|
|
|
@@ -961,6 +964,8 @@ onMounted(() => {
|
|
|
if (props.sessionInfo) {
|
|
|
engine.attachSession(props.sessionInfo)
|
|
|
engine.generateGreeting()
|
|
|
+ } else {
|
|
|
+ console.warn('[DialogueChatView] mounted without sessionInfo; chat is inert. Parent must createSession before mounting.')
|
|
|
}
|
|
|
// 无 sessionInfo 时聊天区保持空(父组件应当先创建 session 再挂载本组件)
|
|
|
|