mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 11:56:55 +08:00
feat: enhance agent tools with end user credential support
This commit is contained in:
parent
1400b9c6e2
commit
cb4670cd68
@ -33,7 +33,7 @@ import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTo
|
|||||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||||
import { useMittContextSelector } from '@/context/mitt-context'
|
import { useMittContextSelector } from '@/context/mitt-context'
|
||||||
|
|
||||||
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
|
type AgentToolWithMoreInfo = (AgentTool & { icon: any; collection?: Collection; use_end_user_credentials?: boolean; end_user_credential_type?: string }) | null
|
||||||
const AgentTools: FC = () => {
|
const AgentTools: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
||||||
@ -102,7 +102,9 @@ const AgentTools: FC = () => {
|
|||||||
tool_parameters: tool.params,
|
tool_parameters: tool.params,
|
||||||
notAuthor: !tool.is_team_authorization,
|
notAuthor: !tool.is_team_authorization,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
use_end_user_credentials: false,
|
||||||
|
end_user_credential_type: '',
|
||||||
|
} as any
|
||||||
}
|
}
|
||||||
const handleSelectTool = (tool: ToolDefaultValue) => {
|
const handleSelectTool = (tool: ToolDefaultValue) => {
|
||||||
const newModelConfig = produce(modelConfig, (draft) => {
|
const newModelConfig = produce(modelConfig, (draft) => {
|
||||||
@ -138,6 +140,34 @@ const AgentTools: FC = () => {
|
|||||||
formattingChangedDispatcher()
|
formattingChangedDispatcher()
|
||||||
}, [currentTool, modelConfig, setModelConfig, formattingChangedDispatcher])
|
}, [currentTool, modelConfig, setModelConfig, formattingChangedDispatcher])
|
||||||
|
|
||||||
|
const handleEndUserCredentialChange = useCallback((enabled: boolean) => {
|
||||||
|
const newModelConfig = produce(modelConfig, (draft) => {
|
||||||
|
const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === currentTool?.provider_id)
|
||||||
|
if (tool)
|
||||||
|
(tool as AgentTool).use_end_user_credentials = enabled
|
||||||
|
})
|
||||||
|
setCurrentTool({
|
||||||
|
...currentTool,
|
||||||
|
use_end_user_credentials: enabled,
|
||||||
|
} as any)
|
||||||
|
setModelConfig(newModelConfig)
|
||||||
|
formattingChangedDispatcher()
|
||||||
|
}, [currentTool, modelConfig, setModelConfig, formattingChangedDispatcher])
|
||||||
|
|
||||||
|
const handleEndUserCredentialTypeChange = useCallback((type: 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).end_user_credential_type = type
|
||||||
|
})
|
||||||
|
setCurrentTool({
|
||||||
|
...currentTool,
|
||||||
|
end_user_credential_type: type,
|
||||||
|
} as any)
|
||||||
|
setModelConfig(newModelConfig)
|
||||||
|
formattingChangedDispatcher()
|
||||||
|
}, [currentTool, modelConfig, setModelConfig, formattingChangedDispatcher])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Panel
|
<Panel
|
||||||
@ -315,6 +345,10 @@ const AgentTools: FC = () => {
|
|||||||
onHide={() => setIsShowSettingTool(false)}
|
onHide={() => setIsShowSettingTool(false)}
|
||||||
credentialId={currentTool?.credential_id}
|
credentialId={currentTool?.credential_id}
|
||||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||||
|
useEndUserCredentialEnabled={currentTool?.use_end_user_credentials}
|
||||||
|
endUserCredentialType={currentTool?.end_user_credential_type}
|
||||||
|
onEndUserCredentialChange={handleEndUserCredentialChange}
|
||||||
|
onEndUserCredentialTypeChange={handleEndUserCredentialTypeChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -42,6 +42,10 @@ type Props = {
|
|||||||
onSave?: (value: Record<string, any>) => void
|
onSave?: (value: Record<string, any>) => void
|
||||||
credentialId?: string
|
credentialId?: string
|
||||||
onAuthorizationItemClick?: (id: string) => void
|
onAuthorizationItemClick?: (id: string) => void
|
||||||
|
useEndUserCredentialEnabled?: boolean
|
||||||
|
endUserCredentialType?: string
|
||||||
|
onEndUserCredentialChange?: (enabled: boolean) => void
|
||||||
|
onEndUserCredentialTypeChange?: (type: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingBuiltInTool: FC<Props> = ({
|
const SettingBuiltInTool: FC<Props> = ({
|
||||||
@ -56,6 +60,10 @@ const SettingBuiltInTool: FC<Props> = ({
|
|||||||
onSave,
|
onSave,
|
||||||
credentialId,
|
credentialId,
|
||||||
onAuthorizationItemClick,
|
onAuthorizationItemClick,
|
||||||
|
useEndUserCredentialEnabled,
|
||||||
|
endUserCredentialType,
|
||||||
|
onEndUserCredentialChange,
|
||||||
|
onEndUserCredentialTypeChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { locale } = useContext(I18n)
|
const { locale } = useContext(I18n)
|
||||||
const language = getLanguage(locale)
|
const language = getLanguage(locale)
|
||||||
@ -220,6 +228,10 @@ const SettingBuiltInTool: FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
credentialId={credentialId}
|
credentialId={credentialId}
|
||||||
onAuthorizationItemClick={onAuthorizationItemClick}
|
onAuthorizationItemClick={onAuthorizationItemClick}
|
||||||
|
useEndUserCredentialEnabled={useEndUserCredentialEnabled}
|
||||||
|
endUserCredentialType={endUserCredentialType}
|
||||||
|
onEndUserCredentialChange={onEndUserCredentialChange}
|
||||||
|
onEndUserCredentialTypeChange={onEndUserCredentialTypeChange}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const usePluginAuth = (pluginPayload: PluginPayload, enable?: boolean) =>
|
|||||||
const canOAuth = data?.supported_credential_types.includes(CredentialTypeEnum.OAUTH2)
|
const canOAuth = data?.supported_credential_types.includes(CredentialTypeEnum.OAUTH2)
|
||||||
const canApiKey = data?.supported_credential_types.includes(CredentialTypeEnum.API_KEY)
|
const canApiKey = data?.supported_credential_types.includes(CredentialTypeEnum.API_KEY)
|
||||||
const invalidPluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
const invalidPluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
||||||
|
const hasOAuthClientConfigured = data?.is_oauth_custom_client_enabled
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isAuthorized,
|
isAuthorized,
|
||||||
@ -22,5 +23,6 @@ export const usePluginAuth = (pluginPayload: PluginPayload, enable?: boolean) =>
|
|||||||
disabled: !isCurrentWorkspaceManager,
|
disabled: !isCurrentWorkspaceManager,
|
||||||
notAllowCustomCredential: data?.allow_custom_token === false,
|
notAllowCustomCredential: data?.allow_custom_token === false,
|
||||||
invalidPluginCredentialInfo,
|
invalidPluginCredentialInfo,
|
||||||
|
hasOAuthClientConfigured: !!hasOAuthClientConfigured,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { RiArrowDownSLine } from '@remixicon/react'
|
import type { ReactNode } from 'react'
|
||||||
|
import {
|
||||||
|
RiAddLine,
|
||||||
|
RiArrowDownSLine,
|
||||||
|
RiEqualizer2Line,
|
||||||
|
RiKey2Line,
|
||||||
|
RiUserStarLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Authorize from './authorize'
|
import Authorize from './authorize'
|
||||||
import Authorized from './authorized'
|
import Authorized from './authorized'
|
||||||
|
import AddOAuthButton from './authorize/add-oauth-button'
|
||||||
|
import AddApiKeyButton from './authorize/add-api-key-button'
|
||||||
import type {
|
import type {
|
||||||
Credential,
|
Credential,
|
||||||
PluginPayload,
|
PluginPayload,
|
||||||
@ -15,19 +26,35 @@ import { usePluginAuth } from './hooks/use-plugin-auth'
|
|||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import cn from '@/utils/classnames'
|
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 = {
|
type PluginAuthInAgentProps = {
|
||||||
pluginPayload: PluginPayload
|
pluginPayload: PluginPayload
|
||||||
credentialId?: string
|
credentialId?: string
|
||||||
onAuthorizationItemClick?: (id: string) => void
|
onAuthorizationItemClick?: (id: string) => void
|
||||||
|
useEndUserCredentialEnabled?: boolean
|
||||||
|
endUserCredentialType?: string
|
||||||
|
onEndUserCredentialChange?: (enabled: boolean) => void
|
||||||
|
onEndUserCredentialTypeChange?: (type: string) => void
|
||||||
}
|
}
|
||||||
const PluginAuthInAgent = ({
|
const PluginAuthInAgent = ({
|
||||||
pluginPayload,
|
pluginPayload,
|
||||||
credentialId,
|
credentialId,
|
||||||
onAuthorizationItemClick,
|
onAuthorizationItemClick,
|
||||||
|
useEndUserCredentialEnabled,
|
||||||
|
endUserCredentialType,
|
||||||
|
onEndUserCredentialChange,
|
||||||
|
onEndUserCredentialTypeChange,
|
||||||
}: PluginAuthInAgentProps) => {
|
}: PluginAuthInAgentProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [showAddMenu, setShowAddMenu] = useState(false)
|
||||||
|
const [showEndUserTypeMenu, setShowEndUserTypeMenu] = useState(false)
|
||||||
const {
|
const {
|
||||||
isAuthorized,
|
isAuthorized,
|
||||||
canOAuth,
|
canOAuth,
|
||||||
@ -36,6 +63,7 @@ const PluginAuthInAgent = ({
|
|||||||
disabled,
|
disabled,
|
||||||
invalidPluginCredentialInfo,
|
invalidPluginCredentialInfo,
|
||||||
notAllowCustomCredential,
|
notAllowCustomCredential,
|
||||||
|
hasOAuthClientConfigured,
|
||||||
} = usePluginAuth(pluginPayload, true)
|
} = usePluginAuth(pluginPayload, true)
|
||||||
|
|
||||||
const extraAuthorizationItems: Credential[] = [
|
const extraAuthorizationItems: Credential[] = [
|
||||||
@ -94,10 +122,219 @@ const PluginAuthInAgent = ({
|
|||||||
)
|
)
|
||||||
}, [credentialId, credentials, t])
|
}, [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: <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (canApiKey) {
|
||||||
|
list.push({
|
||||||
|
value: 'api-key',
|
||||||
|
label: t('plugin.auth.endUserCredentials.optionApiKey'),
|
||||||
|
icon: <RiKey2Line className='h-4 w-4 text-text-tertiary' />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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 = (
|
||||||
|
<div className='flex items-start rounded-lg border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-3 py-3'>
|
||||||
|
<RiUserStarLine className='mt-0.5 h-4 w-4 text-text-tertiary' />
|
||||||
|
<div className='flex-1 space-y-3'>
|
||||||
|
<div className='flex items-center justify-between gap-3'>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<div className='system-sm-semibold text-text-primary'>
|
||||||
|
{t('plugin.auth.endUserCredentials.title')}
|
||||||
|
</div>
|
||||||
|
<div className='system-xs-regular text-text-tertiary'>
|
||||||
|
{t('plugin.auth.endUserCredentials.desc')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
size='md'
|
||||||
|
defaultValue={!!useEndUserCredentialEnabled}
|
||||||
|
onChange={onEndUserCredentialChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
useEndUserCredentialEnabled && availableEndUserTypes.length > 0 && (
|
||||||
|
<div className='flex items-center justify-between gap-3'>
|
||||||
|
<div className='system-sm-semibold text-text-primary'>
|
||||||
|
{t('plugin.auth.endUserCredentials.typeLabel')}
|
||||||
|
</div>
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={showEndUserTypeMenu}
|
||||||
|
onOpenChange={setShowEndUserTypeMenu}
|
||||||
|
placement='bottom-end'
|
||||||
|
offset={6}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger asChild>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='border-components-input-border flex h-9 min-w-[190px] items-center justify-between rounded-lg border bg-components-input-bg-normal px-3 text-left text-text-primary shadow-xs hover:bg-components-input-bg-hover'
|
||||||
|
onClick={() => setShowEndUserTypeMenu(v => !v)}
|
||||||
|
>
|
||||||
|
<span className='system-sm-semibold'>{endUserCredentialLabel}</span>
|
||||||
|
<RiArrowDownSLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
</button>
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[120]'>
|
||||||
|
<div className='w-[220px] rounded-xl border border-components-panel-border bg-components-panel-bg shadow-lg'>
|
||||||
|
<div className='flex flex-col gap-1 p-1'>
|
||||||
|
{canOAuth && (
|
||||||
|
<AddOAuthButton
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
buttonVariant='ghost'
|
||||||
|
className='w-full justify-between bg-transparent text-text-primary hover:bg-transparent'
|
||||||
|
buttonText={t('plugin.auth.addOAuth')}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={() => {
|
||||||
|
handleSelectEndUserType('oauth2')
|
||||||
|
invalidPluginCredentialInfo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{canApiKey && (
|
||||||
|
<AddApiKeyButton
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
buttonVariant='ghost'
|
||||||
|
buttonText={t('plugin.auth.addApi')}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={() => {
|
||||||
|
handleSelectEndUserType('api-key')
|
||||||
|
invalidPluginCredentialInfo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='border-components-panel-border bg-components-panel-bg'>
|
||||||
|
<div className='flex items-start justify-between gap-2'>
|
||||||
|
<div className='flex items-start gap-2'>
|
||||||
|
<RiKey2Line className='mt-0.5 h-4 w-4 text-text-tertiary' />
|
||||||
|
<div className='space-y-0.5'>
|
||||||
|
<div className='system-md-semibold text-text-primary'>
|
||||||
|
{t('plugin.auth.configuredCredentials.title')}
|
||||||
|
</div>
|
||||||
|
<div className='system-xs-regular text-text-tertiary'>
|
||||||
|
{t('plugin.auth.configuredCredentials.desc')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={showAddMenu}
|
||||||
|
onOpenChange={setShowAddMenu}
|
||||||
|
placement='bottom-end'
|
||||||
|
offset={6}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger asChild>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className={cn(
|
||||||
|
'flex h-9 w-9 items-center justify-center rounded-full bg-primary-600 text-white hover:bg-primary-700',
|
||||||
|
(disabled || (!canOAuth && !canApiKey && !hasOAuthClientConfigured)) && 'pointer-events-none opacity-50',
|
||||||
|
)}
|
||||||
|
onClick={() => setShowAddMenu(v => !v)}
|
||||||
|
>
|
||||||
|
<RiAddLine className='h-5 w-5' />
|
||||||
|
</button>
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[120]'>
|
||||||
|
<div className='w-[220px] rounded-xl border border-components-panel-border bg-components-panel-bg shadow-lg'>
|
||||||
|
<div className='flex flex-col gap-1 p-1'>
|
||||||
|
{
|
||||||
|
canOAuth && (
|
||||||
|
<AddOAuthButton
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
buttonVariant='ghost'
|
||||||
|
className='w-full justify-between bg-transparent text-text-primary hover:bg-transparent'
|
||||||
|
buttonText={t('plugin.auth.addOAuth')}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={() => {
|
||||||
|
setShowAddMenu(false)
|
||||||
|
invalidPluginCredentialInfo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
canApiKey && (
|
||||||
|
<AddApiKeyButton
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
buttonVariant='ghost'
|
||||||
|
buttonText={t('plugin.auth.addApi')}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={() => {
|
||||||
|
setShowAddMenu(false)
|
||||||
|
invalidPluginCredentialInfo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
!isAuthorized && (
|
!isAuthorized && shouldShowAuthorizeCard && (
|
||||||
|
<div className='rounded-xl bg-background-section px-4 py-4'>
|
||||||
|
<div className='flex w-full justify-center'>
|
||||||
|
<div className='w-full max-w-[520px]'>
|
||||||
|
<Authorize
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
canOAuth={canOAuth}
|
||||||
|
canApiKey={canApiKey}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={invalidPluginCredentialInfo}
|
||||||
|
notAllowCustomCredential={notAllowCustomCredential}
|
||||||
|
theme='secondary'
|
||||||
|
showDivider={!!(canOAuth && canApiKey)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isAuthorized && !shouldShowAuthorizeCard && (
|
||||||
<Authorize
|
<Authorize
|
||||||
pluginPayload={pluginPayload}
|
pluginPayload={pluginPayload}
|
||||||
canOAuth={canOAuth}
|
canOAuth={canOAuth}
|
||||||
@ -129,7 +366,8 @@ const PluginAuthInAgent = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</>
|
{endUserSection}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type { ReactNode } from 'react'
|
|||||||
import {
|
import {
|
||||||
RiAddLine,
|
RiAddLine,
|
||||||
RiArrowDownSLine,
|
RiArrowDownSLine,
|
||||||
RiCheckLine,
|
|
||||||
RiEqualizer2Line,
|
RiEqualizer2Line,
|
||||||
RiKey2Line,
|
RiKey2Line,
|
||||||
RiUserStarLine,
|
RiUserStarLine,
|
||||||
@ -20,7 +19,6 @@ import {
|
|||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
import { SimpleSelect } from '@/app/components/base/select'
|
|
||||||
import Authorize from './authorize'
|
import Authorize from './authorize'
|
||||||
import Authorized from './authorized'
|
import Authorized from './authorized'
|
||||||
import AddApiKeyButton from './authorize/add-api-key-button'
|
import AddApiKeyButton from './authorize/add-api-key-button'
|
||||||
@ -60,11 +58,18 @@ const PluginAuth = ({
|
|||||||
disabled,
|
disabled,
|
||||||
invalidPluginCredentialInfo,
|
invalidPluginCredentialInfo,
|
||||||
notAllowCustomCredential,
|
notAllowCustomCredential,
|
||||||
|
hasOAuthClientConfigured,
|
||||||
} = usePluginAuth(pluginPayload, !!pluginPayload.provider)
|
} = usePluginAuth(pluginPayload, !!pluginPayload.provider)
|
||||||
const shouldShowGuide = !!showConnectGuide
|
const shouldShowGuide = !!showConnectGuide
|
||||||
const [showCredentialPanel, setShowCredentialPanel] = useState(false)
|
const [showCredentialPanel, setShowCredentialPanel] = useState(false)
|
||||||
const [showAddMenu, setShowAddMenu] = useState(false)
|
const [showAddMenu, setShowAddMenu] = useState(false)
|
||||||
|
const [showEndUserTypeMenu, setShowEndUserTypeMenu] = useState(false)
|
||||||
const configuredDisabled = !!endUserCredentialEnabled
|
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 availableEndUserTypes = useMemo(() => {
|
||||||
const list: { value: string; label: string; icon: ReactNode }[] = []
|
const list: { value: string; label: string; icon: ReactNode }[] = []
|
||||||
if (canOAuth) {
|
if (canOAuth) {
|
||||||
@ -100,6 +105,7 @@ const PluginAuth = ({
|
|||||||
|
|
||||||
const handleSelectEndUserType = useCallback((value: string) => {
|
const handleSelectEndUserType = useCallback((value: string) => {
|
||||||
onEndUserCredentialTypeChange?.(value)
|
onEndUserCredentialTypeChange?.(value)
|
||||||
|
setShowEndUserTypeMenu(false)
|
||||||
}, [onEndUserCredentialTypeChange])
|
}, [onEndUserCredentialTypeChange])
|
||||||
const containerClassName = useMemo(() => {
|
const containerClassName = useMemo(() => {
|
||||||
if (showConnectGuide)
|
if (showConnectGuide)
|
||||||
@ -165,43 +171,54 @@ const PluginAuth = ({
|
|||||||
<div className='system-sm-semibold text-text-primary'>
|
<div className='system-sm-semibold text-text-primary'>
|
||||||
{t('plugin.auth.endUserCredentials.typeLabel')}
|
{t('plugin.auth.endUserCredentials.typeLabel')}
|
||||||
</div>
|
</div>
|
||||||
<SimpleSelect
|
<PortalToFollowElem
|
||||||
wrapperClassName='w-[190px]'
|
open={showEndUserTypeMenu}
|
||||||
items={availableEndUserTypes.map(item => ({
|
onOpenChange={setShowEndUserTypeMenu}
|
||||||
value: item.value,
|
placement='bottom-end'
|
||||||
name: item.label,
|
offset={6}
|
||||||
icon: item.icon,
|
>
|
||||||
}))}
|
<PortalToFollowElemTrigger asChild>
|
||||||
defaultValue={endUserCredentialType || availableEndUserTypes[0]?.value}
|
|
||||||
disabled={disabled}
|
|
||||||
onSelect={item => handleSelectEndUserType(item.value as string)}
|
|
||||||
renderTrigger={(value, open) => (
|
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='border-components-input-border flex h-9 w-full items-center justify-between rounded-lg border bg-components-input-bg-normal px-3 text-left text-text-primary shadow-xs hover:bg-components-input-bg-hover'
|
className='border-components-input-border flex h-9 min-w-[190px] items-center justify-between rounded-lg border bg-components-input-bg-normal px-3 text-left text-text-primary shadow-xs hover:bg-components-input-bg-hover'
|
||||||
|
onClick={() => setShowEndUserTypeMenu(v => !v)}
|
||||||
>
|
>
|
||||||
<span className='system-sm-semibold'>{value?.name || endUserCredentialLabel}</span>
|
<span className='system-sm-semibold'>{endUserCredentialLabel}</span>
|
||||||
<RiArrowDownSLine
|
<RiArrowDownSLine className='h-4 w-4 text-text-tertiary' />
|
||||||
className={cn(
|
|
||||||
'h-4 w-4 text-text-tertiary transition-transform',
|
|
||||||
open && 'rotate-180',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
</PortalToFollowElemTrigger>
|
||||||
renderOption={({ item, selected }) => (
|
<PortalToFollowElemContent className='z-[120]'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='w-[220px] rounded-xl border border-components-panel-border bg-components-panel-bg shadow-lg'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex flex-col gap-1 p-1'>
|
||||||
{item.icon}
|
{canOAuth && (
|
||||||
<span className='system-sm-semibold text-text-primary'>{item.name}</span>
|
<AddOAuthButton
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
buttonVariant='ghost'
|
||||||
|
className='w-full justify-between bg-transparent text-text-primary hover:bg-transparent'
|
||||||
|
buttonText={t('plugin.auth.addOAuth')}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={() => {
|
||||||
|
handleSelectEndUserType('oauth2')
|
||||||
|
invalidPluginCredentialInfo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{canApiKey && (
|
||||||
|
<AddApiKeyButton
|
||||||
|
pluginPayload={pluginPayload}
|
||||||
|
buttonVariant='ghost'
|
||||||
|
buttonText={t('plugin.auth.addApi')}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdate={() => {
|
||||||
|
handleSelectEndUserType('api-key')
|
||||||
|
invalidPluginCredentialInfo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{selected && <RiCheckLine className='h-4 w-4 text-text-accent' />}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</PortalToFollowElemContent>
|
||||||
optionWrapClassName='p-1'
|
</PortalToFollowElem>
|
||||||
optionClassName='px-3 py-2 rounded-lg'
|
|
||||||
hideChecked
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -282,8 +299,9 @@ const PluginAuth = ({
|
|||||||
<AddOAuthButton
|
<AddOAuthButton
|
||||||
pluginPayload={pluginPayload}
|
pluginPayload={pluginPayload}
|
||||||
buttonVariant='ghost'
|
buttonVariant='ghost'
|
||||||
|
className='w-full justify-between bg-transparent text-text-primary hover:bg-transparent'
|
||||||
buttonText={t('plugin.auth.addOAuth')}
|
buttonText={t('plugin.auth.addOAuth')}
|
||||||
disabled={disabled || configuredDisabled}
|
disabled={disabled}
|
||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
setShowAddMenu(false)
|
setShowAddMenu(false)
|
||||||
invalidPluginCredentialInfo()
|
invalidPluginCredentialInfo()
|
||||||
@ -297,7 +315,7 @@ const PluginAuth = ({
|
|||||||
pluginPayload={pluginPayload}
|
pluginPayload={pluginPayload}
|
||||||
buttonVariant='ghost'
|
buttonVariant='ghost'
|
||||||
buttonText={t('plugin.auth.addApi')}
|
buttonText={t('plugin.auth.addApi')}
|
||||||
disabled={disabled || configuredDisabled}
|
disabled={disabled}
|
||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
setShowAddMenu(false)
|
setShowAddMenu(false)
|
||||||
invalidPluginCredentialInfo()
|
invalidPluginCredentialInfo()
|
||||||
@ -314,22 +332,24 @@ const PluginAuth = ({
|
|||||||
{credentialList}
|
{credentialList}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
credentials.length === 0 && (
|
shouldShowAuthorizeCard && (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'mt-4 flex items-start gap-1.5 rounded-xl bg-background-section px-4 py-8',
|
'mt-4 flex items-start gap-1.5 rounded-xl bg-background-section px-4 py-8',
|
||||||
configuredDisabled && 'pointer-events-none opacity-50',
|
configuredDisabled && 'pointer-events-none opacity-50',
|
||||||
)}>
|
)}>
|
||||||
<div className='flex w-full justify-center'>
|
<div className='flex w-full justify-center'>
|
||||||
<Authorize
|
<div className='w-full max-w-[520px]'>
|
||||||
pluginPayload={pluginPayload}
|
<Authorize
|
||||||
canOAuth={canOAuth}
|
pluginPayload={pluginPayload}
|
||||||
canApiKey={canApiKey}
|
canOAuth={canOAuth}
|
||||||
disabled={disabled || configuredDisabled}
|
canApiKey={canApiKey}
|
||||||
onUpdate={invalidPluginCredentialInfo}
|
disabled={disabled || configuredDisabled}
|
||||||
notAllowCustomCredential={notAllowCustomCredential}
|
onUpdate={invalidPluginCredentialInfo}
|
||||||
theme='secondary'
|
notAllowCustomCredential={notAllowCustomCredential}
|
||||||
showDivider={!!(canOAuth && canApiKey)}
|
theme='secondary'
|
||||||
/>
|
showDivider={!!(canOAuth && canApiKey)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -210,6 +210,18 @@ const ToolSelector: FC<Props> = ({
|
|||||||
credential_id: id,
|
credential_id: id,
|
||||||
} as any)
|
} as any)
|
||||||
}
|
}
|
||||||
|
const handleEndUserCredentialChange = (enabled: boolean) => {
|
||||||
|
onSelect({
|
||||||
|
...value,
|
||||||
|
use_end_user_credentials: enabled,
|
||||||
|
} as any)
|
||||||
|
}
|
||||||
|
const handleEndUserCredentialTypeChange = (type: string) => {
|
||||||
|
onSelect({
|
||||||
|
...value,
|
||||||
|
end_user_credential_type: type,
|
||||||
|
} as any)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -323,6 +335,10 @@ const ToolSelector: FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
credentialId={value?.credential_id}
|
credentialId={value?.credential_id}
|
||||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||||
|
useEndUserCredentialEnabled={value?.use_end_user_credentials}
|
||||||
|
endUserCredentialType={value?.end_user_credential_type}
|
||||||
|
onEndUserCredentialChange={handleEndUserCredentialChange}
|
||||||
|
onEndUserCredentialTypeChange={handleEndUserCredentialTypeChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -130,6 +130,8 @@ export type AgentTool = {
|
|||||||
isDeleted?: boolean
|
isDeleted?: boolean
|
||||||
notAuthor?: boolean
|
notAuthor?: boolean
|
||||||
credential_id?: string
|
credential_id?: string
|
||||||
|
use_end_user_credentials?: boolean
|
||||||
|
end_user_credential_type?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolItem = {
|
export type ToolItem = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user