mirror of
https://github.com/langgenius/dify.git
synced 2026-08-02 02:40:43 +08:00
225 lines
7.6 KiB
TypeScript
225 lines
7.6 KiB
TypeScript
'use client'
|
|
import type { ComponentProps } from 'react'
|
|
import type { ToolsContentInset } from '../content-inset'
|
|
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogActions,
|
|
AlertDialogCancelButton,
|
|
AlertDialogConfirmButton,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogTitle,
|
|
} from '@langgenius/dify-ui/alert-dialog'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry'
|
|
import { useCanManageMCP } from '@/app/components/tools/hooks/use-tool-permissions'
|
|
import ToolCardSkeletonGrid from '@/app/components/tools/provider/tool-card-skeleton'
|
|
import { useAllToolProviders, useDeleteMCP, useUpdateMCP } from '@/service/use-tools'
|
|
import { toolsContentInsetClassNames, toolsUnifiedContentFrameClassName } from '../content-inset'
|
|
import NewMCPCard from './create-card'
|
|
import MCPDetailPanel from './detail/provider-detail'
|
|
import MCPModal from './modal'
|
|
import MCPCard from './provider-card'
|
|
|
|
type Props = Readonly<{
|
|
searchText: string
|
|
contentInset?: ToolsContentInset
|
|
createdProviderId?: string
|
|
onCreatedProviderHandled?: () => void
|
|
showCreateCard?: boolean
|
|
}>
|
|
|
|
type MCPModalConfirmPayload = Parameters<ComponentProps<typeof MCPModal>['onConfirm']>[0]
|
|
type MutationResult = {
|
|
result?: string
|
|
}
|
|
|
|
const MCPList = ({
|
|
searchText,
|
|
contentInset = 'default',
|
|
createdProviderId,
|
|
onCreatedProviderHandled,
|
|
showCreateCard = true,
|
|
}: Props) => {
|
|
const { t } = useTranslation()
|
|
const canManageMCP = useCanManageMCP()
|
|
const { data: list = [] as ToolWithProvider[], isLoading, refetch } = useAllToolProviders()
|
|
const [isTriggerAuthorize, setIsTriggerAuthorize] = useState<boolean>(false)
|
|
|
|
const filteredList = useMemo(() => {
|
|
return list.filter((collection) => {
|
|
if (collection.type !== 'mcp') return false
|
|
if (searchText)
|
|
return Object.values(collection.name).some((value) =>
|
|
(value as string).toLowerCase().includes(searchText.toLowerCase()),
|
|
)
|
|
return true
|
|
}) as ToolWithProvider[]
|
|
}, [list, searchText])
|
|
|
|
const [currentProviderID, setCurrentProviderID] = useState<string>()
|
|
const [editingProviderID, setEditingProviderID] = useState<string>()
|
|
const [deletingProviderID, setDeletingProviderID] = useState<string>()
|
|
|
|
const currentProvider = list.find((provider) => provider.id === currentProviderID)
|
|
const editingProvider = list.find((provider) => provider.id === editingProviderID)
|
|
const deletingProvider = list.find((provider) => provider.id === deletingProviderID)
|
|
const detailProvider = editingProvider || deletingProvider ? undefined : currentProvider
|
|
const { mutateAsync: updateMCP } = useUpdateMCP({})
|
|
const { mutateAsync: deleteMCP, isPending: isDeleting } = useDeleteMCP({})
|
|
|
|
const handleCreate = async (provider: ToolWithProvider) => {
|
|
if (!canManageMCP) return
|
|
|
|
await refetch() // update list
|
|
setCurrentProviderID(provider.id)
|
|
setIsTriggerAuthorize(true)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!canManageMCP || !createdProviderId) return
|
|
|
|
let isActive = true
|
|
|
|
const openCreatedProvider = async () => {
|
|
try {
|
|
await refetch()
|
|
if (!isActive) return
|
|
|
|
setCurrentProviderID(createdProviderId)
|
|
setIsTriggerAuthorize(true)
|
|
} finally {
|
|
if (isActive) onCreatedProviderHandled?.()
|
|
}
|
|
}
|
|
|
|
void openCreatedProvider()
|
|
|
|
return () => {
|
|
isActive = false
|
|
}
|
|
}, [canManageMCP, createdProviderId, onCreatedProviderHandled, refetch])
|
|
|
|
const handleEdit = (providerID: string) => {
|
|
if (!canManageMCP) return
|
|
|
|
setEditingProviderID(providerID)
|
|
}
|
|
|
|
const handleEditConfirm = async (form: MCPModalConfirmPayload) => {
|
|
if (!canManageMCP || !editingProvider) return
|
|
|
|
const res = (await updateMCP({
|
|
...form,
|
|
provider_id: editingProvider.id,
|
|
})) as MutationResult
|
|
if (res.result !== 'success') return
|
|
|
|
await refetch() // update list
|
|
setCurrentProviderID(editingProvider.id)
|
|
setIsTriggerAuthorize(true)
|
|
setEditingProviderID(undefined)
|
|
}
|
|
|
|
const handleDelete = (providerID: string) => {
|
|
if (!canManageMCP) return
|
|
|
|
setDeletingProviderID(providerID)
|
|
}
|
|
|
|
const handleDeleteConfirm = async () => {
|
|
if (!canManageMCP || !deletingProvider) return
|
|
|
|
const res = (await deleteMCP(deletingProvider.id)) as MutationResult
|
|
if (res.result !== 'success') return
|
|
|
|
await refetch()
|
|
setCurrentProviderID(undefined)
|
|
setDeletingProviderID(undefined)
|
|
}
|
|
const contentPaddingClassName = toolsContentInsetClassNames[contentInset]
|
|
const contentFrameClassName = cn(contentPaddingClassName, toolsUnifiedContentFrameClassName)
|
|
return (
|
|
<>
|
|
<div
|
|
className={cn(
|
|
'relative grid shrink-0 grid-cols-1 content-start gap-4 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3',
|
|
contentFrameClassName,
|
|
isLoading && 'h-[calc(100vh-136px)] overflow-hidden',
|
|
)}
|
|
>
|
|
{!isLoading && canManageMCP && showCreateCard && <NewMCPCard handleCreate={handleCreate} />}
|
|
{isLoading ? (
|
|
<ToolCardSkeletonGrid variant="mcp" />
|
|
) : (
|
|
filteredList.map((provider, index) => (
|
|
<div
|
|
key={provider.id}
|
|
data-step-by-step-tour-target={
|
|
index === 0 ? STEP_BY_STEP_TOUR_TARGETS.integrationMcpFirstCard : undefined
|
|
}
|
|
>
|
|
<MCPCard
|
|
data={provider}
|
|
currentProvider={detailProvider as ToolWithProvider}
|
|
handleSelect={setCurrentProviderID}
|
|
onEdit={handleEdit}
|
|
onDelete={handleDelete}
|
|
/>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
{detailProvider && (
|
|
<MCPDetailPanel
|
|
detail={detailProvider as ToolWithProvider}
|
|
onHide={() => setCurrentProviderID(undefined)}
|
|
onUpdate={refetch}
|
|
onEdit={handleEdit}
|
|
onDelete={handleDelete}
|
|
isTriggerAuthorize={isTriggerAuthorize}
|
|
onFirstCreate={() => setIsTriggerAuthorize(false)}
|
|
/>
|
|
)}
|
|
{editingProvider && (
|
|
<MCPModal
|
|
data={editingProvider as ToolWithProvider}
|
|
show
|
|
onConfirm={handleEditConfirm}
|
|
onHide={() => setEditingProviderID(undefined)}
|
|
/>
|
|
)}
|
|
{deletingProvider && (
|
|
<AlertDialog open onOpenChange={(open) => !open && setDeletingProviderID(undefined)}>
|
|
<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(($) => $['mcp.delete'], { ns: 'tools' })}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
|
{t(($) => $['mcp.deleteConfirmTitle'], { ns: 'tools', mcp: deletingProvider.name })}
|
|
</AlertDialogDescription>
|
|
</div>
|
|
<AlertDialogActions>
|
|
<AlertDialogCancelButton>
|
|
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
|
</AlertDialogCancelButton>
|
|
<AlertDialogConfirmButton
|
|
loading={isDeleting}
|
|
disabled={isDeleting}
|
|
onClick={handleDeleteConfirm}
|
|
>
|
|
{t(($) => $['operation.confirm'], { ns: 'common' })}
|
|
</AlertDialogConfirmButton>
|
|
</AlertDialogActions>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
export default MCPList
|