Merge pull request #1214 from danielaskdd/upload-error
Browse files- lightrag/api/__init__.py +1 -1
- lightrag/api/routers/document_routes.py +8 -0
- lightrag/api/webui/assets/index-Bwboeqcm.css +0 -0
- lightrag/api/webui/assets/{index-B4QL89Xd.js → index-Bz28HSH8.js} +0 -0
- lightrag/api/webui/assets/index-Cl6-O9yL.css +0 -0
- lightrag/api/webui/index.html +0 -0
- lightrag_webui/src/api/lightrag.ts +1 -1
- lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx +115 -38
- lightrag_webui/src/components/ui/FileUploader.tsx +111 -26
- lightrag_webui/src/locales/ar.json +4 -1
- lightrag_webui/src/locales/en.json +4 -1
- lightrag_webui/src/locales/fr.json +4 -1
- lightrag_webui/src/locales/zh.json +4 -1
lightrag/api/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1 |
-
__api_version__ = "1.2.
|
|
|
1 |
+
__api_version__ = "1.2.8"
|
lightrag/api/routers/document_routes.py
CHANGED
@@ -542,6 +542,7 @@ def create_document_routes(
|
|
542 |
|
543 |
Returns:
|
544 |
InsertResponse: A response object containing the upload status and a message.
|
|
|
545 |
|
546 |
Raises:
|
547 |
HTTPException: If the file type is not supported (400) or other errors occur (500).
|
@@ -554,6 +555,13 @@ def create_document_routes(
|
|
554 |
)
|
555 |
|
556 |
file_path = doc_manager.input_dir / file.filename
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
557 |
with open(file_path, "wb") as buffer:
|
558 |
shutil.copyfileobj(file.file, buffer)
|
559 |
|
|
|
542 |
|
543 |
Returns:
|
544 |
InsertResponse: A response object containing the upload status and a message.
|
545 |
+
status can be "success", "duplicated", or error is thrown.
|
546 |
|
547 |
Raises:
|
548 |
HTTPException: If the file type is not supported (400) or other errors occur (500).
|
|
|
555 |
)
|
556 |
|
557 |
file_path = doc_manager.input_dir / file.filename
|
558 |
+
# Check if file already exists
|
559 |
+
if file_path.exists():
|
560 |
+
return InsertResponse(
|
561 |
+
status="duplicated",
|
562 |
+
message=f"File '{file.filename}' already exists in the input directory.",
|
563 |
+
)
|
564 |
+
|
565 |
with open(file_path, "wb") as buffer:
|
566 |
shutil.copyfileobj(file.file, buffer)
|
567 |
|
lightrag/api/webui/assets/index-Bwboeqcm.css
DELETED
Binary file (55 kB)
|
|
lightrag/api/webui/assets/{index-B4QL89Xd.js → index-Bz28HSH8.js}
RENAMED
Binary files a/lightrag/api/webui/assets/index-B4QL89Xd.js and b/lightrag/api/webui/assets/index-Bz28HSH8.js differ
|
|
lightrag/api/webui/assets/index-Cl6-O9yL.css
ADDED
Binary file (55.1 kB). View file
|
|
lightrag/api/webui/index.html
CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
|
|
lightrag_webui/src/api/lightrag.ts
CHANGED
@@ -109,7 +109,7 @@ export type QueryResponse = {
|
|
109 |
}
|
110 |
|
111 |
export type DocActionResponse = {
|
112 |
-
status: 'success' | 'partial_success' | 'failure'
|
113 |
message: string
|
114 |
}
|
115 |
|
|
|
109 |
}
|
110 |
|
111 |
export type DocActionResponse = {
|
112 |
+
status: 'success' | 'partial_success' | 'failure' | 'duplicated'
|
113 |
message: string
|
114 |
}
|
115 |
|
lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import { useState, useCallback } from 'react'
|
|
|
2 |
import Button from '@/components/ui/Button'
|
3 |
import {
|
4 |
Dialog,
|
@@ -23,57 +24,132 @@ export default function UploadDocumentsDialog() {
|
|
23 |
const [progresses, setProgresses] = useState<Record<string, number>>({})
|
24 |
const [fileErrors, setFileErrors] = useState<Record<string, string>>({})
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
const handleDocumentsUpload = useCallback(
|
27 |
async (filesToUpload: File[]) => {
|
28 |
setIsUploading(true)
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
try {
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
34 |
try {
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
}
|
63 |
-
})
|
64 |
-
{
|
65 |
-
loading: t('documentPanel.uploadDocuments.batch.uploading'),
|
66 |
-
success: t('documentPanel.uploadDocuments.batch.success'),
|
67 |
-
error: t('documentPanel.uploadDocuments.batch.error')
|
68 |
-
}
|
69 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
} catch (err) {
|
71 |
-
|
|
|
72 |
} finally {
|
73 |
setIsUploading(false)
|
74 |
}
|
75 |
},
|
76 |
-
[setIsUploading, setProgresses, t]
|
77 |
)
|
78 |
|
79 |
return (
|
@@ -107,6 +183,7 @@ export default function UploadDocumentsDialog() {
|
|
107 |
maxSize={200 * 1024 * 1024}
|
108 |
description={t('documentPanel.uploadDocuments.fileTypes')}
|
109 |
onUpload={handleDocumentsUpload}
|
|
|
110 |
progresses={progresses}
|
111 |
fileErrors={fileErrors}
|
112 |
disabled={isUploading}
|
|
|
1 |
import { useState, useCallback } from 'react'
|
2 |
+
import { FileRejection } from 'react-dropzone'
|
3 |
import Button from '@/components/ui/Button'
|
4 |
import {
|
5 |
Dialog,
|
|
|
24 |
const [progresses, setProgresses] = useState<Record<string, number>>({})
|
25 |
const [fileErrors, setFileErrors] = useState<Record<string, string>>({})
|
26 |
|
27 |
+
const handleRejectedFiles = useCallback(
|
28 |
+
(rejectedFiles: FileRejection[]) => {
|
29 |
+
// Process rejected files and add them to fileErrors
|
30 |
+
rejectedFiles.forEach(({ file, errors }) => {
|
31 |
+
// Get the first error message
|
32 |
+
let errorMsg = errors[0]?.message || t('documentPanel.uploadDocuments.fileUploader.fileRejected', { name: file.name })
|
33 |
+
|
34 |
+
// Simplify error message for unsupported file types
|
35 |
+
if (errorMsg.includes('file-invalid-type')) {
|
36 |
+
errorMsg = t('documentPanel.uploadDocuments.fileUploader.unsupportedType')
|
37 |
+
}
|
38 |
+
|
39 |
+
// Set progress to 100% to display error message
|
40 |
+
setProgresses((pre) => ({
|
41 |
+
...pre,
|
42 |
+
[file.name]: 100
|
43 |
+
}))
|
44 |
+
|
45 |
+
// Add error message to fileErrors
|
46 |
+
setFileErrors(prev => ({
|
47 |
+
...prev,
|
48 |
+
[file.name]: errorMsg
|
49 |
+
}))
|
50 |
+
})
|
51 |
+
},
|
52 |
+
[setProgresses, setFileErrors, t]
|
53 |
+
)
|
54 |
+
|
55 |
const handleDocumentsUpload = useCallback(
|
56 |
async (filesToUpload: File[]) => {
|
57 |
setIsUploading(true)
|
58 |
+
|
59 |
+
// Only clear errors for files that are being uploaded, keep errors for rejected files
|
60 |
+
setFileErrors(prev => {
|
61 |
+
const newErrors = { ...prev };
|
62 |
+
filesToUpload.forEach(file => {
|
63 |
+
delete newErrors[file.name];
|
64 |
+
});
|
65 |
+
return newErrors;
|
66 |
+
});
|
67 |
+
|
68 |
+
// Show uploading toast
|
69 |
+
const toastId = toast.loading(t('documentPanel.uploadDocuments.batch.uploading'))
|
70 |
|
71 |
try {
|
72 |
+
// Track errors locally to ensure we have the final state
|
73 |
+
const uploadErrors: Record<string, string> = {}
|
74 |
+
|
75 |
+
await Promise.all(
|
76 |
+
filesToUpload.map(async (file) => {
|
77 |
try {
|
78 |
+
// Initialize upload progress
|
79 |
+
setProgresses((pre) => ({
|
80 |
+
...pre,
|
81 |
+
[file.name]: 0
|
82 |
+
}))
|
83 |
+
|
84 |
+
const result = await uploadDocument(file, (percentCompleted: number) => {
|
85 |
+
console.debug(t('documentPanel.uploadDocuments.single.uploading', { name: file.name, percent: percentCompleted }))
|
86 |
+
setProgresses((pre) => ({
|
87 |
+
...pre,
|
88 |
+
[file.name]: percentCompleted
|
89 |
+
}))
|
90 |
+
})
|
91 |
+
|
92 |
+
if (result.status === 'duplicated') {
|
93 |
+
uploadErrors[file.name] = t('documentPanel.uploadDocuments.fileUploader.duplicateFile')
|
94 |
+
setFileErrors(prev => ({
|
95 |
+
...prev,
|
96 |
+
[file.name]: t('documentPanel.uploadDocuments.fileUploader.duplicateFile')
|
97 |
+
}))
|
98 |
+
} else if (result.status !== 'success') {
|
99 |
+
uploadErrors[file.name] = result.message
|
100 |
+
setFileErrors(prev => ({
|
101 |
+
...prev,
|
102 |
+
[file.name]: result.message
|
103 |
+
}))
|
104 |
+
}
|
105 |
+
} catch (err) {
|
106 |
+
console.error(`Upload failed for ${file.name}:`, err)
|
107 |
+
|
108 |
+
// Handle HTTP errors, including 400 errors
|
109 |
+
let errorMsg = errorMessage(err)
|
110 |
+
|
111 |
+
// If it's an axios error with response data, try to extract more detailed error info
|
112 |
+
if (err && typeof err === 'object' && 'response' in err) {
|
113 |
+
const axiosError = err as { response?: { status: number, data?: { detail?: string } } }
|
114 |
+
if (axiosError.response?.status === 400) {
|
115 |
+
// Extract specific error message from backend response
|
116 |
+
errorMsg = axiosError.response.data?.detail || errorMsg
|
117 |
+
}
|
118 |
+
|
119 |
+
// Set progress to 100% to display error message
|
120 |
+
setProgresses((pre) => ({
|
121 |
+
...pre,
|
122 |
+
[file.name]: 100
|
123 |
+
}))
|
124 |
+
}
|
125 |
+
|
126 |
+
// Record error message in both local tracking and state
|
127 |
+
uploadErrors[file.name] = errorMsg
|
128 |
+
setFileErrors(prev => ({
|
129 |
+
...prev,
|
130 |
+
[file.name]: errorMsg
|
131 |
+
}))
|
132 |
}
|
133 |
+
})
|
|
|
|
|
|
|
|
|
|
|
134 |
)
|
135 |
+
|
136 |
+
// Check if any files failed to upload using our local tracking
|
137 |
+
const hasErrors = Object.keys(uploadErrors).length > 0
|
138 |
+
|
139 |
+
// Update toast status
|
140 |
+
if (hasErrors) {
|
141 |
+
toast.error(t('documentPanel.uploadDocuments.batch.error'), { id: toastId })
|
142 |
+
} else {
|
143 |
+
toast.success(t('documentPanel.uploadDocuments.batch.success'), { id: toastId })
|
144 |
+
}
|
145 |
} catch (err) {
|
146 |
+
console.error('Unexpected error during upload:', err)
|
147 |
+
toast.error(t('documentPanel.uploadDocuments.generalError', { error: errorMessage(err) }), { id: toastId })
|
148 |
} finally {
|
149 |
setIsUploading(false)
|
150 |
}
|
151 |
},
|
152 |
+
[setIsUploading, setProgresses, setFileErrors, t]
|
153 |
)
|
154 |
|
155 |
return (
|
|
|
183 |
maxSize={200 * 1024 * 1024}
|
184 |
description={t('documentPanel.uploadDocuments.fileTypes')}
|
185 |
onUpload={handleDocumentsUpload}
|
186 |
+
onReject={handleRejectedFiles}
|
187 |
progresses={progresses}
|
188 |
fileErrors={fileErrors}
|
189 |
disabled={isUploading}
|
lightrag_webui/src/components/ui/FileUploader.tsx
CHANGED
@@ -39,6 +39,14 @@ interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
39 |
*/
|
40 |
onUpload?: (files: File[]) => Promise<void>
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
/**
|
43 |
* Progress of the uploaded files.
|
44 |
* @type Record<string, number> | undefined
|
@@ -125,6 +133,7 @@ function FileUploader(props: FileUploaderProps) {
|
|
125 |
value: valueProp,
|
126 |
onValueChange,
|
127 |
onUpload,
|
|
|
128 |
progresses,
|
129 |
fileErrors,
|
130 |
accept = supportedFileTypes,
|
@@ -144,38 +153,77 @@ function FileUploader(props: FileUploaderProps) {
|
|
144 |
|
145 |
const onDrop = React.useCallback(
|
146 |
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
147 |
-
|
|
|
|
|
|
|
|
|
148 |
toast.error(t('documentPanel.uploadDocuments.fileUploader.singleFileLimit'))
|
149 |
return
|
150 |
}
|
151 |
|
152 |
-
if (
|
153 |
toast.error(t('documentPanel.uploadDocuments.fileUploader.maxFilesLimit', { count: maxFileCount }))
|
154 |
return
|
155 |
}
|
156 |
|
157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
Object.assign(file, {
|
159 |
preview: URL.createObjectURL(file)
|
160 |
})
|
161 |
)
|
162 |
|
163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
|
|
|
165 |
setFiles(updatedFiles)
|
166 |
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
172 |
|
173 |
-
|
174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
}
|
176 |
},
|
177 |
-
|
178 |
-
[files, maxFileCount, multiple, onUpload, setFiles, t]
|
179 |
)
|
180 |
|
181 |
function onRemove(index: number) {
|
@@ -204,11 +252,39 @@ function FileUploader(props: FileUploaderProps) {
|
|
204 |
<div className="relative flex flex-col gap-6 overflow-hidden">
|
205 |
<Dropzone
|
206 |
onDrop={onDrop}
|
207 |
-
accept
|
|
|
|
|
208 |
maxSize={maxSize}
|
209 |
maxFiles={maxFileCount}
|
210 |
multiple={maxFileCount > 1 || multiple}
|
211 |
disabled={isDisabled}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
>
|
213 |
{({ getRootProps, getInputProps, isDragActive }) => (
|
214 |
<div
|
@@ -279,18 +355,21 @@ function FileUploader(props: FileUploaderProps) {
|
|
279 |
interface ProgressProps {
|
280 |
value: number
|
281 |
error?: boolean
|
|
|
282 |
}
|
283 |
|
284 |
function Progress({ value, error }: ProgressProps) {
|
285 |
return (
|
286 |
-
<div className="h-2 w-full
|
287 |
-
<div
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
|
|
|
|
294 |
</div>
|
295 |
)
|
296 |
}
|
@@ -307,16 +386,22 @@ function FileCard({ file, progress, error, onRemove }: FileCardProps) {
|
|
307 |
return (
|
308 |
<div className="relative flex items-center gap-2.5">
|
309 |
<div className="flex flex-1 gap-2.5">
|
310 |
-
{
|
|
|
|
|
|
|
|
|
311 |
<div className="flex w-full flex-col gap-2">
|
312 |
<div className="flex flex-col gap-px">
|
313 |
<p className="text-foreground/80 line-clamp-1 text-sm font-medium">{file.name}</p>
|
314 |
<p className="text-muted-foreground text-xs">{formatBytes(file.size)}</p>
|
315 |
</div>
|
316 |
{error ? (
|
317 |
-
<div className="text-
|
318 |
-
<
|
319 |
-
|
|
|
|
|
320 |
</div>
|
321 |
) : (
|
322 |
progress ? <Progress value={progress} /> : null
|
|
|
39 |
*/
|
40 |
onUpload?: (files: File[]) => Promise<void>
|
41 |
|
42 |
+
/**
|
43 |
+
* Function to be called when files are rejected.
|
44 |
+
* @type (rejections: FileRejection[]) => void
|
45 |
+
* @default undefined
|
46 |
+
* @example onReject={(rejections) => handleRejectedFiles(rejections)}
|
47 |
+
*/
|
48 |
+
onReject?: (rejections: FileRejection[]) => void
|
49 |
+
|
50 |
/**
|
51 |
* Progress of the uploaded files.
|
52 |
* @type Record<string, number> | undefined
|
|
|
133 |
value: valueProp,
|
134 |
onValueChange,
|
135 |
onUpload,
|
136 |
+
onReject,
|
137 |
progresses,
|
138 |
fileErrors,
|
139 |
accept = supportedFileTypes,
|
|
|
153 |
|
154 |
const onDrop = React.useCallback(
|
155 |
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
156 |
+
// Calculate total file count including both accepted and rejected files
|
157 |
+
const totalFileCount = (files?.length ?? 0) + acceptedFiles.length + rejectedFiles.length
|
158 |
+
|
159 |
+
// Check file count limits
|
160 |
+
if (!multiple && maxFileCount === 1 && (acceptedFiles.length + rejectedFiles.length) > 1) {
|
161 |
toast.error(t('documentPanel.uploadDocuments.fileUploader.singleFileLimit'))
|
162 |
return
|
163 |
}
|
164 |
|
165 |
+
if (totalFileCount > maxFileCount) {
|
166 |
toast.error(t('documentPanel.uploadDocuments.fileUploader.maxFilesLimit', { count: maxFileCount }))
|
167 |
return
|
168 |
}
|
169 |
|
170 |
+
// Handle rejected files first - this will set error states
|
171 |
+
if (rejectedFiles.length > 0) {
|
172 |
+
if (onReject) {
|
173 |
+
// Use the onReject callback if provided
|
174 |
+
onReject(rejectedFiles)
|
175 |
+
} else {
|
176 |
+
// Fall back to toast notifications if no callback is provided
|
177 |
+
rejectedFiles.forEach(({ file }) => {
|
178 |
+
toast.error(t('documentPanel.uploadDocuments.fileUploader.fileRejected', { name: file.name }))
|
179 |
+
})
|
180 |
+
}
|
181 |
+
}
|
182 |
+
|
183 |
+
// Process accepted files
|
184 |
+
const newAcceptedFiles = acceptedFiles.map((file) =>
|
185 |
Object.assign(file, {
|
186 |
preview: URL.createObjectURL(file)
|
187 |
})
|
188 |
)
|
189 |
|
190 |
+
// Process rejected files for UI display
|
191 |
+
const newRejectedFiles = rejectedFiles.map(({ file }) =>
|
192 |
+
Object.assign(file, {
|
193 |
+
preview: URL.createObjectURL(file),
|
194 |
+
rejected: true
|
195 |
+
})
|
196 |
+
)
|
197 |
+
|
198 |
+
// Combine all files for display
|
199 |
+
const allNewFiles = [...newAcceptedFiles, ...newRejectedFiles]
|
200 |
+
const updatedFiles = files ? [...files, ...allNewFiles] : allNewFiles
|
201 |
|
202 |
+
// Update the files state with all files
|
203 |
setFiles(updatedFiles)
|
204 |
|
205 |
+
// Only upload accepted files - make sure we're not uploading rejected files
|
206 |
+
if (onUpload && acceptedFiles.length > 0) {
|
207 |
+
// Filter out any files that might have been rejected by our custom validator
|
208 |
+
const validFiles = acceptedFiles.filter(file => {
|
209 |
+
// Check if file type is accepted
|
210 |
+
const fileExt = `.${file.name.split('.').pop()?.toLowerCase() || ''}`;
|
211 |
+
const isAccepted = Object.entries(accept || {}).some(([mimeType, extensions]) => {
|
212 |
+
return file.type === mimeType || extensions.includes(fileExt);
|
213 |
+
});
|
214 |
|
215 |
+
// Check file size
|
216 |
+
const isSizeValid = file.size <= maxSize;
|
217 |
+
|
218 |
+
return isAccepted && isSizeValid;
|
219 |
+
});
|
220 |
+
|
221 |
+
if (validFiles.length > 0) {
|
222 |
+
onUpload(validFiles);
|
223 |
+
}
|
224 |
}
|
225 |
},
|
226 |
+
[files, maxFileCount, multiple, onUpload, onReject, setFiles, t, accept, maxSize]
|
|
|
227 |
)
|
228 |
|
229 |
function onRemove(index: number) {
|
|
|
252 |
<div className="relative flex flex-col gap-6 overflow-hidden">
|
253 |
<Dropzone
|
254 |
onDrop={onDrop}
|
255 |
+
// remove accept,use customizd validator
|
256 |
+
noClick={false}
|
257 |
+
noKeyboard={false}
|
258 |
maxSize={maxSize}
|
259 |
maxFiles={maxFileCount}
|
260 |
multiple={maxFileCount > 1 || multiple}
|
261 |
disabled={isDisabled}
|
262 |
+
validator={(file) => {
|
263 |
+
// Check if file type is accepted
|
264 |
+
const fileExt = `.${file.name.split('.').pop()?.toLowerCase() || ''}`;
|
265 |
+
const isAccepted = Object.entries(accept || {}).some(([mimeType, extensions]) => {
|
266 |
+
return file.type === mimeType || extensions.includes(fileExt);
|
267 |
+
});
|
268 |
+
|
269 |
+
if (!isAccepted) {
|
270 |
+
return {
|
271 |
+
code: 'file-invalid-type',
|
272 |
+
message: t('documentPanel.uploadDocuments.fileUploader.unsupportedType')
|
273 |
+
};
|
274 |
+
}
|
275 |
+
|
276 |
+
// Check file size
|
277 |
+
if (file.size > maxSize) {
|
278 |
+
return {
|
279 |
+
code: 'file-too-large',
|
280 |
+
message: t('documentPanel.uploadDocuments.fileUploader.fileTooLarge', {
|
281 |
+
maxSize: formatBytes(maxSize)
|
282 |
+
})
|
283 |
+
};
|
284 |
+
}
|
285 |
+
|
286 |
+
return null;
|
287 |
+
}}
|
288 |
>
|
289 |
{({ getRootProps, getInputProps, isDragActive }) => (
|
290 |
<div
|
|
|
355 |
interface ProgressProps {
|
356 |
value: number
|
357 |
error?: boolean
|
358 |
+
showIcon?: boolean // New property to control icon display
|
359 |
}
|
360 |
|
361 |
function Progress({ value, error }: ProgressProps) {
|
362 |
return (
|
363 |
+
<div className="relative h-2 w-full">
|
364 |
+
<div className="h-full w-full overflow-hidden rounded-full bg-secondary">
|
365 |
+
<div
|
366 |
+
className={cn(
|
367 |
+
'h-full transition-all',
|
368 |
+
error ? 'bg-red-400' : 'bg-primary'
|
369 |
+
)}
|
370 |
+
style={{ width: `${value}%` }}
|
371 |
+
/>
|
372 |
+
</div>
|
373 |
</div>
|
374 |
)
|
375 |
}
|
|
|
386 |
return (
|
387 |
<div className="relative flex items-center gap-2.5">
|
388 |
<div className="flex flex-1 gap-2.5">
|
389 |
+
{error ? (
|
390 |
+
<FileText className="text-red-400 size-10" aria-hidden="true" />
|
391 |
+
) : (
|
392 |
+
isFileWithPreview(file) ? <FilePreview file={file} /> : null
|
393 |
+
)}
|
394 |
<div className="flex w-full flex-col gap-2">
|
395 |
<div className="flex flex-col gap-px">
|
396 |
<p className="text-foreground/80 line-clamp-1 text-sm font-medium">{file.name}</p>
|
397 |
<p className="text-muted-foreground text-xs">{formatBytes(file.size)}</p>
|
398 |
</div>
|
399 |
{error ? (
|
400 |
+
<div className="text-red-400 text-sm">
|
401 |
+
<div className="relative mb-2">
|
402 |
+
<Progress value={100} error={true} />
|
403 |
+
</div>
|
404 |
+
<p>{error}</p>
|
405 |
</div>
|
406 |
) : (
|
407 |
progress ? <Progress value={progress} /> : null
|
lightrag_webui/src/locales/ar.json
CHANGED
@@ -65,10 +65,13 @@
|
|
65 |
"singleFileLimit": "لا يمكن رفع أكثر من ملف واحد في المرة الواحدة",
|
66 |
"maxFilesLimit": "لا يمكن رفع أكثر من {{count}} ملفات",
|
67 |
"fileRejected": "تم رفض الملف {{name}}",
|
|
|
|
|
68 |
"dropHere": "أفلت الملفات هنا",
|
69 |
"dragAndDrop": "اسحب وأفلت الملفات هنا، أو انقر للاختيار",
|
70 |
"removeFile": "إزالة الملف",
|
71 |
-
"uploadDescription": "يمكنك رفع {{isMultiple ? 'عدة' : count}} ملفات (حتى {{maxSize}} لكل منها)"
|
|
|
72 |
}
|
73 |
},
|
74 |
"documentManager": {
|
|
|
65 |
"singleFileLimit": "لا يمكن رفع أكثر من ملف واحد في المرة الواحدة",
|
66 |
"maxFilesLimit": "لا يمكن رفع أكثر من {{count}} ملفات",
|
67 |
"fileRejected": "تم رفض الملف {{name}}",
|
68 |
+
"unsupportedType": "نوع الملف غير مدعوم",
|
69 |
+
"fileTooLarge": "حجم الملف كبير جدًا، الحد الأقصى {{maxSize}}",
|
70 |
"dropHere": "أفلت الملفات هنا",
|
71 |
"dragAndDrop": "اسحب وأفلت الملفات هنا، أو انقر للاختيار",
|
72 |
"removeFile": "إزالة الملف",
|
73 |
+
"uploadDescription": "يمكنك رفع {{isMultiple ? 'عدة' : count}} ملفات (حتى {{maxSize}} لكل منها)",
|
74 |
+
"duplicateFile": "اسم الملف موجود بالفعل في ذاكرة التخزين المؤقت للخادم"
|
75 |
}
|
76 |
},
|
77 |
"documentManager": {
|
lightrag_webui/src/locales/en.json
CHANGED
@@ -65,10 +65,13 @@
|
|
65 |
"singleFileLimit": "Cannot upload more than 1 file at a time",
|
66 |
"maxFilesLimit": "Cannot upload more than {{count}} files",
|
67 |
"fileRejected": "File {{name}} was rejected",
|
|
|
|
|
68 |
"dropHere": "Drop the files here",
|
69 |
"dragAndDrop": "Drag and drop files here, or click to select files",
|
70 |
"removeFile": "Remove file",
|
71 |
-
"uploadDescription": "You can upload {{isMultiple ? 'multiple' : count}} files (up to {{maxSize}} each)"
|
|
|
72 |
}
|
73 |
},
|
74 |
"documentManager": {
|
|
|
65 |
"singleFileLimit": "Cannot upload more than 1 file at a time",
|
66 |
"maxFilesLimit": "Cannot upload more than {{count}} files",
|
67 |
"fileRejected": "File {{name}} was rejected",
|
68 |
+
"unsupportedType": "Unsupported file type",
|
69 |
+
"fileTooLarge": "File too large, maximum size is {{maxSize}}",
|
70 |
"dropHere": "Drop the files here",
|
71 |
"dragAndDrop": "Drag and drop files here, or click to select files",
|
72 |
"removeFile": "Remove file",
|
73 |
+
"uploadDescription": "You can upload {{isMultiple ? 'multiple' : count}} files (up to {{maxSize}} each)",
|
74 |
+
"duplicateFile": "File name already exists in server cache"
|
75 |
}
|
76 |
},
|
77 |
"documentManager": {
|
lightrag_webui/src/locales/fr.json
CHANGED
@@ -65,10 +65,13 @@
|
|
65 |
"singleFileLimit": "Impossible de télécharger plus d'un fichier à la fois",
|
66 |
"maxFilesLimit": "Impossible de télécharger plus de {{count}} fichiers",
|
67 |
"fileRejected": "Le fichier {{name}} a été rejeté",
|
|
|
|
|
68 |
"dropHere": "Déposez les fichiers ici",
|
69 |
"dragAndDrop": "Glissez et déposez les fichiers ici, ou cliquez pour sélectionner",
|
70 |
"removeFile": "Supprimer le fichier",
|
71 |
-
"uploadDescription": "Vous pouvez télécharger {{isMultiple ? 'plusieurs' : count}} fichiers (jusqu'à {{maxSize}} chacun)"
|
|
|
72 |
}
|
73 |
},
|
74 |
"documentManager": {
|
|
|
65 |
"singleFileLimit": "Impossible de télécharger plus d'un fichier à la fois",
|
66 |
"maxFilesLimit": "Impossible de télécharger plus de {{count}} fichiers",
|
67 |
"fileRejected": "Le fichier {{name}} a été rejeté",
|
68 |
+
"unsupportedType": "Type de fichier non pris en charge",
|
69 |
+
"fileTooLarge": "Fichier trop volumineux, taille maximale {{maxSize}}",
|
70 |
"dropHere": "Déposez les fichiers ici",
|
71 |
"dragAndDrop": "Glissez et déposez les fichiers ici, ou cliquez pour sélectionner",
|
72 |
"removeFile": "Supprimer le fichier",
|
73 |
+
"uploadDescription": "Vous pouvez télécharger {{isMultiple ? 'plusieurs' : count}} fichiers (jusqu'à {{maxSize}} chacun)",
|
74 |
+
"duplicateFile": "Le nom du fichier existe déjà dans le cache du serveur"
|
75 |
}
|
76 |
},
|
77 |
"documentManager": {
|
lightrag_webui/src/locales/zh.json
CHANGED
@@ -65,10 +65,13 @@
|
|
65 |
"singleFileLimit": "一次只能上传一个文件",
|
66 |
"maxFilesLimit": "最多只能上传 {{count}} 个文件",
|
67 |
"fileRejected": "文件 {{name}} 被拒绝",
|
|
|
|
|
68 |
"dropHere": "将文件拖放到此处",
|
69 |
"dragAndDrop": "拖放文件到此处,或点击选择文件",
|
70 |
"removeFile": "移除文件",
|
71 |
-
"uploadDescription": "您可以上传{{isMultiple ? '多个' : count}}个文件(每个文件最大{{maxSize}})"
|
|
|
72 |
}
|
73 |
},
|
74 |
"documentManager": {
|
|
|
65 |
"singleFileLimit": "一次只能上传一个文件",
|
66 |
"maxFilesLimit": "最多只能上传 {{count}} 个文件",
|
67 |
"fileRejected": "文件 {{name}} 被拒绝",
|
68 |
+
"unsupportedType": "不支持的文件类型",
|
69 |
+
"fileTooLarge": "文件过大,最大允许 {{maxSize}}",
|
70 |
"dropHere": "将文件拖放到此处",
|
71 |
"dragAndDrop": "拖放文件到此处,或点击选择文件",
|
72 |
"removeFile": "移除文件",
|
73 |
+
"uploadDescription": "您可以上传{{isMultiple ? '多个' : count}}个文件(每个文件最大{{maxSize}})",
|
74 |
+
"duplicateFile": "文件名与服务器上的缓存重复"
|
75 |
}
|
76 |
},
|
77 |
"documentManager": {
|