mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 03:36:36 +08:00
feat: enhance DocumentDetail and Completed components with child segment handling and improved layout
This commit is contained in:
parent
78fff31e61
commit
b59e95785c
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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: '' }}
|
||||||
|
|||||||
@ -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,
|
||||||
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user