diff --git a/web/app/components/base/file-uploader/file-type-icon.tsx b/web/app/components/base/file-uploader/file-type-icon.tsx index 193a630dee..4e31ab66a8 100644 --- a/web/app/components/base/file-uploader/file-type-icon.tsx +++ b/web/app/components/base/file-uploader/file-type-icon.tsx @@ -82,8 +82,8 @@ const FileTypeIcon = ({ size = 'sm', className, }: FileTypeIconProps) => { - const Icon = FILE_TYPE_ICON_MAP[type].component || FileAppearanceTypeEnum.custom - const color = FILE_TYPE_ICON_MAP[type].color + const Icon = FILE_TYPE_ICON_MAP[type]?.component || FileAppearanceTypeEnum.custom + const color = FILE_TYPE_ICON_MAP[type]?.color || FILE_TYPE_ICON_MAP.custom.color return } 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/common/tag.tsx b/web/app/components/datasets/documents/detail/completed/common/tag.tsx new file mode 100644 index 0000000000..c88bffc736 --- /dev/null +++ b/web/app/components/datasets/documents/detail/completed/common/tag.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import cn from '@/utils/classnames' + +const Tag = ({ text, className }: { text: string; className?: string }) => { + return ( +
+ # + {text} +
+ ) +} + +Tag.displayName = 'Tag' + +export default React.memo(Tag) diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index c3f513e8aa..5cc8b3258d 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -42,21 +42,22 @@ type SegmentListContextValue = { const SegmentListContext = createContext({ isCollapsed: true, - toggleCollapsed: () => {}, + toggleCollapsed: () => { }, fullScreen: false, - toggleFullScreen: () => {}, + toggleFullScreen: () => { }, }) export const useSegmentListContext = (selector: (value: SegmentListContextValue) => any) => { return useContextSelector(SegmentListContext, selector) } -export const SegmentIndexTag: FC<{ positionId?: string | number; label?: string; className?: string }> = React.memo(({ positionId, label, className }) => { +export const SegmentIndexTag: FC<{ positionId?: string | number; label?: string; className?: string; isParentChildRetrieval?: boolean }> = React.memo(({ positionId, label, className, isParentChildRetrieval }) => { + const prefix = `${isParentChildRetrieval ? 'Parent-' : ''}Chunk` const localPositionId = useMemo(() => { const positionIdStr = String(positionId) if (positionIdStr.length >= 3) - return `Chunk-${positionId}` - return `Chunk-${positionIdStr.padStart(2, '0')}` + return `${prefix}-${positionId}` + return `${prefix}-${positionIdStr.padStart(2, '0')}` }, [positionId]) return (
@@ -76,7 +77,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 @@ -180,7 +180,7 @@ const Completed: FC = ({ setSegments([]) setSelectedSegmentIds([]) invalidSegmentList() - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const onClickCard = (detail: SegmentDetailModel, isEditMode = false) => { @@ -211,7 +211,7 @@ const Completed: FC = ({ notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) }, }) - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [datasetId, documentId, selectedSegmentIds, segments]) const { mutateAsync: deleteSegment } = useDeleteSegment() @@ -227,7 +227,7 @@ const Completed: FC = ({ notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) }, }) - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [datasetId, documentId, selectedSegmentIds]) const onCancelBatchOperation = useCallback(() => { @@ -247,7 +247,7 @@ const Completed: FC = ({ question: string, answer: string, keywords: string[], - needRegenerate: boolean, + needRegenerate = false, ) => { const params: SegmentUpdater = { content: '' } if (docForm === 'qa_model') { @@ -290,9 +290,10 @@ const Completed: FC = ({ } } setSegments([...segments]) + eventEmitter?.emit('update-segment-success') } finally { - eventEmitter?.emit('') + eventEmitter?.emit('update-segment-done') } } @@ -337,7 +338,7 @@ const Completed: FC = ({ resetList() currentPage !== totalPages && setCurrentPage(totalPages) } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [segmentListData, limit, currentPage]) return ( @@ -388,7 +389,7 @@ const Completed: FC = ({ /> {}} + handleInputChange={() => { }} enabled={!archived} />
@@ -443,14 +444,14 @@ const Completed: FC = ({ {/* Batch Action Buttons */} {selectedSegmentIds.length > 0 - && } + && } ) } 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/app/components/datasets/hit-testing/assets/test-data.ts b/web/app/components/datasets/hit-testing/assets/test-data.ts index 39a9788afa..623f7e587c 100644 --- a/web/app/components/datasets/hit-testing/assets/test-data.ts +++ b/web/app/components/datasets/hit-testing/assets/test-data.ts @@ -1,4 +1,6 @@ -export const generalResultData = [ +import type { HitTesting } from '@/models/datasets' + +export const generalResultData: HitTesting[] = [ { segment: { id: 'b621b153-f8a7-4e85-bd3d-07feaf61bd9e', @@ -40,7 +42,13 @@ export const generalResultData = [ doc_type: null, }, }, - child_chunks: null, + child_chunks: [ + { + id: '1', + score: 0.8771945, + content: 'It is quite natural for academics who are continuously told to “publish or perish” to want to always create something from scratch that is their own fresh creation.', + }, + ], score: 0.8771945, tsne_position: null, }, diff --git a/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx b/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx new file mode 100644 index 0000000000..b685689b2e --- /dev/null +++ b/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx @@ -0,0 +1,31 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { SliceContent, SliceLabel } from '../../formatted-text/flavours/shared' +import cn from '@/utils/classnames' +import type { HitTestingChildChunk } from '@/models/datasets' + +type Props = { + payload: HitTestingChildChunk + isShowAll: boolean +} + +const ChildChunks: FC = ({ + payload, + isShowAll, +}) => { + const { t } = useTranslation() + const { id, score, content } = payload + return ( +
+ + {id} {score} + + + {content} + +
+ ) +} +export default React.memo(ChildChunks) diff --git a/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx new file mode 100644 index 0000000000..51c6ec0343 --- /dev/null +++ b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx @@ -0,0 +1,87 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { SegmentIndexTag } from '../../documents/detail/completed' +import Dot from '../../documents/detail/completed/common/dot' +import Score from './score' +import ChildChunksItem from './child-chunks-item' +import Modal from '@/app/components/base/modal' +import type { HitTesting } from '@/models/datasets' +import FileIcon from '@/app/components/base/file-uploader/file-type-icon' +import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' +import cn from '@/utils/classnames' +import Tag from '@/app/components/datasets/documents/detail/completed/common/tag' + +type Props = { + payload: HitTesting + onHide: () => void +} + +const ChunkDetailModal: FC = ({ + payload, + onHide, +}) => { + const { t } = useTranslation() + const { segment, score, child_chunks } = payload + const { position, word_count, content, keywords, document } = segment + const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0) + const extension = document.name.split('.').slice(0, -1)[0] as FileAppearanceTypeEnum + + return ( + +
+
+ {/* Meta info */} +
+
+ + +
+ + {document.name} +
+
+ +
+
+ {content} +
+ {!isParentChildRetrieval && keywords && keywords.length > 0 && ( +
+
{t('dataset.keywords')}
+
+ {keywords.map(keyword => ( + + ))} +
+
+ )} +
+ + {isParentChildRetrieval && ( +
+
{t('dataset.hitChunks', { num: child_chunks.length })}
+
+ {child_chunks.map(item => ( + + ))} +
+
+ )} +
+
+ ) +} + +export default React.memo(ChunkDetailModal) 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..e1e51bfbe9 100644 --- a/web/app/components/datasets/hit-testing/components/result-item.tsx +++ b/web/app/components/datasets/hit-testing/components/result-item.tsx @@ -2,9 +2,19 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' +import { RiArrowDownSLine, RiArrowRightSLine, RiArrowRightUpLine } from '@remixicon/react' +import { useBoolean } from 'ahooks' import { SegmentIndexTag } from '../../documents/detail/completed' +import Dot from '../../documents/detail/completed/common/dot' +import Score from './score' +import ChildChunkItem from './child-chunks-item' +import ChunkDetailModal from './chunk-detail-modal' import type { HitTesting } from '@/models/datasets' import cn from '@/utils/classnames' +import FileIcon from '@/app/components/base/file-uploader/file-type-icon' +import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' +import Tag from '@/app/components/datasets/documents/detail/completed/common/tag' + type Props = { payload: HitTesting } @@ -13,20 +23,81 @@ const ResultItem: FC = ({ payload, }) => { const { t } = useTranslation() - const { segment } = payload - const { position, word_count } = segment + const { segment, score, child_chunks } = payload + const { position, word_count, content, keywords, document } = segment + const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0) + const extension = document.name.split('.').slice(0, -1)[0] as FileAppearanceTypeEnum + const [isFold, { + toggle: toggleFold, + }] = useBoolean(false) + const Icon = isFold ? RiArrowRightSLine : RiArrowDownSLine + + const [isShowDetailModal, { + setTrue: showDetailModal, + setFalse: hideDetailModal, + }] = useBoolean(false) return ( -
-
+
+ {/* Meta info */} +
- -
·
+ +
{word_count} {t('datasetDocuments.segment.characters')}
- {/* Score */} +
+ {/* Main */} +
+
{content}
+ {isParentChildRetrieval && ( +
+
+ +
{t('dataset.hitChunks', { num: child_chunks.length })}
+
+ {child_chunks.map(item => ( + + ))} +
+ )} + {!isParentChildRetrieval && keywords && keywords.length > 0 && ( +
+ {keywords.map(keyword => ( + + ))} +
+ )} +
+ {/* Foot */} +
+
+ + {document.name} +
+
+
{t('dataset.open')}
+ +
+
+ + { + isShowDetailModal && ( + + ) + }
) } diff --git a/web/app/components/datasets/hit-testing/components/score.tsx b/web/app/components/datasets/hit-testing/components/score.tsx new file mode 100644 index 0000000000..650ecd497f --- /dev/null +++ b/web/app/components/datasets/hit-testing/components/score.tsx @@ -0,0 +1,22 @@ +'use client' +import type { FC } from 'react' +import React from 'react' + +type Props = { + value: number +} + +const Score: FC = ({ + value, +}) => { + return ( +
+
+
+
score
+
{value.toFixed(2)}
+
+
+ ) +} +export default React.memo(Score) diff --git a/web/app/components/datasets/hit-testing/index.tsx b/web/app/components/datasets/hit-testing/index.tsx index bf3c02a84f..f82edf0267 100644 --- a/web/app/components/datasets/hit-testing/index.tsx +++ b/web/app/components/datasets/hit-testing/index.tsx @@ -12,6 +12,7 @@ import s from './style.module.css' import HitDetail from './hit-detail' import ModifyRetrievalModal from './modify-retrieval-modal' import { generalResultData } from './assets/test-data' +import ResultItem from './components/result-item' import cn from '@/utils/classnames' import type { ExternalKnowledgeBaseHitTestingResponse, ExternalKnowledgeBaseHitTesting as ExternalKnowledgeBaseHitTestingType, HitTestingResponse, HitTesting as HitTestingType } from '@/models/datasets' import Loading from '@/app/components/base/loading' @@ -64,44 +65,26 @@ const HitTesting: FC = ({ datasetId }: Props) => { const total = recordsRes?.total || 0 - const onClickCard = (detail: HitTestingType) => { - setCurrParagraph({ paraInfo: detail, showModal: true }) - } - - const onClickExternalCard = (detail: ExternalKnowledgeBaseHitTestingType) => { - setExternalCurrParagraph({ paraInfo: detail, showModal: true }) - } const { dataset: currentDataset } = useContext(DatasetDetailContext) const isExternal = currentDataset?.provider === 'external' const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false) const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile) - const renderHitResults = (results: any[], onClickCard: (record: any) => void) => ( - <> -
{t('datasetHitTesting.hit.title')}
-
-
- {results.map((record, idx) => ( - onClickCard(record)} - /> - ))} -
+ const renderHitResults = (results: any[]) => ( +
+
+ {t('datasetHitTesting.hit.title', { num: results.length })}
- +
+ {results.map((record, idx) => ( + + ))} +
+
) const renderEmptyState = () => ( @@ -190,8 +173,8 @@ const HitTesting: FC = ({ datasetId }: Props) => { )}
-
- {renderHitResults(generalResultData, onClickCard)} +
+ {renderHitResults(generalResultData)} {submitLoading ?
= ({ datasetId }: Props) => { return renderEmptyState() if (hitResult?.records.length) - return renderHitResults(hitResult.records, onClickCard) + return renderHitResults(hitResult.records) - return renderHitResults(externalHitResult?.records || [], onClickExternalCard) + return renderHitResults(externalHitResult?.records || []) })() ) } diff --git a/web/app/components/datasets/hit-testing/style.module.css b/web/app/components/datasets/hit-testing/style.module.css index 1e90902a70..7d83b8abb6 100644 --- a/web/app/components/datasets/hit-testing/style.module.css +++ b/web/app/components/datasets/hit-testing/style.module.css @@ -5,7 +5,7 @@ @apply flex-1 h-full; } .leftDiv { - @apply border-r border-gray-100 px-6 py-3 flex flex-col; + @apply px-6 py-3 flex flex-col; } .rightDiv { @apply flex flex-col; 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 11f0cd4127..00eafb7a2e 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/en-US/dataset-hit-testing.ts b/web/i18n/en-US/dataset-hit-testing.ts index 6dbfa47fee..385d68c771 100644 --- a/web/i18n/en-US/dataset-hit-testing.ts +++ b/web/i18n/en-US/dataset-hit-testing.ts @@ -19,7 +19,7 @@ const translation = { testing: 'Testing', }, hit: { - title: 'RETRIEVAL PARAGRAPHS', + title: '{{num}} Retrieved Chunks', emptyTip: 'Retrieval Testing results will show here', }, noRecentTip: 'No recent query results here', 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: '可以关闭窗口', }, } diff --git a/web/i18n/zh-Hans/dataset-hit-testing.ts b/web/i18n/zh-Hans/dataset-hit-testing.ts index 09cfdc2824..6aba135c71 100644 --- a/web/i18n/zh-Hans/dataset-hit-testing.ts +++ b/web/i18n/zh-Hans/dataset-hit-testing.ts @@ -19,7 +19,7 @@ const translation = { testing: '测试', }, hit: { - title: '召回段落', + title: '{{num}} 个召回段落', emptyTip: '召回测试结果将展示在这里', }, noRecentTip: '最近无查询结果', diff --git a/web/models/datasets.ts b/web/models/datasets.ts index 10495f19e7..72e7d3751d 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -479,10 +479,16 @@ export type HitTestingRecord = { created_at: number } +export type HitTestingChildChunk = { + id: string + content: string + score: number +} export type HitTesting = { segment: Segment score: number tsne_position: TsnePosition + child_chunks?: HitTestingChildChunk[] | null } export type ExternalKnowledgeBaseHitTesting = {