feat(chat): add voice playback with auto-play and visual effects (#396)
## Features ### Core Functionality - **Web Speech API Integration**: Add TTS (text-to-speech) playback for assistant messages - **Manual Playback**: Click-to-play button next to each assistant message (🔊 icon) - **Auto-play Mode**: Toggle switch in input bar to auto-play responses - **Playback Controls**: Play/pause/stop with visual feedback ### User Interface - **Playback Button**: Hover-activated button in message meta area (next to copy button) - **Auto-play Switch**: Voice icon toggle in input top bar with state persistence - **Mobile Optimization**: Buttons always visible on mobile (≤768px width) - **Visual Feedback**: - Rainbow glowing border during playback (2px border, 10px/20px glow) - 4-second animation cycle through 6 colors - Play/pause icon toggle ### Voice Customization - **Pitch/Rate Control**: Low-pitched (0.5) fast-speaking (1.2) "male voice" - **Auto Voice Selection**: Attempts to select male voices across platforms (macOS: Yaoyao, Windows: David/Daniel) - **Platform Compatibility**: Works with system-provided voices on macOS, iOS, Android, Windows ### Content Filtering - Smart text extraction: filters code blocks, `<thinking>` tags, HTML - Only assistant messages are eligible for playback - Tool and system messages are excluded ### Internationalization - Added 8 language translations (en, zh, de, es, fr, ja, ko, pt) - New keys: `playSpeech`, `pauseSpeech`, `resumeSpeech`, `stopSpeech`, `autoPlaySpeech`, `speechNotSupported` ## Technical Details ### New Files - `packages/client/src/composables/useSpeech.ts`: Core speech synthesis composable - Voice loading and selection logic - Single-instance global speech manager - Event handling (onstart, onend, onerror, onboundary) - State management (isPlaying, isPaused, currentMessageId) ### Modified Components - **ChatInput.vue**: Auto-play switch with localStorage persistence - **MessageItem.vue**: - Playback button integration - Event listener for auto-play triggers - Mobile-first button visibility - Rainbow border animation during playback ### Store Changes - `chat.ts`: - Added `autoPlaySpeechEnabled` state - `setAutoPlaySpeech()` method - `playMessageSpeech()` method for event-based playback - Auto-play trigger on `run.completed` event ## Browser Support - Requires Web Speech API support (all modern browsers) - Graceful degradation: button hidden if API not supported - Voice availability varies by platform and OS Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,13 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const streamStates = ref<Map<string, { abort: () => void }>>(new Map())
|
||||
/** sessionId → server-reported isWorking status */
|
||||
const serverWorking = ref<Set<string>>(new Set())
|
||||
|
||||
// 自动播放语音开关
|
||||
const autoPlaySpeechEnabled = ref(false)
|
||||
|
||||
function setAutoPlaySpeech(enabled: boolean) {
|
||||
autoPlaySpeechEnabled.value = enabled
|
||||
}
|
||||
const isStreaming = computed(() => {
|
||||
const sid = activeSessionId.value
|
||||
if (sid == null) return false
|
||||
@@ -871,6 +878,19 @@ export const useChatStore = defineStore('chat', () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 自动播放语音
|
||||
console.log('[run.completed] autoPlaySpeechEnabled:', autoPlaySpeechEnabled.value)
|
||||
if (autoPlaySpeechEnabled.value) {
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const lastAssistant = [...msgs].reverse().find(m => m.role === 'assistant')
|
||||
if (lastAssistant?.content) {
|
||||
// 延迟一小会儿再播放,确保 UI 更新完成
|
||||
setTimeout(() => {
|
||||
playMessageSpeech(lastAssistant.id, lastAssistant.content)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup()
|
||||
updateSessionTitle(sid)
|
||||
// the in-flight marker. If the browser is reloading right now
|
||||
@@ -1342,6 +1362,15 @@ export const useChatStore = defineStore('chat', () => {
|
||||
thinkingObservation.clear()
|
||||
}
|
||||
|
||||
// 播放消息语音
|
||||
function playMessageSpeech(messageId: string, content: string) {
|
||||
// 触发自定义事件,让 MessageItem 组件处理播放
|
||||
const event = new CustomEvent('auto-play-speech', {
|
||||
detail: { messageId, content }
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
return {
|
||||
sessions,
|
||||
activeSessionId,
|
||||
@@ -1371,5 +1400,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
noteReasoningStart,
|
||||
noteReasoningEnd,
|
||||
clearThinkingObservationFor,
|
||||
setAutoPlaySpeech,
|
||||
playMessageSpeech,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user