import React, { useState, useEffect, useRef } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, TextInput, Share, Platform, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { debugLogger, type LogEntry } from '@/services/DebugLogger'; import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; import sherpaTTS from '@/services/sherpaTTS'; export default function DebugScreen() { const [logs, setLogs] = useState([]); const [filter, setFilter] = useState(''); const [selectedCategory, setSelectedCategory] = useState('All'); const [ttsState, setTtsState] = useState>(sherpaTTS.getState()); const flatListRef = useRef(null); // Initialize TTS and subscribe to state changes useEffect(() => { // Subscribe to TTS state changes const unsubscribeTTS = sherpaTTS.addStateListener(setTtsState); // Start initialization sherpaTTS.initialize().catch(e => debugLogger.error('TTS', `Init failed: ${e}`) ); return unsubscribeTTS; }, []); // Subscribe to log updates useEffect(() => { const unsubscribe = debugLogger.subscribe((newLogs) => { setLogs(newLogs); // Auto-scroll to bottom when new logs arrive setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); }); // Initial load setLogs(debugLogger.getLogs()); return unsubscribe; }, []); // Get unique categories const categories = ['All', ...new Set(logs.map(log => log.category))]; // Filter logs const filteredLogs = logs.filter(log => { const matchesCategory = selectedCategory === 'All' || log.category === selectedCategory; const matchesFilter = !filter || log.message.toLowerCase().includes(filter.toLowerCase()); return matchesCategory && matchesFilter; }); // Clear logs const handleClear = () => { debugLogger.clear(); }; // Export logs const handleExport = async () => { const text = debugLogger.exportAsText(); try { await Share.share({ message: text, title: 'Debug Logs Export', }); } catch (error) { console.error('Failed to export logs:', error); } }; // Test TTS - check if ready before speaking const handleTestTTS = () => { if (!ttsState.initialized) { debugLogger.warn('TTS', 'Cannot test - TTS not ready yet'); return; } debugLogger.info('TTS', 'Testing voice...'); sherpaTTS.speak('Hello, this is a test message', { onDone: () => debugLogger.info('TTS', 'Voice test complete'), onError: (e) => debugLogger.error('TTS', `Voice test failed: ${e}`) }); }; // Get TTS status display const getTTSStatus = () => { if (ttsState.initializing) { return { text: ttsState.error || 'Downloading voice model...', color: AppColors.warning || '#FF9800', icon: 'cloud-download' as const, }; } if (ttsState.initialized) { return { text: 'Ready', color: '#4CAF50', icon: 'checkmark-circle' as const, }; } if (ttsState.error) { return { text: ttsState.error, color: AppColors.error || '#E53935', icon: 'alert-circle' as const, }; } return { text: 'Not initialized', color: AppColors.textMuted, icon: 'time' as const, }; }; const ttsStatus = getTTSStatus(); // Get log level color const getLevelColor = (level: LogEntry['level']): string => { switch (level) { case 'error': return AppColors.error || '#E53935'; case 'warn': return AppColors.warning || '#FF9800'; case 'info': return AppColors.primary; default: return AppColors.textSecondary; } }; // Get level icon const getLevelIcon = (level: LogEntry['level']): any => { switch (level) { case 'error': return 'close-circle'; case 'warn': return 'warning'; case 'info': return 'information-circle'; default: return 'chatbubble-ellipses'; } }; // Render log item const renderLog = ({ item }: { item: LogEntry }) => { const time = item.timestamp.toLocaleTimeString(); const levelColor = getLevelColor(item.level); return ( {item.level.toUpperCase()} [{item.category}] {time} {item.message} {item.data && ( {typeof item.data === 'object' ? JSON.stringify(item.data, null, 2) : String(item.data)} )} ); }; return ( {/* Header */} Debug Console {/* TTS Test Button */} {/* TTS Status Indicator */} {ttsStatus.text} {ttsState.speaking ? 'Stop' : 'Test Voice'} {/* Stats Bar */} Total: {logs.length} Errors: {logs.filter(l => l.level === 'error').length} Warns: {logs.filter(l => l.level === 'warn').length} Filtered: {filteredLogs.length} {/* Category Filter */} item} renderItem={({ item }) => ( setSelectedCategory(item)} > {item} )} contentContainerStyle={styles.categoryList} showsHorizontalScrollIndicator={false} /> {/* Search Filter */} {filter.length > 0 && ( setFilter('')}> )} {/* Logs List */} item.id} renderItem={renderLog} contentContainerStyle={styles.logsList} showsVerticalScrollIndicator={true} ListEmptyComponent={ No logs yet Voice and system logs will appear here } /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: AppColors.background, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, backgroundColor: AppColors.surface, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, headerTitle: { fontSize: FontSizes.xl, fontWeight: '600', color: AppColors.textPrimary, }, headerButtons: { flexDirection: 'row', gap: Spacing.sm, }, headerButton: { padding: Spacing.xs, }, statsBar: { flexDirection: 'row', justifyContent: 'space-around', paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, backgroundColor: AppColors.surface, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, statItem: { alignItems: 'center', }, statLabel: { fontSize: FontSizes.xs, color: AppColors.textSecondary, marginBottom: 2, }, statValue: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textPrimary, }, filterContainer: { backgroundColor: AppColors.surface, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, categoryList: { paddingHorizontal: Spacing.sm, paddingVertical: Spacing.xs, }, categoryChip: { paddingHorizontal: Spacing.sm + 4, paddingVertical: Spacing.xs, borderRadius: BorderRadius.full, backgroundColor: AppColors.background, marginHorizontal: 4, borderWidth: 1, borderColor: AppColors.border, }, categoryChipActive: { backgroundColor: AppColors.primary, borderColor: AppColors.primary, }, categoryChipText: { fontSize: FontSizes.sm, color: AppColors.textSecondary, fontWeight: '500', }, categoryChipTextActive: { color: AppColors.white, }, searchContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, backgroundColor: AppColors.surface, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, searchIcon: { marginRight: Spacing.xs, }, searchInput: { flex: 1, fontSize: FontSizes.base, color: AppColors.textPrimary, paddingVertical: 0, }, logsList: { padding: Spacing.sm, }, logItem: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.md, padding: Spacing.sm, marginBottom: Spacing.sm, borderLeftWidth: 3, borderLeftColor: AppColors.primary, }, logHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: Spacing.xs, }, logInfo: { flexDirection: 'row', alignItems: 'center', gap: Spacing.xs, }, logLevel: { fontSize: FontSizes.xs, fontWeight: '600', }, logCategory: { fontSize: FontSizes.xs, color: AppColors.textSecondary, fontWeight: '500', }, logTime: { fontSize: FontSizes.xs, color: AppColors.textMuted, }, logMessage: { fontSize: FontSizes.sm, color: AppColors.textPrimary, lineHeight: 20, }, logData: { fontSize: FontSizes.xs, color: AppColors.textSecondary, marginTop: Spacing.xs, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 100, }, emptyText: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textSecondary, marginTop: Spacing.md, }, emptySubtext: { fontSize: FontSizes.sm, color: AppColors.textMuted, marginTop: Spacing.xs, }, ttsTestContainer: { paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, backgroundColor: AppColors.surface, borderBottomWidth: 1, borderBottomColor: AppColors.border, gap: Spacing.xs, }, testButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: AppColors.primary, paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, borderRadius: BorderRadius.md, gap: Spacing.xs, }, testButtonText: { fontSize: FontSizes.base, fontWeight: '600', color: AppColors.white, }, testButtonDisabled: { backgroundColor: AppColors.border, opacity: 0.6, }, testButtonTextDisabled: { color: AppColors.textMuted, }, ttsStatusRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.xs, }, ttsStatusText: { fontSize: FontSizes.sm, fontWeight: '500', }, });