From 1eb77a4fc343053fcc3bbb10ebbc85d267df96f5 Mon Sep 17 00:00:00 2001 From: CodingOnStar Date: Fri, 23 Jan 2026 11:51:33 +0800 Subject: [PATCH] refactor: improve ChildSegmentList component and enhance Drawer functionality - Simplified state management and rendering logic in ChildSegmentList. - Introduced computeTotalInfo function for better total information handling. - Enhanced Drawer component with improved click handling and overlay behavior. - Refactored event handling to streamline user interactions. --- .../detail/completed/child-segment-list.tsx | 229 +++++++++--------- .../detail/completed/common/drawer.tsx | 93 ++++--- 2 files changed, 177 insertions(+), 145 deletions(-) diff --git a/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx b/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx index b23aac6af9..fd6fd338d0 100644 --- a/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx +++ b/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ChildChunkDetail } from '@/models/datasets' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' -import { useMemo, useState } from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import Input from '@/app/components/base/input' @@ -29,6 +29,37 @@ type IChildSegmentCardProps = { focused?: boolean } +function computeTotalInfo( + isFullDocMode: boolean, + isSearching: boolean, + total: number | undefined, + childChunksLength: number, +): { displayText: string, count: number, translationKey: 'segment.searchResults' | 'segment.childChunks' } { + if (isSearching) { + const count = total ?? 0 + return { + displayText: count === 0 ? '--' : String(formatNumber(count)), + count, + translationKey: 'segment.searchResults', + } + } + + if (isFullDocMode) { + const count = total ?? 0 + return { + displayText: count === 0 ? '--' : String(formatNumber(count)), + count, + translationKey: 'segment.childChunks', + } + } + + return { + displayText: String(formatNumber(childChunksLength)), + count: childChunksLength, + translationKey: 'segment.childChunks', + } +} + const ChildSegmentList: FC = ({ childChunks, parentChunkId, @@ -49,59 +80,87 @@ const ChildSegmentList: FC = ({ const [collapsed, setCollapsed] = useState(true) - const toggleCollapse = () => { - setCollapsed(!collapsed) + const isParagraphMode = parentMode === 'paragraph' + const isFullDocMode = parentMode === 'full-doc' + const isSearching = inputValue !== '' && isFullDocMode + const contentOpacity = (enabled || focused) ? '' : 'opacity-50 group-hover/card:opacity-100' + const { displayText, count, translationKey } = computeTotalInfo(isFullDocMode, isSearching, total, childChunks.length) + const totalText = `${displayText} ${t(translationKey, { ns: 'datasetDocuments', count })}` + + const toggleCollapse = () => setCollapsed(prev => !prev) + const showContent = (isFullDocMode && !isLoading) || !collapsed + const hoverVisibleClass = isParagraphMode ? 'hidden group-hover/card:inline-block' : '' + + const renderCollapseIcon = () => { + if (!isParagraphMode) + return null + const Icon = collapsed ? RiArrowRightSLine : RiArrowDownSLine + return } - const isParagraphMode = useMemo(() => { - return parentMode === 'paragraph' - }, [parentMode]) + const renderChildChunkItem = (childChunk: ChildChunkDetail) => { + const isEdited = childChunk.updated_at !== childChunk.created_at + const isFocused = currChildChunk?.childChunkInfo?.id === childChunk.id + const label = isEdited + ? `C-${childChunk.position} · ${t('segment.edited', { ns: 'datasetDocuments' })}` + : `C-${childChunk.position}` - const isFullDocMode = useMemo(() => { - return parentMode === 'full-doc' - }, [parentMode]) + return ( + onDelete?.(childChunk.segment_id, childChunk.id)} + className="child-chunk" + labelClassName={isFocused ? 'bg-state-accent-solid text-text-primary-on-surface' : ''} + labelInnerClassName="text-[10px] font-semibold align-bottom leading-6" + contentClassName={cn('!leading-6', isFocused ? 'bg-state-accent-hover-alt text-text-primary' : 'text-text-secondary')} + showDivider={false} + onClick={(e) => { + e.stopPropagation() + onClickSlice?.(childChunk) + }} + offsetOptions={({ rects }) => ({ + mainAxis: isFullDocMode ? -rects.floating.width : 12 - rects.floating.width, + crossAxis: (20 - rects.floating.height) / 2, + })} + /> + ) + } - const contentOpacity = useMemo(() => { - return (enabled || focused) ? '' : 'opacity-50 group-hover/card:opacity-100' - }, [enabled, focused]) - - const totalText = useMemo(() => { - const isSearch = inputValue !== '' && isFullDocMode - if (!isSearch) { - const text = isFullDocMode - ? !total - ? '--' - : formatNumber(total) - : formatNumber(childChunks.length) - const count = isFullDocMode - ? text === '--' - ? 0 - : total - : childChunks.length - return `${text} ${t('segment.childChunks', { ns: 'datasetDocuments', count })}` + const renderContent = () => { + if (childChunks.length > 0) { + return ( + + {childChunks.map(renderChildChunkItem)} + + ) } - else { - const text = !total ? '--' : formatNumber(total) - const count = text === '--' ? 0 : total - return `${count} ${t('segment.searchResults', { ns: 'datasetDocuments', count })}` + if (inputValue !== '') { + return ( +
+ +
+ ) } - }, [isFullDocMode, total, childChunks.length, inputValue]) + return null + } return (
- {isFullDocMode ? : null} -
+ {isFullDocMode && } +
{ @@ -109,23 +168,15 @@ const ChildSegmentList: FC = ({ toggleCollapse() }} > - { - isParagraphMode - ? collapsed - ? ( - - ) - : () - : null - } + {renderCollapseIcon()} {totalText} - · + ·
- {isFullDocMode - ? ( - handleInputChange?.(e.target.value)} - onClear={() => handleInputChange?.('')} - /> - ) - : null} + {isFullDocMode && ( + handleInputChange?.(e.target.value)} + onClear={() => handleInputChange?.('')} + /> + )}
- {isLoading ? : null} - {((isFullDocMode && !isLoading) || !collapsed) - ? ( -
- {isParagraphMode && ( -
- -
- )} - {childChunks.length > 0 - ? ( - - {childChunks.map((childChunk) => { - const edited = childChunk.updated_at !== childChunk.created_at - const focused = currChildChunk?.childChunkInfo?.id === childChunk.id - return ( - onDelete?.(childChunk.segment_id, childChunk.id)} - className="child-chunk" - labelClassName={focused ? 'bg-state-accent-solid text-text-primary-on-surface' : ''} - labelInnerClassName="text-[10px] font-semibold align-bottom leading-6" - contentClassName={cn('!leading-6', focused ? 'bg-state-accent-hover-alt text-text-primary' : 'text-text-secondary')} - showDivider={false} - onClick={(e) => { - e.stopPropagation() - onClickSlice?.(childChunk) - }} - offsetOptions={({ rects }) => { - return { - mainAxis: isFullDocMode ? -rects.floating.width : 12 - rects.floating.width, - crossAxis: (20 - rects.floating.height) / 2, - } - }} - /> - ) - })} - - ) - : inputValue !== '' - ? ( -
- -
- ) - : null} + {isLoading && } + {showContent && ( +
+ {isParagraphMode && ( +
+
- ) - : null} + )} + {renderContent()} +
+ )}
) } diff --git a/web/app/components/datasets/documents/detail/completed/common/drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/drawer.tsx index dc1b7192c3..a68742890a 100644 --- a/web/app/components/datasets/documents/detail/completed/common/drawer.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/drawer.tsx @@ -17,6 +17,31 @@ type DrawerProps = { needCheckChunks?: boolean } +const SIDE_POSITION_CLASS = { + right: 'right-0', + left: 'left-0', + bottom: 'bottom-0', + top: 'top-0', +} as const + +function containsTarget(selector: string, target: Node | null): boolean { + const elements = document.querySelectorAll(selector) + return Array.from(elements).some(el => el?.contains(target)) +} + +function shouldReopenChunkDetail( + isClickOnChunk: boolean, + isClickOnChildChunk: boolean, + segmentModalOpen: boolean, + childChunkModalOpen: boolean, +): boolean { + if (segmentModalOpen && isClickOnChildChunk) + return true + if (childChunkModalOpen && isClickOnChunk && !isClickOnChildChunk) + return true + return !isClickOnChunk && !isClickOnChildChunk +} + const Drawer = ({ open, onClose, @@ -41,22 +66,22 @@ const Drawer = ({ const shouldCloseDrawer = useCallback((target: Node | null) => { const panelContent = panelContentRef.current - if (!panelContent) + if (!panelContent || !target) return false - const chunks = document.querySelectorAll('.chunk-card') - const childChunks = document.querySelectorAll('.child-chunk') - const imagePreviewer = document.querySelector('.image-previewer') - const isClickOnChunk = Array.from(chunks).some((chunk) => { - return chunk && chunk.contains(target) - }) - const isClickOnChildChunk = Array.from(childChunks).some((chunk) => { - return chunk && chunk.contains(target) - }) - const reopenChunkDetail = (currSegment.showModal && isClickOnChildChunk) - || (currChildChunk.showModal && isClickOnChunk && !isClickOnChildChunk) || (!isClickOnChunk && !isClickOnChildChunk) - const isClickOnImagePreviewer = imagePreviewer && imagePreviewer.contains(target) - return target && !panelContent.contains(target) && (!needCheckChunks || reopenChunkDetail) && !isClickOnImagePreviewer - }, [currSegment, currChildChunk, needCheckChunks]) + + if (panelContent.contains(target)) + return false + + if (containsTarget('.image-previewer', target)) + return false + + if (!needCheckChunks) + return true + + const isClickOnChunk = containsTarget('.chunk-card', target) + const isClickOnChildChunk = containsTarget('.child-chunk', target) + return shouldReopenChunkDetail(isClickOnChunk, isClickOnChildChunk, currSegment.showModal, currChildChunk.showModal) + }, [currSegment.showModal, currChildChunk.showModal, needCheckChunks]) const onDownCapture = useCallback((e: PointerEvent) => { if (!open || modal) @@ -77,32 +102,27 @@ const Drawer = ({ const isHorizontal = side === 'left' || side === 'right' + const overlayPointerEvents = modal && open ? 'pointer-events-auto' : 'pointer-events-none' + const content = (
- {showOverlay - ? ( -