diff --git a/web/app/components/base/checkbox/assets/mixed.svg b/web/app/components/base/checkbox/assets/mixed.svg
new file mode 100644
index 0000000000..e16b8fc975
--- /dev/null
+++ b/web/app/components/base/checkbox/assets/mixed.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/base/checkbox/index.module.css b/web/app/components/base/checkbox/index.module.css
index 5fe4172f13..30c887801b 100644
--- a/web/app/components/base/checkbox/index.module.css
+++ b/web/app/components/base/checkbox/index.module.css
@@ -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 {
diff --git a/web/app/components/base/checkbox/index.tsx b/web/app/components/base/checkbox/index.tsx
index fe95155b3c..51d5fd9027 100644
--- a/web/app/components/base/checkbox/index.tsx
+++ b/web/app/components/base/checkbox/index.tsx
@@ -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 (
{
diff --git a/web/app/components/base/icons/assets/public/knowledge/chunk.svg b/web/app/components/base/icons/assets/public/knowledge/chunk.svg
new file mode 100644
index 0000000000..1dc04943fc
--- /dev/null
+++ b/web/app/components/base/icons/assets/public/knowledge/chunk.svg
@@ -0,0 +1,13 @@
+
diff --git a/web/app/components/base/icons/assets/public/knowledge/collapse.svg b/web/app/components/base/icons/assets/public/knowledge/collapse.svg
new file mode 100644
index 0000000000..b54e046085
--- /dev/null
+++ b/web/app/components/base/icons/assets/public/knowledge/collapse.svg
@@ -0,0 +1,9 @@
+
diff --git a/web/app/components/base/icons/src/public/knowledge/Chunk.json b/web/app/components/base/icons/src/public/knowledge/Chunk.json
new file mode 100644
index 0000000000..469d85d1a7
--- /dev/null
+++ b/web/app/components/base/icons/src/public/knowledge/Chunk.json
@@ -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"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/public/knowledge/Chunk.tsx b/web/app/components/base/icons/src/public/knowledge/Chunk.tsx
new file mode 100644
index 0000000000..87ff635811
--- /dev/null
+++ b/web/app/components/base/icons/src/public/knowledge/Chunk.tsx
@@ -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
, Omit>((
+ props,
+ ref,
+) => )
+
+Icon.displayName = 'Chunk'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/public/knowledge/Collapse.json b/web/app/components/base/icons/src/public/knowledge/Collapse.json
new file mode 100644
index 0000000000..66d457155d
--- /dev/null
+++ b/web/app/components/base/icons/src/public/knowledge/Collapse.json
@@ -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"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/public/knowledge/Collapse.tsx b/web/app/components/base/icons/src/public/knowledge/Collapse.tsx
new file mode 100644
index 0000000000..48206c4d0c
--- /dev/null
+++ b/web/app/components/base/icons/src/public/knowledge/Collapse.tsx
@@ -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, Omit>((
+ props,
+ ref,
+) => )
+
+Icon.displayName = 'Collapse'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/public/knowledge/index.ts b/web/app/components/base/icons/src/public/knowledge/index.ts
index 24bcf3e41c..86300c9ee3 100644
--- a/web/app/components/base/icons/src/public/knowledge/index.ts
+++ b/web/app/components/base/icons/src/public/knowledge/index.ts
@@ -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'
diff --git a/web/app/components/base/tooltip/index.tsx b/web/app/components/base/tooltip/index.tsx
index f3b4cff132..e5f27fdd8c 100644
--- a/web/app/components/base/tooltip/index.tsx
+++ b/web/app/components/base/tooltip/index.tsx
@@ -96,7 +96,7 @@ const Tooltip: FC = ({
>
{popupContent && ( triggerMethod === 'hover' && setHoverPopup()}
diff --git a/web/app/components/datasets/documents/detail/completed/batch-action.tsx b/web/app/components/datasets/documents/detail/completed/batch-action.tsx
new file mode 100644
index 0000000000..3bed21df96
--- /dev/null
+++ b/web/app/components/datasets/documents/detail/completed/batch-action.tsx
@@ -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
+ onBatchDisable: () => Promise
+ onBatchDelete: () => Promise
+ onCancel: () => void
+}
+
+const BatchAction: FC = ({
+ selectedSegmentIds,
+ onBatchEnable,
+ onBatchDisable,
+ onBatchDelete,
+ onCancel,
+}) => {
+ return (
+
+
+
+
+ {selectedSegmentIds.length}
+
+ Selected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default React.memo(BatchAction)
diff --git a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx
new file mode 100644
index 0000000000..96a6f5922f
--- /dev/null
+++ b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx
@@ -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 (
+
+
+
+
+ )
+}
+
+export default React.memo(DisplayToggle)
diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx
index 2c9e6ca2ea..c7978620a3 100644
--- a/web/app/components/datasets/documents/detail/completed/index.tsx
+++ b/web/app/components/datasets/documents/detail/completed/index.tsx
@@ -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 (
-
-
- {localPositionId}
+
+
+
+ {localPositionId}
+
)
}
@@ -52,10 +69,11 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri
type ISegmentDetailProps = {
embeddingAvailable: boolean
segInfo?: Partial
& { id: string }
- onChangeSwitch?: (segId: string, enabled: boolean) => Promise
+ onChangeSwitch?: (enabled: boolean, segId?: string) => Promise
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 = ({
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(segInfo?.keywords || [])
@@ -195,7 +214,7 @@ const SegmentDetailComponent: FC = ({
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 = ({
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('') // the input value
const [searchValue, setSearchValue] = useState('') // the search value
const [selectedStatus, setSelectedStatus] = useState('all') // the selected status, enabled/disabled/undefined
- const [lastSegmentsRes, setLastSegmentsRes] = useState(undefined)
- const [allSegments, setAllSegments] = useState>([]) // all segments data
- const [loading, setLoading] = useState(false)
- const [total, setTotal] = useState()
+ const [segments, setSegments] = useState([]) // all segments data
+ const [selectedSegmentIds, setSelectedSegmentIds] = useState([])
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 = ({
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(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)
- 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(opApi({ datasetId, segmentId: segId }) as Promise)
- 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(deleteSegment({ datasetId, documentId, segmentId: segId }) as Promise)
- 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 = ({
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 (
- <>
+ setIsCollapsed(!isCollapsed),
+ }}>
-
{total ? formatNumber(total) : '--'} {t('datasetDocuments.segment.paragraphs')}
+
+
{totalText} {t('datasetDocuments.segment.chunks')}
= ({
]}
defaultValue={'all'}
className={s.select}
- wrapperClassName='h-fit w-[120px] mr-2' />
+ wrapperClassName='h-fit w-[100px] mr-2' />
= ({
onChange={e => handleInputChange(e.target.value)}
onClear={() => handleInputChange('')}
/>
+
+
- = ({
= ({
onCancel={() => onNewSegmentModalChange(false)}
onSave={resetList}
/>
- >
+ {selectedSegmentIds.length > 0
+ && }
+
)
}
diff --git a/web/app/components/datasets/documents/detail/completed/mock-data.ts b/web/app/components/datasets/documents/detail/completed/mock-data.ts
new file mode 100644
index 0000000000..d655b4b2de
--- /dev/null
+++ b/web/app/components/datasets/documents/detail/completed/mock-data.ts
@@ -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,
+}
diff --git a/web/app/components/datasets/documents/detail/completed/segment-card.tsx b/web/app/components/datasets/documents/detail/completed/segment-card.tsx
new file mode 100644
index 0000000000..d929c85401
--- /dev/null
+++ b/web/app/components/datasets/documents/detail/completed/segment-card.tsx
@@ -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 (
+ ·
+ )
+})
+
+Dot.displayName = 'Dot'
+
+const ProgressBar: FC<{ percent: number; loading: boolean }> = React.memo(({ percent, loading }) => {
+ return (
+
+
+
{loading ? null : percent.toFixed(2)}
+
+ )
+})
+
+ProgressBar.displayName = 'ProgressBar'
+
+type DocumentTitleProps = {
+ name: string
+ extension?: string
+}
+
+const DocumentTitle: FC = React.memo(({ extension, name }) => {
+ return (
+
+
+ {name || '--'}
+
+ )
+})
+
+DocumentTitle.displayName = 'DocumentTitle'
+
+const Tag = React.memo(({ text }: { text: string }) => {
+ return (
+
+ #
+ {text}
+
+ )
+})
+
+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
+ onDelete?: (segId: string) => Promise
+ onClickEdit?: () => void
+ scene?: UsageScene
+ className?: string
+ archived?: boolean
+ embeddingAvailable?: boolean
+}
+
+const SegmentCard: FC = ({
+ 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['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 (
+ <>
+
+
+ >
+ )
+ }
+
+ if (contentExternal)
+ return contentExternal
+
+ return content
+ }
+ const isCollapsed = useSegmentListContext(s => s.isCollapsed)
+
+ return (
+ onClick?.()}>
+
+ {isDocScene
+ ? <>
+
+
+
+
{`${formatNumber(word_count)} Characters`}
+
+
{`${formatNumber(hit_count)} Retrieval Count`}
+
+ {chunkEdited && (
+
+ )}
+
+
+ {loading
+ ? (
+
+ )
+ : (
+ <>
+
+ {embeddingAvailable && (
+
+ {!archived && (
+ <>
+
{
+ e.stopPropagation()
+ onClickEdit?.()
+ }}>
+
+
+
{
+ e.stopPropagation()
+ setShowModal(true)
+ }
+ }>
+
+
+
+ >
+ )}
+
) =>
+ e.stopPropagation()
+ }
+ className="flex items-center"
+ >
+ {
+ await onChangeSwitch?.(val, id)
+ }}
+ />
+
+
+ )}
+ >
+ )}
+
+ >
+ : (
+ score !== null
+ ? (
+
+ )
+ : null
+ )}
+
+ {loading
+ ? (
+
+ )
+ : (
+ isDocScene
+ ? <>
+
+ {renderContent()}
+
+
+ {keywords?.map(keyword => )}
+
+ >
+ : <>
+
+ {renderContent()}
+
+
+
+
+
+
+ {isExternal ? t('datasetHitTesting.viewDetail') : t('datasetHitTesting.viewChart')}
+
+
+
+
+ >
+ )}
+ {showModal
+ &&
{ await onDelete?.(id) }}
+ onCancel={() => setShowModal(false)}
+ />
+ }
+
+ )
+}
+
+export default React.memo(SegmentCard)
diff --git a/web/app/components/datasets/documents/detail/completed/segment-list.tsx b/web/app/components/datasets/documents/detail/completed/segment-list.tsx
new file mode 100644
index 0000000000..6e6c849ab3
--- /dev/null
+++ b/web/app/components/datasets/documents/detail/completed/segment-list.tsx
@@ -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
+ onDelete: (segId: string) => Promise
+ archived?: boolean
+ embeddingAvailable: boolean
+}
+
+const SegmentList: FC = ({
+ isLoading,
+ items,
+ selectedSegmentIds,
+ onSelected,
+ onClick: onClickCard,
+ onChangeSwitch,
+ onDelete,
+ archived,
+ embeddingAvailable,
+}) => {
+ if (isLoading)
+ return
+ return (
+
+ {
+ items.map((segItem) => {
+ const isLast = items[items.length - 1].id === segItem.id
+ return (
+
+
onSelected(segItem.id)}
+ />
+
+
onClickCard(segItem)}
+ onChangeSwitch={onChangeSwitch}
+ onClickEdit={() => onClickCard(segItem, true)}
+ onDelete={onDelete}
+ loading={false}
+ archived={archived}
+ embeddingAvailable={embeddingAvailable}
+ />
+ {!isLast && }
+
+
+ )
+ })
+ }
+
+ )
+}
+
+export default SegmentList
diff --git a/web/app/components/datasets/documents/detail/completed/style.module.css b/web/app/components/datasets/documents/detail/completed/style.module.css
index 7633d53209..610d959efe 100644
--- a/web/app/components/datasets/documents/detail/completed/style.module.css
+++ b/web/app/components/datasets/documents/detail/completed/style.module.css
@@ -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);
diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx
index 5d6e2baba2..ea3a91245f 100644
--- a/web/app/components/datasets/documents/detail/index.tsx
+++ b/web/app/components/datasets/documents/detail/index.tsx
@@ -195,6 +195,7 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => {
className={style.layoutRightIcon}
onClick={() => setShowMetadata(!showMetadata)}
>
+ {/* // todo: change icon */}
@@ -202,7 +203,7 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => {
{isDetailLoading
?
- :
+ :
{embedding
?
: = ({ datasetId, documentId }) => {
onNewSegmentModalChange={setNewSegmentModalVisible}
importStatus={importStatus}
archived={documentDetail?.archived}
+ mode={documentDetail?.dataset_process_rule.mode}
+ parentMode={documentDetail?.dataset_process_rule.rules.parent_mode}
/>
}
diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx
index ee883e912b..4321a610cb 100644
--- a/web/app/components/datasets/documents/list.tsx
+++ b/web/app/components/datasets/documents/list.tsx
@@ -270,14 +270,14 @@ export const OperationAction: FC<{
popupClassName='text-text-secondary system-xs-medium'
needsDelay
>
-
router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
-
+
{
+ const { datasetId, documentId, params } = payload
+ const { page, limit, keyword, enabled } = params
+ return useQuery({
+ queryKey: [...useSegmentListKey, datasetId, documentId, page, limit, keyword, enabled],
+ queryFn: () => {
+ return get(`/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(`/datasets/${datasetId}/segments/enable?${query}`)
+ return patch(`/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(`/datasets/${datasetId}/segments/disable?${query}`)
+ return patch(`/datasets/${datasetId}/documents/${documentId}/segments/disable?${query}`)
},
})
}
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index 956cfb66f5..59c896f23f 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -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',
},
},
},
diff --git a/web/themes/manual-dark.css b/web/themes/manual-dark.css
index 26604370c4..6e2f50849f 100644
--- a/web/themes/manual-dark.css
+++ b/web/themes/manual-dark.css
@@ -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%);
}
\ No newline at end of file
diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css
index 2113ef7fc0..c85dd40a87 100644
--- a/web/themes/manual-light.css
+++ b/web/themes/manual-light.css
@@ -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%);
}
\ No newline at end of file