mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
(feat:fil_management) serialising updates, queue
This commit is contained in:
@@ -60,12 +60,23 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
|
||||||
const searchDropdownRef = useRef<HTMLDivElement>(null);
|
const searchDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const currentOpRef = useRef<null | 'add' | 'remove' | 'remove_directory'>(null);
|
||||||
|
|
||||||
const [deleteModalState, setDeleteModalState] = useState<'ACTIVE' | 'INACTIVE'>('INACTIVE');
|
const [deleteModalState, setDeleteModalState] = useState<'ACTIVE' | 'INACTIVE'>('INACTIVE');
|
||||||
const [itemToDelete, setItemToDelete] = useState<{ name: string; isFile: boolean } | null>(null);
|
const [itemToDelete, setItemToDelete] = useState<{ name: string; isFile: boolean } | null>(null);
|
||||||
|
|
||||||
|
type QueuedOperation = {
|
||||||
|
operation: 'add' | 'remove' | 'remove_directory';
|
||||||
|
files?: File[];
|
||||||
|
filePath?: string;
|
||||||
|
directoryPath?: string;
|
||||||
|
parentDirPath?: string;
|
||||||
|
};
|
||||||
|
const opQueueRef = useRef<QueuedOperation[]>([]);
|
||||||
|
const processingRef = useRef(false);
|
||||||
|
const [queueLength, setQueueLength] = useState(0);
|
||||||
|
|
||||||
useOutsideAlerter(
|
useOutsideAlerter(
|
||||||
searchDropdownRef,
|
searchDropdownRef,
|
||||||
() => {
|
() => {
|
||||||
@@ -186,7 +197,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
|||||||
const getActionOptions = (
|
const getActionOptions = (
|
||||||
name: string,
|
name: string,
|
||||||
isFile: boolean,
|
isFile: boolean,
|
||||||
itemId: string,
|
_itemId: string,
|
||||||
): MenuOption[] => {
|
): MenuOption[] => {
|
||||||
const options: MenuOption[] = [];
|
const options: MenuOption[] = [];
|
||||||
|
|
||||||
@@ -238,16 +249,22 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
|||||||
setItemToDelete(null);
|
setItemToDelete(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const manageSource = async (operation: 'add' | 'remove' | 'remove_directory', files?: FileList | null, filePath?: string, directoryPath?: string) => {
|
const manageSource = async (
|
||||||
setIsUploading(true);
|
operation: 'add' | 'remove' | 'remove_directory',
|
||||||
|
files?: File[] | null,
|
||||||
|
filePath?: string,
|
||||||
|
directoryPath?: string,
|
||||||
|
parentDirPath?: string,
|
||||||
|
) => {
|
||||||
|
currentOpRef.current = operation;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('source_id', docId);
|
formData.append('source_id', docId);
|
||||||
formData.append('operation', operation);
|
formData.append('operation', operation);
|
||||||
|
|
||||||
if (operation === 'add' && files) {
|
if (operation === 'add' && files && files.length) {
|
||||||
formData.append('parent_dir', currentPath.join('/'));
|
formData.append('parent_dir', parentDirPath ?? currentPath.join('/'));
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
formData.append('file', files[i]);
|
formData.append('file', files[i]);
|
||||||
@@ -290,7 +307,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
|||||||
|
|
||||||
if (structureData && structureData.directory_structure) {
|
if (structureData && structureData.directory_structure) {
|
||||||
setDirectoryStructure(structureData.directory_structure);
|
setDirectoryStructure(structureData.directory_structure);
|
||||||
setIsUploading(false);
|
currentOpRef.current = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -314,38 +331,64 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
|||||||
console.error(`Error ${actionText}:`, error);
|
console.error(`Error ${actionText}:`, error);
|
||||||
setError(`Failed to ${errorText}`);
|
setError(`Failed to ${errorText}`);
|
||||||
} finally {
|
} finally {
|
||||||
setIsUploading(false);
|
currentOpRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processQueue = async () => {
|
||||||
|
if (processingRef.current) return;
|
||||||
|
processingRef.current = true;
|
||||||
|
try {
|
||||||
|
while (opQueueRef.current.length > 0) {
|
||||||
|
const nextOp = opQueueRef.current.shift()!;
|
||||||
|
setQueueLength(opQueueRef.current.length);
|
||||||
|
await manageSource(
|
||||||
|
nextOp.operation,
|
||||||
|
nextOp.files,
|
||||||
|
nextOp.filePath,
|
||||||
|
nextOp.directoryPath,
|
||||||
|
nextOp.parentDirPath,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
processingRef.current = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const enqueueOperation = (op: QueuedOperation) => {
|
||||||
|
opQueueRef.current.push(op);
|
||||||
|
setQueueLength(opQueueRef.current.length);
|
||||||
|
if (!processingRef.current) {
|
||||||
|
void processQueue();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleAddFile = () => {
|
const handleAddFile = () => {
|
||||||
const fileInput = document.createElement('input');
|
const fileInput = document.createElement('input');
|
||||||
fileInput.type = 'file';
|
fileInput.type = 'file';
|
||||||
fileInput.multiple = true;
|
fileInput.multiple = true;
|
||||||
fileInput.accept = '.rst,.md,.pdf,.txt,.docx,.csv,.epub,.html,.mdx,.json,.xlsx,.pptx,.png,.jpg,.jpeg';
|
fileInput.accept = '.rst,.md,.pdf,.txt,.docx,.csv,.epub,.html,.mdx,.json,.xlsx,.pptx,.png,.jpg,.jpeg';
|
||||||
|
|
||||||
fileInput.onchange = async (event) => {
|
fileInput.onchange = async (event) => {
|
||||||
const files = (event.target as HTMLInputElement).files;
|
const fileList = (event.target as HTMLInputElement).files;
|
||||||
if (!files || files.length === 0) return;
|
if (!fileList || fileList.length === 0) return;
|
||||||
|
const files = Array.from(fileList);
|
||||||
await manageSource('add', files);
|
enqueueOperation({ operation: 'add', files, parentDirPath: currentPath.join('/') });
|
||||||
};
|
};
|
||||||
|
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteFile = async (name: string, isFile: boolean) => {
|
const handleDeleteFile = async (name: string, isFile: boolean) => {
|
||||||
// Construct the full path to the file or directory
|
// Construct the full path to the file or directory
|
||||||
const itemPath = [...currentPath, name].join('/');
|
const itemPath = [...currentPath, name].join('/');
|
||||||
|
|
||||||
if (isFile) {
|
if (isFile) {
|
||||||
// Delete individual file
|
enqueueOperation({ operation: 'remove', filePath: itemPath });
|
||||||
await manageSource('remove', null, itemPath);
|
|
||||||
} else {
|
} else {
|
||||||
// Delete entire directory
|
enqueueOperation({ operation: 'remove_directory', directoryPath: itemPath });
|
||||||
await manageSource('remove_directory', null, undefined, itemPath);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -390,18 +433,21 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!selectedFile && (
|
{!selectedFile && (
|
||||||
<button
|
<div className="flex items-center gap-2 m-2 sm:m-0">
|
||||||
onClick={handleAddFile}
|
{(processingRef.current || queueLength > 0) && (
|
||||||
disabled={isUploading}
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
className={`flex h-[32px] w-full m-2 sm:m-0 sm:w-auto min-w-[108px] items-center justify-center rounded-full px-4 text-sm whitespace-normal text-white ${
|
{processingRef.current ? (currentOpRef.current === 'add' ? 'Uploading…' : 'Deleting…') : null}
|
||||||
isUploading
|
{queueLength > 0 ? `${processingRef.current ? ' • ' : ''}Queued: ${queueLength}` : ''}
|
||||||
? 'bg-gray-400 cursor-not-allowed'
|
</span>
|
||||||
: 'bg-purple-30 hover:bg-violets-are-blue'
|
)}
|
||||||
}`}
|
<button
|
||||||
title={isUploading ? "Uploading files..." : "Add file"}
|
onClick={handleAddFile}
|
||||||
>
|
className="bg-purple-30 hover:bg-violets-are-blue flex h-[32px] w-full sm:w-auto min-w-[108px] items-center justify-center rounded-full px-4 text-sm whitespace-normal text-white"
|
||||||
{isUploading ? 'Uploading...' : 'Add file'}
|
title={processingRef.current ? (currentOpRef.current === 'add' ? 'Uploading files...' : 'Deleting...') : 'Add file'}
|
||||||
</button>
|
>
|
||||||
|
Add file
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user