multiple tool selector

This commit is contained in:
JzoNg 2024-12-27 17:29:21 +08:00
parent a863e9f674
commit 3c85363392
4 changed files with 111 additions and 28 deletions

View File

@ -7,6 +7,7 @@ import ActionList from './action-list'
import ModelList from './model-list'
import AgentStrategyList from './agent-strategy-list'
import Drawer from '@/app/components/base/drawer'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import type { PluginDetail } from '@/app/components/plugins/types'
import cn from '@/utils/classnames'
@ -58,13 +59,15 @@ const PluginDetailPanel: FC<Props> = ({
{!!detail.declaration.agent_strategy && <AgentStrategyList detail={detail} />}
{!!detail.declaration.endpoint && <EndpointList detail={detail} />}
{!!detail.declaration.model && <ModelList detail={detail} />}
{/* <div className='px-4 py-2'>
<MultipleToolSelector
value={value || []}
label='TOOLS'
onChange={testChange}
/>
</div> */}
{false && (
<div className='px-4 py-2'>
<MultipleToolSelector
value={value || []}
label='TOOLS'
onChange={testChange}
/>
</div>
)}
</div>
</>
)}

View File

@ -5,10 +5,11 @@ import {
RiArrowDropDownLine,
RiQuestionLine,
} from '@remixicon/react'
import type { ToolValue } from '@/app/components/plugins/plugin-detail-panel/tool-selector'
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 = {
@ -18,25 +19,57 @@ type Props = {
required?: boolean
tooltip?: any
supportCollapse?: boolean
onChange: (value: ToolValue[]) => void
scope?: string
onChange: (value: ToolValue[]) => void
}
const MultipleToolSelector = ({
disabled,
value,
label,
required,
tooltip,
supportCollapse,
scope,
onChange,
}: Props) => {
const { t } = useTranslation()
// 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'>
@ -74,15 +107,38 @@ const MultipleToolSelector = ({
<Divider type='vertical' className='ml-3 mr-1 h-3' />
</>
)}
<ActionButton className='mx-1' onClick={() => {}}>
<RiAddLine className='w-4 h-4' />
</ActionButton>
{!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>
))}
</>
)}
</>

View File

@ -49,10 +49,11 @@ export type ToolValue = {
}
type Props = {
value?: ToolValue
disabled?: boolean
placement?: Placement
offset?: OffsetOptions
scope?: string
value?: ToolValue
onSelect: (tool: {
provider_name: string
tool_name: string
@ -60,8 +61,11 @@ type Props = {
extra?: Record<string, any>
}) => void
onDelete?: () => void
supportEnableSwitch?: boolean
supportAddCustomTool?: boolean
scope?: string
trigger?: React.ReactNode
controlledState?: boolean
onControlledStateChange?: (state: boolean) => void
}
const ToolSelector: FC<Props> = ({
value,
@ -71,6 +75,10 @@ const ToolSelector: FC<Props> = ({
onSelect,
onDelete,
scope,
supportEnableSwitch,
trigger,
controlledState,
onControlledStateChange,
}) => {
const { t } = useTranslation()
const [isShow, onShowChange] = useState(false)
@ -98,14 +106,13 @@ const ToolSelector: FC<Props> = ({
provider_name: tool.provider_id,
tool_name: tool.tool_name,
parameters: paramValues,
enabled: tool.is_team_authorization,
extra: {
description: '',
},
}
onSelect(toolValue)
setIsShowChooseTool(false)
// if (tool.provider_type === CollectionType.builtIn && tool.is_team_authorization)
// onShowChange(false)
}
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
@ -133,6 +140,13 @@ const ToolSelector: FC<Props> = ({
onSelect(toolValue as any)
}
const handleEnabledChange = (state: boolean) => {
onSelect({
...value,
enabled: state,
} as any)
}
// authorization
const { isCurrentWorkspaceManager } = useAppContext()
const [isShowSettingAuth, setShowSettingAuth] = useState(false)
@ -155,14 +169,15 @@ const ToolSelector: FC<Props> = ({
<PortalToFollowElem
placement={placement}
offset={offset}
open={isShow}
onOpenChange={onShowChange}
open={trigger ? controlledState : isShow}
onOpenChange={trigger ? onControlledStateChange : onShowChange}
>
<PortalToFollowElemTrigger
className='w-full'
onClick={handleTriggerClick}
>
{!value?.provider_name && (
{trigger}
{!trigger && !value?.provider_name && (
<ToolTrigger
isConfigure
open={isShow}
@ -170,16 +185,20 @@ const ToolSelector: FC<Props> = ({
provider={currentProvider}
/>
)}
{value?.provider_name && (
{!trigger && value?.provider_name && (
<ToolItem
open={isShow}
icon={currentProvider?.icon}
providerName={value.provider_name}
toolName={value.tool_name}
showSwitch={supportEnableSwitch}
switchValue={value.enabled}
onSwitchChange={handleEnabledChange}
onDelete={onDelete}
noAuth={currentProvider && !currentProvider.is_team_authorization}
onAuth={() => setShowSettingAuth(true)}
// uninstalled
// uninstalled TODO
// isError TODO
errorTip={<div className='space-y-1 text-xs'>
<h3 className='text-text-primary font-semibold'>{t('workflow.nodes.agent.pluginNotInstalled')}</h3>
<p className='text-text-secondary tracking-tight'>{t('workflow.nodes.agent.pluginNotInstalledDesc')}</p>

View File

@ -77,7 +77,10 @@ const ToolItem = ({
)}
<div
className='p-1 rounded-md text-text-tertiary cursor-pointer hover:text-text-destructive'
onClick={onDelete}
onClick={(e) => {
e.stopPropagation()
onDelete?.()
}}
onMouseOver={() => setIsDeleting(true)}
onMouseLeave={() => setIsDeleting(false)}
>
@ -85,11 +88,13 @@ const ToolItem = ({
</div>
</div>
{!isError && !uninstalled && !noAuth && showSwitch && (
<Switch
className='mr-1'
size='md'
defaultValue={switchValue}
onChange={onSwitchChange} />
<div className='mr-1' onClick={e => e.stopPropagation()}>
<Switch
size='md'
defaultValue={switchValue}
onChange={onSwitchChange}
/>
</div>
)}
{!isError && !uninstalled && noAuth && (
<Button variant='secondary' size='small' onClick={onAuth}>