Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
d637a147
Unverified
Commit
d637a147
authored
Jun 21, 2023
by
Joel
Committed by
GitHub
Jun 21, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: support batch upload files (#419)
parent
8a4d19d9
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
286 additions
and
224 deletions
+286
-224
index.tsx
web/app/components/datasets/create/file-preview/index.tsx
+3
-3
index.module.css
...components/datasets/create/file-uploader/index.module.css
+46
-60
index.tsx
web/app/components/datasets/create/file-uploader/index.tsx
+155
-128
index.tsx
web/app/components/datasets/create/index.tsx
+27
-6
index.module.css
web/app/components/datasets/create/step-one/index.module.css
+2
-0
index.tsx
web/app/components/datasets/create/step-one/index.tsx
+35
-13
index.tsx
web/app/components/datasets/create/step-two/index.tsx
+13
-9
index.tsx
...p/components/datasets/documents/detail/settings/index.tsx
+1
-1
dataset-creation.en.ts
web/i18n/lang/dataset-creation.en.ts
+2
-2
dataset-creation.zh.ts
web/i18n/lang/dataset-creation.zh.ts
+2
-2
No files found.
web/app/components/datasets/create/file-preview/index.tsx
View file @
d637a147
...
...
@@ -9,7 +9,6 @@ import { fetchFilePreview } from '@/service/common'
type
IProps
=
{
file
?:
File
notionPage
?:
any
hidePreview
:
()
=>
void
}
...
...
@@ -33,14 +32,15 @@ const FilePreview = ({
const
getFileName
=
(
currentFile
?:
File
)
=>
{
if
(
!
currentFile
)
return
''
const
arr
=
currentFile
.
name
.
split
(
'.'
)
return
arr
.
slice
(
0
,
-
1
).
join
()
}
useEffect
(()
=>
{
if
(
file
)
if
(
file
)
{
setLoading
(
true
)
getPreviewContent
(
file
.
id
)
}
},
[
file
])
return
(
...
...
web/app/components/datasets/create/file-uploader/index.module.css
View file @
d637a147
.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
:
26
px
;
line-height
:
18
px
;
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
;
...
...
@@ -38,7 +38,7 @@
width
:
100%
;
height
:
100%
;
}
.uploader
::before
{
.uploader
.uploadIcon
{
content
:
''
;
display
:
block
;
margin-right
:
8px
;
...
...
@@ -51,16 +51,20 @@
@apply
pl-1
cursor-pointer;
color
:
#155eef
;
}
.fileList
{
@apply
space-y-2;
}
.file
{
@apply
box-border
relative
flex
items-center;
padding
:
21px
24px
21px
64
px
;
@apply
box-border
relative
flex
items-center
justify-between
;
padding
:
8px
12px
8px
8
px
;
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 +73,27 @@
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
:
1
px
solid
#EAECF0
;
border
:
0.5
px
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
:
32
px
;
background-size
:
24
px
;
}
.fileIcon.csv
{
background-image
:
url(../assets/csv.svg)
;
...
...
@@ -126,7 +121,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 +129,37 @@
}
.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
;
}
web/app/components/datasets/create/file-uploader/index.tsx
View file @
d637a147
'use client'
import
React
,
{
use
Callback
,
use
Effect
,
useRef
,
useState
}
from
'react'
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useContext
}
from
'use-context-selector'
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
file
List
:
any
[]
titleClassName
?:
string
onFileUpdate
:
(
file
?:
FileEntity
)
=>
void
prepareFileList
:
(
files
:
any
[])
=>
void
onFileUpdate
:
(
fileItem
:
any
,
progress
:
number
,
list
:
any
[])
=>
void
onFileListUpdate
?:
(
files
:
any
)
=>
void
onPreview
:
(
file
:
FileEntity
)
=>
void
}
const
ACCEPTS
=
[
...
...
@@ -28,19 +30,25 @@ const ACCEPTS = [
'.csv'
,
]
const
MAX_SIZE
=
15
*
1024
*
1024
const
MAX_SIZE
=
10
*
1024
*
1024
const
BATCH_COUNT
=
5
const
FileUploader
=
({
file
,
onFileUpdate
,
titleClassName
}:
IFileUploaderProps
)
=>
{
const
FileUploader
=
({
fileList
,
titleClassName
,
prepareFileList
,
onFileUpdate
,
onFileListUpdate
,
onPreview
,
}:
IFileUploaderProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
[
dragging
,
setDragging
]
=
useState
(
false
)
const
dropRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
dragRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
fileUploader
=
useRef
<
HTMLInputElement
>
(
null
)
const
uploadPromise
=
useRef
<
any
>
(
null
)
const
[
currentFile
,
setCurrentFile
]
=
useState
<
File
>
()
const
[
uploading
,
setUploading
]
=
useState
(
false
)
const
[
percent
,
setPercent
]
=
useState
(
0
)
const
fileListRef
=
useRef
<
any
>
([])
// utils
const
getFileType
=
(
currentFile
:
File
)
=>
{
...
...
@@ -50,10 +58,7 @@ 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`
...
...
@@ -74,52 +79,76 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
return
isValidType
&&
isValidSize
}
const
onProgress
=
useCallback
((
e
:
ProgressEvent
)
=>
{
if
(
e
.
lengthComputable
)
{
const
percent
=
Math
.
floor
(
e
.
loaded
/
e
.
total
*
100
)
setPercent
(
percent
)
}
},
[
setPercent
])
const
abort
=
()
=>
{
const
currentXHR
=
uploadPromise
.
current
currentXHR
.
abort
()
}
const
fileUpload
=
async
(
file
?:
File
)
=>
{
if
(
!
file
)
return
if
(
!
isValid
(
file
))
return
setCurrentFile
(
file
)
setUploading
(
true
)
const
fileUpload
=
async
(
fileItem
:
any
)
=>
{
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
)
formData
.
append
(
'file'
,
fileItem
.
file
)
const
onProgress
=
(
e
:
ProgressEvent
)
=>
{
if
(
e
.
lengthComputable
)
{
const
percent
=
Math
.
floor
(
e
.
loaded
/
e
.
total
*
100
)
onFileUpdate
(
fileItem
,
percent
,
fileListRef
.
current
)
}
}
catch
(
xhr
:
any
)
{
setUploading
(
false
)
// abort handle
if
(
xhr
.
readyState
===
0
&&
xhr
.
status
===
0
)
{
if
(
fileUploader
.
current
)
fileUploader
.
current
.
value
=
''
setCurrentFile
(
undefined
)
return
}
notify
({
type
:
'error'
,
message
:
t
(
'datasetCreation.stepOne.uploader.failed'
)
})
return
upload
({
xhr
:
new
XMLHttpRequest
(),
data
:
formData
,
onprogress
:
onProgress
,
})
.
then
((
res
:
FileEntity
)
=>
{
const
fileListCopy
=
fileListRef
.
current
const
completeFile
=
{
fileID
:
fileItem
.
fileID
,
file
:
res
,
}
const
index
=
fileListCopy
.
findIndex
((
item
:
any
)
=>
item
.
fileID
===
fileItem
.
fileID
)
fileListCopy
[
index
]
=
completeFile
onFileUpdate
(
completeFile
,
100
,
fileListCopy
)
return
Promise
.
resolve
({
...
completeFile
})
})
.
catch
(()
=>
{
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
})
const
newFiles
=
[...
fileListRef
.
current
,
...
preparedFiles
]
prepareFileList
(
newFiles
)
fileListRef
.
current
=
newFiles
uploadMultipleFiles
(
preparedFiles
)
}
const
handleDragEnter
=
(
e
:
DragEvent
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
...
...
@@ -134,6 +163,7 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
e
.
stopPropagation
()
e
.
target
===
dragRef
.
current
&&
setDragging
(
false
)
}
const
handleDrop
=
(
e
:
DragEvent
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
...
...
@@ -142,29 +172,26 @@ const FileUploader = ({ file, onFileUpdate, titleClassName }: IFileUploaderProps
return
const
files
=
[...
e
.
dataTransfer
.
files
]
if
(
files
.
length
>
1
)
{
notify
({
type
:
'error'
,
message
:
t
(
'datasetCreation.stepOne.uploader.validation.count'
)
})
return
}
onFileUpdate
()
fileUpload
(
files
[
0
])
const
validFiles
=
files
.
filter
(
file
=>
isValid
(
file
))
// fileUpload(files[0])
initialUpload
(
validFiles
)
}
const
selectHandle
=
()
=>
{
if
(
fileUploader
.
current
)
fileUploader
.
current
.
click
()
}
const
removeFile
=
()
=>
{
const
removeFile
=
(
fileID
:
string
)
=>
{
if
(
fileUploader
.
current
)
fileUploader
.
current
.
value
=
''
setCurrentFile
(
undefined
)
onFile
Update
(
)
fileListRef
.
current
=
fileListRef
.
current
.
filter
((
item
:
any
)
=>
item
.
fileID
!==
fileID
)
onFile
ListUpdate
?.([...
fileListRef
.
current
]
)
}
const
fileChangeHandle
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
currentFile
=
e
.
target
.
files
?.[
0
]
onFileUpdate
()
fileUpload
(
currentFile
)
const
files
=
[...(
e
.
target
.
files
??
[])].
filter
(
file
=>
isValid
(
file
))
initialUpload
(
files
)
}
useEffect
(()
=>
{
...
...
@@ -184,83 +211,83 @@ 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
>
)
}
<
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
>
{
currentFile
&&
(
<
div
className=
{
cn
(
s
.
file
,
uploading
&&
s
.
uploading
)
}
>
{
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
className=
{
s
.
fileList
}
>
{
fileList
.
map
((
fileItem
,
index
)
=>
(
<
div
key=
{
`${fileItem.fileID}-${index}`
}
onClick=
{
()
=>
fileItem
.
file
?.
id
&&
onPreview
(
fileItem
.
file
)
}
className=
{
cn
(
s
.
file
,
fileItem
.
progress
<
100
&&
s
.
uploading
,
// s.active,
)
}
>
{
fileItem
.
progress
<
100
&&
(
<
div
className=
{
s
.
progressbar
}
style=
{
{
width
:
`${fileItem.progress}%`
}
}
/>
)
}
<
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
.
fileExtraInfo
}
>
<
span
className=
{
s
.
size
}
>
{
getFileSize
(
currentFile
.
size
)
}
</
span
>
<
span
className=
{
s
.
error
}
></
span
>
<
div
className=
{
s
.
actionWrapper
}
>
{
(
fileItem
.
progress
<
100
&&
fileItem
.
progress
>=
0
)
&&
(
<
div
className=
{
s
.
percent
}
>
{
`${fileItem.progress}%`
}
</
div
>
)
}
{
fileItem
.
progress
===
100
&&
(
<
div
className=
{
s
.
remove
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
()
removeFile
(
fileItem
.
fileID
)
}
}
/>
)
}
</
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
>
</>
))
}
{
/* {currentFile && (
<div
// onClick={() => onPreview(currentFile)}
className={cn(
s.file,
uploading && s.uploading,
// s.active,
)}
{
!
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
>
</>
>
{uploading && (
<div className={s.progressbar} style={{ width: `${percent}%` }}/>
)}
</
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 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
>
<
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 className={s.actionWrapper}>
{uploading && (
<div className={s.percent}>{`${percent}%`}</div>
)}
{!uploading && (
<div className={s.remove} onClick={() => removeFile(index)}/>
)}
</div>
</div>
</
div
>
)
}
<
div
className=
{
s
.
tip
}
>
{
t
(
'datasetCreation.stepOne.uploader.tip'
)
}
</
div
>
)} */
}
</
div
>
</
div
>
)
}
...
...
web/app/components/datasets/create/index.tsx
View file @
d637a147
...
...
@@ -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
[
file
List
,
setFiles
]
=
useState
<
any
[]
>
([]
)
const
[
result
,
setResult
]
=
useState
<
createDocumentResponse
|
undefined
>
()
const
[
hasError
,
setHasError
]
=
useState
(
false
)
...
...
@@ -39,8 +39,28 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
setNotionPages
(
value
)
}
const
updateFile
=
(
file
?:
File
)
=>
{
setFile
(
file
)
const
updateFileList
=
(
preparedFiles
:
any
)
=>
{
setFiles
(
preparedFiles
)
}
const
updateFile
=
(
fileItem
:
any
,
progress
:
number
,
list
:
any
[])
=>
{
const
targetIndex
=
list
.
findIndex
((
file
:
any
)
=>
file
.
fileID
===
fileItem
.
fileID
)
list
[
targetIndex
]
=
{
...
list
[
targetIndex
],
progress
,
}
setFiles
([...
list
])
// use follow code would cause dirty list update problem
// const newList = list.map((file) => {
// if (file.fileID === fileItem.fileID) {
// return {
// ...fileItem,
// progress,
// }
// }
// return file
// })
// setFiles(newList)
}
const
updateIndexingTypeCache
=
(
type
:
string
)
=>
{
setIndexTypeCache
(
type
)
...
...
@@ -104,8 +124,9 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
dataSourceType=
{
dataSourceType
}
dataSourceTypeDisable=
{
!!
detail
?.
data_source_type
}
changeType=
{
setDataSourceType
}
file
=
{
file
}
file
s=
{
fileList
}
updateFile=
{
updateFile
}
updateFileList=
{
updateFileList
}
notionPages=
{
notionPages
}
updateNotionPages=
{
updateNotionPages
}
onStepChange=
{
nextStep
}
...
...
@@ -116,7 +137,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
indexingType=
{
detail
?.
indexing_technique
||
''
}
datasetId=
{
datasetId
}
dataSourceType=
{
dataSourceType
}
file
=
{
file
}
file
s=
{
fileList
.
map
(
file
=>
file
.
file
)
}
notionPages=
{
notionPages
}
onStepChange=
{
changeStep
}
updateIndexingTypeCache=
{
updateIndexingTypeCache
}
...
...
web/app/components/datasets/create/step-one/index.module.css
View file @
d637a147
...
...
@@ -10,7 +10,9 @@
}
.form
{
position
:
relative
;
padding
:
12px
64px
;
background-color
:
#fff
;
}
.dataSourceTypeList
{
...
...
web/app/components/datasets/create/step-one/index.tsx
View file @
d637a147
'use client'
import
React
,
{
useState
}
from
'react'
import
React
,
{
use
Memo
,
use
State
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
cn
from
'classnames'
import
FilePreview
from
'../file-preview'
...
...
@@ -20,8 +20,9 @@ type IStepOneProps = {
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,23 +55,28 @@ const StepOne = ({
hasConnection
,
onSetting
,
onStepChange
,
file
,
files
,
updateFileList
,
updateFile
,
notionPages
=
[],
updateNotionPages
,
}:
IStepOneProps
)
=>
{
const
{
dataset
}
=
useDatasetDetailContext
()
const
[
showModal
,
setShowModal
]
=
useState
(
false
)
const
[
showFilePreview
,
setShowFilePreview
]
=
useState
(
true
)
const
[
currentFile
,
setCurrentFile
]
=
useState
<
File
|
undefined
>
(
)
const
[
currentNotionPage
,
setCurrentNotionPage
]
=
useState
<
Page
|
undefined
>
()
const
{
t
}
=
useTranslation
()
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
)
}
...
...
@@ -81,6 +87,13 @@ const StepOne = ({
const
shouldShowDataSourceTypeList
=
!
datasetId
||
(
datasetId
&&
!
dataset
?.
data_source_type
)
const
nextDisabled
=
useMemo
(()
=>
{
if
(
!
files
.
length
)
return
true
if
(
files
.
some
(
file
=>
!
file
.
file
.
id
))
return
true
return
false
},
[
files
])
return
(
<
div
className=
'flex w-full h-full'
>
<
div
className=
'grow overflow-y-auto relative'
>
...
...
@@ -103,7 +116,8 @@ const StepOne = ({
if
(
dataSourceTypeDisable
)
return
changeType
(
DataSourceType
.
FILE
)
hidePreview
()
hideFilePreview
()
hideNotionPagePreview
()
}
}
>
<
span
className=
{
cn
(
s
.
datasetIcon
)
}
/>
...
...
@@ -119,7 +133,8 @@ const StepOne = ({
if
(
dataSourceTypeDisable
)
return
changeType
(
DataSourceType
.
NOTION
)
hidePreview
()
hideFilePreview
()
hideNotionPagePreview
()
}
}
>
<
span
className=
{
cn
(
s
.
datasetIcon
,
s
.
notion
)
}
/>
...
...
@@ -138,8 +153,15 @@ 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
}
titleClassName=
{
!
shouldShowDataSourceTypeList
?
'mt-[30px] !mb-[44px] !text-lg !font-semibold !text-gray-900'
:
undefined
}
prepareFileList=
{
updateFileList
}
onFileListUpdate=
{
updateFileList
}
onFileUpdate=
{
updateFile
}
onPreview=
{
updateCurrentFile
}
/>
<
Button
disabled=
{
nextDisabled
}
className=
{
s
.
submitButton
}
type=
'primary'
onClick=
{
onStepChange
}
>
{
t
(
'datasetCreation.stepOne.button'
)
}
</
Button
>
</>
)
}
{
dataSourceType
===
DataSourceType
.
NOTION
&&
(
...
...
@@ -164,7 +186,7 @@ const StepOne = ({
</
div
>
<
EmptyDatasetCreationModal
show=
{
showModal
}
onHide=
{
modalCloseHandle
}
/>
</
div
>
{
file
&&
showFilePreview
&&
<
FilePreview
file=
{
file
}
hidePreview=
{
hid
ePreview
}
/>
}
{
currentFile
&&
<
FilePreview
file=
{
currentFile
}
hidePreview=
{
hideFil
ePreview
}
/>
}
{
currentNotionPage
&&
<
NotionPagePreview
currentPage=
{
currentNotionPage
}
hidePreview=
{
hideNotionPagePreview
}
/>
}
</
div
>
)
...
...
web/app/components/datasets/create/step-two/index.tsx
View file @
d637a147
...
...
@@ -36,7 +36,7 @@ type StepTwoProps = {
datasetId
?:
string
indexingType
?:
string
dataSourceType
:
DataSourceType
file
?:
File
file
s
:
File
[]
notionPages
?:
Page
[]
onStepChange
?:
(
delta
:
number
)
=>
void
updateIndexingTypeCache
?:
(
type
:
string
)
=>
void
...
...
@@ -62,7 +62,7 @@ const StepTwo = ({
datasetId
,
indexingType
,
dataSourceType
,
file
,
file
s
,
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
>
</>
)
}
...
...
web/app/components/datasets/documents/detail/settings/index.tsx
View file @
d637a147
...
...
@@ -85,7 +85,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
indexingType=
{
indexingTechnique
||
''
}
isSetting
documentDetail=
{
documentDetail
}
file
=
{
documentDetail
.
data_source_info
.
upload_file
}
file
s=
{
[
documentDetail
.
data_source_info
.
upload_file
]
}
onSave=
{
saveHandler
}
onCancel=
{
cancelHandler
}
/>
...
...
web/i18n/lang/dataset-creation.en.ts
View file @
d637a147
...
...
@@ -23,10 +23,10 @@ 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 1
5
MB'
,
size
:
'File too large. Maximum is 1
0
MB'
,
count
:
'Multiple files not supported'
,
},
cancel
:
'Cancel'
,
...
...
web/i18n/lang/dataset-creation.zh.ts
View file @
d637a147
...
...
@@ -23,10 +23,10 @@ const translation = {
title
:
'上传文本文件'
,
button
:
'拖拽文件至此,或者'
,
browse
:
'选择文件'
,
tip
:
'已支持 TXT
, HTML, Markdown, PDF, XLSX
'
,
tip
:
'已支持 TXT
、 HTML、 Markdown、 PDF、 XLSX,每个文件不超过 10 MB。
'
,
validation
:
{
typeError
:
'文件类型不支持'
,
size
:
'文件太大了,不能超过 1
5
MB'
,
size
:
'文件太大了,不能超过 1
0
MB'
,
count
:
'暂不支持多个文件'
,
},
cancel
:
'取消'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment