diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 0bc7746..ee866c7 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' import { useUserStore } from '../stores/user' import { api } from '../api/client' -import type { VoiceSessionSummary } from '../types/voiceSession' +import type { VoiceSessionAnalytics, VoiceSessionSummary } from '../types/voiceSession' import BaseButton from '../components/ui/BaseButton.vue' import LoginDialog from '../components/ui/LoginDialog.vue' import { @@ -28,6 +28,7 @@ function switchLocale(lang: 'en' | 'zh') { // ========== 登录对话框状态 ========== const showLoginDialog = ref(false) const activeVoiceSession = ref(null) +const voiceAnalytics = ref(null) // ========== 创作入口 ========== // 旧的创作变量已移除,现在只负责跳转 @@ -68,6 +69,18 @@ async function loadActiveVoiceSession() { } } +async function loadVoiceAnalytics() { + if (!userStore.user) { + voiceAnalytics.value = null + return + } + try { + voiceAnalytics.value = await api.get('/api/voice-sessions/analytics?days=30') + } catch { + voiceAnalytics.value = null + } +} + function scrollToFeatures() { document.getElementById('features')?.scrollIntoView({ behavior: 'smooth' }) } @@ -82,12 +95,14 @@ onMounted(async () => { await userStore.fetchSession() } await loadActiveVoiceSession() + await loadVoiceAnalytics() }) watch( () => userStore.user?.id, () => { void loadActiveVoiceSession() + void loadVoiceAnalytics() }, ) @@ -202,6 +217,55 @@ watch( 了解更多功能 + +
+
+
+

+ 最近的语音共创会话 + + {{ activeVoiceSession.working_title || '未命名会话' }} + + 仍可继续,当前已完成 {{ activeVoiceSession.total_turns }} 轮。 +

+

+ 上一轮仍在等待家长确认,回到工作台后可以选择继续、重说或切到文本输入。 +

+

+ 最近一轮触发了儿童内容安全兜底,工作台里可以查看完整记录。 +

+

+ 最近 30 天语音共创 {{ voiceAnalytics.total_sessions }} 个会话, + turn 成功率 {{ Math.round(voiceAnalytics.turn_success_rate * 100) }}%, + finalize 转化率 {{ Math.round(voiceAnalytics.finalize_conversion_rate * 100) }}%。 +

+
+ + + 回到语音共创 + +
+
diff --git a/frontend/src/views/MyStories.vue b/frontend/src/views/MyStories.vue index 9582500..58ac96f 100644 --- a/frontend/src/views/MyStories.vue +++ b/frontend/src/views/MyStories.vue @@ -8,7 +8,7 @@ import BaseCard from '../components/ui/BaseCard.vue' import EmptyState from '../components/ui/EmptyState.vue' import LoadingSpinner from '../components/ui/LoadingSpinner.vue' import type { GenerationOpsSummary, GenerationProviderAnalytics } from '../types/generation' -import type { VoiceSessionSummary } from '../types/voiceSession' +import type { VoiceSessionAnalytics, VoiceSessionSummary } from '../types/voiceSession' import { getAssetStatusMeta, getGenerationStatusMeta, @@ -42,6 +42,7 @@ const stories = ref([]) const providerAnalytics = ref(null) const opsSummary = ref(null) const activeVoiceSession = ref(null) +const voiceAnalytics = ref(null) const loading = ref(true) const error = ref('') const showCreateModal = ref(false) @@ -63,6 +64,14 @@ const providerSuccessRate = computed(() => { }) const topProvider = computed(() => providerAnalytics.value?.by_provider[0] ?? null) const topFailureReason = computed(() => providerAnalytics.value?.failure_reasons[0] ?? null) +const voiceTurnSuccessRate = computed(() => { + if (!voiceAnalytics.value) return null + return Math.round(voiceAnalytics.value.turn_success_rate * 100) +}) +const voiceFinalizeRate = computed(() => { + if (!voiceAnalytics.value) return null + return Math.round(voiceAnalytics.value.finalize_conversion_rate * 100) +}) function buildProviderAnalyticsPath() { const params = new URLSearchParams() @@ -78,16 +87,18 @@ function buildProviderAnalyticsPath() { async function fetchStories() { try { - const [storyList, analytics, ops, activeSession] = await Promise.all([ + const [storyList, analytics, ops, activeSession, voiceOverview] = await Promise.all([ api.get('/api/stories'), api.get(buildProviderAnalyticsPath()), api.get('/api/generations/ops-summary'), api.get('/api/voice-sessions/active').catch(() => null), + api.get('/api/voice-sessions/analytics?days=30').catch(() => null), ]) stories.value = storyList providerAnalytics.value = analytics opsSummary.value = ops activeVoiceSession.value = activeSession + voiceAnalytics.value = voiceOverview } catch (e) { error.value = e instanceof Error ? e.message : '加载失败' } finally { @@ -221,6 +232,18 @@ watch([selectedWindow, selectedCapability], () => { {{ activeVoiceSession.working_title || '未命名语音会话' }}, 当前状态 {{ activeVoiceSession.status }},已完成 {{ activeVoiceSession.total_turns }} 轮。

+

+ 上一轮仍在等待家长确认,建议优先回到语音共创工作台处理。 +

+

+ 最近一轮触发了儿童内容安全兜底,建议回到工作台查看详细记录。 +

@@ -229,6 +252,55 @@ watch([selectedWindow, selectedCapability], () => { + +
+
+

语音共创运营摘要

+

+ 最近 {{ voiceAnalytics.window_days ?? 30 }} 天,你的语音共创已经累计 + {{ voiceAnalytics.total_sessions }} 个会话、{{ voiceAnalytics.total_turns }} 个 turn。 +

+

+ 低置信度确认 {{ voiceAnalytics.low_confidence_turns }} 次, + 安全介入 {{ voiceAnalytics.safety_interventions }} 次。 +

+
+
+
+
Turn 成功率
+
+ {{ voiceTurnSuccessRate }}% +
+
+
+
Finalize 转化率
+
+ {{ voiceFinalizeRate }}% +
+
+
+
ASR / TTS 失败
+
+ {{ voiceAnalytics.asr_failures }} / {{ voiceAnalytics.tts_failures }} +
+
+
+
已完成会话
+
+ {{ voiceAnalytics.finalized_sessions }} +
+
+
+
+
+