Add TTS to speak Julia's text chat responses

- Integrate useTextToSpeech hook in chat screen
- Automatically speak assistant responses after API call
- Add volume icon button in header when TTS is active (tap to stop)
- Stop TTS when clearing chat history
- Use Russian language (ru-RU) for TTS output

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sergei 2026-01-27 16:42:50 -08:00
parent 88d4afcdfd
commit 45f2b676e0

View File

@ -25,6 +25,7 @@ import { useRouter, useFocusEffect } from 'expo-router';
import { api } from '@/services/api'; import { api } from '@/services/api';
import { useBeneficiary } from '@/contexts/BeneficiaryContext'; import { useBeneficiary } from '@/contexts/BeneficiaryContext';
import { useVoiceTranscript } from '@/contexts/VoiceTranscriptContext'; import { useVoiceTranscript } from '@/contexts/VoiceTranscriptContext';
import { useTextToSpeech } from '@/hooks/useTextToSpeech';
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
import type { Message, Beneficiary } from '@/types'; import type { Message, Beneficiary } from '@/types';
@ -112,6 +113,12 @@ export default function ChatScreen() {
const { currentBeneficiary, setCurrentBeneficiary } = useBeneficiary(); const { currentBeneficiary, setCurrentBeneficiary } = useBeneficiary();
const { transcript, hasNewTranscript, markTranscriptAsShown, getTranscriptAsMessages } = useVoiceTranscript(); const { transcript, hasNewTranscript, markTranscriptAsShown, getTranscriptAsMessages } = useVoiceTranscript();
// TTS for reading Julia's responses aloud
const { speak, stop: stopTTS, isSpeaking } = useTextToSpeech({
language: 'ru-RU',
rate: 1.0,
});
// Helper to create initial message with beneficiary name // Helper to create initial message with beneficiary name
const createInitialMessage = useCallback((beneficiaryName?: string | null): Message => ({ const createInitialMessage = useCallback((beneficiaryName?: string | null): Message => ({
id: '1', id: '1',
@ -376,13 +383,17 @@ export default function ChatScreen() {
const data = await response.json(); const data = await response.json();
if (data.ok && data.response?.body) { if (data.ok && data.response?.body) {
const responseText = data.response.body;
const assistantMessage: Message = { const assistantMessage: Message = {
id: (Date.now() + 1).toString(), id: (Date.now() + 1).toString(),
role: 'assistant', role: 'assistant',
content: data.response.body, content: responseText,
timestamp: new Date(), timestamp: new Date(),
}; };
setMessages(prev => [...prev, assistantMessage]); setMessages(prev => [...prev, assistantMessage]);
// Speak the response using TTS
speak(responseText);
} else { } else {
// Token might be expired, clear and retry once // Token might be expired, clear and retry once
if (data.status === '401 Unauthorized') { if (data.status === '401 Unauthorized') {
@ -402,7 +413,7 @@ export default function ChatScreen() {
} finally { } finally {
setIsSending(false); setIsSending(false);
} }
}, [isSending, getWellNuoToken, customDeploymentId, currentBeneficiary, beneficiaries]); }, [isSending, getWellNuoToken, customDeploymentId, currentBeneficiary, beneficiaries, speak]);
// Render message bubble // Render message bubble
const renderMessage = ({ item }: { item: Message }) => { const renderMessage = ({ item }: { item: Message }) => {
@ -461,6 +472,15 @@ export default function ChatScreen() {
</Text> </Text>
</View> </View>
</View> </View>
{/* TTS Stop button - only visible when speaking */}
{isSpeaking && (
<TouchableOpacity
style={[styles.headerButton, styles.speakingButton]}
onPress={stopTTS}
>
<Ionicons name="volume-high" size={22} color={AppColors.primary} />
</TouchableOpacity>
)}
<TouchableOpacity <TouchableOpacity
style={styles.headerButton} style={styles.headerButton}
onPress={() => setSortNewestFirst(prev => !prev)} onPress={() => setSortNewestFirst(prev => !prev)}
@ -474,6 +494,7 @@ export default function ChatScreen() {
<TouchableOpacity <TouchableOpacity
style={styles.headerButton} style={styles.headerButton}
onPress={() => { onPress={() => {
stopTTS(); // Stop TTS when clearing chat
Alert.alert( Alert.alert(
'Clear Chat', 'Clear Chat',
'Are you sure you want to clear all messages?', 'Are you sure you want to clear all messages?',
@ -666,6 +687,10 @@ const styles = StyleSheet.create({
padding: Spacing.xs, padding: Spacing.xs,
marginLeft: Spacing.sm, marginLeft: Spacing.sm,
}, },
speakingButton: {
backgroundColor: AppColors.primaryLight || '#E3F2FD',
borderRadius: BorderRadius.full,
},
chatContainer: { chatContainer: {
flex: 1, flex: 1,
}, },