tool oauth

This commit is contained in:
zxhlyh 2025-07-09 18:28:39 +08:00
parent ce8bf7b5a2
commit 8968a3e254
19 changed files with 432 additions and 124 deletions

View File

@ -4,6 +4,7 @@ import {
useMemo,
} from 'react'
import type { AnyFieldApi } from '@tanstack/react-form'
import { useStore } from '@tanstack/react-form'
import cn from '@/utils/classnames'
import Input from '@/app/components/base/input'
import type { FormSchema } from '@/app/components/base/form/types'
@ -17,6 +18,7 @@ export type BaseFieldProps = {
inputClassName?: string
formSchema: FormSchema
field: AnyFieldApi
disabled?: boolean
}
const BaseField = ({
fieldClassName,
@ -25,6 +27,7 @@ const BaseField = ({
inputClassName,
formSchema,
field,
disabled,
}: BaseFieldProps) => {
const renderI18nObject = useRenderI18nObject()
const {
@ -35,9 +38,13 @@ const BaseField = ({
if (isValidElement(label))
return label
if (typeof label === 'string')
return label
if (typeof label === 'object' && label !== null)
return renderI18nObject(label as Record<string, string>)
}, [label, renderI18nObject])
const value = useStore(field.form.store, s => s.values[field.name])
return (
<div className={cn(fieldClassName)}>
@ -48,23 +55,27 @@ const BaseField = ({
{
formSchema.type === FormTypeEnum.textInput && (
<Input
className={cn(inputClassName)}
id={field.name}
value={field.state.value}
name={field.name}
className={cn(inputClassName)}
value={value}
onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
/>
)
}
{
formSchema.type === FormTypeEnum.secretInput && (
<Input
id={field.name}
name={field.name}
type='password'
className={cn(inputClassName)}
id={field.name}
value={field.state.value}
value={value}
onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
/>
)
}

View File

@ -24,6 +24,7 @@ export type BaseFormProps = {
defaultValues?: Record<string, any>
formClassName?: string
ref?: FormRef
disabled?: boolean
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
const BaseForm = ({
@ -35,6 +36,7 @@ const BaseForm = ({
inputContainerClassName,
inputClassName,
ref,
disabled,
}: BaseFormProps) => {
const form = useForm({
defaultValues,
@ -42,8 +44,8 @@ const BaseForm = ({
useImperativeHandle(ref, () => {
return {
getFormStore() {
return form.store
getForm() {
return form
},
}
}, [form])
@ -60,18 +62,21 @@ const BaseForm = ({
labelClassName={labelClassName}
inputContainerClassName={inputContainerClassName}
inputClassName={inputClassName}
disabled={disabled}
/>
)
}
return null
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName])
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled])
if (!formSchemas?.length)
return null
return (
<form className={cn(formClassName)}>
<form
className={cn(formClassName)}
>
{
formSchemas.map((formSchema) => {
return (

View File

@ -38,7 +38,7 @@ export type FormSchema = {
required: boolean
default?: any
tooltip?: string | TypeWithI18N
show_on: FormShowOnObject[]
show_on?: FormShowOnObject[]
url?: string
scope?: string
}
@ -46,6 +46,6 @@ export type FormSchema = {
export type FormValues = Record<string, any>
export type FromRefObject = {
getFormStore: () => AnyFormApi['store']
getForm: () => AnyFormApi
}
export type FormRef = ForwardedRef<FromRefObject>

View File

@ -25,6 +25,7 @@ type ModalProps = {
onExtraButtonClick?: () => void
footerSlot?: React.ReactNode
bottomSlot?: React.ReactNode
disabled?: boolean
}
const Modal = ({
onClose,
@ -42,13 +43,14 @@ const Modal = ({
onExtraButtonClick,
footerSlot,
bottomSlot,
disabled,
}: ModalProps) => {
const { t } = useTranslation()
return (
<PortalToFollowElem open>
<PortalToFollowElemContent
className='z-[999999] flex h-full w-full items-center justify-center bg-background-overlay'
className='z-[9998] flex h-full w-full items-center justify-center bg-background-overlay'
onClick={onClose}
>
<div
@ -88,6 +90,7 @@ const Modal = ({
<Button
variant={extraButtonVariant}
onClick={onExtraButtonClick}
disabled={disabled}
>
{extraButtonText || t('common.operation.remove')}
</Button>
@ -97,6 +100,7 @@ const Modal = ({
}
<Button
onClick={onCancel}
disabled={disabled}
>
{cancelButtonText || t('common.operation.cancel')}
</Button>
@ -104,6 +108,7 @@ const Modal = ({
className='ml-2'
variant='primary'
onClick={onConfirm}
disabled={disabled}
>
{confirmButtonText || t('common.operation.save')}
</Button>

View File

@ -1,6 +1,7 @@
import {
memo,
useCallback,
useMemo,
useRef,
} from 'react'
import { useTranslation } from 'react-i18next'
@ -14,8 +15,10 @@ import {
useUpdatePluginToolCredential,
} from '@/service/use-plugins-auth'
import { CredentialTypeEnum } from '../types'
import { transformFormSchemasSecretInput } from '../utils'
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
import type { FromRefObject } from '@/app/components/base/form/types'
import { FormTypeEnum } from '@/app/components/base/form/types'
import { useToastContext } from '@/app/components/base/toast'
export type ApiKeyModalProps = {
@ -23,34 +26,65 @@ export type ApiKeyModalProps = {
onClose?: () => void
editValues?: Record<string, any>
onRemove?: () => void
disabled?: boolean
}
const ApiKeyModal = ({
provider,
onClose,
editValues,
onRemove,
disabled,
}: ApiKeyModalProps) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const { data } = useGetPluginToolCredentialSchema(provider, CredentialTypeEnum.API_KEY)
const { data = [] } = useGetPluginToolCredentialSchema(provider, CredentialTypeEnum.API_KEY)
const formSchemas = useMemo(() => {
return [
{
type: FormTypeEnum.textInput,
name: '__name__',
label: 'Authorization name',
required: false,
},
...data,
]
}, [data])
const { mutateAsync: addPluginToolCredential } = useAddPluginToolCredential(provider)
const { mutateAsync: updatePluginToolCredential } = useUpdatePluginToolCredential(provider)
const invalidatePluginToolCredentialInfo = useInvalidPluginToolCredentialInfo(provider)
const formRef = useRef<FromRefObject>(null)
const handleConfirm = useCallback(async () => {
const store = formRef.current?.getFormStore()
const values = store?.state.values
const form = formRef.current?.getForm()
const store = form?.store
const {
__name__,
__credential_id__,
...values
} = store?.state.values
const isPristineSecretInputNames: string[] = []
formSchemas.forEach((schema) => {
if (schema.type === FormTypeEnum.secretInput) {
const fieldMeta = form?.getFieldMeta(schema.name)
if (fieldMeta?.isPristine)
isPristineSecretInputNames.push(schema.name)
}
})
const transformedValues = transformFormSchemasSecretInput(isPristineSecretInputNames, values)
if (editValues) {
await updatePluginToolCredential({
credentials: values,
credentials: transformedValues,
credential_id: __credential_id__,
type: CredentialTypeEnum.API_KEY,
name: __name__ || '',
})
}
else {
await addPluginToolCredential({
credentials: values,
credentials: transformedValues,
type: CredentialTypeEnum.API_KEY,
name: __name__ || '',
})
}
notify({
@ -60,7 +94,7 @@ const ApiKeyModal = ({
onClose?.()
invalidatePluginToolCredentialInfo()
}, [addPluginToolCredential, onClose, invalidatePluginToolCredentialInfo, updatePluginToolCredential, notify, t, editValues])
}, [addPluginToolCredential, onClose, invalidatePluginToolCredentialInfo, updatePluginToolCredential, notify, t, editValues, formSchemas])
return (
<Modal
@ -96,11 +130,13 @@ const ApiKeyModal = ({
onConfirm={handleConfirm}
showExtraButton={!!editValues}
onExtraButtonClick={onRemove}
disabled={disabled}
>
<AuthForm
ref={formRef}
formSchemas={data}
formSchemas={formSchemas}
defaultValues={editValues}
disabled={disabled}
/>
</Modal>
)

View File

@ -36,7 +36,7 @@ const Authorize = ({
}
return {
buttonText: !canApiKey ? 'Use OAuth Authorization' : '',
buttonText: !canApiKey ? 'Use OAuth Authorization' : 'Use OAuth',
}
}, [canApiKey, theme])
@ -50,7 +50,7 @@ const Authorize = ({
}
return {
provider,
buttonText: !canOAuth ? 'API Key Authorization Configuration' : '',
buttonText: !canOAuth ? 'API Key Authorization Configuration' : 'Use API Key',
buttonVariant: !canOAuth ? 'primary' : 'secondary-accent',
}
}, [canOAuth, theme, provider])

View File

@ -0,0 +1,94 @@
import {
memo,
useCallback,
useMemo,
useState,
} from 'react'
import { RiArrowDownSLine } from '@remixicon/react'
import Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator'
import cn from '@/utils/classnames'
import type { Credential } from './types'
import {
Authorized,
usePluginAuth,
} from '.'
type AuthorizedInNodeProps = {
provider: string
onAuthorizationItemClick: (id: string) => void
credentialId?: string
}
const AuthorizedInNode = ({
provider = '',
onAuthorizationItemClick,
credentialId,
}: AuthorizedInNodeProps) => {
const [isOpen, setIsOpen] = useState(false)
const {
canApiKey,
canOAuth,
credentials,
disabled,
} = usePluginAuth(provider, isOpen)
const label = useMemo(() => {
if (!credentialId)
return 'Workspace default'
const credential = credentials.find(c => c.id === credentialId)
if (!credential)
return 'Auth removed'
return credential.name
}, [credentials, credentialId])
const renderTrigger = useCallback((open?: boolean) => {
return (
<Button
size='small'
className={cn(open && 'bg-components-button-ghost-bg-hover')}
>
<Indicator className='mr-1.5' />
{label}
<RiArrowDownSLine className='h-3.5 w-3.5 text-components-button-ghost-text' />
</Button>
)
}, [label])
const extraAuthorizationItems: Credential[] = [
{
id: '__workspace_default__',
name: 'Workspace default',
provider: '',
is_default: false,
isWorkspaceDefault: true,
},
]
const handleAuthorizationItemClick = useCallback((id: string) => {
onAuthorizationItemClick(id)
setIsOpen(false)
}, [
onAuthorizationItemClick,
setIsOpen,
])
return (
<Authorized
provider={provider}
credentials={credentials}
canOAuth={canOAuth}
canApiKey={canApiKey}
renderTrigger={renderTrigger}
isOpen={isOpen}
onOpenChange={setIsOpen}
offset={4}
placement='bottom-end'
triggerPopupSameWidth={false}
popupClassName='w-[360px]'
disabled={disabled}
disableSetDefault
onItemClick={handleAuthorizationItemClick}
extraAuthorizationItems={extraAuthorizationItems}
/>
)
}
export default memo(AuthorizedInNode)

View File

@ -13,6 +13,9 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type {
PortalToFollowElemOptions,
} from '@/app/components/base/portal-to-follow-elem'
import Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator'
import cn from '@/utils/classnames'
@ -35,6 +38,16 @@ type AuthorizedProps = {
canOAuth?: boolean
canApiKey?: boolean
disabled?: boolean
renderTrigger?: (open?: boolean) => React.ReactNode
isOpen?: boolean
onOpenChange?: (open: boolean) => void
offset?: PortalToFollowElemOptions['offset']
placement?: PortalToFollowElemOptions['placement']
triggerPopupSameWidth?: boolean
popupClassName?: string
disableSetDefault?: boolean
onItemClick?: (id: string) => void
extraAuthorizationItems?: Credential[]
}
const Authorized = ({
provider,
@ -42,10 +55,27 @@ const Authorized = ({
canOAuth,
canApiKey,
disabled,
renderTrigger,
isOpen,
onOpenChange,
offset = 8,
placement = 'bottom-start',
triggerPopupSameWidth = true,
popupClassName,
disableSetDefault,
onItemClick,
extraAuthorizationItems,
}: AuthorizedProps) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const [isOpen, setIsOpen] = useState(false)
const [isLocalOpen, setIsLocalOpen] = useState(false)
const mergedIsOpen = isOpen ?? isLocalOpen
const setMergedIsOpen = useCallback((open: boolean) => {
if (onOpenChange)
onOpenChange(open)
setIsLocalOpen(open)
}, [onOpenChange])
const oAuthCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.OAUTH2)
const apiKeyCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.API_KEY)
const pendingOperationCredentialId = useRef<string | null>(null)
@ -98,28 +128,57 @@ const Authorized = ({
return (
<>
<PortalToFollowElem
open={isOpen}
onOpenChange={setIsOpen}
placement='bottom-start'
offset={8}
triggerPopupSameWidth
open={mergedIsOpen}
onOpenChange={setMergedIsOpen}
placement={placement}
offset={offset}
triggerPopupSameWidth={triggerPopupSameWidth}
>
<PortalToFollowElemTrigger
onClick={() => setIsOpen(!isOpen)}
onClick={() => setMergedIsOpen(!mergedIsOpen)}
asChild
>
<Button
className={cn(
'w-full',
isOpen && 'bg-components-button-secondary-bg-hover',
)}>
<Indicator className='mr-2' />
{credentials.length} Authorizations
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
</Button>
{
renderTrigger
? renderTrigger(mergedIsOpen)
: (
<Button
className={cn(
'w-full',
isOpen && 'bg-components-button-secondary-bg-hover',
)}>
<Indicator className='mr-2' />
{credentials.length} Authorizations
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
</Button>
)
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[100]'>
<div className='max-h-[360px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<div className={cn(
'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 && (
<div className='p-1'>
{
extraAuthorizationItems.map(credential => (
<Item
key={credential.id}
credential={credential}
disabled={disabled}
onItemClick={onItemClick}
disableRename
disableEdit
disableDelete
disableSetDefault
/>
))
}
</div>
)
}
<div className='py-1'>
{
!!oAuthCredentials.length && (
@ -153,6 +212,8 @@ const Authorized = ({
onDelete={openConfirm}
onEdit={handleEdit}
onSetDefault={handleSetDefault}
disableSetDefault={disableSetDefault}
onItemClick={onItemClick}
/>
))
}
@ -195,6 +256,7 @@ const Authorized = ({
pendingOperationCredentialId.current = null
}}
onRemove={handleRemove}
disabled={disabled}
/>
)
}

View File

@ -1,5 +1,6 @@
import {
memo,
useMemo,
} from 'react'
import {
RiDeleteBinLine,
@ -20,6 +21,11 @@ type ItemProps = {
onDelete?: (id: string) => void
onEdit?: (id: string, values: Record<string, any>) => void
onSetDefault?: (id: string) => void
disableRename?: boolean
disableEdit?: boolean
disableDelete?: boolean
disableSetDefault?: boolean
onItemClick?: (id: string) => void
}
const Item = ({
credential,
@ -27,16 +33,25 @@ const Item = ({
onDelete,
onEdit,
onSetDefault,
disableRename,
disableEdit,
disableDelete,
disableSetDefault,
onItemClick,
}: ItemProps) => {
const isOAuth = credential.credential_type === CredentialTypeEnum.OAUTH2
const showAction = useMemo(() => {
return !(disableRename && disableEdit && disableDelete && disableSetDefault)
}, [disableRename, disableEdit, disableDelete, disableSetDefault])
return (
<div
key={credential.id}
className='group flex h-8 items-center rounded-lg p-1 hover:bg-state-base-hover'
onClick={() => onItemClick?.(credential.id)}
>
<div className='flex grow items-center space-x-1.5 pl-2'>
<Indicator className='mr-1.5' />
<div className='flex w-0 grow items-center space-x-1.5 pl-2'>
<Indicator className='mr-1.5 shrink-0' />
<div
className='system-md-regular truncate text-text-secondary'
title={credential.name}
@ -51,44 +66,72 @@ const Item = ({
)
}
</div>
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
<Button
size='small'
disabled={disabled}
onClick={() => onSetDefault?.(credential.id)}
>
Set as default
</Button>
{
isOAuth && (
<Tooltip popupContent='rename'>
<ActionButton>
<RiEditLine className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</Tooltip>
)
}
{
!isOAuth && (
<Tooltip popupContent='edit'>
<ActionButton
disabled={disabled}
onClick={() => onEdit?.(credential.id, credential.credentials)}
>
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</Tooltip>
)
}
<Tooltip popupContent='delete'>
<ActionButton
disabled={disabled}
onClick={() => onDelete?.(credential.id)}
>
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</Tooltip>
</div>
{
showAction && (
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
{
!credential.is_default && !disableSetDefault && (
<Button
size='small'
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onSetDefault?.(credential.id)
}}
>
Set as default
</Button>
)
}
{
isOAuth && !disableRename && (
<Tooltip popupContent='rename'>
<ActionButton>
<RiEditLine className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</Tooltip>
)
}
{
!isOAuth && !disableEdit && (
<Tooltip popupContent='edit'>
<ActionButton
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onEdit?.(
credential.id,
{
...credential.credentials,
__name__: credential.name,
__credential_id__: credential.id,
},
)
}}
>
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</Tooltip>
)
}
{
!disableDelete && (
<Tooltip popupContent='delete'>
<ActionButton
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onDelete?.(credential.id)
}}
>
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
</ActionButton>
</Tooltip>
)
}
</div>
)
}
</div>
)
}

View File

@ -0,0 +1,20 @@
import { useAppContext } from '@/context/app-context'
import { useGetPluginToolCredentialInfo } from '@/service/use-plugins-auth'
import { CredentialTypeEnum } from './types'
export const usePluginAuth = (provider: string, enable?: boolean) => {
const { data } = useGetPluginToolCredentialInfo(enable ? provider : '')
const { isCurrentWorkspaceManager } = useAppContext()
const isAuthorized = !!data?.credentials.length
const canOAuth = data?.supported_credential_types.includes(CredentialTypeEnum.OAUTH2)
const canApiKey = data?.supported_credential_types.includes(CredentialTypeEnum.API_KEY)
return {
isAuthorized,
canOAuth,
canApiKey,
credentials: data?.credentials || [],
provider,
disabled: !isCurrentWorkspaceManager,
}
}

View File

@ -1 +1,4 @@
export { default as PluginAuth } from './plugin-auth'
export { default as Authorized } from './authorized'
export { default as AuthorizedInNode } from './authorized-in-node'
export { usePluginAuth } from './hooks'

View File

@ -7,9 +7,11 @@ import { CredentialTypeEnum } from './types'
type PluginAuthProps = {
provider?: string
children?: React.ReactNode
}
const PluginAuth = ({
provider = '',
children,
}: PluginAuthProps) => {
const { data } = useGetPluginToolCredentialInfo(provider)
const { isCurrentWorkspaceManager } = useAppContext()
@ -30,7 +32,7 @@ const PluginAuth = ({
)
}
{
isAuthorized && (
isAuthorized && !children && (
<Authorized
provider={provider}
credentials={data?.credentials}
@ -40,6 +42,9 @@ const PluginAuth = ({
/>
)
}
{
isAuthorized && children
}
</>
)
}

View File

@ -7,7 +7,8 @@ export type Credential = {
id: string
name: string
provider: string
credential_type: CredentialTypeEnum
credential_type?: CredentialTypeEnum
is_default: boolean
credentials: Record<string, any>
credentials?: Record<string, any>
isWorkspaceDefault?: boolean
}

View File

@ -0,0 +1,10 @@
export const transformFormSchemasSecretInput = (isPristineSecretInputNames: string[], values: Record<string, any>) => {
const transformedValues: Record<string, any> = { ...values }
isPristineSecretInputNames.forEach((name) => {
if (transformedValues[name])
transformedValues[name] = '[__HIDDEN__]'
})
return transformedValues
}

View File

@ -30,6 +30,7 @@ export type ToolDefaultValue = {
params: Record<string, any>
paramSchemas: Record<string, any>[]
output_schema: Record<string, any>
credential_id?: string
}
export type ToolValue = {

View File

@ -59,6 +59,11 @@ import { useLogs } from '@/app/components/workflow/run/hooks'
import PanelWrap from '../before-run-form/panel-wrap'
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import {
AuthorizedInNode,
PluginAuth,
} from '@/app/components/plugins/plugin-auth'
import { canFindTool } from '@/utils'
type BasePanelProps = {
children: ReactNode
@ -215,6 +220,22 @@ const BasePanel: FC<BasePanelProps> = ({
return {}
})()
const buildInTools = useStore(s => s.buildInTools)
const currCollection = useMemo(() => {
return buildInTools.find(item => canFindTool(item.id, data.provider_id))
}, [buildInTools, data.provider_id])
const showPluginAuth = useMemo(() => {
return data.type === BlockEnum.Tool && currCollection?.allow_delete && !currCollection.is_team_authorization
}, [currCollection, data.type])
const handleAuthorizationItemClick = useCallback((id: string) => {
handleNodeDataUpdate({
id,
data: {
credential_id: id === '__workspace_default__' ? undefined : id,
},
})
}, [handleNodeDataUpdate])
if(logParams.showSpecialResultPanel) {
return (
<div className={cn(
@ -347,12 +368,39 @@ const BasePanel: FC<BasePanelProps> = ({
onChange={handleDescriptionChange}
/>
</div>
<div className='pl-4'>
<Tab
value={tabType}
onChange={setTabType}
/>
</div>
{
showPluginAuth && (
<PluginAuth
provider={currCollection?.name}
>
<div className='pl-4'>
<Tab
value={tabType}
onChange={setTabType}
/>
</div>
</PluginAuth>
)
}
{
!showPluginAuth && (
<div className='flex items-center justify-between pl-4 pr-3'>
<Tab
value={tabType}
onChange={setTabType}
/>
{
currCollection?.allow_delete && (
<AuthorizedInNode
provider={currCollection?.name}
onAuthorizationItemClick={handleAuthorizationItemClick}
credentialId={data.credential_id}
/>
)
}
</div>
)
}
<Split />
</div>

View File

@ -5,18 +5,13 @@ import Split from '../_base/components/split'
import type { ToolNodeType } from './types'
import useConfig from './use-config'
import InputVarList from './components/input-var-list'
import Button from '@/app/components/base/button'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import type { NodePanelProps } from '@/app/components/workflow/types'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
import Loading from '@/app/components/base/loading'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
import { Type } from '../llm/types'
import {
PluginAuth,
} from '@/app/components/plugins/plugin-auth'
const i18nPrefix = 'workflow.nodes.tool'
@ -38,10 +33,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
setToolSettingValue,
currCollection,
isShowAuthBtn,
showSetAuth,
showSetAuthModal,
hideSetAuthModal,
handleSaveAuth,
isLoading,
outputSchema,
hasObjectOutput,
@ -56,24 +47,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
return (
<div className='pt-2'>
<div className='px-4 py-2'>
<PluginAuth
provider={currCollection?.name}
/>
</div>
{!readOnly && isShowAuthBtn && (
<>
<div className='px-4'>
<Button
variant='primary'
className='w-full'
onClick={showSetAuthModal}
>
{t(`${i18nPrefix}.authorize`)}
</Button>
</div>
</>
)}
{!isShowAuthBtn && <>
<div className='space-y-4 px-4'>
{toolInputVarSchema.length > 0 && (
@ -114,16 +87,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
/>
</div>
</>}
{showSetAuth && (
<ConfigCredential
collection={currCollection!}
onCancel={hideSetAuthModal}
onSaved={handleSaveAuth}
isHideRemoveBtn
/>
)}
<div>
<OutputVars>
<>

View File

@ -92,7 +92,7 @@ export type CommonNodeType<T = {}> = {
error_strategy?: ErrorHandleTypeEnum
retry_config?: WorkflowRetryConfig
default_value?: DefaultValueForm[]
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name' | 'credential_id'>>
export type CommonEdgeType = {
_hovering?: boolean

View File

@ -71,6 +71,7 @@ export const useUpdatePluginToolCredential = (
) => {
return useMutation({
mutationFn: (params: {
credential_id: string
credentials: Record<string, any>
type: CredentialTypeEnum
name?: string