From 521276984866861caf79bc021a1cfd0f4c8b5e7a Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 1 Aug 2025 01:25:37 +0530 Subject: [PATCH] (feat:reingest) UI, polling --- frontend/src/api/endpoints.ts | 1 + frontend/src/api/services/userService.ts | 2 + frontend/src/components/DocumentChunks.tsx | 4 +- frontend/src/components/FileTreeComponent.tsx | 236 +++++++++++++++--- 4 files changed, 203 insertions(+), 40 deletions(-) diff --git a/frontend/src/api/endpoints.ts b/frontend/src/api/endpoints.ts index 2980da46..81d19c87 100644 --- a/frontend/src/api/endpoints.ts +++ b/frontend/src/api/endpoints.ts @@ -55,6 +55,7 @@ const endpoints = { STORE_ATTACHMENT: '/api/store_attachment', DIRECTORY_STRUCTURE: (docId: string) => `/api/directory_structure?id=${docId}`, + MANAGE_SOURCE_FILES: '/api/manage_source_files', }, CONVERSATION: { ANSWER: '/api/answer', diff --git a/frontend/src/api/services/userService.ts b/frontend/src/api/services/userService.ts index a2968312..af5e4f22 100644 --- a/frontend/src/api/services/userService.ts +++ b/frontend/src/api/services/userService.ts @@ -102,6 +102,8 @@ const userService = { apiClient.put(endpoints.USER.UPDATE_CHUNK, data, token), getDirectoryStructure: (docId: string, token: string | null): Promise => apiClient.get(endpoints.USER.DIRECTORY_STRUCTURE(docId), token), + manageSourceFiles: (data: FormData, token: string | null): Promise => + apiClient.postFormData(endpoints.USER.MANAGE_SOURCE_FILES, data, token), }; export default userService; diff --git a/frontend/src/components/DocumentChunks.tsx b/frontend/src/components/DocumentChunks.tsx index 44becf7c..3b2881ed 100644 --- a/frontend/src/components/DocumentChunks.tsx +++ b/frontend/src/components/DocumentChunks.tsx @@ -257,9 +257,11 @@ const DocumentChunks: React.FC = ({ return () => clearTimeout(delayDebounceFn); }, [searchTerm]); + useEffect(() => { - fetchChunks(); + !loading && fetchChunks(); }, [page, perPage, path]); + useEffect(() => { setSearchTerm(''); setPage(1); diff --git a/frontend/src/components/FileTreeComponent.tsx b/frontend/src/components/FileTreeComponent.tsx index 98d148c1..5e4282b1 100644 --- a/frontend/src/components/FileTreeComponent.tsx +++ b/frontend/src/components/FileTreeComponent.tsx @@ -13,6 +13,7 @@ import OutlineSource from '../assets/outline-source.svg'; import Trash from '../assets/red-trash.svg'; import SearchIcon from '../assets/search.svg'; import { useOutsideAlerter } from '../hooks'; +import ConfirmationModal from '../modals/ConfirmationModal'; interface FileNode { type?: string; @@ -59,8 +60,12 @@ const FileTreeComponent: React.FC = ({ } | null>(null); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); + const [isUploading, setIsUploading] = useState(false); const searchDropdownRef = useRef(null); + const [deleteModalState, setDeleteModalState] = useState<'ACTIVE' | 'INACTIVE'>('INACTIVE'); + const [itemToDelete, setItemToDelete] = useState<{ name: string; isFile: boolean } | null>(null); + useOutsideAlerter( searchDropdownRef, () => { @@ -123,9 +128,23 @@ const FileTreeComponent: React.FC = ({ const getCurrentDirectory = (): DirectoryStructure => { if (!directoryStructure) return {}; - let current: any = directoryStructure; + let structure = directoryStructure; + if (typeof structure === 'string') { + try { + structure = JSON.parse(structure); + } catch (e) { + console.error('Error parsing directory structure in getCurrentDirectory:', e); + return {}; + } + } + + if (typeof structure !== 'object' || structure === null) { + return {}; + } + + let current: any = structure; for (const dir of currentPath) { - if (current[dir] && !current[dir].type) { + if (current[dir] && typeof current[dir] === 'object' && !current[dir].type) { current = current[dir]; } else { return {}; @@ -190,8 +209,7 @@ const FileTreeComponent: React.FC = ({ label: t('convTile.delete'), onClick: (event: React.SyntheticEvent) => { event.stopPropagation(); - console.log('Delete item:', name); - // Delete action will be implemented later + confirmDeleteItem(name, isFile); }, iconWidth: 18, iconHeight: 18, @@ -200,44 +218,175 @@ const FileTreeComponent: React.FC = ({ return options; }; + + const confirmDeleteItem = (name: string, isFile: boolean) => { + setItemToDelete({ name, isFile }); + setDeleteModalState('ACTIVE'); + setActiveMenuId(null); + }; + + const handleConfirmedDelete = async () => { + if (itemToDelete) { + await handleDeleteFile(itemToDelete.name, itemToDelete.isFile); + setDeleteModalState('INACTIVE'); + setItemToDelete(null); + } + }; + + const handleCancelDelete = () => { + setDeleteModalState('INACTIVE'); + setItemToDelete(null); + }; + + const manageSource = async (operation: 'add' | 'remove', files?: FileList | null, filePath?: string) => { + setIsUploading(true); + + try { + const formData = new FormData(); + formData.append('source_id', docId); + formData.append('operation', operation); + + if (operation === 'add' && files) { + formData.append('parent_dir', currentPath.join('/')); + + for (let i = 0; i < files.length; i++) { + formData.append('file', files[i]); + } + } else if (operation === 'remove' && filePath) { + const filePaths = JSON.stringify([filePath]); + formData.append('file_paths', filePaths); + } + + const response = await userService.manageSourceFiles(formData, token); + const result = await response.json(); + + if (result.success && result.reingest_task_id) { + console.log(`Files ${operation === 'add' ? 'uploaded' : 'deleted'} successfully:`, + operation === 'add' ? result.added_files : result.removed_files); + console.log('Reingest task started:', result.reingest_task_id); + + const maxAttempts = 30; + const pollInterval = 2000; + + for (let attempt = 0; attempt < maxAttempts; attempt++) { + try { + const statusResponse = await userService.getTaskStatus(result.reingest_task_id, token); + const statusData = await statusResponse.json(); + + console.log(`Task status (attempt ${attempt + 1}):`, statusData.status); + + if (statusData.status === 'SUCCESS') { + console.log('Task completed successfully'); + + const structureResponse = await userService.getDirectoryStructure(docId, token); + const structureData = await structureResponse.json(); + + if (structureData && structureData.directory_structure) { + setDirectoryStructure(structureData.directory_structure); + setIsUploading(false); + return true; + } + break; + } else if (statusData.status === 'FAILURE') { + console.error('Task failed'); + break; + } + + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } catch (error) { + console.error('Error polling task status:', error); + break; + } + } + } else { + throw new Error(`Failed to ${operation} file(s)`); + } + } catch (error) { + console.error(`Error ${operation === 'add' ? 'uploading' : 'deleting'} file(s):`, error); + setError(`Failed to ${operation === 'add' ? 'upload' : 'delete'} file(s)`); + } finally { + setIsUploading(false); + } + + return false; + }; + + const handleAddFile = () => { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.multiple = true; + fileInput.accept = '.rst,.md,.pdf,.txt,.docx,.csv,.epub,.html,.mdx,.json,.xlsx,.pptx,.png,.jpg,.jpeg'; + + fileInput.onchange = async (event) => { + const files = (event.target as HTMLInputElement).files; + if (!files || files.length === 0) return; + + await manageSource('add', files); + }; + + fileInput.click(); + }; + + const handleDeleteFile = async (name: string, isFile: boolean) => { + // Construct the full path to the file + const filePath = [...currentPath, name].join('/'); + await manageSource('remove', null, filePath); + }; const renderPathNavigation = () => { return ( -
- +
+
+ -
- source - {sourceName} - {currentPath.length > 0 && ( - <> - / - {currentPath.map((dir, index) => ( - - - {dir} - - {index < currentPath.length - 1 && ( - / - )} - - ))} - - )} - {selectedFile && ( - <> - / - - {selectedFile.name} - - - )} +
+ source + {sourceName} + {currentPath.length > 0 && ( + <> + / + {currentPath.map((dir, index) => ( + + + {dir} + + {index < currentPath.length - 1 && ( + / + )} + + ))} + + )} + {selectedFile && ( + <> + / + + {selectedFile.name} + + + )} +
+ + {!selectedFile && ( + + )}
); }; @@ -507,7 +656,7 @@ const FileTreeComponent: React.FC = ({ }; return ( - <> +
{selectedFile ? (
@@ -561,7 +710,16 @@ const FileTreeComponent: React.FC = ({
)} - + +
); };