Merge pull request #1193 from danielaskdd/file-upload-improve
Feat: Improve user experience of multiple files upload
This commit is contained in:
File diff suppressed because one or more lines are too long
2
lightrag/api/webui/index.html
generated
2
lightrag/api/webui/index.html
generated
@@ -8,7 +8,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="logo.png" />
|
<link rel="icon" type="image/svg+xml" href="logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Lightrag</title>
|
<title>Lightrag</title>
|
||||||
<script type="module" crossorigin src="/webui/assets/index-BX3dHkLt.js"></script>
|
<script type="module" crossorigin src="/webui/assets/index-DTDDxtXc.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-CbzkrOyx.css">
|
<link rel="stylesheet" crossorigin href="/webui/assets/index-CbzkrOyx.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@@ -21,49 +21,72 @@ export default function UploadDocumentsDialog() {
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [isUploading, setIsUploading] = useState(false)
|
const [isUploading, setIsUploading] = useState(false)
|
||||||
const [progresses, setProgresses] = useState<Record<string, number>>({})
|
const [progresses, setProgresses] = useState<Record<string, number>>({})
|
||||||
|
const [fileErrors, setFileErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
const handleDocumentsUpload = useCallback(
|
const handleDocumentsUpload = useCallback(
|
||||||
async (filesToUpload: File[]) => {
|
async (filesToUpload: File[]) => {
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
|
setFileErrors({})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
toast.promise(
|
||||||
filesToUpload.map(async (file) => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const result = await uploadDocument(file, (percentCompleted: number) => {
|
await Promise.all(
|
||||||
console.debug(t('documentPanel.uploadDocuments.uploading', { name: file.name, percent: percentCompleted }))
|
filesToUpload.map(async (file) => {
|
||||||
setProgresses((pre) => ({
|
try {
|
||||||
...pre,
|
const result = await uploadDocument(file, (percentCompleted: number) => {
|
||||||
[file.name]: percentCompleted
|
console.debug(t('documentPanel.uploadDocuments.single.uploading', { name: file.name, percent: percentCompleted }))
|
||||||
}))
|
setProgresses((pre) => ({
|
||||||
})
|
...pre,
|
||||||
if (result.status === 'success') {
|
[file.name]: percentCompleted
|
||||||
toast.success(t('documentPanel.uploadDocuments.success', { name: file.name }))
|
}))
|
||||||
} else {
|
})
|
||||||
toast.error(t('documentPanel.uploadDocuments.failed', { name: file.name, message: result.message }))
|
|
||||||
}
|
if (result.status !== 'success') {
|
||||||
} catch (err) {
|
setFileErrors(prev => ({
|
||||||
toast.error(t('documentPanel.uploadDocuments.error', { name: file.name, error: errorMessage(err) }))
|
...prev,
|
||||||
|
[file.name]: result.message
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setFileErrors(prev => ({
|
||||||
|
...prev,
|
||||||
|
[file.name]: errorMessage(err)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload failed:', error)
|
||||||
}
|
}
|
||||||
})
|
})(),
|
||||||
|
{
|
||||||
|
loading: t('documentPanel.uploadDocuments.batch.uploading'),
|
||||||
|
success: t('documentPanel.uploadDocuments.batch.success'),
|
||||||
|
error: t('documentPanel.uploadDocuments.batch.error')
|
||||||
|
}
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(t('documentPanel.uploadDocuments.generalError', { error: errorMessage(err) }))
|
toast.error(t('documentPanel.uploadDocuments.generalError', { error: errorMessage(err) }))
|
||||||
} finally {
|
} finally {
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
// setOpen(false)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setIsUploading, setProgresses]
|
[setIsUploading, setProgresses, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (isUploading && !open) {
|
if (isUploading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!open) {
|
||||||
|
setProgresses({})
|
||||||
|
setFileErrors({})
|
||||||
|
}
|
||||||
setOpen(open)
|
setOpen(open)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -85,6 +108,7 @@ export default function UploadDocumentsDialog() {
|
|||||||
description={t('documentPanel.uploadDocuments.fileTypes')}
|
description={t('documentPanel.uploadDocuments.fileTypes')}
|
||||||
onUpload={handleDocumentsUpload}
|
onUpload={handleDocumentsUpload}
|
||||||
progresses={progresses}
|
progresses={progresses}
|
||||||
|
fileErrors={fileErrors}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
@@ -6,11 +6,11 @@ import * as React from 'react'
|
|||||||
import { FileText, Upload, X } from 'lucide-react'
|
import { FileText, Upload, X } from 'lucide-react'
|
||||||
import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone'
|
import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useControllableState } from '@radix-ui/react-use-controllable-state'
|
import { useControllableState } from '@radix-ui/react-use-controllable-state'
|
||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
import Progress from '@/components/ui/Progress'
|
|
||||||
import { ScrollArea } from '@/components/ui/ScrollArea'
|
import { ScrollArea } from '@/components/ui/ScrollArea'
|
||||||
import { supportedFileTypes } from '@/lib/constants'
|
import { supportedFileTypes } from '@/lib/constants'
|
||||||
|
|
||||||
@@ -47,6 +47,14 @@ interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
*/
|
*/
|
||||||
progresses?: Record<string, number>
|
progresses?: Record<string, number>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error messages for failed uploads.
|
||||||
|
* @type Record<string, string> | undefined
|
||||||
|
* @default undefined
|
||||||
|
* @example fileErrors={{ "file1.png": "Upload failed" }}
|
||||||
|
*/
|
||||||
|
fileErrors?: Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepted file types for the uploader.
|
* Accepted file types for the uploader.
|
||||||
* @type { [key: string]: string[]}
|
* @type { [key: string]: string[]}
|
||||||
@@ -112,11 +120,13 @@ function formatBytes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FileUploader(props: FileUploaderProps) {
|
function FileUploader(props: FileUploaderProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
value: valueProp,
|
value: valueProp,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
onUpload,
|
onUpload,
|
||||||
progresses,
|
progresses,
|
||||||
|
fileErrors,
|
||||||
accept = supportedFileTypes,
|
accept = supportedFileTypes,
|
||||||
maxSize = 1024 * 1024 * 200,
|
maxSize = 1024 * 1024 * 200,
|
||||||
maxFileCount = 1,
|
maxFileCount = 1,
|
||||||
@@ -135,12 +145,12 @@ function FileUploader(props: FileUploaderProps) {
|
|||||||
const onDrop = React.useCallback(
|
const onDrop = React.useCallback(
|
||||||
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
||||||
if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
|
if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
|
||||||
toast.error('Cannot upload more than 1 file at a time')
|
toast.error(t('documentPanel.uploadDocuments.fileUploader.singleFileLimit'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
|
if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
|
||||||
toast.error(`Cannot upload more than ${maxFileCount} files`)
|
toast.error(t('documentPanel.uploadDocuments.fileUploader.maxFilesLimit', { count: maxFileCount }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,25 +166,16 @@ function FileUploader(props: FileUploaderProps) {
|
|||||||
|
|
||||||
if (rejectedFiles.length > 0) {
|
if (rejectedFiles.length > 0) {
|
||||||
rejectedFiles.forEach(({ file }) => {
|
rejectedFiles.forEach(({ file }) => {
|
||||||
toast.error(`File ${file.name} was rejected`)
|
toast.error(t('documentPanel.uploadDocuments.fileUploader.fileRejected', { name: file.name }))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {
|
if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {
|
||||||
const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : 'file'
|
onUpload(updatedFiles)
|
||||||
|
|
||||||
toast.promise(onUpload(updatedFiles), {
|
|
||||||
loading: `Uploading ${target}...`,
|
|
||||||
success: () => {
|
|
||||||
setFiles([])
|
|
||||||
return `${target} uploaded`
|
|
||||||
},
|
|
||||||
error: `Failed to upload ${target}`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[files, maxFileCount, multiple, onUpload, setFiles]
|
[files, maxFileCount, multiple, onUpload, setFiles, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
function onRemove(index: number) {
|
function onRemove(index: number) {
|
||||||
@@ -227,7 +228,7 @@ function FileUploader(props: FileUploaderProps) {
|
|||||||
<div className="rounded-full border border-dashed p-3">
|
<div className="rounded-full border border-dashed p-3">
|
||||||
<Upload className="text-muted-foreground size-7" aria-hidden="true" />
|
<Upload className="text-muted-foreground size-7" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground font-medium">Drop the files here</p>
|
<p className="text-muted-foreground font-medium">{t('documentPanel.uploadDocuments.fileUploader.dropHere')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
||||||
@@ -236,18 +237,18 @@ function FileUploader(props: FileUploaderProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-px">
|
<div className="flex flex-col gap-px">
|
||||||
<p className="text-muted-foreground font-medium">
|
<p className="text-muted-foreground font-medium">
|
||||||
Drag and drop files here, or click to select files
|
{t('documentPanel.uploadDocuments.fileUploader.dragAndDrop')}
|
||||||
</p>
|
</p>
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="text-muted-foreground/70 text-sm">{description}</p>
|
<p className="text-muted-foreground/70 text-sm">{description}</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-muted-foreground/70 text-sm">
|
<p className="text-muted-foreground/70 text-sm">
|
||||||
You can upload
|
{t('documentPanel.uploadDocuments.fileUploader.uploadDescription', {
|
||||||
{maxFileCount > 1
|
count: maxFileCount,
|
||||||
? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}
|
isMultiple: maxFileCount === Infinity,
|
||||||
files (up to ${formatBytes(maxSize)} each)`
|
maxSize: formatBytes(maxSize)
|
||||||
: ` a file with ${formatBytes(maxSize)}`}
|
})}
|
||||||
Supported formats: TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS
|
{t('documentPanel.uploadDocuments.fileTypes')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -265,6 +266,7 @@ function FileUploader(props: FileUploaderProps) {
|
|||||||
file={file}
|
file={file}
|
||||||
onRemove={() => onRemove(index)}
|
onRemove={() => onRemove(index)}
|
||||||
progress={progresses?.[file.name]}
|
progress={progresses?.[file.name]}
|
||||||
|
error={fileErrors?.[file.name]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -274,13 +276,34 @@ function FileUploader(props: FileUploaderProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProgressProps {
|
||||||
|
value: number
|
||||||
|
error?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function Progress({ value, error }: ProgressProps) {
|
||||||
|
return (
|
||||||
|
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'h-full transition-all',
|
||||||
|
error ? 'bg-destructive' : 'bg-primary'
|
||||||
|
)}
|
||||||
|
style={{ width: `${value}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface FileCardProps {
|
interface FileCardProps {
|
||||||
file: File
|
file: File
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
progress?: number
|
progress?: number
|
||||||
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileCard({ file, progress, onRemove }: FileCardProps) {
|
function FileCard({ file, progress, error, onRemove }: FileCardProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center gap-2.5">
|
<div className="relative flex items-center gap-2.5">
|
||||||
<div className="flex flex-1 gap-2.5">
|
<div className="flex flex-1 gap-2.5">
|
||||||
@@ -290,13 +313,20 @@ function FileCard({ file, progress, onRemove }: FileCardProps) {
|
|||||||
<p className="text-foreground/80 line-clamp-1 text-sm font-medium">{file.name}</p>
|
<p className="text-foreground/80 line-clamp-1 text-sm font-medium">{file.name}</p>
|
||||||
<p className="text-muted-foreground text-xs">{formatBytes(file.size)}</p>
|
<p className="text-muted-foreground text-xs">{formatBytes(file.size)}</p>
|
||||||
</div>
|
</div>
|
||||||
{progress ? <Progress value={progress} /> : null}
|
{error ? (
|
||||||
|
<div className="text-destructive text-sm">
|
||||||
|
<Progress value={100} error={true} />
|
||||||
|
<p className="mt-1">{error}</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
progress ? <Progress value={progress} /> : null
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button type="button" variant="outline" size="icon" className="size-7" onClick={onRemove}>
|
<Button type="button" variant="outline" size="icon" className="size-7" onClick={onRemove}>
|
||||||
<X className="size-4" aria-hidden="true" />
|
<X className="size-4" aria-hidden="true" />
|
||||||
<span className="sr-only">Remove file</span>
|
<span className="sr-only">{t('documentPanel.uploadDocuments.fileUploader.removeFile')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -48,12 +48,28 @@
|
|||||||
"tooltip": "رفع المستندات",
|
"tooltip": "رفع المستندات",
|
||||||
"title": "رفع المستندات",
|
"title": "رفع المستندات",
|
||||||
"description": "اسحب وأفلت مستنداتك هنا أو انقر للتصفح.",
|
"description": "اسحب وأفلت مستنداتك هنا أو انقر للتصفح.",
|
||||||
"uploading": "جارٍ الرفع {{name}}: {{percent}}%",
|
"single": {
|
||||||
"success": "نجاح الرفع:\nتم رفع {{name}} بنجاح",
|
"uploading": "جارٍ الرفع {{name}}: {{percent}}%",
|
||||||
"failed": "فشل الرفع:\n{{name}}\n{{message}}",
|
"success": "نجاح الرفع:\nتم رفع {{name}} بنجاح",
|
||||||
"error": "فشل الرفع:\n{{name}}\n{{error}}",
|
"failed": "فشل الرفع:\n{{name}}\n{{message}}",
|
||||||
|
"error": "فشل الرفع:\n{{name}}\n{{error}}"
|
||||||
|
},
|
||||||
|
"batch": {
|
||||||
|
"uploading": "جارٍ رفع الملفات...",
|
||||||
|
"success": "تم رفع الملفات بنجاح",
|
||||||
|
"error": "فشل رفع بعض الملفات"
|
||||||
|
},
|
||||||
"generalError": "فشل الرفع\n{{error}}",
|
"generalError": "فشل الرفع\n{{error}}",
|
||||||
"fileTypes": "الأنواع المدعومة: TXT، MD، DOCX، PDF، PPTX، RTF، ODT، EPUB، HTML، HTM، TEX، JSON، XML، YAML، YML، CSV، LOG، CONF، INI، PROPERTIES، SQL، BAT، SH، C، CPP، PY، JAVA، JS، TS، SWIFT، GO، RB، PHP، CSS، SCSS، LESS"
|
"fileTypes": "الأنواع المدعومة: TXT، MD، DOCX، PDF، PPTX، RTF، ODT، EPUB، HTML، HTM، TEX، JSON، XML، YAML، YML، CSV، LOG، CONF، INI، PROPERTIES، SQL، BAT، SH، C، CPP، PY، JAVA، JS، TS، SWIFT، GO، RB، PHP، CSS، SCSS، LESS",
|
||||||
|
"fileUploader": {
|
||||||
|
"singleFileLimit": "لا يمكن رفع أكثر من ملف واحد في المرة الواحدة",
|
||||||
|
"maxFilesLimit": "لا يمكن رفع أكثر من {{count}} ملفات",
|
||||||
|
"fileRejected": "تم رفض الملف {{name}}",
|
||||||
|
"dropHere": "أفلت الملفات هنا",
|
||||||
|
"dragAndDrop": "اسحب وأفلت الملفات هنا، أو انقر للاختيار",
|
||||||
|
"removeFile": "إزالة الملف",
|
||||||
|
"uploadDescription": "يمكنك رفع {{isMultiple ? 'عدة' : count}} ملفات (حتى {{maxSize}} لكل منها)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"documentManager": {
|
"documentManager": {
|
||||||
"title": "إدارة المستندات",
|
"title": "إدارة المستندات",
|
||||||
|
@@ -48,12 +48,28 @@
|
|||||||
"tooltip": "Upload documents",
|
"tooltip": "Upload documents",
|
||||||
"title": "Upload Documents",
|
"title": "Upload Documents",
|
||||||
"description": "Drag and drop your documents here or click to browse.",
|
"description": "Drag and drop your documents here or click to browse.",
|
||||||
"uploading": "Uploading {{name}}: {{percent}}%",
|
"single": {
|
||||||
"success": "Upload Success:\n{{name}} uploaded successfully",
|
"uploading": "Uploading {{name}}: {{percent}}%",
|
||||||
"failed": "Upload Failed:\n{{name}}\n{{message}}",
|
"success": "Upload Success:\n{{name}} uploaded successfully",
|
||||||
"error": "Upload Failed:\n{{name}}\n{{error}}",
|
"failed": "Upload Failed:\n{{name}}\n{{message}}",
|
||||||
|
"error": "Upload Failed:\n{{name}}\n{{error}}"
|
||||||
|
},
|
||||||
|
"batch": {
|
||||||
|
"uploading": "Uploading files...",
|
||||||
|
"success": "Files uploaded successfully",
|
||||||
|
"error": "Some files failed to upload"
|
||||||
|
},
|
||||||
"generalError": "Upload Failed\n{{error}}",
|
"generalError": "Upload Failed\n{{error}}",
|
||||||
"fileTypes": "Supported types: TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS"
|
"fileTypes": "Supported types: TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS",
|
||||||
|
"fileUploader": {
|
||||||
|
"singleFileLimit": "Cannot upload more than 1 file at a time",
|
||||||
|
"maxFilesLimit": "Cannot upload more than {{count}} files",
|
||||||
|
"fileRejected": "File {{name}} was rejected",
|
||||||
|
"dropHere": "Drop the files here",
|
||||||
|
"dragAndDrop": "Drag and drop files here, or click to select files",
|
||||||
|
"removeFile": "Remove file",
|
||||||
|
"uploadDescription": "You can upload {{isMultiple ? 'multiple' : count}} files (up to {{maxSize}} each)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"documentManager": {
|
"documentManager": {
|
||||||
"title": "Document Management",
|
"title": "Document Management",
|
||||||
@@ -95,15 +111,15 @@
|
|||||||
"pipelineStatus": {
|
"pipelineStatus": {
|
||||||
"title": "Pipeline Status",
|
"title": "Pipeline Status",
|
||||||
"busy": "Pipeline Busy",
|
"busy": "Pipeline Busy",
|
||||||
"requestPending": "Reques Pending",
|
"requestPending": "Request Pending",
|
||||||
"jobName": "Job Name",
|
"jobName": "Job Name",
|
||||||
"startTime": "Start Time",
|
"startTime": "Start Time",
|
||||||
"progress": "Progress",
|
"progress": "Progress",
|
||||||
"unit": "batch",
|
"unit": "batch",
|
||||||
"latestMessage": "Latest Message",
|
"latestMessage": "Latest Message",
|
||||||
"historyMessages": "History Message",
|
"historyMessages": "History Messages",
|
||||||
"errors": {
|
"errors": {
|
||||||
"fetchFailed": "Fail to get pipeline status\n{{error}}"
|
"fetchFailed": "Failed to get pipeline status\n{{error}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -48,12 +48,28 @@
|
|||||||
"tooltip": "Télécharger des documents",
|
"tooltip": "Télécharger des documents",
|
||||||
"title": "Télécharger des documents",
|
"title": "Télécharger des documents",
|
||||||
"description": "Glissez-déposez vos documents ici ou cliquez pour parcourir.",
|
"description": "Glissez-déposez vos documents ici ou cliquez pour parcourir.",
|
||||||
"uploading": "Téléchargement de {{name}} : {{percent}}%",
|
"single": {
|
||||||
"success": "Succès du téléchargement :\n{{name}} téléchargé avec succès",
|
"uploading": "Téléchargement de {{name}} : {{percent}}%",
|
||||||
"failed": "Échec du téléchargement :\n{{name}}\n{{message}}",
|
"success": "Succès du téléchargement :\n{{name}} téléchargé avec succès",
|
||||||
"error": "Échec du téléchargement :\n{{name}}\n{{error}}",
|
"failed": "Échec du téléchargement :\n{{name}}\n{{message}}",
|
||||||
|
"error": "Échec du téléchargement :\n{{name}}\n{{error}}"
|
||||||
|
},
|
||||||
|
"batch": {
|
||||||
|
"uploading": "Téléchargement des fichiers...",
|
||||||
|
"success": "Fichiers téléchargés avec succès",
|
||||||
|
"error": "Certains fichiers n'ont pas pu être téléchargés"
|
||||||
|
},
|
||||||
"generalError": "Échec du téléchargement\n{{error}}",
|
"generalError": "Échec du téléchargement\n{{error}}",
|
||||||
"fileTypes": "Types pris en charge : TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS"
|
"fileTypes": "Types pris en charge : TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS",
|
||||||
|
"fileUploader": {
|
||||||
|
"singleFileLimit": "Impossible de télécharger plus d'un fichier à la fois",
|
||||||
|
"maxFilesLimit": "Impossible de télécharger plus de {{count}} fichiers",
|
||||||
|
"fileRejected": "Le fichier {{name}} a été rejeté",
|
||||||
|
"dropHere": "Déposez les fichiers ici",
|
||||||
|
"dragAndDrop": "Glissez et déposez les fichiers ici, ou cliquez pour sélectionner",
|
||||||
|
"removeFile": "Supprimer le fichier",
|
||||||
|
"uploadDescription": "Vous pouvez télécharger {{isMultiple ? 'plusieurs' : count}} fichiers (jusqu'à {{maxSize}} chacun)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"documentManager": {
|
"documentManager": {
|
||||||
"title": "Gestion des documents",
|
"title": "Gestion des documents",
|
||||||
|
@@ -48,12 +48,28 @@
|
|||||||
"tooltip": "上传文档",
|
"tooltip": "上传文档",
|
||||||
"title": "上传文档",
|
"title": "上传文档",
|
||||||
"description": "拖拽文件到此处或点击浏览",
|
"description": "拖拽文件到此处或点击浏览",
|
||||||
"uploading": "正在上传 {{name}}:{{percent}}%",
|
"single": {
|
||||||
"success": "上传成功:\n{{name}} 上传完成",
|
"uploading": "正在上传 {{name}}:{{percent}}%",
|
||||||
"failed": "上传失败:\n{{name}}\n{{message}}",
|
"success": "上传成功:\n{{name}} 上传完成",
|
||||||
"error": "上传失败:\n{{name}}\n{{error}}",
|
"failed": "上传失败:\n{{name}}\n{{message}}",
|
||||||
|
"error": "上传失败:\n{{name}}\n{{error}}"
|
||||||
|
},
|
||||||
|
"batch": {
|
||||||
|
"uploading": "正在上传文件...",
|
||||||
|
"success": "文件上传完成",
|
||||||
|
"error": "部分文件上传失败"
|
||||||
|
},
|
||||||
"generalError": "上传失败\n{{error}}",
|
"generalError": "上传失败\n{{error}}",
|
||||||
"fileTypes": "支持的文件类型:TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS"
|
"fileTypes": "支持的文件类型:TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS",
|
||||||
|
"fileUploader": {
|
||||||
|
"singleFileLimit": "一次只能上传一个文件",
|
||||||
|
"maxFilesLimit": "最多只能上传 {{count}} 个文件",
|
||||||
|
"fileRejected": "文件 {{name}} 被拒绝",
|
||||||
|
"dropHere": "将文件拖放到此处",
|
||||||
|
"dragAndDrop": "拖放文件到此处,或点击选择文件",
|
||||||
|
"removeFile": "移除文件",
|
||||||
|
"uploadDescription": "您可以上传{{isMultiple ? '多个' : count}}个文件(每个文件最大{{maxSize}})"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"documentManager": {
|
"documentManager": {
|
||||||
"title": "文档管理",
|
"title": "文档管理",
|
||||||
|
Reference in New Issue
Block a user