Daniel.y commited on
Commit
b66d9e3
·
unverified ·
2 Parent(s): 602ff41 666e20e

Merge pull request #1214 from danielaskdd/upload-error

Browse files
lightrag/api/__init__.py CHANGED
@@ -1 +1 @@
1
- __api_version__ = "1.2.7"
 
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
- setFileErrors({})
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  try {
32
- toast.promise(
33
- (async () => {
 
 
 
34
  try {
35
- await Promise.all(
36
- filesToUpload.map(async (file) => {
37
- try {
38
- const result = await uploadDocument(file, (percentCompleted: number) => {
39
- console.debug(t('documentPanel.uploadDocuments.single.uploading', { name: file.name, percent: percentCompleted }))
40
- setProgresses((pre) => ({
41
- ...pre,
42
- [file.name]: percentCompleted
43
- }))
44
- })
45
-
46
- if (result.status !== 'success') {
47
- setFileErrors(prev => ({
48
- ...prev,
49
- [file.name]: result.message
50
- }))
51
- }
52
- } catch (err) {
53
- setFileErrors(prev => ({
54
- ...prev,
55
- [file.name]: errorMessage(err)
56
- }))
57
- }
58
- })
59
- )
60
- } catch (error) {
61
- console.error('Upload failed:', error)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- toast.error(t('documentPanel.uploadDocuments.generalError', { error: errorMessage(err) }))
 
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
- if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
 
 
 
 
148
  toast.error(t('documentPanel.uploadDocuments.fileUploader.singleFileLimit'))
149
  return
150
  }
151
 
152
- if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
153
  toast.error(t('documentPanel.uploadDocuments.fileUploader.maxFilesLimit', { count: maxFileCount }))
154
  return
155
  }
156
 
157
- const newFiles = acceptedFiles.map((file) =>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  Object.assign(file, {
159
  preview: URL.createObjectURL(file)
160
  })
161
  )
162
 
163
- const updatedFiles = files ? [...files, ...newFiles] : newFiles
 
 
 
 
 
 
 
 
 
 
164
 
 
165
  setFiles(updatedFiles)
166
 
167
- if (rejectedFiles.length > 0) {
168
- rejectedFiles.forEach(({ file }) => {
169
- toast.error(t('documentPanel.uploadDocuments.fileUploader.fileRejected', { name: file.name }))
170
- })
171
- }
 
 
 
 
172
 
173
- if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {
174
- onUpload(updatedFiles)
 
 
 
 
 
 
 
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={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 overflow-hidden rounded-full bg-secondary">
287
- <div
288
- className={cn(
289
- 'h-full transition-all',
290
- error ? 'bg-destructive' : 'bg-primary'
291
- )}
292
- style={{ width: `${value}%` }}
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
- {isFileWithPreview(file) ? <FilePreview file={file} /> : null}
 
 
 
 
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-destructive text-sm">
318
- <Progress value={100} error={true} />
319
- <p className="mt-1">{error}</p>
 
 
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": {