diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx index 214836b39f..d6203195b3 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx @@ -83,6 +83,7 @@ const CreateFromDSLModal = ({ const isCreatingRef = useRef(false) + // todo: replace with pipeline import DSL and check plugin dependencies const onCreate = async () => { if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile) return diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/details.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/details.tsx new file mode 100644 index 0000000000..2cf6c5cfce --- /dev/null +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/details.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import AppIcon from '@/app/components/base/app-icon' +import { usePipelineTemplateById } from '@/service/use-pipeline' +import type { AppIconType } from '@/types/app' +import { RiAddLine, RiCloseLine } from '@remixicon/react' +import Button from '@/app/components/base/button' +import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' + +type DetailsProps = { + id: string + handleUseTemplate: () => void + onClose: () => void +} + +const Details = ({ + id, + handleUseTemplate, + onClose, +}: DetailsProps) => { + const { t } = useTranslation() + const { data: pipelineTemplateInfo } = usePipelineTemplateById(id, true) + const appIcon = React.useMemo(() => { + if (!pipelineTemplateInfo) + return { type: 'emoji', icon: '📙', background: '#FFF4ED' } + const iconInfo = pipelineTemplateInfo.icon_info + return iconInfo.icon_type === 'image' + ? { type: 'image', url: iconInfo.icon_url || '', fileId: iconInfo.icon || '' } + : { type: 'icon', icon: iconInfo.icon || '', background: iconInfo.icon_background || '' } + }, [pipelineTemplateInfo]) + + if (!pipelineTemplateInfo) { + return ( +
+
Loading...
+
+ ) + } + + return ( +
+
+ Pipeline Preview +
+
+ +
+ +
+
+ {pipelineTemplateInfo.name} +
+
+ {`By ${pipelineTemplateInfo.author}`} +
+
+
+

+ {pipelineTemplateInfo.description} +

+
+ +
+
+
+ + {t('datasetPipeline.details.structure')} + + +
+
+
+
+ ) +} + +export default React.memo(Details) 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 0152bb49c0..200143f703 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 @@ -11,9 +11,13 @@ import EditPipelineInfo from './edit-pipeline-info' import type { PipelineTemple } from '@/models/pipeline' import { DOC_FORM_ICON, DOC_FORM_TEXT } from '@/models/datasets' import Confirm from '@/app/components/base/confirm' -import { useDeletePipeline, useExportPipelineDSL } from '@/service/use-pipeline' +import { useDeletePipeline, useExportPipelineDSL, useImportPipelineDSL, usePipelineTemplateById } from '@/service/use-pipeline' import { downloadFile } from '@/utils/format' import Toast from '@/app/components/base/toast' +import { DSLImportMode } from '@/models/app' +import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { useRouter } from 'next/navigation' +import Details from './details' type TemplateCardProps = { pipeline: PipelineTemple @@ -25,8 +29,52 @@ const TemplateCard = ({ showMoreOperations = true, }: TemplateCardProps) => { const { t } = useTranslation() + const { push } = useRouter() const [showEditModal, setShowEditModal] = useState(false) const [showDeleteConfirm, setShowConfirmDelete] = useState(false) + const [showDetailModal, setShowDetailModal] = useState(false) + + const { refetch: getPipelineTemplateInfo } = usePipelineTemplateById(pipeline.id, false) + const { mutateAsync: importPipelineDSL } = useImportPipelineDSL() + const { handleCheckPluginDependencies } = usePluginDependencies() + + const handleUseTemplate = useCallback(async () => { + try { + const { data: pipelineTemplateInfo } = await getPipelineTemplateInfo() + if (!pipelineTemplateInfo) { + Toast.notify({ + type: 'error', + message: t('datasetPipeline.creation.errorTip'), + }) + return + } + const request = { + mode: DSLImportMode.YAML_CONTENT, + name: pipeline.name, + yaml_content: pipelineTemplateInfo.export_data, + icon_info: pipeline.icon_info, + description: pipeline.description, + } + const newPipeline = await importPipelineDSL(request) + Toast.notify({ + type: 'success', + message: t('app.newApp.appCreated'), + }) + if (newPipeline.dataset_id) + await handleCheckPluginDependencies(newPipeline.dataset_id) // todo: replace with pipeline dependency check + push(`dataset/${newPipeline.dataset_id}/pipeline`) + } + catch { + Toast.notify({ + type: 'error', + message: t('datasetPipeline.creation.errorTip'), + }) + } + }, [getPipelineTemplateInfo, importPipelineDSL, pipeline, t, push, handleCheckPluginDependencies]) + + const handleShowTemplateDetails = useCallback(() => { + setShowDetailModal(true) + }, []) const openEditModal = useCallback(() => { setShowEditModal(true) @@ -36,10 +84,15 @@ const TemplateCard = ({ setShowEditModal(false) }, []) - const { mutateAsync: getDSLFileContent } = useExportPipelineDSL() + const closeDetailsModal = useCallback(() => { + setShowDetailModal(false) + }, []) + + const { mutateAsync: exportPipelineDSL, isPending: isExporting } = useExportPipelineDSL() const handleExportDSL = useCallback(async () => { - await getDSLFileContent(pipeline.id, { + if (isExporting) return + await exportPipelineDSL(pipeline.id, { onSuccess: (res) => { const blob = new Blob([res.data], { type: 'application/yaml' }) downloadFile({ @@ -58,7 +111,7 @@ const TemplateCard = ({ }) }, }) - }, [t, pipeline.id, pipeline.name, getDSLFileContent]) + }, [t, isExporting, pipeline.id, pipeline.name, exportPipelineDSL]) const handleDelete = useCallback(() => { setShowConfirmDelete(true) @@ -117,9 +170,7 @@ const TemplateCard = ({
) } diff --git a/web/app/components/datasets/list/dataset-card/operations.tsx b/web/app/components/datasets/list/dataset-card/operations.tsx index c4fc78ba8f..3d24ebbac5 100644 --- a/web/app/components/datasets/list/dataset-card/operations.tsx +++ b/web/app/components/datasets/list/dataset-card/operations.tsx @@ -1,7 +1,7 @@ import Divider from '@/app/components/base/divider' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiDeleteBinLine, RiEditLine, RiFileCopyLine } from '@remixicon/react' +import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' type OperationsProps = { showDelete: boolean @@ -40,7 +40,7 @@ const Operations = ({ {t('common.operation.edit')} -
{ console.log('duplicate') }} > @@ -48,9 +48,9 @@ const Operations = ({ {t('common.operation.duplicate')} -
+ */} - + {/*
-
+ */} {showDelete && ( <> diff --git a/web/i18n/en-US/dataset-pipeline.ts b/web/i18n/en-US/dataset-pipeline.ts index 97700875b6..251608c31d 100644 --- a/web/i18n/en-US/dataset-pipeline.ts +++ b/web/i18n/en-US/dataset-pipeline.ts @@ -10,6 +10,8 @@ const translation = { description: 'Import from a DSL file', }, createKnowledge: 'Create Knowledge', + errorTip: 'Failed to create a Knowledge Pipeline', + successTip: 'Successfully created a Knowledge Pipeline', }, tabs: { builtInPipeline: 'Built-in pipeline', @@ -20,6 +22,7 @@ const translation = { details: 'Details', editInfo: 'Edit info', exportDSL: 'Export DSL', + useTemplate: 'Use this Knowledge Pipeline', }, knowledgeNameAndIcon: 'Knowledge name & icon', knowledgeNameAndIconPlaceholder: 'Please enter the name of the Knowledge Base', @@ -36,6 +39,9 @@ const translation = { successTip: 'Export pipeline DSL successfully', errorTip: 'Failed to export pipeline DSL', }, + details: { + structure: 'Structure', + }, } export default translation diff --git a/web/i18n/zh-Hans/dataset-pipeline.ts b/web/i18n/zh-Hans/dataset-pipeline.ts index ede0aeb053..4570db18c1 100644 --- a/web/i18n/zh-Hans/dataset-pipeline.ts +++ b/web/i18n/zh-Hans/dataset-pipeline.ts @@ -10,6 +10,8 @@ const translation = { description: '从 DSL 文件导入', }, createKnowledge: '创建知识库', + errorTip: '创建知识库流水线失败', + successTip: '成功创建知识库流水线', }, tabs: { builtInPipeline: '内置流水线', @@ -20,6 +22,7 @@ const translation = { details: '详情', editInfo: '编辑信息', exportDSL: '导出 DSL', + useTemplate: '使用此知识库流水线', }, knowledgeNameAndIcon: '知识库名称和图标', knowledgeNameAndIconPlaceholder: '请输入知识库名称', @@ -36,6 +39,9 @@ const translation = { successTip: '成功导出流水线 DSL', errorTip: '导出流水线 DSL 失败', }, + details: { + structure: '文档结构', + }, } export default translation diff --git a/web/models/app.ts b/web/models/app.ts index d83045570a..2d2322df6d 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -1,5 +1,5 @@ import type { LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider, WeaveConfig } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' -import type { App, AppSSO, AppTemplate, SiteConfig } from '@/types/app' +import type { App, AppMode, AppSSO, AppTemplate, SiteConfig } from '@/types/app' import type { Dependency } from '@/app/components/plugins/types' export enum DSLImportMode { diff --git a/web/models/pipeline.ts b/web/models/pipeline.ts index 101a4e4ab8..0128da841e 100644 --- a/web/models/pipeline.ts +++ b/web/models/pipeline.ts @@ -1,4 +1,6 @@ +import type { DSLImportMode, DSLImportStatus } from './app' import type { ChunkingMode, IconInfo } from './datasets' +import type { Dependency } from '@/app/components/plugins/types' export type PipelineTemplateListParams = { type: 'built-in' | 'customized' @@ -21,6 +23,8 @@ export type PipelineTemplateByIdResponse = { name: string icon_info: IconInfo description: string + author: string // todo: TBD + structure: string // todo: TBD export_data: string } @@ -46,3 +50,22 @@ export type DeletePipelineResponse = { export type ExportPipelineDSLResponse = { data: string } + +export type ImportPipelineDSLRequest = { + mode: DSLImportMode + name: string + yaml_content: string + icon_info: IconInfo + description: string +} + +export type ImportPipelineDSLResponse = { + id: string + status: DSLImportStatus + app_mode: 'pipeline' + dataset_id?: string + current_dsl_version?: string + imported_dsl_version?: string + error: string + leaked_dependencies: Dependency[] +} diff --git a/web/service/use-pipeline.ts b/web/service/use-pipeline.ts index 8e6e2981c4..13ada282ad 100644 --- a/web/service/use-pipeline.ts +++ b/web/service/use-pipeline.ts @@ -1,9 +1,11 @@ import type { MutationOptions } from '@tanstack/react-query' import { useMutation, useQuery } from '@tanstack/react-query' -import { del, get, patch } from './base' +import { del, get, patch, post } from './base' import type { DeletePipelineResponse, ExportPipelineDSLResponse, + ImportPipelineDSLRequest, + ImportPipelineDSLResponse, PipelineTemplateByIdResponse, PipelineTemplateListParams, PipelineTemplateListResponse, @@ -22,12 +24,13 @@ export const usePipelineTemplateList = (params: PipelineTemplateListParams) => { }) } -export const usePipelineTemplateById = (templateId: string) => { +export const usePipelineTemplateById = (templateId: string, enabled: boolean) => { return useQuery({ queryKey: [NAME_SPACE, 'template', templateId], queryFn: () => { return get(`/rag/pipeline/template/${templateId}`) }, + enabled, }) } @@ -69,3 +72,16 @@ export const useExportPipelineDSL = ( ...mutationOptions, }) } + +// TODO: replace with real API +export const useImportPipelineDSL = ( + mutationOptions: MutationOptions = {}, +) => { + return useMutation({ + mutationKey: [NAME_SPACE, 'template', 'import'], + mutationFn: (request: ImportPipelineDSLRequest) => { + return post('/rag/pipeline/import', { body: request }) + }, + ...mutationOptions, + }) +}