mirror of
https://github.com/langgenius/dify.git
synced 2026-07-25 21:48:30 +08:00
235 lines
7.9 KiB
TypeScript
235 lines
7.9 KiB
TypeScript
import type { DataSet } from '@/models/datasets'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogActions,
|
|
AlertDialogCancelButton,
|
|
AlertDialogConfirmButton,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogTitle,
|
|
} from '@langgenius/dify-ui/alert-dialog'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger,
|
|
} from '@langgenius/dify-ui/dropdown-menu'
|
|
import { toast } from '@langgenius/dify-ui/toast'
|
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
|
import { useAtomValue } from 'jotai'
|
|
import * as React from 'react'
|
|
import { useCallback, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { userProfileIdAtom } from '@/context/account-state'
|
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
|
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
|
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
|
import { useRouter } from '@/next/navigation'
|
|
import { checkIsUsedInApp, deleteDataset } from '@/service/datasets'
|
|
import { datasetDetailQueryKeyPrefix, useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
|
import { useInvalid } from '@/service/use-base'
|
|
import { useExportPipelineDSL } from '@/service/use-pipeline'
|
|
import { downloadBlob } from '@/utils/download'
|
|
import { getDatasetACLCapabilities } from '@/utils/permission'
|
|
import ActionButton from '../../base/action-button'
|
|
import RenameDatasetModal from '../../datasets/rename-modal'
|
|
import Menu from './menu'
|
|
|
|
type DropDownProps = {
|
|
expand: boolean
|
|
triggerClassName?: string
|
|
}
|
|
|
|
type JsonErrorResponse = {
|
|
json: () => Promise<{ message?: string }>
|
|
}
|
|
|
|
const isJsonErrorResponse = (error: unknown): error is JsonErrorResponse => {
|
|
return (
|
|
typeof error === 'object' &&
|
|
error !== null &&
|
|
'json' in error &&
|
|
typeof error.json === 'function'
|
|
)
|
|
}
|
|
|
|
const getErrorMessage = async (error: unknown) => {
|
|
if (!isJsonErrorResponse(error)) return 'Unknown error'
|
|
|
|
const res = await error.json()
|
|
return res?.message || 'Unknown error'
|
|
}
|
|
|
|
const DropDown = ({ expand, triggerClassName }: DropDownProps) => {
|
|
const { t } = useTranslation()
|
|
const { push, replace } = useRouter()
|
|
const [open, setOpen] = useState(false)
|
|
const [showRenameModal, setShowRenameModal] = useState(false)
|
|
const [confirmMessage, setConfirmMessage] = useState<string>('')
|
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
|
|
|
const dataset = useDatasetDetailContextWithSelector((state) => state.dataset) as DataSet
|
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
|
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
|
const isRbacEnabled = systemFeatures.rbac_enabled
|
|
const datasetACLCapabilities = React.useMemo(
|
|
() =>
|
|
getDatasetACLCapabilities(dataset?.permission_keys, {
|
|
currentUserId,
|
|
resourceMaintainer: dataset?.maintainer,
|
|
workspacePermissionKeys,
|
|
isRbacEnabled,
|
|
}),
|
|
[
|
|
dataset?.maintainer,
|
|
dataset?.permission_keys,
|
|
currentUserId,
|
|
isRbacEnabled,
|
|
workspacePermissionKeys,
|
|
],
|
|
)
|
|
const canShowOperations =
|
|
datasetACLCapabilities.canEdit ||
|
|
datasetACLCapabilities.canImportExportDSL ||
|
|
datasetACLCapabilities.canAccessConfig ||
|
|
datasetACLCapabilities.canDelete
|
|
|
|
const invalidDatasetList = useInvalidDatasetList()
|
|
const invalidDatasetDetail = useInvalid([...datasetDetailQueryKeyPrefix, dataset.id])
|
|
|
|
const refreshDataset = useCallback(() => {
|
|
invalidDatasetList()
|
|
invalidDatasetDetail()
|
|
}, [invalidDatasetDetail, invalidDatasetList])
|
|
|
|
const openRenameModal = useCallback(() => {
|
|
setOpen(false)
|
|
queueMicrotask(() => {
|
|
setShowRenameModal(true)
|
|
})
|
|
}, [])
|
|
|
|
const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL()
|
|
|
|
const handleExportPipeline = useCallback(
|
|
async (include = false) => {
|
|
const { pipeline_id, name } = dataset
|
|
if (!pipeline_id) return
|
|
setOpen(false)
|
|
try {
|
|
const { data } = await exportPipelineConfig({
|
|
pipelineId: pipeline_id,
|
|
include,
|
|
})
|
|
const file = new Blob([data], { type: 'application/yaml' })
|
|
downloadBlob({ data: file, fileName: `${name}.pipeline` })
|
|
} catch {
|
|
toast.error(t(($) => $.exportFailed, { ns: 'app' }))
|
|
}
|
|
},
|
|
[dataset, exportPipelineConfig, t],
|
|
)
|
|
|
|
const detectIsUsedByApp = useCallback(async () => {
|
|
setOpen(false)
|
|
try {
|
|
const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id)
|
|
setConfirmMessage(
|
|
isUsedByApp
|
|
? t(($) => $.datasetUsedByApp, { ns: 'dataset' })!
|
|
: t(($) => $.deleteDatasetConfirmContent, { ns: 'dataset' })!,
|
|
)
|
|
setShowConfirmDelete(true)
|
|
} catch (e: unknown) {
|
|
toast.error(await getErrorMessage(e))
|
|
}
|
|
}, [dataset.id, t])
|
|
|
|
const openAccessConfig = useCallback(() => {
|
|
setOpen(false)
|
|
push(`/datasets/${dataset.id}/access-config`)
|
|
}, [dataset.id, push])
|
|
|
|
const onConfirmDelete = useCallback(async () => {
|
|
try {
|
|
await deleteDataset(dataset.id)
|
|
toast(
|
|
t(($) => $.datasetDeleted, { ns: 'dataset' }),
|
|
{ type: 'success' },
|
|
)
|
|
invalidDatasetList()
|
|
replace('/datasets')
|
|
} finally {
|
|
setShowConfirmDelete(false)
|
|
}
|
|
}, [dataset.id, replace, invalidDatasetList, t])
|
|
|
|
if (!canShowOperations) return null
|
|
|
|
return (
|
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
<DropdownMenuTrigger
|
|
render={
|
|
<ActionButton
|
|
aria-label={t(($) => $['operation.more'], { ns: 'common' })}
|
|
size={expand ? 'l' : 'm'}
|
|
className={cn('data-popup-open:bg-state-base-hover', triggerClassName)}
|
|
/>
|
|
}
|
|
>
|
|
<span aria-hidden className="i-ri-more-fill size-4" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
placement={expand ? 'bottom-end' : 'right-start'}
|
|
sideOffset={4}
|
|
popupClassName="border-0 bg-transparent p-0 shadow-none backdrop-blur-none"
|
|
>
|
|
<Menu
|
|
showEdit={datasetACLCapabilities.canEdit}
|
|
showDelete={datasetACLCapabilities.canDelete}
|
|
showExportPipeline={datasetACLCapabilities.canImportExportDSL}
|
|
showAccessConfig={datasetACLCapabilities.canAccessConfig}
|
|
openRenameModal={openRenameModal}
|
|
handleExportPipeline={handleExportPipeline}
|
|
detectIsUsedByApp={detectIsUsedByApp}
|
|
openAccessConfig={openAccessConfig}
|
|
/>
|
|
</DropdownMenuContent>
|
|
{showRenameModal && (
|
|
<RenameDatasetModal
|
|
show={showRenameModal}
|
|
dataset={dataset!}
|
|
onClose={() => setShowRenameModal(false)}
|
|
onSuccess={refreshDataset}
|
|
/>
|
|
)}
|
|
<AlertDialog
|
|
open={showConfirmDelete}
|
|
onOpenChange={(open) => !open && setShowConfirmDelete(false)}
|
|
>
|
|
<AlertDialogContent>
|
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
|
{t(($) => $.deleteDatasetConfirmTitle, { ns: 'dataset' })}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
|
{confirmMessage}
|
|
</AlertDialogDescription>
|
|
</div>
|
|
<AlertDialogActions>
|
|
<AlertDialogCancelButton>
|
|
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
|
</AlertDialogCancelButton>
|
|
<AlertDialogConfirmButton onClick={onConfirmDelete}>
|
|
{t(($) => $['operation.confirm'], { ns: 'common' })}
|
|
</AlertDialogConfirmButton>
|
|
</AlertDialogActions>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</DropdownMenu>
|
|
)
|
|
}
|
|
|
|
export default React.memo(DropDown)
|