/** * KnowledgeView Component * * Knowledge management interface with three tabs: * - Core Memory: View and search memory blocks * - Archival Memory: Search, create, edit, delete passages * - Files: Upload, list, delete files * * Features responsive layouts (2-column grid on desktop, single column on mobile). */ import React from 'react'; import { View, Text, TouchableOpacity, TextInput, FlatList, ActivityIndicator, StyleSheet, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import type { Theme } from '../theme'; import type { MemoryBlock, Passage } from '../types/letta'; type KnowledgeTab = 'core' | 'archival' | 'files'; interface FileItem { id: string; fileName?: string; name?: string; createdAt?: string; created_at?: string; } interface KnowledgeViewProps { theme: Theme; // Tab state knowledgeTab: KnowledgeTab; onTabChange: (tab: KnowledgeTab) => void; // Core Memory memoryBlocks: MemoryBlock[]; memorySearchQuery: string; onMemorySearchChange: (query: string) => void; isLoadingBlocks: boolean; blocksError: string | null; onSelectBlock: (block: MemoryBlock) => void; // Archival Memory passages: Passage[]; passageSearchQuery: string; onPassageSearchChange: (query: string) => void; onPassageSearchSubmit: () => void; isLoadingPassages: boolean; passagesError: string | null; hasMorePassages: boolean; onLoadMorePassages: () => void; onPassageCreate: () => void; onPassageEdit: (passage: Passage) => void; onPassageDelete: (id: string) => void; // Files folderFiles: FileItem[]; isLoadingFiles: boolean; filesError: string | null; isUploadingFile: boolean; uploadProgress: string | null; onFileUpload: () => void; onFileDelete: (id: string, name: string) => void; // Layout isDesktop: boolean; } export function KnowledgeView(props: KnowledgeViewProps) { const { theme, knowledgeTab, onTabChange, memoryBlocks, memorySearchQuery, onMemorySearchChange, isLoadingBlocks, blocksError, onSelectBlock, passages, passageSearchQuery, onPassageSearchChange, onPassageSearchSubmit, isLoadingPassages, passagesError, hasMorePassages, onLoadMorePassages, onPassageCreate, onPassageEdit, onPassageDelete, folderFiles, isLoadingFiles, filesError, isUploadingFile, uploadProgress, onFileUpload, onFileDelete, isDesktop, } = props; return ( {/* Tab Switcher */} onTabChange('core')} > Core Memory onTabChange('archival')} > Archival Memory onTabChange('files')} > Files {/* Search Bar for Core Memory */} {knowledgeTab === 'core' && ( )} {/* Search Bar for Archival Memory */} {knowledgeTab === 'archival' && ( {passageSearchQuery && ( { onPassageSearchChange(''); onPassageSearchSubmit(); }} > )} )} {/* Content Grid */} {knowledgeTab === 'files' ? ( // FILES TAB <> Uploaded Files {isUploadingFile ? ( ) : ( )} {uploadProgress && ( {uploadProgress} )} {isLoadingFiles ? ( ) : filesError ? ( {filesError} ) : folderFiles.length === 0 ? ( No files uploaded yet ) : ( item.id} contentContainerStyle={styles.listContent} renderItem={({ item }) => ( {item.fileName || item.name || 'Untitled'} {new Date(item.createdAt || item.created_at || '').toLocaleDateString()} onFileDelete(item.id, item.fileName || item.name || '')} style={{ padding: 8 }} > )} /> )} ) : knowledgeTab === 'archival' ? ( // ARCHIVAL MEMORY TAB isLoadingPassages ? ( ) : passagesError ? ( {passagesError} ) : passages.length === 0 ? ( No archival memories yet ) : ( item.id} contentContainerStyle={styles.listContent} renderItem={({ item }) => ( {new Date(item.created_at).toLocaleString()} onPassageEdit(item)} style={{ padding: 4 }}> onPassageDelete(item.id)} style={{ padding: 4 }}> {item.text} {item.tags && item.tags.length > 0 && ( {item.tags.map((tag, idx) => ( {tag} ))} )} )} ListFooterComponent={ hasMorePassages ? ( Load more... ) : null } /> ) ) : ( // CORE MEMORY TAB isLoadingBlocks ? ( ) : blocksError ? ( {blocksError} ) : ( { if (memorySearchQuery) { return ( block.label.toLowerCase().includes(memorySearchQuery.toLowerCase()) || block.value.toLowerCase().includes(memorySearchQuery.toLowerCase()) ); } return true; })} numColumns={isDesktop ? 2 : 1} key={isDesktop ? 'desktop' : 'mobile'} keyExtractor={(item) => item.id || item.label} contentContainerStyle={styles.listContent} renderItem={({ item }) => ( onSelectBlock(item)} > {item.label} {item.value.length} chars {item.value || 'Empty'} )} ListEmptyComponent={ {memorySearchQuery ? 'No memory blocks found' : 'No memory blocks yet'} } /> ) )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, tabsContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 4, borderBottomWidth: 1, }, tab: { paddingVertical: 12, paddingHorizontal: 16, flex: 1, alignItems: 'center', }, tabText: { fontSize: 14, fontFamily: 'Lexend_500Medium', }, searchContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, }, searchIcon: { position: 'absolute', left: 24, zIndex: 1, }, searchInput: { flex: 1, height: 40, borderRadius: 20, paddingLeft: 40, paddingRight: 16, fontSize: 14, borderWidth: 1, fontFamily: 'Lexend_400Regular', }, clearSearchButton: { position: 'absolute', right: 64, padding: 8, }, createButton: { position: 'absolute', right: 28, padding: 8, }, contentGrid: { flex: 1, }, filesHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 8, paddingVertical: 12, }, sectionTitle: { fontSize: 14, fontFamily: 'Lexend_600SemiBold', textTransform: 'uppercase', letterSpacing: 0.5, }, fileUploadButton: { padding: 4, }, uploadProgress: { marginHorizontal: 8, marginBottom: 12, paddingVertical: 8, paddingHorizontal: 12, borderRadius: 8, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, emptyState: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 80, }, emptyText: { fontSize: 16, fontFamily: 'Lexend_400Regular', marginTop: 16, }, errorText: { fontSize: 14, fontFamily: 'Lexend_400Regular', color: '#E07042', }, listContent: { padding: 8, }, fileCard: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 16, borderRadius: 12, borderWidth: 1, marginBottom: 8, }, fileCardLabel: { fontSize: 16, fontFamily: 'Lexend_500Medium', }, fileCardPreview: { fontSize: 12, fontFamily: 'Lexend_400Regular', marginTop: 4, }, passageCard: { padding: 16, borderRadius: 12, borderWidth: 1, marginBottom: 12, }, passageHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 8, }, passageDate: { fontFamily: 'Lexend_400Regular', }, passageActions: { flexDirection: 'row', gap: 8, }, passageText: { fontSize: 14, fontFamily: 'Lexend_400Regular', lineHeight: 20, }, tagsContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: 8, }, tag: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 4, }, memoryCard: { flex: 1, padding: 16, borderRadius: 12, borderWidth: 1, margin: 4, minHeight: 120, }, memoryCardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, memoryCardLabel: { fontSize: 16, fontFamily: 'Lexend_600SemiBold', flex: 1, }, memoryCardCount: { fontSize: 11, fontFamily: 'Lexend_400Regular', marginLeft: 8, }, memoryCardPreview: { fontSize: 13, fontFamily: 'Lexend_400Regular', lineHeight: 18, }, loadMoreButton: { padding: 16, alignItems: 'center', }, }); export default KnowledgeView;