mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
merge feat/plugins
This commit is contained in:
@ -7,7 +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 ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
|
||||
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'
|
||||
|
||||
@ -33,9 +33,6 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
console.log('tool change', val)
|
||||
setValue(val)
|
||||
}
|
||||
const testDelete = () => {
|
||||
setValue(undefined)
|
||||
}
|
||||
|
||||
if (!detail)
|
||||
return null
|
||||
@ -64,10 +61,10 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
{!!detail.declaration.model && <ModelList detail={detail} />}
|
||||
{false && (
|
||||
<div className='px-4 py-2'>
|
||||
<ToolSelector
|
||||
value={value}
|
||||
onSelect={item => testChange(item)}
|
||||
onDelete={testDelete}
|
||||
<MultipleToolSelector
|
||||
value={value || []}
|
||||
label='TOOLS'
|
||||
onChange={testChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,12 +1,148 @@
|
||||
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 = {
|
||||
value: any[]
|
||||
disabled?: boolean
|
||||
value: ToolValue[]
|
||||
label: string
|
||||
required?: boolean
|
||||
tooltip?: any
|
||||
supportCollapse?: boolean
|
||||
scope?: string
|
||||
onChange: (value: ToolValue[]) => void
|
||||
}
|
||||
|
||||
const MultipleToolSelector = ({ value }: Props) => {
|
||||
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></div>
|
||||
<>
|
||||
<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-error-main'>*</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>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -40,16 +40,20 @@ import type {
|
||||
} from '@floating-ui/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type ToolValue = {
|
||||
provider_name: string
|
||||
tool_name: string
|
||||
parameters?: Record<string, any>
|
||||
enabled?: boolean
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
|
||||
type Props = {
|
||||
value?: {
|
||||
provider_name: string
|
||||
tool_name: string
|
||||
parameters?: Record<string, any>
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
disabled?: boolean
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
scope?: string
|
||||
value?: ToolValue
|
||||
onSelect: (tool: {
|
||||
provider_name: string
|
||||
tool_name: string
|
||||
@ -57,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,
|
||||
@ -68,6 +75,10 @@ const ToolSelector: FC<Props> = ({
|
||||
onSelect,
|
||||
onDelete,
|
||||
scope,
|
||||
supportEnableSwitch,
|
||||
trigger,
|
||||
controlledState,
|
||||
onControlledStateChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShow, onShowChange] = useState(false)
|
||||
@ -95,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>) => {
|
||||
@ -130,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)
|
||||
@ -152,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}
|
||||
@ -167,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>
|
||||
|
||||
@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user