mirror of https://github.com/langgenius/dify.git
feat: add pipeline template details and import functionality, enhance dataset pipeline management
This commit is contained in:
parent
3f7f21ce70
commit
7ce9710229
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className='flex h-full items-center justify-center'>
|
||||
<div className='system-md-medium text-text-secondary'>Loading...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex h-full'>
|
||||
<div className='flex grow items-center justify-center p-3 pr-0'>
|
||||
Pipeline Preview
|
||||
</div>
|
||||
<div className='relative flex w-[360px] shrink-0 flex-col'>
|
||||
<button
|
||||
type='button'
|
||||
className='absolute right-4 top-4 z-10 flex size-8 items-center justify-center'
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
<div className='flex items-center gap-x-3 pb-2 pl-4 pr-12 pt-6'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
iconType={appIcon.type as AppIconType}
|
||||
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
||||
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
||||
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
||||
showEditIcon
|
||||
/>
|
||||
<div className='flex grow flex-col gap-y-1 py-px'>
|
||||
<div className='system-md-semibold text-text-secondary'>
|
||||
{pipelineTemplateInfo.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{`By ${pipelineTemplateInfo.author}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-sm-regular px-4 pb-2 pt-1 text-text-secondary'>
|
||||
{pipelineTemplateInfo.description}
|
||||
</p>
|
||||
<div className='p-3'>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={handleUseTemplate}
|
||||
className='w-full gap-x-0.5'
|
||||
>
|
||||
<RiAddLine className='size-4' />
|
||||
<span className='px-0.5'>{t('datasetPipeline.operations.useTemplate')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1 px-4 py-2'>
|
||||
<div className='flex items-center gap-x-0.5'>
|
||||
<span className='system-sm-semibold-uppercase text-text-secondary'>
|
||||
{t('datasetPipeline.details.structure')}
|
||||
</span>
|
||||
<Tooltip
|
||||
popupContent={'This is the structure of the pipeline.'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Details)
|
||||
|
|
@ -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 = ({
|
|||
<div className='absolute bottom-0 left-0 z-10 hidden w-full items-center gap-x-1 bg-pipeline-template-card-hover-bg p-4 pt-8 group-hover:flex'>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
console.log('Choose', pipeline)
|
||||
}}
|
||||
onClick={handleUseTemplate}
|
||||
className='grow gap-x-0.5'
|
||||
>
|
||||
<RiAddLine className='size-4' />
|
||||
|
|
@ -127,9 +178,7 @@ const TemplateCard = ({
|
|||
</Button>
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={() => {
|
||||
console.log('details', pipeline)
|
||||
}}
|
||||
onClick={handleShowTemplateDetails}
|
||||
className='grow gap-x-0.5'
|
||||
>
|
||||
<RiArrowRightUpLine className='size-4' />
|
||||
|
|
@ -178,6 +227,19 @@ const TemplateCard = ({
|
|||
onCancel={onCancelDelete}
|
||||
/>
|
||||
)}
|
||||
{showDetailModal && (
|
||||
<Modal
|
||||
isShow={showDetailModal}
|
||||
onClose={closeDetailsModal}
|
||||
className='h-[calc(100vh-64px)] max-w-[1680px] p-0'
|
||||
>
|
||||
<Details
|
||||
id={pipeline.id}
|
||||
onClose={closeDetailsModal}
|
||||
handleUseTemplate={handleUseTemplate}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
{/* <div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={() => { console.log('duplicate') }}
|
||||
>
|
||||
|
|
@ -48,9 +48,9 @@ const Operations = ({
|
|||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
{t('common.operation.duplicate')}
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
<Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||
{/* <Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||
<div className='flex flex-col p-1'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
|
|
@ -70,7 +70,7 @@ const Operations = ({
|
|||
Import Solution
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{showDelete && (
|
||||
<>
|
||||
<Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PipelineTemplateByIdResponse>({
|
||||
queryKey: [NAME_SPACE, 'template', templateId],
|
||||
queryFn: () => {
|
||||
return get<PipelineTemplateByIdResponse>(`/rag/pipeline/template/${templateId}`)
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -69,3 +72,16 @@ export const useExportPipelineDSL = (
|
|||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: replace with real API
|
||||
export const useImportPipelineDSL = (
|
||||
mutationOptions: MutationOptions<ImportPipelineDSLResponse, Error, ImportPipelineDSLRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'template', 'import'],
|
||||
mutationFn: (request: ImportPipelineDSLRequest) => {
|
||||
return post<ImportPipelineDSLResponse>('/rag/pipeline/import', { body: request })
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue