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:
parent
88d4afcdfd
commit
45f2b676e0
@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user