mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 00:48:04 +08:00
feat: Enhance dataset pipeline creation and management with new export and delete functionalities, improved internationalization, and refactor for better clarity
This commit is contained in:
@ -17,8 +17,7 @@ import { useCreateDataset } from '@/service/knowledge/use-create-dataset'
|
||||
import type { Member } from '@/models/common'
|
||||
|
||||
type CreateFromScratchProps = {
|
||||
onClose?: () => void
|
||||
onCreate?: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const DEFAULT_APP_ICON: AppIconSelection = {
|
||||
@ -29,7 +28,6 @@ const DEFAULT_APP_ICON: AppIconSelection = {
|
||||
|
||||
const CreateFromScratch = ({
|
||||
onClose,
|
||||
onCreate,
|
||||
}: CreateFromScratchProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState('')
|
||||
@ -79,7 +77,7 @@ const CreateFromScratch = ({
|
||||
|
||||
const { mutateAsync: createEmptyDataset } = useCreateDataset()
|
||||
|
||||
const handleCreate = useCallback(() => {
|
||||
const handleCreate = useCallback(async () => {
|
||||
if (!name) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
@ -108,10 +106,12 @@ const CreateFromScratch = ({
|
||||
})
|
||||
request.partial_member_list = selectedMemberList
|
||||
}
|
||||
createEmptyDataset(request)
|
||||
onCreate?.()
|
||||
onClose?.()
|
||||
}, [name, permission, appIcon, description, createEmptyDataset, memberList, selectedMemberIDs, onCreate, onClose])
|
||||
await createEmptyDataset(request, {
|
||||
onSettled: () => {
|
||||
onClose?.()
|
||||
},
|
||||
})
|
||||
}, [name, permission, appIcon, description, createEmptyDataset, memberList, selectedMemberIDs, onClose])
|
||||
|
||||
return (
|
||||
<div className='relative flex flex-col'>
|
||||
@ -132,12 +132,12 @@ const CreateFromScratch = ({
|
||||
<div className='flex items-end gap-x-3 self-stretch'>
|
||||
<div className='flex grow flex-col gap-y-1 pb-1'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
||||
{t('datasetPipeline.creation.knowledgeNameAndIcon')}
|
||||
{t('datasetPipeline.knowledgeNameAndIcon')}
|
||||
</label>
|
||||
<Input
|
||||
onChange={handleAppNameChange}
|
||||
value={name}
|
||||
placeholder={t('datasetPipeline.creation.knowledgeNameAndIconPlaceholder')}
|
||||
placeholder={t('datasetPipeline.knowledgeNameAndIconPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<AppIcon
|
||||
@ -153,17 +153,17 @@ const CreateFromScratch = ({
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
||||
{t('datasetPipeline.creation.knowledgeDescription')}
|
||||
{t('datasetPipeline.knowledgeDescription')}
|
||||
</label>
|
||||
<Textarea
|
||||
onChange={handleDescriptionChange}
|
||||
value={description}
|
||||
placeholder={t('datasetPipeline.creation.knowledgeDescriptionPlaceholder')}
|
||||
placeholder={t('datasetPipeline.knowledgeDescriptionPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
||||
{t('datasetPipeline.creation.knowledgePermissions')}
|
||||
{t('datasetPipeline.knowledgePermissions')}
|
||||
</label>
|
||||
<PermissionSelector
|
||||
permission={permission}
|
||||
|
||||
@ -34,10 +34,6 @@ const CreateOptions = () => {
|
||||
setShowCreateModal(false)
|
||||
}, [])
|
||||
|
||||
const handleCreateFromScratch = useCallback(() => {
|
||||
setShowCreateModal(false)
|
||||
}, [])
|
||||
|
||||
const openImportFromDSL = useCallback(() => {
|
||||
setShowImportModal(true)
|
||||
}, [])
|
||||
@ -73,7 +69,6 @@ const CreateOptions = () => {
|
||||
>
|
||||
<CreateFromScratch
|
||||
onClose={closeCreateFromScratch}
|
||||
onCreate={handleCreateFromScratch}
|
||||
/>
|
||||
</Modal>
|
||||
<CreateFromDSLModal
|
||||
|
||||
@ -16,7 +16,7 @@ const Item = ({
|
||||
}: ItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className='group flex w-[337px] items-center gap-x-3 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs shadow-shadow-shadow-3 hover:shadow-md hover:shadow-shadow-shadow-5'
|
||||
className='group flex w-[337px] cursor-pointer items-center gap-x-3 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs shadow-shadow-shadow-3 hover:shadow-md hover:shadow-shadow-shadow-5'
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className='flex size-10 shrink-0 items-center justify-center rounded-[10px] border border-dashed border-divider-regular bg-background-section group-hover:border-state-accent-hover-alt group-hover:bg-state-accent-hover'>
|
||||
|
||||
@ -9,16 +9,15 @@ import Button from '@/app/components/base/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { PipelineTemple } from '@/models/pipeline'
|
||||
import { useUpdatePipelineInfo } from '@/service/use-pipeline'
|
||||
|
||||
type EditPipelineInfoProps = {
|
||||
onClose: () => void
|
||||
onSave: () => void
|
||||
pipeline: PipelineTemple
|
||||
}
|
||||
|
||||
const EditPipelineInfo = ({
|
||||
onClose,
|
||||
onSave,
|
||||
pipeline,
|
||||
}: EditPipelineInfoProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -62,7 +61,9 @@ const EditPipelineInfo = ({
|
||||
setDescription(value)
|
||||
}, [])
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { mutateAsync: updatePipeline } = useUpdatePipelineInfo()
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!name) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
@ -70,16 +71,30 @@ const EditPipelineInfo = ({
|
||||
})
|
||||
return
|
||||
}
|
||||
onSave()
|
||||
onClose()
|
||||
}, [name, onSave, onClose])
|
||||
const request = {
|
||||
pipeline_id: pipeline.id,
|
||||
name,
|
||||
icon_info: {
|
||||
icon_type: appIcon.type,
|
||||
icon: appIcon.type === 'image' ? appIcon.fileId : appIcon.icon,
|
||||
icon_background: appIcon.type === 'image' ? undefined : appIcon.background,
|
||||
icon_url: appIcon.type === 'image' ? appIcon.url : undefined,
|
||||
},
|
||||
description,
|
||||
}
|
||||
await updatePipeline(request, {
|
||||
onSettled: () => {
|
||||
onClose()
|
||||
},
|
||||
})
|
||||
}, [name, appIcon, description, pipeline.id, updatePipeline, onClose])
|
||||
|
||||
return (
|
||||
<div className='relative flex flex-col'>
|
||||
{/* Header */}
|
||||
<div className='pb-3 pl-6 pr-14 pt-6'>
|
||||
<span className='title-2xl-semi-bold text-text-primary'>
|
||||
Edit Pipeline Info
|
||||
{t('datasetPipeline.editPipelineInfo')}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@ -92,11 +107,13 @@ const EditPipelineInfo = ({
|
||||
<div className='flex flex-col gap-y-5 px-6 py-3'>
|
||||
<div className='flex items-end gap-x-3 self-stretch'>
|
||||
<div className='flex grow flex-col gap-y-1 pb-1'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Pipeline name & icon</label>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
||||
{t('datasetPipeline.pipelineNameAndIcon')}
|
||||
</label>
|
||||
<Input
|
||||
onChange={handleAppNameChange}
|
||||
value={name}
|
||||
placeholder='Please enter the name of the Knowledge Base'
|
||||
placeholder={t('datasetPipeline.knowledgeNameAndIconPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<AppIcon
|
||||
@ -111,11 +128,13 @@ const EditPipelineInfo = ({
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Knowledge description</label>
|
||||
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
||||
{t('datasetPipeline.knowledgeDescription')}
|
||||
</label>
|
||||
<Textarea
|
||||
onChange={handleDescriptionChange}
|
||||
value={description}
|
||||
placeholder='Describe what is in this Knowledge Base. A detailed description allows AI to access the content of the dataset more accurately. If empty, Dify will use the default hit strategy. (Optional)'
|
||||
placeholder={t('datasetPipeline.knowledgeDescriptionPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,6 +10,10 @@ import Modal from '@/app/components/base/modal'
|
||||
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 { downloadFile } from '@/utils/format'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
type TemplateCardProps = {
|
||||
pipeline: PipelineTemple
|
||||
@ -22,6 +26,7 @@ const TemplateCard = ({
|
||||
}: TemplateCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [showEditModal, setShowEditModal] = useState(false)
|
||||
const [showDeleteConfirm, setShowConfirmDelete] = useState(false)
|
||||
|
||||
const openEditModal = useCallback(() => {
|
||||
setShowEditModal(true)
|
||||
@ -31,6 +36,48 @@ const TemplateCard = ({
|
||||
setShowEditModal(false)
|
||||
}, [])
|
||||
|
||||
const { mutateAsync: getDSLFileContent } = useExportPipelineDSL()
|
||||
|
||||
const handleExportDSL = useCallback(async () => {
|
||||
await getDSLFileContent(pipeline.id, {
|
||||
onSuccess: (res) => {
|
||||
const blob = new Blob([res.data], { type: 'application/yaml' })
|
||||
downloadFile({
|
||||
data: blob,
|
||||
fileName: `${pipeline.name}.dsl`,
|
||||
})
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('datasetPipeline.exportDSL.successTip'),
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('datasetPipeline.exportDSL.errorTip'),
|
||||
})
|
||||
},
|
||||
})
|
||||
}, [t, pipeline.id, pipeline.name, getDSLFileContent])
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
setShowConfirmDelete(true)
|
||||
}, [])
|
||||
|
||||
const onCancelDelete = useCallback(() => {
|
||||
setShowConfirmDelete(false)
|
||||
}, [])
|
||||
|
||||
const { mutateAsync: deletePipeline } = useDeletePipeline()
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
await deletePipeline(pipeline.id, {
|
||||
onSettled: () => {
|
||||
setShowConfirmDelete(false)
|
||||
},
|
||||
})
|
||||
}, [pipeline.id, deletePipeline])
|
||||
|
||||
const Icon = DOC_FORM_ICON[pipeline.doc_form] || General
|
||||
const iconInfo = pipeline.icon_info
|
||||
|
||||
@ -50,13 +97,21 @@ const TemplateCard = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex grow flex-col gap-y-1 py-px'>
|
||||
<div className='system-md-semibold truncate text-text-secondary' title={pipeline.name}>{pipeline.name}</div>
|
||||
<div
|
||||
className='system-md-semibold truncate text-text-secondary'
|
||||
title={pipeline.name}
|
||||
>
|
||||
{pipeline.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{t(`dataset.chunkingMode.${DOC_FORM_TEXT[pipeline.doc_form]}`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-xs-regular line-clamp-3 grow px-4 py-1 text-text-tertiary' title={pipeline.description}>
|
||||
<p
|
||||
className='system-xs-regular line-clamp-3 grow px-4 py-1 text-text-tertiary'
|
||||
title={pipeline.description}
|
||||
>
|
||||
{pipeline.description}
|
||||
</p>
|
||||
<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'>
|
||||
@ -68,7 +123,7 @@ const TemplateCard = ({
|
||||
className='grow gap-x-0.5'
|
||||
>
|
||||
<RiAddLine className='size-4' />
|
||||
<span className='px-0.5'>Choose</span>
|
||||
<span className='px-0.5'>{t('datasetPipeline.operations.choose')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant='secondary'
|
||||
@ -78,7 +133,7 @@ const TemplateCard = ({
|
||||
className='grow gap-x-0.5'
|
||||
>
|
||||
<RiArrowRightUpLine className='size-4' />
|
||||
<span className='px-0.5'>Details</span>
|
||||
<span className='px-0.5'>{t('datasetPipeline.operations.details')}</span>
|
||||
</Button>
|
||||
{
|
||||
showMoreOperations && (
|
||||
@ -86,9 +141,8 @@ const TemplateCard = ({
|
||||
htmlContent={
|
||||
<Operations
|
||||
openEditModal={openEditModal}
|
||||
onDelete={() => {
|
||||
console.log('Delete', pipeline)
|
||||
}}
|
||||
onExport={handleExportDSL}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
className={'z-20 min-w-[160px]'}
|
||||
@ -103,19 +157,27 @@ const TemplateCard = ({
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Modal
|
||||
isShow={showEditModal}
|
||||
onClose={closeEditModal}
|
||||
className='max-w-[520px] p-0'
|
||||
>
|
||||
<EditPipelineInfo
|
||||
pipeline={pipeline}
|
||||
{showEditModal && (
|
||||
<Modal
|
||||
isShow={showEditModal}
|
||||
onClose={closeEditModal}
|
||||
onSave={() => {
|
||||
console.log('Save', pipeline)
|
||||
}}
|
||||
className='max-w-[520px] p-0'
|
||||
>
|
||||
<EditPipelineInfo
|
||||
pipeline={pipeline}
|
||||
onClose={closeEditModal}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{showDeleteConfirm && (
|
||||
<Confirm
|
||||
title={t('datasetPipeline.deletePipeline.title')}
|
||||
content={t('datasetPipeline.deletePipeline.content')}
|
||||
isShow={showDeleteConfirm}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={onCancelDelete}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,21 +5,29 @@ import { useTranslation } from 'react-i18next'
|
||||
type OperationsProps = {
|
||||
openEditModal: () => void
|
||||
onDelete: () => void
|
||||
onExport: () => void
|
||||
}
|
||||
|
||||
const Operations = ({
|
||||
openEditModal,
|
||||
onDelete,
|
||||
onExport,
|
||||
}: OperationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onClickEdit = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const onClickEdit = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
openEditModal()
|
||||
}
|
||||
|
||||
const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const onClickExport = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
onExport()
|
||||
}
|
||||
|
||||
const onClickDelete = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
onDelete()
|
||||
@ -33,15 +41,15 @@ const Operations = ({
|
||||
onClick={onClickEdit}
|
||||
>
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
Edit Info
|
||||
{t('datasetPipeline.operations.editInfo')}
|
||||
</span>
|
||||
</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('Export DSL') }}
|
||||
onClick={onClickExport}
|
||||
>
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
Export DSL
|
||||
{t('datasetPipeline.operations.exportDSL')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user