import { memo, useCallback, } from 'react' import produce from 'immer' import { useReactFlow, useStoreApi, useViewport, } from 'reactflow' import { useEventListener } from 'ahooks' import { useStore, useWorkflowStore, } from './store' import { WorkflowHistoryEvent, useNodesInteractions, useNodesSyncDraft, useWorkflowHistory } from './hooks' import { CUSTOM_NODE } from './constants' import { getIterationStartNode, getLoopStartNode } from './utils' import CustomNode from './nodes' import CustomNoteNode from './note-node' import { CUSTOM_NOTE_NODE } from './note-node/constants' import { BlockEnum } from './types' import { useStore as useAppStore } from '@/app/components/app/store' import { fetchWebhookUrl } from '@/service/apps' const CandidateNode = () => { const store = useStoreApi() const reactflow = useReactFlow() const workflowStore = useWorkflowStore() const candidateNode = useStore(s => s.candidateNode) const mousePosition = useStore(s => s.mousePosition) const { zoom } = useViewport() const { handleNodeSelect } = useNodesInteractions() const { saveStateToHistory } = useWorkflowHistory() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const autoGenerateWebhookUrl = useCallback((nodeId: string) => { const appId = useAppStore.getState().appDetail?.id if (!appId) return fetchWebhookUrl({ appId, nodeId }).then((response) => { const { getNodes, setNodes } = store.getState() let hasUpdated = false const updatedNodes = produce(getNodes(), (draft) => { const targetNode = draft.find(n => n.id === nodeId) if (!targetNode || targetNode.data.type !== BlockEnum.TriggerWebhook) return targetNode.data = { ...targetNode.data, webhook_url: response.webhook_url, webhook_debug_url: response.webhook_debug_url, } hasUpdated = true }) if (hasUpdated) setNodes(updatedNodes) }) .catch((error: unknown) => { console.error('Failed to auto-generate webhook URL from candidate placement:', error) }) }, [store]) useEventListener('click', (e) => { const { candidateNode, mousePosition } = workflowStore.getState() if (candidateNode) { e.preventDefault() const { getNodes, setNodes, } = store.getState() const { screenToFlowPosition } = reactflow const nodes = getNodes() const { x, y } = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY }) const newNodes = produce(nodes, (draft) => { draft.push({ ...candidateNode, data: { ...candidateNode.data, _isCandidate: false, }, position: { x, y, }, }) if (candidateNode.data.type === BlockEnum.Iteration) draft.push(getIterationStartNode(candidateNode.id)) if (candidateNode.data.type === BlockEnum.Loop) draft.push(getLoopStartNode(candidateNode.id)) }) setNodes(newNodes) if (candidateNode.type === CUSTOM_NOTE_NODE) saveStateToHistory(WorkflowHistoryEvent.NoteAdd, { nodeId: candidateNode.id }) else saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: candidateNode.id }) workflowStore.setState({ candidateNode: undefined }) if (candidateNode.type === CUSTOM_NOTE_NODE) handleNodeSelect(candidateNode.id) if (candidateNode.data.type === BlockEnum.TriggerWebhook) { handleSyncWorkflowDraft(true, true, { onSuccess: () => autoGenerateWebhookUrl(candidateNode.id), }) } } }) useEventListener('contextmenu', (e) => { const { candidateNode } = workflowStore.getState() if (candidateNode) { e.preventDefault() workflowStore.setState({ candidateNode: undefined }) } }) if (!candidateNode) return null return (
{ candidateNode.type === CUSTOM_NODE && ( ) } { candidateNode.type === CUSTOM_NOTE_NODE && ( ) }
) } export default memo(CandidateNode)