/** * Voice Debug Screen * Shows transcript logs from voice calls for debugging * Allows easy copying of logs */ import React, { useCallback } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Alert, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons, Feather } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import * as Clipboard from 'expo-clipboard'; import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; import { useVoiceTranscript } from '@/contexts/VoiceTranscriptContext'; export default function VoiceDebugScreen() { const router = useRouter(); const { transcript, clearTranscript, hasNewTranscript, markTranscriptAsShown, addTranscriptEntry } = useVoiceTranscript(); // Mark as shown when viewed React.useEffect(() => { if (hasNewTranscript) { markTranscriptAsShown(); } }, [hasNewTranscript, markTranscriptAsShown]); // Copy all logs to clipboard const copyAllLogs = useCallback(async () => { if (transcript.length === 0) { Alert.alert('No logs', 'There are no voice call logs to copy.'); return; } const logsText = transcript .map((entry) => { const time = entry.timestamp.toLocaleTimeString(); const speaker = entry.role === 'user' ? 'USER' : 'JULIA'; return `[${time}] ${speaker}: ${entry.text}`; }) .join('\n\n'); const header = `=== Voice Call Transcript ===\n${new Date().toLocaleString()}\nTotal entries: ${transcript.length}\n\n`; await Clipboard.setStringAsync(header + logsText); Alert.alert('Copied!', 'Voice call logs copied to clipboard.'); }, [transcript]); // Copy single entry const copySingleEntry = useCallback(async (text: string) => { await Clipboard.setStringAsync(text); Alert.alert('Copied!', 'Message copied to clipboard.'); }, []); // Clear all logs const handleClearLogs = useCallback(() => { Alert.alert( 'Clear Logs', 'Are you sure you want to clear all voice call logs?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Clear', style: 'destructive', onPress: clearTranscript, }, ] ); }, [clearTranscript]); // Start a new voice call const startVoiceCall = useCallback(() => { router.push('/voice-call'); }, [router]); // Add mock data for testing (simulator has no microphone) const addMockData = useCallback(() => { const mockConversation = [ { role: 'assistant' as const, text: "Hi! I have some concerns about Ferdinand today - there was an incident this morning. Want me to tell you more?" }, { role: 'user' as const, text: "Yes, what happened?" }, { role: 'assistant' as const, text: "Ferdinand had a fall at 6:32 AM in the bathroom. He was able to get up on his own, but I recommend checking in with him. His sleep was also shorter than usual - only 5 hours last night." }, { role: 'user' as const, text: "Did he take his medications?" }, { role: 'assistant' as const, text: "Yes, he took his morning medications at 8:15 AM. All on schedule. Would you like me to show you the dashboard with more details?" }, { role: 'user' as const, text: "Show me the dashboard" }, { role: 'assistant' as const, text: "Navigating to Dashboard now. You can see the 7-day overview there." }, ]; mockConversation.forEach((entry, index) => { setTimeout(() => { addTranscriptEntry(entry.role, entry.text); }, index * 100); }); Alert.alert('Mock Data Added', 'Sample voice conversation added for testing.'); }, [addTranscriptEntry]); return ( {/* Header */} Voice Debug {transcript.length > 0 && ( <> )} {/* Start Call Button */} Start Voice Call {/* Mock Data Button for simulator testing */} Add Mock Data {/* Logs Section */} Call Transcript {transcript.length} {transcript.length === 1 ? 'entry' : 'entries'} {/* Transcript List */} {transcript.length === 0 ? ( No voice logs yet Start a voice call with Julia AI to see the transcript here. ) : ( transcript.map((entry) => ( copySingleEntry(entry.text)} activeOpacity={0.7} > {entry.role === 'user' ? 'You' : 'Julia'} {entry.timestamp.toLocaleTimeString()} {entry.text} Long press to copy )) )} {/* Footer hint */} {transcript.length > 0 && ( Tap the copy icon to copy all logs )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: AppColors.background, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, headerLeft: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, }, headerTitle: { fontSize: FontSizes.xl, fontWeight: '700', color: AppColors.textPrimary, }, headerButtons: { flexDirection: 'row', gap: Spacing.sm, }, headerButton: { padding: Spacing.xs, borderRadius: BorderRadius.md, backgroundColor: AppColors.surface, }, callButtonContainer: { paddingHorizontal: Spacing.md, paddingVertical: Spacing.md, }, callButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: Spacing.sm, backgroundColor: AppColors.success, paddingVertical: Spacing.md, borderRadius: BorderRadius.lg, shadowColor: AppColors.success, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 4, }, callButtonText: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.white, }, mockDataButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: Spacing.xs, marginTop: Spacing.sm, paddingVertical: Spacing.sm, borderRadius: BorderRadius.md, borderWidth: 1, borderColor: AppColors.primary, backgroundColor: 'transparent', }, mockDataButtonText: { fontSize: FontSizes.sm, fontWeight: '500', color: AppColors.primary, }, logsHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, logsTitle: { fontSize: FontSizes.base, fontWeight: '600', color: AppColors.textPrimary, }, logsCount: { fontSize: FontSizes.sm, color: AppColors.textMuted, }, logsList: { flex: 1, }, logsContent: { padding: Spacing.md, gap: Spacing.sm, }, emptyState: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: Spacing.xxl * 2, }, emptyTitle: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textPrimary, marginTop: Spacing.md, }, emptySubtitle: { fontSize: FontSizes.sm, color: AppColors.textMuted, textAlign: 'center', marginTop: Spacing.xs, paddingHorizontal: Spacing.xl, }, logEntry: { padding: Spacing.md, borderRadius: BorderRadius.lg, marginBottom: Spacing.sm, }, logEntryUser: { backgroundColor: 'rgba(33, 150, 243, 0.1)', borderLeftWidth: 3, borderLeftColor: AppColors.primary, }, logEntryAssistant: { backgroundColor: 'rgba(76, 175, 80, 0.1)', borderLeftWidth: 3, borderLeftColor: AppColors.success, }, logEntryHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: Spacing.xs, }, logEntrySpeaker: { flexDirection: 'row', alignItems: 'center', gap: 4, }, logEntrySpeakerText: { fontSize: FontSizes.sm, fontWeight: '600', }, logEntryTime: { fontSize: FontSizes.xs, color: AppColors.textMuted, }, logEntryText: { fontSize: FontSizes.base, color: AppColors.textPrimary, lineHeight: 22, }, logEntryHint: { fontSize: FontSizes.xs, color: AppColors.textMuted, marginTop: Spacing.xs, fontStyle: 'italic', }, footer: { padding: Spacing.md, alignItems: 'center', borderTopWidth: 1, borderTopColor: AppColors.border, }, footerText: { fontSize: FontSizes.sm, color: AppColors.textMuted, }, });