Feat/parent child retrieval (#12086)
Signed-off-by: yihong0618 <zouzou0208@gmail.com> Signed-off-by: -LAN- <laipz8200@outlook.com> Co-authored-by: AkaraChen <akarachen@outlook.com> Co-authored-by: nite-knite <nkCoding@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Warren Chen <warren.chen830@gmail.com> Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: Yi Xiao <54782454+YIXIAO0@users.noreply.github.com> Co-authored-by: yihong <zouzou0208@gmail.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: KVOJJJin <jzongcode@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: JzoNgKVO <27049666+JzoNgKVO@users.noreply.github.com> Co-authored-by: Charlie.Wei <luowei@cvte.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: huayaoyue6 <huayaoyue@163.com> Co-authored-by: kurokobo <kuro664@gmail.com> Co-authored-by: Matsuda <yiyth.fcb6@gmail.com> Co-authored-by: shirochan <s.yusuke0711@gmail.com> Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com> Co-authored-by: Huỳnh Gia Bôi <boihuynh147@gmail.com> Co-authored-by: Julian Huynh <julian.huynh@immersio.io> Co-authored-by: Hash Brown <hi@xzd.me> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: Kazuki Takamatsu <kazuki.takamatsu@chowagiken.co.jp> Co-authored-by: Trey Dong <1346650911@qq.com> Co-authored-by: VoidIsVoid <343750470@qq.com> Co-authored-by: Gimling <huangjl@ruyi.ai> Co-authored-by: xiandan-erizo <xiandan.erizo@gmail.com> Co-authored-by: Muneyuki Noguchi <nogu.dev@gmail.com> Co-authored-by: zhaobingshuang <1475195565@qq.com> Co-authored-by: zhaobs <zhaobs@cailian.net> Co-authored-by: suzuki.sh <s2terminal@users.noreply.github.com> Co-authored-by: Yingchun Lai <laiyingchun@apache.org> Co-authored-by: huanshare <huanshare@live.com> Co-authored-by: huanshare <liuhuan101@longfor.com> Co-authored-by: orangeclk <orangeclk@users.noreply.github.com> Co-authored-by: 문정현 <120004247+JungHyunMoon@users.noreply.github.com> Co-authored-by: barabicu <kztk533@gmail.com> Co-authored-by: Wei Mingzhi <whistler_wmz@users.sf.net> Co-authored-by: Paul van Oorschot <20116814+pvoo@users.noreply.github.com> Co-authored-by: zkyTech <zhangkunyuan@hotmail.com> Co-authored-by: zhangkunyuan <zhangkunyuan@cmhi.chinamobile.com> Co-authored-by: Tommy <34446820+Asterovim@users.noreply.github.com> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: Novice <857526207@qq.com> Co-authored-by: Novice Lee <novicelee@NovicedeMacBook-Pro.local> Co-authored-by: Novice Lee <novicelee@NoviPro.local> Co-authored-by: zxhlyh <16177003+zxhlyh@users.noreply.github.com> Co-authored-by: liuzhenghua <1090179900@qq.com> Co-authored-by: Jiang <65766008+AlwaysBluer@users.noreply.github.com> Co-authored-by: jiangzhijie <jiangzhijie.jzj@alibaba-inc.com> Co-authored-by: Joe <79627742+ZhouhaoJiang@users.noreply.github.com> Co-authored-by: Alok Shrivastwa <alok.shrivastwa@gmail.com> Co-authored-by: Alok Shrivastwa <Alok.Shrivastwa@microland.com> Co-authored-by: JasonVV <jasonwangiii@outlook.com> Co-authored-by: Hiroshi Fujita <fujita-h@users.noreply.github.com> Co-authored-by: Kevin9703 <51311316+Kevin9703@users.noreply.github.com> Co-authored-by: NFish <douxc512@gmail.com> Co-authored-by: Junyan Qin <1010553892@qq.com> Co-authored-by: IWAI, Masaharu <iwaim.sub@gmail.com> Co-authored-by: IWAI, Masaharu <iwai_masaharu@funkit.co.jp> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: luckylhb90 <luckylhb90@gmail.com> Co-authored-by: hobo.l <hobo.l@binance.com> Co-authored-by: douxc <7553076+douxc@users.noreply.github.com>
This commit is contained in:
@@ -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<Props> = ({
|
||||
payload,
|
||||
isShowAll,
|
||||
}) => {
|
||||
const { id, score, content, position } = payload
|
||||
return (
|
||||
<div
|
||||
className={!isShowAll ? 'line-clamp-2' : ''}
|
||||
>
|
||||
<div className='inline-flex items-center relative top-[-2px]'>
|
||||
<div className='flex items-center h-[20.5px] bg-state-accent-solid system-2xs-semibold-uppercase text-text-primary-on-surface px-1'>C-{position}</div>
|
||||
<Score value={score} besideChunkName />
|
||||
</div>
|
||||
<SliceContent className='py-0.5 bg-state-accent-hover group-hover:bg-state-accent-hover text-sm text-text-secondary font-normal'>{content}</SliceContent>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ChildChunks)
|
||||
@@ -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/common/segment-index-tag'
|
||||
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<Props> = ({
|
||||
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 heighClassName = isParentChildRetrieval ? 'h-[min(627px,_80vh)] overflow-y-auto' : 'h-[min(539px,_80vh)] overflow-y-auto'
|
||||
return (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}.chunkDetail`)}
|
||||
isShow
|
||||
closable
|
||||
onClose={onHide}
|
||||
className={cn(isParentChildRetrieval ? '!min-w-[1200px]' : '!min-w-[800px]')}
|
||||
>
|
||||
<div className='mt-4 flex'>
|
||||
<div className={cn('flex-1', isParentChildRetrieval && 'pr-6')}>
|
||||
{/* Meta info */}
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='grow flex items-center space-x-2'>
|
||||
<SegmentIndexTag
|
||||
labelPrefix={`${isParentChildRetrieval ? 'Parent-' : ''}Chunk`}
|
||||
positionId={position}
|
||||
className={cn('w-fit group-hover:opacity-100')}
|
||||
/>
|
||||
<Dot />
|
||||
<div className='grow flex items-center space-x-1'>
|
||||
<FileIcon type={extension} size='sm' />
|
||||
<span className='grow w-0 truncate text-text-secondary text-[13px] font-normal'>{document.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Score value={score} />
|
||||
</div>
|
||||
<div className={cn('mt-2 body-md-regular text-text-secondary', heighClassName)}>
|
||||
{content}
|
||||
</div>
|
||||
{!isParentChildRetrieval && keywords && keywords.length > 0 && (
|
||||
<div className='mt-6'>
|
||||
<div className='font-medium text-xs text-text-tertiary uppercase'>{t(`${i18nPrefix}.keyword`)}</div>
|
||||
<div className='mt-1 flex flex-wrap'>
|
||||
{keywords.map(keyword => (
|
||||
<Tag key={keyword} text={keyword} className='mr-2' />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isParentChildRetrieval && (
|
||||
<div className='flex-1 pl-6 pb-6'>
|
||||
<div className='system-xs-semibold-uppercase text-text-secondary'>{t(`${i18nPrefix}.hitChunks`, { num: child_chunks.length })}</div>
|
||||
<div className={cn('mt-1 space-y-2', heighClassName)}>
|
||||
{child_chunks.map(item => (
|
||||
<ChildChunksItem key={item.id} payload={item} isShowAll />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ChunkDetailModal)
|
||||
@@ -0,0 +1,121 @@
|
||||
'use client'
|
||||
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'
|
||||
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 { 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
|
||||
const fileType = extensionToFileType(extension)
|
||||
const [isFold, {
|
||||
toggle: toggleFold,
|
||||
}] = useBoolean(false)
|
||||
const Icon = isFold ? RiArrowRightSLine : RiArrowDownSLine
|
||||
|
||||
const [isShowDetailModal, {
|
||||
setTrue: showDetailModal,
|
||||
setFalse: hideDetailModal,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleClickCard = () => {
|
||||
if (!isParentChildRetrieval)
|
||||
showDetailModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('pt-3 bg-chat-bubble-bg rounded-xl hover:shadow-lg', !isParentChildRetrieval && 'cursor-pointer')} onClick={handleClickCard}>
|
||||
{/* 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>
|
||||
|
||||
{/* Main */}
|
||||
<div className='mt-1 px-3'>
|
||||
<div className='line-clamp-2 body-md-regular'>{content}</div>
|
||||
{isParentChildRetrieval && (
|
||||
<div className='mt-1'>
|
||||
<div className={cn('inline-flex items-center h-6 space-x-0.5 text-text-secondary select-none rounded-lg cursor-pointer', isFold && 'pl-1 bg-[linear-gradient(90deg,_rgba(200,_206,_218,_0.20)_0%,_rgba(200,_206,_218,_0.04)_100%)]')} onClick={toggleFold}>
|
||||
<Icon className={cn('w-4 h-4', isFold && 'opacity-50')} />
|
||||
<div className='text-xs font-semibold uppercase'>{t(`${i18nPrefix}.hitChunks`, { num: child_chunks.length })}</div>
|
||||
</div>
|
||||
{!isFold && (
|
||||
<div className='space-y-2'>
|
||||
{child_chunks.map(item => (
|
||||
<div key={item.id} className='ml-[7px] pl-[7px] border-l-[2px] border-text-accent-secondary'>
|
||||
<ChildChunkItem payload={item} isShowAll={false} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isParentChildRetrieval && keywords && keywords.length > 0 && (
|
||||
<div className='mt-2 flex flex-wrap'>
|
||||
{keywords.map(keyword => (
|
||||
<Tag key={keyword} text={keyword} className='mr-2' />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</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>
|
||||
|
||||
{
|
||||
isShowDetailModal && (
|
||||
<ChunkDetailModal
|
||||
payload={payload}
|
||||
onHide={hideDetailModal}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
export default React.memo(ResultItem)
|
||||
25
web/app/components/datasets/hit-testing/components/score.tsx
Normal file
25
web/app/components/datasets/hit-testing/components/score.tsx
Normal file
@@ -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<Props> = ({
|
||||
value,
|
||||
besideChunkName,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('relative items-center px-[5px] border border-components-progress-bar-border overflow-hidden', besideChunkName ? 'border-l-0 h-[20.5px]' : 'h-[20px] rounded-md')}>
|
||||
<div className={cn('absolute top-0 left-0 h-full bg-util-colors-blue-brand-blue-brand-100 border-r-[1.5px] border-components-progress-brand-progress', value === 1 && 'border-r-0')} style={{ width: `${value * 100}%` }} />
|
||||
<div className={cn('relative flex items-center h-full space-x-0.5 text-util-colors-blue-brand-blue-brand-700')}>
|
||||
<div className='system-2xs-medium-uppercase'>score</div>
|
||||
<div className='system-xs-semibold'>{value.toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Score)
|
||||
Reference in New Issue
Block a user