mirror of https://github.com/langgenius/dify.git
feat: tool oauth
This commit is contained in:
parent
31eb8548ef
commit
c53d5c105b
|
|
@ -0,0 +1,118 @@
|
|||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ModalProps = {
|
||||
onClose?: () => void
|
||||
size?: 'sm' | 'md'
|
||||
title: string
|
||||
subTitle?: string
|
||||
children?: React.ReactNode
|
||||
confirmButtonText?: string
|
||||
onConfirm?: () => void
|
||||
cancelButtonText?: string
|
||||
onCancel?: () => void
|
||||
showExtraButton?: boolean
|
||||
extraButtonText?: string
|
||||
extraButtonVariant?: ButtonProps['variant']
|
||||
onExtraButtonClick?: () => void
|
||||
footerSlot?: React.ReactNode
|
||||
bottomSlot?: React.ReactNode
|
||||
}
|
||||
const Modal = ({
|
||||
onClose,
|
||||
size = 'sm',
|
||||
title,
|
||||
subTitle,
|
||||
children,
|
||||
confirmButtonText,
|
||||
onConfirm,
|
||||
cancelButtonText,
|
||||
onCancel,
|
||||
showExtraButton,
|
||||
extraButtonVariant = 'warning',
|
||||
extraButtonText,
|
||||
onExtraButtonClick,
|
||||
footerSlot,
|
||||
bottomSlot,
|
||||
}: ModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent
|
||||
className='z-[999999] flex h-full w-full items-center justify-center bg-background-overlay'
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'max-h-[80%] w-[480px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xs',
|
||||
size === 'sm' && 'w-[480px',
|
||||
size === 'md' && 'w-[640px]',
|
||||
)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className='title-2xl-semi-bold relative p-6 pb-3 pr-14 text-text-primary'>
|
||||
{title}
|
||||
{
|
||||
subTitle && (
|
||||
<div className='system-xs-regular mt-1 text-text-tertiary'>
|
||||
{subTitle}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div
|
||||
className='absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg'
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiCloseLine className='h-5 w-5 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
children && (
|
||||
<div className='px-6 py-3'>{children}</div>
|
||||
)
|
||||
}
|
||||
<div className='flex items-center justify-end p-6 pt-5'>
|
||||
{footerSlot}
|
||||
{
|
||||
showExtraButton && (
|
||||
<>
|
||||
<Button
|
||||
variant={extraButtonVariant}
|
||||
onClick={onExtraButtonClick}
|
||||
>
|
||||
{extraButtonText || t('common.operation.remove')}
|
||||
</Button>
|
||||
<div className='mx-3 h-4 w-[1px] bg-divider-regular'></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
>
|
||||
{cancelButtonText || t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className='ml-2'
|
||||
variant='primary'
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{confirmButtonText || t('common.operation.save')}
|
||||
</Button>
|
||||
</div>
|
||||
{bottomSlot}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Modal)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import ApiKeyModal from './api-key-modal'
|
||||
|
||||
type AddApiKeyButtonProps = {
|
||||
buttonVariant?: ButtonProps['variant']
|
||||
buttonText?: string
|
||||
}
|
||||
const AddApiKeyButton = ({
|
||||
buttonVariant = 'secondary-accent',
|
||||
buttonText = 'use api key',
|
||||
}: AddApiKeyButtonProps) => {
|
||||
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className='grow'
|
||||
variant={buttonVariant}
|
||||
onClick={() => setIsApiKeyModalOpen(true)}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
{
|
||||
isApiKeyModalOpen && (
|
||||
<ApiKeyModal
|
||||
onClose={() => setIsApiKeyModalOpen(false)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddApiKeyButton)
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import OAuthClientSettings from './oauth-client-settings'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type AddOAuthButtonProps = {
|
||||
buttonVariant?: ButtonProps['variant']
|
||||
buttonText?: string
|
||||
className?: string
|
||||
buttonLeftClassName?: string
|
||||
buttonRightClassName?: string
|
||||
dividerClassName?: string
|
||||
}
|
||||
const AddOAuthButton = ({
|
||||
buttonVariant = 'primary',
|
||||
buttonText = 'use oauth',
|
||||
className,
|
||||
buttonLeftClassName,
|
||||
buttonRightClassName,
|
||||
dividerClassName,
|
||||
}: AddOAuthButtonProps) => {
|
||||
const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={buttonVariant}
|
||||
className={cn(
|
||||
'grow px-0 py-0 hover:bg-components-button-primary-bg',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex h-full grow items-center justify-center rounded-l-lg hover:bg-components-button-primary-bg-hover',
|
||||
buttonLeftClassName,
|
||||
)}>
|
||||
{buttonText}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'h-4 w-[1px] bg-text-primary-on-surface opacity-[0.15]',
|
||||
dividerClassName,
|
||||
)}></div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full w-8 shrink-0 items-center justify-center rounded-r-lg hover:bg-components-button-primary-bg-hover',
|
||||
buttonRightClassName,
|
||||
)}
|
||||
onClick={() => setIsOAuthSettingsOpen(true)}
|
||||
>
|
||||
<RiEqualizer2Line className='h-4 w-4' />
|
||||
</div>
|
||||
</Button>
|
||||
{
|
||||
isOAuthSettingsOpen && (
|
||||
<OAuthClientSettings
|
||||
onClose={() => setIsOAuthSettingsOpen(false)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddOAuthButton)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiExternalLinkLine } from '@remixicon/react'
|
||||
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
|
||||
export type ApiKeyModalProps = {
|
||||
onClose?: () => void
|
||||
}
|
||||
const ApiKeyModal = ({
|
||||
onClose,
|
||||
}: ApiKeyModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size='md'
|
||||
title='API Key Authorization Configuration'
|
||||
subTitle='After configuring credentials, all members within the workspace can use this tool when orchestrating applications.'
|
||||
onClose={onClose}
|
||||
onCancel={onClose}
|
||||
footerSlot={
|
||||
<a
|
||||
className='system-xs-regular flex h-8 grow items-center text-text-accent'
|
||||
href=''
|
||||
target='_blank'
|
||||
>
|
||||
Get your API Key from OpenAI
|
||||
<RiExternalLinkLine className='ml-1 h-3 w-3' />
|
||||
</a>
|
||||
}
|
||||
bottomSlot={
|
||||
<div className='flex items-center justify-center bg-background-section-burn py-3 text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 h-3 w-3 text-text-tertiary' />
|
||||
{t('common.modelProvider.encrypted.front')}
|
||||
<a
|
||||
className='mx-1 text-text-accent'
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||
>
|
||||
PKCS1_OAEP
|
||||
</a>
|
||||
{t('common.modelProvider.encrypted.back')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div>oauth</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ApiKeyModal)
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import cn from '@/utils/classnames'
|
||||
import AddOauthButton from './add-oauth-button'
|
||||
import AddApiKeyButton from './add-api-key-button'
|
||||
|
||||
const Authorization = () => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
placement='bottom-start'
|
||||
offset={8}
|
||||
triggerPopupSameWidth
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
asChild
|
||||
>
|
||||
<Button
|
||||
className={cn(
|
||||
'w-full',
|
||||
isOpen && 'bg-components-button-secondary-bg-hover',
|
||||
)}>
|
||||
<Indicator className='mr-2' />
|
||||
4 Authorizations
|
||||
<RiArrowDownSLine className='ml-0.5 h-4 w-4' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<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='py-1'>
|
||||
<div className='p-1'>
|
||||
<div className='system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary'>
|
||||
OAuth
|
||||
</div>
|
||||
<div className='flex items-center rounded-lg p-1 hover:bg-state-base-hover'>
|
||||
<div className='flex grow items-center space-x-1.5 pl-2'>
|
||||
<Indicator className='mr-1.5' />
|
||||
<div
|
||||
className='system-md-regular truncate text-text-secondary'
|
||||
title='Auth 1'
|
||||
>
|
||||
Auth 1
|
||||
</div>
|
||||
<Badge>
|
||||
Default
|
||||
</Badge>
|
||||
</div>
|
||||
<div className='ml-2 flex shrink-0 items-center'>
|
||||
<ActionButton>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
<ActionButton>
|
||||
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
<div className='system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary'>
|
||||
API Keys
|
||||
</div>
|
||||
<div className='flex items-center rounded-lg p-1 hover:bg-state-base-hover'>
|
||||
<div className='flex grow items-center space-x-1.5 pl-2'>
|
||||
<Indicator className='mr-1.5' />
|
||||
<div
|
||||
className='system-md-regular truncate text-text-secondary'
|
||||
title='Production'
|
||||
>
|
||||
Production
|
||||
</div>
|
||||
</div>
|
||||
<div className='ml-2 flex shrink-0 items-center space-x-1'>
|
||||
<Badge>
|
||||
0.2
|
||||
</Badge>
|
||||
<Badge>
|
||||
ENTERPRISE
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[1px] bg-divider-subtle'></div>
|
||||
<div className='flex items-center space-x-1.5 p-2'>
|
||||
<AddOauthButton
|
||||
buttonVariant='secondary'
|
||||
buttonText='Add OAuth'
|
||||
className='hover:bg-components-button-secondary-bg'
|
||||
buttonLeftClassName='hover:bg-components-button-secondary-bg-hover'
|
||||
buttonRightClassName='hover:bg-components-button-secondary-bg-hover'
|
||||
dividerClassName='bg-divider-regular opacity-100'
|
||||
/>
|
||||
<AddApiKeyButton
|
||||
buttonVariant='secondary'
|
||||
buttonText='Add API Key'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Authorization)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
memo,
|
||||
} from 'react'
|
||||
import AddOAuthButton from './add-oauth-button'
|
||||
import AddApiKeyButton from './add-api-key-button'
|
||||
|
||||
const ToolAuth = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='flex items-center space-x-1.5'>
|
||||
<AddOAuthButton />
|
||||
<div className='system-2xs-medium-uppercase flex shrink-0 flex-col items-center justify-between text-text-tertiary'>
|
||||
<div className='h-2 w-[1px] bg-divider-subtle'></div>
|
||||
or
|
||||
<div className='h-2 w-[1px] bg-divider-subtle'></div>
|
||||
</div>
|
||||
<AddApiKeyButton />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ToolAuth)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { memo } from 'react'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
|
||||
type OAuthClientSettingsProps = {
|
||||
onClose?: () => void
|
||||
}
|
||||
const OAuthClientSettings = ({
|
||||
onClose,
|
||||
}: OAuthClientSettingsProps) => {
|
||||
return (
|
||||
<Modal
|
||||
title='Oauth client settings'
|
||||
confirmButtonText='Save & Authorize'
|
||||
cancelButtonText='Save only'
|
||||
extraButtonText='Cancel'
|
||||
showExtraButton
|
||||
extraButtonVariant='secondary'
|
||||
onExtraButtonClick={onClose}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div>oauth</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(OAuthClientSettings)
|
||||
|
|
@ -14,6 +14,8 @@ 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 ToolAuth from '@/app/components/tools/tool-auth'
|
||||
import Authorization from '@/app/components/tools/tool-auth/authorization'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.tool'
|
||||
|
||||
|
|
@ -53,6 +55,10 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||
|
||||
return (
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 py-2'>
|
||||
<ToolAuth />
|
||||
<Authorization />
|
||||
</div>
|
||||
{!readOnly && isShowAuthBtn && (
|
||||
<>
|
||||
<div className='px-4'>
|
||||
|
|
|
|||
Loading…
Reference in New Issue