mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
feat: introduce trigger functionality (#27644)
Signed-off-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: Stream <Stream_2@qq.com> Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do> Co-authored-by: Harry <xh001x@hotmail.com> Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: yessenia <yessenia.contact@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: WTW0313 <twwu@dify.ai> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@ -1,8 +1,8 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { del, get, patch, post, put } from './base'
|
||||
import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
||||
import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type { AppIconType, AppMode, ModelConfig } from '@/types/app'
|
||||
import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app'
|
||||
import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
|
||||
|
||||
export const fetchAppList: Fetcher<AppListResponse, { url: string; params?: Record<string, any> }> = ({ url, params }) => {
|
||||
@ -22,7 +22,7 @@ export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> =
|
||||
return get<AppTemplatesResponse>(url)
|
||||
}
|
||||
|
||||
export const createApp: Fetcher<AppDetailResponse, { name: string; icon_type?: AppIconType; icon?: string; icon_background?: string; mode: AppMode; description?: string; config?: ModelConfig }> = ({ name, icon_type, icon, icon_background, mode, description, config }) => {
|
||||
export const createApp: Fetcher<AppDetailResponse, { name: string; icon_type?: AppIconType; icon?: string; icon_background?: string; mode: AppModeEnum; description?: string; config?: ModelConfig }> = ({ name, icon_type, icon, icon_background, mode, description, config }) => {
|
||||
return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } })
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: st
|
||||
return put<AppDetailResponse>(`apps/${appID}`, { body })
|
||||
}
|
||||
|
||||
export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppMode; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => {
|
||||
export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppModeEnum; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => {
|
||||
return post<AppDetailResponse>(`apps/${appID}/copy`, { body: { name, icon_type, icon, icon_background, mode, description } })
|
||||
}
|
||||
|
||||
@ -162,6 +162,11 @@ export const updateTracingStatus: Fetcher<CommonResponse, { appId: string; body:
|
||||
return post(`/apps/${appId}/trace`, { body })
|
||||
}
|
||||
|
||||
// Webhook Trigger
|
||||
export const fetchWebhookUrl: Fetcher<WebhookTriggerResponse, { appId: string; nodeId: string }> = ({ appId, nodeId }) => {
|
||||
return get<WebhookTriggerResponse>(`apps/${appId}/workflows/triggers/webhook`, { params: { node_id: nodeId } })
|
||||
}
|
||||
|
||||
export const fetchTracingConfig: Fetcher<TracingConfig & { has_not_configured: true }, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
||||
return get(`/apps/${appId}/trace-config`, {
|
||||
params: {
|
||||
|
||||
@ -155,7 +155,7 @@ export function format(text: string) {
|
||||
return res.replaceAll('\n', '<br/>').replaceAll('```', '')
|
||||
}
|
||||
|
||||
const handleStream = (
|
||||
export const handleStream = (
|
||||
response: Response,
|
||||
onData: IOnData,
|
||||
onCompleted?: IOnCompleted,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { get, post, ssePost } from './base'
|
||||
import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessageReplace, IOnThought } from './base'
|
||||
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
|
||||
import type { ModelModeType } from '@/types/app'
|
||||
import type { AppModeEnum, ModelModeType } from '@/types/app'
|
||||
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
export type BasicAppFirstRes = {
|
||||
prompt: string
|
||||
variables: string[]
|
||||
@ -105,7 +106,7 @@ export const fetchPromptTemplate = ({
|
||||
mode,
|
||||
modelName,
|
||||
hasSetDataSet,
|
||||
}: { appMode: string; mode: ModelModeType; modelName: string; hasSetDataSet: boolean }) => {
|
||||
}: { appMode: AppModeEnum; mode: ModelModeType; modelName: string; hasSetDataSet: boolean }) => {
|
||||
return get<Promise<{ chat_prompt_config: ChatPromptConfig; completion_prompt_config: CompletionPromptConfig; stop: [] }>>('/app/prompt-templates', {
|
||||
params: {
|
||||
app_mode: appMode,
|
||||
|
||||
@ -4,6 +4,8 @@ import React from 'react'
|
||||
import useSWR, { useSWRConfig } from 'swr'
|
||||
import { createApp, fetchAppDetail, fetchAppList, getAppDailyConversations, getAppDailyEndUsers, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
const Service: FC = () => {
|
||||
const { data: appList, error: appListError } = useSWR({ url: '/apps', params: { page: 1 } }, fetchAppList)
|
||||
const { data: firstApp, error: appDetailError } = useSWR({ url: '/apps', id: '1' }, fetchAppDetail)
|
||||
@ -21,7 +23,7 @@ const Service: FC = () => {
|
||||
const handleCreateApp = async () => {
|
||||
await createApp({
|
||||
name: `new app${Math.round(Math.random() * 100)}`,
|
||||
mode: 'chat',
|
||||
mode: AppModeEnum.CHAT,
|
||||
})
|
||||
// reload app list
|
||||
mutate({ url: '/apps', params: { page: 1 } })
|
||||
|
||||
@ -2,7 +2,7 @@ import type { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, Hooks } fro
|
||||
import ky from 'ky'
|
||||
import type { IOtherOptions } from './base'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { API_PREFIX, APP_VERSION, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, MARKETPLACE_API_PREFIX, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
|
||||
import { API_PREFIX, APP_VERSION, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_MARKETPLACE, MARKETPLACE_API_PREFIX, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
|
||||
import Cookies from 'js-cookie'
|
||||
import { getWebAppAccessToken, getWebAppPassport } from './webapp-auth'
|
||||
|
||||
@ -160,7 +160,7 @@ async function base<T>(url: string, options: FetchOptionType = {}, otherOptions:
|
||||
|
||||
// ! For Marketplace API, help to filter tags added in new version
|
||||
if (isMarketplaceAPI)
|
||||
(headers as any).set('X-Dify-Version', APP_VERSION)
|
||||
(headers as any).set('X-Dify-Version', !IS_MARKETPLACE ? APP_VERSION : '999.0.0')
|
||||
|
||||
const client = baseClient.extend({
|
||||
hooks: {
|
||||
|
||||
@ -295,7 +295,8 @@ export const fetchAccessToken = async ({ userId, appCode }: { userId?: string, a
|
||||
if (accessToken)
|
||||
headers.append('Authorization', `Bearer ${accessToken}`)
|
||||
const params = new URLSearchParams()
|
||||
userId && params.append('user_id', userId)
|
||||
if (userId)
|
||||
params.append('user_id', userId)
|
||||
const url = `/passport?${params.toString()}`
|
||||
return get<{ access_token: string }>(url, { headers }) as Promise<{ access_token: string }>
|
||||
}
|
||||
|
||||
@ -3,9 +3,11 @@ import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
export const useInvalid = (key: QueryKey) => {
|
||||
export const useInvalid = (key?: QueryKey) => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
if (!key)
|
||||
return
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: key,
|
||||
@ -14,9 +16,11 @@ export const useInvalid = (key: QueryKey) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useReset = (key: QueryKey) => {
|
||||
export const useReset = (key?: QueryKey) => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
if (!key)
|
||||
return
|
||||
queryClient.resetQueries(
|
||||
{
|
||||
queryKey: key,
|
||||
|
||||
@ -19,7 +19,6 @@ import type {
|
||||
PluginDetail,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginTask,
|
||||
PluginType,
|
||||
PluginsFromMarketplaceByInfoResponse,
|
||||
PluginsFromMarketplaceResponse,
|
||||
ReferenceSetting,
|
||||
@ -28,7 +27,7 @@ import type {
|
||||
uploadGitHubResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import { TaskStatus } from '@/app/components/plugins/types'
|
||||
import { PluginType as PluginTypeEnum } from '@/app/components/plugins/types'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import type {
|
||||
PluginsSearchParams,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
@ -45,6 +44,7 @@ import useReferenceSetting from '@/app/components/plugins/plugin-page/use-refere
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
|
||||
|
||||
const NAME_SPACE = 'plugins'
|
||||
|
||||
@ -68,6 +68,66 @@ export const useCheckInstalled = ({
|
||||
})
|
||||
}
|
||||
|
||||
const useRecommendedMarketplacePluginsKey = [NAME_SPACE, 'recommendedMarketplacePlugins']
|
||||
export const useRecommendedMarketplacePlugins = ({
|
||||
collection = '__recommended-plugins-tools',
|
||||
enabled = true,
|
||||
limit = 15,
|
||||
}: {
|
||||
collection?: string
|
||||
enabled?: boolean
|
||||
limit?: number
|
||||
} = {}) => {
|
||||
return useQuery<Plugin[]>({
|
||||
queryKey: [...useRecommendedMarketplacePluginsKey, collection, limit],
|
||||
queryFn: async () => {
|
||||
const response = await postMarketplace<{ data: { plugins: Plugin[] } }>(
|
||||
`/collections/${collection}/plugins`,
|
||||
{
|
||||
body: {
|
||||
limit,
|
||||
},
|
||||
},
|
||||
)
|
||||
return response.data.plugins.map(plugin => getFormattedPlugin(plugin))
|
||||
},
|
||||
enabled,
|
||||
staleTime: 60 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export const useFeaturedToolsRecommendations = (enabled: boolean, limit = 15) => {
|
||||
const {
|
||||
data: plugins = [],
|
||||
isLoading,
|
||||
} = useRecommendedMarketplacePlugins({
|
||||
collection: '__recommended-plugins-tools',
|
||||
enabled,
|
||||
limit,
|
||||
})
|
||||
|
||||
return {
|
||||
plugins,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFeaturedTriggersRecommendations = (enabled: boolean, limit = 15) => {
|
||||
const {
|
||||
data: plugins = [],
|
||||
isLoading,
|
||||
} = useRecommendedMarketplacePlugins({
|
||||
collection: '__recommended-plugins-triggers',
|
||||
enabled,
|
||||
limit,
|
||||
})
|
||||
|
||||
return {
|
||||
plugins,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
|
||||
export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => {
|
||||
const fetchPlugins = async ({ pageParam = 1 }) => {
|
||||
const response = await get<InstalledPluginListWithTotalResponse>(
|
||||
@ -518,7 +578,7 @@ export const useFetchPluginsInMarketPlaceByInfo = (infos: Record<string, any>[])
|
||||
}
|
||||
|
||||
const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']
|
||||
export const usePluginTaskList = (category?: PluginType) => {
|
||||
export const usePluginTaskList = (category?: PluginCategoryEnum | string) => {
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const {
|
||||
canManagement,
|
||||
@ -544,20 +604,20 @@ export const usePluginTaskList = (category?: PluginType) => {
|
||||
useEffect(() => {
|
||||
// After first fetch, refresh plugin list each time all tasks are done
|
||||
// Skip initialization period, because the query cache is not updated yet
|
||||
if (initialized && !isRefetching) {
|
||||
const lastData = cloneDeep(data)
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
|
||||
if (taskDone) {
|
||||
if (lastData?.tasks.length && !taskAllFailed)
|
||||
refreshPluginList(category ? { category } as any : undefined, !category)
|
||||
}
|
||||
}
|
||||
}, [isRefetching])
|
||||
if (!initialized || isRefetching)
|
||||
return
|
||||
|
||||
const lastData = cloneDeep(data)
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
|
||||
if (taskDone && lastData?.tasks.length && !taskAllFailed)
|
||||
refreshPluginList(category ? { category } as any : undefined, !category)
|
||||
}, [initialized, isRefetching, data, category, refreshPluginList])
|
||||
|
||||
useEffect(() => {
|
||||
setInitialized(true)
|
||||
}, [])
|
||||
if (isFetched && !initialized)
|
||||
setInitialized(true)
|
||||
}, [isFetched, initialized])
|
||||
|
||||
const handleRefetch = useCallback(() => {
|
||||
refetch()
|
||||
@ -641,7 +701,7 @@ export const usePluginInfo = (providerName?: string) => {
|
||||
const name = parts[1]
|
||||
try {
|
||||
const response = await fetchPluginInfoFromMarketPlace({ org, name })
|
||||
return response.data.plugin.category === PluginTypeEnum.model ? response.data.plugin : null
|
||||
return response.data.plugin.category === PluginCategoryEnum.model ? response.data.plugin : null
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
@ -651,7 +711,7 @@ export const usePluginInfo = (providerName?: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type: 'tool') => {
|
||||
export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type?: string, extra?: Record<string, any>) => {
|
||||
return useMutation({
|
||||
mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', {
|
||||
params: {
|
||||
@ -660,7 +720,26 @@ export const useFetchDynamicOptions = (plugin_id: string, provider: string, acti
|
||||
action,
|
||||
parameter,
|
||||
provider_type,
|
||||
...extra,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginReadme = ({ plugin_unique_identifier, language }: { plugin_unique_identifier: string, language?: string }) => {
|
||||
return useQuery({
|
||||
queryKey: ['pluginReadme', plugin_unique_identifier, language],
|
||||
queryFn: () => get<{ readme: string }>('/workspaces/current/plugin/readme', { params: { plugin_unique_identifier, language } }, { silent: true }),
|
||||
enabled: !!plugin_unique_identifier,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginReadmeAsset = ({ file_name, plugin_unique_identifier }: { file_name?: string, plugin_unique_identifier?: string }) => {
|
||||
const normalizedFileName = file_name?.replace(/(^\.\/_assets\/|^_assets\/)/, '')
|
||||
return useQuery({
|
||||
queryKey: ['pluginReadmeAsset', plugin_unique_identifier, file_name],
|
||||
queryFn: () => get<Blob>('/workspaces/current/plugin/asset', { params: { plugin_unique_identifier, file_name: normalizedFileName } }, { silent: true }),
|
||||
enabled: !!plugin_unique_identifier && !!file_name && /(^\.\/_assets|^_assets)/.test(file_name),
|
||||
})
|
||||
}
|
||||
|
||||
@ -84,8 +84,9 @@ const useInvalidToolsKeyMap: Record<string, QueryKey> = {
|
||||
[CollectionType.workflow]: useAllWorkflowToolsKey,
|
||||
[CollectionType.mcp]: useAllMCPToolsKey,
|
||||
}
|
||||
export const useInvalidToolsByType = (type: CollectionType | string) => {
|
||||
return useInvalid(useInvalidToolsKeyMap[type])
|
||||
export const useInvalidToolsByType = (type?: CollectionType | string) => {
|
||||
const queryKey = type ? useInvalidToolsKeyMap[type] : undefined
|
||||
return useInvalid(queryKey)
|
||||
}
|
||||
|
||||
export const useCreateMCP = () => {
|
||||
@ -339,3 +340,53 @@ export const useRAGRecommendedPlugins = () => {
|
||||
export const useInvalidateRAGRecommendedPlugins = () => {
|
||||
return useInvalid(useRAGRecommendedPluginListKey)
|
||||
}
|
||||
|
||||
// App Triggers API hooks
|
||||
export type AppTrigger = {
|
||||
id: string
|
||||
trigger_type: 'trigger-webhook' | 'trigger-schedule' | 'trigger-plugin'
|
||||
title: string
|
||||
node_id: string
|
||||
provider_name: string
|
||||
icon: string
|
||||
status: 'enabled' | 'disabled' | 'unauthorized'
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export const useAppTriggers = (appId: string | undefined, options?: any) => {
|
||||
return useQuery<{ data: AppTrigger[] }>({
|
||||
queryKey: [NAME_SPACE, 'app-triggers', appId],
|
||||
queryFn: () => get<{ data: AppTrigger[] }>(`/apps/${appId}/triggers`),
|
||||
enabled: !!appId,
|
||||
...options, // Merge additional options while maintaining backward compatibility
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAppTriggers = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (appId: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'app-triggers', appId],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useUpdateTriggerStatus = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-trigger-status'],
|
||||
mutationFn: (payload: {
|
||||
appId: string
|
||||
triggerId: string
|
||||
enableTrigger: boolean
|
||||
}) => {
|
||||
const { appId, triggerId, enableTrigger } = payload
|
||||
return post<AppTrigger>(`/apps/${appId}/trigger-enable`, {
|
||||
body: {
|
||||
trigger_id: triggerId,
|
||||
enable_trigger: enableTrigger,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
320
web/service/use-triggers.ts
Normal file
320
web/service/use-triggers.ts
Normal file
@ -0,0 +1,320 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { del, get, post } from './base'
|
||||
import type {
|
||||
TriggerLogEntity,
|
||||
TriggerOAuthClientParams,
|
||||
TriggerOAuthConfig,
|
||||
TriggerProviderApiEntity,
|
||||
TriggerSubscription,
|
||||
TriggerSubscriptionBuilder,
|
||||
TriggerWithProvider,
|
||||
} from '@/app/components/workflow/block-selector/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { useInvalid } from './use-base'
|
||||
|
||||
const NAME_SPACE = 'triggers'
|
||||
|
||||
// Trigger Provider Service - Provider ID Format: plugin_id/provider_name
|
||||
|
||||
// Convert backend API response to frontend ToolWithProvider format
|
||||
const convertToTriggerWithProvider = (provider: TriggerProviderApiEntity): TriggerWithProvider => {
|
||||
return {
|
||||
// Collection fields
|
||||
id: provider.plugin_id || provider.name,
|
||||
name: provider.name,
|
||||
author: provider.author,
|
||||
description: provider.description,
|
||||
icon: provider.icon || '',
|
||||
label: provider.label,
|
||||
type: CollectionType.trigger,
|
||||
team_credentials: {},
|
||||
is_team_authorization: false,
|
||||
allow_delete: false,
|
||||
labels: provider.tags || [],
|
||||
plugin_id: provider.plugin_id,
|
||||
plugin_unique_identifier: provider.plugin_unique_identifier || '',
|
||||
events: provider.events.map(event => ({
|
||||
name: event.name,
|
||||
author: provider.author,
|
||||
label: event.identity.label,
|
||||
description: event.description,
|
||||
parameters: event.parameters.map(param => ({
|
||||
name: param.name,
|
||||
label: param.label,
|
||||
human_description: param.description || param.label,
|
||||
type: param.type,
|
||||
form: param.type,
|
||||
llm_description: JSON.stringify(param.description || {}),
|
||||
required: param.required || false,
|
||||
default: param.default || '',
|
||||
options: param.options?.map(option => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
})) || [],
|
||||
multiple: param.multiple || false,
|
||||
})),
|
||||
labels: provider.tags || [],
|
||||
output_schema: event.output_schema || {},
|
||||
})),
|
||||
|
||||
// Trigger-specific schema fields
|
||||
subscription_constructor: provider.subscription_constructor,
|
||||
subscription_schema: provider.subscription_schema,
|
||||
supported_creation_methods: provider.supported_creation_methods,
|
||||
|
||||
meta: {
|
||||
version: '1.0',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const useAllTriggerPlugins = (enabled = true) => {
|
||||
return useQuery<TriggerWithProvider[]>({
|
||||
queryKey: [NAME_SPACE, 'all'],
|
||||
queryFn: async () => {
|
||||
const response = await get<TriggerProviderApiEntity[]>('/workspaces/current/triggers')
|
||||
return response.map(convertToTriggerWithProvider)
|
||||
},
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useTriggerPluginsByType = (triggerType: string, enabled = true) => {
|
||||
return useQuery<TriggerWithProvider[]>({
|
||||
queryKey: [NAME_SPACE, 'byType', triggerType],
|
||||
queryFn: async () => {
|
||||
const response = await get<TriggerProviderApiEntity[]>(`/workspaces/current/triggers?type=${triggerType}`)
|
||||
return response.map(convertToTriggerWithProvider)
|
||||
},
|
||||
enabled: enabled && !!triggerType,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllTriggerPlugins = () => {
|
||||
return useInvalid([NAME_SPACE, 'all'])
|
||||
}
|
||||
|
||||
// ===== Trigger Subscriptions Management =====
|
||||
|
||||
export const useTriggerProviderInfo = (provider: string, enabled = true) => {
|
||||
return useQuery<TriggerProviderApiEntity>({
|
||||
queryKey: [NAME_SPACE, 'provider-info', provider],
|
||||
queryFn: () => get<TriggerProviderApiEntity>(`/workspaces/current/trigger-provider/${provider}/info`),
|
||||
enabled: enabled && !!provider,
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useTriggerSubscriptions = (provider: string, enabled = true) => {
|
||||
return useQuery<TriggerSubscription[]>({
|
||||
queryKey: [NAME_SPACE, 'list-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
|
||||
credential_type?: string
|
||||
}) => {
|
||||
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
|
||||
properties?: Record<string, any>
|
||||
parameters?: 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
|
||||
credentials?: Record<string, any>
|
||||
}) => {
|
||||
const { provider, subscriptionBuilderId, ...body } = payload
|
||||
return post<{ verified: boolean }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify/${subscriptionBuilderId}`,
|
||||
{ body },
|
||||
{ silent: true },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type BuildTriggerSubscriptionPayload = {
|
||||
provider: string
|
||||
subscriptionBuilderId: string
|
||||
name?: string
|
||||
parameters?: Record<string, any>
|
||||
}
|
||||
|
||||
export const useBuildTriggerSubscription = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'build-subscription'],
|
||||
mutationFn: (payload: BuildTriggerSubscriptionPayload) => {
|
||||
const { provider, subscriptionBuilderId, ...body } = payload
|
||||
return post(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/build/${subscriptionBuilderId}`,
|
||||
{ body },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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<{ logs: TriggerLogEntity[] }>({
|
||||
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 type ConfigureTriggerOAuthPayload = {
|
||||
provider: string
|
||||
client_params?: TriggerOAuthClientParams
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export const useConfigureTriggerOAuth = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'configure-oauth'],
|
||||
mutationFn: (payload: ConfigureTriggerOAuthPayload) => {
|
||||
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: TriggerSubscriptionBuilder }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/oauth/authorize`,
|
||||
{},
|
||||
{ silent: true },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Dynamic Options Support =====
|
||||
export const useTriggerPluginDynamicOptions = (payload: {
|
||||
plugin_id: string
|
||||
provider: string
|
||||
action: string
|
||||
parameter: string
|
||||
credential_id: 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.credential_id, payload.extra],
|
||||
queryFn: () => get<{ options: Array<{ value: string; label: any }> }>(
|
||||
'/workspaces/current/plugin/parameters/dynamic-options',
|
||||
{
|
||||
params: {
|
||||
...payload,
|
||||
provider_type: 'trigger', // Add required provider_type parameter
|
||||
},
|
||||
},
|
||||
{ silent: true },
|
||||
),
|
||||
enabled: enabled && !!payload.plugin_id && !!payload.provider && !!payload.action && !!payload.parameter && !!payload.credential_id,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Cache Invalidation Helpers =====
|
||||
|
||||
export const useInvalidateTriggerOAuthConfig = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (provider: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'oauth-config', provider],
|
||||
})
|
||||
}
|
||||
}
|
||||
152
web/service/workflow-payload.ts
Normal file
152
web/service/workflow-payload.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { produce } from 'immer'
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types'
|
||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
|
||||
export type TriggerPluginNodePayload = {
|
||||
title: string
|
||||
desc: string
|
||||
plugin_id: string
|
||||
provider_id: string
|
||||
event_name: string
|
||||
subscription_id: string
|
||||
plugin_unique_identifier: string
|
||||
event_parameters: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type WorkflowDraftSyncParams = Pick<
|
||||
FetchWorkflowDraftResponse,
|
||||
'graph' | 'features' | 'environment_variables' | 'conversation_variables'
|
||||
>
|
||||
|
||||
const removeTempProperties = (data: Record<string, unknown>): void => {
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (key.startsWith('_'))
|
||||
delete data[key]
|
||||
})
|
||||
}
|
||||
|
||||
type TriggerParameterSchema = Record<string, unknown>
|
||||
|
||||
type TriggerPluginHydratePayload = (PluginTriggerNodeType & {
|
||||
paramSchemas?: TriggerParameterSchema[]
|
||||
parameters_schema?: TriggerParameterSchema[]
|
||||
})
|
||||
|
||||
const sanitizeTriggerPluginNode = (node: Node<TriggerPluginNodePayload>): Node<TriggerPluginNodePayload> => {
|
||||
const data = node.data
|
||||
|
||||
if (!data || data.type !== BlockEnum.TriggerPlugin)
|
||||
return node
|
||||
|
||||
const sanitizedData: TriggerPluginNodePayload & { type: BlockEnum.TriggerPlugin } = {
|
||||
type: BlockEnum.TriggerPlugin,
|
||||
title: data.title ?? '',
|
||||
desc: data.desc ?? '',
|
||||
plugin_id: data.plugin_id ?? '',
|
||||
provider_id: data.provider_id ?? '',
|
||||
event_name: data.event_name ?? '',
|
||||
subscription_id: data.subscription_id ?? '',
|
||||
plugin_unique_identifier: data.plugin_unique_identifier ?? '',
|
||||
event_parameters: (typeof data.event_parameters === 'object' && data.event_parameters !== null)
|
||||
? data.event_parameters as Record<string, unknown>
|
||||
: {},
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: sanitizedData,
|
||||
}
|
||||
}
|
||||
|
||||
export const sanitizeWorkflowDraftPayload = (params: WorkflowDraftSyncParams): WorkflowDraftSyncParams => {
|
||||
const { graph } = params
|
||||
|
||||
if (!graph?.nodes?.length)
|
||||
return params
|
||||
|
||||
const sanitizedNodes = graph.nodes.map(node => sanitizeTriggerPluginNode(node as Node<TriggerPluginNodePayload>))
|
||||
|
||||
return {
|
||||
...params,
|
||||
graph: {
|
||||
...graph,
|
||||
nodes: sanitizedNodes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const isTriggerPluginNode = (node: Node): node is Node<TriggerPluginHydratePayload> => {
|
||||
const data = node.data as unknown
|
||||
|
||||
if (!data || typeof data !== 'object')
|
||||
return false
|
||||
|
||||
const payload = data as Partial<TriggerPluginHydratePayload> & { type?: BlockEnum }
|
||||
|
||||
if (payload.type !== BlockEnum.TriggerPlugin)
|
||||
return false
|
||||
|
||||
return 'event_parameters' in payload
|
||||
}
|
||||
|
||||
const hydrateTriggerPluginNode = (node: Node): Node => {
|
||||
if (!isTriggerPluginNode(node))
|
||||
return node
|
||||
|
||||
const typedNode = node as Node<TriggerPluginHydratePayload>
|
||||
const data = typedNode.data
|
||||
const eventParameters = data.event_parameters ?? {}
|
||||
const parametersSchema = data.parameters_schema ?? data.paramSchemas ?? []
|
||||
const config = data.config ?? eventParameters ?? {}
|
||||
|
||||
const nextData: typeof data = {
|
||||
...data,
|
||||
config,
|
||||
paramSchemas: data.paramSchemas ?? parametersSchema,
|
||||
parameters_schema: parametersSchema,
|
||||
}
|
||||
|
||||
return {
|
||||
...typedNode,
|
||||
data: nextData,
|
||||
}
|
||||
}
|
||||
|
||||
export const hydrateWorkflowDraftResponse = (draft: FetchWorkflowDraftResponse): FetchWorkflowDraftResponse => {
|
||||
return produce(draft, (mutableDraft) => {
|
||||
if (!mutableDraft?.graph)
|
||||
return
|
||||
|
||||
if (mutableDraft.graph.nodes) {
|
||||
mutableDraft.graph.nodes = mutableDraft.graph.nodes
|
||||
.filter((node: Node) => !node.data?._isTempNode)
|
||||
.map((node: Node) => {
|
||||
if (node.data)
|
||||
removeTempProperties(node.data as Record<string, unknown>)
|
||||
|
||||
return hydrateTriggerPluginNode(node)
|
||||
})
|
||||
}
|
||||
|
||||
if (mutableDraft.graph.edges) {
|
||||
mutableDraft.graph.edges = mutableDraft.graph.edges
|
||||
.filter((edge: Edge) => !edge.data?._isTemp)
|
||||
.map((edge: Edge) => {
|
||||
if (edge.data)
|
||||
removeTempProperties(edge.data as Record<string, unknown>)
|
||||
|
||||
return edge
|
||||
})
|
||||
}
|
||||
|
||||
if (mutableDraft.environment_variables) {
|
||||
mutableDraft.environment_variables = mutableDraft.environment_variables.map(env =>
|
||||
env.value_type === 'secret'
|
||||
? { ...env, value: '[__HIDDEN__]' }
|
||||
: env,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user