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/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx
index 0b5414a816..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 (
@@ -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) => {
@@ -210,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()
@@ -226,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(() => {
@@ -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..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..d7c5264c03
--- /dev/null
+++ b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx
@@ -0,0 +1,83 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { SegmentIndexTag } from '../../documents/detail/completed'
+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'
+
+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 => (
+
{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..29c61e9de7 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,17 @@
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 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'
+
type Props = {
payload: HitTesting
}
@@ -13,20 +21,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 => (
+
{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..0aef53cd6d 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'
@@ -83,20 +84,9 @@ const HitTesting: FC = ({ datasetId }: Props) => {
{results.map((record, idx) => (
- onClickCard(record)}
+ payload={record}
/>
))}
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 = {