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:
Yeuoly
2025-11-12 17:59:37 +08:00
committed by GitHub
parent ca7794305b
commit b76e17b25d
785 changed files with 41186 additions and 3725 deletions

View File

@@ -35,7 +35,7 @@ const Empty = ({
const hasTitle = t(`tools.addToolModal.${renderType}.title`) !== `tools.addToolModal.${renderType}.title`
return (
<div className='flex h-[336px] flex-col items-center justify-center'>
<div className='flex flex-col items-center justify-center'>
<NoToolPlaceholder className={theme === 'dark' ? 'invert' : ''} />
<div className='mb-1 mt-2 text-[13px] font-medium leading-[18px] text-text-primary'>
{hasTitle ? t(`tools.addToolModal.${renderType}.title`) : 'No tools available'}

View File

@@ -9,7 +9,7 @@ import {
useMarketplaceCollectionsAndPlugins,
useMarketplacePlugins,
} from '@/app/components/plugins/marketplace/hooks'
import { PluginType } from '@/app/components/plugins/types'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils'
import { useAllToolProviders } from '@/service/use-tools'
@@ -49,7 +49,7 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
if (searchPluginText) {
queryPluginsWithDebounced({
category: PluginType.tool,
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
@@ -59,7 +59,7 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
return
}
queryPlugins({
category: PluginType.tool,
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
@@ -70,8 +70,8 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
else {
if (isSuccess) {
queryMarketplaceCollectionsAndPlugins({
category: PluginType.tool,
condition: getMarketplaceListCondition(PluginType.tool),
category: PluginCategoryEnum.tool,
condition: getMarketplaceListCondition(PluginCategoryEnum.tool),
exclude,
type: 'plugin',
})
@@ -95,7 +95,7 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
pageRef.current++
queryPlugins({
category: PluginType.tool,
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,

View File

@@ -13,7 +13,7 @@ import CopyFeedback from '@/app/components/base/copy-feedback'
import Confirm from '@/app/components/base/confirm'
import type { AppDetailResponse } from '@/models/app'
import { useAppContext } from '@/context/app-context'
import type { AppSSO } from '@/types/app'
import { AppModeEnum, type AppSSO } from '@/types/app'
import Indicator from '@/app/components/header/indicator'
import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal'
import { useAppWorkflow } from '@/service/use-workflow'
@@ -26,6 +26,7 @@ import {
import { BlockEnum } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
import { fetchAppDetail } from '@/service/apps'
import { useDocLink } from '@/context/i18n'
export type IAppCardProps = {
appInfo: AppDetailResponse & Partial<AppSSO>
@@ -35,6 +36,7 @@ function MCPServiceCard({
appInfo,
}: IAppCardProps) {
const { t } = useTranslation()
const docLink = useDocLink()
const appId = appInfo.id
const { mutateAsync: updateMCPServer } = useUpdateMCPServer()
const { mutateAsync: refreshMCPServerCode, isPending: genLoading } = useRefreshMCPServerCode()
@@ -43,7 +45,7 @@ function MCPServiceCard({
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showMCPServerModal, setShowMCPServerModal] = useState(false)
const isAdvancedApp = appInfo?.mode === 'advanced-chat' || appInfo?.mode === 'workflow'
const isAdvancedApp = appInfo?.mode === AppModeEnum.ADVANCED_CHAT || appInfo?.mode === AppModeEnum.WORKFLOW
const isBasicApp = !isAdvancedApp
const { data: currentWorkflow } = useAppWorkflow(isAdvancedApp ? appId : '')
const [basicAppConfig, setBasicAppConfig] = useState<any>({})
@@ -69,11 +71,16 @@ function MCPServiceCard({
const { data: detail } = useMCPServerDetail(appId)
const { id, status, server_code } = detail ?? {}
const isWorkflowApp = appInfo.mode === AppModeEnum.WORKFLOW
const appUnpublished = isAdvancedApp ? !currentWorkflow?.graph : !basicAppConfig.updated_at
const serverPublished = !!id
const serverActivated = status === 'active'
const serverURL = serverPublished ? `${appInfo.api_base_url.replace('/v1', '')}/mcp/server/${server_code}/mcp` : '***********'
const toggleDisabled = !isCurrentWorkspaceEditor || appUnpublished
const hasStartNode = currentWorkflow?.graph?.nodes?.some(node => node.data.type === BlockEnum.Start)
const missingStartNode = isWorkflowApp && !hasStartNode
const hasInsufficientPermissions = !isCurrentWorkspaceEditor
const toggleDisabled = hasInsufficientPermissions || appUnpublished || missingStartNode
const isMinimalState = appUnpublished || missingStartNode
const [activated, setActivated] = useState(serverActivated)
@@ -136,12 +143,12 @@ function MCPServiceCard({
return (
<>
<div className={cn('w-full max-w-full rounded-xl border-l-[0.5px] border-t border-effects-highlight')}>
<div className={cn('w-full max-w-full rounded-xl border-l-[0.5px] border-t border-effects-highlight', isMinimalState && 'h-12')}>
<div className='rounded-xl bg-background-default'>
<div className='flex w-full flex-col items-start justify-center gap-3 self-stretch border-b-[0.5px] border-divider-subtle p-3'>
<div className={cn('flex w-full flex-col items-start justify-center gap-3 self-stretch p-3', isMinimalState ? 'border-0' : 'border-b-[0.5px] border-divider-subtle')}>
<div className='flex w-full items-center gap-3 self-stretch'>
<div className='flex grow items-center'>
<div className='mr-3 shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-indigo-indigo-500 p-1 shadow-md'>
<div className='mr-2 shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-brand-blue-brand-500 p-1 shadow-md'>
<Mcp className='h-4 w-4 text-text-primary-on-surface' />
</div>
<div className="group w-full">
@@ -159,61 +166,86 @@ function MCPServiceCard({
</div>
</div>
<Tooltip
popupContent={appUnpublished ? t('tools.mcp.server.publishTip') : ''}
popupContent={
toggleDisabled ? (
appUnpublished ? (
t('tools.mcp.server.publishTip')
) : missingStartNode ? (
<>
<div className="mb-1 text-xs font-normal text-text-secondary">
{t('appOverview.overview.appInfo.enableTooltip.description')}
</div>
<div
className="cursor-pointer text-xs font-normal text-text-accent hover:underline"
onClick={() => window.open(docLink('/guides/workflow/node/user-input'), '_blank')}
>
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
</div>
</>
) : ''
) : ''
}
position="right"
popupClassName="w-58 max-w-60 rounded-xl bg-components-panel-bg px-3.5 py-3 shadow-lg"
offset={24}
>
<div>
<Switch defaultValue={activated} onChange={onChangeStatus} disabled={toggleDisabled} />
</div>
</Tooltip>
</div>
<div className='flex flex-col items-start justify-center self-stretch'>
<div className="system-xs-medium pb-1 text-text-tertiary">
{t('tools.mcp.server.url')}
</div>
<div className="inline-flex h-9 w-full items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 pl-2">
<div className="flex h-4 min-w-0 flex-1 items-start justify-start gap-2 px-1">
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-medium text-text-secondary">
{serverURL}
</div>
{!isMinimalState && (
<div className='flex flex-col items-start justify-center self-stretch'>
<div className="system-xs-medium pb-1 text-text-tertiary">
{t('tools.mcp.server.url')}
</div>
{serverPublished && (
<>
<CopyFeedback
content={serverURL}
className={'!size-6'}
/>
<Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />
{isCurrentWorkspaceManager && (
<Tooltip
popupContent={t('appOverview.overview.appInfo.regenerate') || ''}
>
<div
className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover"
onClick={() => setShowConfirmDelete(true)}
<div className="inline-flex h-9 w-full items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 pl-2">
<div className="flex h-4 min-w-0 flex-1 items-start justify-start gap-2 px-1">
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-medium text-text-secondary">
{serverURL}
</div>
</div>
{serverPublished && (
<>
<CopyFeedback
content={serverURL}
className={'!size-6'}
/>
<Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />
{isCurrentWorkspaceManager && (
<Tooltip
popupContent={t('appOverview.overview.appInfo.regenerate') || ''}
>
<RiLoopLeftLine className={cn('h-4 w-4 text-text-tertiary hover:text-text-secondary', genLoading && 'animate-spin')}/>
</div>
</Tooltip>
)}
</>
)}
<div
className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover"
onClick={() => setShowConfirmDelete(true)}
>
<RiLoopLeftLine className={cn('h-4 w-4 text-text-tertiary hover:text-text-secondary', genLoading && 'animate-spin')}/>
</div>
</Tooltip>
)}
</>
)}
</div>
</div>
</div>
)}
</div>
<div className='flex items-center gap-1 self-stretch p-3'>
<Button
disabled={toggleDisabled}
size='small'
variant='ghost'
onClick={() => setShowMCPServerModal(true)}
>
{!isMinimalState && (
<div className='flex items-center gap-1 self-stretch p-3'>
<Button
disabled={toggleDisabled}
size='small'
variant='ghost'
onClick={() => setShowMCPServerModal(true)}
>
<div className="flex items-center justify-center gap-[1px]">
<RiEditLine className="h-3.5 w-3.5" />
<div className="system-xs-medium px-[3px] text-text-tertiary">{serverPublished ? t('tools.mcp.server.edit') : t('tools.mcp.server.addDescription')}</div>
</div>
</Button>
</div>
<div className="flex items-center justify-center gap-[1px]">
<RiEditLine className="h-3.5 w-3.5" />
<div className="system-xs-medium px-[3px] text-text-tertiary">{serverPublished ? t('tools.mcp.server.edit') : t('tools.mcp.server.addDescription')}</div>
</div>
</Button>
</div>
)}
</div>
</div>
{showMCPServerModal && (

View File

@@ -34,6 +34,7 @@ export enum CollectionType {
workflow = 'workflow',
mcp = 'mcp',
datasource = 'datasource',
trigger = 'trigger',
}
export type Emoji = {
@@ -65,6 +66,7 @@ export type Collection = {
masked_headers?: Record<string, string>
is_authorized?: boolean
provider?: string
credential_id?: string
is_dynamic_registration?: boolean
authentication?: {
client_id?: string
@@ -84,6 +86,7 @@ export type ToolParameter = {
form: string
llm_description: string
required: boolean
multiple: boolean
default: string
options?: {
label: TypeWithI18N
@@ -93,7 +96,33 @@ export type ToolParameter = {
max?: number
}
export type TriggerParameter = {
name: string
label: TypeWithI18N
human_description: TypeWithI18N
type: string
form: string
llm_description: string
required: boolean
multiple: boolean
default: string
options?: {
label: TypeWithI18N
value: string
}[]
}
// Action
export type Event = {
name: string
author: string
label: TypeWithI18N
description: TypeWithI18N
parameters: TriggerParameter[]
labels: string[]
output_schema: Record<string, any>
}
export type Tool = {
name: string
author: string

View File

@@ -1,6 +1,7 @@
import type { ToolCredential, ToolParameter } from '../types'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import type { TriggerEventParameter } from '../../plugins/types'
import type { ToolCredential, ToolParameter } from '../types'
export const toType = (type: string) => {
switch (type) {
@@ -14,6 +15,21 @@ export const toType = (type: string) => {
return type
}
}
export const triggerEventParametersToFormSchemas = (parameters: TriggerEventParameter[]) => {
if (!parameters?.length)
return []
return parameters.map((parameter) => {
return {
...parameter,
type: toType(parameter.type),
_type: parameter.type,
tooltip: parameter.description,
}
})
}
export const toolParametersToFormSchemas = (parameters: ToolParameter[]) => {
if (!parameters)
return []
@@ -165,7 +181,7 @@ export const getConfiguredValue = (value: Record<string, any>, formSchemas: { va
const getVarKindType = (type: FormTypeEnum) => {
if (type === FormTypeEnum.file || type === FormTypeEnum.files)
return VarKindType.variable
if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber)
if (type === FormTypeEnum.select || type === FormTypeEnum.checkbox || type === FormTypeEnum.textNumber)
return VarKindType.constant
if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput)
return VarKindType.mixed

View File

@@ -28,6 +28,7 @@ type Props = {
inputs?: InputVar[]
handlePublish: (params?: PublishWorkflowParams) => Promise<void>
onRefreshData?: () => void
disabledReason?: string
}
const WorkflowToolConfigureButton = ({
@@ -41,6 +42,7 @@ const WorkflowToolConfigureButton = ({
inputs,
handlePublish,
onRefreshData,
disabledReason,
}: Props) => {
const { t } = useTranslation()
const router = useRouter()
@@ -200,7 +202,8 @@ const WorkflowToolConfigureButton = ({
{t('workflow.common.configureRequired')}
</span>
)}
</div>)
</div>
)
: (
<div
className='flex items-center justify-start gap-2 p-2 pl-2.5'
@@ -214,6 +217,11 @@ const WorkflowToolConfigureButton = ({
</div>
</div>
)}
{disabledReason && (
<div className='mt-1 px-2.5 pb-2 text-xs leading-[18px] text-text-tertiary'>
{disabledReason}
</div>
)}
{published && (
<div className='border-t-[0.5px] border-divider-regular px-2.5 py-2'>
<div className='flex justify-between gap-x-2'>
@@ -221,7 +229,7 @@ const WorkflowToolConfigureButton = ({
size='small'
className='w-[140px]'
onClick={() => setShowModal(true)}
disabled={!isCurrentWorkspaceManager}
disabled={!isCurrentWorkspaceManager || disabled}
>
{t('workflow.common.configure')}
{outdated && <Indicator className='ml-1' color={'yellow'} />}
@@ -230,14 +238,17 @@ const WorkflowToolConfigureButton = ({
size='small'
className='w-[140px]'
onClick={() => router.push('/tools?category=workflow')}
disabled={disabled}
>
{t('workflow.common.manageInTools')}
<RiArrowRightUpLine className='ml-1 h-4 w-4' />
</Button>
</div>
{outdated && <div className='mt-1 text-xs leading-[18px] text-text-warning'>
{t('workflow.common.workflowAsToolTip')}
</div>}
{outdated && (
<div className='mt-1 text-xs leading-[18px] text-text-warning'>
{t('workflow.common.workflowAsToolTip')}
</div>
)}
</div>
)}
</div>