mirror of
https://gitee.com/dify_ai/dify.git
synced 2025-12-07 03:45:27 +08:00
Compare commits
119 Commits
feat/add-q
...
feat/files
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6c08ca6ac | ||
|
|
586c90c984 | ||
|
|
511de822d7 | ||
|
|
93f8fe8406 | ||
|
|
3f9af702a2 | ||
|
|
68eb3126e7 | ||
|
|
6cebdb3ccb | ||
|
|
dd1edab89b | ||
|
|
b668d06ca9 | ||
|
|
03c69feda4 | ||
|
|
76f9694fae | ||
|
|
6634274151 | ||
|
|
ba1fa57dc3 | ||
|
|
1d860cd85b | ||
|
|
d658ea8a8b | ||
|
|
4c727d0de1 | ||
|
|
a7cf7d6f41 | ||
|
|
af5190d017 | ||
|
|
ea36f95c81 | ||
|
|
477030330c | ||
|
|
476e2c5157 | ||
|
|
598336b209 | ||
|
|
fefa3482de | ||
|
|
58d769b34e | ||
|
|
3e6320fc11 | ||
|
|
a090767491 | ||
|
|
9da3cc6c2e | ||
|
|
9e84233029 | ||
|
|
147ad5b579 | ||
|
|
5c355f4406 | ||
|
|
f571d7e728 | ||
|
|
fa3522dcef | ||
|
|
780f4c72f8 | ||
|
|
80a3d5772d | ||
|
|
779312a3f9 | ||
|
|
26ad00fff9 | ||
|
|
c6918fbcab | ||
|
|
10b9688689 | ||
|
|
110b5b015f | ||
|
|
6f9fd44589 | ||
|
|
1b3028d7fb | ||
|
|
3cbd57f392 | ||
|
|
4a4ed8c5bd | ||
|
|
677aec942a | ||
|
|
9291325128 | ||
|
|
75e302eb93 | ||
|
|
3cf48c8f64 | ||
|
|
ecf421d241 | ||
|
|
32985421fa | ||
|
|
5e6dc98788 | ||
|
|
aaa0a8d150 | ||
|
|
0789642a32 | ||
|
|
cc9955189e | ||
|
|
b1a0f150f1 | ||
|
|
0c51b42350 | ||
|
|
4354b6803b | ||
|
|
44fcd1c1ef | ||
|
|
47b299f52e | ||
|
|
0cbb3c1508 | ||
|
|
2b9f394a22 | ||
|
|
56bebe04e3 | ||
|
|
703075b10c | ||
|
|
1d9f1fdbdb | ||
|
|
7e11aab412 | ||
|
|
ff594cdcd1 | ||
|
|
4b039eadc6 | ||
|
|
3c589c44c6 | ||
|
|
94b112fc6b | ||
|
|
1f37a3a7ea | ||
|
|
686e2efe96 | ||
|
|
d965a280c1 | ||
|
|
8f55352c7b | ||
|
|
cd04225419 | ||
|
|
1ee00cba97 | ||
|
|
6b884ab253 | ||
|
|
7104fba05d | ||
|
|
111813c7c2 | ||
|
|
68daf79f12 | ||
|
|
cca2ba95c1 | ||
|
|
270c3d7b6b | ||
|
|
5097aa229a | ||
|
|
2809406ab9 | ||
|
|
6e5b66ddca | ||
|
|
e4d52c508c | ||
|
|
7fc97ae7a1 | ||
|
|
d8d27a49b7 | ||
|
|
3b2b819985 | ||
|
|
761febc788 | ||
|
|
4814d6ea0d | ||
|
|
62a5a1ea4f | ||
|
|
ed5b80e97a | ||
|
|
651781f227 | ||
|
|
7241253a36 | ||
|
|
d3713c5122 | ||
|
|
c7cbe3ba66 | ||
|
|
ef56fe3176 | ||
|
|
8a05f53ca1 | ||
|
|
c9487caf52 | ||
|
|
581e906897 | ||
|
|
b41a476620 | ||
|
|
e0c1d94fcc | ||
|
|
e08af0a69c | ||
|
|
a1aee4e714 | ||
|
|
a842f1c251 | ||
|
|
6cc4f47d4f | ||
|
|
a5a6119709 | ||
|
|
c77029e63e | ||
|
|
ff1aadf9d3 | ||
|
|
8c6f5addfd | ||
|
|
1268f3bbfe | ||
|
|
42c4ab7344 | ||
|
|
f1f5d45d2e | ||
|
|
e2ef272f48 | ||
|
|
201d9943bb | ||
|
|
3a98c636fd | ||
|
|
dbd2babb05 | ||
|
|
d834deceff | ||
|
|
6e725e2ed4 | ||
|
|
f7539ce4c5 |
@@ -1,5 +1,5 @@
|
||||
.fileUploader {
|
||||
@apply mb-9;
|
||||
@apply mb-6;
|
||||
}
|
||||
.fileUploader .title {
|
||||
@apply mb-2;
|
||||
@@ -9,14 +9,14 @@
|
||||
color: #344054;
|
||||
}
|
||||
.fileUploader .tip {
|
||||
@apply mt-2;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 26px;
|
||||
line-height: 18px;
|
||||
color: #667085;
|
||||
}
|
||||
.uploader {
|
||||
@apply relative box-border flex justify-center items-center;
|
||||
@apply relative box-border flex justify-center items-center mb-2;
|
||||
flex-direction: column;
|
||||
max-width: 640px;
|
||||
height: 80px;
|
||||
background: #F9FAFB;
|
||||
@@ -25,7 +25,7 @@
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #667085;
|
||||
color: #344054;
|
||||
}
|
||||
.uploader.dragging {
|
||||
background: #F5F8FF;
|
||||
@@ -38,8 +38,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.uploader::before {
|
||||
content: '';
|
||||
.uploader .uploadIcon {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
width: 24px;
|
||||
@@ -53,14 +52,16 @@
|
||||
}
|
||||
|
||||
.file {
|
||||
@apply box-border relative flex items-center;
|
||||
padding: 21px 24px 21px 64px;
|
||||
@apply box-border relative flex items-center justify-between;
|
||||
padding: 8px 12px 8px 8px;
|
||||
max-width: 640px;
|
||||
height: 80px;
|
||||
background: #F9FAFB;
|
||||
border: 1px solid #F2F4F7;
|
||||
border-radius: 12px;
|
||||
height: 40px;
|
||||
background: #ffffff;
|
||||
border: 0.5px solid #EAECF0;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.progressbar {
|
||||
position: absolute;
|
||||
@@ -69,36 +70,25 @@
|
||||
height: 100%;
|
||||
background-color: #F2F4F7;
|
||||
}
|
||||
.file:hover {
|
||||
background: #F5F8FF;
|
||||
border: 1px solid #D1E0FF;
|
||||
}
|
||||
.file:hover .actionWrapper .buttonWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.file:hover .actionWrapper .divider {
|
||||
display: block;
|
||||
}
|
||||
.file.uploading,
|
||||
.file.uploading:hover {
|
||||
background: #FCFCFD;
|
||||
border: 1px solid #EAECF0;
|
||||
border: 0.5px solid #EAECF0;
|
||||
}
|
||||
.file.uploading:hover .actionWrapper .percent {
|
||||
padding: 8px;
|
||||
.file.active {
|
||||
background: #F5F8FF;
|
||||
border: 1px solid #D1E0FF;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
}
|
||||
.file.uploading:hover .actionWrapper .buttonWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.file:hover {
|
||||
background: #F5F8FF;
|
||||
border: 1px solid #D1E0FF;
|
||||
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
.fileIcon {
|
||||
@apply w-8 h-8 bg-center bg-no-repeat;
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 24px;
|
||||
@apply shrink-0 w-6 h-6 mr-2 bg-center bg-no-repeat;
|
||||
background-image: url(../assets/unknow.svg);
|
||||
background-size: 32px;
|
||||
background-size: 24px;
|
||||
}
|
||||
.fileIcon.csv {
|
||||
background-image: url(../assets/csv.svg);
|
||||
@@ -126,7 +116,7 @@
|
||||
background-image: url(../assets/json.svg);
|
||||
}
|
||||
.fileInfo {
|
||||
@apply grow;
|
||||
@apply grow flex items-center;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -134,46 +124,35 @@
|
||||
}
|
||||
.filename {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.name {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #1D2939;
|
||||
line-height: 20px;
|
||||
}
|
||||
.extension {
|
||||
color: #667085;
|
||||
line-height: 20px;
|
||||
}
|
||||
.fileExtraInfo {
|
||||
color: #667085;
|
||||
.size {
|
||||
@apply ml-3;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #667085;
|
||||
}
|
||||
.actionWrapper {
|
||||
@apply flex items-center shrink-0;
|
||||
z-index: 1;
|
||||
}
|
||||
.actionWrapper .percent {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #344054;
|
||||
}
|
||||
.actionWrapper .divider {
|
||||
display: none;
|
||||
margin: 0 8px;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: #FEE4E2;
|
||||
}
|
||||
.actionWrapper .remove {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: center no-repeat url(../assets/trash.svg);
|
||||
background-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.actionWrapper .buttonWrapper {
|
||||
@apply flex items-center;
|
||||
display: none;
|
||||
.file:hover .actionWrapper .remove {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,15 @@ import cn from 'classnames'
|
||||
import s from './index.module.css'
|
||||
import type { File as FileEntity } from '@/models/datasets'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
import { upload } from '@/service/base'
|
||||
|
||||
type IFileUploaderProps = {
|
||||
file?: FileEntity
|
||||
fileList: FileEntity[]
|
||||
prepareFileList: (files: any[]) => void
|
||||
onFileUpdate: (fileItem: any, progress: number, list: any[]) => void
|
||||
onPreview: (file: FileEntity) => void
|
||||
titleClassName?: string
|
||||
onFileUpdate: (file?: FileEntity) => void
|
||||
}
|
||||
|
||||
const ACCEPTS = [
|
||||
@@ -29,8 +30,15 @@ const ACCEPTS = [
|
||||
]
|
||||
|
||||
const MAX_SIZE = 15 * 1024 * 1024
|
||||
const BATCH_COUNT = 5
|
||||
|
||||
const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps) => {
|
||||
const FileUploader = ({
|
||||
fileList,
|
||||
prepareFileList,
|
||||
onFileUpdate,
|
||||
onPreview,
|
||||
titleClassName,
|
||||
}: IFileUploaderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const [dragging, setDragging] = useState(false)
|
||||
@@ -42,6 +50,9 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [percent, setPercent] = useState(0)
|
||||
|
||||
// TODO
|
||||
const fileListRef = useRef<any>(null)
|
||||
|
||||
// utils
|
||||
const getFileType = (currentFile: File) => {
|
||||
if (!currentFile)
|
||||
@@ -50,10 +61,6 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
const arr = currentFile.name.split('.')
|
||||
return arr[arr.length - 1]
|
||||
}
|
||||
const getFileName = (name: string) => {
|
||||
const arr = name.split('.')
|
||||
return arr.slice(0, -1).join()
|
||||
}
|
||||
const getFileSize = (size: number) => {
|
||||
if (size / 1024 < 10)
|
||||
return `${(size / 1024).toFixed(2)}KB`
|
||||
@@ -77,6 +84,7 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
const onProgress = useCallback((e: ProgressEvent) => {
|
||||
if (e.lengthComputable) {
|
||||
const percent = Math.floor(e.loaded / e.total * 100)
|
||||
// updateFileItem
|
||||
setPercent(percent)
|
||||
}
|
||||
}, [setPercent])
|
||||
@@ -84,42 +92,93 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
const currentXHR = uploadPromise.current
|
||||
currentXHR.abort()
|
||||
}
|
||||
const fileUpload = async (file?: File) => {
|
||||
if (!file)
|
||||
return
|
||||
const selectHandle = () => {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.click()
|
||||
}
|
||||
// TODO
|
||||
const removeFile = () => {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.value = ''
|
||||
|
||||
if (!isValid(file))
|
||||
return
|
||||
setCurrentFile(undefined)
|
||||
// onFileUpdate()
|
||||
}
|
||||
|
||||
setCurrentFile(file)
|
||||
setUploading(true)
|
||||
const fileUpload = (fileItem: any) => {
|
||||
const fileListCopy = fileListRef.current
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
// store for abort
|
||||
const currentXHR = new XMLHttpRequest()
|
||||
uploadPromise.current = currentXHR
|
||||
try {
|
||||
const result = await upload({
|
||||
xhr: currentXHR,
|
||||
data: formData,
|
||||
onprogress: onProgress,
|
||||
}) as FileEntity
|
||||
onFileUpdate(result)
|
||||
setUploading(false)
|
||||
}
|
||||
catch (xhr: any) {
|
||||
setUploading(false)
|
||||
// abort handle
|
||||
if (xhr.readyState === 0 && xhr.status === 0) {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.value = ''
|
||||
|
||||
setCurrentFile(undefined)
|
||||
return
|
||||
formData.append('file', fileItem.file)
|
||||
const onProgress = (e: ProgressEvent) => {
|
||||
if (e.lengthComputable) {
|
||||
const percent = Math.floor(e.loaded / e.total * 100)
|
||||
onFileUpdate(fileItem, percent, fileListCopy)
|
||||
}
|
||||
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') })
|
||||
}
|
||||
console.log('fff1', fileListCopy)
|
||||
|
||||
return upload({
|
||||
xhr: new XMLHttpRequest(),
|
||||
data: formData,
|
||||
onprogress: onProgress,
|
||||
})
|
||||
.then((res: FileEntity) => {
|
||||
const completeFile = {
|
||||
fileID: fileItem.fileID,
|
||||
file: res,
|
||||
}
|
||||
console.log('fff2', fileListCopy)
|
||||
onFileUpdate(completeFile, 100, fileListCopy)
|
||||
return Promise.resolve({ ...completeFile })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') })
|
||||
onFileUpdate(fileItem, -2, fileListCopy)
|
||||
return Promise.resolve({ ...fileItem })
|
||||
})
|
||||
.finally()
|
||||
}
|
||||
const uploadBatchFiles = (bFiles: any) => {
|
||||
bFiles.forEach((bf: any) => (bf.progress = 0))
|
||||
return Promise.all(bFiles.map((bFile: any) => fileUpload(bFile)))
|
||||
}
|
||||
const uploadMultipleFiles = async (files: any) => {
|
||||
const length = files.length
|
||||
let start = 0
|
||||
let end = 0
|
||||
|
||||
while (start < length) {
|
||||
if (start + BATCH_COUNT > length)
|
||||
end = length
|
||||
else
|
||||
end = start + BATCH_COUNT
|
||||
const bFiles = files.slice(start, end)
|
||||
await uploadBatchFiles(bFiles)
|
||||
start = end
|
||||
}
|
||||
}
|
||||
const initialUpload = (files: any) => {
|
||||
if (!files.length)
|
||||
return false
|
||||
const preparedFiles = files.map((file: any, index: number) => {
|
||||
const fileItem = {
|
||||
fileID: `file${index}-${Date.now()}`,
|
||||
file,
|
||||
progress: -1,
|
||||
}
|
||||
return fileItem
|
||||
})
|
||||
prepareFileList(preparedFiles)
|
||||
// TODO fix filelist copy
|
||||
fileListRef.current = preparedFiles
|
||||
uploadMultipleFiles(preparedFiles)
|
||||
}
|
||||
const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = [...(e.target.files ?? [])].filter(file => isValid(file))
|
||||
initialUpload(files)
|
||||
}
|
||||
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -134,6 +193,7 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
e.stopPropagation()
|
||||
e.target === dragRef.current && setDragging(false)
|
||||
}
|
||||
// TODO
|
||||
const handleDrop = (e: DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -150,23 +210,6 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
fileUpload(files[0])
|
||||
}
|
||||
|
||||
const selectHandle = () => {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.click()
|
||||
}
|
||||
const removeFile = () => {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.value = ''
|
||||
|
||||
setCurrentFile(undefined)
|
||||
onFileUpdate()
|
||||
}
|
||||
const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const currentFile = e.target.files?.[0]
|
||||
onFileUpdate()
|
||||
fileUpload(currentFile)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dropRef.current?.addEventListener('dragenter', handleDragEnter)
|
||||
dropRef.current?.addEventListener('dragover', handleDragOver)
|
||||
@@ -184,83 +227,107 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
|
||||
<div className={s.fileUploader}>
|
||||
<input
|
||||
ref={fileUploader}
|
||||
id="fileUploader"
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
id="fileUploader"
|
||||
multiple
|
||||
accept={ACCEPTS.join(',')}
|
||||
onChange={fileChangeHandle}
|
||||
/>
|
||||
<div className={cn(s.title, titleClassName)}>{t('datasetCreation.stepOne.uploader.title')}</div>
|
||||
<div ref={dropRef}>
|
||||
{!currentFile && !file && (
|
||||
<div className={cn(s.uploader, dragging && s.dragging)}>
|
||||
<span>{t('datasetCreation.stepOne.uploader.button')}</span>
|
||||
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
|
||||
{dragging && <div ref={dragRef} className={s.draggingCover}/>}
|
||||
<div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}>
|
||||
<div className='flex justify-center items-center h-6 mb-2'>
|
||||
<span className={s.uploadIcon}/>
|
||||
<span>{t('datasetCreation.stepOne.uploader.button')}</span>
|
||||
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
|
||||
</div>
|
||||
<div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip')}</div>
|
||||
{dragging && <div ref={dragRef} className={s.draggingCover}/>}
|
||||
</div>
|
||||
<div className={s.fileList}>
|
||||
{fileList.map(fileItem => (
|
||||
<div
|
||||
// onClick={() => onPreview(currentFile)}
|
||||
className={cn(
|
||||
s.file,
|
||||
fileItem.progress < 100 && s.uploading,
|
||||
// s.active,
|
||||
)}
|
||||
>
|
||||
{fileItem.progress < 100 && (
|
||||
<div className={s.progressbar} style={{ width: `${percent}%` }}/>
|
||||
)}
|
||||
<div className={s.fileInfo}>
|
||||
<div className={cn(s.fileIcon, s[getFileType(fileItem.file)])}/>
|
||||
<div className={s.filename}>{fileItem.file.name}</div>
|
||||
<div className={s.size}>{getFileSize(fileItem.file.size)}</div>
|
||||
</div>
|
||||
<div className={s.actionWrapper}>
|
||||
{fileItem.progress < 100 && (
|
||||
<div className={s.percent}>{`${fileItem.progress}%`}</div>
|
||||
)}
|
||||
{fileItem.progress === 100 && (
|
||||
<div className={s.remove} onClick={removeFile}/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{currentFile && (
|
||||
<div
|
||||
// onClick={() => onPreview(currentFile)}
|
||||
className={cn(
|
||||
s.file,
|
||||
uploading && s.uploading,
|
||||
// s.active,
|
||||
)}
|
||||
>
|
||||
{uploading && (
|
||||
<div className={s.progressbar} style={{ width: `${percent}%` }}/>
|
||||
)}
|
||||
<div className={s.fileInfo}>
|
||||
<div className={cn(s.fileIcon, s[getFileType(currentFile)])}/>
|
||||
<div className={s.filename}>{currentFile.name}</div>
|
||||
<div className={s.size}>{getFileSize(currentFile.size)}</div>
|
||||
</div>
|
||||
<div className={s.actionWrapper}>
|
||||
{uploading && (
|
||||
<div className={s.percent}>{`${percent}%`}</div>
|
||||
)}
|
||||
{!uploading && (
|
||||
<div className={s.remove} onClick={removeFile}/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{currentFile && (
|
||||
<div className={cn(s.file, uploading && s.uploading)}>
|
||||
{/* TODO */}
|
||||
{false && !currentFile && fileList[0] && (
|
||||
<div
|
||||
// onClick={() => onPreview(currentFile)}
|
||||
className={cn(
|
||||
s.file,
|
||||
uploading && s.uploading,
|
||||
s.active,
|
||||
)}
|
||||
>
|
||||
{uploading && (
|
||||
<div className={s.progressbar} style={{ width: `${percent}%` }}/>
|
||||
)}
|
||||
<div className={cn(s.fileIcon, s[getFileType(currentFile)])}/>
|
||||
<div className={s.fileInfo}>
|
||||
<div className={s.filename}>
|
||||
<span className={s.name}>{getFileName(currentFile.name)}</span>
|
||||
<span className={s.extension}>{`.${getFileType(currentFile)}`}</span>
|
||||
</div>
|
||||
<div className={s.fileExtraInfo}>
|
||||
<span className={s.size}>{getFileSize(currentFile.size)}</span>
|
||||
<span className={s.error}></span>
|
||||
</div>
|
||||
<div className={cn(s.fileIcon, s[getFileType(fileList[0])])}/>
|
||||
<div className={s.filename}>{fileList[0].name}</div>
|
||||
<div className={s.size}>{getFileSize(fileList[0].size)}</div>
|
||||
</div>
|
||||
<div className={s.actionWrapper}>
|
||||
{uploading && (
|
||||
<>
|
||||
<div className={s.percent}>{`${percent}%`}</div>
|
||||
<div className={s.divider}/>
|
||||
<div className={s.buttonWrapper}>
|
||||
<Button className={cn(s.button, 'ml-2 !h-8 bg-white')} onClick={abort}>{t('datasetCreation.stepOne.uploader.cancel')}</Button>
|
||||
</div>
|
||||
</>
|
||||
<div className={s.percent}>{`${percent}%`}</div>
|
||||
)}
|
||||
{!uploading && (
|
||||
<>
|
||||
<div className={s.buttonWrapper}>
|
||||
<Button className={cn(s.button, 'ml-2 !h-8 bg-white')} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
|
||||
<div className={s.divider}/>
|
||||
<div className={s.remove} onClick={removeFile}/>
|
||||
</div>
|
||||
</>
|
||||
<div className={s.remove} onClick={removeFile}/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!currentFile && file && (
|
||||
<div className={cn(s.file)}>
|
||||
<div className={cn(s.fileIcon, s[file.extension])}/>
|
||||
<div className={s.fileInfo}>
|
||||
<div className={s.filename}>
|
||||
<span className={s.name}>{getFileName(file.name)}</span>
|
||||
<span className={s.extension}>{`.${file.extension}`}</span>
|
||||
</div>
|
||||
<div className={s.fileExtraInfo}>
|
||||
<span className={s.size}>{getFileSize(file.size)}</span>
|
||||
<span className={s.error}></span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.actionWrapper}>
|
||||
<div className={s.buttonWrapper}>
|
||||
<Button className={cn(s.button, 'ml-2 !h-8 bg-white')} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
|
||||
<div className={s.divider}/>
|
||||
<div className={s.remove} onClick={removeFile}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import StepOne from './step-one'
|
||||
import StepTwo from './step-two'
|
||||
import StepThree from './step-three'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import type { DataSet, File, createDocumentResponse } from '@/models/datasets'
|
||||
import type { DataSet, createDocumentResponse } from '@/models/datasets'
|
||||
import { fetchDataSource, fetchTenantInfo } from '@/service/common'
|
||||
import { fetchDataDetail } from '@/service/datasets'
|
||||
import type { DataSourceNotionPage } from '@/models/common'
|
||||
@@ -30,7 +30,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
||||
const [dataSourceType, setDataSourceType] = useState<DataSourceType>(DataSourceType.FILE)
|
||||
const [step, setStep] = useState(1)
|
||||
const [indexingTypeCache, setIndexTypeCache] = useState('')
|
||||
const [file, setFile] = useState<File | undefined>()
|
||||
const [fileList, setFiles] = useState<any[]>([])
|
||||
const [result, setResult] = useState<createDocumentResponse | undefined>()
|
||||
const [hasError, setHasError] = useState(false)
|
||||
|
||||
@@ -39,9 +39,20 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
||||
setNotionPages(value)
|
||||
}
|
||||
|
||||
const updateFile = (file?: File) => {
|
||||
setFile(file)
|
||||
// TODO
|
||||
const updateFileList = (preparedFiles: any) => {
|
||||
console.log('preparedFiles', preparedFiles)
|
||||
setFiles(preparedFiles)
|
||||
}
|
||||
const updateFile = (fileItem: any, progress: number, list: any[]) => {
|
||||
const targetIndex = list.findIndex((file: any) => file.fileID === fileItem.fileID)
|
||||
list[targetIndex] = {
|
||||
...fileItem,
|
||||
progress,
|
||||
}
|
||||
setFiles([...list])
|
||||
}
|
||||
|
||||
const updateIndexingTypeCache = (type: string) => {
|
||||
setIndexTypeCache(type)
|
||||
}
|
||||
@@ -104,7 +115,8 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
||||
dataSourceType={dataSourceType}
|
||||
dataSourceTypeDisable={!!detail?.data_source_type}
|
||||
changeType={setDataSourceType}
|
||||
file={file}
|
||||
files={fileList}
|
||||
updateFileList={updateFileList}
|
||||
updateFile={updateFile}
|
||||
notionPages={notionPages}
|
||||
updateNotionPages={updateNotionPages}
|
||||
@@ -116,7 +128,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
||||
indexingType={detail?.indexing_technique || ''}
|
||||
datasetId={datasetId}
|
||||
dataSourceType={dataSourceType}
|
||||
file={file}
|
||||
files={fileList}
|
||||
notionPages={notionPages}
|
||||
onStepChange={changeStep}
|
||||
updateIndexingTypeCache={updateIndexingTypeCache}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import FilePreview from '../file-preview'
|
||||
@@ -16,12 +16,13 @@ import { useDatasetDetailContext } from '@/context/dataset-detail'
|
||||
|
||||
type IStepOneProps = {
|
||||
datasetId?: string
|
||||
dataSourceType?: DataSourceType
|
||||
dataSourceType: DataSourceType
|
||||
dataSourceTypeDisable: Boolean
|
||||
hasConnection: boolean
|
||||
onSetting: () => void
|
||||
file?: File
|
||||
updateFile: (file?: File) => void
|
||||
files: any[]
|
||||
updateFileList: (files: any[]) => void
|
||||
updateFile: (fileItem: any, progress: number, list: any[]) => void
|
||||
notionPages?: any[]
|
||||
updateNotionPages: (value: any[]) => void
|
||||
onStepChange: () => void
|
||||
@@ -54,11 +55,13 @@ const StepOne = ({
|
||||
hasConnection,
|
||||
onSetting,
|
||||
onStepChange,
|
||||
file,
|
||||
files,
|
||||
updateFileList,
|
||||
updateFile,
|
||||
notionPages = [],
|
||||
updateNotionPages,
|
||||
}: IStepOneProps) => {
|
||||
const [currentFile, setCurrentFile] = useState<File | undefined>()
|
||||
const { dataset } = useDatasetDetailContext()
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [showFilePreview, setShowFilePreview] = useState(true)
|
||||
@@ -68,17 +71,32 @@ const StepOne = ({
|
||||
const hidePreview = () => setShowFilePreview(false)
|
||||
|
||||
const modalShowHandle = () => setShowModal(true)
|
||||
|
||||
const modalCloseHandle = () => setShowModal(false)
|
||||
|
||||
const updateCurrentFile = (file: File) => {
|
||||
setCurrentFile(file)
|
||||
}
|
||||
const hideFilePreview = () => {
|
||||
setCurrentNotionPage(undefined)
|
||||
}
|
||||
|
||||
const updateCurrentPage = (page: Page) => {
|
||||
setCurrentNotionPage(page)
|
||||
}
|
||||
|
||||
const hideNotionPagePreview = () => {
|
||||
setCurrentNotionPage(undefined)
|
||||
}
|
||||
|
||||
// TODO
|
||||
const nextDisabled = useMemo(() => {
|
||||
if (!files.length)
|
||||
return true
|
||||
console.log(files)
|
||||
console.log(files.some(file => file.progress !== 100))
|
||||
if (files.some(file => file.progress !== 100))
|
||||
return true
|
||||
return false
|
||||
}, [files])
|
||||
const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
|
||||
|
||||
return (
|
||||
@@ -103,7 +121,8 @@ const StepOne = ({
|
||||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.FILE)
|
||||
hidePreview()
|
||||
hideFilePreview()
|
||||
hideNotionPagePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon)} />
|
||||
@@ -119,7 +138,8 @@ const StepOne = ({
|
||||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.NOTION)
|
||||
hidePreview()
|
||||
hideFilePreview()
|
||||
hideNotionPagePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.notion)} />
|
||||
@@ -138,8 +158,14 @@ const StepOne = ({
|
||||
}
|
||||
{dataSourceType === DataSourceType.FILE && (
|
||||
<>
|
||||
<FileUploader onFileUpdate={updateFile} file={file} titleClassName={(!shouldShowDataSourceTypeList) ? 'mt-[30px] !mb-[44px] !text-lg !font-semibold !text-gray-900' : undefined} />
|
||||
<Button disabled={!file} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
|
||||
<FileUploader
|
||||
fileList={files}
|
||||
prepareFileList={updateFileList}
|
||||
onFileUpdate={updateFile}
|
||||
onPreview={updateCurrentFile}
|
||||
titleClassName={(!shouldShowDataSourceTypeList) ? 'mt-[30px] !mb-[44px] !text-lg !font-semibold !text-gray-900' : undefined}
|
||||
/>
|
||||
<Button disabled={nextDisabled} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
|
||||
</>
|
||||
)}
|
||||
{dataSourceType === DataSourceType.NOTION && (
|
||||
@@ -164,7 +190,7 @@ const StepOne = ({
|
||||
</div>
|
||||
<EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} />
|
||||
</div>
|
||||
{file && showFilePreview && <FilePreview file={file} hidePreview={hidePreview} />}
|
||||
{currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />}
|
||||
{currentNotionPage && <NotionPagePreview currentPage={currentNotionPage} hidePreview={hideNotionPagePreview} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ type StepTwoProps = {
|
||||
datasetId?: string
|
||||
indexingType?: string
|
||||
dataSourceType: DataSourceType
|
||||
file?: File
|
||||
files: File[]
|
||||
notionPages?: Page[]
|
||||
onStepChange?: (delta: number) => void
|
||||
updateIndexingTypeCache?: (type: string) => void
|
||||
@@ -62,7 +62,7 @@ const StepTwo = ({
|
||||
datasetId,
|
||||
indexingType,
|
||||
dataSourceType,
|
||||
file,
|
||||
files,
|
||||
notionPages = [],
|
||||
onStepChange,
|
||||
updateIndexingTypeCache,
|
||||
@@ -212,8 +212,7 @@ const StepTwo = ({
|
||||
info_list: {
|
||||
data_source_type: dataSourceType,
|
||||
file_info_list: {
|
||||
// TODO multi files
|
||||
file_ids: [file?.id || ''],
|
||||
file_ids: files.map(file => file.id),
|
||||
},
|
||||
},
|
||||
indexing_technique: getIndexing_technique(),
|
||||
@@ -254,8 +253,7 @@ const StepTwo = ({
|
||||
} as CreateDocumentReq
|
||||
if (dataSourceType === DataSourceType.FILE) {
|
||||
params.data_source.info_list.file_info_list = {
|
||||
// TODO multi files
|
||||
file_ids: [file?.id || ''],
|
||||
file_ids: files.map(file => file.id),
|
||||
}
|
||||
}
|
||||
if (dataSourceType === DataSourceType.NOTION)
|
||||
@@ -529,15 +527,21 @@ const StepTwo = ({
|
||||
<Link className='text-[#155EEF]' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
|
||||
</div>
|
||||
)}
|
||||
{/* TODO multi files */}
|
||||
<div className={s.source}>
|
||||
<div className={s.sourceContent}>
|
||||
{dataSourceType === DataSourceType.FILE && (
|
||||
<>
|
||||
<div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.fileSource')}</div>
|
||||
<div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
|
||||
<span className={cn(s.fileIcon, file && s[file.extension])} />
|
||||
{getFileName(file?.name || '')}
|
||||
<span className={cn(s.fileIcon, files.length && s[files[0].extension])} />
|
||||
{getFileName(files[0].name || '')}
|
||||
{files.length > 1 && (
|
||||
<span className={s.sourceCount}>
|
||||
<span>{t('datasetCreation.stepTwo.other')}</span>
|
||||
<span>{files.length - 1}</span>
|
||||
<span>{t('datasetCreation.stepTwo.fileUnit')}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -85,7 +85,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
|
||||
indexingType={indexingTechnique || ''}
|
||||
isSetting
|
||||
documentDetail={documentDetail}
|
||||
file={documentDetail.data_source_info.upload_file}
|
||||
files={[documentDetail.data_source_info.upload_file]}
|
||||
onSave={saveHandler}
|
||||
onCancel={cancelHandler}
|
||||
/>
|
||||
|
||||
@@ -23,7 +23,7 @@ const translation = {
|
||||
title: 'Upload text file',
|
||||
button: 'Drag and drop file, or',
|
||||
browse: 'Browse',
|
||||
tip: 'Supports txt, html, markdown, xlsx, and pdf.',
|
||||
tip: 'Supports txt, html, markdown, xlsx, and pdf. Max 10MB each.',
|
||||
validation: {
|
||||
typeError: 'File type not supported',
|
||||
size: 'File too large. Maximum is 15MB',
|
||||
|
||||
@@ -23,7 +23,7 @@ const translation = {
|
||||
title: '上传文本文件',
|
||||
button: '拖拽文件至此,或者',
|
||||
browse: '选择文件',
|
||||
tip: '已支持 TXT, HTML, Markdown, PDF, XLSX',
|
||||
tip: '已支持 TXT、 HTML、 Markdown、 PDF、 XLSX,每个文件不超过 10 MB。',
|
||||
validation: {
|
||||
typeError: '文件类型不支持',
|
||||
size: '文件太大了,不能超过 15MB',
|
||||
|
||||
Reference in New Issue
Block a user