dify/web/app/components/workflow/nodes/start-placeholder/panel.tsx

168 lines
5.3 KiB
TypeScript

import type { FC } from 'react'
import type { StartPlaceholderNodeType } from './types'
import type {
PluginDefaultValue,
TriggerDefaultValue,
} from '@/app/components/workflow/block-selector/types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import * as React from 'react'
import {
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useStoreApi } from 'reactflow'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import AllStartBlocks from '@/app/components/workflow/block-selector/all-start-blocks'
import { useAutoGenerateWebhookUrl } from '@/app/components/workflow/hooks'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
const getTriggerPluginNodeData = (
triggerConfig: TriggerDefaultValue,
fallbackTitle?: string,
fallbackDesc?: string,
) => {
return {
plugin_id: triggerConfig.plugin_id,
provider_id: triggerConfig.provider_name,
provider_type: triggerConfig.provider_type,
provider_name: triggerConfig.provider_name,
event_name: triggerConfig.event_name,
event_label: triggerConfig.event_label,
event_description: triggerConfig.event_description,
title: triggerConfig.event_label || triggerConfig.title || fallbackTitle,
desc: triggerConfig.event_description || fallbackDesc,
output_schema: { ...triggerConfig.output_schema },
parameters_schema: triggerConfig.paramSchemas ? [...triggerConfig.paramSchemas] : [],
config: { ...triggerConfig.params },
subscription_id: triggerConfig.subscription_id,
plugin_unique_identifier: triggerConfig.plugin_unique_identifier,
is_team_authorization: triggerConfig.is_team_authorization,
meta: triggerConfig.meta ? { ...triggerConfig.meta } : undefined,
}
}
const Panel: FC<NodePanelProps<StartPlaceholderNodeType>> = ({
id,
}) => {
const { t } = useTranslation()
const [searchText, setSearchText] = useState('')
const [tags, setTags] = useState<string[]>([])
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)
const setHasSelectedStartNode = useWorkflowStore(s => s.setHasSelectedStartNode)
const setShouldAutoOpenStartNodeSelector = useWorkflowStore(s => s.setShouldAutoOpenStartNodeSelector)
const reactFlowStore = useStoreApi()
const autoGenerateWebhookUrl = useAutoGenerateWebhookUrl()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const handleSelectStartNode = useCallback((nodeType: BlockEnum, toolConfig?: PluginDefaultValue) => {
const nodeDefault = availableNodesMetaData?.nodesMap?.[nodeType]
if (!nodeDefault?.defaultValue)
return
const baseNodeData = { ...nodeDefault.defaultValue }
const mergedNodeData = (() => {
if (nodeType !== BlockEnum.TriggerPlugin || !toolConfig) {
return {
...baseNodeData,
...toolConfig,
}
}
const triggerNodeData = getTriggerPluginNodeData(
toolConfig as TriggerDefaultValue,
baseNodeData.title,
baseNodeData.desc,
)
return {
...baseNodeData,
...triggerNodeData,
config: {
...(baseNodeData as { config?: Record<string, unknown> }).config,
...triggerNodeData.config,
},
}
})()
const { getNodes, setNodes } = reactFlowStore.getState()
const nextNodes = getNodes().map((node) => {
if (node.id !== id) {
return {
...node,
data: {
...node.data,
selected: false,
},
}
}
return {
...node,
data: {
...mergedNodeData,
type: nodeType,
selected: true,
},
}
})
setNodes(nextNodes)
setHasSelectedStartNode?.(true)
setShouldAutoOpenStartNodeSelector?.(true)
handleSyncWorkflowDraft(true, false, {
onSuccess: () => {
autoGenerateWebhookUrl(id)
},
onError: () => {
console.error('Failed to save start node selection to draft')
},
})
}, [
autoGenerateWebhookUrl,
availableNodesMetaData?.nodesMap,
handleSyncWorkflowDraft,
id,
reactFlowStore,
setHasSelectedStartNode,
setShouldAutoOpenStartNodeSelector,
])
return (
<div className="flex h-full min-h-0 flex-col">
<div className="px-3 py-2">
<SearchBox
search={searchText}
onSearchChange={setSearchText}
tags={tags}
onTagsChange={setTags}
placeholder={t('tabs.searchTrigger', { ns: 'workflow' })}
inputClassName="grow"
/>
</div>
<div className="min-h-0 flex-1 overflow-hidden">
<AllStartBlocks
className="max-w-none min-w-0"
searchText={searchText}
onSelect={handleSelectStartNode}
availableBlocksTypes={[
BlockEnum.Start,
BlockEnum.TriggerSchedule,
BlockEnum.TriggerWebhook,
BlockEnum.TriggerPlugin,
]}
tags={tags}
allowUserInputSelection
variant="panel"
/>
</div>
</div>
)
}
export default React.memo(Panel)