|
|
@@ -84,7 +84,11 @@
|
|
|
<!-- AI 错误 -->
|
|
|
<div v-if="message.status === 'error'" class="error-card">
|
|
|
<span class="error-text">{{ message.error || '生成失败' }}</span>
|
|
|
- <button class="retry-btn" @click="engine.regenerateAiMessage(message.id)">重新生成</button>
|
|
|
+ <button
|
|
|
+ class="retry-btn"
|
|
|
+ :disabled="engine.greetingInflight.value"
|
|
|
+ @click="retryAiMessage(message)"
|
|
|
+ >{{ message.unrecoverable ? '返回重开' : '重新生成' }}</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -497,6 +501,7 @@ interface Props {
|
|
|
mode?: 'preview' | 'real'
|
|
|
showEnglishText?: boolean
|
|
|
showChineseText?: boolean
|
|
|
+ sessionInfo?: { sessionId: string; expiresAt: string | null } | null
|
|
|
}
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
@@ -508,9 +513,13 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
mode: 'preview',
|
|
|
showEnglishText: true,
|
|
|
showChineseText: false,
|
|
|
+ sessionInfo: null,
|
|
|
})
|
|
|
|
|
|
-const emit = defineEmits<{ complete: [report: DialogueReport | null] }>()
|
|
|
+const emit = defineEmits<{
|
|
|
+ complete: [report: DialogueReport | null]
|
|
|
+ restart: []
|
|
|
+}>()
|
|
|
|
|
|
// ─────────────────────────────────────────────
|
|
|
// Config
|
|
|
@@ -726,6 +735,24 @@ function handleRetry() {
|
|
|
else engine.regenerateAiMessage(last.id)
|
|
|
}
|
|
|
|
|
|
+function retryAiMessage(message: PreviewChatMessage) {
|
|
|
+ const idx = engine.messages.value.indexOf(message)
|
|
|
+ const hasPrevStudent = engine.messages.value.slice(0, idx).some(m => m.role === 'student')
|
|
|
+
|
|
|
+ // 第一条 AI 消息(前面没有学生消息)= greeting
|
|
|
+ if (!hasPrevStudent) {
|
|
|
+ if (message.unrecoverable) {
|
|
|
+ emit('restart')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ engine.retryGreeting()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 非首条:沿用原 regenerate
|
|
|
+ engine.regenerateAiMessage(message.id)
|
|
|
+}
|
|
|
+
|
|
|
function toggleExpand(id: string) {
|
|
|
expandedMessageId.value = expandedMessageId.value === id ? null : id
|
|
|
}
|
|
|
@@ -818,24 +845,7 @@ function handleRestart() {
|
|
|
showExitConfirm.value = false
|
|
|
engine.abort()
|
|
|
engine.cancelTTS()
|
|
|
- engine.messages.value = []
|
|
|
- engine.currentRound.value = 1
|
|
|
- engine.isComplete.value = false
|
|
|
- expandedMessageId.value = null
|
|
|
- playingMessageId.value = null
|
|
|
- phonemeDetail.value = null
|
|
|
- silenceHintText.value = ''
|
|
|
- showBadge.value = null
|
|
|
- consecutiveFluent.value = 0
|
|
|
- consecutiveAccurate.value = 0
|
|
|
- totalSeconds.value = 0
|
|
|
-
|
|
|
- engine.initSession({
|
|
|
- topic: props.topic,
|
|
|
- roleId: 'tom',
|
|
|
- totalRounds: props.totalRounds,
|
|
|
- vocabulary: props.keywords,
|
|
|
- })
|
|
|
+ emit('restart')
|
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────
|
|
|
@@ -948,15 +958,13 @@ watch(
|
|
|
// ─────────────────────────────────────────────
|
|
|
|
|
|
onMounted(() => {
|
|
|
- engine.initSession({
|
|
|
- topic: props.topic,
|
|
|
- roleId: 'tom',
|
|
|
- totalRounds: props.totalRounds,
|
|
|
- vocabulary: props.keywords,
|
|
|
- })
|
|
|
+ if (props.sessionInfo) {
|
|
|
+ engine.attachSession(props.sessionInfo)
|
|
|
+ engine.generateGreeting()
|
|
|
+ }
|
|
|
+ // 无 sessionInfo 时聊天区保持空(父组件应当先创建 session 再挂载本组件)
|
|
|
|
|
|
totalTimer = setInterval(() => {
|
|
|
- // 仅当 engine 未接管倒计时时显示正计时
|
|
|
if (engine.countdownSeconds.value == null) totalSeconds.value += 1
|
|
|
}, 1000)
|
|
|
})
|