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..de9166d2ae 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.document + const color = FILE_TYPE_ICON_MAP[type]?.color || FILE_TYPE_ICON_MAP[FileAppearanceTypeEnum.document].color return } diff --git a/web/app/components/datasets/create/step-two/index.module.css b/web/app/components/datasets/create/step-two/index.module.css index db4c616363..bda6138153 100644 --- a/web/app/components/datasets/create/step-two/index.module.css +++ b/web/app/components/datasets/create/step-two/index.module.css @@ -76,7 +76,7 @@ } .disabled { - cursor: not-allowed; + cursor: not-allowed !important; } .indexItem.disabled:hover { diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index 023f555681..5a3d8536dc 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, PropsWithChildren } from 'react' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { @@ -10,6 +10,7 @@ import { } from '@remixicon/react' import Link from 'next/link' import Image from 'next/image' +import { useHover } from 'ahooks' import SettingCog from '../assets/setting-gear-mod.svg' import OrangeEffect from '../assets/option-card-effect-orange.svg' import FamilyMod from '../assets/family-mod.svg' @@ -58,6 +59,8 @@ import { getNotionInfo, getWebsiteInfo, useCreateDocument, useCreateFirstDocumen import Badge from '@/app/components/base/badge' import { SkeletonContanier, SkeletonPoint, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' import Tooltip from '@/app/components/base/tooltip' +import CustomDialog from '@/app/components/base/dialog' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' const TextLabel: FC = (props) => { return @@ -175,16 +178,21 @@ const StepTwo = ({ ) // QA Related - const [isLanguageSelectDisabled, setIsLanguageSelectDisabled] = useState(false) + const [isLanguageSelectDisabled, _setIsLanguageSelectDisabled] = useState(false) + const [isQAConfirmDialogOpen, setIsQAConfirmDialogOpen] = useState(false) const [docForm, setDocForm] = useState( (datasetId && documentDetail) ? documentDetail.doc_form as ChuckingMode : ChuckingMode.text, ) const handleChangeDocform = (value: ChuckingMode) => { + if (value === ChuckingMode.qa && indexType === IndexingType.ECONOMICAL) { + setIsQAConfirmDialogOpen(true) + return + } + if (value === ChuckingMode.parentChild && indexType === IndexingType.ECONOMICAL) + setIndexType(IndexingType.QUALIFIED) setDocForm(value) // eslint-disable-next-line @typescript-eslint/no-use-before-define currentEstimateMutation.reset() - if (value === ChuckingMode.parentChild) - setIndexType(IndexingType.QUALIFIED) } const [docLanguage, setDocLanguage] = useState( @@ -513,14 +521,11 @@ const StepTwo = ({ } const changeToEconomicalType = () => { - if (docForm === ChuckingMode.parentChild) + if (docForm !== ChuckingMode.text) return - if (!hasSetIndexType) { + if (!hasSetIndexType) setIndexType(IndexingType.ECONOMICAL) - if (docForm === ChuckingMode.qa) - handleChangeDocform(ChuckingMode.text) - } } useEffect(() => { @@ -556,6 +561,9 @@ const StepTwo = ({ score_threshold: 0.5, } as RetrievalConfig) + const economyDomRef = useRef(null) + const isHoveringEconomy = useHover(economyDomRef) + return (
@@ -563,226 +571,231 @@ const StepTwo = ({
{t('datasetCreation.stepTwo.segmentation')}
- } - activeHeaderClassName='bg-gradient-to-r from-[#EFF0F9] to-[#F9FAFB]' - description={t('datasetCreation.stepTwo.generalTip')} - isActive={ - [ChuckingMode.text, ChuckingMode.qa].includes(docForm) - } - onSwitched={() => - handleChangeDocform(ChuckingMode.text) - } - actions={ - <> - - - - } - > -
-
- setSegmentIdentifier(e.target.value)} - /> - - -
-
-
- {t('datasetCreation.stepTwo.rules')} -
- {rules.map(rule => ( -
{ - ruleChangeHandle(rule.id) - }}> - - -
- ))} -
-
-
- {IS_CE_EDITION && <> -
- { - if (docForm === ChuckingMode.qa) - handleChangeDocform(ChuckingMode.text) - else - handleChangeDocform(ChuckingMode.qa) - }} - className='mr-2' - /> -
- - {t('datasetCreation.stepTwo.QALanguage')} - -
- -
- -
-
- {docForm === ChuckingMode.qa && ( -
- - - {t('datasetCreation.stepTwo.QATip')} - -
- )} - } -
-
- } - effectImg={OrangeEffect.src} - activeHeaderClassName='bg-gradient-to-r from-[#F9F1EE] to-[#F9FAFB]' - description={t('datasetCreation.stepTwo.parentChildTip')} - isActive={docForm === ChuckingMode.parentChild} - onSwitched={() => handleChangeDocform(ChuckingMode.parentChild)} - actions={ - <> - - - - } - > -
-
- - {t('datasetCreation.stepTwo.parentChunkForContext')} - - } - title={t('datasetCreation.stepTwo.paragraph')} - description={t('datasetCreation.stepTwo.paragraphTip')} - isChosen={parentChildConfig.chunkForContext === 'paragraph'} - onChosen={() => setParentChildConfig( - { - ...parentChildConfig, - chunkForContext: 'paragraph', - }, - )} - chosenConfig={ -
- setParentChildConfig({ - ...parentChildConfig, - parent: { - ...parentChildConfig.parent, - delimiter: e.target.value, - }, - })} - /> - setParentChildConfig({ - ...parentChildConfig, - parent: { - ...parentChildConfig.parent, - maxLength: value, - }, - })} - /> -
- } - /> - } - title={t('datasetCreation.stepTwo.fullDoc')} - description={t('datasetCreation.stepTwo.fullDocTip')} - onChosen={() => setParentChildConfig( - { - ...parentChildConfig, - chunkForContext: 'full-doc', - }, - )} - isChosen={parentChildConfig.chunkForContext === 'full-doc'} - /> -
- + {(!datasetId || [ChuckingMode.text, ChuckingMode.qa].includes(docForm)) + && } + activeHeaderClassName='bg-gradient-to-r from-[#EFF0F9] to-[#F9FAFB]' + description={t('datasetCreation.stepTwo.generalTip')} + isActive={ + [ChuckingMode.text, ChuckingMode.qa].includes(docForm) + } + onSwitched={() => + handleChangeDocform(ChuckingMode.text) + } + actions={ + <> + + + + } + noHighlight={Boolean(datasetId)} + >
- - {t('datasetCreation.stepTwo.childChunkForRetrieval')} - -
+
setParentChildConfig({ - ...parentChildConfig, - child: { - ...parentChildConfig.child, - delimiter: e.target.value, - }, - })} + value={segmentIdentifier} + onChange={e => setSegmentIdentifier(e.target.value)} /> setParentChildConfig({ - ...parentChildConfig, - child: { - ...parentChildConfig.child, - maxLength: value, + value={maxChunkLength} + onChange={setMaxChunkLength} + /> + +
+
+
+ {t('datasetCreation.stepTwo.rules')} +
+ {rules.map(rule => ( +
{ + ruleChangeHandle(rule.id) + }}> + + +
+ ))} +
+
+
+ {IS_CE_EDITION && <> +
+ { + if (docForm === ChuckingMode.qa) + handleChangeDocform(ChuckingMode.text) + else + handleChangeDocform(ChuckingMode.qa) + }} + className='mr-2' + /> +
+ + {t('datasetCreation.stepTwo.QALanguage')} + +
+ +
+ +
+
+ {docForm === ChuckingMode.qa && ( +
+ + + {t('datasetCreation.stepTwo.QATip')} + +
+ )} + } +
+ } + { + (!datasetId || docForm === ChuckingMode.parentChild) + && } + effectImg={OrangeEffect.src} + activeHeaderClassName='bg-gradient-to-r from-[#F9F1EE] to-[#F9FAFB]' + description={t('datasetCreation.stepTwo.parentChildTip')} + isActive={docForm === ChuckingMode.parentChild} + onSwitched={() => handleChangeDocform(ChuckingMode.parentChild)} + actions={ + <> + + + + } + noHighlight={Boolean(datasetId)} + > +
+
+ + {t('datasetCreation.stepTwo.parentChunkForContext')} + + } + title={t('datasetCreation.stepTwo.paragraph')} + description={t('datasetCreation.stepTwo.paragraphTip')} + isChosen={parentChildConfig.chunkForContext === 'paragraph'} + onChosen={() => setParentChildConfig( + { + ...parentChildConfig, + chunkForContext: 'paragraph', }, - })} + )} + chosenConfig={ +
+ setParentChildConfig({ + ...parentChildConfig, + parent: { + ...parentChildConfig.parent, + delimiter: e.target.value, + }, + })} + /> + setParentChildConfig({ + ...parentChildConfig, + parent: { + ...parentChildConfig.parent, + maxLength: value, + }, + })} + /> +
+ } + /> + } + title={t('datasetCreation.stepTwo.fullDoc')} + description={t('datasetCreation.stepTwo.fullDocTip')} + onChosen={() => setParentChildConfig( + { + ...parentChildConfig, + chunkForContext: 'full-doc', + }, + )} + isChosen={parentChildConfig.chunkForContext === 'full-doc'} />
-
+
- {t('datasetCreation.stepTwo.rules')} + {t('datasetCreation.stepTwo.childChunkForRetrieval')} -
- {rules.map(rule => ( -
{ - ruleChangeHandle(rule.id) - }}> - - -
- ))} +
+ setParentChildConfig({ + ...parentChildConfig, + child: { + ...parentChildConfig.child, + delimiter: e.target.value, + }, + })} + /> + setParentChildConfig({ + ...parentChildConfig, + child: { + ...parentChildConfig.child, + maxLength: value, + }, + })} + /> +
+ +
+ + {t('datasetCreation.stepTwo.rules')} + +
+ {rules.map(rule => ( +
{ + ruleChangeHandle(rule.id) + }}> + + +
+ ))} +
-
- + }
@@ -825,26 +838,69 @@ const StepTwo = ({ )} {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && ( -
-
- Economical Icon -
- {!hasSetIndexType && } -
-
{t('datasetCreation.stepTwo.economical')}
-
{t('datasetCreation.stepTwo.economicalTip')}
-
-
+ +
+ setIsQAConfirmDialogOpen(false)} className='w-[432px]'> +
+

+ {t('datasetCreation.stepTwo.qaSwitchHighQualityTipTitle')} +

+

+ {t('datasetCreation.stepTwo.qaSwitchHighQualityTipContent')} +

+
+
+ + +
+
+
+ Economical Icon +
+ {!hasSetIndexType && } +
+
{t('datasetCreation.stepTwo.economical')}
+
{t('datasetCreation.stepTwo.economicalTip')}
+
+
+
+ +
+ { + docForm === ChuckingMode.qa + ? t('datasetCreation.stepTwo.notAvailableForQA') + : t('datasetCreation.stepTwo.notAvailableForParentChild') + } +
+
+ )}
{hasSetIndexType && indexType === IndexingType.ECONOMICAL && ( @@ -937,7 +993,7 @@ const StepTwo = ({ >
({ name: file.name!, id: file.id!, extension: 'pdf' }))} + files={files as Array>} onChange={(selected) => { currentEstimateMutation.reset() setPreviewFile(selected) diff --git a/web/app/components/datasets/create/step-two/option-card.tsx b/web/app/components/datasets/create/step-two/option-card.tsx index 466fa78111..ba84d335c3 100644 --- a/web/app/components/datasets/create/step-two/option-card.tsx +++ b/web/app/components/datasets/create/step-two/option-card.tsx @@ -51,14 +51,15 @@ type OptionCardProps = { actions?: ReactNode effectImg?: string onSwitched?: () => void + noHighlight?: boolean } & Omit, 'title'> export const OptionCard: FC = (props) => { - const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, onClick, ...rest } = props + const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, onClick, noHighlight, ...rest } = props return
= (props) => { icon={icon} title={title} description={description} - isActive={isActive} + isActive={isActive && !noHighlight} activeClassName={activeHeaderClassName} effectImg={effectImg} /> diff --git a/web/app/components/datasets/documents/detail/completed/common/tag.tsx b/web/app/components/datasets/documents/detail/completed/common/tag.tsx index 9517d38f1e..c88bffc736 100644 --- a/web/app/components/datasets/documents/detail/completed/common/tag.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/tag.tsx @@ -1,8 +1,9 @@ import React from 'react' +import cn from '@/utils/classnames' -const Tag = ({ text }: { text: string }) => { +const Tag = ({ text, className }: { text: string; className?: string }) => { return ( -
+
# {text}
diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index dda54cd0ea..4b428a5c84 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 (
@@ -179,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) => { @@ -209,7 +210,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() @@ -225,7 +226,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 handleUpdateSegment = async ( @@ -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/hit-testing/assets/test-data.ts b/web/app/components/datasets/hit-testing/assets/test-data.ts index 39a9788afa..74f27908b8 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,8 +42,19 @@ export const generalResultData = [ doc_type: null, }, }, - child_chunks: null, - score: 0.8771945, + 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.', + }, + { + id: '2', + score: 0.5, + content: 'It is quite natural for ', + }, + ], + score: 0.99, tsne_position: null, }, { @@ -86,7 +99,7 @@ export const generalResultData = [ }, }, child_chunks: null, - score: 0.8642928, + score: 1, tsne_position: null, }, { @@ -131,7 +144,7 @@ export const generalResultData = [ }, }, child_chunks: null, - score: 0.80618876, + score: 0.2, 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..052d270b3f --- /dev/null +++ b/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx @@ -0,0 +1,30 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { SliceContent } from '../../formatted-text/flavours/shared' +import Score from './score' +import type { HitTestingChildChunk } from '@/models/datasets' + +type Props = { + payload: HitTestingChildChunk + isShowAll: boolean +} + +const ChildChunks: FC = ({ + payload, + isShowAll, +}) => { + const { id, score, content } = payload + return ( +
+
+
C-{id}
+ +
+ {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..5b5f42a6af --- /dev/null +++ b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx @@ -0,0 +1,89 @@ +'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' + +const i18nPrefix = 'datasetHitTesting' + +type Props = { + payload: HitTesting + onHide: () => void +} + +const ChunkDetailModal: FC = ({ + payload, + onHide, +}) => { + const { t } = useTranslation() + const { segment, score, child_chunks } = payload + const { position, content, keywords, document } = segment + const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0) + const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum + const maxHeighClassName = 'max-h-[min(752px,_80vh)] overflow-y-auto' + return ( + +
+
+ {/* Meta info */} +
+
+ + +
+ + {document.name} +
+
+ +
+
+ {content} +
+ {!isParentChildRetrieval && keywords && keywords.length > 0 && ( +
+
{t(`${i18nPrefix}.keyword`)}
+
+ {keywords.map(keyword => ( + + ))} +
+
+ )} +
+ + {isParentChildRetrieval && ( +
+
{t(`${i18nPrefix}.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 a53bbdcd60..f426699fb4 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,20 @@ 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/common/segment-index-tag' +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' + +const i18nPrefix = 'datasetHitTesting' type Props = { payload: HitTesting } @@ -13,21 +24,88 @@ 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(-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(`${i18nPrefix}.hitChunks`, { num: child_chunks.length })}
+
+ {!isFold && ( +
+ {child_chunks.map(item => ( +
+ +
+ ))} +
+ )} +
+ )} + {!isParentChildRetrieval && keywords && keywords.length > 0 && ( +
+ {keywords.map(keyword => ( + + ))} +
+ )} +
+ {/* Foot */} +
+
+ + {document.name} +
+
+
{t(`${i18nPrefix}.open`)}
+ +
+
+ + { + isShowDetailModal && ( + + ) + } +
) } export default React.memo(ResultItem) 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..6f48edd4bd --- /dev/null +++ b/web/app/components/datasets/hit-testing/components/score.tsx @@ -0,0 +1,25 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from '@/utils/classnames' + +type Props = { + value: number + besideChunkName?: boolean +} + +const Score: FC = ({ + value, + besideChunkName, +}) => { + 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..2a0a4aeb8a 100644 --- a/web/app/components/datasets/hit-testing/index.tsx +++ b/web/app/components/datasets/hit-testing/index.tsx @@ -11,7 +11,7 @@ import Textarea from './textarea' 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' @@ -24,6 +24,8 @@ import DatasetDetailContext from '@/context/dataset-detail' import type { RetrievalConfig } from '@/types/app' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useTimestamp from '@/hooks/use-timestamp' +import docStyle from '@/app/components/datasets/documents/detail/completed/style.module.css' + const limit = 10 type Props = { @@ -64,55 +66,35 @@ 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 = () => ( - // for test -
- //
- //
- //
- // {t('datasetHitTesting.hit.emptyTip')} - //
- //
+
+
+
+ {t('datasetHitTesting.hit.emptyTip')} +
+
) useEffect(() => { @@ -190,30 +172,23 @@ const HitTesting: FC = ({ datasetId }: Props) => { )}
-
- {renderHitResults(generalResultData, onClickCard)} +
+ {/* {renderHitResults(generalResultData)} */} {submitLoading - ?
- - -
+ ? : ( (() => { if (!hitResult?.records.length && !externalHitResult?.records.length) 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/dataset-creation.ts b/web/i18n/en-US/dataset-creation.ts index 014f9a1a93..514738e9da 100644 --- a/web/i18n/en-US/dataset-creation.ts +++ b/web/i18n/en-US/dataset-creation.ts @@ -166,6 +166,11 @@ const translation = { datasetSettingLink: 'Knowledge settings.', previewChunkTip: 'Click the \'Preview Chunk\' button on the left to load the preview', previewChunkCount: '{{count}} Estimated chunks', + switch: 'Switch', + qaSwitchHighQualityTipTitle: 'Q&A Format Requires High-quality Indexing Method', + qaSwitchHighQualityTipContent: 'Currently, only high-quality index method supports Q&A format chunking. Would you like to switch to high-quality mode?', + notAvailableForParentChild: 'Not available for Parent-child Index', + notAvailableForQA: 'Not available for Q&A Index', }, stepThree: { creationTitle: '🎉 Knowledge created', diff --git a/web/i18n/en-US/dataset-hit-testing.ts b/web/i18n/en-US/dataset-hit-testing.ts index 6dbfa47fee..8b8629e90a 100644 --- a/web/i18n/en-US/dataset-hit-testing.ts +++ b/web/i18n/en-US/dataset-hit-testing.ts @@ -19,12 +19,16 @@ 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', viewChart: 'View VECTOR CHART', viewDetail: 'View Detail', + chunkDetail: 'Chunk Detail', + hitChunks: 'Hit {{num}} child chunks', + open: 'Open', + keyword: 'Keywords', } export default translation diff --git a/web/i18n/zh-Hans/dataset-creation.ts b/web/i18n/zh-Hans/dataset-creation.ts index c8f64777fb..48e1b4e7f2 100644 --- a/web/i18n/zh-Hans/dataset-creation.ts +++ b/web/i18n/zh-Hans/dataset-creation.ts @@ -166,6 +166,11 @@ const translation = { datasetSettingLink: '知识库设置。', previewChunkTip: '点击左侧的“预览块”按钮来加载预览', previewChunkCount: '{{count}} 预估块', + switch: '切换', + qaSwitchHighQualityTipTitle: 'Q&A 格式需要高质量的索引方法', + qaSwitchHighQualityTipContent: '目前,只有高质量的索引方法支持 Q&A 格式分块。您要切换到高质量模式吗?', + notAvailableForParentChild: '不支持父子索引', + notAvailableForQA: '不支持 Q&A 索引', }, stepThree: { creationTitle: '🎉 知识库已创建', diff --git a/web/i18n/zh-Hans/dataset-hit-testing.ts b/web/i18n/zh-Hans/dataset-hit-testing.ts index 09cfdc2824..caf88acc76 100644 --- a/web/i18n/zh-Hans/dataset-hit-testing.ts +++ b/web/i18n/zh-Hans/dataset-hit-testing.ts @@ -19,12 +19,16 @@ const translation = { testing: '测试', }, hit: { - title: '召回段落', + title: '{{num}} 个召回段落', emptyTip: '召回测试结果将展示在这里', }, noRecentTip: '最近无查询结果', viewChart: '查看向量图表', viewDetail: '查看详情', + chunkDetail: '段落详情', + hitChunks: '命中 {{num}} 个子段落', + open: '打开', + keyword: '关键词', } export default translation diff --git a/web/models/datasets.ts b/web/models/datasets.ts index 921b72b545..ac2484ef79 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 = {