diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index 66fe85a170..d78d4dba0a 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -18,7 +18,6 @@ import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' import Indicator from '@/app/components/header/indicator' import Switch from '@/app/components/base/switch' -import Toast from '@/app/components/base/toast' import ConfigContext from '@/context/debug-configuration' import type { AgentTool } from '@/types/app' import { type Collection, CollectionType } from '@/app/components/tools/types' @@ -26,8 +25,6 @@ import { MAX_TOOLS_NUM } from '@/config' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import Tooltip from '@/app/components/base/tooltip' import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' -import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' -import { updateBuiltInToolCredential } from '@/service/tools' import cn from '@/utils/classnames' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types' @@ -57,13 +54,7 @@ const AgentTools: FC = () => { const formattingChangedDispatcher = useFormattingChangedDispatcher() const [currentTool, setCurrentTool] = useState(null) - const currentCollection = useMemo(() => { - if (!currentTool) return null - const collection = collectionList.find(collection => canFindTool(collection.id, currentTool?.provider_id) && collection.type === currentTool?.provider_type) - return collection - }, [currentTool, collectionList]) const [isShowSettingTool, setIsShowSettingTool] = useState(false) - const [isShowSettingAuth, setShowSettingAuth] = useState(false) const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => { const collection = collectionList.find( collection => @@ -100,17 +91,6 @@ const AgentTools: FC = () => { formattingChangedDispatcher() } - const handleToolAuthSetting = (value: AgentToolWithMoreInfo) => { - const newModelConfig = produce(modelConfig, (draft) => { - const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === value?.collection?.id && item.tool_name === value?.tool_name) - if (tool) - (tool as AgentTool).notAuthor = false - }) - setModelConfig(newModelConfig) - setIsShowSettingTool(false) - formattingChangedDispatcher() - } - const [isDeleting, setIsDeleting] = useState(-1) const getToolValue = (tool: ToolDefaultValue) => { return { @@ -144,6 +124,20 @@ const AgentTools: FC = () => { return item.provider_name } + const handleAuthorizationItemClick = useCallback((credentialId: string) => { + const newModelConfig = produce(modelConfig, (draft) => { + const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === currentTool?.provider_id) + if (tool) + (tool as AgentTool).credential_id = credentialId + }) + setCurrentTool({ + ...currentTool, + credential_id: credentialId, + } as any) + setModelConfig(newModelConfig) + formattingChangedDispatcher() + }, [currentTool, modelConfig, setModelConfig, formattingChangedDispatcher]) + return ( <> { {item.notAuthor && ( ) - }, [credentialId, credentials]) + }, [credentialId, credentials, t]) const extraAuthorizationItems: Credential[] = [ { id: '__workspace_default__', - name: 'Workspace default', + name: t('plugin.auth.workspaceDefault'), provider: '', - is_default: false, + is_default: !credentialId, isWorkspaceDefault: true, }, ] @@ -100,6 +102,8 @@ const AuthorizedInNode = ({ disableSetDefault onItemClick={handleAuthorizationItemClick} extraAuthorizationItems={extraAuthorizationItems} + showItemSelectedIcon + selectedCredentialId={credentialId || '__workspace_default__'} /> ) } diff --git a/web/app/components/plugins/plugin-auth/authorized/index.tsx b/web/app/components/plugins/plugin-auth/authorized/index.tsx index 13fec807f0..5762bad2da 100644 --- a/web/app/components/plugins/plugin-auth/authorized/index.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/index.tsx @@ -31,6 +31,7 @@ import { useDeletePluginCredentialHook, useInvalidPluginCredentialInfoHook, useSetPluginDefaultCredentialHook, + useUpdatePluginCredentialHook, } from '../hooks/use-credential' type AuthorizedProps = { @@ -49,6 +50,8 @@ type AuthorizedProps = { disableSetDefault?: boolean onItemClick?: (id: string) => void extraAuthorizationItems?: Credential[] + showItemSelectedIcon?: boolean + selectedCredentialId?: string } const Authorized = ({ pluginPayload, @@ -66,6 +69,8 @@ const Authorized = ({ disableSetDefault, onItemClick, extraAuthorizationItems, + showItemSelectedIcon, + selectedCredentialId, }: AuthorizedProps) => { const { t } = useTranslation() const { notify } = useToastContext() @@ -125,6 +130,17 @@ const Authorized = ({ }) invalidatePluginCredentialInfo() }, [setPluginDefaultCredential, invalidatePluginCredentialInfo, notify, t]) + const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) + const handleRename = useCallback(async (payload: { + credential_id: string + name: string + }) => { + await updatePluginCredential(payload) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + }, [updatePluginCredential, notify, t]) return ( <> @@ -149,7 +165,12 @@ const Authorized = ({ isOpen && 'bg-components-button-secondary-bg-hover', )}> - {credentials.length} Authorizations + {credentials.length}  + { + credentials.length > 1 + ? t('plugin.auth.authorizations') + : t('plugin.auth.authorization') + } ) @@ -160,31 +181,36 @@ const Authorized = ({ 'max-h-[360px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg', popupClassName, )}> - { - !!extraAuthorizationItems?.length && ( -
- { - extraAuthorizationItems.map(credential => ( - - )) - } -
- ) - }
+ { + !!extraAuthorizationItems?.length && ( +
+ { + extraAuthorizationItems.map(credential => ( + + )) + } +
+ ) + } { !!oAuthCredentials.length && (
-
+
OAuth
{ @@ -192,6 +218,15 @@ const Authorized = ({ )) } @@ -201,7 +236,10 @@ const Authorized = ({ { !!apiKeyCredentials.length && (
-
+
API Keys
{ @@ -214,7 +252,11 @@ const Authorized = ({ onEdit={handleEdit} onSetDefault={handleSetDefault} disableSetDefault={disableSetDefault} + disableRename onItemClick={onItemClick} + onRename={handleRename} + showSelectedIcon={showItemSelectedIcon} + selectedCredentialId={selectedCredentialId} /> )) } diff --git a/web/app/components/plugins/plugin-auth/authorized/item.tsx b/web/app/components/plugins/plugin-auth/authorized/item.tsx index f54ef5ac9b..4c0f88d985 100644 --- a/web/app/components/plugins/plugin-auth/authorized/item.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/item.tsx @@ -1,8 +1,11 @@ import { memo, useMemo, + useState, } from 'react' +import { useTranslation } from 'react-i18next' import { + RiCheckLine, RiDeleteBinLine, RiEditLine, RiEqualizer2Line, @@ -12,6 +15,8 @@ import Badge from '@/app/components/base/badge' import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import cn from '@/utils/classnames' import type { Credential } from '../types' import { CredentialTypeEnum } from '../types' @@ -21,11 +26,17 @@ type ItemProps = { onDelete?: (id: string) => void onEdit?: (id: string, values: Record) => void onSetDefault?: (id: string) => void + onRename?: (payload: { + credential_id: string + name: string + }) => void disableRename?: boolean disableEdit?: boolean disableDelete?: boolean disableSetDefault?: boolean onItemClick?: (id: string) => void + showSelectedIcon?: boolean + selectedCredentialId?: string } const Item = ({ credential, @@ -33,12 +44,18 @@ const Item = ({ onDelete, onEdit, onSetDefault, + onRename, disableRename, disableEdit, disableDelete, disableSetDefault, onItemClick, + showSelectedIcon, + selectedCredentialId, }: ItemProps) => { + const { t } = useTranslation() + const [renaming, setRenaming] = useState(false) + const [renameValue, setRenameValue] = useState(credential.name) const isOAuth = credential.credential_type === CredentialTypeEnum.OAUTH2 const showAction = useMemo(() => { return !(disableRename && disableEdit && disableDelete && disableSetDefault) @@ -47,27 +64,77 @@ const Item = ({ return (
onItemClick?.(credential.id)} > -
- -
- {credential.name} -
- { - credential.is_default && ( - - Default - - ) - } -
{ - showAction && ( + renaming && ( +
+ setRenameValue(e.target.value)} + placeholder={t('common.placeholder.input')} + /> + + +
+ ) + } + { + !renaming && ( +
+ { + showSelectedIcon && ( +
+ { + selectedCredentialId === credential.id && ( + + ) + } +
+ ) + } + +
+ {credential.name} +
+ { + credential.is_default && ( + + {t('plugin.auth.default')} + + ) + } +
+ ) + } + { + showAction && !renaming && (
{ !credential.is_default && !disableSetDefault && ( @@ -79,14 +146,21 @@ const Item = ({ onSetDefault?.(credential.id) }} > - Set as default + {t('plugin.auth.setDefault')} ) } { - isOAuth && !disableRename && ( - - + !disableRename && ( + + { + e.stopPropagation() + setRenaming(true) + setRenameValue(credential.name) + }} + > @@ -94,7 +168,7 @@ const Item = ({ } { !isOAuth && !disableEdit && ( - + { @@ -116,15 +190,16 @@ const Item = ({ } { !disableDelete && ( - + { e.stopPropagation() onDelete?.(credential.id) }} > - + ) diff --git a/web/app/components/plugins/plugin-auth/hooks/use-credential.ts b/web/app/components/plugins/plugin-auth/hooks/use-credential.ts index 1fc9f0e012..3b2080253c 100644 --- a/web/app/components/plugins/plugin-auth/hooks/use-credential.ts +++ b/web/app/components/plugins/plugin-auth/hooks/use-credential.ts @@ -3,8 +3,12 @@ import { useDeletePluginCredential, useGetPluginCredentialInfo, useGetPluginCredentialSchema, + useGetPluginOAuthClientSchema, + useGetPluginOAuthCustomClientSchema, + useGetPluginOAuthUrl, useInvalidPluginCredentialInfo, useSetPluginDefaultCredential, + useSetPluginOAuthCustomClient, useUpdatePluginCredential, } from '@/service/use-plugins-auth' import { useGetApi } from './use-get-api' @@ -51,3 +55,27 @@ export const useUpdatePluginCredentialHook = (pluginPayload: PluginPayload) => { return useUpdatePluginCredential(apiMap.updateCredential) } + +export const useGetPluginOAuthUrlHook = (pluginPayload: PluginPayload) => { + const apiMap = useGetApi(pluginPayload) + + return useGetPluginOAuthUrl(apiMap.getOauthUrl) +} + +export const useGetPluginOAuthClientSchemaHook = (pluginPayload: PluginPayload) => { + const apiMap = useGetApi(pluginPayload) + + return useGetPluginOAuthClientSchema(apiMap.getOauthClientSchema) +} + +export const useSetPluginOAuthCustomClientHook = (pluginPayload: PluginPayload) => { + const apiMap = useGetApi(pluginPayload) + + return useSetPluginOAuthCustomClient(apiMap.setCustomOauthClient) +} + +export const useGetPluginOAuthCustomClientSchemaHook = (pluginPayload: PluginPayload) => { + const apiMap = useGetApi(pluginPayload) + + return useGetPluginOAuthCustomClientSchema(apiMap.getCustomOAuthClient) +} diff --git a/web/app/components/plugins/plugin-auth/index.tsx b/web/app/components/plugins/plugin-auth/index.tsx index f5b2f0c457..e4f6ae8b2f 100644 --- a/web/app/components/plugins/plugin-auth/index.tsx +++ b/web/app/components/plugins/plugin-auth/index.tsx @@ -1,5 +1,6 @@ export { default as PluginAuth } from './plugin-auth' export { default as Authorized } from './authorized' export { default as AuthorizedInNode } from './authorized-in-node' +export { default as PluginAuthInAgent } from './plugin-auth-in-agent' export { usePluginAuth } from './hooks/use-plugin-auth' export * from './types' diff --git a/web/app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx b/web/app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx new file mode 100644 index 0000000000..4c7b885226 --- /dev/null +++ b/web/app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx @@ -0,0 +1,120 @@ +import { + memo, + useCallback, + useState, +} from 'react' +import { RiArrowDownSLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import Authorize from './authorize' +import Authorized from './authorized' +import type { + Credential, + PluginPayload, +} from './types' +import { usePluginAuth } from './hooks/use-plugin-auth' +import Button from '@/app/components/base/button' +import Indicator from '@/app/components/header/indicator' +import cn from '@/utils/classnames' + +type PluginAuthInAgentProps = { + pluginPayload: PluginPayload + credentialId?: string + onAuthorizationItemClick?: (id: string) => void +} +const PluginAuthInAgent = ({ + pluginPayload, + credentialId, + onAuthorizationItemClick, +}: PluginAuthInAgentProps) => { + const { t } = useTranslation() + const [isOpen, setIsOpen] = useState(false) + const { + isAuthorized, + canOAuth, + canApiKey, + credentials, + disabled, + } = usePluginAuth(pluginPayload, true) + + const extraAuthorizationItems: Credential[] = [ + { + id: '__workspace_default__', + name: t('plugin.auth.workspaceDefault'), + provider: '', + is_default: !credentialId, + isWorkspaceDefault: true, + }, + ] + + const handleAuthorizationItemClick = useCallback((id: string) => { + onAuthorizationItemClick?.(id) + setIsOpen(false) + }, [ + onAuthorizationItemClick, + setIsOpen, + ]) + + const renderTrigger = useCallback((isOpen?: boolean) => { + let label = '' + let removed = false + if (!credentialId) { + label = t('plugin.auth.workspaceDefault') + } + else { + const credential = credentials.find(c => c.id === credentialId) + label = credential ? credential.name : t('plugin.auth.authRemoved') + removed = !credential + } + return ( + + ) + }, [credentialId, credentials, t]) + + return ( + <> + { + !isAuthorized && ( + + ) + } + { + isAuthorized && ( + + ) + } + + ) +} + +export default memo(PluginAuthInAgent) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 352bfd7a0f..53d01c37db 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -304,7 +304,7 @@ const DetailHeader = ({
- + { category === PluginType.tool && ( = ({ } as any) } - // authorization - const { isCurrentWorkspaceManager } = useAppContext() - const [isShowSettingAuth, setShowSettingAuth] = useState(false) - const handleCredentialSettingUpdate = () => { - invalidateAllBuiltinTools() - Toast.notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - setShowSettingAuth(false) - onShowChange(false) - } - - const { mutate: updatePermission } = useUpdateProviderCredentials({ - onSuccess: handleCredentialSettingUpdate, - }) - // install from marketplace const currentTool = useMemo(() => { return currentProvider?.tools.find(tool => tool.name === value?.tool_name) @@ -226,6 +203,12 @@ const ToolSelector: FC = ({ invalidateAllBuiltinTools() invalidateInstalledPluginList() } + const handleAuthorizationItemClick = (id: string) => { + onSelect({ + ...value, + credential_id: id, + } as any) + } return ( <> @@ -264,7 +247,6 @@ const ToolSelector: FC = ({ onSwitchChange={handleEnabledChange} onDelete={onDelete} noAuth={currentProvider && currentTool && !currentProvider.is_team_authorization} - onAuth={() => setShowSettingAuth(true)} uninstalled={!currentProvider && inMarketPlace} versionMismatch={currentProvider && inMarketPlace && !currentTool} installInfo={manifest?.latest_package_identifier} @@ -284,171 +266,131 @@ const ToolSelector: FC = ({ )} -
- {!isShowSettingAuth && ( - <> -
{t(`plugin.detailPanel.toolSelector.${isEdit ? 'toolSetting' : 'title'}`)}
- {/* base form */} -
-
-
{t('plugin.detailPanel.toolSelector.toolLabel')}
- - } - isShow={panelShowState || isShowChooseTool} - onShowChange={trigger ? onPanelShowStateChange as any : setIsShowChooseTool} - disabled={false} - supportAddCustomTool - onSelect={handleSelectTool} - onSelectMultiple={handleSelectMultipleTool} - scope={scope} - selectedTools={selectedTools} - canChooseMCPTool={canChooseMCPTool} - /> -
-
-
{t('plugin.detailPanel.toolSelector.descriptionLabel')}
-