|
|
@@ -27,10 +27,17 @@
|
|
|
<div class="ready-body">
|
|
|
<!-- <span class="topic-emoji">🐼</span> -->
|
|
|
<h3 class="topic-name">{{ speakingStore.config.topic || topic }}</h3>
|
|
|
+ <VideoSceneGrid
|
|
|
+ v-if="isVideoSceneMode"
|
|
|
+ :videos="videos"
|
|
|
+ :selected-id="selectedVideo?.id ?? null"
|
|
|
+ @select="selectedVideo = $event"
|
|
|
+ @play="playingVideo = $event"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
<div class="ready-footer">
|
|
|
- <button class="start-btn" :disabled="sessionCreating || topicMissing" @click="startDialogue">
|
|
|
+ <button class="start-btn" :disabled="sessionCreating || startBlocked" @click="startDialogue">
|
|
|
<svg v-if="!sessionCreating" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
|
|
|
@@ -41,7 +48,7 @@
|
|
|
<span v-else class="start-btn-spinner" />
|
|
|
{{ sessionCreating ? '创建中…' : '开始对话' }}
|
|
|
</button>
|
|
|
- <p v-if="topicMissing" class="session-error-text">至少设置topic</p>
|
|
|
+ <p v-if="startBlockReason" class="session-error-text">{{ startBlockReason }}</p>
|
|
|
<p v-else-if="sessionError" class="session-error-text">{{ sessionError }}</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -78,12 +85,18 @@
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
+ <VideoPlayerModal
|
|
|
+ v-if="playingVideo"
|
|
|
+ :title="playingVideo.title"
|
|
|
+ :video-url="playingVideo.videoUrl"
|
|
|
+ @close="playingVideo = null"
|
|
|
+ />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
import { ref, computed, watch, onMounted, onUnmounted, inject } from 'vue'
|
|
|
-import type { DialogueReport, OverallEvaluation, PreviewAIRole, PreviewDialogueState, SessionStartInfo } from '@/types/englishSpeaking'
|
|
|
+import type { DialogueReport, OverallEvaluation, PreviewAIRole, PreviewDialogueState, SessionStartInfo, VideoMeta } from '@/types/englishSpeaking'
|
|
|
import { useSpeakingStore } from '@/store/speaking'
|
|
|
import { getSpeakingConfig } from '@/services/speaking'
|
|
|
import { createDialogueApi, DialogueApiError } from '../services/llmService'
|
|
|
@@ -91,6 +104,9 @@ import { createDialogueApi, DialogueApiError } from '../services/llmService'
|
|
|
import DialogueChatView from './DialogueChatView.vue'
|
|
|
import OverallReport from './OverallReport.vue'
|
|
|
import DetailedReport from './DetailedReport.vue'
|
|
|
+import VideoSceneGrid from './VideoSceneGrid.vue'
|
|
|
+import VideoPlayerModal from './VideoPlayerModal.vue'
|
|
|
+import topicDiscussionVideos from '../data/topicDiscussionVideos.json'
|
|
|
|
|
|
interface Props {
|
|
|
topic?: string
|
|
|
@@ -157,7 +173,18 @@ const preparedSession = ref<SessionStartInfo | null>(null)
|
|
|
const historyChecked = ref(false)
|
|
|
const historyLoadToken = ref(0)
|
|
|
|
|
|
-const topicMissing = computed(() => !(speakingStore.config.topic ?? '').trim())
|
|
|
+const videos = topicDiscussionVideos as VideoMeta[]
|
|
|
+const selectedVideo = ref<VideoMeta | null>(null)
|
|
|
+const playingVideo = ref<VideoMeta | null>(null)
|
|
|
+
|
|
|
+const isVideoSceneMode = computed(() => speakingStore.config.videoScene?.enabled === true)
|
|
|
+
|
|
|
+const startBlockReason = computed<string | null>(() => {
|
|
|
+ if (!(speakingStore.config.topic ?? '').trim()) return '至少设置topic'
|
|
|
+ if (isVideoSceneMode.value && !selectedVideo.value) return '请先选择一个影片'
|
|
|
+ return null
|
|
|
+})
|
|
|
+const startBlocked = computed(() => startBlockReason.value !== null)
|
|
|
|
|
|
const runtimeParams = computed(() => {
|
|
|
const params = new URLSearchParams(window.location.search)
|
|
|
@@ -324,6 +351,8 @@ async function startDialogue() {
|
|
|
sentences: speakingStore.config.learningGoals.sentences,
|
|
|
configId: props.configId || null,
|
|
|
userId: isStudentRuntime.value ? runtimeUserId.value : null,
|
|
|
+ mode: isVideoSceneMode.value ? 'video_scene' : 'normal',
|
|
|
+ selectedVideo: selectedVideo.value ?? undefined,
|
|
|
})
|
|
|
preparedSession.value = {
|
|
|
sessionId: info.sessionId,
|
|
|
@@ -468,6 +497,8 @@ function resetPreview() {
|
|
|
preparedSession.value = null
|
|
|
sessionError.value = null
|
|
|
sessionCreating.value = false
|
|
|
+ selectedVideo.value = null
|
|
|
+ playingVideo.value = null
|
|
|
}
|
|
|
|
|
|
// ── Sync with speakingStore (让 CanvasTool 可以驱动"重置预览") ──
|