mirror of
https://github.com/langgenius/dify.git
synced 2026-01-05 22:15:52 +00:00
Merge branch 'feat/parent-child-retrieval' of https://github.com/langgenius/dify into feat/parent-child-retrieval
This commit is contained in:
5
web/app/components/base/checkbox/assets/mixed.svg
Normal file
5
web/app/components/base/checkbox/assets/mixed.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="check">
|
||||
<path id="Vector 1" d="M2.5 6H9.5" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 217 B |
@@ -1,11 +1,13 @@
|
||||
.wrapper {
|
||||
border-color: #d0d5dd;
|
||||
.checked {
|
||||
background: var(--color-components-checkbox-bg) url(./assets/check.svg) center center no-repeat;
|
||||
background-size: 12px 12px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checked {
|
||||
background: #155eef url(./assets/check.svg) center center no-repeat;
|
||||
.mixed {
|
||||
background: var(--color-components-checkbox-bg) url(./assets/mixed.svg) center center no-repeat;
|
||||
background-size: 12px 12px;
|
||||
border-color: #155eef;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checked.disabled {
|
||||
|
||||
@@ -6,16 +6,17 @@ type CheckboxProps = {
|
||||
onCheck?: () => void
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
mixed?: boolean
|
||||
}
|
||||
|
||||
const Checkbox = ({ checked, onCheck, className, disabled }: CheckboxProps) => {
|
||||
const Checkbox = ({ checked, onCheck, className, disabled, mixed }: CheckboxProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
s.wrapper,
|
||||
'w-4 h-4 border rounded border-components-checkbox-border bg-components-checkbox-bg-unchecked shadow-xs shadow-shadow-shadow-3',
|
||||
checked && s.checked,
|
||||
disabled && s.disabled,
|
||||
'w-4 h-4 border rounded border-gray-300',
|
||||
mixed && s.mixed,
|
||||
className,
|
||||
)}
|
||||
onClick={() => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<path id="Vector" d="M2.5 10H0V7.5H2.5V10Z" fill="#676F83"/>
|
||||
<path id="Vector_2" d="M6.25 6.25H3.75V3.75H6.25V6.25Z" fill="#676F83"/>
|
||||
<path id="Vector_3" d="M2.5 6.25H0V3.75H2.5V6.25Z" fill="#676F83"/>
|
||||
<path id="Vector_4" d="M6.25 2.5H3.75V0H6.25V2.5Z" fill="#676F83"/>
|
||||
<path id="Vector_5" d="M2.5 2.5H0V0H2.5V2.5Z" fill="#676F83"/>
|
||||
<path id="Vector_6" d="M10 2.5H7.5V0H10V2.5Z" fill="#676F83"/>
|
||||
<path id="Vector_7" d="M9.58342 7.91663H7.91675V9.58329H9.58342V7.91663Z" fill="#676F83"/>
|
||||
<path id="Vector_8" d="M9.58342 4.16663H7.91675V5.83329H9.58342V4.16663Z" fill="#676F83"/>
|
||||
<path id="Vector_9" d="M5.83341 7.91663H4.16675V9.58329H5.83341V7.91663Z" fill="#676F83"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 792 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon L">
|
||||
<g id="Vector">
|
||||
<path d="M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z" fill="#354052"/>
|
||||
<path d="M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z" fill="#354052"/>
|
||||
<path d="M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z" fill="#354052"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 579 B |
116
web/app/components/base/icons/src/public/knowledge/Chunk.json
Normal file
116
web/app/components/base/icons/src/public/knowledge/Chunk.json
Normal file
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "10",
|
||||
"height": "10",
|
||||
"viewBox": "0 0 10 10",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Group"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"d": "M2.5 10H0V7.5H2.5V10Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_2",
|
||||
"d": "M6.25 6.25H3.75V3.75H6.25V6.25Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_3",
|
||||
"d": "M2.5 6.25H0V3.75H2.5V6.25Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_4",
|
||||
"d": "M6.25 2.5H3.75V0H6.25V2.5Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_5",
|
||||
"d": "M2.5 2.5H0V0H2.5V2.5Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_6",
|
||||
"d": "M10 2.5H7.5V0H10V2.5Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_7",
|
||||
"d": "M9.58342 7.91663H7.91675V9.58329H9.58342V7.91663Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_8",
|
||||
"d": "M9.58342 4.16663H7.91675V5.83329H9.58342V4.16663Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_9",
|
||||
"d": "M5.83341 7.91663H4.16675V9.58329H5.83341V7.91663Z",
|
||||
"fill": "#676F83"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Chunk"
|
||||
}
|
||||
16
web/app/components/base/icons/src/public/knowledge/Chunk.tsx
Normal file
16
web/app/components/base/icons/src/public/knowledge/Chunk.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Chunk.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Chunk'
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Icon L"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Vector"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z",
|
||||
"fill": "#354052"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z",
|
||||
"fill": "#354052"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z",
|
||||
"fill": "#354052"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Collapse"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Collapse.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Collapse'
|
||||
|
||||
export default Icon
|
||||
@@ -1,3 +1,5 @@
|
||||
export { default as Chunk } from './Chunk'
|
||||
export { default as Collapse } from './Collapse'
|
||||
export { default as GeneralType } from './GeneralType'
|
||||
export { default as ParentChildType } from './ParentChildType'
|
||||
export { default as SelectionMod } from './SelectionMod'
|
||||
|
||||
@@ -96,7 +96,7 @@ const Tooltip: FC<TooltipProps> = ({
|
||||
>
|
||||
{popupContent && (<div
|
||||
className={cn(
|
||||
'relative px-3 py-2 text-xs font-normal text-gray-700 bg-white rounded-md shadow-lg break-words',
|
||||
'relative px-3 py-2 text-xs font-normal text-text-secondary bg-components-tooltip-bg rounded-md shadow-lg break-words',
|
||||
popupClassName,
|
||||
)}
|
||||
onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { type FC } from 'react'
|
||||
import { RiCheckboxCircleLine, RiCloseCircleLine, RiDeleteBinLine } from '@remixicon/react'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
type IBatchActionProps = {
|
||||
selectedSegmentIds: string[]
|
||||
onBatchEnable: () => Promise<void>
|
||||
onBatchDisable: () => Promise<void>
|
||||
onBatchDelete: () => Promise<void>
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const BatchAction: FC<IBatchActionProps> = ({
|
||||
selectedSegmentIds,
|
||||
onBatchEnable,
|
||||
onBatchDisable,
|
||||
onBatchDelete,
|
||||
onCancel,
|
||||
}) => {
|
||||
return (
|
||||
<div className='w-full flex justify-center gap-x-2 absolute bottom-16 z-20'>
|
||||
<div className='flex items-center gap-x-1 p-1 rounded-[10px] bg-components-actionbar-bg-accent border border-components-actionbar-border-accent shadow-xl shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
||||
<div className='inline-flex items-center gap-x-2 pl-2 pr-3 py-1'>
|
||||
<span className='w-5 h-5 flex items-center justify-center px-1 py-0.5 bg-text-accent rounded-md text-text-primary-on-surface text-xs font-medium'>
|
||||
{selectedSegmentIds.length}
|
||||
</span>
|
||||
<span className='text-text-accent text-[13px] font-semibold leading-[16px]'>Selected</span>
|
||||
</div>
|
||||
<Divider type='vertical' className='mx-0.5 h-3.5 bg-divider-regular' />
|
||||
<div className='flex items-center gap-x-0.5 px-3 py-2'>
|
||||
<RiCheckboxCircleLine className='w-4 h-4 text-components-button-ghost-text' />
|
||||
<button className='px-0.5 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onBatchEnable}>
|
||||
Enable
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex items-center gap-x-0.5 px-3 py-2'>
|
||||
<RiCloseCircleLine className='w-4 h-4 text-components-button-ghost-text' />
|
||||
<button className='px-0.5 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onBatchDisable}>
|
||||
Disable
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex items-center gap-x-0.5 px-3 py-2'>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-components-button-destructive-ghost-text' />
|
||||
<button className='px-0.5 text-components-button-destructive-ghost-text text-[13px] font-medium leading-[16px]' onClick={onBatchDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
<Divider type='vertical' className='mx-0.5 h-3.5 bg-divider-regular' />
|
||||
<button className='px-3.5 py-2 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(BatchAction)
|
||||
@@ -0,0 +1,31 @@
|
||||
import React, { type FC } from 'react'
|
||||
import { RiLineHeight } from '@remixicon/react'
|
||||
import { useSegmentListContext } from '.'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Collapse } from '@/app/components/base/icons/src/public/knowledge'
|
||||
|
||||
const DisplayToggle: FC = () => {
|
||||
const [isCollapsed, toggleCollapsed] = useSegmentListContext(s => [s.isCollapsed, s.toggleCollapsed])
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={isCollapsed ? 'Expand chunks' : 'Collapse chunks'}
|
||||
popupClassName='text-text-secondary system-xs-medium border-[0.5px] border-components-panel-border'
|
||||
needsDelay
|
||||
>
|
||||
<button
|
||||
className='flex items-center justify-center p-2 rounded-lg bg-components-button-secondary-bg cursor-pointer
|
||||
border-[0.5px] border-components-button-secondary-border shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px]'
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
{
|
||||
isCollapsed
|
||||
? <RiLineHeight className='w-4 h-4 text-components-button-secondary-text' />
|
||||
: <Collapse className='w-4 h-4 text-components-button-secondary-text' />
|
||||
}
|
||||
</button>
|
||||
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DisplayToggle)
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { memo, useEffect, useMemo, useState } from 'react'
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { HashtagIcon } from '@heroicons/react/24/solid'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { isNil, omitBy } from 'lodash-es'
|
||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiEditLine,
|
||||
@@ -14,7 +12,9 @@ import { StatusItem } from '../../list'
|
||||
import { DocumentContext } from '../index'
|
||||
import { ProcessStatus } from '../segment-add'
|
||||
import s from './style.module.css'
|
||||
import InfiniteVirtualList from './InfiniteVirtualList'
|
||||
import SegmentList from './segment-list'
|
||||
import DisplayToggle from './display-toggle'
|
||||
import BatchAction from './batch-action'
|
||||
import cn from '@/utils/classnames'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
@@ -24,27 +24,44 @@ import Input from '@/app/components/base/input'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import { deleteSegment, disableSegment, enableSegment, fetchSegments, updateSegment } from '@/service/datasets'
|
||||
import type { SegmentDetailModel, SegmentUpdater, SegmentsQuery, SegmentsResponse } from '@/models/datasets'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import { updateSegment } from '@/service/datasets'
|
||||
import type { ParentMode, ProcessMode, SegmentDetailModel, SegmentUpdater } from '@/models/datasets'
|
||||
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
|
||||
import Button from '@/app/components/base/button'
|
||||
import NewSegmentModal from '@/app/components/datasets/documents/detail/new-segment-modal'
|
||||
import TagInput from '@/app/components/base/tag-input'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import { useDeleteSegment, useDisableSegment, useEnableSegment, useSegmentList } from '@/service/knowledge/use-segment'
|
||||
import { Chunk } from '@/app/components/base/icons/src/public/knowledge'
|
||||
|
||||
type SegmentListContextValue = {
|
||||
isCollapsed: boolean
|
||||
toggleCollapsed: () => void
|
||||
}
|
||||
|
||||
const SegmentListContext = createContext({
|
||||
isCollapsed: true,
|
||||
toggleCollapsed: () => {},
|
||||
})
|
||||
|
||||
export const useSegmentListContext = (selector: (value: SegmentListContextValue) => any) => {
|
||||
return useContextSelector(SegmentListContext, selector)
|
||||
}
|
||||
|
||||
export const SegmentIndexTag: FC<{ positionId: string | number; className?: string }> = ({ positionId, className }) => {
|
||||
const localPositionId = useMemo(() => {
|
||||
const positionIdStr = String(positionId)
|
||||
if (positionIdStr.length >= 3)
|
||||
return positionId
|
||||
return positionIdStr.padStart(3, '0')
|
||||
return `Chunk-${positionId}`
|
||||
return `Chunk-${positionIdStr.padStart(2, '0')}`
|
||||
}, [positionId])
|
||||
return (
|
||||
<div className={`text-gray-500 border border-gray-200 box-border flex items-center rounded-md italic text-[11px] pl-1 pr-1.5 font-medium ${className ?? ''}`}>
|
||||
<HashtagIcon className='w-3 h-3 text-gray-400 fill-current mr-1 stroke-current stroke-1' />
|
||||
{localPositionId}
|
||||
<div className={cn('flex items-center', className)}>
|
||||
<Chunk className='w-3 h-3 p-[1px] text-text-tertiary mr-0.5' />
|
||||
<div className='text-text-tertiary system-xs-medium'>
|
||||
{localPositionId}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -52,10 +69,11 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri
|
||||
type ISegmentDetailProps = {
|
||||
embeddingAvailable: boolean
|
||||
segInfo?: Partial<SegmentDetailModel> & { id: string }
|
||||
onChangeSwitch?: (segId: string, enabled: boolean) => Promise<void>
|
||||
onChangeSwitch?: (enabled: boolean, segId?: string) => Promise<void>
|
||||
onUpdate: (segmentId: string, q: string, a: string, k: string[]) => void
|
||||
onCancel: () => void
|
||||
archived?: boolean
|
||||
isEditing?: boolean
|
||||
}
|
||||
/**
|
||||
* Show all the contents of the segment
|
||||
@@ -67,9 +85,10 @@ const SegmentDetailComponent: FC<ISegmentDetailProps> = ({
|
||||
onChangeSwitch,
|
||||
onUpdate,
|
||||
onCancel,
|
||||
isEditing: initialIsEditing,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(initialIsEditing)
|
||||
const [question, setQuestion] = useState(segInfo?.content || '')
|
||||
const [answer, setAnswer] = useState(segInfo?.answer || '')
|
||||
const [keywords, setKeywords] = useState<string[]>(segInfo?.keywords || [])
|
||||
@@ -195,7 +214,7 @@ const SegmentDetailComponent: FC<ISegmentDetailProps> = ({
|
||||
size='md'
|
||||
defaultValue={segInfo?.enabled}
|
||||
onChange={async (val) => {
|
||||
await onChangeSwitch?.(segInfo?.id || '', val)
|
||||
await onChangeSwitch?.(val, segInfo?.id || '')
|
||||
}}
|
||||
disabled={archived}
|
||||
/>
|
||||
@@ -223,6 +242,8 @@ type ICompletedProps = {
|
||||
onNewSegmentModalChange: (state: boolean) => void
|
||||
importStatus: ProcessStatus | string | undefined
|
||||
archived?: boolean
|
||||
mode?: ProcessMode
|
||||
parentMode?: ParentMode
|
||||
// data: Array<{}> // all/part segments
|
||||
}
|
||||
/**
|
||||
@@ -235,22 +256,26 @@ const Completed: FC<ICompletedProps> = ({
|
||||
onNewSegmentModalChange,
|
||||
importStatus,
|
||||
archived,
|
||||
mode,
|
||||
parentMode,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { datasetId = '', documentId = '', docForm } = useContext(DocumentContext)
|
||||
// the current segment id and whether to show the modal
|
||||
const [currSegment, setCurrSegment] = useState<{ segInfo?: SegmentDetailModel; showModal: boolean }>({ showModal: false })
|
||||
const [currSegment, setCurrSegment] = useState<{ segInfo?: SegmentDetailModel; showModal: boolean; isEditing?: boolean }>({ showModal: false })
|
||||
|
||||
const [inputValue, setInputValue] = useState<string>('') // the input value
|
||||
const [searchValue, setSearchValue] = useState<string>('') // the search value
|
||||
const [selectedStatus, setSelectedStatus] = useState<boolean | 'all'>('all') // the selected status, enabled/disabled/undefined
|
||||
|
||||
const [lastSegmentsRes, setLastSegmentsRes] = useState<SegmentsResponse | undefined>(undefined)
|
||||
const [allSegments, setAllSegments] = useState<Array<SegmentDetailModel[]>>([]) // all segments data
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [total, setTotal] = useState<number | undefined>()
|
||||
const [segments, setSegments] = useState<SegmentDetailModel[]>([]) // all segments data
|
||||
const [selectedSegmentIds, setSelectedSegmentIds] = useState<string[]>([])
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [isCollapsed, setIsCollapsed] = useState(true)
|
||||
// todo: pagination
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [limit, setLimit] = useState(10)
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchValue(inputValue)
|
||||
@@ -265,72 +290,86 @@ const Completed: FC<ICompletedProps> = ({
|
||||
setSelectedStatus(value === 'all' ? 'all' : !!value)
|
||||
}
|
||||
|
||||
const getSegments = async (needLastId?: boolean) => {
|
||||
const finalLastId = lastSegmentsRes?.data?.[lastSegmentsRes.data.length - 1]?.id || ''
|
||||
setLoading(true)
|
||||
const [e, res] = await asyncRunSafe<SegmentsResponse>(fetchSegments({
|
||||
const { isLoading: isLoadingSegmentList, data: segmentList, refetch: refreshSegmentList } = useSegmentList(
|
||||
{
|
||||
datasetId,
|
||||
documentId,
|
||||
params: omitBy({
|
||||
last_id: !needLastId ? undefined : finalLastId,
|
||||
limit: 12,
|
||||
params: {
|
||||
page: currentPage,
|
||||
limit,
|
||||
keyword: searchValue,
|
||||
enabled: selectedStatus === 'all' ? 'all' : !!selectedStatus,
|
||||
}, isNil) as SegmentsQuery,
|
||||
}) as Promise<SegmentsResponse>)
|
||||
if (!e) {
|
||||
setAllSegments([...(!needLastId ? [] : allSegments), ...splitArray(res.data || [])])
|
||||
setLastSegmentsRes(res)
|
||||
if (!lastSegmentsRes || !needLastId)
|
||||
setTotal(res?.total || 0)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
},
|
||||
mode === 'hierarchical' && parentMode === 'full-doc',
|
||||
)
|
||||
|
||||
const resetList = () => {
|
||||
setLastSegmentsRes(undefined)
|
||||
setAllSegments([])
|
||||
setLoading(false)
|
||||
setTotal(undefined)
|
||||
getSegments(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (segmentList)
|
||||
setSegments(segmentList.data || [])
|
||||
}, [segmentList])
|
||||
|
||||
const onClickCard = (detail: SegmentDetailModel) => {
|
||||
setCurrSegment({ segInfo: detail, showModal: true })
|
||||
const resetList = useCallback(() => {
|
||||
setSegments([])
|
||||
refreshSegmentList()
|
||||
}, [])
|
||||
|
||||
const onClickCard = (detail: SegmentDetailModel, isEditing = false) => {
|
||||
setCurrSegment({ segInfo: detail, showModal: true, isEditing })
|
||||
}
|
||||
|
||||
const onCloseModal = () => {
|
||||
setCurrSegment({ ...currSegment, showModal: false })
|
||||
}
|
||||
|
||||
const onChangeSwitch = async (segId: string, enabled: boolean) => {
|
||||
const opApi = enabled ? enableSegment : disableSegment
|
||||
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId, segmentId: segId }) as Promise<CommonResponse>)
|
||||
if (!e) {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
for (const item of allSegments) {
|
||||
for (const seg of item) {
|
||||
if (seg.id === segId)
|
||||
seg.enabled = enabled
|
||||
}
|
||||
}
|
||||
setAllSegments([...allSegments])
|
||||
}
|
||||
else {
|
||||
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
}
|
||||
}
|
||||
const { mutateAsync: enableSegment } = useEnableSegment()
|
||||
|
||||
const onDelete = async (segId: string) => {
|
||||
const [e] = await asyncRunSafe<CommonResponse>(deleteSegment({ datasetId, documentId, segmentId: segId }) as Promise<CommonResponse>)
|
||||
if (!e) {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
resetList()
|
||||
}
|
||||
else {
|
||||
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
}
|
||||
}
|
||||
const { mutateAsync: disableSegment } = useDisableSegment()
|
||||
|
||||
const onChangeSwitch = useCallback(async (enable: boolean, segId?: string) => {
|
||||
const operationApi = enable ? enableSegment : disableSegment
|
||||
await operationApi({ datasetId, documentId, segmentIds: segId ? [segId] : selectedSegmentIds }, {
|
||||
onSuccess: () => {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
for (const seg of segments) {
|
||||
if (segId ? seg.id === segId : selectedSegmentIds.includes(seg.id))
|
||||
seg.enabled = enable
|
||||
}
|
||||
setSegments([...segments])
|
||||
},
|
||||
onError: () => {
|
||||
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
},
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [datasetId, documentId, selectedSegmentIds, segments])
|
||||
|
||||
const { mutateAsync: deleteSegment } = useDeleteSegment()
|
||||
|
||||
const onDelete = useCallback(async (segId?: string) => {
|
||||
await deleteSegment({ datasetId, documentId, segmentIds: segId ? [segId] : selectedSegmentIds }, {
|
||||
onSuccess: () => {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
resetList()
|
||||
},
|
||||
onError: () => {
|
||||
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
},
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [datasetId, documentId, selectedSegmentIds])
|
||||
|
||||
const onCancelBatchOperation = useCallback(() => {
|
||||
setSelectedSegmentIds([])
|
||||
}, [])
|
||||
|
||||
const onSelected = useCallback((segId: string) => {
|
||||
setSelectedSegmentIds(prev =>
|
||||
prev.includes(segId)
|
||||
? prev.filter(id => id !== segId)
|
||||
: [...prev, segId],
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handleUpdateSegment = async (segmentId: string, question: string, answer: string, keywords: string[]) => {
|
||||
const params: SegmentUpdater = { content: '' }
|
||||
@@ -358,40 +397,62 @@ const Completed: FC<ICompletedProps> = ({
|
||||
const res = await updateSegment({ datasetId, documentId, segmentId, body: params })
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
onCloseModal()
|
||||
for (const item of allSegments) {
|
||||
for (const seg of item) {
|
||||
if (seg.id === segmentId) {
|
||||
seg.answer = res.data.answer
|
||||
seg.content = res.data.content
|
||||
seg.keywords = res.data.keywords
|
||||
seg.word_count = res.data.word_count
|
||||
seg.hit_count = res.data.hit_count
|
||||
seg.index_node_hash = res.data.index_node_hash
|
||||
seg.enabled = res.data.enabled
|
||||
}
|
||||
for (const seg of segments) {
|
||||
if (seg.id === segmentId) {
|
||||
seg.answer = res.data.answer
|
||||
seg.content = res.data.content
|
||||
seg.keywords = res.data.keywords
|
||||
seg.word_count = res.data.word_count
|
||||
seg.hit_count = res.data.hit_count
|
||||
seg.index_node_hash = res.data.index_node_hash
|
||||
seg.enabled = res.data.enabled
|
||||
}
|
||||
}
|
||||
setAllSegments([...allSegments])
|
||||
setSegments([...segments])
|
||||
}
|
||||
finally {
|
||||
eventEmitter?.emit('')
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (lastSegmentsRes !== undefined)
|
||||
getSegments(false)
|
||||
}, [selectedStatus, searchValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (importStatus === ProcessStatus.COMPLETED)
|
||||
resetList()
|
||||
}, [importStatus])
|
||||
}, [importStatus, resetList])
|
||||
|
||||
const isAllSelected = useMemo(() => {
|
||||
return segments.every(seg => selectedSegmentIds.includes(seg.id))
|
||||
}, [segments, selectedSegmentIds])
|
||||
|
||||
const isSomeSelected = useMemo(() => {
|
||||
return segments.some(seg => selectedSegmentIds.includes(seg.id))
|
||||
}, [segments, selectedSegmentIds])
|
||||
|
||||
const onSelectedAll = useCallback(() => {
|
||||
setSelectedSegmentIds((prev) => {
|
||||
const currentAllSegIds = segments.map(seg => seg.id)
|
||||
const prevSelectedIds = prev.filter(item => !currentAllSegIds.includes(item))
|
||||
return [...prevSelectedIds, ...((isAllSelected || selectedSegmentIds.length > 0) ? [] : currentAllSegIds)]
|
||||
})
|
||||
}, [segments, isAllSelected, selectedSegmentIds])
|
||||
|
||||
const totalText = useMemo(() => {
|
||||
return segmentList?.total ? formatNumber(segmentList.total) : '--'
|
||||
}, [segmentList?.total])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SegmentListContext.Provider value={{
|
||||
isCollapsed,
|
||||
toggleCollapsed: () => setIsCollapsed(!isCollapsed),
|
||||
}}>
|
||||
<div className={s.docSearchWrapper}>
|
||||
<div className={s.totalText}>{total ? formatNumber(total) : '--'} {t('datasetDocuments.segment.paragraphs')}</div>
|
||||
<Checkbox
|
||||
className='shrink-0'
|
||||
checked={isAllSelected}
|
||||
mixed={!isAllSelected && isSomeSelected}
|
||||
onCheck={onSelectedAll}
|
||||
/>
|
||||
<div className={cn('system-sm-semibold-uppercase pl-5', s.totalText)}>{totalText} {t('datasetDocuments.segment.chunks')}</div>
|
||||
<SimpleSelect
|
||||
onSelect={onChangeStatus}
|
||||
items={[
|
||||
@@ -401,7 +462,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
]}
|
||||
defaultValue={'all'}
|
||||
className={s.select}
|
||||
wrapperClassName='h-fit w-[120px] mr-2' />
|
||||
wrapperClassName='h-fit w-[100px] mr-2' />
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
@@ -410,13 +471,15 @@ const Completed: FC<ICompletedProps> = ({
|
||||
onChange={e => handleInputChange(e.target.value)}
|
||||
onClear={() => handleInputChange('')}
|
||||
/>
|
||||
<Divider type='vertical' className='h-3.5 mx-3' />
|
||||
<DisplayToggle />
|
||||
</div>
|
||||
<InfiniteVirtualList
|
||||
<SegmentList
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
hasNextPage={lastSegmentsRes?.has_more ?? true}
|
||||
isNextPageLoading={loading}
|
||||
items={allSegments}
|
||||
loadNextPage={getSegments}
|
||||
isLoading={isLoadingSegmentList}
|
||||
items={segments}
|
||||
selectedSegmentIds={selectedSegmentIds}
|
||||
onSelected={onSelected}
|
||||
onChangeSwitch={onChangeSwitch}
|
||||
onDelete={onDelete}
|
||||
onClick={onClickCard}
|
||||
@@ -426,6 +489,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
<SegmentDetail
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
segInfo={currSegment.segInfo ?? { id: '' }}
|
||||
isEditing={currSegment.isEditing}
|
||||
onChangeSwitch={onChangeSwitch}
|
||||
onUpdate={handleUpdateSegment}
|
||||
onCancel={onCloseModal}
|
||||
@@ -438,7 +502,15 @@ const Completed: FC<ICompletedProps> = ({
|
||||
onCancel={() => onNewSegmentModalChange(false)}
|
||||
onSave={resetList}
|
||||
/>
|
||||
</>
|
||||
{selectedSegmentIds.length > 0
|
||||
&& <BatchAction
|
||||
selectedSegmentIds={selectedSegmentIds}
|
||||
onBatchEnable={onChangeSwitch.bind(null, true)}
|
||||
onBatchDisable={onChangeSwitch.bind(null, false)}
|
||||
onBatchDelete={onDelete}
|
||||
onCancel={onCancelBatchOperation}
|
||||
/>}
|
||||
</SegmentListContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
export const mockSegments = {
|
||||
data: [
|
||||
{
|
||||
id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
|
||||
position: 1,
|
||||
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
|
||||
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\nDify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。以下是其核心功能列表:\n\n1. 工作流: 在画布上构建和测试功能强大的 AI 工作流程,利用以下所有功能以及更多功能。\n\nhttps://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa\n\n2. 全面的模型支持: 与数百种专有/开源 LLMs 以及数十种推理提供商和自托管解决方案无缝集成,涵盖 GPT、Mistral、Llama3 以及任何与 OpenAI API 兼容的模型。完整的支持模型提供商列表可在此处找到。\n\n3. Prompt IDE: 用于制作提示、比较模型性能以及向基于聊天的应用程序添加其他功能(如文本转语音)的直观界面。\n\n4. RAG Pipeline: 广泛的 RAG 功能,涵盖从文档摄入到检索的所有内容,支持从 PDF、PPT 和其他常见文档格式中提取文本的开箱即用的支持。\n\n5. Agent 智能体: 您可以基于 LLM 函数调用或 ReAct 定义 Agent,并为 Agent 添加预构建或自定义工具。Dify 为 AI Agent 提供了50多种内置工具,如谷歌搜索、DALL·E、Stable Diffusion 和 WolframAlpha 等。',
|
||||
answer: '',
|
||||
word_count: 672,
|
||||
tokens: 481,
|
||||
keywords: [
|
||||
'功能',
|
||||
'AI',
|
||||
'LLM',
|
||||
'模型',
|
||||
'文档',
|
||||
'Agent',
|
||||
'开源',
|
||||
'Dify',
|
||||
'支持',
|
||||
'RAG',
|
||||
],
|
||||
index_node_id: 'b67972c2-4a95-4e46-bf8e-f32535bfc483',
|
||||
index_node_hash: '40ead185f2ec6a451da09e99f4f5a7438df4542590090660b7f2f40099220cf0',
|
||||
hit_count: 0,
|
||||
enabled: true,
|
||||
disabled_at: 1732081062,
|
||||
disabled_by: '',
|
||||
status: 'completed',
|
||||
created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5',
|
||||
created_at: 1732081062,
|
||||
indexing_at: 1732081061,
|
||||
completed_at: 1732081064,
|
||||
error: null,
|
||||
stopped_at: 1732081062,
|
||||
},
|
||||
{
|
||||
id: '4c701023-90a6-4df9-bc26-49cfb701badc',
|
||||
position: 2,
|
||||
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
|
||||
content: '6. LLMOps: 随时间监视和分析应用程序日志和性能。您可以根据生产数据和标注持续改进提示、数据集和模型。\n\n7. 后端即服务: 所有 Dify 的功能都带有相应的 API,因此您可以轻松地将 Dify 集成到自己的业务逻辑中。\n\n功能比较',
|
||||
answer: '',
|
||||
word_count: 122,
|
||||
tokens: 104,
|
||||
keywords: [
|
||||
'标注',
|
||||
'API',
|
||||
'Dify',
|
||||
'集成',
|
||||
'LLMOps',
|
||||
'后端',
|
||||
'应用程序',
|
||||
'数据',
|
||||
'日志',
|
||||
'功能',
|
||||
],
|
||||
index_node_id: 'fd5a3ea6-c726-41cb-bf0f-00da11f7cab9',
|
||||
index_node_hash: '4e3f5f693e9e43734c12613bbb9971eae154be7765fd0b91fb7263b1755319b8',
|
||||
hit_count: 0,
|
||||
enabled: false,
|
||||
disabled_at: 1732081062,
|
||||
disabled_by: '',
|
||||
status: 'completed',
|
||||
created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5',
|
||||
created_at: 1732081062,
|
||||
indexing_at: 1732081061,
|
||||
completed_at: 1732081064,
|
||||
error: null,
|
||||
stopped_at: 1732081062,
|
||||
},
|
||||
{
|
||||
id: '070f9780-1819-43fc-b976-780db8e19ed9',
|
||||
position: 3,
|
||||
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
|
||||
content: '功能 Dify.AI LangChain Flowise OpenAI Assistant API 编程方法 API + 应用程序导向 Python 代码 应用程序导向 API 导向 支持的 LLMs 丰富多样 丰富多样 丰富多样 仅限 OpenAI RAG引擎 ✅ ✅ ✅ ✅ Agent ✅ ✅ ❌ ✅ 工作流 ✅ ❌ ✅ ❌ 可观测性 ✅ ✅ ❌ ❌ 企业功能(SSO/访问控制) ✅ ❌ ❌ ❌ 本地部署 ✅ ✅ ✅ ❌',
|
||||
answer: '',
|
||||
word_count: 214,
|
||||
tokens: 158,
|
||||
keywords: [
|
||||
'导向',
|
||||
'API',
|
||||
'Dify',
|
||||
'OpenAI',
|
||||
'AI',
|
||||
'多样',
|
||||
'LangChain',
|
||||
'应用程序',
|
||||
'Flowise',
|
||||
'丰富',
|
||||
],
|
||||
index_node_id: 'a3c7a2bd-003a-4667-a4a8-2da6c27cd887',
|
||||
index_node_hash: 'e824b23aa039ebc6a6b34a366251235bd81ad72535c2ea66fab949b1f78a65dc',
|
||||
hit_count: 0,
|
||||
enabled: true,
|
||||
disabled_at: 1732081062,
|
||||
disabled_by: '',
|
||||
status: 'completed',
|
||||
created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5',
|
||||
created_at: 1732081062,
|
||||
indexing_at: 1732081061,
|
||||
completed_at: 1732081064,
|
||||
error: null,
|
||||
stopped_at: 1732081062,
|
||||
},
|
||||
{
|
||||
id: 'c817f359-d927-4987-b940-e040251b10e1',
|
||||
position: 4,
|
||||
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
|
||||
content: '使用 Dify\n\n云 我们提供 Dify 云服务,任何人都可以零设置尝试。它提供了自部署版本的所有功能,并在沙盒计划中包含 200 次免费的 GPT-4 调用。\n\n自托管 Dify 社区版 使用这个入门指南快速在您的环境中运行 Dify。 使用我们的文档进行进一步的参考和更深入的说明。\n\n面向企业/组织的 Dify 我们提供额外的面向企业的功能。给我们发送电子邮件讨论企业需求。\n\n对于使用 AWS 的初创公司和中小型企业,请查看 AWS Marketplace 上的 Dify 高级版,并使用一键部署到您自己的 AWS VPC。它是一个价格实惠的 AMI 产品,提供了使用自定义徽标和品牌创建应用程序的选项。\n\n保持领先\n\n在 GitHub 上给 Dify Star,并立即收到新版本的通知。\n\n安装社区版\n\n系统要求\n\n在安装 Dify 之前,请确保您的机器满足以下最低系统要求:\n\nCPU >= 2 Core\n\nRAM >= 4 GiB\n\n快速启动\n\n启动 Dify 服务器的最简单方法是运行我们的 docker-compose.yml 文件。在运行安装命令之前,请确保您的机器上安装了 Docker 和 Docker Compose:\n\nbash cd docker cp .env.example .env docker compose up -d\n\n运行后,可以在浏览器上访问 http://localhost/install 进入 Dify 控制台并开始初始化安装操作。\n\n自定义配置',
|
||||
answer: '',
|
||||
word_count: 650,
|
||||
tokens: 427,
|
||||
keywords: [
|
||||
'Docker',
|
||||
'Dify',
|
||||
'env',
|
||||
'AWS',
|
||||
'docker',
|
||||
'自定义',
|
||||
'使用',
|
||||
'确保您',
|
||||
'安装',
|
||||
'compose',
|
||||
],
|
||||
index_node_id: '2af623f5-dea6-4b6b-a147-17f9e76ac1dd',
|
||||
index_node_hash: '7570a716c175c92b47658536e3c0df7dce8bac30b09cd33fb4333299874ebb0d',
|
||||
hit_count: 0,
|
||||
enabled: true,
|
||||
disabled_at: 1732081062,
|
||||
disabled_by: '',
|
||||
status: 'completed',
|
||||
created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5',
|
||||
created_at: 1732081062,
|
||||
indexing_at: 1732081061,
|
||||
completed_at: 1732081064,
|
||||
error: null,
|
||||
stopped_at: 1732081062,
|
||||
},
|
||||
{
|
||||
id: 'c2cbfe0b-304c-40c2-9980-7d39d65e5b18',
|
||||
position: 5,
|
||||
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
|
||||
content: '运行后,可以在浏览器上访问 http://localhost/install 进入 Dify 控制台并开始初始化安装操作。\n\n自定义配置\n\n如果您需要自定义配置,请参考 .env.example 文件中的注释,并更新 .env 文件中对应的值。此外,您可能需要根据您的具体部署环境和需求对 docker-compose.yaml 文件本身进行调整,例如更改镜像版本、端口映射或卷挂载。完成任何更改后,请重新运行 docker-compose up -d。您可以在此处找到可用环境变量的完整列表。\n\n使用 Helm Chart 部署\n\n使用 Helm Chart 版本或者 YAML 文件,可以在 Kubernetes 上部署 Dify。\n\nHelm Chart by @LeoQuote\n\nHelm Chart by @BorisPolonsky\n\nYAML 文件 by @Winson-030\n\n使用 Terraform 部署\n\n使用 terraform 一键将 Dify 部署到云平台\n\nAzure Global\n\nAzure Terraform by @nikawang\n\nGoogle Cloud\n\nGoogle Cloud Terraform by @sotazum\n\nStar History\n\nContributing\n\n对于那些想要贡献代码的人,请参阅我们的贡献指南。 同时,请考虑通过社交媒体、活动和会议来支持 Dify 的分享。\n\n我们正在寻找贡献者来帮助将Dify翻译成除了中文和英文之外的其他语言。如果您有兴趣帮助,请参阅我们的i18n README获取更多信息,并在我们的Discord社区服务器的global-users频道中留言。\n\nContributors\n\n社区与支持',
|
||||
answer: '',
|
||||
word_count: 751,
|
||||
tokens: 424,
|
||||
keywords: [
|
||||
'Terraform',
|
||||
'Dify',
|
||||
'env',
|
||||
'Chart',
|
||||
'自定义',
|
||||
'docker',
|
||||
'Helm',
|
||||
'部署',
|
||||
'请参阅',
|
||||
'文件',
|
||||
],
|
||||
index_node_id: 'e8b230c2-1ab6-4e70-b317-c50479b284d1',
|
||||
index_node_hash: '1efe0128dc40d87f3cd57855e872e4b67f20cc71a6c52732bfd67cd5bdcff65e',
|
||||
hit_count: 0,
|
||||
enabled: true,
|
||||
disabled_at: 1732081062,
|
||||
disabled_by: '',
|
||||
status: 'completed',
|
||||
created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5',
|
||||
created_at: 1732081062,
|
||||
indexing_at: 1732081061,
|
||||
completed_at: 1732081064,
|
||||
error: null,
|
||||
stopped_at: 1732081062,
|
||||
},
|
||||
{
|
||||
id: '0dcea77f-657d-4765-bc4a-a71806bede29',
|
||||
position: 6,
|
||||
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
|
||||
content: 'Contributors\n\n社区与支持\n\n我们欢迎您为 Dify 做出贡献,以帮助改善 Dify。包括:提交代码、问题、新想法,或分享您基于 Dify 创建的有趣且有用的 AI 应用程序。同时,我们也欢迎您在不同的活动、会议和社交媒体上分享 Dify。\n\nGithub Discussion. 👉:分享您的应用程序并与社区交流。\n\nGitHub Issues。👉:使用 Dify.AI 时遇到的错误和问题,请参阅贡献指南。\n\n电子邮件支持。👉:关于使用 Dify.AI 的问题。\n\nDiscord。👉:分享您的应用程序并与社区交流。\n\nX(Twitter)。👉:分享您的应用程序并与社区交流。\n\n商业许可。👉:有关商业用途许可 Dify.AI 的商业咨询。\n\n微信 👉:扫描下方二维码,添加微信好友,备注 Dify,我们将邀请您加入 Dify 社区。\n\n安全问题\n\n为了保护您的隐私,请避免在 GitHub 上发布安全问题。发送问题至 security@dify.ai,我们将为您做更细致的解答。\n\nLicense\n\n本仓库遵循 Dify Open Source License 开源协议,该许可证本质上是 Apache 2.0,但有一些额外的限制。',
|
||||
answer: '',
|
||||
word_count: 525,
|
||||
tokens: 388,
|
||||
keywords: [
|
||||
'问题',
|
||||
'Dify',
|
||||
'分享',
|
||||
'AI',
|
||||
'GitHub',
|
||||
'微信',
|
||||
'应用程序',
|
||||
'社区',
|
||||
'欢迎您',
|
||||
'License',
|
||||
],
|
||||
index_node_id: '3d17802d-9316-4e0d-9e9e-179f12e9830c',
|
||||
index_node_hash: 'd7d3093eb73803bdbfabe811e33ff60c8b75c15340f9046cac53b2e02fa07203',
|
||||
hit_count: 0,
|
||||
enabled: true,
|
||||
disabled_at: 1732081062,
|
||||
disabled_by: '',
|
||||
status: 'completed',
|
||||
created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5',
|
||||
created_at: 1732081062,
|
||||
indexing_at: 1732081061,
|
||||
completed_at: 1732081064,
|
||||
error: null,
|
||||
stopped_at: 1732081062,
|
||||
},
|
||||
],
|
||||
doc_form: 'text_model',
|
||||
has_more: false,
|
||||
limit: 10,
|
||||
total: 6,
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
import React, { type FC, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightUpLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { StatusItem } from '../../list'
|
||||
import DocumentFileIcon from '../../../common/document-file-icon'
|
||||
import { SegmentIndexTag, useSegmentListContext } from '.'
|
||||
import type { SegmentDetailModel } from '@/models/datasets'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import cn from '@/utils/classnames'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
const Dot = React.memo(() => {
|
||||
return (
|
||||
<div className='text-text-quaternary text-xs font-medium'>·</div>
|
||||
)
|
||||
})
|
||||
|
||||
Dot.displayName = 'Dot'
|
||||
|
||||
const ProgressBar: FC<{ percent: number; loading: boolean }> = React.memo(({ percent, loading }) => {
|
||||
return (
|
||||
<div className=''>
|
||||
<div className=''>
|
||||
<div
|
||||
className=''
|
||||
style={{ width: `${loading ? 0 : (Math.min(percent, 1) * 100).toFixed(2)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className=''>{loading ? null : percent.toFixed(2)}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
ProgressBar.displayName = 'ProgressBar'
|
||||
|
||||
type DocumentTitleProps = {
|
||||
name: string
|
||||
extension?: string
|
||||
}
|
||||
|
||||
const DocumentTitle: FC<DocumentTitleProps> = React.memo(({ extension, name }) => {
|
||||
return (
|
||||
<div className=''>
|
||||
<DocumentFileIcon name={name} extension={extension} size={'sm'} />
|
||||
<span className=''>{name || '--'}</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
DocumentTitle.displayName = 'DocumentTitle'
|
||||
|
||||
const Tag = React.memo(({ text }: { text: string }) => {
|
||||
return (
|
||||
<div className='inline-flex items-center gap-x-0.5'>
|
||||
<span className='text-text-quaternary text-xs font-medium'>#</span>
|
||||
<span className='text-text-tertiary text-xs'>{text}</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
Tag.displayName = 'Tag'
|
||||
|
||||
export type UsageScene = 'doc' | 'hitTesting'
|
||||
|
||||
type ISegmentCardProps = {
|
||||
loading: boolean
|
||||
detail?: SegmentDetailModel & { document?: { name: string } }
|
||||
contentExternal?: string
|
||||
refSource?: {
|
||||
title: string
|
||||
uri: string
|
||||
}
|
||||
isExternal?: boolean
|
||||
score?: number
|
||||
onClick?: () => void
|
||||
onChangeSwitch?: (enabled: boolean, segId?: string) => Promise<void>
|
||||
onDelete?: (segId: string) => Promise<void>
|
||||
onClickEdit?: () => void
|
||||
scene?: UsageScene
|
||||
className?: string
|
||||
archived?: boolean
|
||||
embeddingAvailable?: boolean
|
||||
}
|
||||
|
||||
const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
detail = {},
|
||||
contentExternal,
|
||||
isExternal,
|
||||
refSource,
|
||||
score,
|
||||
onClick,
|
||||
onChangeSwitch,
|
||||
onDelete,
|
||||
onClickEdit,
|
||||
loading = true,
|
||||
scene = 'doc',
|
||||
className = '',
|
||||
archived,
|
||||
embeddingAvailable,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
id,
|
||||
position,
|
||||
enabled,
|
||||
content,
|
||||
word_count,
|
||||
hit_count,
|
||||
answer,
|
||||
keywords,
|
||||
} = detail as Required<ISegmentCardProps>['detail']
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
const isDocScene = useMemo(() => {
|
||||
return scene === 'doc'
|
||||
}, [scene])
|
||||
|
||||
// todo: change to real logic
|
||||
const chunkEdited = useMemo(() => {
|
||||
return true
|
||||
}, [])
|
||||
|
||||
const textOpacity = useMemo(() => {
|
||||
return enabled ? '' : 'opacity-50'
|
||||
}, [enabled])
|
||||
|
||||
const renderContent = () => {
|
||||
if (answer) {
|
||||
return (
|
||||
<>
|
||||
<div className='flex'>
|
||||
<div className='w-4 mr-2 text-[13px] font-medium leading-[20px] text-text-tertiary'>Q</div>
|
||||
<div className='text-text-secondary body-md-regular'>{content}</div>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<div className='w-4 mr-2 text-[13px] font-medium leading-[20px] text-text-tertiary'>A</div>
|
||||
<div className='text-text-secondary body-md-regular'>{answer}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (contentExternal)
|
||||
return contentExternal
|
||||
|
||||
return content
|
||||
}
|
||||
const isCollapsed = useSegmentListContext(s => s.isCollapsed)
|
||||
|
||||
return (
|
||||
<div className={cn('p-3 pb-2.5 hover:bg-dataset-chunk-detail-card-hover-bg rounded-xl group/card', className)} onClick={() => onClick?.()}>
|
||||
<div className='relative flex items-center justify-between'>
|
||||
{isDocScene
|
||||
? <>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<SegmentIndexTag positionId={position} className={textOpacity} />
|
||||
<Dot />
|
||||
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(word_count)} Characters`}</div>
|
||||
<Dot />
|
||||
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(hit_count)} Retrieval Count`}</div>
|
||||
<Dot />
|
||||
{chunkEdited && (
|
||||
<Badge text='edited' uppercase className={textOpacity} />
|
||||
)}
|
||||
</div>
|
||||
<div className=''>
|
||||
{loading
|
||||
? (
|
||||
<Indicator color="gray" />
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
|
||||
{embeddingAvailable && (
|
||||
<div className="absolute -top-2.5 -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]">
|
||||
{!archived && (
|
||||
<>
|
||||
<div
|
||||
className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onClickEdit?.()
|
||||
}}>
|
||||
<RiEditLine className='w-4 h-4 text-text-tertiary' />
|
||||
</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'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setShowModal(true)
|
||||
}
|
||||
}>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' />
|
||||
</div>
|
||||
<Divider type="vertical" className="h-3.5 bg-divider-regular" />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
|
||||
e.stopPropagation()
|
||||
}
|
||||
className="flex items-center"
|
||||
>
|
||||
<Switch
|
||||
size='md'
|
||||
disabled={archived || detail.status !== 'completed'}
|
||||
defaultValue={enabled}
|
||||
onChange={async (val) => {
|
||||
await onChangeSwitch?.(val, id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
: (
|
||||
score !== null
|
||||
? (
|
||||
<div className=''>
|
||||
<div className='' />
|
||||
<ProgressBar percent={score ?? 0} loading={loading} />
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
)}
|
||||
</div>
|
||||
{loading
|
||||
? (
|
||||
<div className=''>
|
||||
<div className='' />
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
isDocScene
|
||||
? <>
|
||||
<div className={cn('text-text-secondary body-md-regular -tracking-[0.07px] mt-1', textOpacity, isCollapsed ? 'line-clamp-2' : 'line-clamp-20')}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className={cn('flex items-center gap-x-2 pt-1.5', textOpacity)}>
|
||||
{keywords?.map(keyword => <Tag key={keyword} text={keyword} />)}
|
||||
</div>
|
||||
</>
|
||||
: <>
|
||||
<div className='text-text-secondary body-md-regular -tracking-[0.07px]'>
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className=''>
|
||||
<Divider />
|
||||
<div className="relative flex items-center w-full pb-1">
|
||||
<DocumentTitle
|
||||
name={detail?.document?.name || refSource?.title || ''}
|
||||
extension={(detail?.document?.name || refSource?.title || '').split('.').pop() || 'txt'}
|
||||
/>
|
||||
<div className=''>
|
||||
{isExternal ? t('datasetHitTesting.viewDetail') : t('datasetHitTesting.viewChart')}
|
||||
<RiArrowRightUpLine className="w-3.5 h-3.5 ml-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{showModal
|
||||
&& <Confirm
|
||||
isShow={showModal}
|
||||
title={t('datasetDocuments.segment.delete')}
|
||||
confirmText={t('common.operation.sure')}
|
||||
onConfirm={async () => { await onDelete?.(id) }}
|
||||
onCancel={() => setShowModal(false)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SegmentCard)
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import SegmentCard from './segment-card'
|
||||
import type { SegmentDetailModel } from '@/models/datasets'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
type ISegmentListProps = {
|
||||
isLoading: boolean
|
||||
items: SegmentDetailModel[]
|
||||
selectedSegmentIds: string[]
|
||||
onSelected: (segId: string) => void
|
||||
onClick: (detail: SegmentDetailModel, isEditing?: boolean) => void
|
||||
onChangeSwitch: (enabled: boolean, segId?: string,) => Promise<void>
|
||||
onDelete: (segId: string) => Promise<void>
|
||||
archived?: boolean
|
||||
embeddingAvailable: boolean
|
||||
}
|
||||
|
||||
const SegmentList: FC<ISegmentListProps> = ({
|
||||
isLoading,
|
||||
items,
|
||||
selectedSegmentIds,
|
||||
onSelected,
|
||||
onClick: onClickCard,
|
||||
onChangeSwitch,
|
||||
onDelete,
|
||||
archived,
|
||||
embeddingAvailable,
|
||||
}) => {
|
||||
if (isLoading)
|
||||
return <Loading type='app' />
|
||||
return (
|
||||
<div className='flex flex-col h-full overflow-y-auto'>
|
||||
{
|
||||
items.map((segItem) => {
|
||||
const isLast = items[items.length - 1].id === segItem.id
|
||||
return (
|
||||
<div key={segItem.id} className='flex items-start gap-x-2'>
|
||||
<Checkbox
|
||||
key={`${segItem.id}-checkbox`}
|
||||
className='shrink-0 mt-3.5'
|
||||
checked={selectedSegmentIds.includes(segItem.id)}
|
||||
onCheck={() => onSelected(segItem.id)}
|
||||
/>
|
||||
<div className='flex flex-col'>
|
||||
<SegmentCard
|
||||
key={`${segItem.id}-card`}
|
||||
detail={segItem}
|
||||
onClick={() => onClickCard(segItem)}
|
||||
onChangeSwitch={onChangeSwitch}
|
||||
onClickEdit={() => onClickCard(segItem, true)}
|
||||
onDelete={onDelete}
|
||||
loading={false}
|
||||
archived={archived}
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
/>
|
||||
{!isLast && <div className='w-full px-3'>
|
||||
<Divider type='horizontal' className='bg-divider-subtle my-1' />
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SegmentList
|
||||
@@ -5,10 +5,10 @@
|
||||
grid-auto-rows: 180px;
|
||||
} */
|
||||
.totalText {
|
||||
@apply text-gray-900 font-medium text-base flex-1;
|
||||
@apply text-text-secondary flex-1;
|
||||
}
|
||||
.docSearchWrapper {
|
||||
@apply sticky w-full py-1 -top-3 bg-white flex items-center mb-3 justify-between z-10 flex-wrap gap-y-1;
|
||||
@apply sticky w-full -top-3 bg-white flex items-center mb-3 justify-between z-10 flex-wrap gap-y-1;
|
||||
}
|
||||
.listContainer {
|
||||
height: calc(100% - 3.25rem);
|
||||
|
||||
@@ -195,6 +195,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
className={style.layoutRightIcon}
|
||||
onClick={() => setShowMetadata(!showMetadata)}
|
||||
>
|
||||
{/* // todo: change icon */}
|
||||
<RiLayoutRight2Line className={cn('w-4 h-4', showMetadata ? 'text-components-button-secondary-accent-text' : 'text-components-button-secondary-text')} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -202,7 +203,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
<div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
|
||||
{isDetailLoading
|
||||
? <Loading type='app' />
|
||||
: <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : 'pb-[30px] pt-3 px-6'}`}>
|
||||
: <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : 'pb-[30px] pt-3 pl-5 pr-11'}`}>
|
||||
{embedding
|
||||
? <Embedding detail={documentDetail} detailUpdate={detailMutate} />
|
||||
: <Completed
|
||||
@@ -211,6 +212,8 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
onNewSegmentModalChange={setNewSegmentModalVisible}
|
||||
importStatus={importStatus}
|
||||
archived={documentDetail?.archived}
|
||||
mode={documentDetail?.dataset_process_rule.mode}
|
||||
parentMode={documentDetail?.dataset_process_rule.rules.parent_mode}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -270,14 +270,14 @@ export const OperationAction: FC<{
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
needsDelay
|
||||
>
|
||||
<div
|
||||
<button
|
||||
className={cn('rounded-lg mr-2 cursor-pointer',
|
||||
!isListScene
|
||||
? 'p-2 bg-components-button-secondary-bg hover:bg-components-button-secondary-bg-hover border-[0.5px] border-components-button-secondary-border hover:border-components-button-secondary-border-hover shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px]'
|
||||
: 'p-0.5 hover:bg-state-base-hover')}
|
||||
onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
|
||||
<RiEqualizer2Line className='w-4 h-4 text-components-button-secondary-text' />
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
htmlContent={
|
||||
|
||||
@@ -332,6 +332,7 @@ const translation = {
|
||||
},
|
||||
segment: {
|
||||
paragraphs: 'Paragraphs',
|
||||
chunks: 'CHUNKS',
|
||||
keywords: 'Key Words',
|
||||
addKeyWord: 'Add key word',
|
||||
keywordError: 'The maximum length of keyword is 20',
|
||||
|
||||
@@ -330,6 +330,7 @@ const translation = {
|
||||
},
|
||||
segment: {
|
||||
paragraphs: '段落',
|
||||
chunks: '段落',
|
||||
keywords: '关键词',
|
||||
addKeyWord: '添加关键词',
|
||||
keywordError: '关键词最大长度为 20',
|
||||
|
||||
@@ -182,7 +182,7 @@ export type ProcessRuleResponse = {
|
||||
export type Rules = {
|
||||
pre_processing_rules: PreProcessingRule[]
|
||||
segmentation: Segmentation
|
||||
parent_node: ParentMode
|
||||
parent_mode: ParentMode
|
||||
subchunk_segmentation: Segmentation
|
||||
}
|
||||
|
||||
@@ -405,12 +405,12 @@ export const SEGMENT_STATUS_LIST = ['waiting', 'completed', 'error', 'indexing']
|
||||
export type SegmentStatus = typeof SEGMENT_STATUS_LIST[number]
|
||||
|
||||
export type SegmentsQuery = {
|
||||
last_id?: string
|
||||
page?: string
|
||||
limit: number
|
||||
// status?: SegmentStatus
|
||||
hit_count_gte?: number
|
||||
keyword?: string
|
||||
enabled?: boolean
|
||||
enabled?: boolean | 'all'
|
||||
}
|
||||
|
||||
export type SegmentDetailModel = {
|
||||
@@ -435,6 +435,7 @@ export type SegmentDetailModel = {
|
||||
error: string | null
|
||||
stopped_at: number
|
||||
answer?: string
|
||||
child_chunks?: ChildChunkDetail[]
|
||||
}
|
||||
|
||||
export type SegmentsResponse = {
|
||||
@@ -442,6 +443,7 @@ export type SegmentsResponse = {
|
||||
has_more: boolean
|
||||
limit: number
|
||||
total: number
|
||||
total_pages: number
|
||||
}
|
||||
|
||||
export type HitTestingRecord = {
|
||||
@@ -585,3 +587,15 @@ export const DEFAULT_WEIGHTED_SCORE = {
|
||||
keyword: 0.3,
|
||||
},
|
||||
}
|
||||
|
||||
export type ChildChunkType = 'automatic' | 'customized'
|
||||
|
||||
export type ChildChunkDetail = {
|
||||
id: number
|
||||
position: number
|
||||
segment_id: string
|
||||
content: string
|
||||
word_count: number
|
||||
created_at: number
|
||||
type: ChildChunkType
|
||||
}
|
||||
|
||||
@@ -1,16 +1,44 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { del, patch } from '../base'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { del, get, patch } from '../base'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type { SegmentsResponse } from '@/models/datasets'
|
||||
|
||||
const NAME_SPACE = 'segment'
|
||||
|
||||
const useSegmentListKey = [NAME_SPACE, 'list']
|
||||
|
||||
export const useSegmentList = (
|
||||
payload: {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
params: {
|
||||
page: number
|
||||
limit: number
|
||||
keyword: string
|
||||
enabled: boolean | 'all'
|
||||
}
|
||||
},
|
||||
disable?: boolean,
|
||||
) => {
|
||||
const { datasetId, documentId, params } = payload
|
||||
const { page, limit, keyword, enabled } = params
|
||||
return useQuery<SegmentsResponse>({
|
||||
queryKey: [...useSegmentListKey, datasetId, documentId, page, limit, keyword, enabled],
|
||||
queryFn: () => {
|
||||
return get<SegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments`, { params })
|
||||
},
|
||||
enabled: !disable,
|
||||
initialData: disable ? { data: [], has_more: false, total: 0, total_pages: 0, limit: 10 } : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
export const useEnableSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'enable'],
|
||||
mutationFn: (payload: { datasetId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, segmentIds } = payload
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, documentId, segmentIds } = payload
|
||||
const query = segmentIds.map(id => `segment_id=${id}`).join('&')
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/segments/enable?${query}`)
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/enable?${query}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -18,10 +46,10 @@ export const useEnableSegment = () => {
|
||||
export const useDisableSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'disable'],
|
||||
mutationFn: (payload: { datasetId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, segmentIds } = payload
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, documentId, segmentIds } = payload
|
||||
const query = segmentIds.map(id => `segment_id=${id}`).join('&')
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/segments/disable?${query}`)
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/disable?${query}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ module.exports = {
|
||||
'workflow-process-bg': 'var(--color-workflow-process-bg)',
|
||||
'dataset-chunk-process-success-bg': 'var(--color-dataset-chunk-process-success-bg)',
|
||||
'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)',
|
||||
'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)',
|
||||
},
|
||||
lineClamp: {
|
||||
20: '20',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,4 +4,5 @@ html[data-theme="dark"] {
|
||||
--color-workflow-process-bg: linear-gradient(90deg, rgba(24, 24, 27, 0.25) 0%, rgba(24, 24, 27, 0.04) 100%);
|
||||
--color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%);
|
||||
--color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%);
|
||||
--color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%);
|
||||
}
|
||||
@@ -4,4 +4,5 @@ html[data-theme="light"] {
|
||||
--color-workflow-process-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%);
|
||||
--color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%);
|
||||
--color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%);
|
||||
--color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%);
|
||||
}
|
||||
Reference in New Issue
Block a user