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
7756f9d4
Commit
7756f9d4
authored
Aug 12, 2023
by
JzoNg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: segment batch import
parent
76dd849d
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
557 additions
and
51 deletions
+557
-51
dots-horizontal.svg
...base/icons/assets/vender/line/general/dots-horizontal.svg
+9
-0
DotsHorizontal.json
...ts/base/icons/src/vender/line/general/DotsHorizontal.json
+71
-0
DotsHorizontal.tsx
...nts/base/icons/src/vender/line/general/DotsHorizontal.tsx
+14
-0
index.ts
...pp/components/base/icons/src/vender/line/general/index.ts
+1
-0
csv-downloader.tsx
.../datasets/documents/detail/batch-modal/csv-downloader.tsx
+57
-0
csv-uploader.tsx
...ts/datasets/documents/detail/batch-modal/csv-uploader.tsx
+126
-0
index.tsx
...omponents/datasets/documents/detail/batch-modal/index.tsx
+60
-0
index.tsx
.../components/datasets/documents/detail/completed/index.tsx
+17
-6
index.tsx
web/app/components/datasets/documents/detail/index.tsx
+60
-16
index.tsx
...omponents/datasets/documents/detail/segment-add/index.tsx
+84
-0
list.tsx
web/app/components/datasets/documents/list.tsx
+13
-20
style.module.css
web/app/components/datasets/documents/style.module.css
+0
-4
dataset-documents.en.ts
web/i18n/lang/dataset-documents.en.ts
+19
-1
dataset-documents.zh.ts
web/i18n/lang/dataset-documents.zh.ts
+18
-0
datasets.ts
web/service/datasets.ts
+8
-4
No files found.
web/app/components/base/icons/assets/vender/line/general/dots-horizontal.svg
0 → 100644
View file @
7756f9d4
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"Icon"
>
<g
id=
"Icon_2"
>
<path
d=
"M8.00008 8.66634C8.36827 8.66634 8.66675 8.36786 8.66675 7.99967C8.66675 7.63148 8.36827 7.33301 8.00008 7.33301C7.63189 7.33301 7.33341 7.63148 7.33341 7.99967C7.33341 8.36786 7.63189 8.66634 8.00008 8.66634Z"
stroke=
"#344054"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
<path
d=
"M12.6667 8.66634C13.0349 8.66634 13.3334 8.36786 13.3334 7.99967C13.3334 7.63148 13.0349 7.33301 12.6667 7.33301C12.2986 7.33301 12.0001 7.63148 12.0001 7.99967C12.0001 8.36786 12.2986 8.66634 12.6667 8.66634Z"
stroke=
"#344054"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
<path
d=
"M3.33341 8.66634C3.7016 8.66634 4.00008 8.36786 4.00008 7.99967C4.00008 7.63148 3.7016 7.33301 3.33341 7.33301C2.96522 7.33301 2.66675 7.63148 2.66675 7.99967C2.66675 8.36786 2.96522 8.66634 3.33341 8.66634Z"
stroke=
"#344054"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
</g>
</svg>
web/app/components/base/icons/src/vender/line/general/DotsHorizontal.json
0 → 100644
View file @
7756f9d4
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"viewBox"
:
"0 0 16 16"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"Icon"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"Icon_2"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M8.00008 8.66634C8.36827 8.66634 8.66675 8.36786 8.66675 7.99967C8.66675 7.63148 8.36827 7.33301 8.00008 7.33301C7.63189 7.33301 7.33341 7.63148 7.33341 7.99967C7.33341 8.36786 7.63189 8.66634 8.00008 8.66634Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M12.6667 8.66634C13.0349 8.66634 13.3334 8.36786 13.3334 7.99967C13.3334 7.63148 13.0349 7.33301 12.6667 7.33301C12.2986 7.33301 12.0001 7.63148 12.0001 7.99967C12.0001 8.36786 12.2986 8.66634 12.6667 8.66634Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M3.33341 8.66634C3.7016 8.66634 4.00008 8.36786 4.00008 7.99967C4.00008 7.63148 3.7016 7.33301 3.33341 7.33301C2.96522 7.33301 2.66675 7.63148 2.66675 7.99967C2.66675 8.36786 2.96522 8.66634 3.33341 8.66634Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
}
]
}
]
},
"name"
:
"DotsHorizontal"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/general/DotsHorizontal.tsx
0 → 100644
View file @
7756f9d4
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./DotsHorizontal.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
export
default
Icon
web/app/components/base/icons/src/vender/line/general/index.ts
View file @
7756f9d4
export
{
default
as
Check
}
from
'./Check'
export
{
default
as
DotsHorizontal
}
from
'./DotsHorizontal'
export
{
default
as
Edit03
}
from
'./Edit03'
export
{
default
as
Hash02
}
from
'./Hash02'
export
{
default
as
LinkExternal02
}
from
'./LinkExternal02'
...
...
web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx
0 → 100644
View file @
7756f9d4
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
{
useCSVDownloader
,
}
from
'react-papaparse'
import
{
useTranslation
}
from
'react-i18next'
import
{
Download02
as
DownloadIcon
}
from
'@/app/components/base/icons/src/vender/solid/general'
const
CSV_TEMPLATE
=
[
[
'content/question'
,
'answer(Only need with QA model)'
],
]
const
CSVDownload
:
FC
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
CSVDownloader
,
Type
}
=
useCSVDownloader
()
return
(
<
div
className=
'mt-6'
>
<
div
className=
'text-sm text-gray-900 font-medium'
>
{
t
(
'share.generation.csvStructureTitle'
)
}
</
div
>
<
div
className=
'mt-2 max-h-[500px] overflow-auto'
>
<
table
className=
'table-fixed w-full border-separate border-spacing-0 border border-gray-200 rounded-lg text-xs'
>
<
thead
className=
'text-gray-500'
>
<
tr
>
<
td
className=
'h-9 pl-3 pr-2 border-b border-gray-200'
>
{
t
(
'datasetDocuments.list.batchModal.question'
)
}
</
td
>
<
td
className=
'h-9 pl-3 pr-2 border-b border-gray-200'
>
{
t
(
'datasetDocuments.list.batchModal.answer'
)
}
</
td
>
</
tr
>
</
thead
>
<
tbody
className=
'text-gray-700'
>
<
tr
>
<
td
className=
'h-9 pl-3 pr-2 border-b border-gray-100 text-[13px]'
>
{
t
(
'datasetDocuments.list.batchModal.question'
)
}
1
</
td
>
<
td
className=
'h-9 pl-3 pr-2 border-b border-gray-100 text-[13px]'
>
{
t
(
'datasetDocuments.list.batchModal.answer'
)
}
1
</
td
>
</
tr
>
<
tr
>
<
td
className=
'h-9 pl-3 pr-2 text-[13px]'
>
{
t
(
'datasetDocuments.list.batchModal.question'
)
}
2
</
td
>
<
td
className=
'h-9 pl-3 pr-2 text-[13px]'
>
{
t
(
'datasetDocuments.list.batchModal.answer'
)
}
2
</
td
>
</
tr
>
</
tbody
>
</
table
>
</
div
>
<
CSVDownloader
className=
"block mt-2 cursor-pointer"
type=
{
Type
.
Link
}
filename=
{
'template'
}
bom=
{
true
}
data=
{
CSV_TEMPLATE
}
>
<
div
className=
'flex items-center h-[18px] space-x-1 text-[#155EEF] text-xs font-medium'
>
<
DownloadIcon
className=
'w-3 h-3'
/>
{
t
(
'datasetDocuments.list.batchModal.template'
)
}
</
div
>
</
CSVDownloader
>
</
div
>
)
}
export
default
React
.
memo
(
CSVDownload
)
web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
0 → 100644
View file @
7756f9d4
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
{
useContext
}
from
'use-context-selector'
import
{
Csv
as
CSVIcon
}
from
'@/app/components/base/icons/src/public/files'
import
{
ToastContext
}
from
'@/app/components/base/toast'
import
{
Trash03
}
from
'@/app/components/base/icons/src/vender/line/general'
import
Button
from
'@/app/components/base/button'
export
type
Props
=
{
file
:
File
|
undefined
updateFile
:
(
file
?:
File
)
=>
void
}
const
CSVUploader
:
FC
<
Props
>
=
({
file
,
updateFile
,
})
=>
{
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
handleDragEnter
=
(
e
:
DragEvent
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
e
.
target
!==
dragRef
.
current
&&
setDragging
(
true
)
}
const
handleDragOver
=
(
e
:
DragEvent
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
}
const
handleDragLeave
=
(
e
:
DragEvent
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
e
.
target
===
dragRef
.
current
&&
setDragging
(
false
)
}
const
handleDrop
=
(
e
:
DragEvent
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
setDragging
(
false
)
if
(
!
e
.
dataTransfer
)
return
const
files
=
[...
e
.
dataTransfer
.
files
]
if
(
files
.
length
>
1
)
{
notify
({
type
:
'error'
,
message
:
t
(
'datasetCreation.stepOne.uploader.validation.count'
)
})
return
}
updateFile
(
files
[
0
])
}
const
selectHandle
=
()
=>
{
if
(
fileUploader
.
current
)
fileUploader
.
current
.
click
()
}
const
removeFile
=
()
=>
{
if
(
fileUploader
.
current
)
fileUploader
.
current
.
value
=
''
updateFile
()
}
const
fileChangeHandle
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
currentFile
=
e
.
target
.
files
?.[
0
]
updateFile
(
currentFile
)
}
useEffect
(()
=>
{
dropRef
.
current
?.
addEventListener
(
'dragenter'
,
handleDragEnter
)
dropRef
.
current
?.
addEventListener
(
'dragover'
,
handleDragOver
)
dropRef
.
current
?.
addEventListener
(
'dragleave'
,
handleDragLeave
)
dropRef
.
current
?.
addEventListener
(
'drop'
,
handleDrop
)
return
()
=>
{
dropRef
.
current
?.
removeEventListener
(
'dragenter'
,
handleDragEnter
)
dropRef
.
current
?.
removeEventListener
(
'dragover'
,
handleDragOver
)
dropRef
.
current
?.
removeEventListener
(
'dragleave'
,
handleDragLeave
)
dropRef
.
current
?.
removeEventListener
(
'drop'
,
handleDrop
)
}
},
[])
return
(
<
div
className=
'mt-6'
>
<
input
ref=
{
fileUploader
}
style=
{
{
display
:
'none'
}
}
type=
"file"
id=
"fileUploader"
accept=
'.csv'
onChange=
{
fileChangeHandle
}
/>
<
div
ref=
{
dropRef
}
>
{
!
file
&&
(
<
div
className=
{
cn
(
'flex items-center h-20 rounded-xl bg-gray-50 border border-dashed border-gray-200 text-sm font-normal'
,
dragging
&&
'bg-[#F5F8FF] border border-[#B2CCFF]'
)
}
>
<
div
className=
'w-full flex items-center justify-center space-x-2'
>
<
CSVIcon
className=
"shrink-0"
/>
<
div
className=
'text-gray-500'
>
{
t
(
'datasetDocuments.list.batchModal.csvUploadTitle'
)
}
<
span
className=
'text-primary-400 cursor-pointer'
onClick=
{
selectHandle
}
>
{
t
(
'datasetDocuments.list.batchModal.browse'
)
}
</
span
>
</
div
>
</
div
>
{
dragging
&&
<
div
ref=
{
dragRef
}
className=
'absolute w-full h-full top-0 left-0'
/>
}
</
div
>
)
}
{
file
&&
(
<
div
className=
{
cn
(
'flex items-center h-20 px-6 rounded-xl bg-gray-50 border border-gray-200 text-sm font-normal group'
,
'hover:bg-[#F5F8FF] hover:border-[#B2CCFF]'
)
}
>
<
CSVIcon
className=
"shrink-0"
/>
<
div
className=
'flex ml-2 w-0 grow'
>
<
span
className=
'max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-gray-800'
>
{
file
.
name
.
replace
(
/.csv$/
,
''
)
}
</
span
>
<
span
className=
'shrink-0 text-gray-500'
>
.csv
</
span
>
</
div
>
<
div
className=
'hidden group-hover:flex items-center'
>
<
Button
className=
'!h-8 !px-3 !py-[6px] bg-white !text-[13px] !leading-[18px] text-gray-700'
onClick=
{
selectHandle
}
>
{
t
(
'datasetCreation.stepOne.uploader.change'
)
}
</
Button
>
<
div
className=
'mx-2 w-px h-4 bg-gray-200'
/>
<
div
className=
'p-2 cursor-pointer'
onClick=
{
removeFile
}
>
<
Trash03
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
</
div
>
</
div
>
)
}
</
div
>
</
div
>
)
}
export
default
React
.
memo
(
CSVUploader
)
web/app/components/datasets/documents/detail/batch-modal/index.tsx
0 → 100644
View file @
7756f9d4
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
useEffect
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
CSVUploader
from
'./csv-uploader'
import
CSVDownloader
from
'./csv-downloader'
import
Button
from
'@/app/components/base/button'
import
Modal
from
'@/app/components/base/modal'
import
{
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
export
type
IBatchModalProps
=
{
isShow
:
boolean
onCancel
:
()
=>
void
onConfirm
:
(
file
:
File
)
=>
void
}
const
BatchModal
:
FC
<
IBatchModalProps
>
=
({
isShow
,
onCancel
,
onConfirm
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
[
currentCSV
,
setCurrentCSV
]
=
useState
<
File
>
()
const
handleFile
=
(
file
?:
File
)
=>
setCurrentCSV
(
file
)
const
handleSend
=
()
=>
{
if
(
!
currentCSV
)
return
onCancel
()
onConfirm
(
currentCSV
)
}
useEffect
(()
=>
{
if
(
!
isShow
)
setCurrentCSV
(
undefined
)
},
[
isShow
])
return
(
<
Modal
isShow=
{
isShow
}
onClose=
{
()
=>
{}
}
className=
'px-8 py-6 !max-w-[520px] !rounded-xl'
>
<
div
className=
'relative pb-1 text-xl font-medium leading-[30px] text-gray-900'
>
{
t
(
'datasetDocuments.list.batchModal.title'
)
}
</
div
>
<
div
className=
'absolute right-4 top-4 p-2 cursor-pointer'
onClick=
{
onCancel
}
>
<
XClose
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
<
CSVUploader
file=
{
currentCSV
}
updateFile=
{
handleFile
}
/>
<
CSVDownloader
/>
<
div
className=
'mt-[28px] pt-6 flex justify-end'
>
<
Button
className=
'mr-2 text-gray-700 text-sm font-medium'
onClick=
{
onCancel
}
>
{
t
(
'datasetDocuments.list.batchModal.cancel'
)
}
</
Button
>
<
Button
className=
'text-sm font-medium'
type=
"primary"
onClick=
{
handleSend
}
disabled=
{
!
currentCSV
}
>
{
t
(
'datasetDocuments.list.batchModal.run'
)
}
</
Button
>
</
div
>
</
Modal
>
)
}
export
default
React
.
memo
(
BatchModal
)
web/app/components/datasets/documents/detail/completed/index.tsx
View file @
7756f9d4
...
...
@@ -8,6 +8,7 @@ import { debounce, isNil, omitBy } from 'lodash-es'
import
cn
from
'classnames'
import
{
StatusItem
}
from
'../../list'
import
{
DocumentContext
}
from
'../index'
import
{
ProcessStatus
}
from
'../segment-add'
import
s
from
'./style.module.css'
import
InfiniteVirtualList
from
'./InfiniteVirtualList'
import
{
formatNumber
}
from
'@/utils/format'
...
...
@@ -186,13 +187,18 @@ export const splitArray = (arr: any[], size = 3) => {
type
ICompletedProps
=
{
showNewSegmentModal
:
boolean
onNewSegmentModalChange
:
(
state
:
boolean
)
=>
void
importStatus
:
ProcessStatus
|
string
|
undefined
// data: Array<{}> // all/part segments
}
/**
* Embedding done, show list of all segments
* Support search and filter
*/
const
Completed
:
FC
<
ICompletedProps
>
=
({
showNewSegmentModal
,
onNewSegmentModalChange
})
=>
{
const
Completed
:
FC
<
ICompletedProps
>
=
({
showNewSegmentModal
,
onNewSegmentModalChange
,
importStatus
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
{
datasetId
=
''
,
documentId
=
''
,
docForm
}
=
useContext
(
DocumentContext
)
...
...
@@ -241,11 +247,6 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal
getSegments
(
false
)
}
useEffect
(()
=>
{
if
(
lastSegmentsRes
!==
undefined
)
getSegments
(
false
)
},
[
selectedStatus
,
searchValue
])
const
onClickCard
=
(
detail
:
SegmentDetailModel
)
=>
{
setCurrSegment
({
segInfo
:
detail
,
showModal
:
true
})
}
...
...
@@ -319,6 +320,16 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal
setAllSegments
([...
allSegments
])
}
useEffect
(()
=>
{
if
(
lastSegmentsRes
!==
undefined
)
getSegments
(
false
)
},
[
selectedStatus
,
searchValue
])
useEffect
(()
=>
{
if
(
importStatus
===
ProcessStatus
.
COMPLETED
)
resetList
()
},
[
importStatus
])
return
(
<>
<
div
className=
{
s
.
docSearchWrapper
}
>
...
...
web/app/components/datasets/documents/detail/index.tsx
View file @
7756f9d4
...
...
@@ -3,7 +3,7 @@ import type { FC } from 'react'
import
React
,
{
useState
}
from
'react'
import
useSWR
from
'swr'
import
{
ArrowLeftIcon
}
from
'@heroicons/react/24/solid'
import
{
createContext
}
from
'use-context-selector'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
{
useTranslation
}
from
'react-i18next'
import
{
useRouter
}
from
'next/navigation'
import
{
omit
}
from
'lodash-es'
...
...
@@ -13,19 +13,14 @@ import s from '../style.module.css'
import
Completed
from
'./completed'
import
Embedding
from
'./embedding'
import
Metadata
from
'./metadata'
import
SegmentAdd
,
{
ProcessStatus
}
from
'./segment-add'
import
BatchModal
from
'./batch-modal'
import
style
from
'./style.module.css'
import
Divider
from
'@/app/components/base/divider'
import
Loading
from
'@/app/components/base/loading'
import
type
{
MetadataType
}
from
'@/service/datasets'
import
{
fetchDocumentDetail
}
from
'@/service/datasets'
export
const
BackCircleBtn
:
FC
<
{
onClick
:
()
=>
void
}
>
=
({
onClick
})
=>
{
return
(
<
div
onClick=
{
onClick
}
className=
{
'rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'
}
>
<
ArrowLeftIcon
className=
'text-primary-600 fill-current stroke-current h-4 w-4'
/>
</
div
>
)
}
import
{
checkSegmentBatchImportProgress
,
fetchDocumentDetail
,
segmentBatchImport
}
from
'@/service/datasets'
import
{
ToastContext
}
from
'@/app/components/base/toast'
export
const
DocumentContext
=
createContext
<
{
datasetId
?:
string
;
documentId
?:
string
;
docForm
:
string
}
>
({
docForm
:
''
})
...
...
@@ -51,10 +46,45 @@ type Props = {
}
const
DocumentDetail
:
FC
<
Props
>
=
({
datasetId
,
documentId
})
=>
{
const
{
t
}
=
useTranslation
()
const
router
=
useRouter
()
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
[
showMetadata
,
setShowMetadata
]
=
useState
(
true
)
const
[
showNewSegmentModal
,
setShowNewSegmentModal
]
=
useState
(
false
)
const
[
newSegmentModalVisible
,
setNewSegmentModalVisible
]
=
useState
(
false
)
const
[
batchModalVisible
,
setBatchModalVisible
]
=
useState
(
false
)
const
[
importStatus
,
setImportStatus
]
=
useState
<
ProcessStatus
|
string
>
()
const
showNewSegmentModal
=
()
=>
setNewSegmentModalVisible
(
true
)
const
showBatchModal
=
()
=>
setBatchModalVisible
(
true
)
const
hideBatchModal
=
()
=>
setBatchModalVisible
(
false
)
const
resetProcessStatus
=
()
=>
setImportStatus
(
''
)
const
checkProcess
=
async
(
jobID
:
string
)
=>
{
try
{
const
res
=
await
checkSegmentBatchImportProgress
({
jobID
})
setImportStatus
(
res
.
job_status
)
if
(
res
.
job_status
===
ProcessStatus
.
WAITING
||
res
.
job_status
===
ProcessStatus
.
PROCESSING
)
setTimeout
(()
=>
checkProcess
(
res
.
job_id
),
2500
)
if
(
res
.
job_status
===
ProcessStatus
.
ERROR
)
notify
({
type
:
'error'
,
message
:
`
${
t
(
'datasetDocuments.list.batchModal.runError'
)}
`
})
}
catch
(
e
:
any
)
{
notify
({
type
:
'error'
,
message
:
`
${
t
(
'datasetDocuments.list.batchModal.runError'
)}${
'message'
in
e
?
`:
${
e
.
message
}
`
:
''
}
`
})
}
}
const
runBatch
=
async
(
csv
:
File
)
=>
{
const
formData
=
new
FormData
()
formData
.
append
(
'file'
,
csv
)
try
{
const
res
=
await
segmentBatchImport
({
url
:
`/datasets/
${
datasetId
}
/documents/
${
documentId
}
/segments/batch_import`
,
body
:
formData
,
})
setImportStatus
(
res
.
job_status
)
checkProcess
(
res
.
job_id
)
}
catch
(
e
:
any
)
{
notify
({
type
:
'error'
,
message
:
`
${
t
(
'datasetDocuments.list.batchModal.runError'
)}${
'message'
in
e
?
`:
${
e
.
message
}
`
:
''
}
`
})
}
}
const
{
data
:
documentDetail
,
error
,
mutate
:
detailMutate
}
=
useSWR
({
action
:
'fetchDocumentDetail'
,
...
...
@@ -91,22 +121,30 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
<
DocumentContext
.
Provider
value=
{
{
datasetId
,
documentId
,
docForm
:
documentDetail
?.
doc_form
||
''
}
}
>
<
div
className=
'flex flex-col h-full'
>
<
div
className=
'flex h-16 border-b-gray-100 border-b items-center p-4'
>
<
BackCircleBtn
onClick=
{
backToPrev
}
/>
<
div
onClick=
{
backToPrev
}
className=
{
'rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'
}
>
<
ArrowLeftIcon
className=
'text-primary-600 fill-current stroke-current h-4 w-4'
/>
</
div
>
<
Divider
className=
'!h-4'
type=
'vertical'
/>
<
DocumentTitle
extension=
{
documentDetail
?.
data_source_info
?.
upload_file
?.
extension
}
name=
{
documentDetail
?.
name
}
/>
<
StatusItem
status=
{
documentDetail
?.
display_status
||
'available'
}
scene=
'detail'
/>
<
SegmentAdd
importStatus=
{
importStatus
}
clearProcessStatus=
{
resetProcessStatus
}
showNewSegmentModal=
{
showNewSegmentModal
}
showBatchModal=
{
showBatchModal
}
/>
<
OperationAction
scene=
'detail'
detail=
{
{
enabled
:
documentDetail
?.
enabled
||
false
,
archived
:
documentDetail
?.
archived
||
false
,
id
:
documentId
,
data_source_type
:
documentDetail
?.
data_source_type
||
''
,
doc_form
:
documentDetail
?.
doc_form
||
''
,
}
}
datasetId=
{
datasetId
}
onUpdate=
{
handleOperate
}
className=
'!w-[216px]'
showNewSegmentModal=
{
()
=>
setShowNewSegmentModal
(
true
)
}
/>
<
button
className=
{
cn
(
style
.
layoutRightIcon
,
showMetadata
?
style
.
iconShow
:
style
.
iconClose
)
}
...
...
@@ -120,8 +158,9 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
{
embedding
?
<
Embedding
detail=
{
documentDetail
}
detailUpdate=
{
detailMutate
}
/>
:
<
Completed
showNewSegmentModal=
{
showNewSegmentModal
}
onNewSegmentModalChange=
{
setShowNewSegmentModal
}
showNewSegmentModal=
{
newSegmentModalVisible
}
onNewSegmentModalChange=
{
setNewSegmentModalVisible
}
importStatus=
{
importStatus
}
/>
}
</
div
>
...
...
@@ -132,6 +171,11 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
onUpdate=
{
metadataMutate
}
/>
}
</
div
>
<
BatchModal
isShow=
{
batchModalVisible
}
onCancel=
{
hideBatchModal
}
onConfirm=
{
runBatch
}
/>
</
div
>
</
DocumentContext
.
Provider
>
)
...
...
web/app/components/datasets/documents/detail/segment-add/index.tsx
0 → 100644
View file @
7756f9d4
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
cn
from
'classnames'
import
{
FilePlus02
}
from
'@/app/components/base/icons/src/vender/line/files'
import
{
Loading02
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
AlertCircle
}
from
'@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import
{
CheckCircle
}
from
'@/app/components/base/icons/src/vender/solid/general'
import
Popover
from
'@/app/components/base/popover'
export
type
ISegmentAddProps
=
{
importStatus
:
ProcessStatus
|
string
|
undefined
clearProcessStatus
:
()
=>
void
showNewSegmentModal
:
()
=>
void
showBatchModal
:
()
=>
void
}
export
enum
ProcessStatus
{
WAITING
=
'waiting'
,
PROCESSING
=
'processing'
,
COMPLETED
=
'completed'
,
ERROR
=
'error'
,
}
const
SegmentAdd
:
FC
<
ISegmentAddProps
>
=
({
importStatus
,
clearProcessStatus
,
showNewSegmentModal
,
showBatchModal
,
})
=>
{
const
{
t
}
=
useTranslation
()
if
(
importStatus
)
{
return
(
<>
{
(
importStatus
===
ProcessStatus
.
WAITING
||
importStatus
===
ProcessStatus
.
PROCESSING
)
&&
(
<
div
className=
'relative overflow-hidden inline-flex items-center mr-2 px-3 py-[6px] text-blue-700 bg-[#F5F8FF] rounded-lg border border-black/5'
>
{
importStatus
===
ProcessStatus
.
WAITING
&&
<
div
className=
'absolute left-0 top-0 w-3/12 h-full bg-[#D1E0FF] z-0'
/>
}
{
importStatus
===
ProcessStatus
.
PROCESSING
&&
<
div
className=
'absolute left-0 top-0 w-2/3 h-full bg-[#D1E0FF] z-0'
/>
}
<
Loading02
className=
'animate-spin mr-2 w-4 h-4'
/>
<
span
className=
'font-medium text-[13px] leading-[18px] z-10'
>
{
t
(
'datasetDocuments.list.batchModal.processing'
)
}
</
span
>
</
div
>
)
}
{
importStatus
===
ProcessStatus
.
COMPLETED
&&
(
<
div
className=
'inline-flex items-center mr-2 px-3 py-[6px] text-gray-700 bg-[#F6FEF9] rounded-lg border border-black/5'
>
<
CheckCircle
className=
'mr-2 w-4 h-4 text-[#039855]'
/>
<
span
className=
'font-medium text-[13px] leading-[18px]'
>
{
t
(
'datasetDocuments.list.batchModal.completed'
)
}
</
span
>
<
span
className=
'pl-2 font-medium text-[13px] leading-[18px] text-[#155EEF] cursor-pointer'
onClick=
{
clearProcessStatus
}
>
{
t
(
'datasetDocuments.list.batchModal.ok'
)
}
</
span
>
</
div
>
)
}
{
importStatus
===
ProcessStatus
.
ERROR
&&
(
<
div
className=
'inline-flex items-center mr-2 px-3 py-[6px] text-red-600 bg-red-100 rounded-lg border border-black/5'
>
<
AlertCircle
className=
'mr-2 w-4 h-4 text-[#D92D20]'
/>
<
span
className=
'font-medium text-[13px] leading-[18px]'
>
{
t
(
'datasetDocuments.list.batchModal.error'
)
}
</
span
>
<
span
className=
'pl-2 font-medium text-[13px] leading-[18px] text-[#155EEF] cursor-pointer'
onClick=
{
clearProcessStatus
}
>
{
t
(
'datasetDocuments.list.batchModal.ok'
)
}
</
span
>
</
div
>
)
}
</>
)
}
return
(
<
Popover
manualClose
trigger=
'click'
htmlContent=
{
<
div
className=
'w-full py-1'
>
<
div
className=
'py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer text-gray-700 text-sm'
onClick=
{
showNewSegmentModal
}
>
{
t
(
'datasetDocuments.list.action.add'
)
}
</
div
>
<
div
className=
'py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer text-gray-700 text-sm'
onClick=
{
showBatchModal
}
>
{
t
(
'datasetDocuments.list.action.batchAdd'
)
}
</
div
>
</
div
>
}
btnElement=
{
<
div
className=
'inline-flex items-center'
>
<
FilePlus02
className=
'w-4 h-4 text-gray-700'
/>
<
span
className=
'pl-1'
>
{
t
(
'datasetDocuments.list.action.addButton'
)
}
</
span
>
</
div
>
}
btnClassName=
{
open
=>
cn
(
'mr-2 !py-[6px] !text-[13px] !leading-[18px] hover:bg-gray-50 border border-gray-200 hover:border-gray-300 hover:shadow-[0_1px_2px_rgba(16,24,40,0.05)]'
,
open
?
'!bg-gray-100 !shadow-none'
:
'!bg-transparent'
)
}
className=
'!w-[200px] h-fit !z-20 !translate-x-0 !left-0'
/>
)
}
export
default
React
.
memo
(
SegmentAdd
)
web/app/components/datasets/documents/list.tsx
View file @
7756f9d4
...
...
@@ -27,7 +27,7 @@ import NotionIcon from '@/app/components/base/notion-icon'
import
ProgressBar
from
'@/app/components/base/progress-bar'
import
{
DataSourceType
,
type
DocumentDisplayStatus
,
type
SimpleDocumentDetail
}
from
'@/models/datasets'
import
type
{
CommonResponse
}
from
'@/models/common'
import
{
FilePlus02
}
from
'@/app/components/base/icons/src/vender/line/files
'
import
{
DotsHorizontal
}
from
'@/app/components/base/icons/src/vender/line/general
'
export
const
SettingsIcon
:
FC
<
{
className
?:
string
}
>
=
({
className
})
=>
{
return
<
svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
className=
{
className
??
''
}
>
...
...
@@ -101,8 +101,7 @@ export const OperationAction: FC<{
onUpdate
:
(
operationName
?:
string
)
=>
void
scene
?:
'list'
|
'detail'
className
?:
string
showNewSegmentModal
?:
()
=>
void
}
>
=
({
datasetId
,
detail
,
onUpdate
,
scene
=
'list'
,
className
=
''
,
showNewSegmentModal
})
=>
{
}
>
=
({
datasetId
,
detail
,
onUpdate
,
scene
=
'list'
,
className
=
''
})
=>
{
const
{
id
,
enabled
=
false
,
archived
=
false
,
data_source_type
}
=
detail
||
{}
const
[
showModal
,
setShowModal
]
=
useState
(
false
)
const
{
notify
}
=
useContext
(
ToastContext
)
...
...
@@ -191,22 +190,12 @@ export const OperationAction: FC<{
<
SettingsIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.settings'
)
}
</
span
>
</
div
>
{
!
isListScene
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
showNewSegmentModal
}
>
<
FilePlus02
className=
'w-4 h-4 text-gray-500'
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.add'
)
}
</
span
>
</
div
>
)
}
{
data_source_type
===
'notion_import'
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'sync'
)
}
>
<
SyncIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.sync'
)
}
</
span
>
</
div
>
)
}
{
data_source_type
===
'notion_import'
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'sync'
)
}
>
<
SyncIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.sync'
)
}
</
span
>
</
div
>
)
}
<
Divider
className=
'my-1'
/>
</>
)
}
...
...
@@ -228,7 +217,11 @@ export const OperationAction: FC<{
}
trigger=
'click'
position=
'br'
btnElement=
{
<
div
className=
{
cn
(
s
.
actionIcon
,
s
.
commonIcon
)
}
/>
}
btnElement=
{
<
div
className=
{
cn
(
s
.
commonIcon
)
}
>
<
DotsHorizontal
className=
'w-4 h-4 text-gray-700'
/>
</
div
>
}
btnClassName=
{
open
=>
cn
(
isListScene
?
s
.
actionIconWrapperList
:
s
.
actionIconWrapperDetail
,
open
?
'!bg-gray-100 !shadow-none'
:
'!bg-transparent'
)
}
className=
{
`!w-[200px] h-fit !z-20 ${className}`
}
/>
...
...
web/app/components/datasets/documents/style.module.css
View file @
7756f9d4
...
...
@@ -56,10 +56,6 @@
background-position
:
center
center
;
background-size
:
contain
;
}
.actionIcon
{
@apply
bg-gray-500;
mask-image
:
url(./assets/action.svg)
;
}
.pdfIcon
{
background-image
:
url(./assets/pdf.svg)
;
}
...
...
web/i18n/lang/dataset-documents.en.ts
View file @
7756f9d4
...
...
@@ -17,7 +17,9 @@ const translation = {
action
:
{
uploadFile
:
'Upload new file'
,
settings
:
'Segment settings'
,
add
:
'Add new segment'
,
addButton
:
'Add segment'
,
add
:
'Add a segment'
,
batchAdd
:
'Batch add'
,
archive
:
'Archive'
,
unarchive
:
'Unarchive'
,
delete
:
'Delete'
,
...
...
@@ -54,6 +56,22 @@ const translation = {
title
:
'Are you sure Delete?'
,
content
:
'If you need to resume processing later, you will continue from where you left off'
,
},
batchModal
:
{
title
:
'Batch add segments'
,
csvUploadTitle
:
'Drag and drop your CSV file here, or '
,
browse
:
'browse'
,
tip
:
'The CSV file must conform to the following structure:'
,
question
:
'Question'
,
answer
:
'Answer'
,
template
:
'Download the template here'
,
cancel
:
'Cancel'
,
run
:
'Run Batch'
,
runError
:
'Run batch failed'
,
processing
:
'In batch processing'
,
completed
:
'Import completed'
,
error
:
'Import Error'
,
ok
:
'OK'
,
},
},
metadata
:
{
title
:
'Metadata'
,
...
...
web/i18n/lang/dataset-documents.zh.ts
View file @
7756f9d4
...
...
@@ -17,7 +17,9 @@ const translation = {
action
:
{
uploadFile
:
'上传新文件'
,
settings
:
'分段设置'
,
addButton
:
'添加分段'
,
add
:
'添加新分段'
,
batchAdd
:
'批量添加'
,
archive
:
'归档'
,
unarchive
:
'撤销归档'
,
delete
:
'删除'
,
...
...
@@ -54,6 +56,22 @@ const translation = {
title
:
'确定删除吗?'
,
content
:
'如果您需要稍后恢复处理,您将从您离开的地方继续'
,
},
batchModal
:
{
title
:
'批量添加分段'
,
csvUploadTitle
:
'将您的 CSV 文件拖放到此处,或'
,
browse
:
'选择文件'
,
tip
:
'CSV 文件必须符合以下结构:'
,
question
:
'问题'
,
answer
:
'回答'
,
template
:
'下载模板'
,
cancel
:
'取消'
,
run
:
'导入'
,
runError
:
'批量导入失败'
,
processing
:
'批量处理中'
,
completed
:
'导入完成'
,
error
:
'导入出错'
,
ok
:
'确定'
,
},
},
metadata
:
{
title
:
'元数据'
,
...
...
web/service/datasets.ts
View file @
7756f9d4
...
...
@@ -142,10 +142,6 @@ export const modifyDocMetadata: Fetcher<CommonResponse, CommonDocReq & { body: {
return
put
(
`/datasets/
${
datasetId
}
/documents/
${
documentId
}
/metadata`
,
{
body
})
as
Promise
<
CommonResponse
>
}
export
const
getDatasetIndexingStatus
:
Fetcher
<
{
data
:
IndexingStatusResponse
[]
},
string
>
=
(
datasetId
)
=>
{
return
get
(
`/datasets/
${
datasetId
}
/indexing-status`
)
as
Promise
<
{
data
:
IndexingStatusResponse
[]
}
>
}
// apis for segments in a document
export
const
fetchSegments
:
Fetcher
<
SegmentsResponse
,
CommonDocReq
&
{
params
:
SegmentsQuery
}
>
=
({
datasetId
,
documentId
,
params
})
=>
{
...
...
@@ -172,6 +168,14 @@ export const deleteSegment: Fetcher<CommonResponse, { datasetId: string; documen
return
del
(
`/datasets/
${
datasetId
}
/documents/
${
documentId
}
/segments/
${
segmentId
}
`
)
as
Promise
<
CommonResponse
>
}
export
const
segmentBatchImport
:
Fetcher
<
{
job_id
:
string
;
job_status
:
string
},
{
url
:
string
;
body
:
FormData
}
>
=
({
url
,
body
})
=>
{
return
post
(
url
,
{
body
},
{
bodyStringify
:
false
,
deleteContentType
:
true
})
as
Promise
<
{
job_id
:
string
;
job_status
:
string
}
>
}
export
const
checkSegmentBatchImportProgress
:
Fetcher
<
{
job_id
:
string
;
job_status
:
string
},
{
jobID
:
string
}
>
=
({
jobID
})
=>
{
return
get
(
`/datasets/batch_import_status/
${
jobID
}
`
)
as
Promise
<
{
job_id
:
string
;
job_status
:
string
}
>
}
// hit testing
export
const
hitTesting
:
Fetcher
<
HitTestingResponse
,
{
datasetId
:
string
;
queryText
:
string
}
>
=
({
datasetId
,
queryText
})
=>
{
return
post
(
`/datasets/
${
datasetId
}
/hit-testing`
,
{
body
:
{
query
:
queryText
}
})
as
Promise
<
HitTestingResponse
>
...
...
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