dify/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx
2025-01-09 16:18:08 +08:00

150 lines
4.4 KiB
TypeScript

import React from 'react'
import { useTranslation } from 'react-i18next'
import {
RiAddLine,
RiArrowDropDownLine,
RiQuestionLine,
} from '@remixicon/react'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import Divider from '@/app/components/base/divider'
import type { ToolValue } from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import cn from '@/utils/classnames'
type Props = {
disabled?: boolean
value: ToolValue[]
label: string
required?: boolean
tooltip?: any
supportCollapse?: boolean
scope?: string
onChange: (value: ToolValue[]) => void
}
const MultipleToolSelector = ({
disabled,
value = [],
label,
required,
tooltip,
supportCollapse,
scope,
onChange,
}: Props) => {
const { t } = useTranslation()
const enabledCount = value.filter(item => item.enabled).length
// collapse control
const [collapse, setCollapse] = React.useState(false)
const handleCollapse = () => {
if (supportCollapse)
setCollapse(!collapse)
}
// add tool
const [open, setOpen] = React.useState(false)
const handleAdd = (val: ToolValue) => {
const newValue = [...value, val]
// deduplication
const deduplication = newValue.reduce((acc, cur) => {
if (!acc.find(item => item.provider_name === cur.provider_name && item.tool_name === cur.tool_name))
acc.push(cur)
return acc
}, [] as ToolValue[])
// update value
onChange(deduplication)
setOpen(false)
}
// delete tool
const handleDelete = (index: number) => {
const newValue = [...value]
newValue.splice(index, 1)
onChange(newValue)
}
// configure tool
const handleConfigure = (val: ToolValue, index: number) => {
const newValue = [...value]
newValue[index] = val
onChange(newValue)
}
return (
<>
<div className='flex items-center mb-1'>
<div
className={cn('relative grow flex items-center gap-0.5', supportCollapse && 'cursor-pointer')}
onClick={handleCollapse}
>
<div className='h-6 flex items-center text-text-secondary system-sm-semibold-uppercase'>{label}</div>
{required && <div className='text-red-500'>*</div>}
{tooltip && (
<Tooltip
popupContent={tooltip}
needsDelay
>
<div><RiQuestionLine className='w-3.5 h-3.5 text-text-quaternary hover:text-text-tertiary'/></div>
</Tooltip>
)}
{supportCollapse && (
<div className='absolute -left-4 top-1'>
<RiArrowDropDownLine
className={cn(
'w-4 h-4 text-text-tertiary',
collapse && 'transform -rotate-90',
)}
/>
</div>
)}
</div>
{value.length > 0 && (
<>
<div className='flex items-center gap-1 text-text-tertiary system-xs-medium'>
<span>{`${enabledCount}/${value.length}`}</span>
<span>{t('appDebug.agent.tools.enabled')}</span>
</div>
<Divider type='vertical' className='ml-3 mr-1 h-3' />
</>
)}
{!disabled && (
<ActionButton className='mx-1' onClick={() => setOpen(!open)}>
<RiAddLine className='w-4 h-4' />
</ActionButton>
)}
</div>
{!collapse && (
<>
<ToolSelector
scope={scope}
value={undefined}
onSelect={handleAdd}
controlledState={open}
onControlledStateChange={setOpen}
trigger={
<div className=''></div>
}
/>
{value.length === 0 && (
<div className='p-3 flex justify-center rounded-[10px] bg-background-section text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.empty')}</div>
)}
{value.length > 0 && value.map((item, index) => (
<div className='mb-1' key={index}>
<ToolSelector
scope={scope}
value={item}
onSelect={item => handleConfigure(item, index)}
onDelete={() => handleDelete(index)}
supportEnableSwitch
/>
</div>
))}
</>
)}
</>
)
}
export default MultipleToolSelector