diff --git a/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx b/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx index 0f59070d12..264d62b68a 100644 --- a/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx +++ b/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx @@ -8,7 +8,7 @@ import { import { StatusItem } from '../../list' import style from '../../style.module.css' import s from './style.module.css' -import { SegmentIndexTag } from './index' +import { SegmentIndexTag } from './common/segment-index-tag' import cn from '@/utils/classnames' import Confirm from '@/app/components/base/confirm' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/datasets/documents/detail/completed/full-screen-drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx similarity index 100% rename from web/app/components/datasets/documents/detail/completed/full-screen-drawer.tsx rename to web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx diff --git a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx index bc7ad4a73a..c9356b7f8a 100644 --- a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx @@ -1,6 +1,7 @@ -import React, { type FC, useEffect, useRef, useState } from 'react' +import React, { type FC, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiLoader2Line } from '@remixicon/react' +import { useCountDown } from 'ahooks' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import { useEventEmitterContextContext } from '@/context/event-emitter' @@ -18,15 +19,15 @@ const DefaultContent: FC = React.memo(({ return ( <> -
- {t('datasetDocuments.segment.regenerationConfirm')} -

{t('datasetDocuments.segment.regenerationWarning')}

+
+ {t('datasetDocuments.segment.regenerationConfirmTitle')} +

{t('datasetDocuments.segment.regenerationConfirmMessage')}

-
+
-
@@ -34,18 +35,20 @@ const DefaultContent: FC = React.memo(({ ) }) +DefaultContent.displayName = 'DefaultContent' + const RegeneratingContent: FC = React.memo(() => { const { t } = useTranslation() return ( <> -
+
{t('datasetDocuments.segment.regeneratingTitle')}

{t('datasetDocuments.segment.regeneratingMessage')}

-
-
@@ -53,6 +56,8 @@ const RegeneratingContent: FC = React.memo(() => { ) }) +RegeneratingContent.displayName = 'RegeneratingContent' + type IRegenerationCompletedContentProps = { onClose: () => void } @@ -61,67 +66,64 @@ const RegenerationCompletedContent: FC = Rea onClose, }) => { const { t } = useTranslation() - const [countDown, setCountDown] = useState(5) - const timerRef = useRef(null) - - useEffect(() => { - timerRef.current = setInterval(() => { - if (countDown > 0) - setCountDown(countDown - 1) - else - clearInterval(timerRef.current) - }, 1000) - return () => { - clearInterval(timerRef.current) - } - }, []) + const targetTime = useRef(Date.now() + 5000) + const [countdown] = useCountDown({ + targetDate: targetTime.current, + onEnd: () => { + onClose() + }, + }) return ( <> -
+
{t('datasetDocuments.segment.regenerationSuccessTitle')}

{t('datasetDocuments.segment.regenerationSuccessMessage')}

-
+
) }) +RegenerationCompletedContent.displayName = 'RegenerationCompletedContent' + type IRegenerationModalProps = { isShow: boolean onConfirm: () => void onCancel: () => void + onClose: () => void } const RegenerationModal: FC = ({ isShow, onConfirm, onCancel, + onClose, }) => { const [loading, setLoading] = useState(false) - const [updateSuccess, setUpdateSuccess] = useState(false) + const [updateSucceeded, setUpdateSucceeded] = useState(false) const { eventEmitter } = useEventEmitterContextContext() eventEmitter?.useSubscription((v) => { if (v === 'update-segment') { setLoading(true) - setUpdateSuccess(false) + setUpdateSucceeded(false) } if (v === 'update-segment-success') - setUpdateSuccess(true) + setUpdateSucceeded(true) if (v === 'update-segment-done') setLoading(false) }) return ( {}} className='!max-w-[480px] !rounded-2xl'> - {(!loading && !updateSuccess) && } - {(loading && !updateSuccess) && } - {!loading && updateSuccess && } + {!loading && !updateSucceeded && } + {loading && !updateSucceeded && } + {!loading && updateSucceeded && } ) } diff --git a/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx b/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx new file mode 100644 index 0000000000..7d21311c9a --- /dev/null +++ b/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx @@ -0,0 +1,36 @@ +import React, { type FC, useMemo } from 'react' +import { Chunk } from '@/app/components/base/icons/src/public/knowledge' +import cn from '@/utils/classnames' + +type ISegmentIndexTagProps = { + positionId?: string | number + label?: string + className?: string + labelPrefix?: string +} + +export const SegmentIndexTag: FC = ({ + positionId, + label, + className, + labelPrefix = 'Chunk', +}) => { + const localPositionId = useMemo(() => { + const positionIdStr = String(positionId) + if (positionIdStr.length >= 3) + return `${labelPrefix}-${positionId}` + return `${labelPrefix}-${positionIdStr.padStart(2, '0')}` + }, [positionId, labelPrefix]) + return ( +
+ +
+ {label || localPositionId} +
+
+ ) +} + +SegmentIndexTag.displayName = 'SegmentIndexTag' + +export default React.memo(SegmentIndexTag) diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 0b5414a816..dda54cd0ea 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -13,7 +13,7 @@ import BatchAction from './batch-action' import SegmentDetail from './segment-detail' import SegmentCard from './segment-card' import ChildSegmentList from './child-segment-list' -import FullScreenDrawer from './full-screen-drawer' +import FullScreenDrawer from './common/full-screen-drawer' import Pagination from '@/app/components/base/pagination' import cn from '@/utils/classnames' import { formatNumber } from '@/utils/format' @@ -192,7 +192,6 @@ const Completed: FC = ({ } const { mutateAsync: enableSegment } = useEnableSegment() - const { mutateAsync: disableSegment } = useDisableSegment() const onChangeSwitch = useCallback(async (enable: boolean, segId?: string) => { @@ -229,18 +228,6 @@ const Completed: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [datasetId, documentId, selectedSegmentIds]) - const onCancelBatchOperation = useCallback(() => { - setSelectedSegmentIds([]) - }, []) - - const onSelected = useCallback((segId: string) => { - setSelectedSegmentIds(prev => - prev.includes(segId) - ? prev.filter(id => id !== segId) - : [...prev, segId], - ) - }, []) - const handleUpdateSegment = async ( segmentId: string, question: string, @@ -275,7 +262,8 @@ const Completed: FC = ({ eventEmitter?.emit('update-segment') const res = await updateSegment({ datasetId, documentId, segmentId, body: params }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - onCloseDrawer() + if (!needRegenerate) + onCloseDrawer() for (const seg of segments) { if (seg.id === segmentId) { seg.answer = res.data.answer @@ -301,6 +289,18 @@ const Completed: FC = ({ resetList() }, [importStatus, resetList]) + const onCancelBatchOperation = useCallback(() => { + setSelectedSegmentIds([]) + }, []) + + const onSelected = useCallback((segId: string) => { + setSelectedSegmentIds(prev => + prev.includes(segId) + ? prev.filter(id => id !== segId) + : [...prev, segId], + ) + }, []) + const isAllSelected = useMemo(() => { return segments.length > 0 && segments.every(seg => selectedSegmentIds.includes(seg.id)) }, [segments, selectedSegmentIds]) diff --git a/web/app/components/datasets/documents/detail/completed/segment-card.tsx b/web/app/components/datasets/documents/detail/completed/segment-card.tsx index 18f59df345..f21f72053d 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card.tsx @@ -6,7 +6,8 @@ import { useDocumentContext } from '../index' import ChildSegmentList from './child-segment-list' import Tag from './common/tag' import Dot from './common/dot' -import { SegmentIndexTag, useSegmentListContext } from '.' +import { SegmentIndexTag } from './common/segment-index-tag' +import { useSegmentListContext } from './index' import type { SegmentDetailModel } from '@/models/datasets' import Indicator from '@/app/components/header/indicator' import Switch from '@/app/components/base/switch' @@ -108,7 +109,7 @@ const SegmentCard: FC = ({
<>
- +
{`${formatNumber(word_count)} Characters`}
@@ -205,7 +206,7 @@ const SegmentCard: FC = ({
} { isFullDocMode - ? + ? : null } { diff --git a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx index 5f9262cab9..4f77da7856 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx @@ -1,4 +1,4 @@ -import React, { type FC, useState } from 'react' +import React, { type FC, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiCloseLine, @@ -9,7 +9,8 @@ import ActionButtons from './common/action-buttons' import ChunkContent from './common/chunk-content' import Keywords from './common/keywords' import RegenerationModal from './common/regeneration-modal' -import { SegmentIndexTag, useSegmentListContext } from './index' +import { SegmentIndexTag } from './common/segment-index-tag' +import { useSegmentListContext } from './index' import type { SegmentDetailModel } from '@/models/datasets' import { useEventEmitterContextContext } from '@/context/event-emitter' import { formatNumber } from '@/utils/format' @@ -74,13 +75,17 @@ const SegmentDetail: FC = ({ onUpdate(segInfo?.id || '', question, answer, keywords, true) } + const isParentChildMode = useMemo(() => { + return mode === 'hierarchical' + }, [mode]) + return (
{isEditMode ? 'Edit Chunk' : 'Chunk Detail'}
- + · {formatNumber(isEditMode ? question.length : segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}
@@ -135,11 +140,16 @@ const SegmentDetail: FC = ({ />
)} - + { + showRegenerationModal && ( + + ) + }
) } diff --git a/web/app/components/datasets/documents/detail/new-segment.tsx b/web/app/components/datasets/documents/detail/new-segment.tsx index ae88900cf3..2806ffbcb1 100644 --- a/web/app/components/datasets/documents/detail/new-segment.tsx +++ b/web/app/components/datasets/documents/detail/new-segment.tsx @@ -5,7 +5,8 @@ import { useContext } from 'use-context-selector' import { useParams } from 'next/navigation' import { RiCloseLine, RiExpandDiagonalLine } from '@remixicon/react' import { useShallow } from 'zustand/react/shallow' -import { SegmentIndexTag, useSegmentListContext } from './completed' +import { useSegmentListContext } from './completed' +import { SegmentIndexTag } from './completed/common/segment-index-tag' import ActionButtons from './completed/common/action-buttons' import Keywords from './completed/common/keywords' import ChunkContent from './completed/common/chunk-content' @@ -41,7 +42,7 @@ const NewSegmentModal: FC = ({ const [loading, setLoading] = useState(false) const [addAnother, setAddAnother] = useState(true) const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen]) - const [mode] = useDocumentContext(s => s.mode) + const mode = useDocumentContext(s => s.mode) const { appSidebarExpand } = useAppStore(useShallow(state => ({ appSidebarExpand: state.appSidebarExpand, }))) diff --git a/web/app/components/datasets/formatted-text/flavours/edit-slice.tsx b/web/app/components/datasets/formatted-text/flavours/edit-slice.tsx index 13f109b5d7..1fe81180c4 100644 --- a/web/app/components/datasets/formatted-text/flavours/edit-slice.tsx +++ b/web/app/components/datasets/formatted-text/flavours/edit-slice.tsx @@ -70,7 +70,8 @@ export const EditSlice: FC = (props) => { onMouseLeave={() => setDelBtnHover(false)} > { + onClick={(e) => { + e.stopPropagation() onDelete() setDelBtnShow(false) }} diff --git a/web/app/components/datasets/hit-testing/components/result-item.tsx b/web/app/components/datasets/hit-testing/components/result-item.tsx index 35d9d1bdf9..a53bbdcd60 100644 --- a/web/app/components/datasets/hit-testing/components/result-item.tsx +++ b/web/app/components/datasets/hit-testing/components/result-item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { SegmentIndexTag } from '../../documents/detail/completed' +import { SegmentIndexTag } from '../../documents/detail/completed/common/segment-index-tag' import type { HitTesting } from '@/models/datasets' import cn from '@/utils/classnames' type Props = { diff --git a/web/app/components/datasets/hit-testing/hit-detail.tsx b/web/app/components/datasets/hit-testing/hit-detail.tsx index 066e2238c8..e8b3dbd302 100644 --- a/web/app/components/datasets/hit-testing/hit-detail.tsx +++ b/web/app/components/datasets/hit-testing/hit-detail.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { SegmentIndexTag } from '../documents/detail/completed' +import { SegmentIndexTag } from '../documents/detail/completed/common/segment-index-tag' import s from '../documents/detail/completed/style.module.css' import cn from '@/utils/classnames' import type { SegmentDetailModel } from '@/models/datasets' diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index f2cef3f0c6..82fa6ce01e 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -44,6 +44,8 @@ const translation = { openInNewTab: 'Open in new tab', saveAndRegenerate: 'Save & Regenerate Child Chunks', close: 'Close', + viewMore: 'VIEW MORE', + regenerate: 'Regenerate', }, errorMsg: { fieldRequired: '{{field}} is required', diff --git a/web/i18n/en-US/dataset-documents.ts b/web/i18n/en-US/dataset-documents.ts index 00eafb7a2e..e074054815 100644 --- a/web/i18n/en-US/dataset-documents.ts +++ b/web/i18n/en-US/dataset-documents.ts @@ -349,6 +349,7 @@ const translation = { newTextSegment: 'New Text Segment', newQaSegment: 'New Q&A Segment', addChunk: 'Add Chunk', + addChildChunk: 'Add Child Chunk', addAnother: 'Add another', delete: 'Delete this chunk ?', chunkAdded: '1 chunk added', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 7c74b08d73..1af56d0cfe 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -44,6 +44,8 @@ const translation = { openInNewTab: '在新标签页打开', saveAndRegenerate: '保存并重新生成子分段', close: '关闭', + viewMore: '查看更多', + regenerate: '重新生成', }, errorMsg: { fieldRequired: '{{field}} 为必填项', diff --git a/web/i18n/zh-Hans/dataset-documents.ts b/web/i18n/zh-Hans/dataset-documents.ts index 18378c9154..e06ae3f628 100644 --- a/web/i18n/zh-Hans/dataset-documents.ts +++ b/web/i18n/zh-Hans/dataset-documents.ts @@ -347,6 +347,7 @@ const translation = { newTextSegment: '新文本分段', newQaSegment: '新问答分段', addChunk: '新增分段', + addChildChunk: '新增子分段', addAnother: '连续新增', delete: '删除这个分段?', chunkAdded: '新增一个分段', diff --git a/web/models/datasets.ts b/web/models/datasets.ts index 10495f19e7..921b72b545 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -624,7 +624,7 @@ export type ChildChunkDetail = { type: ChildChunkType } -export type ChildSegmentResponse = { +export type ChildSegmentsResponse = { data: ChildChunkDetail[] total: number total_pages: number diff --git a/web/service/datasets.ts b/web/service/datasets.ts index c19eaf2a5b..158b716764 100644 --- a/web/service/datasets.ts +++ b/web/service/datasets.ts @@ -25,8 +25,6 @@ import type { RelatedAppResponse, SegmentDetailModel, SegmentUpdater, - SegmentsQuery, - SegmentsResponse, createDocumentResponse, } from '@/models/datasets' import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations' @@ -180,19 +178,6 @@ export const modifyDocMetadata: Fetcher = ({ datasetId, documentId, params }) => { - return get(`/datasets/${datasetId}/documents/${documentId}/segments`, { params }) -} - -export const enableSegment: Fetcher = ({ datasetId, segmentId }) => { - return patch(`/datasets/${datasetId}/segments/${segmentId}/enable`) -} - -export const disableSegment: Fetcher = ({ datasetId, segmentId }) => { - return patch(`/datasets/${datasetId}/segments/${segmentId}/disable`) -} - export const updateSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string }, { datasetId: string; documentId: string; segmentId: string; body: SegmentUpdater }> = ({ datasetId, documentId, segmentId, body }) => { return patch<{ data: SegmentDetailModel; doc_form: string }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) } diff --git a/web/service/knowledge/use-segment.ts b/web/service/knowledge/use-segment.ts index 65ae2c17d3..ec6763ce36 100644 --- a/web/service/knowledge/use-segment.ts +++ b/web/service/knowledge/use-segment.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery } from '@tanstack/react-query' -import { del, get, patch } from '../base' +import { del, get, patch, post } from '../base' import type { CommonResponse } from '@/models/common' -import type { ChildSegmentResponse, SegmentsResponse } from '@/models/datasets' +import type { ChildChunkDetail, ChildSegmentsResponse, SegmentsResponse } from '@/models/datasets' const NAME_SPACE = 'segment' @@ -65,7 +65,7 @@ export const useDeleteSegment = () => { }) } -const useChildSegmentListKey = [NAME_SPACE, 'childChunkList'] +export const useChildSegmentListKey = [NAME_SPACE, 'childChunkList'] export const useChildSegmentList = ( payload: { @@ -85,9 +85,29 @@ export const useChildSegmentList = ( return useQuery({ queryKey: [...useChildSegmentListKey, datasetId, documentId, segmentId, page, limit, keyword], queryFn: () => { - return get(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { params }) + return get(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { params }) }, enabled: !disable, initialData: disable ? { data: [], total: 0, page: 1, total_pages: 0, limit: 10 } : undefined, }) } + +export const useDeleteChildSegment = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'childChunk', 'delete'], + mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; childChunkId: string }) => { + const { datasetId, documentId, segmentId, childChunkId } = payload + return del(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks/${childChunkId}`) + }, + }) +} + +export const useAddChildSegment = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'childChunk', 'add'], + mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: { content: string } }) => { + const { datasetId, documentId, segmentId, body } = payload + return post<{ data: ChildChunkDetail }>(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { body }) + }, + }) +}