A React Native app for the ultimate thinking partner.
at main 7.4 kB view raw
1import React, { useRef, useEffect } from 'react'; 2import { 3 View, 4 Text, 5 StyleSheet, 6 TouchableOpacity, 7 Animated, 8 ScrollView, 9 Dimensions, 10} from 'react-native'; 11import { Ionicons } from '@expo/vector-icons'; 12import MessageContent from './MessageContent'; 13import { darkTheme, lightTheme } from '../theme'; 14import type { MemoryBlock } from '../types/letta'; 15 16interface MemoryBlockViewerProps { 17 block: MemoryBlock | null; 18 onClose: () => void; 19 isDark?: boolean; 20 isDesktop: boolean; 21} 22 23const MemoryBlockViewer: React.FC<MemoryBlockViewerProps> = ({ 24 block, 25 onClose, 26 isDark = true, 27 isDesktop, 28}) => { 29 const theme = isDark ? darkTheme : lightTheme; 30 const slideAnim = useRef(new Animated.Value(0)).current; 31 const fadeAnim = useRef(new Animated.Value(0)).current; 32 33 useEffect(() => { 34 if (block) { 35 Animated.parallel([ 36 Animated.timing(slideAnim, { 37 toValue: 1, 38 duration: 300, 39 useNativeDriver: false, 40 }), 41 Animated.timing(fadeAnim, { 42 toValue: 1, 43 duration: 250, 44 useNativeDriver: true, 45 }), 46 ]).start(); 47 } else { 48 Animated.parallel([ 49 Animated.timing(slideAnim, { 50 toValue: 0, 51 duration: 250, 52 useNativeDriver: false, 53 }), 54 Animated.timing(fadeAnim, { 55 toValue: 0, 56 duration: 200, 57 useNativeDriver: true, 58 }), 59 ]).start(); 60 } 61 }, [block]); 62 63 if (!block) return null; 64 65 if (isDesktop) { 66 // Desktop: Right pane 67 const panelWidth = slideAnim.interpolate({ 68 inputRange: [0, 1], 69 outputRange: [0, 440], 70 }); 71 72 return ( 73 <Animated.View 74 style={[ 75 styles.desktopPane, 76 { 77 width: panelWidth, 78 backgroundColor: theme.colors.background.primary, 79 borderLeftColor: theme.colors.border.primary, 80 }, 81 ]} 82 > 83 <View style={[styles.desktopHeader, { borderBottomColor: theme.colors.border.primary }]}> 84 <View style={styles.headerLeft}> 85 <Ionicons name="cube-outline" size={20} color={theme.colors.text.tertiary} /> 86 <Text style={[styles.headerLabel, { color: theme.colors.text.tertiary }]}> 87 KNOWLEDGE 88 </Text> 89 </View> 90 <TouchableOpacity onPress={onClose} style={styles.closeButton}> 91 <Ionicons name="close" size={24} color={theme.colors.text.primary} /> 92 </TouchableOpacity> 93 </View> 94 95 <ScrollView 96 style={styles.scrollContent} 97 contentContainerStyle={styles.scrollContentContainer} 98 showsVerticalScrollIndicator={true} 99 > 100 <View style={styles.blockContent}> 101 <Text style={[styles.blockTitle, { color: theme.colors.text.primary }]}> 102 {block.label} 103 </Text> 104 {block.description && ( 105 <Text style={[styles.blockDescription, { color: theme.colors.text.secondary }]}> 106 {block.description} 107 </Text> 108 )} 109 <View style={[styles.divider, { backgroundColor: theme.colors.border.primary }]} /> 110 <MessageContent content={block.value} isUser={false} isDark={isDark} /> 111 </View> 112 </ScrollView> 113 </Animated.View> 114 ); 115 } else { 116 // Mobile: Full screen overlay 117 return ( 118 <Animated.View 119 style={[ 120 styles.mobileOverlay, 121 { 122 opacity: fadeAnim, 123 }, 124 ]} 125 > 126 <TouchableOpacity 127 style={styles.backdrop} 128 activeOpacity={1} 129 onPress={onClose} 130 /> 131 <Animated.View 132 style={[ 133 styles.mobilePanel, 134 { 135 backgroundColor: theme.colors.background.primary, 136 transform: [ 137 { 138 translateY: slideAnim.interpolate({ 139 inputRange: [0, 1], 140 outputRange: [Dimensions.get('window').height, 0], 141 }), 142 }, 143 ], 144 }, 145 ]} 146 > 147 <View style={[styles.mobileHeader, { borderBottomColor: theme.colors.border.primary }]}> 148 <View style={styles.headerLeft}> 149 <Ionicons name="cube-outline" size={20} color={theme.colors.text.tertiary} /> 150 <Text style={[styles.headerLabel, { color: theme.colors.text.tertiary }]}> 151 KNOWLEDGE 152 </Text> 153 </View> 154 <TouchableOpacity onPress={onClose} style={styles.closeButton}> 155 <Ionicons name="close" size={24} color={theme.colors.text.primary} /> 156 </TouchableOpacity> 157 </View> 158 159 <ScrollView 160 style={styles.scrollContent} 161 contentContainerStyle={styles.scrollContentContainer} 162 showsVerticalScrollIndicator={true} 163 > 164 <View style={styles.blockContent}> 165 <Text style={[styles.blockTitle, { color: theme.colors.text.primary }]}> 166 {block.label} 167 </Text> 168 {block.description && ( 169 <Text style={[styles.blockDescription, { color: theme.colors.text.secondary }]}> 170 {block.description} 171 </Text> 172 )} 173 <View style={[styles.divider, { backgroundColor: theme.colors.border.primary }]} /> 174 <MessageContent content={block.value} isUser={false} isDark={isDark} /> 175 </View> 176 </ScrollView> 177 </Animated.View> 178 </Animated.View> 179 ); 180 } 181}; 182 183const styles = StyleSheet.create({ 184 // Desktop styles 185 desktopPane: { 186 borderLeftWidth: 1, 187 overflow: 'hidden', 188 }, 189 desktopHeader: { 190 flexDirection: 'row', 191 justifyContent: 'space-between', 192 alignItems: 'center', 193 paddingHorizontal: 20, 194 paddingVertical: 16, 195 borderBottomWidth: 1, 196 }, 197 198 // Mobile styles 199 mobileOverlay: { 200 position: 'absolute', 201 top: 0, 202 left: 0, 203 right: 0, 204 bottom: 0, 205 zIndex: 1000, 206 }, 207 backdrop: { 208 position: 'absolute', 209 top: 0, 210 left: 0, 211 right: 0, 212 bottom: 0, 213 backgroundColor: 'rgba(0, 0, 0, 0.6)', 214 }, 215 mobilePanel: { 216 position: 'absolute', 217 top: 60, 218 left: 0, 219 right: 0, 220 bottom: 0, 221 borderTopLeftRadius: 20, 222 borderTopRightRadius: 20, 223 boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.25)', 224 elevation: 10, 225 }, 226 mobileHeader: { 227 flexDirection: 'row', 228 justifyContent: 'space-between', 229 alignItems: 'center', 230 paddingHorizontal: 20, 231 paddingTop: 20, 232 paddingBottom: 16, 233 borderBottomWidth: 1, 234 }, 235 236 // Shared styles 237 headerLeft: { 238 flexDirection: 'row', 239 alignItems: 'center', 240 gap: 8, 241 }, 242 headerLabel: { 243 fontSize: 12, 244 fontFamily: 'Lexend_600SemiBold', 245 letterSpacing: 1.2, 246 }, 247 closeButton: { 248 padding: 4, 249 }, 250 scrollContent: { 251 flex: 1, 252 }, 253 scrollContentContainer: { 254 padding: 24, 255 }, 256 blockContent: { 257 flex: 1, 258 }, 259 blockTitle: { 260 fontSize: 24, 261 fontFamily: 'Lexend_700Bold', 262 marginBottom: 8, 263 }, 264 blockDescription: { 265 fontSize: 14, 266 fontFamily: 'Lexend_400Regular', 267 marginBottom: 16, 268 lineHeight: 20, 269 }, 270 divider: { 271 height: 1, 272 backgroundColor: 'rgba(255, 255, 255, 0.1)', 273 marginVertical: 20, 274 }, 275}); 276 277export default MemoryBlockViewer;