dify/web/app/components/app-sidebar/dataset-info/dropdown.tsx
-LAN- 85cda47c70
feat: knowledge pipeline (#25360)
Signed-off-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: twwu <twwu@dify.ai>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: jyong <718720800@qq.com>
Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com>
Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com>
Co-authored-by: quicksand <quicksandzn@gmail.com>
Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com>
Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: Yongtao Huang <yongtaoh2022@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: nite-knite <nkCoding@gmail.com>
Co-authored-by: Hanqing Zhao <sherry9277@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Harry <xh001x@hotmail.com>
2025-09-18 12:49:10 +08:00

153 lines
5.1 KiB
TypeScript

import React, { useCallback, useState } from 'react'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
import ActionButton from '../../base/action-button'
import { RiMoreFill } from '@remixicon/react'
import cn from '@/utils/classnames'
import Menu from './menu'
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import type { DataSet } from '@/models/datasets'
import { datasetDetailQueryKeyPrefix, useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { useInvalid } from '@/service/use-base'
import { useExportPipelineDSL } from '@/service/use-pipeline'
import Toast from '../../base/toast'
import { useTranslation } from 'react-i18next'
import RenameDatasetModal from '../../datasets/rename-modal'
import { checkIsUsedInApp, deleteDataset } from '@/service/datasets'
import Confirm from '../../base/confirm'
import { useRouter } from 'next/navigation'
type DropDownProps = {
expand: boolean
}
const DropDown = ({
expand,
}: DropDownProps) => {
const { t } = useTranslation()
const { replace } = useRouter()
const [open, setOpen] = useState(false)
const [showRenameModal, setShowRenameModal] = useState(false)
const [confirmMessage, setConfirmMessage] = useState<string>('')
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const isCurrentWorkspaceDatasetOperator = useAppContextWithSelector(state => state.isCurrentWorkspaceDatasetOperator)
const dataset = useDatasetDetailContextWithSelector(state => state.dataset) as DataSet
const handleTrigger = useCallback(() => {
setOpen(prev => !prev)
}, [])
const invalidDatasetList = useInvalidDatasetList()
const invalidDatasetDetail = useInvalid([...datasetDetailQueryKeyPrefix, dataset.id])
const refreshDataset = useCallback(() => {
invalidDatasetList()
invalidDatasetDetail()
}, [invalidDatasetDetail, invalidDatasetList])
const openRenameModal = useCallback(() => {
setShowRenameModal(true)
handleTrigger()
}, [handleTrigger])
const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL()
const handleExportPipeline = useCallback(async (include = false) => {
const { pipeline_id, name } = dataset
if (!pipeline_id)
return
handleTrigger()
try {
const { data } = await exportPipelineConfig({
pipelineId: pipeline_id,
include,
})
const a = document.createElement('a')
const file = new Blob([data], { type: 'application/yaml' })
const url = URL.createObjectURL(file)
a.href = url
a.download = `${name}.pipeline`
a.click()
URL.revokeObjectURL(url)
}
catch {
Toast.notify({ type: 'error', message: t('app.exportFailed') })
}
}, [dataset, exportPipelineConfig, handleTrigger, t])
const detectIsUsedByApp = useCallback(async () => {
try {
const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id)
setConfirmMessage(isUsedByApp ? t('dataset.datasetUsedByApp')! : t('dataset.deleteDatasetConfirmContent')!)
setShowConfirmDelete(true)
}
catch (e: any) {
const res = await e.json()
Toast.notify({ type: 'error', message: res?.message || 'Unknown error' })
}
finally {
handleTrigger()
}
}, [dataset.id, handleTrigger, t])
const onConfirmDelete = useCallback(async () => {
try {
await deleteDataset(dataset.id)
Toast.notify({ type: 'success', message: t('dataset.datasetDeleted') })
invalidDatasetList()
replace('/datasets')
}
finally {
setShowConfirmDelete(false)
}
}, [dataset.id, replace, invalidDatasetList, t])
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement={expand ? 'bottom-end' : 'right'}
offset={expand ? {
mainAxis: 4,
crossAxis: 10,
} : {
mainAxis: 4,
}}
>
<PortalToFollowElemTrigger onClick={handleTrigger}>
<ActionButton className={cn(expand ? 'size-8 rounded-lg' : 'size-6 rounded-md')}>
<RiMoreFill className='size-4' />
</ActionButton>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[60]'>
<Menu
showDelete={!isCurrentWorkspaceDatasetOperator}
openRenameModal={openRenameModal}
handleExportPipeline={handleExportPipeline}
detectIsUsedByApp={detectIsUsedByApp}
/>
</PortalToFollowElemContent>
{showRenameModal && (
<RenameDatasetModal
show={showRenameModal}
dataset={dataset!}
onClose={() => setShowRenameModal(false)}
onSuccess={refreshDataset}
/>
)}
{showConfirmDelete && (
<Confirm
title={t('dataset.deleteDatasetConfirmTitle')}
content={confirmMessage}
isShow={showConfirmDelete}
onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)}
/>
)}
</PortalToFollowElem>
)
}
export default React.memo(DropDown)