| | |
| | | useUpdateSegment, |
| | | } from '@/service/knowledge/use-segment' |
| | | import { useInvalid } from '@/service/use-base' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | const DEFAULT_LIMIT = 10 |
| | | |
| | |
| | | const SegmentListContext = createContext<SegmentListContextValue>({ |
| | | isCollapsed: true, |
| | | fullScreen: false, |
| | | toggleFullScreen: noop, |
| | | toggleFullScreen: () => {}, |
| | | currSegment: { showModal: false }, |
| | | currChildChunk: { showModal: false }, |
| | | }) |
| | |
| | | const resetList = useCallback(() => { |
| | | setSelectedSegmentIds([]) |
| | | invalidSegmentList() |
| | | }, [invalidSegmentList]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, []) |
| | | |
| | | const resetChildList = useCallback(() => { |
| | | invalidChildSegmentList() |
| | | }, [invalidChildSegmentList]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, []) |
| | | |
| | | const onClickCard = (detail: SegmentDetailModel, isEditMode = false) => { |
| | | setCurrSegment({ segInfo: detail, showModal: true, isEditMode }) |
| | |
| | | const invalidChunkListEnabled = useInvalid(useChunkListEnabledKey) |
| | | const invalidChunkListDisabled = useInvalid(useChunkListDisabledKey) |
| | | |
| | | const refreshChunkListWithStatusChanged = useCallback(() => { |
| | | const refreshChunkListWithStatusChanged = () => { |
| | | switch (selectedStatus) { |
| | | case 'all': |
| | | invalidChunkListDisabled() |
| | |
| | | default: |
| | | invalidSegmentList() |
| | | } |
| | | }, [selectedStatus, invalidChunkListDisabled, invalidChunkListEnabled, invalidSegmentList]) |
| | | } |
| | | |
| | | const onChangeSwitch = useCallback(async (enable: boolean, segId?: string) => { |
| | | const operationApi = enable ? enableSegment : disableSegment |
| | |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | }, |
| | | }) |
| | | }, [datasetId, documentId, selectedSegmentIds, segments, disableSegment, enableSegment, t, notify, refreshChunkListWithStatusChanged]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [datasetId, documentId, selectedSegmentIds, segments]) |
| | | |
| | | const { mutateAsync: deleteSegment } = useDeleteSegment() |
| | | |
| | |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | }, |
| | | }) |
| | | }, [datasetId, documentId, selectedSegmentIds, deleteSegment, resetList, t, notify]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [datasetId, documentId, selectedSegmentIds]) |
| | | |
| | | const { mutateAsync: updateSegment } = useUpdateSegment() |
| | | |
| | | const refreshChunkListDataWithDetailChanged = useCallback(() => { |
| | | const refreshChunkListDataWithDetailChanged = () => { |
| | | switch (selectedStatus) { |
| | | case 'all': |
| | | invalidChunkListDisabled() |
| | |
| | | invalidChunkListEnabled() |
| | | break |
| | | } |
| | | }, [selectedStatus, invalidChunkListDisabled, invalidChunkListEnabled, invalidChunkListAll]) |
| | | } |
| | | |
| | | const handleUpdateSegment = useCallback(async ( |
| | | segmentId: string, |
| | |
| | | if (seg.id === segmentId) { |
| | | seg.answer = res.data.answer |
| | | seg.content = res.data.content |
| | | seg.sign_content = res.data.sign_content |
| | | seg.keywords = res.data.keywords |
| | | seg.word_count = res.data.word_count |
| | | seg.hit_count = res.data.hit_count |
| | |
| | | eventEmitter?.emit('update-segment-done') |
| | | }, |
| | | }) |
| | | }, [segments, datasetId, documentId, updateSegment, docForm, notify, eventEmitter, onCloseSegmentDetail, refreshChunkListDataWithDetailChanged, t]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [segments, datasetId, documentId]) |
| | | |
| | | useEffect(() => { |
| | | resetList() |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [pathname]) |
| | | |
| | | useEffect(() => { |
| | | if (importStatus === ProcessStatus.COMPLETED) |
| | | resetList() |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [importStatus]) |
| | | }, [importStatus, resetList]) |
| | | |
| | | const onCancelBatchOperation = useCallback(() => { |
| | | setSelectedSegmentIds([]) |
| | |
| | | setSelectedSegmentIds((prev) => { |
| | | const currentAllSegIds = segments.map(seg => seg.id) |
| | | const prevSelectedIds = prev.filter(item => !currentAllSegIds.includes(item)) |
| | | return [...prevSelectedIds, ...(isAllSelected ? [] : currentAllSegIds)] |
| | | return [...prevSelectedIds, ...((isAllSelected || selectedSegmentIds.length > 0) ? [] : currentAllSegIds)] |
| | | }) |
| | | }, [segments, isAllSelected]) |
| | | }, [segments, isAllSelected, selectedSegmentIds]) |
| | | |
| | | const totalText = useMemo(() => { |
| | | const isSearch = searchValue !== '' || selectedStatus !== 'all' |
| | |
| | | const count = segmentListData?.total || 0 |
| | | return `${total} ${t('datasetDocuments.segment.searchResults', { count })}` |
| | | } |
| | | }, [segmentListData, mode, parentMode, searchValue, selectedStatus, t]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [segmentListData?.total, mode, parentMode, searchValue, selectedStatus]) |
| | | |
| | | const toggleFullScreen = useCallback(() => { |
| | | setFullScreen(!fullScreen) |
| | |
| | | resetList() |
| | | currentPage !== totalPages && setCurrentPage(totalPages) |
| | | } |
| | | }, [segmentListData, limit, currentPage, resetList]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [segmentListData, limit, currentPage]) |
| | | |
| | | const { mutateAsync: deleteChildSegment } = useDeleteChildSegment() |
| | | |
| | |
| | | }, |
| | | }, |
| | | ) |
| | | }, [datasetId, documentId, parentMode, deleteChildSegment, resetList, resetChildList, t, notify]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [datasetId, documentId, parentMode]) |
| | | |
| | | const handleAddNewChildChunk = useCallback((parentChunkId: string) => { |
| | | setShowNewChildSegmentModal(true) |
| | |
| | | else { |
| | | resetChildList() |
| | | } |
| | | }, [parentMode, currChunkId, segments, refreshChunkListDataWithDetailChanged, resetChildList]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [parentMode, currChunkId, segments]) |
| | | |
| | | const viewNewlyAddedChildChunk = useCallback(() => { |
| | | const totalPages = childChunkListData?.total_pages || 0 |
| | |
| | | resetChildList() |
| | | currentPage !== totalPages && setCurrentPage(totalPages) |
| | | } |
| | | }, [childChunkListData, limit, currentPage, resetChildList]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [childChunkListData, limit, currentPage]) |
| | | |
| | | const onClickSlice = useCallback((detail: ChildChunkDetail) => { |
| | | setCurrChildChunk({ childChunkInfo: detail, showModal: true }) |
| | |
| | | eventEmitter?.emit('update-child-segment-done') |
| | | }, |
| | | }) |
| | | }, [segments, datasetId, documentId, parentMode, updateChildSegment, notify, eventEmitter, onCloseChildSegmentDetail, refreshChunkListDataWithDetailChanged, resetChildList, t]) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [segments, childSegments, datasetId, documentId, parentMode]) |
| | | |
| | | const onClearFilter = useCallback(() => { |
| | | setInputValue('') |
| | |
| | | setSelectedStatus('all') |
| | | setCurrentPage(1) |
| | | }, []) |
| | | |
| | | const selectDefaultValue = useMemo(() => { |
| | | if (selectedStatus === 'all') |
| | | return 'all' |
| | | return selectedStatus ? 1 : 0 |
| | | }, [selectedStatus]) |
| | | |
| | | return ( |
| | | <SegmentListContext.Provider value={{ |
| | |
| | | <Checkbox |
| | | className='shrink-0' |
| | | checked={isAllSelected} |
| | | indeterminate={!isAllSelected && isSomeSelected} |
| | | mixed={!isAllSelected && isSomeSelected} |
| | | onCheck={onSelectedAll} |
| | | disabled={isLoadingSegmentList} |
| | | /> |
| | | <div className={'system-sm-semibold-uppercase flex-1 pl-5 text-text-secondary'}>{totalText}</div> |
| | | <div className={'system-sm-semibold-uppercase pl-5 text-text-secondary flex-1'}>{totalText}</div> |
| | | <SimpleSelect |
| | | onSelect={onChangeStatus} |
| | | items={statusList.current} |
| | | defaultValue={selectDefaultValue} |
| | | defaultValue={selectedStatus === 'all' ? 'all' : selectedStatus ? 1 : 0} |
| | | className={s.select} |
| | | wrapperClassName='h-fit mr-2' |
| | | optionWrapClassName='w-[160px]' |
| | |
| | | onChange={e => handleInputChange(e.target.value)} |
| | | onClear={() => handleInputChange('')} |
| | | /> |
| | | <Divider type='vertical' className='mx-3 h-3.5' /> |
| | | <Divider type='vertical' className='h-3.5 mx-3' /> |
| | | <DisplayToggle isCollapsed={isCollapsed} toggleCollapsed={() => setIsCollapsed(!isCollapsed)} /> |
| | | </div>} |
| | | {/* Segment list */} |
| | | { |
| | | isFullDocMode |
| | | ? <div className={cn( |
| | | 'flex grow flex-col overflow-x-hidden', |
| | | 'flex flex-col grow overflow-x-hidden', |
| | | (isLoadingSegmentList || isLoadingChildSegmentList) ? 'overflow-y-hidden' : 'overflow-y-auto', |
| | | )}> |
| | | <SegmentCard |
| | |
| | | /> |
| | | } |
| | | {/* Pagination */} |
| | | <Divider type='horizontal' className='mx-6 my-0 h-[1px] w-auto bg-divider-subtle' /> |
| | | <Divider type='horizontal' className='w-auto h-[1px] my-0 mx-6 bg-divider-subtle' /> |
| | | <Pagination |
| | | current={currentPage - 1} |
| | | onChange={cur => setCurrentPage(cur + 1)} |
| | |
| | | {/* Batch Action Buttons */} |
| | | {selectedSegmentIds.length > 0 |
| | | && <BatchAction |
| | | className='absolute bottom-16 left-0 z-20' |
| | | className='absolute left-0 bottom-16 z-20' |
| | | selectedIds={selectedSegmentIds} |
| | | onBatchEnable={onChangeSwitch.bind(null, true, '')} |
| | | onBatchDisable={onChangeSwitch.bind(null, false, '')} |