mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
available nodes
This commit is contained in:
@ -19,10 +19,12 @@ import Tooltip from '@/app/components/base/tooltip'
|
|||||||
type BlocksProps = {
|
type BlocksProps = {
|
||||||
searchText: string
|
searchText: string
|
||||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||||
|
availableBlocksTypes?: BlockEnum[]
|
||||||
}
|
}
|
||||||
const Blocks = ({
|
const Blocks = ({
|
||||||
searchText,
|
searchText,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
availableBlocksTypes = [],
|
||||||
}: BlocksProps) => {
|
}: BlocksProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
@ -35,7 +37,7 @@ const Blocks = ({
|
|||||||
if (block.type === BlockEnum.Answer && !isChatMode)
|
if (block.type === BlockEnum.Answer && !isChatMode)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return block.title.toLowerCase().includes(searchText.toLowerCase())
|
return block.title.toLowerCase().includes(searchText.toLowerCase()) && availableBlocksTypes.includes(block.type)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -43,7 +45,7 @@ const Blocks = ({
|
|||||||
[classification]: list,
|
[classification]: list,
|
||||||
}
|
}
|
||||||
}, {} as Record<string, typeof blocks>)
|
}, {} as Record<string, typeof blocks>)
|
||||||
}, [blocks, isChatMode, searchText])
|
}, [blocks, isChatMode, searchText, availableBlocksTypes])
|
||||||
const isEmpty = Object.values(groups).every(list => !list.length)
|
const isEmpty = Object.values(groups).every(list => !list.length)
|
||||||
|
|
||||||
const renderGroup = useCallback((classification: string) => {
|
const renderGroup = useCallback((classification: string) => {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import type {
|
|||||||
OffsetOptions,
|
OffsetOptions,
|
||||||
Placement,
|
Placement,
|
||||||
} from '@floating-ui/react'
|
} from '@floating-ui/react'
|
||||||
|
import type { BlockEnum, OnSelectBlock } from '../types'
|
||||||
import Tabs from './tabs'
|
import Tabs from './tabs'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
@ -22,7 +23,6 @@ import {
|
|||||||
Plus02,
|
Plus02,
|
||||||
SearchLg,
|
SearchLg,
|
||||||
} from '@/app/components/base/icons/src/vender/line/general'
|
} from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
|
||||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||||
|
|
||||||
type NodeSelectorProps = {
|
type NodeSelectorProps = {
|
||||||
@ -36,6 +36,7 @@ type NodeSelectorProps = {
|
|||||||
triggerClassName?: (open: boolean) => string
|
triggerClassName?: (open: boolean) => string
|
||||||
popupClassName?: string
|
popupClassName?: string
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
|
availableBlocksTypes?: BlockEnum[]
|
||||||
}
|
}
|
||||||
const NodeSelector: FC<NodeSelectorProps> = ({
|
const NodeSelector: FC<NodeSelectorProps> = ({
|
||||||
open: openFromProps,
|
open: openFromProps,
|
||||||
@ -48,6 +49,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||||||
triggerStyle,
|
triggerStyle,
|
||||||
popupClassName,
|
popupClassName,
|
||||||
asChild,
|
asChild,
|
||||||
|
availableBlocksTypes,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
@ -125,6 +127,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||||||
<Tabs
|
<Tabs
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
|
availableBlocksTypes={availableBlocksTypes}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
|
|||||||
@ -13,10 +13,12 @@ import Blocks from './blocks'
|
|||||||
export type TabsProps = {
|
export type TabsProps = {
|
||||||
searchText: string
|
searchText: string
|
||||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||||
|
availableBlocksTypes?: BlockEnum[]
|
||||||
}
|
}
|
||||||
const Tabs: FC<TabsProps> = ({
|
const Tabs: FC<TabsProps> = ({
|
||||||
searchText,
|
searchText,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
availableBlocksTypes,
|
||||||
}) => {
|
}) => {
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const [activeTab, setActiveTab] = useState(tabs[0].key)
|
const [activeTab, setActiveTab] = useState(tabs[0].key)
|
||||||
@ -46,6 +48,7 @@ const Tabs: FC<TabsProps> = ({
|
|||||||
<Blocks
|
<Blocks
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
availableBlocksTypes={availableBlocksTypes}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,59 +13,128 @@ import ToolDefault from './nodes/tool/default'
|
|||||||
import VariableAssignerDefault from './nodes/variable-assigner/default'
|
import VariableAssignerDefault from './nodes/variable-assigner/default'
|
||||||
import EndNodeDefault from './nodes/end/default'
|
import EndNodeDefault from './nodes/end/default'
|
||||||
|
|
||||||
export const NODES_EXTRA_DATA = {
|
type NodesExtraData = {
|
||||||
|
author: string
|
||||||
|
about: string
|
||||||
|
availablePrevNodes: BlockEnum[]
|
||||||
|
availableNextNodes: BlockEnum[]
|
||||||
|
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
|
||||||
|
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
|
||||||
|
checkValid: any
|
||||||
|
}
|
||||||
|
export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
||||||
[BlockEnum.Start]: {
|
[BlockEnum.Start]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: StartNodeDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: StartNodeDefault.getAvailableNextNodes,
|
||||||
|
checkValid: StartNodeDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.End]: {
|
[BlockEnum.End]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: EndNodeDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: EndNodeDefault.getAvailableNextNodes,
|
||||||
|
checkValid: EndNodeDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.Answer]: {
|
[BlockEnum.Answer]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: AnswerDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: AnswerDefault.getAvailableNextNodes,
|
||||||
|
checkValid: AnswerDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.LLM]: {
|
[BlockEnum.LLM]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: LLMDefault.getAvailableNextNodes,
|
||||||
|
checkValid: LLMDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.KnowledgeRetrieval]: {
|
[BlockEnum.KnowledgeRetrieval]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: KnowledgeRetrievalDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: KnowledgeRetrievalDefault.getAvailableNextNodes,
|
||||||
|
checkValid: KnowledgeRetrievalDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.IfElse]: {
|
[BlockEnum.IfElse]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: IfElseDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: IfElseDefault.getAvailableNextNodes,
|
||||||
|
checkValid: IfElseDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.Code]: {
|
[BlockEnum.Code]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: CodeDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: CodeDefault.getAvailableNextNodes,
|
||||||
|
checkValid: CodeDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.TemplateTransform]: {
|
[BlockEnum.TemplateTransform]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: TemplateTransformDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: TemplateTransformDefault.getAvailableNextNodes,
|
||||||
|
checkValid: TemplateTransformDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.QuestionClassifier]: {
|
[BlockEnum.QuestionClassifier]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: QuestionClassifierDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: QuestionClassifierDefault.getAvailableNextNodes,
|
||||||
|
checkValid: QuestionClassifierDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.HttpRequest]: {
|
[BlockEnum.HttpRequest]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: HttpRequestDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: HttpRequestDefault.getAvailableNextNodes,
|
||||||
|
checkValid: HttpRequestDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.VariableAssigner]: {
|
[BlockEnum.VariableAssigner]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: VariableAssignerDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes,
|
||||||
|
checkValid: VariableAssignerDefault.checkValid,
|
||||||
},
|
},
|
||||||
[BlockEnum.Tool]: {
|
[BlockEnum.Tool]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
about: '',
|
about: '',
|
||||||
|
availablePrevNodes: [],
|
||||||
|
availableNextNodes: [],
|
||||||
|
getAvailablePrevNodes: ToolDefault.getAvailablePrevNodes,
|
||||||
|
getAvailableNextNodes: ToolDefault.getAvailableNextNodes,
|
||||||
|
checkValid: ToolDefault.checkValid,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALL_CHAT_AVAILABLE_BLOCKS = Object.keys(NODES_EXTRA_DATA).filter(key => key !== BlockEnum.End) as BlockEnum[]
|
export const ALL_CHAT_AVAILABLE_BLOCKS = Object.keys(NODES_EXTRA_DATA).filter(key => key !== BlockEnum.End && key !== BlockEnum.Start) as BlockEnum[]
|
||||||
export const ALL_COMPLETION_AVAILABLE_BLOCKS = Object.keys(NODES_EXTRA_DATA).filter(key => key !== BlockEnum.Answer) as BlockEnum[]
|
export const ALL_COMPLETION_AVAILABLE_BLOCKS = Object.keys(NODES_EXTRA_DATA).filter(key => key !== BlockEnum.Answer && key !== BlockEnum.Start) as BlockEnum[]
|
||||||
|
|
||||||
export const NODES_INITIAL_DATA = {
|
export const NODES_INITIAL_DATA = {
|
||||||
[BlockEnum.Start]: {
|
[BlockEnum.Start]: {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
NODES_INITIAL_DATA,
|
NODES_INITIAL_DATA,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
|
import { useIsChatMode } from './use-workflow'
|
||||||
|
|
||||||
export const useNodesInitialData = () => {
|
export const useNodesInitialData = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -28,10 +29,13 @@ export const useNodesInitialData = () => {
|
|||||||
|
|
||||||
export const useNodesExtraData = () => {
|
export const useNodesExtraData = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const isChatMode = useIsChatMode()
|
||||||
|
|
||||||
return useMemo(() => produce(NODES_EXTRA_DATA, (draft) => {
|
return useMemo(() => produce(NODES_EXTRA_DATA, (draft) => {
|
||||||
Object.keys(draft).forEach((key) => {
|
Object.keys(draft).forEach((key) => {
|
||||||
draft[key as BlockEnum].about = t(`workflow.blocksAbout.${key}`)
|
draft[key as BlockEnum].about = t(`workflow.blocksAbout.${key}`)
|
||||||
|
draft[key as BlockEnum].availablePrevNodes = draft[key as BlockEnum].getAvailablePrevNodes(isChatMode)
|
||||||
|
draft[key as BlockEnum].availableNextNodes = draft[key as BlockEnum].getAvailableNextNodes(isChatMode)
|
||||||
})
|
})
|
||||||
}), [t])
|
}), [t, isChatMode])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,13 +96,7 @@ export const useWorkflow = () => {
|
|||||||
list.push(...incomers)
|
list.push(...incomers)
|
||||||
|
|
||||||
return list.filter((item) => {
|
return list.filter((item) => {
|
||||||
if (item.data.type === BlockEnum.IfElse)
|
return SUPPORT_OUTPUT_VARS_NODE.includes(item.data.type)
|
||||||
return false
|
|
||||||
|
|
||||||
if (item.data.type === BlockEnum.QuestionClassifier)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
@ -175,21 +169,16 @@ export const useWorkflow = () => {
|
|||||||
return list
|
return list
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
const getIncomersNodes = useCallback((currentNode: Node) => {
|
const isValidConnection = useCallback(() => {
|
||||||
const {
|
return true
|
||||||
getNodes,
|
}, [])
|
||||||
edges,
|
|
||||||
} = store.getState()
|
|
||||||
|
|
||||||
return getIncomers(currentNode, getNodes(), edges)
|
|
||||||
}, [store])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleLayout,
|
handleLayout,
|
||||||
getTreeLeafNodes,
|
getTreeLeafNodes,
|
||||||
getBeforeNodesInSameBranch,
|
getBeforeNodesInSameBranch,
|
||||||
getAfterNodesInSameBranch,
|
getAfterNodesInSameBranch,
|
||||||
getIncomersNodes,
|
isValidConnection,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import ReactFlow, {
|
|||||||
Background,
|
Background,
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
useOnViewportChange,
|
useOnViewportChange,
|
||||||
|
useStoreApi,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import type { Viewport } from 'reactflow'
|
import type { Viewport } from 'reactflow'
|
||||||
import 'reactflow/dist/style.css'
|
import 'reactflow/dist/style.css'
|
||||||
@ -21,6 +22,7 @@ import {
|
|||||||
useEdgesInteractions,
|
useEdgesInteractions,
|
||||||
useNodesInteractions,
|
useNodesInteractions,
|
||||||
useNodesSyncDraft,
|
useNodesSyncDraft,
|
||||||
|
useWorkflow,
|
||||||
useWorkflowInit,
|
useWorkflowInit,
|
||||||
} from './hooks'
|
} from './hooks'
|
||||||
import Header from './header'
|
import Header from './header'
|
||||||
@ -60,6 +62,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
|
const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
|
||||||
const runningStatus = useStore(s => s.runningStatus)
|
const runningStatus = useStore(s => s.runningStatus)
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const store = useStoreApi()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAutoFreeze(false)
|
setAutoFreeze(false)
|
||||||
@ -84,6 +87,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
handleEdgeDelete,
|
handleEdgeDelete,
|
||||||
handleEdgesChange,
|
handleEdgesChange,
|
||||||
} = useEdgesInteractions()
|
} = useEdgesInteractions()
|
||||||
|
const { isValidConnection } = useWorkflow()
|
||||||
|
|
||||||
useOnViewportChange({
|
useOnViewportChange({
|
||||||
onEnd: () => handleSyncWorkflowDraft(),
|
onEnd: () => handleSyncWorkflowDraft(),
|
||||||
@ -130,6 +134,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
zoomOnPinch={!runningStatus}
|
zoomOnPinch={!runningStatus}
|
||||||
zoomOnScroll={!runningStatus}
|
zoomOnScroll={!runningStatus}
|
||||||
zoomOnDoubleClick={!runningStatus}
|
zoomOnDoubleClick={!runningStatus}
|
||||||
|
isValidConnection={isValidConnection}
|
||||||
>
|
>
|
||||||
<Background
|
<Background
|
||||||
gap={[14, 14]}
|
gap={[14, 14]}
|
||||||
|
|||||||
@ -3,23 +3,33 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
import {
|
||||||
|
useNodesExtraData,
|
||||||
|
useNodesInteractions,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
import type {
|
||||||
|
BlockEnum,
|
||||||
|
OnSelectBlock,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
|
||||||
type AddProps = {
|
type AddProps = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
|
nodeType: BlockEnum
|
||||||
sourceHandle: string
|
sourceHandle: string
|
||||||
branchName?: string
|
branchName?: string
|
||||||
}
|
}
|
||||||
const Add = ({
|
const Add = ({
|
||||||
nodeId,
|
nodeId,
|
||||||
|
nodeType,
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
branchName,
|
branchName,
|
||||||
}: AddProps) => {
|
}: AddProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleNodeAdd } = useNodesInteractions()
|
const { handleNodeAdd } = useNodesInteractions()
|
||||||
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const availableNextNodes = nodesExtraData[nodeType].availableNextNodes
|
||||||
|
|
||||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||||
handleNodeAdd(
|
handleNodeAdd(
|
||||||
@ -65,6 +75,7 @@ const Add = ({
|
|||||||
offset={0}
|
offset={0}
|
||||||
trigger={renderTrigger}
|
trigger={renderTrigger}
|
||||||
popupClassName='!w-[328px]'
|
popupClassName='!w-[328px]'
|
||||||
|
availableBlocksTypes={availableNextNodes}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ const NextStep = ({
|
|||||||
!nodeWithBranches && !outgoers.length && (
|
!nodeWithBranches && !outgoers.length && (
|
||||||
<Add
|
<Add
|
||||||
nodeId={selectedNode!.id}
|
nodeId={selectedNode!.id}
|
||||||
|
nodeType={selectedNode!.data.type}
|
||||||
sourceHandle='source'
|
sourceHandle='source'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -81,6 +82,7 @@ const NextStep = ({
|
|||||||
<Add
|
<Add
|
||||||
key={branch.id}
|
key={branch.id}
|
||||||
nodeId={selectedNode!.id}
|
nodeId={selectedNode!.id}
|
||||||
|
nodeType={selectedNode!.data.type}
|
||||||
sourceHandle={branch.id}
|
sourceHandle={branch.id}
|
||||||
branchName={branch.name}
|
branchName={branch.name}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -3,13 +3,17 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { union } from 'lodash-es'
|
||||||
import type {
|
import type {
|
||||||
CommonNodeType,
|
CommonNodeType,
|
||||||
OnSelectBlock,
|
OnSelectBlock,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||||
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
import {
|
||||||
|
useNodesExtraData,
|
||||||
|
useNodesInteractions,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
@ -26,6 +30,9 @@ const Item = ({
|
|||||||
}: ItemProps) => {
|
}: ItemProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleNodeChange } = useNodesInteractions()
|
const { handleNodeChange } = useNodesInteractions()
|
||||||
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
|
||||||
|
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
||||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||||
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
||||||
}, [nodeId, sourceHandle, handleNodeChange])
|
}, [nodeId, sourceHandle, handleNodeChange])
|
||||||
@ -68,6 +75,7 @@ const Item = ({
|
|||||||
}}
|
}}
|
||||||
trigger={renderTrigger}
|
trigger={renderTrigger}
|
||||||
popupClassName='!w-[328px]'
|
popupClassName='!w-[328px]'
|
||||||
|
availableBlocksTypes={union(availablePrevNodes, availableNextNodes)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,7 +13,10 @@ import { BlockEnum } from '../../../types'
|
|||||||
import type { Node } from '../../../types'
|
import type { Node } from '../../../types'
|
||||||
import BlockSelector from '../../../block-selector'
|
import BlockSelector from '../../../block-selector'
|
||||||
import type { ToolDefaultValue } from '../../../block-selector/types'
|
import type { ToolDefaultValue } from '../../../block-selector/types'
|
||||||
import { useNodesInteractions } from '../../../hooks'
|
import {
|
||||||
|
useNodesExtraData,
|
||||||
|
useNodesInteractions,
|
||||||
|
} from '../../../hooks'
|
||||||
import { useStore } from '../../../store'
|
import { useStore } from '../../../store'
|
||||||
|
|
||||||
type NodeHandleProps = {
|
type NodeHandleProps = {
|
||||||
@ -31,7 +34,10 @@ export const NodeTargetHandle = memo(({
|
|||||||
}: NodeHandleProps) => {
|
}: NodeHandleProps) => {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const { handleNodeAdd } = useNodesInteractions()
|
const { handleNodeAdd } = useNodesInteractions()
|
||||||
|
const nodesExtraData = useNodesExtraData()
|
||||||
const connected = data._connectedTargetHandleIds?.includes(handleId)
|
const connected = data._connectedTargetHandleIds?.includes(handleId)
|
||||||
|
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
|
||||||
|
const isConnectable = !!availablePrevNodes.length
|
||||||
|
|
||||||
const handleOpenChange = useCallback((v: boolean) => {
|
const handleOpenChange = useCallback((v: boolean) => {
|
||||||
setOpen(v)
|
setOpen(v)
|
||||||
@ -67,11 +73,11 @@ export const NodeTargetHandle = memo(({
|
|||||||
${data.type === BlockEnum.Start && 'opacity-0'}
|
${data.type === BlockEnum.Start && 'opacity-0'}
|
||||||
${handleClassName}
|
${handleClassName}
|
||||||
`}
|
`}
|
||||||
isConnectable={data.type !== BlockEnum.Start}
|
isConnectable={isConnectable}
|
||||||
onClick={handleHandleClick}
|
onClick={handleHandleClick}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!connected && data.type !== BlockEnum.Start && (
|
!connected && isConnectable && (
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
@ -84,6 +90,7 @@ export const NodeTargetHandle = memo(({
|
|||||||
group-hover:flex
|
group-hover:flex
|
||||||
${open && '!flex'}
|
${open && '!flex'}
|
||||||
`}
|
`}
|
||||||
|
availableBlocksTypes={availablePrevNodes}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -103,6 +110,9 @@ export const NodeSourceHandle = memo(({
|
|||||||
const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
|
const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const { handleNodeAdd } = useNodesInteractions()
|
const { handleNodeAdd } = useNodesInteractions()
|
||||||
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
||||||
|
const isConnectable = !!availableNextNodes.length
|
||||||
const connected = data._connectedSourceHandleIds?.includes(handleId)
|
const connected = data._connectedSourceHandleIds?.includes(handleId)
|
||||||
const handleOpenChange = useCallback((v: boolean) => {
|
const handleOpenChange = useCallback((v: boolean) => {
|
||||||
setOpen(v)
|
setOpen(v)
|
||||||
@ -142,10 +152,11 @@ export const NodeSourceHandle = memo(({
|
|||||||
${!connected && 'after:opacity-0'}
|
${!connected && 'after:opacity-0'}
|
||||||
${handleClassName}
|
${handleClassName}
|
||||||
`}
|
`}
|
||||||
|
isConnectable={isConnectable}
|
||||||
onClick={handleHandleClick}
|
onClick={handleHandleClick}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!connected && (
|
!connected && isConnectable && (
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
@ -157,6 +168,7 @@ export const NodeSourceHandle = memo(({
|
|||||||
group-hover:flex
|
group-hover:flex
|
||||||
${open && '!flex'}
|
${open && '!flex'}
|
||||||
`}
|
`}
|
||||||
|
availableBlocksTypes={availableNextNodes}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,20 +3,32 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { union } from 'lodash-es'
|
||||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||||
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
import {
|
||||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
useNodesExtraData,
|
||||||
|
useNodesInteractions,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import type {
|
||||||
|
BlockEnum,
|
||||||
|
OnSelectBlock,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
|
||||||
type ChangeBlockProps = {
|
type ChangeBlockProps = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
|
nodeType: BlockEnum
|
||||||
sourceHandle: string
|
sourceHandle: string
|
||||||
}
|
}
|
||||||
const ChangeBlock = ({
|
const ChangeBlock = ({
|
||||||
nodeId,
|
nodeId,
|
||||||
|
nodeType,
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
}: ChangeBlockProps) => {
|
}: ChangeBlockProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleNodeChange } = useNodesInteractions()
|
const { handleNodeChange } = useNodesInteractions()
|
||||||
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const availablePrevNodes = nodesExtraData[nodeType].availablePrevNodes
|
||||||
|
const availableNextNodes = nodesExtraData[nodeType].availableNextNodes
|
||||||
|
|
||||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||||
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
||||||
@ -39,6 +51,7 @@ const ChangeBlock = ({
|
|||||||
}}
|
}}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
trigger={renderTrigger}
|
trigger={renderTrigger}
|
||||||
|
availableBlocksTypes={union(availablePrevNodes, availableNextNodes)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,6 +129,7 @@ const PanelOperator = ({
|
|||||||
data.type !== BlockEnum.Start && (
|
data.type !== BlockEnum.Start && (
|
||||||
<ChangeBlock
|
<ChangeBlock
|
||||||
nodeId={id}
|
nodeId={id}
|
||||||
|
nodeType={data.type}
|
||||||
sourceHandle={edge?.sourceHandle || 'source'}
|
sourceHandle={edge?.sourceHandle || 'source'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
import {
|
import {
|
||||||
useNodeDataUpdate,
|
useNodeDataUpdate,
|
||||||
|
useNodesExtraData,
|
||||||
useNodesInteractions,
|
useNodesInteractions,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
import { canRunBySingle } from '@/app/components/workflow/utils'
|
import { canRunBySingle } from '@/app/components/workflow/utils'
|
||||||
@ -27,7 +28,6 @@ import { GitBranch01 } from '@/app/components/base/icons/src/vender/line/develop
|
|||||||
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||||
import type { Node } from '@/app/components/workflow/types'
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
|
||||||
|
|
||||||
type BasePanelProps = {
|
type BasePanelProps = {
|
||||||
children: ReactElement
|
children: ReactElement
|
||||||
@ -40,6 +40,9 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleNodeSelect } = useNodesInteractions()
|
const { handleNodeSelect } = useNodesInteractions()
|
||||||
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleNodeDataUpdate,
|
handleNodeDataUpdate,
|
||||||
handleNodeDataUpdateWithSyncDraft,
|
handleNodeDataUpdateWithSyncDraft,
|
||||||
@ -102,7 +105,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
{cloneElement(children, { id, data })}
|
{cloneElement(children, { id, data })}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
data.type !== BlockEnum.End && (
|
!!availableNextNodes.length && (
|
||||||
<div className='p-4 border-t-[0.5px] border-t-black/5'>
|
<div className='p-4 border-t-[0.5px] border-t-black/5'>
|
||||||
<div className='flex items-center mb-1 text-gray-700 text-[13px] font-semibold'>
|
<div className='flex items-center mb-1 text-gray-700 text-[13px] font-semibold'>
|
||||||
<GitBranch01 className='mr-1 w-4 h-4' />
|
<GitBranch01 className='mr-1 w-4 h-4' />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { NodeDefault } from '../../types'
|
import { BlockEnum, type NodeDefault } from '../../types'
|
||||||
import { type IfElseNodeType, LogicalOperator } from './types'
|
import { type IfElseNodeType, LogicalOperator } from './types'
|
||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ const nodeDefault: NodeDefault<IfElseNodeType> = {
|
|||||||
},
|
},
|
||||||
getAvailableNextNodes(isChatMode: boolean) {
|
getAvailableNextNodes(isChatMode: boolean) {
|
||||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||||
return nodes
|
return nodes.filter(type => type !== BlockEnum.VariableAssigner)
|
||||||
},
|
},
|
||||||
checkValid(payload: IfElseNodeType) {
|
checkValid(payload: IfElseNodeType) {
|
||||||
let isValid = true
|
let isValid = true
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { NodeDefault } from '../../types'
|
import type { NodeDefault } from '../../types'
|
||||||
|
import { BlockEnum } from '../../types'
|
||||||
import type { QuestionClassifierNodeType } from './types'
|
import type { QuestionClassifierNodeType } from './types'
|
||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ const nodeDefault: NodeDefault<QuestionClassifierNodeType> = {
|
|||||||
},
|
},
|
||||||
getAvailableNextNodes(isChatMode: boolean) {
|
getAvailableNextNodes(isChatMode: boolean) {
|
||||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||||
return nodes
|
return nodes.filter(type => type !== BlockEnum.VariableAssigner)
|
||||||
},
|
},
|
||||||
checkValid(payload: QuestionClassifierNodeType) {
|
checkValid(payload: QuestionClassifierNodeType) {
|
||||||
let isValid = true
|
let isValid = true
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { type NodeDefault, VarType } from '../../types'
|
import { type NodeDefault, VarType } from '../../types'
|
||||||
|
import { BlockEnum } from '../../types'
|
||||||
import type { VariableAssignerNodeType } from './types'
|
import type { VariableAssignerNodeType } from './types'
|
||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ const nodeDefault: NodeDefault<VariableAssignerNodeType> = {
|
|||||||
},
|
},
|
||||||
getAvailablePrevNodes(isChatMode: boolean) {
|
getAvailablePrevNodes(isChatMode: boolean) {
|
||||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||||
return nodes
|
return nodes.filter(type => type !== BlockEnum.IfElse && type !== BlockEnum.QuestionClassifier)
|
||||||
},
|
},
|
||||||
getAvailableNextNodes(isChatMode: boolean) {
|
getAvailableNextNodes(isChatMode: boolean) {
|
||||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||||
|
|||||||
Reference in New Issue
Block a user