mirror of
https://github.com/langgenius/dify.git
synced 2026-04-25 09:36:40 +08:00
feat: implement trigger plugin frontend integration (#25283)
This commit is contained in:
parent
814787677a
commit
a91e59d544
3
.gitignore
vendored
3
.gitignore
vendored
@ -218,3 +218,6 @@ mise.toml
|
|||||||
.roo/
|
.roo/
|
||||||
api/.env.backup
|
api/.env.backup
|
||||||
/clickzetta
|
/clickzetta
|
||||||
|
|
||||||
|
# mcp
|
||||||
|
.serena
|
||||||
@ -293,13 +293,14 @@ class TriggerOAuthAuthorizeApi(Resource):
|
|||||||
credential_type=CredentialType.OAUTH2,
|
credential_type=CredentialType.OAUTH2,
|
||||||
credential_expires_at=0,
|
credential_expires_at=0,
|
||||||
expires_at=0,
|
expires_at=0,
|
||||||
|
name=f"{provider_name} OAuth Authentication",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create response with cookie
|
# Create response with cookie
|
||||||
response = make_response(
|
response = make_response(
|
||||||
jsonable_encoder(
|
jsonable_encoder(
|
||||||
{
|
{
|
||||||
"authorization_url": authorization_url_response,
|
"authorization_url": authorization_url_response.authorization_url,
|
||||||
"subscription_builder": subscription_builder,
|
"subscription_builder": subscription_builder,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -377,6 +378,7 @@ class TriggerOAuthCallbackApi(Resource):
|
|||||||
credential_type=CredentialType.OAUTH2,
|
credential_type=CredentialType.OAUTH2,
|
||||||
credential_expires_at=expires_at,
|
credential_expires_at=expires_at,
|
||||||
expires_at=expires_at,
|
expires_at=expires_at,
|
||||||
|
name=f"{provider_name} OAuth Authentication",
|
||||||
)
|
)
|
||||||
# Redirect to OAuth callback page
|
# Redirect to OAuth callback page
|
||||||
return redirect(f"{dify_config.CONSOLE_WEB_URL}/oauth-callback?subscription_id={subscription_builder.id}")
|
return redirect(f"{dify_config.CONSOLE_WEB_URL}/oauth-callback?subscription_id={subscription_builder.id}")
|
||||||
|
|||||||
@ -58,15 +58,56 @@ export type TriggerParameter = {
|
|||||||
name: string
|
name: string
|
||||||
label: TypeWithI18N
|
label: TypeWithI18N
|
||||||
description?: TypeWithI18N
|
description?: TypeWithI18N
|
||||||
type: string
|
type: 'string' | 'number' | 'boolean' | 'select' | 'file' | 'files'
|
||||||
|
| 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select'
|
||||||
|
auto_generate?: {
|
||||||
|
type: string
|
||||||
|
value?: any
|
||||||
|
} | null
|
||||||
|
template?: {
|
||||||
|
type: string
|
||||||
|
value?: any
|
||||||
|
} | null
|
||||||
|
scope?: string | null
|
||||||
required?: boolean
|
required?: boolean
|
||||||
default?: any
|
default?: any
|
||||||
|
min?: number | null
|
||||||
|
max?: number | null
|
||||||
|
precision?: number | null
|
||||||
|
options?: Array<{
|
||||||
|
value: string
|
||||||
|
label: TypeWithI18N
|
||||||
|
icon?: string | null
|
||||||
|
}> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TriggerCredentialField = {
|
||||||
|
type: 'secret-input' | 'text-input' | 'select' | 'boolean'
|
||||||
|
| 'app-selector' | 'model-selector' | 'tools-selector'
|
||||||
|
name: string
|
||||||
|
scope?: string | null
|
||||||
|
required: boolean
|
||||||
|
default?: string | number | boolean | Array<any> | null
|
||||||
|
options?: Array<{
|
||||||
|
value: string
|
||||||
|
label: TypeWithI18N
|
||||||
|
}> | null
|
||||||
|
label: TypeWithI18N
|
||||||
|
help?: TypeWithI18N
|
||||||
|
url?: string | null
|
||||||
|
placeholder?: TypeWithI18N
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TriggerSubscriptionSchema = {
|
||||||
|
parameters_schema: TriggerParameter[]
|
||||||
|
properties_schema: TriggerCredentialField[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TriggerIdentity = {
|
export type TriggerIdentity = {
|
||||||
author: string
|
author: string
|
||||||
name: string
|
name: string
|
||||||
version: string
|
label: TypeWithI18N
|
||||||
|
provider: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TriggerDescription = {
|
export type TriggerDescription = {
|
||||||
@ -92,6 +133,9 @@ export type TriggerProviderApiEntity = {
|
|||||||
tags: string[]
|
tags: string[]
|
||||||
plugin_id?: string
|
plugin_id?: string
|
||||||
plugin_unique_identifier?: string
|
plugin_unique_identifier?: string
|
||||||
|
credentials_schema: TriggerCredentialField[]
|
||||||
|
oauth_client_schema: TriggerCredentialField[]
|
||||||
|
subscription_schema: TriggerSubscriptionSchema
|
||||||
triggers: TriggerApiEntity[]
|
triggers: TriggerApiEntity[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,4 +143,56 @@ export type TriggerProviderApiEntity = {
|
|||||||
export type TriggerWithProvider = Collection & {
|
export type TriggerWithProvider = Collection & {
|
||||||
tools: Tool[] // Use existing Tool type for compatibility
|
tools: Tool[] // Use existing Tool type for compatibility
|
||||||
meta: PluginMeta
|
meta: PluginMeta
|
||||||
|
credentials_schema?: TriggerCredentialField[]
|
||||||
|
oauth_client_schema?: TriggerCredentialField[]
|
||||||
|
subscription_schema?: TriggerSubscriptionSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== API Service Types =====
|
||||||
|
|
||||||
|
// Trigger subscription instance types
|
||||||
|
export type TriggerSubscription = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
provider: string
|
||||||
|
credential_type: 'api_key' | 'oauth2' | 'unauthorized'
|
||||||
|
credentials: Record<string, any>
|
||||||
|
endpoint: string
|
||||||
|
parameters: Record<string, any>
|
||||||
|
properties: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TriggerSubscriptionBuilder = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
provider: string
|
||||||
|
endpoint: string
|
||||||
|
parameters: Record<string, any>
|
||||||
|
properties: Record<string, any>
|
||||||
|
credentials: Record<string, any>
|
||||||
|
credential_type: 'api_key' | 'oauth2' | 'unauthorized'
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuth configuration types
|
||||||
|
export type TriggerOAuthConfig = {
|
||||||
|
configured: boolean
|
||||||
|
custom_configured: boolean
|
||||||
|
custom_enabled: boolean
|
||||||
|
params: {
|
||||||
|
client_id: string
|
||||||
|
client_secret: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TriggerOAuthClientParams = {
|
||||||
|
client_id: string
|
||||||
|
client_secret: string
|
||||||
|
authorization_url?: string
|
||||||
|
token_url?: string
|
||||||
|
scope?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TriggerOAuthResponse = {
|
||||||
|
authorization_url: string
|
||||||
|
subscription_builder: TriggerSubscriptionBuilder
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,11 +59,11 @@ import PanelWrap from '../before-run-form/panel-wrap'
|
|||||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||||
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||||
import {
|
import {
|
||||||
AuthorizedInNode,
|
|
||||||
PluginAuth,
|
PluginAuth,
|
||||||
} from '@/app/components/plugins/plugin-auth'
|
} from '@/app/components/plugins/plugin-auth'
|
||||||
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
||||||
import { canFindTool } from '@/utils'
|
import { canFindTool } from '@/utils'
|
||||||
|
import NodeAuth from './node-auth-factory'
|
||||||
|
|
||||||
type BasePanelProps = {
|
type BasePanelProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -235,9 +235,13 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
const currCollection = useMemo(() => {
|
const currCollection = useMemo(() => {
|
||||||
return buildInTools.find(item => canFindTool(item.id, data.provider_id))
|
return buildInTools.find(item => canFindTool(item.id, data.provider_id))
|
||||||
}, [buildInTools, data.provider_id])
|
}, [buildInTools, data.provider_id])
|
||||||
const showPluginAuth = useMemo(() => {
|
|
||||||
return data.type === BlockEnum.Tool && currCollection?.allow_delete
|
// Unified check for any node that needs authentication UI
|
||||||
}, [currCollection, data.type])
|
const needsAuth = useMemo(() => {
|
||||||
|
return (data.type === BlockEnum.Tool && currCollection?.allow_delete)
|
||||||
|
|| (data.type === BlockEnum.TriggerPlugin)
|
||||||
|
}, [data.type, currCollection?.allow_delete])
|
||||||
|
|
||||||
const handleAuthorizationItemClick = useCallback((credential_id: string) => {
|
const handleAuthorizationItemClick = useCallback((credential_id: string) => {
|
||||||
handleNodeDataUpdateWithSyncDraft({
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
id,
|
id,
|
||||||
@ -379,7 +383,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
showPluginAuth && (
|
needsAuth && data.type === BlockEnum.Tool && currCollection?.allow_delete && (
|
||||||
<PluginAuth
|
<PluginAuth
|
||||||
className='px-4 pb-2'
|
className='px-4 pb-2'
|
||||||
pluginPayload={{
|
pluginPayload={{
|
||||||
@ -392,20 +396,30 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
value={tabType}
|
value={tabType}
|
||||||
onChange={setTabType}
|
onChange={setTabType}
|
||||||
/>
|
/>
|
||||||
<AuthorizedInNode
|
<NodeAuth
|
||||||
pluginPayload={{
|
data={data}
|
||||||
provider: currCollection?.name || '',
|
onAuthorizationChange={handleAuthorizationItemClick}
|
||||||
category: AuthCategory.tool,
|
|
||||||
}}
|
|
||||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
|
||||||
credentialId={data.credential_id}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PluginAuth>
|
</PluginAuth>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!showPluginAuth && (
|
needsAuth && data.type !== BlockEnum.Tool && (
|
||||||
|
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||||
|
<Tab
|
||||||
|
value={tabType}
|
||||||
|
onChange={setTabType}
|
||||||
|
/>
|
||||||
|
<NodeAuth
|
||||||
|
data={data}
|
||||||
|
onAuthorizationChange={handleAuthorizationItemClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!needsAuth && (
|
||||||
<div className='flex items-center justify-between pl-4 pr-3'>
|
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||||
<Tab
|
<Tab
|
||||||
value={tabType}
|
value={tabType}
|
||||||
|
|||||||
@ -0,0 +1,149 @@
|
|||||||
|
import type { FC } from 'react'
|
||||||
|
import { memo, useCallback, useMemo } from 'react'
|
||||||
|
import { AuthorizedInNode } from '@/app/components/plugins/plugin-auth'
|
||||||
|
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
||||||
|
import { BlockEnum, type Node } from '@/app/components/workflow/types'
|
||||||
|
import { canFindTool } from '@/utils'
|
||||||
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
|
import AuthenticationMenu from '@/app/components/workflow/nodes/trigger-plugin/components/authentication-menu'
|
||||||
|
import type { AuthSubscription } from '@/app/components/workflow/nodes/trigger-plugin/components/authentication-menu'
|
||||||
|
import {
|
||||||
|
useDeleteTriggerSubscription,
|
||||||
|
useInitiateTriggerOAuth,
|
||||||
|
useInvalidateTriggerSubscriptions,
|
||||||
|
useTriggerSubscriptions,
|
||||||
|
} from '@/service/use-triggers'
|
||||||
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
|
|
||||||
|
type NodeAuthProps = {
|
||||||
|
data: Node['data']
|
||||||
|
onAuthorizationChange: (credential_id: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeAuth: FC<NodeAuthProps> = ({ data, onAuthorizationChange }) => {
|
||||||
|
const buildInTools = useStore(s => s.buildInTools)
|
||||||
|
const { notify } = useToastContext()
|
||||||
|
|
||||||
|
// Construct the correct provider path for trigger plugins
|
||||||
|
// Format should be: plugin_id/provider_name (e.g., "langgenius/github_trigger/github_trigger")
|
||||||
|
const provider = useMemo(() => {
|
||||||
|
if (data.type === BlockEnum.TriggerPlugin) {
|
||||||
|
// If we have both plugin_id and provider_name, construct the full path
|
||||||
|
if (data.provider_id && data.provider_name)
|
||||||
|
return `${data.provider_id}/${data.provider_name}`
|
||||||
|
|
||||||
|
// Otherwise use provider_id as fallback (might be already complete)
|
||||||
|
return data.provider_id || ''
|
||||||
|
}
|
||||||
|
return data.provider_id || ''
|
||||||
|
}, [data.type, data.provider_id, data.provider_name])
|
||||||
|
|
||||||
|
// Always call hooks at the top level
|
||||||
|
const { data: subscriptions = [] } = useTriggerSubscriptions(
|
||||||
|
provider,
|
||||||
|
data.type === BlockEnum.TriggerPlugin && !!provider,
|
||||||
|
)
|
||||||
|
const deleteSubscription = useDeleteTriggerSubscription()
|
||||||
|
const initiateTriggerOAuth = useInitiateTriggerOAuth()
|
||||||
|
const invalidateSubscriptions = useInvalidateTriggerSubscriptions()
|
||||||
|
|
||||||
|
const currCollection = useMemo(() => {
|
||||||
|
return buildInTools.find(item => canFindTool(item.id, data.provider_id))
|
||||||
|
}, [buildInTools, data.provider_id])
|
||||||
|
|
||||||
|
// Convert TriggerSubscription to AuthSubscription format
|
||||||
|
const authSubscription: AuthSubscription = useMemo(() => {
|
||||||
|
if (data.type !== BlockEnum.TriggerPlugin) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
status: 'not_configured',
|
||||||
|
credentials: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = subscriptions[0] // Use first subscription if available
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
status: 'not_configured',
|
||||||
|
credentials: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = subscription.credential_type === 'unauthorized'
|
||||||
|
? 'not_configured'
|
||||||
|
: 'authorized'
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: subscription.id,
|
||||||
|
name: subscription.name,
|
||||||
|
status,
|
||||||
|
credentials: subscription.credentials,
|
||||||
|
}
|
||||||
|
}, [data.type, subscriptions])
|
||||||
|
|
||||||
|
const handleConfigure = useCallback(async () => {
|
||||||
|
if (!provider) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Directly initiate OAuth flow, backend will automatically create subscription builder
|
||||||
|
const response = await initiateTriggerOAuth.mutateAsync(provider)
|
||||||
|
if (response.authorization_url) {
|
||||||
|
// Open OAuth authorization window
|
||||||
|
const authWindow = window.open(response.authorization_url, 'oauth_authorization', 'width=600,height=600')
|
||||||
|
|
||||||
|
// Monitor window closure and refresh subscription list
|
||||||
|
const checkClosed = setInterval(() => {
|
||||||
|
if (authWindow?.closed) {
|
||||||
|
clearInterval(checkClosed)
|
||||||
|
invalidateSubscriptions(provider)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: `Failed to configure authentication: ${error.message}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [provider, initiateTriggerOAuth, invalidateSubscriptions, notify])
|
||||||
|
|
||||||
|
const handleRemove = useCallback(() => {
|
||||||
|
if (authSubscription.id)
|
||||||
|
deleteSubscription.mutate(authSubscription.id)
|
||||||
|
}, [authSubscription.id, deleteSubscription])
|
||||||
|
|
||||||
|
// Tool authentication
|
||||||
|
if (data.type === BlockEnum.Tool && currCollection?.allow_delete) {
|
||||||
|
return (
|
||||||
|
<AuthorizedInNode
|
||||||
|
pluginPayload={{
|
||||||
|
provider: currCollection?.name || '',
|
||||||
|
category: AuthCategory.tool,
|
||||||
|
}}
|
||||||
|
onAuthorizationItemClick={onAuthorizationChange}
|
||||||
|
credentialId={data.credential_id}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger Plugin authentication
|
||||||
|
if (data.type === BlockEnum.TriggerPlugin) {
|
||||||
|
return (
|
||||||
|
<AuthenticationMenu
|
||||||
|
subscription={authSubscription}
|
||||||
|
onConfigure={handleConfigure}
|
||||||
|
onRemove={handleRemove}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No authentication needed
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(NodeAuth)
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { memo, useCallback, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiArrowDownSLine } from '@remixicon/react'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import Indicator from '@/app/components/header/indicator'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
export type AuthenticationStatus = 'authorized' | 'not_configured' | 'error'
|
||||||
|
|
||||||
|
export type AuthSubscription = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
status: AuthenticationStatus
|
||||||
|
credentials?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticationMenuProps = {
|
||||||
|
subscription?: AuthSubscription
|
||||||
|
onConfigure: () => void
|
||||||
|
onRemove: () => void
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthenticationMenu: FC<AuthenticationMenuProps> = ({
|
||||||
|
subscription,
|
||||||
|
onConfigure,
|
||||||
|
onRemove,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
const getStatusConfig = useCallback(() => {
|
||||||
|
if (!subscription) {
|
||||||
|
return {
|
||||||
|
label: t('workflow.nodes.triggerPlugin.notConfigured'),
|
||||||
|
color: 'red' as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (subscription.status) {
|
||||||
|
case 'authorized':
|
||||||
|
return {
|
||||||
|
label: t('workflow.nodes.triggerPlugin.authorized'),
|
||||||
|
color: 'green' as const,
|
||||||
|
}
|
||||||
|
case 'error':
|
||||||
|
return {
|
||||||
|
label: t('workflow.nodes.triggerPlugin.error'),
|
||||||
|
color: 'red' as const,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
label: t('workflow.nodes.triggerPlugin.notConfigured'),
|
||||||
|
color: 'red' as const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [subscription, t])
|
||||||
|
|
||||||
|
const statusConfig = getStatusConfig()
|
||||||
|
|
||||||
|
const handleConfigure = useCallback(() => {
|
||||||
|
onConfigure()
|
||||||
|
setIsOpen(false)
|
||||||
|
}, [onConfigure])
|
||||||
|
|
||||||
|
const handleRemove = useCallback(() => {
|
||||||
|
onRemove()
|
||||||
|
setIsOpen(false)
|
||||||
|
}, [onRemove])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('relative', className)}>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
variant='ghost'
|
||||||
|
className={cn(
|
||||||
|
'h-6 px-1.5 py-1',
|
||||||
|
'hover:bg-components-button-ghost-bg-hover',
|
||||||
|
isOpen && 'bg-components-button-ghost-bg-hover',
|
||||||
|
)}
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<Indicator
|
||||||
|
className='mr-1.5'
|
||||||
|
color={statusConfig.color}
|
||||||
|
/>
|
||||||
|
<span className="text-xs font-medium text-components-button-ghost-text">
|
||||||
|
{statusConfig.label}
|
||||||
|
</span>
|
||||||
|
<RiArrowDownSLine
|
||||||
|
className='ml-1 h-3.5 w-3.5 text-components-button-ghost-text'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<>
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-20"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Dropdown Menu */}
|
||||||
|
<div className={cn(
|
||||||
|
'absolute right-0 z-30 mt-1',
|
||||||
|
'w-[136px] rounded-xl border-[0.5px] border-components-panel-border',
|
||||||
|
'bg-components-panel-bg-blur shadow-lg backdrop-blur-sm',
|
||||||
|
)}>
|
||||||
|
<div className="py-1">
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'block w-full px-4 py-2 text-left text-sm',
|
||||||
|
'text-text-secondary hover:bg-state-base-hover',
|
||||||
|
'mx-1 rounded-lg',
|
||||||
|
)}
|
||||||
|
onClick={handleConfigure}
|
||||||
|
>
|
||||||
|
{t('workflow.nodes.triggerPlugin.configuration')}
|
||||||
|
</button>
|
||||||
|
{subscription && subscription.status === 'authorized' && (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'block w-full px-4 py-2 text-left text-sm',
|
||||||
|
'text-text-destructive hover:bg-state-destructive-hover',
|
||||||
|
'mx-1 rounded-lg',
|
||||||
|
)}
|
||||||
|
onClick={handleRemove}
|
||||||
|
>
|
||||||
|
{t('workflow.nodes.triggerPlugin.remove')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AuthenticationMenu)
|
||||||
@ -6,11 +6,11 @@ import { ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/block
|
|||||||
const nodeDefault: NodeDefault<PluginTriggerNodeType> = {
|
const nodeDefault: NodeDefault<PluginTriggerNodeType> = {
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
plugin_id: '',
|
plugin_id: '',
|
||||||
plugin_name: '',
|
tool_name: '',
|
||||||
event_type: '',
|
event_type: '',
|
||||||
config: {},
|
config: {},
|
||||||
},
|
},
|
||||||
getAvailablePrevNodes(isChatMode: boolean) {
|
getAvailablePrevNodes(_isChatMode: boolean) {
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
getAvailableNextNodes(isChatMode: boolean) {
|
getAvailableNextNodes(isChatMode: boolean) {
|
||||||
@ -19,7 +19,7 @@ const nodeDefault: NodeDefault<PluginTriggerNodeType> = {
|
|||||||
: ALL_COMPLETION_AVAILABLE_BLOCKS
|
: ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||||
return nodes.filter(type => type !== BlockEnum.Start)
|
return nodes.filter(type => type !== BlockEnum.Start)
|
||||||
},
|
},
|
||||||
checkValid(payload: PluginTriggerNodeType, t: any) {
|
checkValid(_payload: PluginTriggerNodeType, _t: any) {
|
||||||
return {
|
return {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
|
|||||||
@ -2,54 +2,45 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { PluginTriggerNodeType } from './types'
|
import type { PluginTriggerNodeType } from './types'
|
||||||
import type { NodeProps } from '@/app/components/workflow/types'
|
import type { NodeProps } from '@/app/components/workflow/types'
|
||||||
|
import useConfig from './use-config'
|
||||||
|
|
||||||
const Node: FC<NodeProps<PluginTriggerNodeType>> = ({
|
const Node: FC<NodeProps<PluginTriggerNodeType>> = ({
|
||||||
|
id,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isAuthenticated } = useConfig(id, data)
|
||||||
const { config = {} } = data
|
const { config = {} } = data
|
||||||
const configKeys = Object.keys(config)
|
const configKeys = Object.keys(config)
|
||||||
|
|
||||||
if (!data.plugin_name && configKeys.length === 0)
|
// Only show config when authenticated and has config values
|
||||||
|
if (!isAuthenticated || configKeys.length === 0)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-1 px-3 py-1">
|
<div className="mb-1 px-3 py-1">
|
||||||
{data.plugin_name && (
|
<div className="space-y-0.5">
|
||||||
<div className="mb-1 text-xs font-medium text-gray-700">
|
{configKeys.map((key, index) => (
|
||||||
{data.plugin_name}
|
<div
|
||||||
{data.event_type && (
|
key={index}
|
||||||
<div className="text-xs text-gray-500">
|
className="flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary"
|
||||||
{data.event_type}
|
>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{configKeys.length > 0 && (
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
{configKeys.map((key, index) => (
|
|
||||||
<div
|
<div
|
||||||
key={index}
|
title={key}
|
||||||
className="flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary"
|
className="max-w-[100px] shrink-0 truncate text-xs font-medium uppercase text-text-tertiary"
|
||||||
>
|
>
|
||||||
<div
|
{key}
|
||||||
title={key}
|
|
||||||
className="max-w-[100px] shrink-0 truncate text-xs font-medium uppercase text-text-tertiary"
|
|
||||||
>
|
|
||||||
{key}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
title={String(config[key] || '')}
|
|
||||||
className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary"
|
|
||||||
>
|
|
||||||
{typeof config[key] === 'string' && config[key].includes('secret')
|
|
||||||
? '********'
|
|
||||||
: String(config[key] || '')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div
|
||||||
</div>
|
title={String(config[key] || '')}
|
||||||
)}
|
className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary"
|
||||||
|
>
|
||||||
|
{typeof config[key] === 'string' && config[key].includes('secret')
|
||||||
|
? '********'
|
||||||
|
: String(config[key] || '')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,85 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { PluginTriggerNodeType } from './types'
|
import type { PluginTriggerNodeType } from './types'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
|
import useConfig from './use-config'
|
||||||
|
import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form'
|
||||||
|
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
||||||
|
import { Type } from '../llm/types'
|
||||||
|
|
||||||
const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({
|
const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({
|
||||||
|
id,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
|
const {
|
||||||
|
readOnly,
|
||||||
|
triggerParameterSchema,
|
||||||
|
triggerParameterValue,
|
||||||
|
setTriggerParameterValue,
|
||||||
|
outputSchema,
|
||||||
|
hasObjectOutput,
|
||||||
|
isAuthenticated,
|
||||||
|
} = useConfig(id, data)
|
||||||
|
|
||||||
|
// Convert output schema to VarItem format
|
||||||
|
const outputVars = Object.entries(outputSchema.properties || {}).map(([name, schema]: [string, any]) => ({
|
||||||
|
name,
|
||||||
|
type: schema.type || 'string',
|
||||||
|
description: schema.description || '',
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='mt-2'>
|
||||||
<div className='space-y-4 px-4 pb-2'>
|
{/* Dynamic Parameters Form - Only show when authenticated */}
|
||||||
<Field title="Plugin Trigger">
|
{isAuthenticated && triggerParameterSchema.length > 0 && (
|
||||||
{data.plugin_name ? (
|
<>
|
||||||
<div className="space-y-2">
|
<div className='px-4 pb-4'>
|
||||||
<div className="flex items-center space-x-2">
|
<ToolForm
|
||||||
<span className="text-sm font-medium">{data.plugin_name}</span>
|
readOnly={readOnly}
|
||||||
{data.event_type && (
|
nodeId={id}
|
||||||
<span className="rounded bg-blue-100 px-2 py-1 text-xs text-blue-800">
|
schema={triggerParameterSchema as any}
|
||||||
{data.event_type}
|
value={triggerParameterValue}
|
||||||
</span>
|
onChange={setTriggerParameterValue}
|
||||||
)}
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">
|
<Split />
|
||||||
Plugin trigger configured
|
</>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{/* Output Variables - Always show */}
|
||||||
|
<OutputVars>
|
||||||
|
<>
|
||||||
|
{outputVars.map(varItem => (
|
||||||
|
<VarItem
|
||||||
|
key={varItem.name}
|
||||||
|
name={varItem.name}
|
||||||
|
type={varItem.type}
|
||||||
|
description={varItem.description}
|
||||||
|
isIndent={hasObjectOutput}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{Object.entries(outputSchema.properties || {}).map(([name, schema]: [string, any]) => (
|
||||||
|
<div key={name}>
|
||||||
|
{schema.type === 'object' ? (
|
||||||
|
<StructureOutputItem
|
||||||
|
rootClassName='code-sm-semibold text-text-secondary'
|
||||||
|
payload={{
|
||||||
|
schema: {
|
||||||
|
type: Type.object,
|
||||||
|
properties: {
|
||||||
|
[name]: schema,
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
))}
|
||||||
<div className="text-sm text-gray-500">
|
</>
|
||||||
No plugin selected. Configure this trigger in the workflow canvas.
|
</OutputVars>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { CollectionType } from '@/app/components/tools/types'
|
|||||||
|
|
||||||
export type PluginTriggerNodeType = CommonNodeType & {
|
export type PluginTriggerNodeType = CommonNodeType & {
|
||||||
plugin_id?: string
|
plugin_id?: string
|
||||||
plugin_name?: string
|
tool_name?: string
|
||||||
event_type?: string
|
event_type?: string
|
||||||
config?: Record<string, any>
|
config?: Record<string, any>
|
||||||
provider_id?: string
|
provider_id?: string
|
||||||
|
|||||||
122
web/app/components/workflow/nodes/trigger-plugin/use-config.ts
Normal file
122
web/app/components/workflow/nodes/trigger-plugin/use-config.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import produce from 'immer'
|
||||||
|
import type { PluginTriggerNodeType } from './types'
|
||||||
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
|
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
|
||||||
|
import { useAllTriggerPlugins, useTriggerSubscriptions } from '@/service/use-triggers'
|
||||||
|
import {
|
||||||
|
addDefaultValue,
|
||||||
|
toolParametersToFormSchemas,
|
||||||
|
} from '@/app/components/tools/utils/to-form-schema'
|
||||||
|
import type { InputVar } from '@/app/components/workflow/types'
|
||||||
|
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
||||||
|
import type { Tool } from '@/app/components/tools/types'
|
||||||
|
|
||||||
|
const useConfig = (id: string, payload: PluginTriggerNodeType) => {
|
||||||
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
|
const { data: triggerPlugins = [] } = useAllTriggerPlugins()
|
||||||
|
|
||||||
|
const { inputs, setInputs: doSetInputs } = useNodeCrud<PluginTriggerNodeType>(id, payload)
|
||||||
|
|
||||||
|
const { provider_id, provider_name, tool_name, config } = inputs
|
||||||
|
|
||||||
|
// Construct provider for authentication check
|
||||||
|
const authProvider = useMemo(() => {
|
||||||
|
if (provider_id && provider_name)
|
||||||
|
return `${provider_id}/${provider_name}`
|
||||||
|
return provider_id || ''
|
||||||
|
}, [provider_id, provider_name])
|
||||||
|
|
||||||
|
const { data: subscriptions = [] } = useTriggerSubscriptions(
|
||||||
|
authProvider,
|
||||||
|
!!authProvider,
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentProvider = useMemo<TriggerWithProvider | undefined>(() => {
|
||||||
|
return triggerPlugins.find(provider =>
|
||||||
|
provider.name === provider_name
|
||||||
|
|| provider.id === provider_id
|
||||||
|
|| (provider_id && provider.plugin_id === provider_id),
|
||||||
|
)
|
||||||
|
}, [triggerPlugins, provider_name, provider_id])
|
||||||
|
|
||||||
|
const currentTrigger = useMemo<Tool | undefined>(() => {
|
||||||
|
return currentProvider?.tools.find(tool => tool.name === tool_name)
|
||||||
|
}, [currentProvider, tool_name])
|
||||||
|
|
||||||
|
// Dynamic subscription parameters (from subscription_schema.parameters_schema)
|
||||||
|
const subscriptionParameterSchema = useMemo(() => {
|
||||||
|
if (!currentProvider?.subscription_schema?.parameters_schema) return []
|
||||||
|
return toolParametersToFormSchemas(currentProvider.subscription_schema.parameters_schema as any)
|
||||||
|
}, [currentProvider])
|
||||||
|
|
||||||
|
// Dynamic trigger parameters (from specific trigger.parameters)
|
||||||
|
const triggerSpecificParameterSchema = useMemo(() => {
|
||||||
|
if (!currentTrigger) return []
|
||||||
|
return toolParametersToFormSchemas(currentTrigger.parameters)
|
||||||
|
}, [currentTrigger])
|
||||||
|
|
||||||
|
// Combined parameter schema (subscription + trigger specific)
|
||||||
|
const triggerParameterSchema = useMemo(() => {
|
||||||
|
return [...subscriptionParameterSchema, ...triggerSpecificParameterSchema]
|
||||||
|
}, [subscriptionParameterSchema, triggerSpecificParameterSchema])
|
||||||
|
|
||||||
|
const triggerParameterValue = useMemo(() => {
|
||||||
|
if (!triggerParameterSchema.length) return {}
|
||||||
|
return addDefaultValue(config || {}, triggerParameterSchema)
|
||||||
|
}, [triggerParameterSchema, config])
|
||||||
|
|
||||||
|
const setTriggerParameterValue = useCallback((value: Record<string, any>) => {
|
||||||
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
draft.config = value
|
||||||
|
})
|
||||||
|
doSetInputs(newInputs)
|
||||||
|
}, [inputs, doSetInputs])
|
||||||
|
|
||||||
|
const setInputVar = useCallback((variable: InputVar, varDetail: InputVar) => {
|
||||||
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
draft.config = {
|
||||||
|
...draft.config,
|
||||||
|
[variable.variable]: varDetail.variable,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
doSetInputs(newInputs)
|
||||||
|
}, [inputs, doSetInputs])
|
||||||
|
|
||||||
|
// Get output schema
|
||||||
|
const outputSchema = useMemo(() => {
|
||||||
|
return currentTrigger?.output_schema || {}
|
||||||
|
}, [currentTrigger])
|
||||||
|
|
||||||
|
// Check if trigger has complex output structure
|
||||||
|
const hasObjectOutput = useMemo(() => {
|
||||||
|
const properties = outputSchema.properties || {}
|
||||||
|
return Object.values(properties).some((prop: any) => prop.type === 'object')
|
||||||
|
}, [outputSchema])
|
||||||
|
|
||||||
|
// Authentication status check
|
||||||
|
const isAuthenticated = useMemo(() => {
|
||||||
|
if (!subscriptions.length) return false
|
||||||
|
const subscription = subscriptions[0]
|
||||||
|
return subscription.credential_type !== 'unauthorized'
|
||||||
|
}, [subscriptions])
|
||||||
|
|
||||||
|
const showAuthRequired = !isAuthenticated && !!currentProvider
|
||||||
|
|
||||||
|
return {
|
||||||
|
readOnly,
|
||||||
|
inputs,
|
||||||
|
currentProvider,
|
||||||
|
currentTrigger,
|
||||||
|
triggerParameterSchema,
|
||||||
|
triggerParameterValue,
|
||||||
|
setTriggerParameterValue,
|
||||||
|
setInputVar,
|
||||||
|
outputSchema,
|
||||||
|
hasObjectOutput,
|
||||||
|
isAuthenticated,
|
||||||
|
showAuthRequired,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useConfig
|
||||||
@ -724,6 +724,13 @@ const translation = {
|
|||||||
json: 'tool generated json',
|
json: 'tool generated json',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
triggerPlugin: {
|
||||||
|
authorized: 'Authorized',
|
||||||
|
notConfigured: 'Not Configured',
|
||||||
|
error: 'Error',
|
||||||
|
configuration: 'Configuration',
|
||||||
|
remove: 'Remove',
|
||||||
|
},
|
||||||
questionClassifiers: {
|
questionClassifiers: {
|
||||||
model: 'model',
|
model: 'model',
|
||||||
inputVars: 'Input Variables',
|
inputVars: 'Input Variables',
|
||||||
|
|||||||
@ -1034,6 +1034,13 @@ const translation = {
|
|||||||
invalidParameterType: 'パラメータ"{{name}}"の無効なパラメータタイプ"{{type}}"です',
|
invalidParameterType: 'パラメータ"{{name}}"の無効なパラメータタイプ"{{type}}"です',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
triggerPlugin: {
|
||||||
|
authorized: '認可された',
|
||||||
|
notConfigured: '設定されていません',
|
||||||
|
error: 'エラー',
|
||||||
|
configuration: '構成',
|
||||||
|
remove: '削除する',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tracing: {
|
tracing: {
|
||||||
stopBy: '{{user}}によって停止',
|
stopBy: '{{user}}によって停止',
|
||||||
|
|||||||
@ -1034,6 +1034,13 @@ const translation = {
|
|||||||
invalidParameterType: '参数"{{name}}"的参数类型"{{type}}"无效',
|
invalidParameterType: '参数"{{name}}"的参数类型"{{type}}"无效',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
triggerPlugin: {
|
||||||
|
authorized: '已授权',
|
||||||
|
notConfigured: '未配置',
|
||||||
|
error: '错误',
|
||||||
|
configuration: '配置',
|
||||||
|
remove: '移除',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tracing: {
|
tracing: {
|
||||||
stopBy: '由{{user}}终止',
|
stopBy: '由{{user}}终止',
|
||||||
|
|||||||
@ -1,10 +1,20 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { get } from './base'
|
import { del, get, post } from './base'
|
||||||
import type { TriggerProviderApiEntity, TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
import type {
|
||||||
|
TriggerOAuthClientParams,
|
||||||
|
TriggerOAuthConfig,
|
||||||
|
TriggerProviderApiEntity,
|
||||||
|
TriggerSubscription,
|
||||||
|
TriggerSubscriptionBuilder,
|
||||||
|
TriggerWithProvider,
|
||||||
|
} from '@/app/components/workflow/block-selector/types'
|
||||||
import { CollectionType } from '@/app/components/tools/types'
|
import { CollectionType } from '@/app/components/tools/types'
|
||||||
|
import { useInvalid } from './use-base'
|
||||||
|
|
||||||
const NAME_SPACE = 'triggers'
|
const NAME_SPACE = 'triggers'
|
||||||
|
|
||||||
|
// Trigger Provider Service - Provider ID Format: plugin_id/provider_name
|
||||||
|
|
||||||
// Convert backend API response to frontend ToolWithProvider format
|
// Convert backend API response to frontend ToolWithProvider format
|
||||||
const convertToTriggerWithProvider = (provider: TriggerProviderApiEntity): TriggerWithProvider => {
|
const convertToTriggerWithProvider = (provider: TriggerProviderApiEntity): TriggerWithProvider => {
|
||||||
return {
|
return {
|
||||||
@ -37,19 +47,26 @@ const convertToTriggerWithProvider = (provider: TriggerProviderApiEntity): Trigg
|
|||||||
llm_description: JSON.stringify(param.description || {}),
|
llm_description: JSON.stringify(param.description || {}),
|
||||||
required: param.required || false,
|
required: param.required || false,
|
||||||
default: param.default || '',
|
default: param.default || '',
|
||||||
options: [],
|
options: param.options?.map(option => ({
|
||||||
|
label: option.label,
|
||||||
|
value: option.value,
|
||||||
|
})) || [],
|
||||||
})),
|
})),
|
||||||
labels: provider.tags || [],
|
labels: provider.tags || [],
|
||||||
output_schema: trigger.output_schema || {},
|
output_schema: trigger.output_schema || {},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
// Trigger-specific schema fields
|
||||||
|
credentials_schema: provider.credentials_schema,
|
||||||
|
oauth_client_schema: provider.oauth_client_schema,
|
||||||
|
subscription_schema: provider.subscription_schema,
|
||||||
|
|
||||||
meta: {
|
meta: {
|
||||||
version: '1.0',
|
version: '1.0',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main hook - follows exact same pattern as tools
|
|
||||||
export const useAllTriggerPlugins = (enabled = true) => {
|
export const useAllTriggerPlugins = (enabled = true) => {
|
||||||
return useQuery<TriggerWithProvider[]>({
|
return useQuery<TriggerWithProvider[]>({
|
||||||
queryKey: [NAME_SPACE, 'all'],
|
queryKey: [NAME_SPACE, 'all'],
|
||||||
@ -61,7 +78,6 @@ export const useAllTriggerPlugins = (enabled = true) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional hook for consistency with tools pattern
|
|
||||||
export const useTriggerPluginsByType = (triggerType: string, enabled = true) => {
|
export const useTriggerPluginsByType = (triggerType: string, enabled = true) => {
|
||||||
return useQuery<TriggerWithProvider[]>({
|
return useQuery<TriggerWithProvider[]>({
|
||||||
queryKey: [NAME_SPACE, 'byType', triggerType],
|
queryKey: [NAME_SPACE, 'byType', triggerType],
|
||||||
@ -72,3 +88,200 @@ export const useTriggerPluginsByType = (triggerType: string, enabled = true) =>
|
|||||||
enabled: enabled && !!triggerType,
|
enabled: enabled && !!triggerType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useInvalidateAllTriggerPlugins = () => {
|
||||||
|
return useInvalid([NAME_SPACE, 'all'])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Trigger Subscriptions Management =====
|
||||||
|
export const useTriggerSubscriptions = (provider: string, enabled = true) => {
|
||||||
|
return useQuery<TriggerSubscription[]>({
|
||||||
|
queryKey: [NAME_SPACE, 'subscriptions', provider],
|
||||||
|
queryFn: () => get<TriggerSubscription[]>(`/workspaces/current/trigger-provider/${provider}/subscriptions/list`),
|
||||||
|
enabled: enabled && !!provider,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInvalidateTriggerSubscriptions = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return (provider: string) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [NAME_SPACE, 'subscriptions', provider],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateTriggerSubscriptionBuilder = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'create-subscription-builder'],
|
||||||
|
mutationFn: (payload: {
|
||||||
|
provider: string
|
||||||
|
name?: string
|
||||||
|
credentials?: Record<string, any>
|
||||||
|
}) => {
|
||||||
|
const { provider, ...body } = payload
|
||||||
|
return post<{ subscription_builder: TriggerSubscriptionBuilder }>(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/create`,
|
||||||
|
{ body },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateTriggerSubscriptionBuilder = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'update-subscription-builder'],
|
||||||
|
mutationFn: (payload: {
|
||||||
|
provider: string
|
||||||
|
subscriptionBuilderId: string
|
||||||
|
name?: string
|
||||||
|
parameters?: Record<string, any>
|
||||||
|
properties?: Record<string, any>
|
||||||
|
credentials?: Record<string, any>
|
||||||
|
}) => {
|
||||||
|
const { provider, subscriptionBuilderId, ...body } = payload
|
||||||
|
return post<TriggerSubscriptionBuilder>(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/update/${subscriptionBuilderId}`,
|
||||||
|
{ body },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVerifyTriggerSubscriptionBuilder = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'verify-subscription-builder'],
|
||||||
|
mutationFn: (payload: {
|
||||||
|
provider: string
|
||||||
|
subscriptionBuilderId: string
|
||||||
|
}) => {
|
||||||
|
const { provider, subscriptionBuilderId } = payload
|
||||||
|
return post(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify/${subscriptionBuilderId}`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBuildTriggerSubscription = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'build-subscription'],
|
||||||
|
mutationFn: (payload: {
|
||||||
|
provider: string
|
||||||
|
subscriptionBuilderId: string
|
||||||
|
}) => {
|
||||||
|
const { provider, subscriptionBuilderId } = payload
|
||||||
|
return post(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/build/${subscriptionBuilderId}`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeleteTriggerSubscription = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'delete-subscription'],
|
||||||
|
mutationFn: (subscriptionId: string) => {
|
||||||
|
return post<{ result: string }>(
|
||||||
|
`/workspaces/current/trigger-provider/${subscriptionId}/subscriptions/delete`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTriggerSubscriptionBuilderLogs = (
|
||||||
|
provider: string,
|
||||||
|
subscriptionBuilderId: string,
|
||||||
|
options: {
|
||||||
|
enabled?: boolean
|
||||||
|
refetchInterval?: number | false
|
||||||
|
} = {},
|
||||||
|
) => {
|
||||||
|
const { enabled = true, refetchInterval = false } = options
|
||||||
|
|
||||||
|
return useQuery<Record<string, any>[]>({
|
||||||
|
queryKey: [NAME_SPACE, 'subscription-builder-logs', provider, subscriptionBuilderId],
|
||||||
|
queryFn: () => get(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/logs/${subscriptionBuilderId}`,
|
||||||
|
),
|
||||||
|
enabled: enabled && !!provider && !!subscriptionBuilderId,
|
||||||
|
refetchInterval,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== OAuth Management =====
|
||||||
|
export const useTriggerOAuthConfig = (provider: string, enabled = true) => {
|
||||||
|
return useQuery<TriggerOAuthConfig>({
|
||||||
|
queryKey: [NAME_SPACE, 'oauth-config', provider],
|
||||||
|
queryFn: () => get<TriggerOAuthConfig>(`/workspaces/current/trigger-provider/${provider}/oauth/client`),
|
||||||
|
enabled: enabled && !!provider,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useConfigureTriggerOAuth = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'configure-oauth'],
|
||||||
|
mutationFn: (payload: {
|
||||||
|
provider: string
|
||||||
|
client_params: TriggerOAuthClientParams
|
||||||
|
enabled: boolean
|
||||||
|
}) => {
|
||||||
|
const { provider, ...body } = payload
|
||||||
|
return post<{ result: string }>(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/oauth/client`,
|
||||||
|
{ body },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeleteTriggerOAuth = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'delete-oauth'],
|
||||||
|
mutationFn: (provider: string) => {
|
||||||
|
return del<{ result: string }>(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/oauth/client`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInitiateTriggerOAuth = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: [NAME_SPACE, 'initiate-oauth'],
|
||||||
|
mutationFn: (provider: string) => {
|
||||||
|
return get<{ authorization_url: string; subscription_builder: any }>(
|
||||||
|
`/workspaces/current/trigger-provider/${provider}/subscriptions/oauth/authorize`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Dynamic Options Support =====
|
||||||
|
export const useTriggerPluginDynamicOptions = (payload: {
|
||||||
|
plugin_id: string
|
||||||
|
provider: string
|
||||||
|
action: string
|
||||||
|
parameter: string
|
||||||
|
extra?: Record<string, any>
|
||||||
|
}, enabled = true) => {
|
||||||
|
return useQuery<{ options: Array<{ value: string; label: any }> }>({
|
||||||
|
queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.extra],
|
||||||
|
queryFn: () => get<{ options: Array<{ value: string; label: any }> }>(
|
||||||
|
'/workspaces/current/plugin/parameters/dynamic-options',
|
||||||
|
{ params: payload },
|
||||||
|
),
|
||||||
|
enabled: enabled && !!payload.plugin_id && !!payload.provider && !!payload.action && !!payload.parameter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Cache Invalidation Helpers =====
|
||||||
|
|
||||||
|
export const useInvalidateTriggerOAuthConfig = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return (provider: string) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [NAME_SPACE, 'oauth-config', provider],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user