Переглянути джерело

fix: external dataset hit test display issue(#12564) (#12612)

Co-authored-by: zhuxinliang <zhuxinliang@didiglobal.com>
le0zh 3 місяців тому
батько
коміт
0c6a8a130e

+ 60 - 0
web/app/components/datasets/hit-testing/components/result-item-external.tsx

@@ -0,0 +1,60 @@
+'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)

+ 42 - 0
web/app/components/datasets/hit-testing/components/result-item-footer.tsx

@@ -0,0 +1,42 @@
+'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)

+ 45 - 0
web/app/components/datasets/hit-testing/components/result-item-meta.tsx

@@ -0,0 +1,45 @@
+'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)

+ 7 - 34
web/app/components/datasets/hit-testing/components/result-item.tsx

@@ -2,33 +2,29 @@
 import type { FC } from 'react'
 import React from 'react'
 import { useTranslation } from 'react-i18next'
-import { RiArrowDownSLine, RiArrowRightSLine, RiArrowRightUpLine } from '@remixicon/react'
+import { RiArrowDownSLine, RiArrowRightSLine } 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 ResultItemMeta from './result-item-meta'
+import ResultItemFooter from './result-item-footer'
 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'
 import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type'
 
 const i18nPrefix = 'datasetHitTesting'
 type Props = {
-  isExternal: boolean
   payload: HitTesting
 }
 
 const ResultItem: FC<Props> = ({
-  isExternal,
   payload,
 }) => {
   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 isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0)
   const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum
@@ -46,18 +42,7 @@ const ResultItem: FC<Props> = ({
   return (
     <div className={cn('pt-3 bg-chat-bubble-bg rounded-xl hover:shadow-lg cursor-pointer')} onClick={showDetailModal}>
       {/* 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 */}
       <div className='mt-1 px-3'>
@@ -88,19 +73,7 @@ const ResultItem: FC<Props> = ({
         )}
       </div>
       {/* 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 && (

+ 19 - 12
web/app/components/datasets/hit-testing/index.tsx

@@ -12,8 +12,9 @@ import Textarea from './textarea'
 import s from './style.module.css'
 import ModifyRetrievalModal from './modify-retrieval-modal'
 import ResultItem from './components/result-item'
+import ResultItemExternal from './components/result-item-external'
 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 Drawer from '@/app/components/base/drawer'
 import Pagination from '@/app/components/base/pagination'
@@ -41,7 +42,7 @@ const RecordsEmpty: FC = () => {
   </div>
 }
 
-const HitTesting: FC<Props> = ({ datasetId }: Props) => {
+const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
   const { t } = useTranslation()
   const { formatTime } = useTimestamp()
 
@@ -68,19 +69,25 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
   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[]) => (
+  const renderHitResults = (results: HitTesting[] | ExternalKnowledgeBaseHitTesting[]) => (
     <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'>
         {t('datasetHitTesting.hit.title', { num: results.length })}
       </div>
       <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>
   )
@@ -130,7 +137,7 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
               <>
                 <div className='grow overflow-y-auto'>
                   <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>
                         <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>
@@ -208,4 +215,4 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
   )
 }
 
-export default HitTesting
+export default HitTestingPage

+ 1 - 0
web/app/components/datasets/hit-testing/textarea.tsx

@@ -84,6 +84,7 @@ const TextAreaWithButton = ({
   }
 
   const externalRetrievalTestingOnSubmit = async () => {
+    setLoading(true)
     const [e, res] = await asyncRunSafe<ExternalKnowledgeBaseHitTestingResponse>(
       externalKnowledgeBaseHitTesting({
         datasetId,