feat: enhance DocumentDetail and Completed components with child segment handling and improved layout

This commit is contained in:
twwu 2024-12-05 15:44:53 +08:00
parent 78fff31e61
commit b59e95785c
6 changed files with 341 additions and 125 deletions

View File

@ -1,4 +1,4 @@
import { type FC, useState } from 'react' import { type FC, useMemo, useState } from 'react'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import { FormattedText } from '../../../formatted-text/formatted' import { FormattedText } from '../../../formatted-text/formatted'
import { EditSlice } from '../../../formatted-text/flavours/edit-slice' import { EditSlice } from '../../../formatted-text/flavours/edit-slice'
@ -9,33 +9,46 @@ import classNames from '@/utils/classnames'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
type IChildSegmentCardProps = { type IChildSegmentCardProps = {
child_chunks: ChildChunkDetail[] childChunks: ChildChunkDetail[]
onSave: () => void
handleInputChange: (value: string) => void handleInputChange: (value: string) => void
enabled: boolean
} }
const ChildSegmentList: FC<IChildSegmentCardProps> = ({ const ChildSegmentList: FC<IChildSegmentCardProps> = ({
child_chunks, childChunks,
onSave,
handleInputChange, handleInputChange,
enabled,
}) => { }) => {
let parentMode = useDocumentContext(s => s.parentMode) const parentMode = useDocumentContext(s => s.parentMode)
parentMode = 'paragraph'
const [collapsed, setCollapsed] = useState(true) const [collapsed, setCollapsed] = useState(true)
const toggleCollapse = () => { const toggleCollapse = () => {
setCollapsed(!collapsed) setCollapsed(!collapsed)
} }
const isParagraphMode = useMemo(() => {
return parentMode === 'paragraph'
}, [parentMode])
const isFullDocMode = useMemo(() => {
return parentMode === 'full-doc'
}, [parentMode])
const contentOpacity = useMemo(() => {
return enabled ? '' : 'opacity-50 group-hover/card:opacity-100'
}, [enabled])
return ( return (
<div className='flex flex-col p-1 pb-2'> <div className={classNames('flex flex-col', contentOpacity, isParagraphMode ? 'p-1 pb-2' : 'px-3 grow overflow-y-hidden')}>
<div className='flex items-center justify-between'> {isFullDocMode ? <Divider type='horizontal' className='h-[1px] bg-divider-subtle my-1' /> : null}
<div className={classNames('h-7 flex items-center pl-1 pr-3 rounded-lg', collapsed ? 'bg-dataset-child-chunk-expand-btn-bg' : '')} onClick={(event) => { <div className={classNames('flex items-center justify-between', isFullDocMode ? 'pt-2 pb-3' : '')}>
<div className={classNames('h-7 flex items-center pl-1 pr-3 rounded-lg', (isParagraphMode && collapsed) ? 'bg-dataset-child-chunk-expand-btn-bg' : '')} onClick={(event) => {
event.stopPropagation() event.stopPropagation()
toggleCollapse() toggleCollapse()
}}> }}>
{ {
parentMode === 'paragraph' isParagraphMode
? collapsed ? collapsed
? ( ? (
<RiArrowRightSLine className='w-4 h-4 text-text-secondary opacity-50 mr-0.5' /> <RiArrowRightSLine className='w-4 h-4 text-text-secondary opacity-50 mr-0.5' />
@ -43,11 +56,18 @@ const ChildSegmentList: FC<IChildSegmentCardProps> = ({
: (<RiArrowDownSLine className='w-4 h-4 text-text-secondary mr-0.5' />) : (<RiArrowDownSLine className='w-4 h-4 text-text-secondary mr-0.5' />)
: null : null
} }
<span className='text-text-secondary system-sm-semibold-uppercase'>{`${child_chunks.length} CHILD CHUNKS`}</span> <span className='text-text-secondary system-sm-semibold-uppercase'>{`${childChunks.length} CHILD CHUNKS`}</span>
<span className='hidden group-hover/card:inline-block text-text-quaternary text-xs font-medium pl-1.5'>·</span> <span className={classNames('text-text-quaternary text-xs font-medium pl-1.5', isParagraphMode ? 'hidden group-hover/card:inline-block' : '')}>·</span>
<button className='hidden group-hover/card:inline-block px-1.5 py-1 text-components-button-secondary-accent-text system-xs-semibold'>ADD</button> <button
className={classNames('px-1.5 py-1 text-components-button-secondary-accent-text system-xs-semibold', isParagraphMode ? 'hidden group-hover/card:inline-block' : '')}
onClick={(event) => {
event.stopPropagation()
}}
>
ADD
</button>
</div> </div>
{parentMode === 'full-doc' {isFullDocMode
? <Input ? <Input
showLeftIcon showLeftIcon
showClearIcon showClearIcon
@ -58,13 +78,13 @@ const ChildSegmentList: FC<IChildSegmentCardProps> = ({
/> />
: null} : null}
</div> </div>
{(parentMode === 'full-doc' || !collapsed) {(isFullDocMode || !collapsed)
? <div className='flex gap-x-0.5 p-1'> ? <div className={classNames('flex gap-x-0.5', isFullDocMode ? 'grow overflow-y-auto' : '')}>
<Divider type='vertical' className='h-auto w-[2px] mx-[7px] bg-text-accent-secondary' /> {isParagraphMode && <Divider type='vertical' className='h-auto w-[2px] mx-[7px] bg-text-accent-secondary' />}
<FormattedText className='w-full !leading-5'> <FormattedText className={classNames('w-full !leading-5 flex flex-col', isParagraphMode ? 'gap-y-2' : 'gap-y-3')}>
{child_chunks.map((childChunk, index) => { {childChunks.map((childChunk) => {
return <EditSlice return <EditSlice
key={index} key={childChunk.segment_id}
label={`C-${childChunk.position}`} label={`C-${childChunk.position}`}
text={childChunk.content} text={childChunk.content}
onDelete={() => {}} onDelete={() => {}}
@ -73,7 +93,6 @@ const ChildSegmentList: FC<IChildSegmentCardProps> = ({
})} })}
</FormattedText> </FormattedText>
</div> </div>
: null} : null}
</div> </div>
) )

View File

@ -11,7 +11,9 @@ import SegmentList from './segment-list'
import DisplayToggle from './display-toggle' import DisplayToggle from './display-toggle'
import BatchAction from './batch-action' import BatchAction from './batch-action'
import SegmentDetail from './segment-detail' import SegmentDetail from './segment-detail'
import { mockSegments } from './mock-data' import { mockChildSegments, mockSegments } from './mock-data'
import SegmentCard from './segment-card'
import ChildSegmentList from './child-segment-list'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { formatNumber } from '@/utils/format' import { formatNumber } from '@/utils/format'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
@ -260,12 +262,16 @@ const Completed: FC<ICompletedProps> = ({
return segmentList?.total ? formatNumber(segmentList.total) : '--' return segmentList?.total ? formatNumber(segmentList.total) : '--'
}, [segmentList?.total]) }, [segmentList?.total])
const isFullDocMode = useMemo(() => {
return mode === 'hierarchical' && parentMode === 'full-doc'
}, [mode, parentMode])
return ( return (
<SegmentListContext.Provider value={{ <SegmentListContext.Provider value={{
isCollapsed, isCollapsed,
toggleCollapsed: () => setIsCollapsed(!isCollapsed), toggleCollapsed: () => setIsCollapsed(!isCollapsed),
}}> }}>
<div className={s.docSearchWrapper}> {!isFullDocMode && <div className={s.docSearchWrapper}>
<Checkbox <Checkbox
className='shrink-0' className='shrink-0'
checked={isAllSelected} checked={isAllSelected}
@ -293,19 +299,34 @@ const Completed: FC<ICompletedProps> = ({
/> />
<Divider type='vertical' className='h-3.5 mx-3' /> <Divider type='vertical' className='h-3.5 mx-3' />
<DisplayToggle /> <DisplayToggle />
</div> </div>}
<SegmentList {
embeddingAvailable={embeddingAvailable} isFullDocMode
isLoading={isLoadingSegmentList} ? <div className='h-full flex flex-col'>
items={segments} <SegmentCard
selectedSegmentIds={selectedSegmentIds} detail={segments[0]}
onSelected={onSelected} onClick={() => onClickCard(segments[0])}
onChangeSwitch={onChangeSwitch} loading={false}
onDelete={onDelete} />
onClick={onClickCard} <ChildSegmentList
archived={archived} childChunks={mockChildSegments.data}
/> handleInputChange={() => {}}
<Modal isShow={currSegment.showModal} onClose={() => { }} className='!max-w-[640px] !overflow-visible'> enabled={!archived}
/>
</div>
: <SegmentList
embeddingAvailable={embeddingAvailable}
isLoading={isLoadingSegmentList}
items={segments}
selectedSegmentIds={selectedSegmentIds}
onSelected={onSelected}
onChangeSwitch={onChangeSwitch}
onDelete={onDelete}
onClick={onClickCard}
archived={archived}
/>
}
<Modal isShow={currSegment.showModal} onClose={() => {}} className='!max-w-[640px] !overflow-visible'>
<SegmentDetail <SegmentDetail
embeddingAvailable={embeddingAvailable} embeddingAvailable={embeddingAvailable}
segInfo={currSegment.segInfo ?? { id: '' }} segInfo={currSegment.segInfo ?? { id: '' }}

View File

@ -25,7 +25,7 @@ export const mockSegments = {
index_node_id: 'b67972c2-4a95-4e46-bf8e-f32535bfc483', index_node_id: 'b67972c2-4a95-4e46-bf8e-f32535bfc483',
index_node_hash: '40ead185f2ec6a451da09e99f4f5a7438df4542590090660b7f2f40099220cf0', index_node_hash: '40ead185f2ec6a451da09e99f4f5a7438df4542590090660b7f2f40099220cf0',
hit_count: 0, hit_count: 0,
enabled: false, enabled: true,
disabled_at: 1732081062, disabled_at: 1732081062,
disabled_by: '', disabled_by: '',
status: 'completed', status: 'completed',
@ -36,24 +36,24 @@ export const mockSegments = {
error: null, error: null,
stopped_at: 1732081062, stopped_at: 1732081062,
child_chunks: [ child_chunks: [
{ // {
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', // id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1, // position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', // segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', // content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45, // word_count: 45,
created_at: 1732081062, // created_at: 1732081062,
type: 'automatic' as ChildChunkType, // type: 'automatic' as ChildChunkType,
}, // },
{ // {
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', // id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2, // position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', // segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。', // content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79, // word_count: 79,
created_at: 1732081062, // created_at: 1732081062,
type: 'automatic' as ChildChunkType, // type: 'automatic' as ChildChunkType,
}, // },
], ],
}, },
{ {
@ -232,3 +232,138 @@ export const mockSegments = {
limit: 10, limit: 10,
total: 6, total: 6,
} }
export const mockChildSegments = {
data: [
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d',
position: 1,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n',
word_count: 45,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
{
id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c',
position: 2,
segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。',
word_count: 79,
created_at: 1732081062,
type: 'automatic' as ChildChunkType,
},
],
total: 2,
total_pages: 10,
page: 1,
limit: 10,
}

View File

@ -1,4 +1,4 @@
import React, { type FC, useMemo, useState } from 'react' import React, { type FC, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiArrowRightUpLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react' import { RiArrowRightUpLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
import { StatusItem } from '../../list' import { StatusItem } from '../../list'
@ -118,21 +118,34 @@ const SegmentCard: FC<ISegmentCardProps> = ({
} = detail as Required<ISegmentCardProps>['detail'] } = detail as Required<ISegmentCardProps>['detail']
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const isCollapsed = useSegmentListContext(s => s.isCollapsed) const isCollapsed = useSegmentListContext(s => s.isCollapsed)
const mode = useDocumentContext(s => s.mode) const [mode, parentMode] = useDocumentContext(s => [s.mode, s.parentMode])
const isDocScene = useMemo(() => { const isDocScene = useMemo(() => {
return scene === 'doc' return scene === 'doc'
}, [scene]) }, [scene])
const isGeneralMode = useMemo(() => {
return mode === 'custom'
}, [mode])
const isFullDocMode = useMemo(() => {
return mode === 'hierarchical' && parentMode === 'full-doc'
}, [mode, parentMode])
// todo: change to real logic // todo: change to real logic
const chunkEdited = useMemo(() => { const chunkEdited = useMemo(() => {
return true return mode !== 'hierarchical' || parentMode !== 'full-doc'
}, []) }, [mode, parentMode])
const textOpacity = useMemo(() => { const textOpacity = useMemo(() => {
return enabled ? '' : 'opacity-50' return enabled ? '' : 'opacity-50 group-hover/card:opacity-100'
}, [enabled]) }, [enabled])
const handleClickCard = useCallback(() => {
if (!isFullDocMode)
onClick?.()
}, [isFullDocMode, onClick])
const renderContent = () => { const renderContent = () => {
if (answer) { if (answer) {
return ( return (
@ -156,8 +169,11 @@ const SegmentCard: FC<ISegmentCardProps> = ({
} }
return ( return (
<div className={cn('p-1 pt-2.5 hover:bg-dataset-chunk-detail-card-hover-bg rounded-xl group/card', className, !enabled ? 'opacity-50 hover:opacity-100' : '')} onClick={() => onClick?.()}> <div
<div className='h-5 px-2 relative flex items-center justify-between'> className={cn('px-3 rounded-xl group/card', isFullDocMode ? '' : 'pt-2.5 pb-2 hover:bg-dataset-chunk-detail-card-hover-bg', className)}
onClick={handleClickCard}
>
<div className='h-5 relative flex items-center justify-between'>
{isDocScene {isDocScene
? <> ? <>
<div className='flex items-center gap-x-2'> <div className='flex items-center gap-x-2'>
@ -166,63 +182,67 @@ const SegmentCard: FC<ISegmentCardProps> = ({
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(word_count)} Characters`}</div> <div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(word_count)} Characters`}</div>
<Dot /> <Dot />
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(hit_count)} Retrieval Count`}</div> <div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(hit_count)} Retrieval Count`}</div>
<Dot />
{chunkEdited && ( {chunkEdited && (
<Badge text='edited' uppercase className={textOpacity} /> <>
<Dot />
<Badge text='edited' uppercase className={textOpacity} />
</>
)} )}
</div> </div>
<div className='relative z-10'> {!isFullDocMode
{loading ? <div className='flex items-center'>
? ( {loading
<Indicator color="gray" /> ? (
) <Indicator color="gray" />
: ( )
<> : (
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" /> <>
{embeddingAvailable && ( <StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
<div className="absolute -top-2 -right-2.5 z-20 hidden group-hover/card:flex items-center gap-x-0.5 p-1 {embeddingAvailable && (
<div className="absolute -top-2 -right-2.5 z-20 hidden group-hover/card:flex items-center gap-x-0.5 p-1
rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-md backdrop-blur-[5px]"> rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-md backdrop-blur-[5px]">
{!archived && ( {!archived && (
<> <>
<div <div
className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer' className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer'
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onClickEdit?.() onClickEdit?.()
}}> }}>
<RiEditLine className='w-4 h-4 text-text-tertiary' /> <RiEditLine className='w-4 h-4 text-text-tertiary' />
</div> </div>
<div className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-destructive-hover cursor-pointer group/delete' <div className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-destructive-hover cursor-pointer group/delete'
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
setShowModal(true) setShowModal(true)
} }
}> }>
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' /> <RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' />
</div> </div>
<Divider type="vertical" className="h-3.5 bg-divider-regular" /> <Divider type="vertical" className="h-3.5 bg-divider-regular" />
</> </>
)} )}
<div <div
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
e.stopPropagation() e.stopPropagation()
} }
className="flex items-center" className="flex items-center"
> >
<Switch <Switch
size='md' size='md'
disabled={archived || detail.status !== 'completed'} disabled={archived || detail.status !== 'completed'}
defaultValue={enabled} defaultValue={enabled}
onChange={async (val) => { onChange={async (val) => {
await onChangeSwitch?.(val, id) await onChangeSwitch?.(val, id)
}} }}
/> />
</div>
</div> </div>
</div> )}
)} </>
</> )}
)} </div>
</div> : null}
</> </>
: ( : (
score !== null score !== null
@ -244,18 +264,26 @@ const SegmentCard: FC<ISegmentCardProps> = ({
: ( : (
isDocScene isDocScene
? <> ? <>
<div className={cn('text-text-secondary body-md-regular -tracking-[0.07px] mt-1 px-2', textOpacity, isCollapsed ? 'line-clamp-2' : 'line-clamp-20')}> <div className={cn('text-text-secondary body-md-regular -tracking-[0.07px] mt-0.5',
textOpacity,
isCollapsed ? 'line-clamp-2' : 'line-clamp-20',
)}>
{renderContent()} {renderContent()}
</div> </div>
{mode === 'custom' && <div className={cn('flex items-center gap-x-2 px-2 py-1.5', textOpacity)}> {isGeneralMode && <div className={cn('flex items-center gap-x-2 py-1.5', textOpacity)}>
{keywords?.map(keyword => <Tag key={keyword} text={keyword} />)} {keywords?.map(keyword => <Tag key={keyword} text={keyword} />)}
</div>} </div>}
{
isFullDocMode
? <button className='mt-0.5 mb-2 text-text-accent system-xs-semibold-uppercase' onClick={() => onClick?.()}>VIEW MORE</button>
: null
}
{ {
child_chunks.length > 0 child_chunks.length > 0
&& <ChildSegmentList && <ChildSegmentList
child_chunks={child_chunks} childChunks={child_chunks}
onSave={() => {}}
handleInputChange={() => {}} handleInputChange={() => {}}
enabled={enabled}
/> />
} }
</> </>

View File

@ -5,6 +5,7 @@ import type { SegmentDetailModel } from '@/models/datasets'
import Checkbox from '@/app/components/base/checkbox' import Checkbox from '@/app/components/base/checkbox'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import classNames from '@/utils/classnames'
type ISegmentListProps = { type ISegmentListProps = {
isLoading: boolean isLoading: boolean
@ -32,7 +33,7 @@ const SegmentList: FC<ISegmentListProps> = ({
if (isLoading) if (isLoading)
return <Loading type='app' /> return <Loading type='app' />
return ( return (
<div className='flex flex-col h-full overflow-y-auto'> <div className={classNames('flex flex-col h-full overflow-y-auto')}>
{ {
items.map((segItem) => { items.map((segItem) => {
const isLast = items[items.length - 1].id === segItem.id const isLast = items[items.length - 1].id === segItem.id
@ -44,7 +45,7 @@ const SegmentList: FC<ISegmentListProps> = ({
checked={selectedSegmentIds.includes(segItem.id)} checked={selectedSegmentIds.includes(segItem.id)}
onCheck={() => onSelected(segItem.id)} onCheck={() => onSelected(segItem.id)}
/> />
<div className='flex flex-col'> <div>
<SegmentCard <SegmentCard
key={`${segItem.id}-card`} key={`${segItem.id}-card`}
detail={segItem} detail={segItem}

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useState } from 'react' import React, { useMemo, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { createContext, useContext, useContextSelector } from 'use-context-selector' import { createContext, useContext, useContextSelector } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -152,13 +152,25 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
detailMutate() detailMutate()
} }
const mode = useMemo(() => {
return documentDetail?.dataset_process_rule?.mode
}, [documentDetail])
const parentMode = useMemo(() => {
return documentDetail?.dataset_process_rule.rules.parent_mode
}, [documentDetail])
const isFullDocMode = useMemo(() => {
return mode === 'hierarchical' && parentMode === 'full-doc'
}, [mode, parentMode])
return ( return (
<DocumentContext.Provider value={{ <DocumentContext.Provider value={{
datasetId, datasetId,
documentId, documentId,
docForm: documentDetail?.doc_form || '', docForm: documentDetail?.doc_form || '',
mode: documentDetail?.dataset_process_rule.mode, mode,
parentMode: documentDetail?.dataset_process_rule.rules.parent_mode, parentMode,
}}> }}>
<div className='flex flex-col h-full'> <div className='flex flex-col h-full'>
<div className='flex items-center justify-between flex-wrap min-h-16 pl-3 pr-4 py-2.5 border-b border-b-divider-subtle'> <div className='flex items-center justify-between flex-wrap min-h-16 pl-3 pr-4 py-2.5 border-b border-b-divider-subtle'>
@ -222,7 +234,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
<div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}> <div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
{isDetailLoading {isDetailLoading
? <Loading type='app' /> ? <Loading type='app' />
: <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : 'relative pb-[30px] pt-3 pl-5 pr-11'}`}> : <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : `relative pb-[30px] pt-3 ${isFullDocMode ? 'pl-11' : 'pl-5'} pr-11`}`}>
{embedding {embedding
? <Embedding detail={documentDetail} detailUpdate={detailMutate} /> ? <Embedding detail={documentDetail} detailUpdate={detailMutate} />
: <Completed : <Completed