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:
@@ -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'}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user