From 35a7add4e930ba1062b4fd348d597ddc7c41a3d6 Mon Sep 17 00:00:00 2001 From: twwu Date: Fri, 16 May 2025 15:14:50 +0800 Subject: [PATCH] refactor: refactor pipeline-related components and services to use template terminology --- .../(appDetailLayout)/[appId]/layout-main.tsx | 4 - .../[datasetId]/layout-main.tsx | 5 - .../components/app-sidebar/dataset-info.tsx | 57 +-- web/app/components/app-sidebar/index.tsx | 24 +- .../create-from-scratch-modal.tsx | 10 +- .../list/built-in-pipeline-list.tsx | 50 +-- .../list/customized-list.tsx | 50 +-- .../list/template-card/edit-pipeline-info.tsx | 6 +- .../list/template-card/index.tsx | 19 +- .../components/datasets/documents/list.tsx | 359 +----------------- .../datasets/documents/operations.tsx | 238 ++++++++++++ .../datasets/documents/status-item/hooks.ts | 15 + .../datasets/documents/status-item/index.tsx | 133 +++++++ .../components/datasets/documents/types.ts | 1 + .../datasets/list/dataset-card/index.tsx | 18 +- web/app/components/datasets/list/datasets.tsx | 2 +- web/models/datasets.ts | 14 +- web/models/pipeline.ts | 15 +- web/service/use-pipeline.ts | 43 +-- 19 files changed, 483 insertions(+), 580 deletions(-) create mode 100644 web/app/components/datasets/documents/operations.tsx create mode 100644 web/app/components/datasets/documents/status-item/hooks.ts create mode 100644 web/app/components/datasets/documents/status-item/index.tsx create mode 100644 web/app/components/datasets/documents/types.ts diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index fbd332c69d..690c23780c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -167,10 +167,6 @@ const AppDetailLayout: FC = (props) => {
{appDetail && ( )} diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 2f69fb48a5..059dc2ddac 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -173,11 +173,6 @@ const DatasetDetailLayout: FC = (props) => { }}> {!hideSideBar && ( = ({ 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 (
@@ -46,32 +47,34 @@ const DatasetInfo: FC = ({ background={iconInfo.icon_background} imageUrl={iconInfo.icon_url} /> -
- -
-
- <> -
-
- {dataset.name} -
-
- {isExternal && t('dataset.externalTag')} - {!isExternal && ( -
- {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)} - {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)} -
- )} -
+ {(dataset.doc_form || isExternalProvider) && ( +
+
-

- {dataset.description} -

- + )} +
+ <> +
+
+ {dataset.name} +
+
+ {isExternalProvider && t('dataset.externalTag')} + {!isExternalProvider && dataset.doc_form && dataset.indexing_technique && ( +
+ {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)} + {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)} +
+ )} +
+
+

+ {dataset.description} +

+
)} diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index ab4ba32d93..54d7d5ac75 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -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' && ( )} - {iconType === 'dataset' && ( + {iconType !== 'app' && ( )} - {!['app', 'dataset'].includes(iconType) && ( - - )}
diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-scratch-modal.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-scratch-modal.tsx index 0dca658639..a871b35641 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-scratch-modal.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-scratch-modal.tsx @@ -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([]) 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 ( { - // 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 diff --git a/web/app/components/datasets/create-from-pipeline/list/customized-list.tsx b/web/app/components/datasets/create-from-pipeline/list/customized-list.tsx index ece772cebb..6959b2e2cd 100644 --- a/web/app/components/datasets/create-from-pipeline/list/customized-list.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/customized-list.tsx @@ -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 diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx index b58502a8f6..c149f37e31 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx @@ -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, diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx index 11d0f88e64..291422a22a 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx @@ -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, { diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index cb349ee01c..cebf89e1c5 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -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(opApi({ datasetId, documentId: id }) as Promise) - 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
- - - {DOC_INDEX_STATUS_MAP[localStatus]?.text} - - { - errorMessage && ( - {errorMessage}
- } - triggerClassName='ml-1 w-4 h-4' - /> - ) - } - { - scene === 'detail' && ( -
- - !archived && handleSwitch(v ? 'enable' : 'disable')} - disabled={embedding || archived} - size='md' - /> - -
- ) - } -
-} - -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(opApi({ datasetId, documentId: id }) as Promise) - 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
e.stopPropagation()}> - {isListScene && !embeddingAvailable && ( - - )} - {isListScene && embeddingAvailable && ( - <> - {archived - ? -
- -
-
- : handleSwitch(v ? 'enable' : 'disable')} size='md' /> - } - - - )} - {embeddingAvailable && ( - <> - - - - - {!archived && ( - <> -
{ - handleShowRenameModal({ - id: detail.id, - name: detail.name, - }) - }}> - - {t('datasetDocuments.list.table.rename')} -
- {['notion_import', DataSourceType.WEB].includes(data_source_type) && ( -
onOperate('sync')}> - - {t('datasetDocuments.list.action.sync')} -
- )} - - - )} - {!archived &&
onOperate('archive')}> - - {t('datasetDocuments.list.action.archive')} -
} - {archived && ( -
onOperate('un_archive')}> - - {t('datasetDocuments.list.action.unarchive')} -
- )} -
setShowModal(true)}> - - {t('datasetDocuments.list.action.delete')} -
-
- } - trigger='click' - position='br' - btnElement={ -
- -
- } - 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 - && onOperate('delete')} - onCancel={() => setShowModal(false)} - /> - } - - {isShowRenameModal && currDocument && ( - - )} -
-} +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 = ({ ) }} /> - {/* {doc.position} */} {index + 1} @@ -623,7 +280,7 @@ const DocumentList: FC = ({ } - 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(opApi({ datasetId, documentId: id }) as Promise) + 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
e.stopPropagation()}> + {isListScene && !embeddingAvailable && ( + + )} + {isListScene && embeddingAvailable && ( + <> + {archived + ? +
+ +
+
+ : handleSwitch(v ? 'enable' : 'disable')} size='md' /> + } + + + )} + {embeddingAvailable && ( + <> + + + + + {!archived && ( + <> +
{ + handleShowRenameModal({ + id: detail.id, + name: detail.name, + }) + }}> + + {t('datasetDocuments.list.table.rename')} +
+ {['notion_import', DataSourceType.WEB].includes(data_source_type) && ( +
onOperate('sync')}> + + {t('datasetDocuments.list.action.sync')} +
+ )} + + + )} + {!archived &&
onOperate('archive')}> + + {t('datasetDocuments.list.action.archive')} +
} + {archived && ( +
onOperate('un_archive')}> + + {t('datasetDocuments.list.action.unarchive')} +
+ )} +
setShowModal(true)}> + + {t('datasetDocuments.list.action.delete')} +
+
+ } + trigger='click' + position='br' + btnElement={ +
+ +
+ } + 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 + && onOperate('delete')} + onCancel={() => setShowModal(false)} + /> + } + + {isShowRenameModal && currDocument && ( + + )} + +} + +export default React.memo(Operations) diff --git a/web/app/components/datasets/documents/status-item/hooks.ts b/web/app/components/datasets/documents/status-item/hooks.ts new file mode 100644 index 0000000000..f3b99588df --- /dev/null +++ b/web/app/components/datasets/documents/status-item/hooks.ts @@ -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 + } +} diff --git a/web/app/components/datasets/documents/status-item/index.tsx b/web/app/components/datasets/documents/status-item/index.tsx new file mode 100644 index 0000000000..42f42a999a --- /dev/null +++ b/web/app/components/datasets/documents/status-item/index.tsx @@ -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(opApi({ datasetId, documentId: id }) as Promise) + 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
+ + + {DOC_INDEX_STATUS_MAP[localStatus]?.text} + + { + errorMessage && ( + {errorMessage}
+ } + triggerClassName='ml-1 w-4 h-4' + /> + ) + } + { + scene === 'detail' && ( +
+ + !archived && handleSwitch(v ? 'enable' : 'disable')} + disabled={embedding || archived} + size='md' + /> + +
+ ) + } + +} + +export default React.memo(StatusItem) diff --git a/web/app/components/datasets/documents/types.ts b/web/app/components/datasets/documents/types.ts new file mode 100644 index 0000000000..8934fe661f --- /dev/null +++ b/web/app/components/datasets/documents/types.ts @@ -0,0 +1 @@ +export type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' diff --git a/web/app/components/datasets/list/dataset-card/index.tsx b/web/app/components/datasets/list/dataset-card/index.tsx index d83dc5b2e1..70838fcc12 100644 --- a/web/app/components/datasets/list/dataset-card/index.tsx +++ b/web/app/components/datasets/list/dataset-card/index.tsx @@ -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} /> -
- -
+ {(dataset.doc_form || isExternalProvider) && ( +
+ +
+ )}
- {!isExternalProvider ? ( + {isExternalProvider && {t('dataset.externalKnowledgeBase')}} + {!isExternalProvider && dataset.doc_form && dataset.indexing_technique && ( <> {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)} {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)} - ) : ( - {t('dataset.externalKnowledgeBase')} )}
diff --git a/web/app/components/datasets/list/datasets.tsx b/web/app/components/datasets/list/datasets.tsx index c8a1a547a6..36137c8d31 100644 --- a/web/app/components/datasets/list/datasets.tsx +++ b/web/app/components/datasets/list/datasets.tsx @@ -29,7 +29,7 @@ const Datasets = ({ } = useDatasetList({ initialPage: 1, tag_ids: tags, - limit: 20, + limit: 30, include_all: includeAll, keyword: keywords, }) diff --git a/web/models/datasets.ts b/web/models/datasets.ts index bbe6f2dc24..990babfc3f 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -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 } diff --git a/web/models/pipeline.ts b/web/models/pipeline.ts index 3d3110cefd..b516611a42 100644 --- a/web/models/pipeline.ts +++ b/web/models/pipeline.ts @@ -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 } diff --git a/web/service/use-pipeline.ts b/web/service/use-pipeline.ts index 685e7c6fc3..f9dcac2201 100644 --- a/web/service/use-pipeline.ts +++ b/web/service/use-pipeline.ts @@ -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({ queryKey: [NAME_SPACE, 'template', 'list'], queryFn: () => { - return get('/rag/pipeline/template', { params }) + return get('/rag/pipeline/templates', { params }) }, }) } @@ -32,20 +31,20 @@ export const usePipelineTemplateById = (templateId: string, enabled: boolean) => return useQuery({ queryKey: [NAME_SPACE, 'template', templateId], queryFn: () => { - return get(`/rag/pipeline/template/${templateId}`) + return get(`/rag/pipeline/templates/${templateId}`) }, enabled, }) } -export const useUpdatePipelineInfo = ( - mutationOptions: MutationOptions = {}, +export const useUpdateTemplateInfo = ( + mutationOptions: MutationOptions = {}, ) => { return useMutation({ mutationKey: [NAME_SPACE, 'template', 'update'], - mutationFn: (request: UpdatePipelineInfoRequest) => { - const { pipeline_id, ...rest } = request - return patch(`/rag/pipeline/${pipeline_id}`, { + mutationFn: (request: UpdateTemplateInfoRequest) => { + const { template_id, ...rest } = request + return patch(`/rag/customized/templates/${template_id}`, { body: rest, }) }, @@ -53,29 +52,25 @@ export const useUpdatePipelineInfo = ( }) } -export const useDeletePipeline = ( - mutationOptions: MutationOptions = {}, +export const useDeleteTemplate = ( + mutationOptions: MutationOptions = {}, ) => { return useMutation({ mutationKey: [NAME_SPACE, 'template', 'delete'], - mutationFn: (pipelineId: string) => { - return del(`/rag/pipeline/${pipelineId}`) + mutationFn: (templateId: string) => { + return del(`/rag/customized/templates/${templateId}`) }, ...mutationOptions, }) } -export const useExportPipelineDSL = ( - mutationOptions: MutationOptions = {}, +export const useExportTemplateDSL = ( + mutationOptions: MutationOptions = {}, ) => { return useMutation({ mutationKey: [NAME_SPACE, 'dsl-export'], - mutationFn: (request: ExportPipelineDSLRequest) => { - return get(`/rag/pipeline/${request.pipeline_id}/export`, { - params: { - include_secret: !!request.include_secret, - }, - }) + mutationFn: (templateId: string) => { + return get(`/rag/customized/templates/${templateId}`) }, ...mutationOptions, })