feat: add empty state handling and translations for segment list
This commit is contained in:
parent
f7d6dbe90b
commit
f1782664b6
@ -74,7 +74,7 @@ const DocumentPicker: FC<Props> = ({
|
|||||||
placement='bottom-start'
|
placement='bottom-start'
|
||||||
>
|
>
|
||||||
<PortalToFollowElemTrigger onClick={togglePopup}>
|
<PortalToFollowElemTrigger onClick={togglePopup}>
|
||||||
<div className={cn('flex items-center ml-1 px-2 py-0.5 rounded-lg hover:bg-state-base-hover select-none', open && 'bg-state-base-hover')}>
|
<div className={cn('flex items-center ml-1 px-2 py-0.5 rounded-lg hover:bg-state-base-hover select-none cursor-pointer', open && 'bg-state-base-hover')}>
|
||||||
<FileIcon name={name} extension={extension} size='lg' />
|
<FileIcon name={name} extension={extension} size='lg' />
|
||||||
<div className='flex flex-col items-start ml-1 mr-0.5'>
|
<div className='flex flex-col items-start ml-1 mr-0.5'>
|
||||||
<div className='flex items-center space-x-0.5'>
|
<div className='flex items-center space-x-0.5'>
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import React, { type FC } from 'react'
|
||||||
|
import { RiFileList2Line } from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type IEmptyProps = {
|
||||||
|
onClearFilter: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmptyCard = React.memo(() => {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-32 rounded-xl opacity-30 bg-background-section-burn shrink-0' />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
EmptyCard.displayName = 'EmptyCard'
|
||||||
|
|
||||||
|
type LineProps = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Line = React.memo(({
|
||||||
|
className,
|
||||||
|
}: LineProps) => {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="2" height="241" viewBox="0 0 2 241" fill="none" className={className}>
|
||||||
|
<path d="M1 0.5L1 240.5" stroke="url(#paint0_linear_1989_74474)"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_1989_74474" x1="-7.99584" y1="240.5" x2="-7.88094" y2="0.50004" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stopColor="white" stopOpacity="0.01"/>
|
||||||
|
<stop offset="0.503965" stopColor="#101828" stopOpacity="0.08"/>
|
||||||
|
<stop offset="1" stopColor="white" stopOpacity="0.01"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
Line.displayName = 'Line'
|
||||||
|
|
||||||
|
const Empty: FC<IEmptyProps> = ({
|
||||||
|
onClearFilter,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'h-full relative flex items-center justify-center z-0'}>
|
||||||
|
<div className='flex flex-col items-center'>
|
||||||
|
<div className='relative z-10 flex items-center justify-center w-14 h-14 border border-divider-subtle bg-components-card-bg rounded-xl shadow-lg shadow-shadow-shadow-5'>
|
||||||
|
<RiFileList2Line className='w-6 h-6 text-text-secondary' />
|
||||||
|
<Line className='absolute -right-[1px] top-1/2 -translate-y-1/2' />
|
||||||
|
<Line className='absolute -left-[1px] top-1/2 -translate-y-1/2' />
|
||||||
|
<Line className='absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
|
||||||
|
<Line className='absolute top-full left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
|
||||||
|
</div>
|
||||||
|
<div className='text-text-tertiary system-md-regular mt-3'>
|
||||||
|
{t('datasetDocuments.segment.empty')}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className='text-text-accent system-sm-medium mt-1'
|
||||||
|
onClick={onClearFilter}
|
||||||
|
>
|
||||||
|
{t('datasetDocuments.segment.clearFilter')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full absolute top-0 left-0 flex flex-col gap-y-3 -z-20 overflow-hidden'>
|
||||||
|
{
|
||||||
|
Array.from({ length: 10 }).map((_, i) => (
|
||||||
|
<EmptyCard key={i} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full absolute top-0 left-0 bg-dataset-chunk-list-empty-bg -z-10' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(Empty)
|
@ -106,6 +106,7 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
|
|
||||||
const { run: handleSearch } = useDebounceFn(() => {
|
const { run: handleSearch } = useDebounceFn(() => {
|
||||||
setSearchValue(inputValue)
|
setSearchValue(inputValue)
|
||||||
|
setCurrentPage(1)
|
||||||
}, { wait: 500 })
|
}, { wait: 500 })
|
||||||
|
|
||||||
const handleInputChange = (value: string) => {
|
const handleInputChange = (value: string) => {
|
||||||
@ -115,6 +116,7 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
|
|
||||||
const onChangeStatus = ({ value }: Item) => {
|
const onChangeStatus = ({ value }: Item) => {
|
||||||
setSelectedStatus(value === 'all' ? 'all' : !!value)
|
setSelectedStatus(value === 'all' ? 'all' : !!value)
|
||||||
|
setCurrentPage(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFullDocMode = useMemo(() => {
|
const isFullDocMode = useMemo(() => {
|
||||||
@ -132,6 +134,7 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
enabled: selectedStatus === 'all' ? 'all' : !!selectedStatus,
|
enabled: selectedStatus === 'all' ? 'all' : !!selectedStatus,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentPage === 0,
|
||||||
)
|
)
|
||||||
const invalidSegmentList = useInvalid(useSegmentListKey)
|
const invalidSegmentList = useInvalid(useSegmentListKey)
|
||||||
|
|
||||||
@ -162,7 +165,7 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
keyword: searchValue,
|
keyword: searchValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
!isFullDocMode || segments.length === 0,
|
!isFullDocMode || segments.length === 0 || currentPage === 0,
|
||||||
)
|
)
|
||||||
const invalidChildSegmentList = useInvalid(useChildSegmentListKey)
|
const invalidChildSegmentList = useInvalid(useChildSegmentListKey)
|
||||||
|
|
||||||
@ -174,8 +177,12 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
}, [childSegments])
|
}, [childSegments])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (childChunkListData)
|
if (childChunkListData) {
|
||||||
setChildSegments(childChunkListData.data || [])
|
setChildSegments(childChunkListData.data || [])
|
||||||
|
if (childChunkListData.total_pages < currentPage)
|
||||||
|
setCurrentPage(childChunkListData.total_pages)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [childChunkListData])
|
}, [childChunkListData])
|
||||||
|
|
||||||
const resetList = useCallback(() => {
|
const resetList = useCallback(() => {
|
||||||
@ -328,12 +335,20 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
}, [segments, isAllSelected, selectedSegmentIds])
|
}, [segments, isAllSelected, selectedSegmentIds])
|
||||||
|
|
||||||
const totalText = useMemo(() => {
|
const totalText = useMemo(() => {
|
||||||
|
const isSearch = searchValue !== '' || selectedStatus !== 'all'
|
||||||
|
if (!isSearch) {
|
||||||
const total = segmentListData?.total ? formatNumber(segmentListData.total) : '--'
|
const total = segmentListData?.total ? formatNumber(segmentListData.total) : '--'
|
||||||
const count = total === '--' ? 0 : segmentListData!.total
|
const count = total === '--' ? 0 : segmentListData!.total
|
||||||
const translationKey = (mode === 'hierarchical' && parentMode === 'paragraph')
|
const translationKey = (mode === 'hierarchical' && parentMode === 'paragraph')
|
||||||
? 'datasetDocuments.segment.parentChunks'
|
? 'datasetDocuments.segment.parentChunks'
|
||||||
: 'datasetDocuments.segment.chunks'
|
: 'datasetDocuments.segment.chunks'
|
||||||
return `${total} ${t(translationKey, { count })}`
|
return `${total} ${t(translationKey, { count })}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const total = typeof segmentListData?.total === 'number' ? formatNumber(segmentListData.total) : 0
|
||||||
|
const count = segmentListData?.total || 0
|
||||||
|
return `${total} ${t('datasetDocuments.segment.searchResults', { count })}`
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [segmentListData?.total, mode, parentMode])
|
}, [segmentListData?.total, mode, parentMode])
|
||||||
|
|
||||||
@ -472,6 +487,13 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [segments, childSegments, datasetId, documentId, parentMode])
|
}, [segments, childSegments, datasetId, documentId, parentMode])
|
||||||
|
|
||||||
|
const onClearFilter = useCallback(() => {
|
||||||
|
setInputValue('')
|
||||||
|
setSearchValue('')
|
||||||
|
setSelectedStatus('all')
|
||||||
|
setCurrentPage(1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SegmentListContext.Provider value={{
|
<SegmentListContext.Provider value={{
|
||||||
isCollapsed,
|
isCollapsed,
|
||||||
@ -543,6 +565,7 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
onDeleteChildChunk={onDeleteChildChunk}
|
onDeleteChildChunk={onDeleteChildChunk}
|
||||||
handleAddNewChildChunk={handleAddNewChildChunk}
|
handleAddNewChildChunk={handleAddNewChildChunk}
|
||||||
onClickSlice={onClickSlice}
|
onClickSlice={onClickSlice}
|
||||||
|
onClearFilter={onClearFilter}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { type ForwardedRef } from 'react'
|
import React, { type ForwardedRef } from 'react'
|
||||||
import SegmentCard from './segment-card'
|
import SegmentCard from './segment-card'
|
||||||
|
import Empty from './common/empty'
|
||||||
import type { ChildChunkDetail, SegmentDetailModel } from '@/models/datasets'
|
import type { ChildChunkDetail, SegmentDetailModel } from '@/models/datasets'
|
||||||
import Checkbox from '@/app/components/base/checkbox'
|
import Checkbox from '@/app/components/base/checkbox'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
@ -19,6 +20,7 @@ type ISegmentListProps = {
|
|||||||
onClickSlice: (childChunk: ChildChunkDetail) => void
|
onClickSlice: (childChunk: ChildChunkDetail) => void
|
||||||
archived?: boolean
|
archived?: boolean
|
||||||
embeddingAvailable: boolean
|
embeddingAvailable: boolean
|
||||||
|
onClearFilter: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SegmentList = React.forwardRef(({
|
const SegmentList = React.forwardRef(({
|
||||||
@ -34,11 +36,19 @@ const SegmentList = React.forwardRef(({
|
|||||||
onClickSlice,
|
onClickSlice,
|
||||||
archived,
|
archived,
|
||||||
embeddingAvailable,
|
embeddingAvailable,
|
||||||
|
onClearFilter,
|
||||||
}: ISegmentListProps,
|
}: ISegmentListProps,
|
||||||
ref: ForwardedRef<HTMLDivElement>,
|
ref: ForwardedRef<HTMLDivElement>,
|
||||||
) => {
|
) => {
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return <Loading type='app' />
|
return <Loading type='app' />
|
||||||
|
if (items.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className='h-full pl-6'>
|
||||||
|
<Empty onClearFilter={onClearFilter} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={classNames('flex flex-col h-full overflow-y-auto')}>
|
<div ref={ref} className={classNames('flex flex-col h-full overflow-y-auto')}>
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
@apply text-text-secondary flex-1;
|
@apply text-text-secondary flex-1;
|
||||||
}
|
}
|
||||||
.docSearchWrapper {
|
.docSearchWrapper {
|
||||||
@apply sticky w-full -top-3 bg-white flex items-center mb-3 justify-between z-10 flex-wrap gap-y-1;
|
@apply sticky w-full -top-3 bg-white flex items-center mb-3 justify-between z-10 flex-wrap gap-y-1 pr-3;
|
||||||
}
|
}
|
||||||
.listContainer {
|
.listContainer {
|
||||||
height: calc(100% - 3.25rem);
|
height: calc(100% - 3.25rem);
|
||||||
|
@ -55,7 +55,7 @@ type DocumentTitleProps = {
|
|||||||
export const DocumentTitle: FC<DocumentTitleProps> = ({ datasetId, extension, name, processMode, parent_mode, wrapperCls }) => {
|
export const DocumentTitle: FC<DocumentTitleProps> = ({ datasetId, extension, name, processMode, parent_mode, wrapperCls }) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex items-center justify-start flex-1 cursor-pointer', wrapperCls)}>
|
<div className={cn('flex items-center justify-start flex-1', wrapperCls)}>
|
||||||
<DocumentPicker
|
<DocumentPicker
|
||||||
datasetId={datasetId}
|
datasetId={datasetId}
|
||||||
value={{
|
value={{
|
||||||
|
@ -339,6 +339,11 @@ const translation = {
|
|||||||
parentChunks_other: 'PARENT CHUNKS',
|
parentChunks_other: 'PARENT CHUNKS',
|
||||||
childChunks_one: 'CHILD CHUNK',
|
childChunks_one: 'CHILD CHUNK',
|
||||||
childChunks_other: 'CHILD CHUNKS',
|
childChunks_other: 'CHILD CHUNKS',
|
||||||
|
searchResults_zero: 'RESULT',
|
||||||
|
searchResults_one: 'RESULT',
|
||||||
|
searchResults_other: 'RESULTS',
|
||||||
|
empty: 'No Chunk found',
|
||||||
|
clearFilter: 'Clear filter',
|
||||||
chunk: 'Chunk',
|
chunk: 'Chunk',
|
||||||
parentChunk: 'Parent-Chunk',
|
parentChunk: 'Parent-Chunk',
|
||||||
childChunk: 'Child-Chunk',
|
childChunk: 'Child-Chunk',
|
||||||
|
@ -337,6 +337,11 @@ const translation = {
|
|||||||
parentChunks_other: '父分段',
|
parentChunks_other: '父分段',
|
||||||
childChunks_one: '子分段',
|
childChunks_one: '子分段',
|
||||||
childChunks_other: '子分段',
|
childChunks_other: '子分段',
|
||||||
|
searchResults_zero: '搜索结果',
|
||||||
|
searchResults_one: '搜索结果',
|
||||||
|
searchResults_other: '搜索结果',
|
||||||
|
empty: '未找到分段',
|
||||||
|
clearFilter: '清空搜索条件',
|
||||||
chunk: '分段',
|
chunk: '分段',
|
||||||
parentChunk: '父分段',
|
parentChunk: '父分段',
|
||||||
childChunk: '子分段',
|
childChunk: '子分段',
|
||||||
@ -358,7 +363,7 @@ const translation = {
|
|||||||
newQaSegment: '新问答分段',
|
newQaSegment: '新问答分段',
|
||||||
addChunk: '新增分段',
|
addChunk: '新增分段',
|
||||||
addChildChunk: '新增子分段',
|
addChildChunk: '新增子分段',
|
||||||
addAnother: '继续新增',
|
addAnother: '连续新增',
|
||||||
delete: '删除这个分段?',
|
delete: '删除这个分段?',
|
||||||
chunkAdded: '新增一个分段',
|
chunkAdded: '新增一个分段',
|
||||||
childChunkAdded: '新增一个子分段',
|
childChunkAdded: '新增一个子分段',
|
||||||
|
@ -28,7 +28,6 @@ export const useSegmentList = (
|
|||||||
return get<SegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments`, { params })
|
return get<SegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments`, { params })
|
||||||
},
|
},
|
||||||
enabled: !disable,
|
enabled: !disable,
|
||||||
initialData: disable ? { data: [], has_more: false, page: 1, total: 0, total_pages: 0, limit: 10 } : undefined,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +87,6 @@ export const useChildSegmentList = (
|
|||||||
return get<ChildSegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, { params })
|
return get<ChildSegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, { params })
|
||||||
},
|
},
|
||||||
enabled: !disable,
|
enabled: !disable,
|
||||||
initialData: disable ? { data: [], total: 0, page: 1, total_pages: 0, limit: 10 } : undefined,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,7 @@ const config = {
|
|||||||
'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)',
|
'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)',
|
||||||
'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)',
|
'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)',
|
||||||
'dataset-child-chunk-expand-btn-bg': 'var(--color-dataset-child-chunk-expand-btn-bg)',
|
'dataset-child-chunk-expand-btn-bg': 'var(--color-dataset-child-chunk-expand-btn-bg)',
|
||||||
|
'dataset-chunk-list-empty-bg': 'var(--color-dataset-chunk-list-empty-bg)',
|
||||||
},
|
},
|
||||||
lineClamp: {
|
lineClamp: {
|
||||||
20: '20',
|
20: '20',
|
||||||
|
@ -9,4 +9,5 @@ html[data-theme="dark"] {
|
|||||||
--color-dataset-option-card-blue-gradient: linear-gradient(180deg, #24252E 0%, #1E1E21 100%);
|
--color-dataset-option-card-blue-gradient: linear-gradient(180deg, #24252E 0%, #1E1E21 100%);
|
||||||
--color-dataset-option-card-purple-gradient: linear-gradient(180deg, #25242E 0%, #1E1E21 100%);
|
--color-dataset-option-card-purple-gradient: linear-gradient(180deg, #25242E 0%, #1E1E21 100%);
|
||||||
--color-dataset-option-card-orange-gradient: linear-gradient(180deg, #2B2322 0%, #1E1E21 100%);
|
--color-dataset-option-card-orange-gradient: linear-gradient(180deg, #2B2322 0%, #1E1E21 100%);
|
||||||
|
--color-dataset-chunk-list-empty-bg: linear-gradient(180deg, rgba(34, 34, 37, 0.00) 0%, #222225 100%);
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,5 @@ html[data-theme="light"] {
|
|||||||
--color-dataset-option-card-blue-gradient: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%);
|
--color-dataset-option-card-blue-gradient: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%);
|
||||||
--color-dataset-option-card-purple-gradient: linear-gradient(180deg, #F0EEFA 0%, #F9FAFB 100%);
|
--color-dataset-option-card-purple-gradient: linear-gradient(180deg, #F0EEFA 0%, #F9FAFB 100%);
|
||||||
--color-dataset-option-card-orange-gradient: linear-gradient(180deg, #F8F2EE 0%, #F9FAFB 100%);
|
--color-dataset-option-card-orange-gradient: linear-gradient(180deg, #F8F2EE 0%, #F9FAFB 100%);
|
||||||
|
--color-dataset-chunk-list-empty-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, #FCFCFD 100%);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user