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

@@ -0,0 +1,99 @@
'use client'
import type { FC } from 'react'
import {
useCallback,
useEffect,
} from 'react'
import { useTranslation } from 'react-i18next'
import { BlockEnum } from '@/app/components/workflow/types'
import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types'
import Modal from '@/app/components/base/modal'
import StartNodeSelectionPanel from './start-node-selection-panel'
import { useDocLink } from '@/context/i18n'
type WorkflowOnboardingModalProps = {
isShow: boolean
onClose: () => void
onSelectStartNode: (nodeType: BlockEnum, toolConfig?: PluginDefaultValue) => void
}
const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
isShow,
onClose,
onSelectStartNode,
}) => {
const { t } = useTranslation()
const docLink = useDocLink()
const handleSelectUserInput = useCallback(() => {
onSelectStartNode(BlockEnum.Start)
onClose() // Close modal after selection
}, [onSelectStartNode, onClose])
const handleTriggerSelect = useCallback((nodeType: BlockEnum, toolConfig?: PluginDefaultValue) => {
onSelectStartNode(nodeType, toolConfig)
onClose() // Close modal after selection
}, [onSelectStartNode, onClose])
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isShow)
onClose()
}
document.addEventListener('keydown', handleEsc)
return () => document.removeEventListener('keydown', handleEsc)
}, [isShow, onClose])
return (
<>
<Modal
isShow={isShow}
onClose={onClose}
className="w-[618px] max-w-[618px] rounded-2xl border border-effects-highlight bg-background-default-subtle shadow-lg"
overlayOpacity
closable
clickOutsideNotClose
>
<div className="pb-4">
{/* Header */}
<div className="mb-6">
<h3 className="title-2xl-semi-bold mb-2 text-text-primary">
{t('workflow.onboarding.title')}
</h3>
<div className="body-xs-regular leading-4 text-text-tertiary">
{t('workflow.onboarding.description')}{' '}
<a
href={docLink('/guides/workflow/node/start')}
target="_blank"
rel="noopener noreferrer"
className="hover:text-text-accent-hover cursor-pointer text-text-accent underline"
>
{t('workflow.onboarding.learnMore')}
</a>{' '}
{t('workflow.onboarding.aboutStartNode')}
</div>
</div>
{/* Content */}
<StartNodeSelectionPanel
onSelectUserInput={handleSelectUserInput}
onSelectTrigger={handleTriggerSelect}
/>
</div>
</Modal>
{/* ESC tip below modal */}
{isShow && (
<div className="body-xs-regular pointer-events-none fixed left-1/2 top-1/2 z-[70] flex -translate-x-1/2 translate-y-[165px] items-center gap-1 text-text-quaternary">
<span>{t('workflow.onboarding.escTip.press')}</span>
<kbd className="system-kbd inline-flex h-4 min-w-4 items-center justify-center rounded bg-components-kbd-bg-gray px-1 text-text-tertiary">
{t('workflow.onboarding.escTip.key')}
</kbd>
<span>{t('workflow.onboarding.escTip.toDismiss')}</span>
</div>
)}
</>
)
}
export default WorkflowOnboardingModal

View File

@@ -0,0 +1,53 @@
'use client'
import type { FC, ReactNode } from 'react'
import cn from '@/utils/classnames'
type StartNodeOptionProps = {
icon: ReactNode
title: string
subtitle?: string
description: string
onClick: () => void
}
const StartNodeOption: FC<StartNodeOptionProps> = ({
icon,
title,
subtitle,
description,
onClick,
}) => {
return (
<div
onClick={onClick}
className={cn(
'hover:border-components-panel-border-active flex h-40 w-[280px] cursor-pointer flex-col gap-2 rounded-xl border-[0.5px] border-components-option-card-option-border bg-components-panel-on-panel-item-bg p-4 shadow-sm transition-all hover:shadow-md',
)}
>
{/* Icon */}
<div className="shrink-0">
{icon}
</div>
{/* Text content */}
<div className="flex h-[74px] flex-col gap-1 py-0.5">
<div className="h-5 leading-5">
<h3 className="system-md-semi-bold text-text-primary">
{title}
{subtitle && (
<span className="system-md-regular text-text-quaternary"> {subtitle}</span>
)}
</h3>
</div>
<div className="h-12 leading-4">
<p className="system-xs-regular text-text-tertiary">
{description}
</p>
</div>
</div>
</div>
)
}
export default StartNodeOption

View File

@@ -0,0 +1,80 @@
'use client'
import type { FC } from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import StartNodeOption from './start-node-option'
import NodeSelector from '@/app/components/workflow/block-selector'
import { Home } from '@/app/components/base/icons/src/vender/workflow'
import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow'
import { BlockEnum } from '@/app/components/workflow/types'
import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types'
import { TabsEnum } from '@/app/components/workflow/block-selector/types'
type StartNodeSelectionPanelProps = {
onSelectUserInput: () => void
onSelectTrigger: (nodeType: BlockEnum, toolConfig?: PluginDefaultValue) => void
}
const StartNodeSelectionPanel: FC<StartNodeSelectionPanelProps> = ({
onSelectUserInput,
onSelectTrigger,
}) => {
const { t } = useTranslation()
const [showTriggerSelector, setShowTriggerSelector] = useState(false)
const handleTriggerClick = useCallback(() => {
setShowTriggerSelector(true)
}, [])
const handleTriggerSelect = useCallback((nodeType: BlockEnum, toolConfig?: PluginDefaultValue) => {
setShowTriggerSelector(false)
onSelectTrigger(nodeType, toolConfig)
}, [onSelectTrigger])
return (
<div className="grid grid-cols-2 gap-4">
<StartNodeOption
icon={
<div className="flex h-9 w-9 items-center justify-center rounded-[10px] border-[0.5px] border-transparent bg-util-colors-blue-brand-blue-brand-500 p-2">
<Home className="h-5 w-5 text-white" />
</div>
}
title={t('workflow.onboarding.userInputFull')}
description={t('workflow.onboarding.userInputDescription')}
onClick={onSelectUserInput}
/>
<NodeSelector
open={showTriggerSelector}
onOpenChange={setShowTriggerSelector}
onSelect={handleTriggerSelect}
placement="right"
offset={-200}
noBlocks={true}
showStartTab={true}
defaultActiveTab={TabsEnum.Start}
forceShowStartContent={true}
availableBlocksTypes={[
BlockEnum.TriggerSchedule,
BlockEnum.TriggerWebhook,
BlockEnum.TriggerPlugin,
]}
trigger={() => (
<StartNodeOption
icon={
<div className="flex h-9 w-9 items-center justify-center rounded-[10px] border-[0.5px] border-transparent bg-util-colors-blue-brand-blue-brand-500 p-2">
<TriggerAll className="h-5 w-5 text-white" />
</div>
}
title={t('workflow.onboarding.trigger')}
description={t('workflow.onboarding.triggerDescription')}
onClick={handleTriggerClick}
/>
)}
popupClassName="z-[1200]"
/>
</div>
)
}
export default StartNodeSelectionPanel