From 5e96e4dda66c471f8ad95bad06e47d0c07aa5081 Mon Sep 17 00:00:00 2001 From: zhsama Date: Fri, 28 Nov 2025 18:12:11 +0800 Subject: [PATCH] feat: introduce CredentialConfigHeader and EndUserCredentialSection components for enhanced plugin authentication UI --- .../config/agent/agent-tools/index.tsx | 24 -- .../plugin-auth/credential-config-header.tsx | 116 ++++++ .../end-user-credential-section.tsx | 176 ++++++++++ .../components/plugins/plugin-auth/index.tsx | 4 + .../plugin-auth/plugin-auth-in-agent.tsx | 332 ++++-------------- .../plugins/plugin-auth/plugin-auth.tsx | 136 +------ 6 files changed, 386 insertions(+), 402 deletions(-) create mode 100644 web/app/components/plugins/plugin-auth/credential-config-header.tsx create mode 100644 web/app/components/plugins/plugin-auth/end-user-credential-section.tsx 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 5e03d3e529..f3f58fa9f6 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 GroupAuthControl from './group-auth-control' import ConfigContext from '@/context/debug-configuration' import type { AgentTool } from '@/types/app' import { type Collection, CollectionType } from '@/app/components/tools/types' @@ -237,24 +236,6 @@ const AgentTools: FC = () => { ?
: }
{group.providerName}
-
- !!t.credential_id)?.credential_id} - onChange={(id) => { - const newModelConfig = produce(modelConfig, (draft) => { - draft.agentConfig.tools.forEach((tool: any) => { - if (tool.provider_id === group.providerId) - tool.credential_id = id - }) - }) - setModelConfig(newModelConfig) - formattingChangedDispatcher() - }} - /> -
{group.tools.every(t => t.notAuthor) && ( + + +
+
+ {canOAuth && ( + { + setShowAddMenu(false) + onCredentialAdded?.() + }} + /> + )} + {canApiKey && ( + { + setShowAddMenu(false) + onCredentialAdded?.() + }} + /> + )} +
+
+
+ +
+ ) +} + +export default memo(CredentialConfigHeader) diff --git a/web/app/components/plugins/plugin-auth/end-user-credential-section.tsx b/web/app/components/plugins/plugin-auth/end-user-credential-section.tsx new file mode 100644 index 0000000000..ccf6ae6d23 --- /dev/null +++ b/web/app/components/plugins/plugin-auth/end-user-credential-section.tsx @@ -0,0 +1,176 @@ +import { + memo, + useCallback, + useEffect, + useMemo, + useState, +} from 'react' +import type { ReactNode } from 'react' +import { + RiArrowDownSLine, + RiEqualizer2Line, + RiKey2Line, + RiUserStarLine, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import AddOAuthButton from './authorize/add-oauth-button' +import AddApiKeyButton from './authorize/add-api-key-button' +import type { PluginPayload } from './types' +import cn from '@/utils/classnames' +import Switch from '@/app/components/base/switch' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +export type EndUserCredentialSectionProps = { + pluginPayload: PluginPayload + canOAuth?: boolean + canApiKey?: boolean + disabled?: boolean + useEndUserCredentialEnabled?: boolean + endUserCredentialType?: string + onEndUserCredentialChange?: (enabled: boolean) => void + onEndUserCredentialTypeChange?: (type: string) => void + onCredentialAdded?: () => void + className?: string +} + +const EndUserCredentialSection = ({ + pluginPayload, + canOAuth, + canApiKey, + disabled, + useEndUserCredentialEnabled, + endUserCredentialType, + onEndUserCredentialChange, + onEndUserCredentialTypeChange, + onCredentialAdded, + className, +}: EndUserCredentialSectionProps) => { + const { t } = useTranslation() + const [showEndUserTypeMenu, setShowEndUserTypeMenu] = useState(false) + + const availableEndUserTypes = useMemo(() => { + const list: { value: string; label: string; icon: ReactNode }[] = [] + if (canOAuth) { + list.push({ + value: 'oauth2', + label: t('plugin.auth.endUserCredentials.optionOAuth'), + icon: , + }) + } + if (canApiKey) { + list.push({ + value: 'api-key', + label: t('plugin.auth.endUserCredentials.optionApiKey'), + icon: , + }) + } + return list + }, [canOAuth, canApiKey, t]) + + const endUserCredentialLabel = useMemo(() => { + const found = availableEndUserTypes.find(item => item.value === endUserCredentialType) + return found?.label || availableEndUserTypes[0]?.label || '-' + }, [availableEndUserTypes, endUserCredentialType]) + + useEffect(() => { + if (!useEndUserCredentialEnabled) + return + if (!availableEndUserTypes.length) + return + const isValid = availableEndUserTypes.some(item => item.value === endUserCredentialType) + if (!isValid) + onEndUserCredentialTypeChange?.(availableEndUserTypes[0].value) + }, [useEndUserCredentialEnabled, endUserCredentialType, availableEndUserTypes, onEndUserCredentialTypeChange]) + + const handleSelectEndUserType = useCallback((value: string) => { + onEndUserCredentialTypeChange?.(value) + setShowEndUserTypeMenu(false) + }, [onEndUserCredentialTypeChange]) + + return ( +
+ +
+
+
+
+ {t('plugin.auth.endUserCredentials.title')} +
+
+ {t('plugin.auth.endUserCredentials.desc')} +
+
+ +
+ { + useEndUserCredentialEnabled && availableEndUserTypes.length > 0 && ( +
+
+ {t('plugin.auth.endUserCredentials.typeLabel')} +
+ + + + + +
+
+ {canOAuth && ( + { + handleSelectEndUserType('oauth2') + onCredentialAdded?.() + }} + /> + )} + {canApiKey && ( + { + handleSelectEndUserType('api-key') + onCredentialAdded?.() + }} + /> + )} +
+
+
+
+
+ ) + } +
+
+ ) +} + +export default memo(EndUserCredentialSection) diff --git a/web/app/components/plugins/plugin-auth/index.tsx b/web/app/components/plugins/plugin-auth/index.tsx index ee6f839590..c9b231907a 100644 --- a/web/app/components/plugins/plugin-auth/index.tsx +++ b/web/app/components/plugins/plugin-auth/index.tsx @@ -2,6 +2,10 @@ 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 { default as CredentialConfigHeader } from './credential-config-header' +export type { CredentialConfigHeaderProps } from './credential-config-header' +export { default as EndUserCredentialSection } from './end-user-credential-section' +export type { EndUserCredentialSectionProps } from './end-user-credential-section' export { usePluginAuth } from './hooks/use-plugin-auth' export { default as PluginAuthInDataSourceNode } from './plugin-auth-in-datasource-node' export { default as AuthorizedInDataSourceNode } from './authorized-in-data-source-node' 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 index d4b45edb26..4183cf6766 100644 --- a/web/app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx +++ b/web/app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx @@ -1,23 +1,16 @@ import { memo, useCallback, - useEffect, - useMemo, useState, } from 'react' -import type { ReactNode } from 'react' import { - RiAddLine, RiArrowDownSLine, - RiEqualizer2Line, - RiKey2Line, - RiUserStarLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import Authorize from './authorize' import Authorized from './authorized' -import AddOAuthButton from './authorize/add-oauth-button' -import AddApiKeyButton from './authorize/add-api-key-button' +import CredentialConfigHeader from './credential-config-header' +import EndUserCredentialSection from './end-user-credential-section' import type { Credential, PluginPayload, @@ -26,12 +19,6 @@ 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' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Switch from '@/app/components/base/switch' type PluginAuthInAgentProps = { pluginPayload: PluginPayload @@ -53,8 +40,6 @@ const PluginAuthInAgent = ({ }: PluginAuthInAgentProps) => { const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) - const [showAddMenu, setShowAddMenu] = useState(false) - const [showEndUserTypeMenu, setShowEndUserTypeMenu] = useState(false) const { isAuthorized, canOAuth, @@ -66,6 +51,8 @@ const PluginAuthInAgent = ({ hasOAuthClientConfigured, } = usePluginAuth(pluginPayload, true) + const configuredDisabled = !!useEndUserCredentialEnabled + const extraAuthorizationItems: Credential[] = [ { id: '__workspace_default__', @@ -122,251 +109,86 @@ const PluginAuthInAgent = ({ ) }, [credentialId, credentials, t]) - const availableEndUserTypes = useMemo(() => { - const list: { value: string; label: string; icon: ReactNode }[] = [] - if (canOAuth) { - list.push({ - value: 'oauth2', - label: t('plugin.auth.endUserCredentials.optionOAuth'), - icon: , - }) - } - if (canApiKey) { - list.push({ - value: 'api-key', - label: t('plugin.auth.endUserCredentials.optionApiKey'), - icon: , - }) - } - return list - }, [canOAuth, canApiKey, t]) - - const endUserCredentialLabel = useMemo(() => { - const found = availableEndUserTypes.find(item => item.value === endUserCredentialType) - return found?.label || availableEndUserTypes[0]?.label || '-' - }, [availableEndUserTypes, endUserCredentialType]) - - useEffect(() => { - if (!useEndUserCredentialEnabled) - return - if (!availableEndUserTypes.length) - return - const isValid = availableEndUserTypes.some(item => item.value === endUserCredentialType) - if (!isValid) - onEndUserCredentialTypeChange?.(availableEndUserTypes[0].value) - }, [useEndUserCredentialEnabled, endUserCredentialType, availableEndUserTypes, onEndUserCredentialTypeChange]) - - const handleSelectEndUserType = useCallback((value: string) => { - onEndUserCredentialTypeChange?.(value) - setShowEndUserTypeMenu(false) - }, [onEndUserCredentialTypeChange]) - const shouldShowAuthorizeCard = !credentials.length && (canOAuth || canApiKey || hasOAuthClientConfigured) - const endUserSection = ( -
- -
-
-
-
- {t('plugin.auth.endUserCredentials.title')} -
-
- {t('plugin.auth.endUserCredentials.desc')} -
-
- -
- { - useEndUserCredentialEnabled && availableEndUserTypes.length > 0 && ( -
-
- {t('plugin.auth.endUserCredentials.typeLabel')} -
- - - - - -
-
- {canOAuth && ( - { - handleSelectEndUserType('oauth2') - invalidPluginCredentialInfo() - }} - /> - )} - {canApiKey && ( - { - handleSelectEndUserType('api-key') - invalidPluginCredentialInfo() - }} - /> - )} -
-
-
-
-
- ) - } -
-
- ) - return (
-
-
- -
-
- {t('plugin.auth.configuredCredentials.title')} -
-
- {t('plugin.auth.configuredCredentials.desc')} -
-
-
- - - - - -
-
- { - canOAuth && ( - { - setShowAddMenu(false) - invalidPluginCredentialInfo() - }} - /> - ) - } - { - canApiKey && ( - { - setShowAddMenu(false) - invalidPluginCredentialInfo() - }} - /> - ) - } -
-
-
-
+
+
- { - !isAuthorized && shouldShowAuthorizeCard && ( -
-
-
- +
+ { + !isAuthorized && shouldShowAuthorizeCard && ( +
+
+
+ +
-
- ) - } - { - !isAuthorized && !shouldShowAuthorizeCard && ( - - ) - } - { - isAuthorized && ( - - ) - } - {endUserSection} + ) + } + { + !isAuthorized && !shouldShowAuthorizeCard && ( + + ) + } + { + isAuthorized && ( + + ) + } +
+
) } diff --git a/web/app/components/plugins/plugin-auth/plugin-auth.tsx b/web/app/components/plugins/plugin-auth/plugin-auth.tsx index 6ba0a6f0a2..ef27dda518 100644 --- a/web/app/components/plugins/plugin-auth/plugin-auth.tsx +++ b/web/app/components/plugins/plugin-auth/plugin-auth.tsx @@ -1,17 +1,13 @@ import { memo, - useCallback, useEffect, useMemo, useState, } from 'react' -import type { ReactNode } from 'react' import { RiAddLine, RiArrowDownSLine, - RiEqualizer2Line, RiKey2Line, - RiUserStarLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { @@ -23,10 +19,10 @@ import Authorize from './authorize' import Authorized from './authorized' import AddApiKeyButton from './authorize/add-api-key-button' import AddOAuthButton from './authorize/add-oauth-button' +import EndUserCredentialSection from './end-user-credential-section' import Item from './authorized/item' import type { PluginPayload } from './types' import { usePluginAuth } from './hooks/use-plugin-auth' -import Switch from '@/app/components/base/switch' import cn from '@/utils/classnames' type PluginAuthProps = { @@ -63,50 +59,12 @@ const PluginAuth = ({ const shouldShowGuide = !!showConnectGuide const [showCredentialPanel, setShowCredentialPanel] = useState(false) const [showAddMenu, setShowAddMenu] = useState(false) - const [showEndUserTypeMenu, setShowEndUserTypeMenu] = useState(false) const configuredDisabled = !!endUserCredentialEnabled const shouldShowAuthorizeCard = useMemo(() => { const hasCredential = credentials.length > 0 const canAdd = canOAuth || canApiKey || hasOAuthClientConfigured return !hasCredential && canAdd }, [credentials.length, canOAuth, canApiKey, hasOAuthClientConfigured]) - const availableEndUserTypes = useMemo(() => { - const list: { value: string; label: string; icon: ReactNode }[] = [] - if (canOAuth) { - list.push({ - value: 'oauth2', - label: t('plugin.auth.endUserCredentials.optionOAuth'), - icon: , - }) - } - if (canApiKey) { - list.push({ - value: 'api-key', - label: t('plugin.auth.endUserCredentials.optionApiKey'), - icon: , - }) - } - return list - }, [canOAuth, canApiKey, t]) - const endUserCredentialLabel = useMemo(() => { - const found = availableEndUserTypes.find(item => item.value === endUserCredentialType) - return found?.label || availableEndUserTypes[0]?.label || '-' - }, [availableEndUserTypes, endUserCredentialType]) - - useEffect(() => { - if (!endUserCredentialEnabled) - return - if (!availableEndUserTypes.length) - return - const isValid = availableEndUserTypes.some(item => item.value === endUserCredentialType) - if (!isValid) - onEndUserCredentialTypeChange?.(availableEndUserTypes[0].value) - }, [endUserCredentialEnabled, endUserCredentialType, availableEndUserTypes, onEndUserCredentialTypeChange]) - - const handleSelectEndUserType = useCallback((value: string) => { - onEndUserCredentialTypeChange?.(value) - setShowEndUserTypeMenu(false) - }, [onEndUserCredentialTypeChange]) const containerClassName = useMemo(() => { if (showConnectGuide) return className @@ -145,86 +103,18 @@ const PluginAuth = ({ }, [credentials, t]) const endUserSwitch = ( -
-
- -
-
-
-
- {t('plugin.auth.endUserCredentials.title')} -
-
- {t('plugin.auth.endUserCredentials.desc')} -
-
- -
- { - endUserCredentialEnabled && availableEndUserTypes.length > 0 && ( -
-
- {t('plugin.auth.endUserCredentials.typeLabel')} -
- - - - - -
-
- {canOAuth && ( - { - handleSelectEndUserType('oauth2') - invalidPluginCredentialInfo() - }} - /> - )} - {canApiKey && ( - { - handleSelectEndUserType('api-key') - invalidPluginCredentialInfo() - }} - /> - )} -
-
-
-
-
- ) - } -
-
-
+ ) return (