diff --git a/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx b/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx index aa3336a0ad..04c829dfda 100644 --- a/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx @@ -7,9 +7,10 @@ import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/co type IActionButtonsProps = { handleCancel: () => void - handleSave: (needRegenerate: boolean) => void + handleSave: () => void loading: boolean actionType?: 'edit' | 'add' + handleRegeneration?: () => void } const ActionButtons: FC = ({ @@ -17,6 +18,7 @@ const ActionButtons: FC = ({ handleSave, loading, actionType = 'edit', + handleRegeneration, }) => { const { t } = useTranslation() const [mode, parentMode] = useDocumentContext(s => [s.mode, s.parentMode]) @@ -30,7 +32,7 @@ const ActionButtons: FC = ({ if (loading) return e.preventDefault() - handleSave(false) + handleSave() } , { exactMatch: true, useCapture: true }) @@ -50,18 +52,18 @@ const ActionButtons: FC = ({ {(isParentChildParagraphMode && actionType === 'edit') ? : null } + + + + ) +}) + +const RegeneratingContent: FC = React.memo(() => { + const { t } = useTranslation() + + return ( + <> +
+ {t('datasetDocuments.segment.regeneratingTitle')} +

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

+
+
+ +
+ + ) +}) + +type IRegenerationCompletedContentProps = { + onClose: () => void +} + +const RegenerationCompletedContent: FC = React.memo(({ + 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) + } + }, []) + + return ( + <> +
+ {t('datasetDocuments.segment.regenerationSuccessTitle')} +

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

+
+
+ +
+ + ) +}) + +type IRegenerationModalProps = { + isShow: boolean + onConfirm: () => void + onCancel: () => void +} + +const RegenerationModal: FC = ({ + isShow, + onConfirm, + onCancel, +}) => { + const [loading, setLoading] = useState(false) + const [updateSuccess, setUpdateSuccess] = useState(false) + const { eventEmitter } = useEventEmitterContextContext() + + eventEmitter?.useSubscription((v) => { + if (v === 'update-segment') { + setLoading(true) + setUpdateSuccess(false) + } + if (v === 'update-segment-success') + setUpdateSuccess(true) + if (v === 'update-segment-done') + setLoading(false) + }) + + return ( + {}} className='!max-w-[480px] !rounded-2xl'> + {(!loading && !updateSuccess) && } + {(loading && !updateSuccess) && } + {!loading && updateSuccess && } + + ) +} + +export default RegenerationModal diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index c3f513e8aa..0b5414a816 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -76,7 +76,6 @@ type ICompletedProps = { onNewSegmentModalChange: (state: boolean) => void importStatus: ProcessStatus | string | undefined archived?: boolean - // data: Array<{}> // all/part segments } /** * Embedding done, show list of all segments @@ -247,7 +246,7 @@ const Completed: FC = ({ question: string, answer: string, keywords: string[], - needRegenerate: boolean, + needRegenerate = false, ) => { const params: SegmentUpdater = { content: '' } if (docForm === 'qa_model') { @@ -290,9 +289,10 @@ const Completed: FC = ({ } } setSegments([...segments]) + eventEmitter?.emit('update-segment-success') } finally { - eventEmitter?.emit('') + eventEmitter?.emit('update-segment-done') } } 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 4a862e6633..18f59df345 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card.tsx @@ -1,10 +1,11 @@ import React, { type FC, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowRightUpLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react' +import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' import { StatusItem } from '../../list' -import DocumentFileIcon from '../../../common/document-file-icon' 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 type { SegmentDetailModel } from '@/models/datasets' import Indicator from '@/app/components/header/indicator' @@ -17,74 +18,13 @@ import Badge from '@/app/components/base/badge' import { isAfter } from '@/utils/time' import Tooltip from '@/app/components/base/tooltip' -const Dot = React.memo(() => { - return ( -
·
- ) -}) - -Dot.displayName = 'Dot' - -const ProgressBar: FC<{ percent: number; loading: boolean }> = React.memo(({ percent, loading }) => { - return ( -
-
-
-
-
{loading ? null : percent.toFixed(2)}
-
- ) -}) - -ProgressBar.displayName = 'ProgressBar' - -type DocumentTitleProps = { - name: string - extension?: string -} - -const DocumentTitle: FC = React.memo(({ extension, name }) => { - return ( -
- - {name || '--'} -
- ) -}) - -DocumentTitle.displayName = 'DocumentTitle' - -const Tag = React.memo(({ text }: { text: string }) => { - return ( -
- # - {text} -
- ) -}) - -Tag.displayName = 'Tag' - -export type UsageScene = 'doc' | 'hitTesting' - type ISegmentCardProps = { loading: boolean detail?: SegmentDetailModel & { document?: { name: string } } - contentExternal?: string - refSource?: { - title: string - uri: string - } - isExternal?: boolean - score?: number onClick?: () => void onChangeSwitch?: (enabled: boolean, segId?: string) => Promise onDelete?: (segId: string) => Promise onClickEdit?: () => void - scene?: UsageScene className?: string archived?: boolean embeddingAvailable?: boolean @@ -92,16 +32,11 @@ type ISegmentCardProps = { const SegmentCard: FC = ({ detail = {}, - contentExternal, - isExternal, - refSource, - score, onClick, onChangeSwitch, onDelete, onClickEdit, loading = true, - scene = 'doc', className = '', archived, embeddingAvailable, @@ -124,10 +59,6 @@ const SegmentCard: FC = ({ const isCollapsed = useSegmentListContext(s => s.isCollapsed) const [mode, parentMode] = useDocumentContext(s => [s.mode, s.parentMode]) - const isDocScene = useMemo(() => { - return scene === 'doc' - }, [scene]) - const isGeneralMode = useMemo(() => { return mode === 'custom' }, [mode]) @@ -147,9 +78,9 @@ const SegmentCard: FC = ({ }, [enabled]) const handleClickCard = useCallback(() => { - if (!isFullDocMode) + if (mode !== 'hierarchical' || parentMode !== 'full-doc') onClick?.() - }, [isFullDocMode, onClick]) + }, [mode, parentMode, onClick]) const renderContent = () => { if (answer) { @@ -166,10 +97,6 @@ const SegmentCard: FC = ({ ) } - - if (contentExternal) - return contentExternal - return content } @@ -179,96 +106,85 @@ const SegmentCard: FC = ({ onClick={handleClickCard} >
- {isDocScene - ? <> -
- - -
{`${formatNumber(word_count)} Characters`}
- -
{`${formatNumber(hit_count)} Retrieval Count`}
- {chunkEdited && ( - <> - - - - )} -
- {!isFullDocMode - ?
- {loading - ? ( - - ) - : ( - <> - - {embeddingAvailable && ( -
- {!archived && ( - <> - -
{ - e.stopPropagation() - onClickEdit?.() - }}> - -
-
- -
{ - e.stopPropagation() - setShowModal(true) - } - }> - -
-
- - - )} -
) => - e.stopPropagation() - } - className="flex items-center" - > - { - await onChangeSwitch?.(val, id) - }} - /> -
+ <> +
+ + +
{`${formatNumber(word_count)} Characters`}
+ +
{`${formatNumber(hit_count)} Retrieval Count`}
+ {chunkEdited && ( + <> + + + + )} +
+ {!isFullDocMode + ?
+ {loading + ? ( + + ) + : ( + <> + + {embeddingAvailable && ( +
+ {!archived && ( + <> + +
{ + e.stopPropagation() + onClickEdit?.() + }}> + +
+
+ +
{ + e.stopPropagation() + setShowModal(true) + } + }> + +
+
+ + + )} +
) => + e.stopPropagation() + } + className="flex items-center" + > + { + await onChangeSwitch?.(val, id) + }} + />
- )} - - )} -
- : null} - - : ( - score !== null - ? ( -
-
- -
- ) - : null - )} +
+ )} + + )} +
+ : null} +
{loading ? ( @@ -277,50 +193,32 @@ const SegmentCard: FC = ({
) : ( - isDocScene - ? <> -
- {renderContent()} -
- {isGeneralMode &&
- {keywords?.map(keyword => )} -
} - { - isFullDocMode - ? - : null - } - { - child_chunks.length > 0 - && {}} - enabled={enabled} - /> - } - - : <> -
- {renderContent()} -
-
- -
- -
- {isExternal ? t('datasetHitTesting.viewDetail') : t('datasetHitTesting.viewChart')} - -
-
-
- - )} + <> +
+ {renderContent()} +
+ {isGeneralMode &&
+ {keywords?.map(keyword => )} +
} + { + isFullDocMode + ? + : null + } + { + child_chunks.length > 0 + && {}} + enabled={enabled} + /> + } + + ) + } {showModal && & { id: string } - onUpdate: (segmentId: string, q: string, a: string, k: string[], needRegenerate: boolean) => void + onUpdate: (segmentId: string, q: string, a: string, k: string[], needRegenerate?: boolean) => void onCancel: () => void isEditMode?: boolean docForm: string @@ -39,13 +40,14 @@ const SegmentDetail: FC = ({ const [keywords, setKeywords] = useState(segInfo?.keywords || []) const { eventEmitter } = useEventEmitterContextContext() const [loading, setLoading] = useState(false) + const [showRegenerationModal, setShowRegenerationModal] = useState(false) const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen]) - const [mode] = useDocumentContext(s => s.mode) + const mode = useDocumentContext(s => s.mode) eventEmitter?.useSubscription((v) => { if (v === 'update-segment') setLoading(true) - else + if (v === 'update-segment-done') setLoading(false) }) @@ -56,8 +58,20 @@ const SegmentDetail: FC = ({ setKeywords(segInfo?.keywords || []) } - const handleSave = (needRegenerate = false) => { - onUpdate(segInfo?.id || '', question, answer, keywords, needRegenerate) + const handleSave = () => { + onUpdate(segInfo?.id || '', question, answer, keywords) + } + + const handleRegeneration = () => { + setShowRegenerationModal(true) + } + + const onCancelRegeneration = () => { + setShowRegenerationModal(false) + } + + const onConfirmRegeneration = () => { + onUpdate(segInfo?.id || '', question, answer, keywords, true) } return ( @@ -74,7 +88,12 @@ const SegmentDetail: FC = ({
{isEditMode && fullScreen && ( <> - + )} @@ -108,9 +127,19 @@ const SegmentDetail: FC = ({
{isEditMode && !fullScreen && (
- +
)} +
) } diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 6b60a7a5cc..f2cef3f0c6 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -42,6 +42,8 @@ const translation = { zoomOut: 'Zoom Out', zoomIn: 'Zoom In', openInNewTab: 'Open in new tab', + saveAndRegenerate: 'Save & Regenerate Child Chunks', + close: 'Close', }, errorMsg: { fieldRequired: '{{field}} is required', diff --git a/web/i18n/en-US/dataset-documents.ts b/web/i18n/en-US/dataset-documents.ts index 470d62252c..ee0428a302 100644 --- a/web/i18n/en-US/dataset-documents.ts +++ b/web/i18n/en-US/dataset-documents.ts @@ -353,7 +353,12 @@ const translation = { delete: 'Delete this chunk ?', chunkAdded: '1 chunk added', viewAddedChunk: 'View', - saveAndRegenerate: 'Save & Regenerate Child Chunks', + regenerationConfirmTitle: 'Do you want to regenerate child chunks?', + regenerationConfirmMessage: 'Regenerating child chunks will overwrite the current child chunks, including edited chunks and newly added chunks. The regeneration cannot be undone.', + regeneratingTitle: 'Regenerating child chunks', + regeneratingMessage: 'This may take a moment, please wait...', + regenerationSuccessTitle: 'Regeneration completed', + regenerationSuccessMessage: 'You can close this window.', }, } diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index ec4b0acf9f..7c74b08d73 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -42,6 +42,8 @@ const translation = { zoomOut: '缩小', zoomIn: '放大', openInNewTab: '在新标签页打开', + saveAndRegenerate: '保存并重新生成子分段', + close: '关闭', }, errorMsg: { fieldRequired: '{{field}} 为必填项', diff --git a/web/i18n/zh-Hans/dataset-documents.ts b/web/i18n/zh-Hans/dataset-documents.ts index 824da98850..18378c9154 100644 --- a/web/i18n/zh-Hans/dataset-documents.ts +++ b/web/i18n/zh-Hans/dataset-documents.ts @@ -351,7 +351,12 @@ const translation = { delete: '删除这个分段?', chunkAdded: '新增一个分段', viewAddedChunk: '查看', - saveAndRegenerate: '保存并重新生成子分段', + regenerationConfirmTitle: '是否需要重新生成子分段?', + regenerationConfirmMessage: '重新生成的子分段将会覆盖当前的子分段,包括编辑过的分段和新添加的分段。重新生成操作无法撤销。', + regeneratingTitle: '正在生成子分段', + regeneratingMessage: '生成子分段需要一些时间,请耐心等待...', + regenerationSuccessTitle: '子分段已重新生成', + regenerationSuccessMessage: '可以关闭窗口', }, }