Co-authored-by: zhuxinliang <zhuxinliang@didiglobal.com>tags/0.15.2
| 'use client' | |||||
| import type { FC } from 'react' | |||||
| import React from 'react' | |||||
| import { useTranslation } from 'react-i18next' | |||||
| import { useBoolean } from 'ahooks' | |||||
| import ResultItemMeta from './result-item-meta' | |||||
| import ResultItemFooter from './result-item-footer' | |||||
| import type { ExternalKnowledgeBaseHitTesting } from '@/models/datasets' | |||||
| import cn from '@/utils/classnames' | |||||
| import Modal from '@/app/components/base/modal' | |||||
| import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' | |||||
| const i18nPrefix = 'datasetHitTesting' | |||||
| type Props = { | |||||
| payload: ExternalKnowledgeBaseHitTesting | |||||
| positionId: number | |||||
| } | |||||
| const ResultItemExternal: FC<Props> = ({ payload, positionId }) => { | |||||
| const { t } = useTranslation() | |||||
| const { content, title, score } = payload | |||||
| const [ | |||||
| isShowDetailModal, | |||||
| { setTrue: showDetailModal, setFalse: hideDetailModal }, | |||||
| ] = useBoolean(false) | |||||
| return ( | |||||
| <div className={cn('pt-3 bg-chat-bubble-bg rounded-xl hover:shadow-lg cursor-pointer')} onClick={showDetailModal}> | |||||
| {/* Meta info */} | |||||
| <ResultItemMeta className='px-3' labelPrefix={'Chunk'} positionId={positionId} wordCount={content.length} score={score} /> | |||||
| {/* Main */} | |||||
| <div className='mt-1 px-3'> | |||||
| <div className='line-clamp-2 body-md-regular break-all'>{content}</div> | |||||
| </div> | |||||
| {/* Foot */} | |||||
| <ResultItemFooter docType={FileAppearanceTypeEnum.custom} docTitle={title} showDetailModal={showDetailModal} /> | |||||
| {isShowDetailModal && ( | |||||
| <Modal | |||||
| title={t(`${i18nPrefix}.chunkDetail`)} | |||||
| className={'!min-w-[800px]'} | |||||
| closable | |||||
| onClose={hideDetailModal} | |||||
| isShow={isShowDetailModal} | |||||
| > | |||||
| <div className='mt-4 flex-1'> | |||||
| <ResultItemMeta labelPrefix={'Chunk'} positionId={positionId} wordCount={content.length} score={score} /> | |||||
| <div className={cn('mt-2 body-md-regular text-text-secondary break-all', 'h-[min(539px,_80vh)] overflow-y-auto')}> | |||||
| {content} | |||||
| </div> | |||||
| </div> | |||||
| </Modal> | |||||
| )} | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| export default React.memo(ResultItemExternal) |
| 'use client' | |||||
| import type { FC } from 'react' | |||||
| import React from 'react' | |||||
| import { useTranslation } from 'react-i18next' | |||||
| import { RiArrowRightUpLine } from '@remixicon/react' | |||||
| import FileIcon from '@/app/components/base/file-uploader/file-type-icon' | |||||
| import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' | |||||
| type Props = { | |||||
| docType: FileAppearanceTypeEnum | |||||
| docTitle: string | |||||
| showDetailModal: () => void | |||||
| } | |||||
| const i18nPrefix = 'datasetHitTesting' | |||||
| const ResultItemFooter: FC<Props> = ({ | |||||
| docType, | |||||
| docTitle, | |||||
| showDetailModal, | |||||
| }) => { | |||||
| const { t } = useTranslation() | |||||
| return ( | |||||
| <div className="mt-3 flex justify-between items-center h-10 pl-3 pr-2 border-t border-divider-subtle"> | |||||
| <div className="grow flex items-center space-x-1"> | |||||
| <FileIcon type={docType} size="sm" /> | |||||
| <span className="grow w-0 truncate text-text-secondary text-[13px] font-normal"> | |||||
| {docTitle} | |||||
| </span> | |||||
| </div> | |||||
| <div | |||||
| className="flex items-center space-x-1 cursor-pointer text-text-tertiary" | |||||
| onClick={showDetailModal} | |||||
| > | |||||
| <div className="text-xs uppercase">{t(`${i18nPrefix}.open`)}</div> | |||||
| <RiArrowRightUpLine className="size-3.5" /> | |||||
| </div> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| export default React.memo(ResultItemFooter) |
| 'use client' | |||||
| import type { FC } from 'react' | |||||
| import React from 'react' | |||||
| import { useTranslation } from 'react-i18next' | |||||
| import { SegmentIndexTag } from '../../documents/detail/completed/common/segment-index-tag' | |||||
| import Dot from '../../documents/detail/completed/common/dot' | |||||
| import Score from './score' | |||||
| import cn from '@/utils/classnames' | |||||
| type Props = { | |||||
| labelPrefix: string | |||||
| positionId: number | |||||
| wordCount: number | |||||
| score: number | |||||
| className?: string | |||||
| } | |||||
| const ResultItemMeta: FC<Props> = ({ | |||||
| labelPrefix, | |||||
| positionId, | |||||
| wordCount, | |||||
| score, | |||||
| className, | |||||
| }) => { | |||||
| const { t } = useTranslation() | |||||
| return ( | |||||
| <div className={cn('flex justify-between items-center', className)}> | |||||
| <div className="flex items-center space-x-2"> | |||||
| <SegmentIndexTag | |||||
| labelPrefix={labelPrefix} | |||||
| positionId={positionId} | |||||
| className={cn('w-fit group-hover:opacity-100')} | |||||
| /> | |||||
| <Dot /> | |||||
| <div className="system-xs-medium text-text-tertiary"> | |||||
| {wordCount} {t('datasetDocuments.segment.characters', { count: wordCount })} | |||||
| </div> | |||||
| </div> | |||||
| <Score value={score} /> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| export default React.memo(ResultItemMeta) |
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React from 'react' | import React from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { RiArrowDownSLine, RiArrowRightSLine, RiArrowRightUpLine } from '@remixicon/react' | |||||
| import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' | |||||
| import { useBoolean } from 'ahooks' | 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 ChildChunkItem from './child-chunks-item' | ||||
| import ChunkDetailModal from './chunk-detail-modal' | import ChunkDetailModal from './chunk-detail-modal' | ||||
| import ResultItemMeta from './result-item-meta' | |||||
| import ResultItemFooter from './result-item-footer' | |||||
| import type { HitTesting } from '@/models/datasets' | import type { HitTesting } from '@/models/datasets' | ||||
| import cn from '@/utils/classnames' | 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 type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' | ||||
| import Tag from '@/app/components/datasets/documents/detail/completed/common/tag' | import Tag from '@/app/components/datasets/documents/detail/completed/common/tag' | ||||
| import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type' | import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type' | ||||
| const i18nPrefix = 'datasetHitTesting' | const i18nPrefix = 'datasetHitTesting' | ||||
| type Props = { | type Props = { | ||||
| isExternal: boolean | |||||
| payload: HitTesting | payload: HitTesting | ||||
| } | } | ||||
| const ResultItem: FC<Props> = ({ | const ResultItem: FC<Props> = ({ | ||||
| isExternal, | |||||
| payload, | payload, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { segment, content: externalContent, score, child_chunks } = payload | |||||
| const data = isExternal ? externalContent : segment | |||||
| const { segment, score, child_chunks } = payload | |||||
| const data = segment | |||||
| const { position, word_count, content, keywords, document } = data | const { position, word_count, content, keywords, document } = data | ||||
| const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0) | const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0) | ||||
| const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum | const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum | ||||
| return ( | return ( | ||||
| <div className={cn('pt-3 bg-chat-bubble-bg rounded-xl hover:shadow-lg cursor-pointer')} onClick={showDetailModal}> | <div className={cn('pt-3 bg-chat-bubble-bg rounded-xl hover:shadow-lg cursor-pointer')} onClick={showDetailModal}> | ||||
| {/* Meta info */} | {/* Meta info */} | ||||
| <div className='flex justify-between items-center px-3'> | |||||
| <div className='flex items-center space-x-2'> | |||||
| <SegmentIndexTag | |||||
| labelPrefix={`${isParentChildRetrieval ? 'Parent-' : ''}Chunk`} | |||||
| positionId={position} | |||||
| className={cn('w-fit group-hover:opacity-100')} | |||||
| /> | |||||
| <Dot /> | |||||
| <div className='system-xs-medium text-text-tertiary'>{word_count} {t('datasetDocuments.segment.characters', { count: word_count })}</div> | |||||
| </div> | |||||
| <Score value={score} /> | |||||
| </div> | |||||
| <ResultItemMeta className='px-3' labelPrefix={`${isParentChildRetrieval ? 'Parent-' : ''}Chunk`} positionId={position} wordCount={word_count} score={score} /> | |||||
| {/* Main */} | {/* Main */} | ||||
| <div className='mt-1 px-3'> | <div className='mt-1 px-3'> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| {/* Foot */} | {/* Foot */} | ||||
| <div className='mt-3 flex justify-between items-center h-10 pl-3 pr-2 border-t border-divider-subtle'> | |||||
| <div className='grow flex items-center space-x-1'> | |||||
| <FileIcon type={fileType} size='sm' /> | |||||
| <span className='grow w-0 truncate text-text-secondary text-[13px] font-normal'>{document.name}</span> | |||||
| </div> | |||||
| <div | |||||
| className='flex items-center space-x-1 cursor-pointer text-text-tertiary' | |||||
| onClick={showDetailModal} | |||||
| > | |||||
| <div className='text-xs uppercase'>{t(`${i18nPrefix}.open`)}</div> | |||||
| <RiArrowRightUpLine className='size-3.5' /> | |||||
| </div> | |||||
| </div> | |||||
| <ResultItemFooter docType={fileType} docTitle={document.name} showDetailModal={showDetailModal} /> | |||||
| { | { | ||||
| isShowDetailModal && ( | isShowDetailModal && ( |
| import s from './style.module.css' | import s from './style.module.css' | ||||
| import ModifyRetrievalModal from './modify-retrieval-modal' | import ModifyRetrievalModal from './modify-retrieval-modal' | ||||
| import ResultItem from './components/result-item' | import ResultItem from './components/result-item' | ||||
| import ResultItemExternal from './components/result-item-external' | |||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import type { ExternalKnowledgeBaseHitTestingResponse, HitTestingResponse } from '@/models/datasets' | |||||
| import type { ExternalKnowledgeBaseHitTesting, ExternalKnowledgeBaseHitTestingResponse, HitTesting, HitTestingResponse } from '@/models/datasets' | |||||
| import Loading from '@/app/components/base/loading' | import Loading from '@/app/components/base/loading' | ||||
| import Drawer from '@/app/components/base/drawer' | import Drawer from '@/app/components/base/drawer' | ||||
| import Pagination from '@/app/components/base/pagination' | import Pagination from '@/app/components/base/pagination' | ||||
| </div> | </div> | ||||
| } | } | ||||
| const HitTesting: FC<Props> = ({ datasetId }: Props) => { | |||||
| const HitTestingPage: FC<Props> = ({ datasetId }: Props) => { | |||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { formatTime } = useTimestamp() | const { formatTime } = useTimestamp() | ||||
| const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) | const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) | ||||
| const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false) | const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false) | ||||
| const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile) | const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile) | ||||
| const renderHitResults = (results: any[]) => ( | |||||
| const renderHitResults = (results: HitTesting[] | ExternalKnowledgeBaseHitTesting[]) => ( | |||||
| <div className='h-full flex flex-col py-3 px-4 rounded-t-2xl bg-background-body'> | <div className='h-full flex flex-col py-3 px-4 rounded-t-2xl bg-background-body'> | ||||
| <div className='shrink-0 pl-2 text-text-primary font-semibold leading-6 mb-2'> | <div className='shrink-0 pl-2 text-text-primary font-semibold leading-6 mb-2'> | ||||
| {t('datasetHitTesting.hit.title', { num: results.length })} | {t('datasetHitTesting.hit.title', { num: results.length })} | ||||
| </div> | </div> | ||||
| <div className='grow overflow-y-auto space-y-2'> | <div className='grow overflow-y-auto space-y-2'> | ||||
| {results.map((record, idx) => ( | |||||
| <ResultItem | |||||
| key={idx} | |||||
| payload={record} | |||||
| isExternal={isExternal} | |||||
| /> | |||||
| ))} | |||||
| {results.map((record, idx) => | |||||
| isExternal | |||||
| ? ( | |||||
| <ResultItemExternal | |||||
| key={idx} | |||||
| positionId={idx + 1} | |||||
| payload={record as ExternalKnowledgeBaseHitTesting} | |||||
| /> | |||||
| ) | |||||
| : ( | |||||
| <ResultItem key={idx} payload={record as HitTesting} /> | |||||
| ), | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ) | ) | ||||
| <> | <> | ||||
| <div className='grow overflow-y-auto'> | <div className='grow overflow-y-auto'> | ||||
| <table className={'w-full border-collapse border-0 text-[13px] leading-4 text-text-secondary '}> | <table className={'w-full border-collapse border-0 text-[13px] leading-4 text-text-secondary '}> | ||||
| <thead className="sticky top-0 h-7 leading-7 text-xs text-text-tertiary font-medium uppercase"> | |||||
| <thead className='sticky top-0 h-7 leading-7 text-xs text-text-tertiary font-medium uppercase'> | |||||
| <tr> | <tr> | ||||
| <td className='pl-3 w-[128px] rounded-l-lg bg-background-section-burn'>{t('datasetHitTesting.table.header.source')}</td> | <td className='pl-3 w-[128px] rounded-l-lg bg-background-section-burn'>{t('datasetHitTesting.table.header.source')}</td> | ||||
| <td className='bg-background-section-burn'>{t('datasetHitTesting.table.header.text')}</td> | <td className='bg-background-section-burn'>{t('datasetHitTesting.table.header.text')}</td> | ||||
| ) | ) | ||||
| } | } | ||||
| export default HitTesting | |||||
| export default HitTestingPage |
| } | } | ||||
| const externalRetrievalTestingOnSubmit = async () => { | const externalRetrievalTestingOnSubmit = async () => { | ||||
| setLoading(true) | |||||
| const [e, res] = await asyncRunSafe<ExternalKnowledgeBaseHitTestingResponse>( | const [e, res] = await asyncRunSafe<ExternalKnowledgeBaseHitTestingResponse>( | ||||
| externalKnowledgeBaseHitTesting({ | externalKnowledgeBaseHitTesting({ | ||||
| datasetId, | datasetId, |