From aa5f71657849666f09984e13304e2fdebcc9a1a6 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 28 Apr 2026 13:22:25 +0800 Subject: [PATCH] feat(upload): support multiple file upload on RAG Database page --- frontend/src/components/DocumentUpload.tsx | 30 ++----- frontend/src/pages/RAGDatabasePage.tsx | 97 +++++++++++++++++----- 2 files changed, 84 insertions(+), 43 deletions(-) diff --git a/frontend/src/components/DocumentUpload.tsx b/frontend/src/components/DocumentUpload.tsx index 686c782..17613a3 100644 --- a/frontend/src/components/DocumentUpload.tsx +++ b/frontend/src/components/DocumentUpload.tsx @@ -1,24 +1,21 @@ import React from 'react' -import { Upload, Loader2, CheckCircle, AlertCircle } from 'lucide-react' +import { Upload, Loader2 } from 'lucide-react' interface DocumentUploadProps { - onUpload: (file: File) => void + onUpload: (files: File[]) => void isLoading: boolean - success: string | null - error: string | null } export const DocumentUpload: React.FC = ({ onUpload, isLoading, - success, - error, }) => { const handleFileChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (file) { - onUpload(file) + const files = event.target.files + if (files && files.length > 0) { + onUpload(Array.from(files)) } + event.target.value = '' } return ( @@ -26,6 +23,7 @@ export const DocumentUpload: React.FC = ({ = ({ )} - - {success && ( -
- - {success} -
- )} - - {error && ( -
- - {error} -
- )} ) } diff --git a/frontend/src/pages/RAGDatabasePage.tsx b/frontend/src/pages/RAGDatabasePage.tsx index 3bf6ee5..2d13ccd 100644 --- a/frontend/src/pages/RAGDatabasePage.tsx +++ b/frontend/src/pages/RAGDatabasePage.tsx @@ -1,15 +1,20 @@ -import React, { useState } from 'react' -import { Database, AlertCircle } from 'lucide-react' +import React, { useState, useCallback } from 'react' +import { Database, AlertCircle, CheckCircle, XCircle, Loader2 } from 'lucide-react' import { useQueryClient } from '@tanstack/react-query' import { useDocuments, useDocumentChunks, useDeleteDocument, useDeleteChunk, useIngestDocument } from '../lib/queries' import { DocumentList } from '../components/DocumentList' import { ChunkList } from '../components/ChunkList' import { DocumentUpload } from '../components/DocumentUpload' +interface FileUploadEntry { + name: string + status: 'uploading' | 'success' | 'error' + error?: string +} + export const RAGDatabasePage: React.FC = () => { const [expandedId, setExpandedId] = useState(null) - const [uploadSuccess, setUploadSuccess] = useState(null) - const [uploadError, setUploadError] = useState(null) + const [uploadEntries, setUploadEntries] = useState([]) const { data: documentsData, isLoading: isLoadingDocuments, error: documentsError } = useDocuments() const { data: chunks, isLoading: isLoadingChunks } = useDocumentChunks(expandedId) @@ -38,20 +43,42 @@ export const RAGDatabasePage: React.FC = () => { } } - const handleUpload = (file: File) => { - setUploadSuccess(null) - setUploadError(null) - ingestDocumentMutation.mutate(file, { - onSuccess: (data) => { - setUploadSuccess(`Uploaded ${data.filename}`) - queryClient.invalidateQueries({ queryKey: ['documents'] }) - setTimeout(() => setUploadSuccess(null), 3000) - }, - onError: (error) => { - setUploadError(error.message) - }, - }) - } + const handleUpload = useCallback(async (files: File[]) => { + const entries: FileUploadEntry[] = files.map((f) => ({ + name: f.name, + status: 'uploading' as const, + })) + setUploadEntries(entries) + + const results = await Promise.allSettled( + files.map(async (file) => { + try { + await ingestDocumentMutation.mutateAsync(file) + setUploadEntries((prev) => + prev.map((e) => + e.name === file.name ? { ...e, status: 'success' as const } : e + ) + ) + } catch (err: any) { + setUploadEntries((prev) => + prev.map((e) => + e.name === file.name + ? { ...e, status: 'error' as const, error: err?.message ?? 'Upload failed' } + : e + ) + ) + } + }) + ) + + queryClient.invalidateQueries({ queryKey: ['documents'] }) + setTimeout(() => setUploadEntries([]), 5000) + }, [ingestDocumentMutation, queryClient]) + + const uploadingCount = uploadEntries.filter((e) => e.status === 'uploading').length + const successCount = uploadEntries.filter((e) => e.status === 'success').length + const errorCount = uploadEntries.filter((e) => e.status === 'error').length + const hasEntries = uploadEntries.length > 0 if (isLoadingDocuments) { return ( @@ -90,10 +117,40 @@ export const RAGDatabasePage: React.FC = () => { + + {hasEntries && ( +
+
+ {uploadingCount > 0 + ? `Uploading ${successCount + errorCount + 1} of ${uploadEntries.length}…` + : `${successCount} succeeded${errorCount > 0 ? `, ${errorCount} failed` : ''}`} +
+
+ {uploadEntries.map((entry) => ( +
+ {entry.status === 'uploading' && ( + + )} + {entry.status === 'success' && ( + + )} + {entry.status === 'error' && ( + + )} + {entry.name} + {entry.status === 'error' && entry.error && ( + — {entry.error} + )} +
+ ))} +
+
+ )} {hasDocuments && (