dify/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

218 lines
7.8 KiB
TypeScript

'use client'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Switch } from '@langgenius/dify-ui/switch'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import AppIcon from '@/app/components/base/app-icon'
import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button'
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
import McpToolNotSupportTooltip from '@/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip'
import { SwitchPluginVersion } from '@/app/components/workflow/nodes/_base/components/switch-plugin-version'
type Props = Readonly<{
icon?: string | { content?: string; background?: string }
providerName?: string
isMCPTool?: boolean
providerShowName?: string
toolLabel?: string
showSwitch?: boolean
switchValue?: boolean
onSwitchChange?: (value: boolean) => void
onDelete?: () => void
noAuth?: boolean
isError?: boolean
errorTip?: React.ReactNode
uninstalled?: boolean
installInfo?: string
onInstall?: () => void
versionMismatch?: boolean
open: boolean
authRemoved?: boolean
}>
const ToolItem = ({
open,
icon,
isMCPTool,
providerShowName,
providerName,
toolLabel,
showSwitch,
switchValue,
onSwitchChange,
onDelete,
noAuth,
uninstalled,
installInfo,
onInstall,
isError,
errorTip,
versionMismatch,
authRemoved,
}: Props) => {
const { t } = useTranslation()
const { allowed: isMCPToolAllowed } = useMCPToolAvailability()
const providerNameText = isMCPTool ? providerShowName : providerName?.split('/').pop()
const isTransparent = uninstalled || versionMismatch || isError
const [isDeleting, setIsDeleting] = useState(false)
const isShowCanNotChooseMCPTip = isMCPTool && !isMCPToolAllowed
return (
<div
className={cn(
'group flex cursor-default items-center gap-1 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg p-1.5 pr-2 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm',
open && 'bg-components-panel-on-panel-item-bg-hover shadow-sm',
isDeleting && 'border-state-destructive-border shadow-xs hover:bg-state-destructive-hover',
)}
>
{icon && (
<div
className={cn(
'shrink-0',
isTransparent && 'opacity-50',
isShowCanNotChooseMCPTip && 'opacity-30',
)}
>
{typeof icon === 'string' && (
<div
className="h-7 w-7 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge bg-cover bg-center"
style={{ backgroundImage: `url(${icon})` }}
/>
)}
{typeof icon !== 'string' && (
<AppIcon
className="h-7 w-7 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge"
size="xs"
icon={icon?.content}
background={icon?.background}
/>
)}
</div>
)}
{!icon && (
<div
className={cn(
'flex h-7 w-7 items-center justify-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle',
isTransparent && 'opacity-50',
isShowCanNotChooseMCPTip && 'opacity-30',
)}
>
<div className="flex size-5 items-center justify-center opacity-35">
<span className="i-custom-vender-other-group text-text-tertiary" />
</div>
</div>
)}
<div
className={cn(
'grow truncate pl-0.5',
isTransparent && 'opacity-50',
isShowCanNotChooseMCPTip && 'opacity-30',
)}
>
<div className="system-2xs-medium-uppercase text-text-tertiary">{providerNameText}</div>
<div className="system-xs-medium text-text-secondary">{toolLabel}</div>
</div>
<div className="hidden items-center gap-1 group-hover:flex">
{!noAuth && !isError && !uninstalled && !versionMismatch && !isShowCanNotChooseMCPTip && (
<ActionButton>
<span className="i-ri-equalizer-2-line size-4" />
</ActionButton>
)}
<div
className="cursor-pointer rounded-md p-1 text-text-tertiary hover:text-text-destructive"
onClick={(e) => {
e.stopPropagation()
onDelete?.()
}}
onMouseOver={() => setIsDeleting(true)}
onMouseLeave={() => setIsDeleting(false)}
>
<span className="i-ri-delete-bin-line size-4" />
</div>
</div>
{!isError &&
!uninstalled &&
!noAuth &&
!versionMismatch &&
!isShowCanNotChooseMCPTip &&
showSwitch && (
<div className="mr-1" onClick={(e) => e.stopPropagation()}>
<Switch size="md" checked={switchValue ?? false} onCheckedChange={onSwitchChange} />
</div>
)}
{isShowCanNotChooseMCPTip && <McpToolNotSupportTooltip />}
{!isError && !uninstalled && !versionMismatch && noAuth && (
<Button variant="secondary" size="small">
{t(($) => $.notAuthorized, { ns: 'tools' })}
<StatusDot className="ml-2" status="warning" />
</Button>
)}
{!isError && !uninstalled && !versionMismatch && authRemoved && (
<Button variant="secondary" size="small">
{t(($) => $['auth.authRemoved'], { ns: 'plugin' })}
<StatusDot className="ml-2" status="error" />
</Button>
)}
{!isError && !uninstalled && versionMismatch && installInfo && (
<div onClick={(e) => e.stopPropagation()}>
<SwitchPluginVersion
className="-mt-1"
uniqueIdentifier={installInfo}
tooltip={
<div className="w-45" data-testid="tooltip-content">
<div
className="mb-1.5 font-semibold text-text-secondary"
data-testid="tooltip-content-title"
>
{t(($) => $['detailPanel.toolSelector.unsupportedTitle'], { ns: 'plugin' })}
</div>
<div className="mb-1.5 text-text-tertiary" data-testid="tooltip-content-body">
{`${t(($) => $['detailPanel.toolSelector.unsupportedContent'], { ns: 'plugin' })} ${t(($) => $['detailPanel.toolSelector.unsupportedContent2'], { ns: 'plugin' })}`}
</div>
</div>
}
onChange={() => {
onInstall?.()
}}
/>
</div>
)}
{!isError && uninstalled && installInfo && (
<InstallPluginButton
onClick={(e) => e.stopPropagation()}
size="small"
uniqueIdentifier={installInfo}
onSuccess={() => {
onInstall?.()
}}
/>
)}
{isError && (
<Popover>
<PopoverTrigger
openOnHover
aria-label={
typeof errorTip === 'string'
? errorTip
: t(($) => $['detailPanel.toolSelector.unsupportedTitle'], { ns: 'plugin' })
}
className="inline-flex border-0 bg-transparent p-0"
>
<span className="i-ri-error-warning-fill size-4 text-text-destructive" />
</PopoverTrigger>
<PopoverContent popupClassName="px-3 py-2 system-xs-regular text-text-tertiary">
{errorTip}
</PopoverContent>
</Popover>
)}
</div>
)
}
export default ToolItem