mirror of https://github.com/langgenius/dify.git
refactor: refactor pipeline-related components and services to use template terminology
This commit is contained in:
parent
019ef74bf2
commit
35a7add4e9
|
|
@ -167,10 +167,6 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
<div className={cn(s.app, 'relative flex', 'overflow-hidden')}>
|
||||
{appDetail && (
|
||||
<AppSideBar
|
||||
title={appDetail.name}
|
||||
icon={appDetail.icon}
|
||||
icon_background={appDetail.icon_background as string}
|
||||
desc={appDetail.mode}
|
||||
navigation={navigation}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -173,11 +173,6 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||
}}>
|
||||
{!hideSideBar && (
|
||||
<AppSideBar
|
||||
title={datasetRes?.name || '--'}
|
||||
icon={datasetRes?.icon_info?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
|
||||
icon_background={datasetRes?.icon_info?.icon_background || '#F5F5F5'}
|
||||
desc={datasetRes?.description || '--'}
|
||||
isExternal={datasetRes?.provider === 'external'}
|
||||
navigation={navigation}
|
||||
extraInfo={
|
||||
!isCurrentWorkspaceDatasetOperator
|
||||
|
|
|
|||
|
|
@ -28,9 +28,10 @@ const DatasetInfo: FC<Props> = ({
|
|||
icon_background: '#FFF4ED',
|
||||
icon_url: '',
|
||||
}
|
||||
const isExternal = dataset.provider === 'external'
|
||||
const isExternalProvider = dataset.provider === 'external'
|
||||
const { formatIndexingTechniqueAndMethod } = useKnowledge()
|
||||
const Icon = isExternal ? DOC_FORM_ICON_WITH_BG.external : DOC_FORM_ICON_WITH_BG[dataset.doc_form]
|
||||
const chunkingModeIcon = dataset.doc_form ? DOC_FORM_ICON_WITH_BG[dataset.doc_form] : React.Fragment
|
||||
const Icon = isExternalProvider ? DOC_FORM_ICON_WITH_BG.external : chunkingModeIcon
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex flex-col', expand ? '' : 'p-1')}>
|
||||
|
|
@ -46,32 +47,34 @@ const DatasetInfo: FC<Props> = ({
|
|||
background={iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_url}
|
||||
/>
|
||||
<div className='absolute -bottom-1 -right-1 z-10'>
|
||||
<Icon className='size-4' />
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<div
|
||||
className='system-md-semibold truncate text-text-secondary'
|
||||
title={dataset.name}
|
||||
>
|
||||
{dataset.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{isExternal && t('dataset.externalTag')}
|
||||
{!isExternal && (
|
||||
<div className='flex items-center gap-x-1'>
|
||||
<Badge>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</Badge>
|
||||
<Badge>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(dataset.doc_form || isExternalProvider) && (
|
||||
<div className='absolute -bottom-1 -right-1 z-10'>
|
||||
<Icon className='size-4' />
|
||||
</div>
|
||||
<p className='system-xs-regular line-clamp-3 text-text-tertiary first-letter:capitalize'>
|
||||
{dataset.description}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<div
|
||||
className='system-md-semibold truncate text-text-secondary'
|
||||
title={dataset.name}
|
||||
>
|
||||
{dataset.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{isExternalProvider && t('dataset.externalTag')}
|
||||
{!isExternalProvider && dataset.doc_form && dataset.indexing_technique && (
|
||||
<div className='flex items-center gap-x-1'>
|
||||
<Badge>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</Badge>
|
||||
<Badge>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-xs-regular line-clamp-3 text-text-tertiary first-letter:capitalize'>
|
||||
{dataset.description}
|
||||
</p>
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { useShallow } from 'zustand/react/shallow'
|
|||
import { RiLayoutLeft2Line, RiLayoutRight2Line } from '@remixicon/react'
|
||||
import NavLink from './navLink'
|
||||
import type { NavIcon } from './navLink'
|
||||
import AppBasic from './basic'
|
||||
import AppInfo from './app-info'
|
||||
import DatasetInfo from './dataset-info'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
|
@ -12,11 +11,6 @@ import cn from '@/utils/classnames'
|
|||
|
||||
export type IAppDetailNavProps = {
|
||||
iconType?: 'app' | 'dataset' | 'notion'
|
||||
title: string
|
||||
desc: string
|
||||
isExternal?: boolean
|
||||
icon: string
|
||||
icon_background: string
|
||||
navigation: Array<{
|
||||
name: string
|
||||
href: string
|
||||
|
|
@ -27,11 +21,6 @@ export type IAppDetailNavProps = {
|
|||
}
|
||||
|
||||
const AppDetailNav = ({
|
||||
title,
|
||||
desc,
|
||||
isExternal,
|
||||
icon,
|
||||
icon_background,
|
||||
navigation,
|
||||
extraInfo,
|
||||
iconType = 'app',
|
||||
|
|
@ -71,23 +60,12 @@ const AppDetailNav = ({
|
|||
{iconType === 'app' && (
|
||||
<AppInfo expand={expand} />
|
||||
)}
|
||||
{iconType === 'dataset' && (
|
||||
{iconType !== 'app' && (
|
||||
<DatasetInfo
|
||||
expand={expand}
|
||||
extraInfo={extraInfo && extraInfo(appSidebarExpand)}
|
||||
/>
|
||||
)}
|
||||
{!['app', 'dataset'].includes(iconType) && (
|
||||
<AppBasic
|
||||
mode={appSidebarExpand}
|
||||
iconType={iconType}
|
||||
icon={icon}
|
||||
icon_background={icon_background}
|
||||
name={title}
|
||||
type={desc}
|
||||
isExternal={isExternal}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='px-4'>
|
||||
<div className={cn('mx-auto mt-1 h-[1px] bg-divider-subtle', !expand && 'w-6')} />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { Member } from '@/models/common'
|
|||
import CreateForm from '../create-form'
|
||||
import type { CreateFormData } from '@/models/pipeline'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
type CreateFromScratchModalProps = {
|
||||
show: boolean
|
||||
|
|
@ -17,6 +18,7 @@ const CreateFromScratchModal = ({
|
|||
show,
|
||||
onClose,
|
||||
}: CreateFromScratchModalProps) => {
|
||||
const { push } = useRouter()
|
||||
const [memberList, setMemberList] = useState<Member[]>([])
|
||||
const { data: members } = useMembers()
|
||||
|
||||
|
|
@ -52,11 +54,15 @@ const CreateFromScratchModal = ({
|
|||
request.partial_member_list = selectedMemberList
|
||||
}
|
||||
await createEmptyDataset(request, {
|
||||
onSettled: () => {
|
||||
onSettled: (data) => {
|
||||
if (data) {
|
||||
const { id } = data
|
||||
push(`/datasets/${id}/pipeline`)
|
||||
}
|
||||
onClose?.()
|
||||
},
|
||||
})
|
||||
}, [createEmptyDataset, memberList, onClose])
|
||||
}, [createEmptyDataset, memberList, onClose, push])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
|||
|
|
@ -1,57 +1,9 @@
|
|||
import { usePipelineTemplateList } from '@/service/use-pipeline'
|
||||
import TemplateCard from './template-card'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
import type { PipelineTemplate } from '@/models/pipeline'
|
||||
|
||||
const BuiltInPipelineList = () => {
|
||||
// TODO: remove mock data
|
||||
const mockData: PipelineTemplate[] = [{
|
||||
id: '1',
|
||||
name: 'Pipeline 1',
|
||||
description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_info: {
|
||||
icon: '🤖',
|
||||
icon_background: '#F0FDF9',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.text,
|
||||
position: 0,
|
||||
}, {
|
||||
id: '2',
|
||||
name: 'Pipeline 2',
|
||||
description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_info: {
|
||||
icon: '🏖️',
|
||||
icon_background: '#FFF4ED',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.parentChild,
|
||||
position: 1,
|
||||
}, {
|
||||
id: '3',
|
||||
name: 'Pipeline 3',
|
||||
description: 'This is a description of Pipeline 3',
|
||||
icon_info: {
|
||||
icon: '🚀',
|
||||
icon_background: '#FEFBE8',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.qa,
|
||||
position: 2,
|
||||
}, {
|
||||
id: '4',
|
||||
name: 'Pipeline 4',
|
||||
description: 'This is a description of Pipeline 4',
|
||||
icon_info: {
|
||||
icon: '🍯',
|
||||
icon_background: '#F5F3FF',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.graph,
|
||||
position: 3,
|
||||
}]
|
||||
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'built-in' })
|
||||
const list = pipelineList?.pipelines || mockData
|
||||
const list = pipelineList?.pipelines
|
||||
|
||||
if (isLoading || !list)
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,57 +1,9 @@
|
|||
import { ChunkingMode } from '@/models/datasets'
|
||||
import TemplateCard from './template-card'
|
||||
import { usePipelineTemplateList } from '@/service/use-pipeline'
|
||||
import type { PipelineTemplate } from '@/models/pipeline'
|
||||
|
||||
const CustomizedList = () => {
|
||||
const mockData: PipelineTemplate[] = [{
|
||||
id: '1',
|
||||
name: 'Pipeline 1',
|
||||
description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_info: {
|
||||
icon: '🤖',
|
||||
icon_background: '#F0FDF9',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.text,
|
||||
position: 0,
|
||||
}, {
|
||||
id: '2',
|
||||
name: 'Pipeline 2',
|
||||
description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.',
|
||||
icon_info: {
|
||||
icon: '🏖️',
|
||||
icon_background: '#FFF4ED',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.parentChild,
|
||||
position: 1,
|
||||
}, {
|
||||
id: '3',
|
||||
name: 'Pipeline 3',
|
||||
description: 'This is a description of Pipeline 3',
|
||||
icon_info: {
|
||||
icon: '🚀',
|
||||
icon_background: '#FEFBE8',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.qa,
|
||||
position: 2,
|
||||
}, {
|
||||
id: '4',
|
||||
name: 'Pipeline 4',
|
||||
description: 'This is a description of Pipeline 4',
|
||||
icon_info: {
|
||||
icon: '🍯',
|
||||
icon_background: '#F5F3FF',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
doc_form: ChunkingMode.graph,
|
||||
position: 3,
|
||||
}]
|
||||
|
||||
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'customized' })
|
||||
const list = pipelineList?.pipelines || mockData
|
||||
const list = pipelineList?.pipelines
|
||||
|
||||
if (isLoading || !list)
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Button from '@/app/components/base/button'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { PipelineTemplate } from '@/models/pipeline'
|
||||
import { useUpdatePipelineInfo } from '@/service/use-pipeline'
|
||||
import { useUpdateTemplateInfo } from '@/service/use-pipeline'
|
||||
|
||||
type EditPipelineInfoProps = {
|
||||
onClose: () => void
|
||||
|
|
@ -61,7 +61,7 @@ const EditPipelineInfo = ({
|
|||
setDescription(value)
|
||||
}, [])
|
||||
|
||||
const { mutateAsync: updatePipeline } = useUpdatePipelineInfo()
|
||||
const { mutateAsync: updatePipeline } = useUpdateTemplateInfo()
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!name) {
|
||||
|
|
@ -72,7 +72,7 @@ const EditPipelineInfo = ({
|
|||
return
|
||||
}
|
||||
const request = {
|
||||
pipeline_id: pipeline.id,
|
||||
template_id: pipeline.id,
|
||||
name,
|
||||
icon_info: {
|
||||
icon_type: appIcon.type,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import EditPipelineInfo from './edit-pipeline-info'
|
|||
import type { PipelineTemplate } from '@/models/pipeline'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
useDeletePipeline,
|
||||
useExportPipelineDSL,
|
||||
useDeleteTemplate,
|
||||
useExportTemplateDSL,
|
||||
usePipelineTemplateById,
|
||||
} from '@/service/use-pipeline'
|
||||
import { downloadFile } from '@/utils/format'
|
||||
|
|
@ -63,8 +63,8 @@ const TemplateCard = ({
|
|||
type: 'success',
|
||||
message: t('app.newApp.appCreated'),
|
||||
})
|
||||
if (newDataset.pipeline_info?.id)
|
||||
await handleCheckPluginDependencies(newDataset.pipeline_info.id, true)
|
||||
if (newDataset.pipeline_id)
|
||||
await handleCheckPluginDependencies(newDataset.pipeline_id, true)
|
||||
push(`dataset/${newDataset.id}/pipeline`)
|
||||
}
|
||||
catch {
|
||||
|
|
@ -91,14 +91,11 @@ const TemplateCard = ({
|
|||
setShowDetailModal(false)
|
||||
}, [])
|
||||
|
||||
const { mutateAsync: exportPipelineDSL, isPending: isExporting } = useExportPipelineDSL()
|
||||
const { mutateAsync: exportPipelineDSL, isPending: isExporting } = useExportTemplateDSL()
|
||||
|
||||
const handleExportDSL = useCallback(async (includeSecret = false) => {
|
||||
const handleExportDSL = useCallback(async () => {
|
||||
if (isExporting) return
|
||||
await exportPipelineDSL({
|
||||
pipeline_id: pipeline.id,
|
||||
include_secret: includeSecret,
|
||||
}, {
|
||||
await exportPipelineDSL(pipeline.id, {
|
||||
onSuccess: (res) => {
|
||||
const blob = new Blob([res.data], { type: 'application/yaml' })
|
||||
downloadFile({
|
||||
|
|
@ -127,7 +124,7 @@ const TemplateCard = ({
|
|||
setShowConfirmDelete(false)
|
||||
}, [])
|
||||
|
||||
const { mutateAsync: deletePipeline } = useDeletePipeline()
|
||||
const { mutateAsync: deletePipeline } = useDeleteTemplate()
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
await deletePipeline(pipeline.id, {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { ArrowDownIcon } from '@heroicons/react/24/outline'
|
||||
import { pick, uniq } from 'lodash-es'
|
||||
import {
|
||||
RiArchive2Line,
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
RiEqualizer2Line,
|
||||
RiLoopLeftLine,
|
||||
RiMoreFill,
|
||||
} from '@remixicon/react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { RiEditLine } from '@remixicon/react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import dayjs from 'dayjs'
|
||||
|
|
@ -23,359 +15,25 @@ import s from './style.module.css'
|
|||
import RenameModal from './rename-modal'
|
||||
import BatchAction from './detail/completed/common/batch-action'
|
||||
import cn from '@/utils/classnames'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Popover from '@/app/components/base/popover'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Toast, { ToastContext } from '@/app/components/base/toast'
|
||||
import type { ColorMap, IndicatorProps } from '@/app/components/header/indicator'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import NotionIcon from '@/app/components/base/notion-icon'
|
||||
import ProgressBar from '@/app/components/base/progress-bar'
|
||||
import { ChunkingMode, DataSourceType, DocumentActionType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets'
|
||||
import { ChunkingMode, DataSourceType, DocumentActionType, type SimpleDocumentDetail } from '@/models/datasets'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useDatasetDetailContextWithSelector as useDatasetDetailContext } from '@/context/dataset-detail'
|
||||
import type { Props as PaginationProps } from '@/app/components/base/pagination'
|
||||
import Pagination from '@/app/components/base/pagination'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import { useDocumentArchive, useDocumentDelete, useDocumentDisable, useDocumentEnable, useDocumentUnArchive, useSyncDocument, useSyncWebsite } from '@/service/knowledge/use-document'
|
||||
import { useDocumentArchive, useDocumentDelete, useDocumentDisable, useDocumentEnable } from '@/service/knowledge/use-document'
|
||||
import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type'
|
||||
import useBatchEditDocumentMetadata from '../metadata/hooks/use-batch-edit-document-metadata'
|
||||
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
export const useIndexStatus = () => {
|
||||
const { t } = useTranslation()
|
||||
return {
|
||||
queuing: { color: 'orange', text: t('datasetDocuments.list.status.queuing') }, // waiting
|
||||
indexing: { color: 'blue', text: t('datasetDocuments.list.status.indexing') }, // indexing splitting parsing cleaning
|
||||
paused: { color: 'orange', text: t('datasetDocuments.list.status.paused') }, // paused
|
||||
error: { color: 'red', text: t('datasetDocuments.list.status.error') }, // error
|
||||
available: { color: 'green', text: t('datasetDocuments.list.status.available') }, // completed,archived = false,enabled = true
|
||||
enabled: { color: 'green', text: t('datasetDocuments.list.status.enabled') }, // completed,archived = false,enabled = true
|
||||
disabled: { color: 'gray', text: t('datasetDocuments.list.status.disabled') }, // completed,archived = false,enabled = false
|
||||
archived: { color: 'gray', text: t('datasetDocuments.list.status.archived') }, // completed,archived = true
|
||||
}
|
||||
}
|
||||
|
||||
const STATUS_TEXT_COLOR_MAP: ColorMap = {
|
||||
green: 'text-util-colors-green-green-600',
|
||||
orange: 'text-util-colors-warning-warning-600',
|
||||
red: 'text-util-colors-red-red-600',
|
||||
blue: 'text-util-colors-blue-light-blue-light-600',
|
||||
yellow: 'text-util-colors-warning-warning-600',
|
||||
gray: 'text-text-tertiary',
|
||||
}
|
||||
|
||||
// status item for list
|
||||
export const StatusItem: FC<{
|
||||
status: DocumentDisplayStatus
|
||||
reverse?: boolean
|
||||
scene?: 'list' | 'detail'
|
||||
textCls?: string
|
||||
errorMessage?: string
|
||||
detail?: {
|
||||
enabled: boolean
|
||||
archived: boolean
|
||||
id: string
|
||||
}
|
||||
datasetId?: string
|
||||
onUpdate?: (operationName?: string) => void
|
||||
|
||||
}> = ({ status, reverse = false, scene = 'list', textCls = '', errorMessage, datasetId = '', detail, onUpdate }) => {
|
||||
const DOC_INDEX_STATUS_MAP = useIndexStatus()
|
||||
const localStatus = status.toLowerCase() as keyof typeof DOC_INDEX_STATUS_MAP
|
||||
const { enabled = false, archived = false, id = '' } = detail || {}
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync: enableDocument } = useDocumentEnable()
|
||||
const { mutateAsync: disableDocument } = useDocumentDisable()
|
||||
const { mutateAsync: deleteDocument } = useDocumentDelete()
|
||||
|
||||
const onOperate = async (operationName: OperationName) => {
|
||||
let opApi = deleteDocument
|
||||
switch (operationName) {
|
||||
case 'enable':
|
||||
opApi = enableDocument
|
||||
break
|
||||
case 'disable':
|
||||
opApi = disableDocument
|
||||
break
|
||||
}
|
||||
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId, documentId: id }) as Promise<CommonResponse>)
|
||||
if (!e) {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
onUpdate?.()
|
||||
// onUpdate?.(operationName)
|
||||
}
|
||||
else { notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) }
|
||||
}
|
||||
|
||||
const { run: handleSwitch } = useDebounceFn((operationName: OperationName) => {
|
||||
if (operationName === 'enable' && enabled)
|
||||
return
|
||||
if (operationName === 'disable' && !enabled)
|
||||
return
|
||||
onOperate(operationName)
|
||||
}, { wait: 500 })
|
||||
|
||||
const embedding = useMemo(() => {
|
||||
return ['queuing', 'indexing', 'paused'].includes(localStatus)
|
||||
}, [localStatus])
|
||||
|
||||
return <div className={
|
||||
cn('flex items-center',
|
||||
reverse ? 'flex-row-reverse' : '',
|
||||
scene === 'detail' ? s.statusItemDetail : '')
|
||||
}>
|
||||
<Indicator color={DOC_INDEX_STATUS_MAP[localStatus]?.color as IndicatorProps['color']} className={reverse ? 'ml-2' : 'mr-2'} />
|
||||
<span className={cn(`${STATUS_TEXT_COLOR_MAP[DOC_INDEX_STATUS_MAP[localStatus].color as keyof typeof STATUS_TEXT_COLOR_MAP]} text-sm`, textCls)}>
|
||||
{DOC_INDEX_STATUS_MAP[localStatus]?.text}
|
||||
</span>
|
||||
{
|
||||
errorMessage && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='max-w-[260px] break-all'>{errorMessage}</div>
|
||||
}
|
||||
triggerClassName='ml-1 w-4 h-4'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
scene === 'detail' && (
|
||||
<div className='ml-1.5 flex items-center justify-between'>
|
||||
<Tooltip
|
||||
popupContent={t('datasetDocuments.list.action.enableWarning')}
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
needsDelay
|
||||
disabled={!archived}
|
||||
>
|
||||
<Switch
|
||||
defaultValue={archived ? false : enabled}
|
||||
onChange={v => !archived && handleSwitch(v ? 'enable' : 'disable')}
|
||||
disabled={embedding || archived}
|
||||
size='md'
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive'
|
||||
|
||||
// operation action for list and detail
|
||||
export const OperationAction: FC<{
|
||||
embeddingAvailable: boolean
|
||||
detail: {
|
||||
name: string
|
||||
enabled: boolean
|
||||
archived: boolean
|
||||
id: string
|
||||
data_source_type: string
|
||||
doc_form: string
|
||||
}
|
||||
datasetId: string
|
||||
onUpdate: (operationName?: string) => void
|
||||
scene?: 'list' | 'detail'
|
||||
className?: string
|
||||
}> = ({ embeddingAvailable, datasetId, detail, onUpdate, scene = 'list', className = '' }) => {
|
||||
const { id, enabled = false, archived = false, data_source_type } = detail || {}
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { mutateAsync: archiveDocument } = useDocumentArchive()
|
||||
const { mutateAsync: unArchiveDocument } = useDocumentUnArchive()
|
||||
const { mutateAsync: enableDocument } = useDocumentEnable()
|
||||
const { mutateAsync: disableDocument } = useDocumentDisable()
|
||||
const { mutateAsync: deleteDocument } = useDocumentDelete()
|
||||
const { mutateAsync: syncDocument } = useSyncDocument()
|
||||
const { mutateAsync: syncWebsite } = useSyncWebsite()
|
||||
const isListScene = scene === 'list'
|
||||
|
||||
const onOperate = async (operationName: OperationName) => {
|
||||
let opApi
|
||||
switch (operationName) {
|
||||
case 'archive':
|
||||
opApi = archiveDocument
|
||||
break
|
||||
case 'un_archive':
|
||||
opApi = unArchiveDocument
|
||||
break
|
||||
case 'enable':
|
||||
opApi = enableDocument
|
||||
break
|
||||
case 'disable':
|
||||
opApi = disableDocument
|
||||
break
|
||||
case 'sync':
|
||||
if (data_source_type === 'notion_import')
|
||||
opApi = syncDocument
|
||||
else
|
||||
opApi = syncWebsite
|
||||
break
|
||||
default:
|
||||
opApi = deleteDocument
|
||||
setDeleting(true)
|
||||
break
|
||||
}
|
||||
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId, documentId: id }) as Promise<CommonResponse>)
|
||||
if (!e) {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
onUpdate(operationName)
|
||||
}
|
||||
else { notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) }
|
||||
if (operationName === 'delete')
|
||||
setDeleting(false)
|
||||
}
|
||||
|
||||
const { run: handleSwitch } = useDebounceFn((operationName: OperationName) => {
|
||||
if (operationName === 'enable' && enabled)
|
||||
return
|
||||
if (operationName === 'disable' && !enabled)
|
||||
return
|
||||
onOperate(operationName)
|
||||
}, { wait: 500 })
|
||||
|
||||
const [currDocument, setCurrDocument] = useState<{
|
||||
id: string
|
||||
name: string
|
||||
} | null>(null)
|
||||
const [isShowRenameModal, {
|
||||
setTrue: setShowRenameModalTrue,
|
||||
setFalse: setShowRenameModalFalse,
|
||||
}] = useBoolean(false)
|
||||
const handleShowRenameModal = useCallback((doc: {
|
||||
id: string
|
||||
name: string
|
||||
}) => {
|
||||
setCurrDocument(doc)
|
||||
setShowRenameModalTrue()
|
||||
}, [setShowRenameModalTrue])
|
||||
const handleRenamed = useCallback(() => {
|
||||
onUpdate()
|
||||
}, [onUpdate])
|
||||
|
||||
return <div className='flex items-center' onClick={e => e.stopPropagation()}>
|
||||
{isListScene && !embeddingAvailable && (
|
||||
<Switch defaultValue={false} onChange={noop} disabled={true} size='md' />
|
||||
)}
|
||||
{isListScene && embeddingAvailable && (
|
||||
<>
|
||||
{archived
|
||||
? <Tooltip
|
||||
popupContent={t('datasetDocuments.list.action.enableWarning')}
|
||||
popupClassName='!font-semibold'
|
||||
needsDelay
|
||||
>
|
||||
<div>
|
||||
<Switch defaultValue={false} onChange={noop} disabled={true} size='md' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
: <Switch defaultValue={enabled} onChange={v => handleSwitch(v ? 'enable' : 'disable')} size='md' />
|
||||
}
|
||||
<Divider className='!ml-4 !mr-2 !h-3' type='vertical' />
|
||||
</>
|
||||
)}
|
||||
{embeddingAvailable && (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent={t('datasetDocuments.list.action.settings')}
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
>
|
||||
<button
|
||||
className={cn('mr-2 cursor-pointer rounded-lg',
|
||||
!isListScene
|
||||
? 'border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg p-2 shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px] hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover'
|
||||
: 'p-0.5 hover:bg-state-base-hover')}
|
||||
onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-components-button-secondary-text' />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
htmlContent={
|
||||
<div className='w-full py-1'>
|
||||
{!archived && (
|
||||
<>
|
||||
<div className={s.actionItem} onClick={() => {
|
||||
handleShowRenameModal({
|
||||
id: detail.id,
|
||||
name: detail.name,
|
||||
})
|
||||
}}>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.table.rename')}</span>
|
||||
</div>
|
||||
{['notion_import', DataSourceType.WEB].includes(data_source_type) && (
|
||||
<div className={s.actionItem} onClick={() => onOperate('sync')}>
|
||||
<RiLoopLeftLine className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.sync')}</span>
|
||||
</div>
|
||||
)}
|
||||
<Divider className='my-1' />
|
||||
</>
|
||||
)}
|
||||
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
|
||||
<RiArchive2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
|
||||
</div>}
|
||||
{archived && (
|
||||
<div className={s.actionItem} onClick={() => onOperate('un_archive')}>
|
||||
<RiArchive2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.unarchive')}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={cn(s.actionItem, s.deleteActionItem, 'group')} onClick={() => setShowModal(true)}>
|
||||
<RiDeleteBinLine className={'h-4 w-4 text-text-tertiary group-hover:text-text-destructive'} />
|
||||
<span className={cn(s.actionName, 'group-hover:text-text-destructive')}>{t('datasetDocuments.list.action.delete')}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
trigger='click'
|
||||
position='br'
|
||||
btnElement={
|
||||
<div className={cn(s.commonIcon)}>
|
||||
<RiMoreFill className='h-4 w-4 text-components-button-secondary-text' />
|
||||
</div>
|
||||
}
|
||||
btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!hover:bg-state-base-hover !shadow-none' : '!bg-transparent')}
|
||||
popupClassName='!w-full'
|
||||
className={`!z-20 flex h-fit !w-[200px] justify-end ${className}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showModal
|
||||
&& <Confirm
|
||||
isShow={showModal}
|
||||
isLoading={deleting}
|
||||
isDisabled={deleting}
|
||||
title={t('datasetDocuments.list.delete.title')}
|
||||
content={t('datasetDocuments.list.delete.content')}
|
||||
confirmText={t('common.operation.sure')}
|
||||
onConfirm={() => onOperate('delete')}
|
||||
onCancel={() => setShowModal(false)}
|
||||
/>
|
||||
}
|
||||
|
||||
{isShowRenameModal && currDocument && (
|
||||
<RenameModal
|
||||
datasetId={datasetId}
|
||||
documentId={currDocument.id}
|
||||
name={currDocument.name}
|
||||
onClose={setShowRenameModalFalse}
|
||||
onSaved={handleRenamed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
import StatusItem from './status-item'
|
||||
import Operations from './operations'
|
||||
|
||||
export const renderTdValue = (value: string | number | null, isEmptyStyle = false) => {
|
||||
return (
|
||||
|
|
@ -575,7 +233,6 @@ const DocumentList: FC<IDocumentListProps> = ({
|
|||
)
|
||||
}}
|
||||
/>
|
||||
{/* {doc.position} */}
|
||||
{index + 1}
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -623,7 +280,7 @@ const DocumentList: FC<IDocumentListProps> = ({
|
|||
}
|
||||
</td>
|
||||
<td>
|
||||
<OperationAction
|
||||
<Operations
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
datasetId={datasetId}
|
||||
detail={pick(doc, ['name', 'enabled', 'archived', 'id', 'data_source_type', 'doc_form'])}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
import React, { useCallback, useState } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { ToastContext } from '../../base/toast'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useDocumentArchive, useDocumentDelete, useDocumentDisable, useDocumentEnable, useDocumentUnArchive, useSyncDocument, useSyncWebsite } from '@/service/knowledge/use-document'
|
||||
import type { OperationName } from './types'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||
import Switch from '../../base/switch'
|
||||
import { noop } from 'lodash'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import Divider from '../../base/divider'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiArchive2Line, RiDeleteBinLine, RiEditLine, RiEqualizer2Line, RiLoopLeftLine, RiMoreFill } from '@remixicon/react'
|
||||
import CustomPopover from '../../base/popover'
|
||||
import s from './style.module.css'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import Confirm from '../../base/confirm'
|
||||
import RenameModal from './rename-modal'
|
||||
|
||||
type OperationsProps = {
|
||||
embeddingAvailable: boolean
|
||||
detail: {
|
||||
name: string
|
||||
enabled: boolean
|
||||
archived: boolean
|
||||
id: string
|
||||
data_source_type: string
|
||||
doc_form: string
|
||||
}
|
||||
datasetId: string
|
||||
onUpdate: (operationName?: string) => void
|
||||
scene?: 'list' | 'detail'
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Operations = ({
|
||||
embeddingAvailable,
|
||||
datasetId,
|
||||
detail,
|
||||
onUpdate,
|
||||
scene = 'list',
|
||||
className = '',
|
||||
}: OperationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { id, enabled = false, archived = false, data_source_type } = detail || {}
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { mutateAsync: archiveDocument } = useDocumentArchive()
|
||||
const { mutateAsync: unArchiveDocument } = useDocumentUnArchive()
|
||||
const { mutateAsync: enableDocument } = useDocumentEnable()
|
||||
const { mutateAsync: disableDocument } = useDocumentDisable()
|
||||
const { mutateAsync: deleteDocument } = useDocumentDelete()
|
||||
const { mutateAsync: syncDocument } = useSyncDocument()
|
||||
const { mutateAsync: syncWebsite } = useSyncWebsite()
|
||||
const isListScene = scene === 'list'
|
||||
|
||||
const onOperate = async (operationName: OperationName) => {
|
||||
let opApi
|
||||
switch (operationName) {
|
||||
case 'archive':
|
||||
opApi = archiveDocument
|
||||
break
|
||||
case 'un_archive':
|
||||
opApi = unArchiveDocument
|
||||
break
|
||||
case 'enable':
|
||||
opApi = enableDocument
|
||||
break
|
||||
case 'disable':
|
||||
opApi = disableDocument
|
||||
break
|
||||
case 'sync':
|
||||
if (data_source_type === 'notion_import')
|
||||
opApi = syncDocument
|
||||
else
|
||||
opApi = syncWebsite
|
||||
break
|
||||
default:
|
||||
opApi = deleteDocument
|
||||
setDeleting(true)
|
||||
break
|
||||
}
|
||||
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId, documentId: id }) as Promise<CommonResponse>)
|
||||
if (!e) {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
onUpdate(operationName)
|
||||
}
|
||||
else { notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) }
|
||||
if (operationName === 'delete')
|
||||
setDeleting(false)
|
||||
}
|
||||
|
||||
const { run: handleSwitch } = useDebounceFn((operationName: OperationName) => {
|
||||
if (operationName === 'enable' && enabled)
|
||||
return
|
||||
if (operationName === 'disable' && !enabled)
|
||||
return
|
||||
onOperate(operationName)
|
||||
}, { wait: 500 })
|
||||
|
||||
const [currDocument, setCurrDocument] = useState<{
|
||||
id: string
|
||||
name: string
|
||||
} | null>(null)
|
||||
const [isShowRenameModal, {
|
||||
setTrue: setShowRenameModalTrue,
|
||||
setFalse: setShowRenameModalFalse,
|
||||
}] = useBoolean(false)
|
||||
const handleShowRenameModal = useCallback((doc: {
|
||||
id: string
|
||||
name: string
|
||||
}) => {
|
||||
setCurrDocument(doc)
|
||||
setShowRenameModalTrue()
|
||||
}, [setShowRenameModalTrue])
|
||||
const handleRenamed = useCallback(() => {
|
||||
onUpdate()
|
||||
}, [onUpdate])
|
||||
|
||||
return <div className='flex items-center' onClick={e => e.stopPropagation()}>
|
||||
{isListScene && !embeddingAvailable && (
|
||||
<Switch defaultValue={false} onChange={noop} disabled={true} size='md' />
|
||||
)}
|
||||
{isListScene && embeddingAvailable && (
|
||||
<>
|
||||
{archived
|
||||
? <Tooltip
|
||||
popupContent={t('datasetDocuments.list.action.enableWarning')}
|
||||
popupClassName='!font-semibold'
|
||||
needsDelay
|
||||
>
|
||||
<div>
|
||||
<Switch defaultValue={false} onChange={noop} disabled={true} size='md' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
: <Switch defaultValue={enabled} onChange={v => handleSwitch(v ? 'enable' : 'disable')} size='md' />
|
||||
}
|
||||
<Divider className='!ml-4 !mr-2 !h-3' type='vertical' />
|
||||
</>
|
||||
)}
|
||||
{embeddingAvailable && (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent={t('datasetDocuments.list.action.settings')}
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
>
|
||||
<button
|
||||
className={cn('mr-2 cursor-pointer rounded-lg',
|
||||
!isListScene
|
||||
? 'border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg p-2 shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px] hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover'
|
||||
: 'p-0.5 hover:bg-state-base-hover')}
|
||||
onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-components-button-secondary-text' />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<CustomPopover
|
||||
htmlContent={
|
||||
<div className='w-full py-1'>
|
||||
{!archived && (
|
||||
<>
|
||||
<div className={s.actionItem} onClick={() => {
|
||||
handleShowRenameModal({
|
||||
id: detail.id,
|
||||
name: detail.name,
|
||||
})
|
||||
}}>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.table.rename')}</span>
|
||||
</div>
|
||||
{['notion_import', DataSourceType.WEB].includes(data_source_type) && (
|
||||
<div className={s.actionItem} onClick={() => onOperate('sync')}>
|
||||
<RiLoopLeftLine className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.sync')}</span>
|
||||
</div>
|
||||
)}
|
||||
<Divider className='my-1' />
|
||||
</>
|
||||
)}
|
||||
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
|
||||
<RiArchive2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
|
||||
</div>}
|
||||
{archived && (
|
||||
<div className={s.actionItem} onClick={() => onOperate('un_archive')}>
|
||||
<RiArchive2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.unarchive')}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={cn(s.actionItem, s.deleteActionItem, 'group')} onClick={() => setShowModal(true)}>
|
||||
<RiDeleteBinLine className={'h-4 w-4 text-text-tertiary group-hover:text-text-destructive'} />
|
||||
<span className={cn(s.actionName, 'group-hover:text-text-destructive')}>{t('datasetDocuments.list.action.delete')}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
trigger='click'
|
||||
position='br'
|
||||
btnElement={
|
||||
<div className={cn(s.commonIcon)}>
|
||||
<RiMoreFill className='h-4 w-4 text-components-button-secondary-text' />
|
||||
</div>
|
||||
}
|
||||
btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!hover:bg-state-base-hover !shadow-none' : '!bg-transparent')}
|
||||
popupClassName='!w-full'
|
||||
className={`!z-20 flex h-fit !w-[200px] justify-end ${className}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showModal
|
||||
&& <Confirm
|
||||
isShow={showModal}
|
||||
isLoading={deleting}
|
||||
isDisabled={deleting}
|
||||
title={t('datasetDocuments.list.delete.title')}
|
||||
content={t('datasetDocuments.list.delete.content')}
|
||||
confirmText={t('common.operation.sure')}
|
||||
onConfirm={() => onOperate('delete')}
|
||||
onCancel={() => setShowModal(false)}
|
||||
/>
|
||||
}
|
||||
|
||||
{isShowRenameModal && currDocument && (
|
||||
<RenameModal
|
||||
datasetId={datasetId}
|
||||
documentId={currDocument.id}
|
||||
name={currDocument.name}
|
||||
onClose={setShowRenameModalFalse}
|
||||
onSaved={handleRenamed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default React.memo(Operations)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const useIndexStatus = () => {
|
||||
const { t } = useTranslation()
|
||||
return {
|
||||
queuing: { color: 'orange', text: t('datasetDocuments.list.status.queuing') }, // waiting
|
||||
indexing: { color: 'blue', text: t('datasetDocuments.list.status.indexing') }, // indexing splitting parsing cleaning
|
||||
paused: { color: 'orange', text: t('datasetDocuments.list.status.paused') }, // paused
|
||||
error: { color: 'red', text: t('datasetDocuments.list.status.error') }, // error
|
||||
available: { color: 'green', text: t('datasetDocuments.list.status.available') }, // completed,archived = false,enabled = true
|
||||
enabled: { color: 'green', text: t('datasetDocuments.list.status.enabled') }, // completed,archived = false,enabled = true
|
||||
disabled: { color: 'gray', text: t('datasetDocuments.list.status.disabled') }, // completed,archived = false,enabled = false
|
||||
archived: { color: 'gray', text: t('datasetDocuments.list.status.archived') }, // completed,archived = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import type { ColorMap, IndicatorProps } from '@/app/components/header/indicator'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import type { DocumentDisplayStatus } from '@/models/datasets'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useIndexStatus } from './hooks'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDocumentDelete, useDocumentDisable, useDocumentEnable } from '@/service/knowledge/use-document'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import s from '../style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import type { OperationName } from '../types'
|
||||
|
||||
const STATUS_TEXT_COLOR_MAP: ColorMap = {
|
||||
green: 'text-util-colors-green-green-600',
|
||||
orange: 'text-util-colors-warning-warning-600',
|
||||
red: 'text-util-colors-red-red-600',
|
||||
blue: 'text-util-colors-blue-light-blue-light-600',
|
||||
yellow: 'text-util-colors-warning-warning-600',
|
||||
gray: 'text-text-tertiary',
|
||||
}
|
||||
|
||||
type StatusItemProps = {
|
||||
status: DocumentDisplayStatus
|
||||
reverse?: boolean
|
||||
scene?: 'list' | 'detail'
|
||||
textCls?: string
|
||||
errorMessage?: string
|
||||
detail?: {
|
||||
enabled: boolean
|
||||
archived: boolean
|
||||
id: string
|
||||
}
|
||||
datasetId?: string
|
||||
onUpdate?: (operationName?: string) => void
|
||||
}
|
||||
|
||||
const StatusItem = ({
|
||||
status,
|
||||
reverse = false,
|
||||
scene = 'list',
|
||||
textCls = '',
|
||||
errorMessage,
|
||||
datasetId = '',
|
||||
detail,
|
||||
onUpdate,
|
||||
}: StatusItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const DOC_INDEX_STATUS_MAP = useIndexStatus()
|
||||
const localStatus = status.toLowerCase() as keyof typeof DOC_INDEX_STATUS_MAP
|
||||
const { enabled = false, archived = false, id = '' } = detail || {}
|
||||
const { mutateAsync: enableDocument } = useDocumentEnable()
|
||||
const { mutateAsync: disableDocument } = useDocumentDisable()
|
||||
const { mutateAsync: deleteDocument } = useDocumentDelete()
|
||||
|
||||
const onOperate = async (operationName: OperationName) => {
|
||||
let opApi = deleteDocument
|
||||
switch (operationName) {
|
||||
case 'enable':
|
||||
opApi = enableDocument
|
||||
break
|
||||
case 'disable':
|
||||
opApi = disableDocument
|
||||
break
|
||||
}
|
||||
const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId, documentId: id }) as Promise<CommonResponse>)
|
||||
if (!e) {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
onUpdate?.()
|
||||
}
|
||||
else { notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) }
|
||||
}
|
||||
|
||||
const { run: handleSwitch } = useDebounceFn((operationName: OperationName) => {
|
||||
if (operationName === 'enable' && enabled)
|
||||
return
|
||||
if (operationName === 'disable' && !enabled)
|
||||
return
|
||||
onOperate(operationName)
|
||||
}, { wait: 500 })
|
||||
|
||||
const embedding = useMemo(() => {
|
||||
return ['queuing', 'indexing', 'paused'].includes(localStatus)
|
||||
}, [localStatus])
|
||||
|
||||
return <div className={
|
||||
cn('flex items-center',
|
||||
reverse ? 'flex-row-reverse' : '',
|
||||
scene === 'detail' ? s.statusItemDetail : '')
|
||||
}>
|
||||
<Indicator color={DOC_INDEX_STATUS_MAP[localStatus]?.color as IndicatorProps['color']} className={reverse ? 'ml-2' : 'mr-2'} />
|
||||
<span className={cn(`${STATUS_TEXT_COLOR_MAP[DOC_INDEX_STATUS_MAP[localStatus].color as keyof typeof STATUS_TEXT_COLOR_MAP]} text-sm`, textCls)}>
|
||||
{DOC_INDEX_STATUS_MAP[localStatus]?.text}
|
||||
</span>
|
||||
{
|
||||
errorMessage && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='max-w-[260px] break-all'>{errorMessage}</div>
|
||||
}
|
||||
triggerClassName='ml-1 w-4 h-4'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
scene === 'detail' && (
|
||||
<div className='ml-1.5 flex items-center justify-between'>
|
||||
<Tooltip
|
||||
popupContent={t('datasetDocuments.list.action.enableWarning')}
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
needsDelay
|
||||
disabled={!archived}
|
||||
>
|
||||
<Switch
|
||||
defaultValue={archived ? false : enabled}
|
||||
onChange={v => !archived && handleSwitch(v ? 'enable' : 'disable')}
|
||||
disabled={embedding || archived}
|
||||
size='md'
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default React.memo(StatusItem)
|
||||
|
|
@ -0,0 +1 @@
|
|||
export type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive'
|
||||
|
|
@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import type { DataSet } from '@/models/datasets'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { useKnowledge } from '@/hooks/use-knowledge'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { Tag } from '@/app/components/base/tag-management/constant'
|
||||
import TagSelector from '@/app/components/base/tag-management/selector'
|
||||
import cn from '@/utils/classnames'
|
||||
|
|
@ -49,7 +49,8 @@ const DatasetCard = ({
|
|||
const isExternalProvider = useMemo(() => {
|
||||
return dataset.provider === EXTERNAL_PROVIDER
|
||||
}, [dataset.provider])
|
||||
const Icon = isExternalProvider ? DOC_FORM_ICON_WITH_BG.external : DOC_FORM_ICON_WITH_BG[dataset.doc_form]
|
||||
const chunkingModeIcon = dataset.doc_form ? DOC_FORM_ICON_WITH_BG[dataset.doc_form] : React.Fragment
|
||||
const Icon = isExternalProvider ? DOC_FORM_ICON_WITH_BG.external : chunkingModeIcon
|
||||
const iconInfo = dataset.icon_info || {
|
||||
icon: '📙',
|
||||
icon_type: 'emoji',
|
||||
|
|
@ -133,9 +134,11 @@ const DatasetCard = ({
|
|||
background={iconInfo.icon_type === 'image' ? undefined : iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_type === 'image' ? iconInfo.icon_url : undefined}
|
||||
/>
|
||||
<div className='absolute -bottom-1 -right-1 z-[5]'>
|
||||
<Icon className='size-4' />
|
||||
</div>
|
||||
{(dataset.doc_form || isExternalProvider) && (
|
||||
<div className='absolute -bottom-1 -right-1 z-[5]'>
|
||||
<Icon className='size-4' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex grow flex-col gap-y-1 overflow-hidden py-px'>
|
||||
<div
|
||||
|
|
@ -145,13 +148,12 @@ const DatasetCard = ({
|
|||
{dataset.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase flex items-center gap-x-3 text-text-tertiary'>
|
||||
{!isExternalProvider ? (
|
||||
{isExternalProvider && <span>{t('dataset.externalKnowledgeBase')}</span>}
|
||||
{!isExternalProvider && dataset.doc_form && dataset.indexing_technique && (
|
||||
<>
|
||||
<span>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</span>
|
||||
<span>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</span>
|
||||
</>
|
||||
) : (
|
||||
<span>{t('dataset.externalKnowledgeBase')}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const Datasets = ({
|
|||
} = useDatasetList({
|
||||
initialPage: 1,
|
||||
tag_ids: tags,
|
||||
limit: 20,
|
||||
limit: 30,
|
||||
include_all: includeAll,
|
||||
keyword: keywords,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ import type { MetadataFilteringVariableType } from '@/app/components/workflow/no
|
|||
import type { MetadataItemWithValue } from '@/app/components/datasets/metadata/types'
|
||||
import { ExternalKnowledgeBase, General, Graph, ParentChild, Qa } from '@/app/components/base/icons/src/public/knowledge/dataset-card'
|
||||
import { GeneralChunk, ParentChildChunk, QuestionAndAnswer } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import type { Edge, EnvironmentVariable, Node } from '@/app/components/workflow/types'
|
||||
import type { Viewport } from 'reactflow'
|
||||
import type { RAGPipelineVariables } from './pipeline'
|
||||
|
||||
export enum DataSourceType {
|
||||
FILE = 'upload_file',
|
||||
|
|
@ -761,14 +758,5 @@ export type CreateDatasetResponse = {
|
|||
created_at: number
|
||||
updated_by: string
|
||||
updated_at: number
|
||||
pipeline_info: {
|
||||
id: string
|
||||
graph: {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
viewport?: Viewport
|
||||
}
|
||||
environment_variables: EnvironmentVariable[]
|
||||
rag_pipeline_variables: RAGPipelineVariables
|
||||
}
|
||||
pipeline_id: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ export type CreateFormData = {
|
|||
selectedMemberIDs: string[]
|
||||
}
|
||||
|
||||
export type UpdatePipelineInfoRequest = {
|
||||
pipeline_id: string
|
||||
export type UpdateTemplateInfoRequest = {
|
||||
template_id: string
|
||||
name: string
|
||||
icon_info: IconInfo
|
||||
description: string
|
||||
}
|
||||
|
||||
export type UpdatePipelineInfoResponse = {
|
||||
export type UpdateTemplateInfoResponse = {
|
||||
pipeline_id: string
|
||||
name: string
|
||||
icon_info: IconInfo
|
||||
|
|
@ -59,16 +59,11 @@ export type UpdatePipelineInfoResponse = {
|
|||
position: number
|
||||
}
|
||||
|
||||
export type DeletePipelineResponse = {
|
||||
export type DeleteTemplateResponse = {
|
||||
code: number
|
||||
}
|
||||
|
||||
export type ExportPipelineDSLRequest = {
|
||||
pipeline_id: string
|
||||
include_secret?: boolean
|
||||
}
|
||||
|
||||
export type ExportPipelineDSLResponse = {
|
||||
export type ExportTemplateDSLResponse = {
|
||||
data: string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import type { MutationOptions } from '@tanstack/react-query'
|
|||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { del, get, patch, post } from './base'
|
||||
import type {
|
||||
DeletePipelineResponse,
|
||||
ExportPipelineDSLRequest,
|
||||
ExportPipelineDSLResponse,
|
||||
DeleteTemplateResponse,
|
||||
ExportTemplateDSLResponse,
|
||||
ImportPipelineDSLConfirmResponse,
|
||||
ImportPipelineDSLRequest,
|
||||
ImportPipelineDSLResponse,
|
||||
|
|
@ -13,8 +12,8 @@ import type {
|
|||
PipelineTemplateByIdResponse,
|
||||
PipelineTemplateListParams,
|
||||
PipelineTemplateListResponse,
|
||||
UpdatePipelineInfoRequest,
|
||||
UpdatePipelineInfoResponse,
|
||||
UpdateTemplateInfoRequest,
|
||||
UpdateTemplateInfoResponse,
|
||||
} from '@/models/pipeline'
|
||||
|
||||
const NAME_SPACE = 'pipeline'
|
||||
|
|
@ -23,7 +22,7 @@ export const usePipelineTemplateList = (params: PipelineTemplateListParams) => {
|
|||
return useQuery<PipelineTemplateListResponse>({
|
||||
queryKey: [NAME_SPACE, 'template', 'list'],
|
||||
queryFn: () => {
|
||||
return get<PipelineTemplateListResponse>('/rag/pipeline/template', { params })
|
||||
return get<PipelineTemplateListResponse>('/rag/pipeline/templates', { params })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -32,20 +31,20 @@ export const usePipelineTemplateById = (templateId: string, enabled: boolean) =>
|
|||
return useQuery<PipelineTemplateByIdResponse>({
|
||||
queryKey: [NAME_SPACE, 'template', templateId],
|
||||
queryFn: () => {
|
||||
return get<PipelineTemplateByIdResponse>(`/rag/pipeline/template/${templateId}`)
|
||||
return get<PipelineTemplateByIdResponse>(`/rag/pipeline/templates/${templateId}`)
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdatePipelineInfo = (
|
||||
mutationOptions: MutationOptions<UpdatePipelineInfoResponse, Error, UpdatePipelineInfoRequest> = {},
|
||||
export const useUpdateTemplateInfo = (
|
||||
mutationOptions: MutationOptions<UpdateTemplateInfoResponse, Error, UpdateTemplateInfoRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'template', 'update'],
|
||||
mutationFn: (request: UpdatePipelineInfoRequest) => {
|
||||
const { pipeline_id, ...rest } = request
|
||||
return patch<UpdatePipelineInfoResponse>(`/rag/pipeline/${pipeline_id}`, {
|
||||
mutationFn: (request: UpdateTemplateInfoRequest) => {
|
||||
const { template_id, ...rest } = request
|
||||
return patch<UpdateTemplateInfoResponse>(`/rag/customized/templates/${template_id}`, {
|
||||
body: rest,
|
||||
})
|
||||
},
|
||||
|
|
@ -53,29 +52,25 @@ export const useUpdatePipelineInfo = (
|
|||
})
|
||||
}
|
||||
|
||||
export const useDeletePipeline = (
|
||||
mutationOptions: MutationOptions<DeletePipelineResponse, Error, string> = {},
|
||||
export const useDeleteTemplate = (
|
||||
mutationOptions: MutationOptions<DeleteTemplateResponse, Error, string> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'template', 'delete'],
|
||||
mutationFn: (pipelineId: string) => {
|
||||
return del<DeletePipelineResponse>(`/rag/pipeline/${pipelineId}`)
|
||||
mutationFn: (templateId: string) => {
|
||||
return del<DeleteTemplateResponse>(`/rag/customized/templates/${templateId}`)
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useExportPipelineDSL = (
|
||||
mutationOptions: MutationOptions<ExportPipelineDSLResponse, Error, ExportPipelineDSLRequest> = {},
|
||||
export const useExportTemplateDSL = (
|
||||
mutationOptions: MutationOptions<ExportTemplateDSLResponse, Error, ExportTemplateDSLRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'dsl-export'],
|
||||
mutationFn: (request: ExportPipelineDSLRequest) => {
|
||||
return get<ExportPipelineDSLResponse>(`/rag/pipeline/${request.pipeline_id}/export`, {
|
||||
params: {
|
||||
include_secret: !!request.include_secret,
|
||||
},
|
||||
})
|
||||
mutationFn: (templateId: string) => {
|
||||
return get<ExportTemplateDSLResponse>(`/rag/customized/templates/${templateId}`)
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue