From f1782664b6e172cce593bf91b56b44e84bf341d3 Mon Sep 17 00:00:00 2001 From: twwu Date: Fri, 13 Dec 2024 17:38:26 +0800 Subject: [PATCH] feat: add empty state handling and translations for segment list --- .../datasets/common/document-picker/index.tsx | 2 +- .../detail/completed/common/empty.tsx | 77 +++++++++++++++++++ .../documents/detail/completed/index.tsx | 39 ++++++++-- .../detail/completed/segment-list.tsx | 10 +++ .../detail/completed/style.module.css | 2 +- .../datasets/documents/detail/index.tsx | 2 +- web/i18n/en-US/dataset-documents.ts | 5 ++ web/i18n/zh-Hans/dataset-documents.ts | 7 +- web/service/knowledge/use-segment.ts | 2 - web/tailwind.config.js | 1 + web/themes/manual-dark.css | 1 + web/themes/manual-light.css | 1 + 12 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 web/app/components/datasets/documents/detail/completed/common/empty.tsx diff --git a/web/app/components/datasets/common/document-picker/index.tsx b/web/app/components/datasets/common/document-picker/index.tsx index cd95314d28..2f816fe5ea 100644 --- a/web/app/components/datasets/common/document-picker/index.tsx +++ b/web/app/components/datasets/common/document-picker/index.tsx @@ -74,7 +74,7 @@ const DocumentPicker: FC = ({ placement='bottom-start' > -
+
diff --git a/web/app/components/datasets/documents/detail/completed/common/empty.tsx b/web/app/components/datasets/documents/detail/completed/common/empty.tsx new file mode 100644 index 0000000000..4b78143fca --- /dev/null +++ b/web/app/components/datasets/documents/detail/completed/common/empty.tsx @@ -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 ( +
+ ) +}) + +EmptyCard.displayName = 'EmptyCard' + +type LineProps = { + className?: string +} + +const Line = React.memo(({ + className, +}: LineProps) => { + return ( + + + + + + + + + + + ) +}) + +Line.displayName = 'Line' + +const Empty: FC = ({ + onClearFilter, +}) => { + const { t } = useTranslation() + + return ( +
+
+
+ + + + + +
+
+ {t('datasetDocuments.segment.empty')} +
+ +
+
+ { + Array.from({ length: 10 }).map((_, i) => ( + + )) + } +
+
+
+ ) +} + +export default React.memo(Empty) diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 3ef87da226..28c945d51a 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -106,6 +106,7 @@ const Completed: FC = ({ const { run: handleSearch } = useDebounceFn(() => { setSearchValue(inputValue) + setCurrentPage(1) }, { wait: 500 }) const handleInputChange = (value: string) => { @@ -115,6 +116,7 @@ const Completed: FC = ({ const onChangeStatus = ({ value }: Item) => { setSelectedStatus(value === 'all' ? 'all' : !!value) + setCurrentPage(1) } const isFullDocMode = useMemo(() => { @@ -132,6 +134,7 @@ const Completed: FC = ({ enabled: selectedStatus === 'all' ? 'all' : !!selectedStatus, }, }, + currentPage === 0, ) const invalidSegmentList = useInvalid(useSegmentListKey) @@ -162,7 +165,7 @@ const Completed: FC = ({ keyword: searchValue, }, }, - !isFullDocMode || segments.length === 0, + !isFullDocMode || segments.length === 0 || currentPage === 0, ) const invalidChildSegmentList = useInvalid(useChildSegmentListKey) @@ -174,8 +177,12 @@ const Completed: FC = ({ }, [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 = ({ }, [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 = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [segments, childSegments, datasetId, documentId, parentMode]) + const onClearFilter = useCallback(() => { + setInputValue('') + setSearchValue('') + setSelectedStatus('all') + setCurrentPage(1) + }, []) + return ( = ({ onDeleteChildChunk={onDeleteChildChunk} handleAddNewChildChunk={handleAddNewChildChunk} onClickSlice={onClickSlice} + onClearFilter={onClearFilter} /> } {/* Pagination */} diff --git a/web/app/components/datasets/documents/detail/completed/segment-list.tsx b/web/app/components/datasets/documents/detail/completed/segment-list.tsx index 4f50b3a050..840acba00e 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-list.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-list.tsx @@ -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, ) => { if (isLoading) return + if (items.length === 0) { + return ( +
+ +
+ ) + } return (
{ diff --git a/web/app/components/datasets/documents/detail/completed/style.module.css b/web/app/components/datasets/documents/detail/completed/style.module.css index 610d959efe..e9bab39782 100644 --- a/web/app/components/datasets/documents/detail/completed/style.module.css +++ b/web/app/components/datasets/documents/detail/completed/style.module.css @@ -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); diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index 8a0b2782a0..1f4b98ee9f 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -55,7 +55,7 @@ type DocumentTitleProps = { export const DocumentTitle: FC = ({ datasetId, extension, name, processMode, parent_mode, wrapperCls }) => { const router = useRouter() return ( -
+
(`/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(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, { params }) }, enabled: !disable, - initialData: disable ? { data: [], total: 0, page: 1, total_pages: 0, limit: 10 } : undefined, }) } diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 8f289c784d..84ae0a94c1 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -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', diff --git a/web/themes/manual-dark.css b/web/themes/manual-dark.css index 9a342897b2..631d13cd78 100644 --- a/web/themes/manual-dark.css +++ b/web/themes/manual-dark.css @@ -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%); } diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css index 2de9edda1e..5477c1f23d 100644 --- a/web/themes/manual-light.css +++ b/web/themes/manual-light.css @@ -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%); }