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'
|
||||
>
|
||||
<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' />
|
||||
<div className='flex flex-col items-start ml-1 mr-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(() => {
|
||||
setSearchValue(inputValue)
|
||||
setCurrentPage(1)
|
||||
}, { wait: 500 })
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
@ -115,6 +116,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
|
||||
const onChangeStatus = ({ value }: Item) => {
|
||||
setSelectedStatus(value === 'all' ? 'all' : !!value)
|
||||
setCurrentPage(1)
|
||||
}
|
||||
|
||||
const isFullDocMode = useMemo(() => {
|
||||
@ -132,6 +134,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
enabled: selectedStatus === 'all' ? 'all' : !!selectedStatus,
|
||||
},
|
||||
},
|
||||
currentPage === 0,
|
||||
)
|
||||
const invalidSegmentList = useInvalid(useSegmentListKey)
|
||||
|
||||
@ -162,7 +165,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
keyword: searchValue,
|
||||
},
|
||||
},
|
||||
!isFullDocMode || segments.length === 0,
|
||||
!isFullDocMode || segments.length === 0 || currentPage === 0,
|
||||
)
|
||||
const invalidChildSegmentList = useInvalid(useChildSegmentListKey)
|
||||
|
||||
@ -174,8 +177,12 @@ const Completed: FC<ICompletedProps> = ({
|
||||
}, [childSegments])
|
||||
|
||||
useEffect(() => {
|
||||
if (childChunkListData)
|
||||
if (childChunkListData) {
|
||||
setChildSegments(childChunkListData.data || [])
|
||||
if (childChunkListData.total_pages < currentPage)
|
||||
setCurrentPage(childChunkListData.total_pages)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [childChunkListData])
|
||||
|
||||
const resetList = useCallback(() => {
|
||||
@ -328,12 +335,20 @@ const Completed: FC<ICompletedProps> = ({
|
||||
}, [segments, isAllSelected, selectedSegmentIds])
|
||||
|
||||
const totalText = useMemo(() => {
|
||||
const total = segmentListData?.total ? formatNumber(segmentListData.total) : '--'
|
||||
const count = total === '--' ? 0 : segmentListData!.total
|
||||
const translationKey = (mode === 'hierarchical' && parentMode === 'paragraph')
|
||||
? 'datasetDocuments.segment.parentChunks'
|
||||
: 'datasetDocuments.segment.chunks'
|
||||
return `${total} ${t(translationKey, { count })}`
|
||||
const isSearch = searchValue !== '' || selectedStatus !== 'all'
|
||||
if (!isSearch) {
|
||||
const total = segmentListData?.total ? formatNumber(segmentListData.total) : '--'
|
||||
const count = total === '--' ? 0 : segmentListData!.total
|
||||
const translationKey = (mode === 'hierarchical' && parentMode === 'paragraph')
|
||||
? 'datasetDocuments.segment.parentChunks'
|
||||
: 'datasetDocuments.segment.chunks'
|
||||
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
|
||||
}, [segmentListData?.total, mode, parentMode])
|
||||
|
||||
@ -472,6 +487,13 @@ const Completed: FC<ICompletedProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [segments, childSegments, datasetId, documentId, parentMode])
|
||||
|
||||
const onClearFilter = useCallback(() => {
|
||||
setInputValue('')
|
||||
setSearchValue('')
|
||||
setSelectedStatus('all')
|
||||
setCurrentPage(1)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SegmentListContext.Provider value={{
|
||||
isCollapsed,
|
||||
@ -543,6 +565,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
onDeleteChildChunk={onDeleteChildChunk}
|
||||
handleAddNewChildChunk={handleAddNewChildChunk}
|
||||
onClickSlice={onClickSlice}
|
||||
onClearFilter={onClearFilter}
|
||||
/>
|
||||
}
|
||||
{/* Pagination */}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { type ForwardedRef } from 'react'
|
||||
import SegmentCard from './segment-card'
|
||||
import Empty from './common/empty'
|
||||
import type { ChildChunkDetail, SegmentDetailModel } from '@/models/datasets'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -19,6 +20,7 @@ type ISegmentListProps = {
|
||||
onClickSlice: (childChunk: ChildChunkDetail) => void
|
||||
archived?: boolean
|
||||
embeddingAvailable: boolean
|
||||
onClearFilter: () => void
|
||||
}
|
||||
|
||||
const SegmentList = React.forwardRef(({
|
||||
@ -34,11 +36,19 @@ const SegmentList = React.forwardRef(({
|
||||
onClickSlice,
|
||||
archived,
|
||||
embeddingAvailable,
|
||||
onClearFilter,
|
||||
}: ISegmentListProps,
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
) => {
|
||||
if (isLoading)
|
||||
return <Loading type='app' />
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className='h-full pl-6'>
|
||||
<Empty onClearFilter={onClearFilter} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div ref={ref} className={classNames('flex flex-col h-full overflow-y-auto')}>
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
@apply text-text-secondary flex-1;
|
||||
}
|
||||
.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 {
|
||||
height: calc(100% - 3.25rem);
|
||||
|
@ -55,7 +55,7 @@ type DocumentTitleProps = {
|
||||
export const DocumentTitle: FC<DocumentTitleProps> = ({ datasetId, extension, name, processMode, parent_mode, wrapperCls }) => {
|
||||
const router = useRouter()
|
||||
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
|
||||
datasetId={datasetId}
|
||||
value={{
|
||||
|
@ -339,6 +339,11 @@ const translation = {
|
||||
parentChunks_other: 'PARENT CHUNKS',
|
||||
childChunks_one: 'CHILD CHUNK',
|
||||
childChunks_other: 'CHILD CHUNKS',
|
||||
searchResults_zero: 'RESULT',
|
||||
searchResults_one: 'RESULT',
|
||||
searchResults_other: 'RESULTS',
|
||||
empty: 'No Chunk found',
|
||||
clearFilter: 'Clear filter',
|
||||
chunk: 'Chunk',
|
||||
parentChunk: 'Parent-Chunk',
|
||||
childChunk: 'Child-Chunk',
|
||||
|
@ -337,6 +337,11 @@ const translation = {
|
||||
parentChunks_other: '父分段',
|
||||
childChunks_one: '子分段',
|
||||
childChunks_other: '子分段',
|
||||
searchResults_zero: '搜索结果',
|
||||
searchResults_one: '搜索结果',
|
||||
searchResults_other: '搜索结果',
|
||||
empty: '未找到分段',
|
||||
clearFilter: '清空搜索条件',
|
||||
chunk: '分段',
|
||||
parentChunk: '父分段',
|
||||
childChunk: '子分段',
|
||||
@ -358,7 +363,7 @@ const translation = {
|
||||
newQaSegment: '新问答分段',
|
||||
addChunk: '新增分段',
|
||||
addChildChunk: '新增子分段',
|
||||
addAnother: '继续新增',
|
||||
addAnother: '连续新增',
|
||||
delete: '删除这个分段?',
|
||||
chunkAdded: '新增一个分段',
|
||||
childChunkAdded: '新增一个子分段',
|
||||
|
@ -28,7 +28,6 @@ export const useSegmentList = (
|
||||
return get<SegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments`, { params })
|
||||
},
|
||||
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 })
|
||||
},
|
||||
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-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-chunk-list-empty-bg': 'var(--color-dataset-chunk-list-empty-bg)',
|
||||
},
|
||||
lineClamp: {
|
||||
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-purple-gradient: linear-gradient(180deg, #25242E 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-purple-gradient: linear-gradient(180deg, #F0EEFA 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