chore: refactor workflow
parent
5e09ac696c
commit
f7de55364f
@ -0,0 +1,60 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useGetLanguage } from '@/context/i18n'
|
||||||
|
import StartDefault from '@/app/components/workflow/nodes/start/default'
|
||||||
|
import EndDefault from '@/app/components/workflow/nodes/end/default'
|
||||||
|
import AnswerDefault from '@/app/components/workflow/nodes/answer/default'
|
||||||
|
import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node'
|
||||||
|
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store'
|
||||||
|
import { useIsChatMode } from './use-is-chat-mode'
|
||||||
|
|
||||||
|
export const useAvailableNodesMetaData = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const isChatMode = useIsChatMode()
|
||||||
|
const language = useGetLanguage()
|
||||||
|
|
||||||
|
console.log('isChatMode', isChatMode)
|
||||||
|
|
||||||
|
const mergedNodesMetaData = useMemo(() => [
|
||||||
|
...WORKFLOW_COMMON_NODES,
|
||||||
|
StartDefault,
|
||||||
|
...(
|
||||||
|
isChatMode
|
||||||
|
? [AnswerDefault]
|
||||||
|
: [EndDefault]
|
||||||
|
),
|
||||||
|
], [isChatMode])
|
||||||
|
|
||||||
|
const prefixLink = useMemo(() => {
|
||||||
|
if (language === 'zh_Hans')
|
||||||
|
return 'https://docs.dify.ai/zh-hans/guides/workflow/node/'
|
||||||
|
|
||||||
|
return 'https://docs.dify.ai/guides/workflow/node/'
|
||||||
|
}, [language])
|
||||||
|
|
||||||
|
const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
defaultValue: {
|
||||||
|
...node.defaultValue,
|
||||||
|
type: node.type,
|
||||||
|
},
|
||||||
|
title: t(`workflow.blocks.${node.type}`),
|
||||||
|
description: t(`workflow.blocksAbout.${node.type}`),
|
||||||
|
helpLinkUri: `${prefixLink}${node.helpLinkUri}`,
|
||||||
|
|
||||||
|
}
|
||||||
|
}), [mergedNodesMetaData, t, prefixLink])
|
||||||
|
|
||||||
|
const availableNodesMetaDataMap = useMemo(() => availableNodesMetaData.reduce((acc, node) => {
|
||||||
|
acc![node.type] = node
|
||||||
|
return acc
|
||||||
|
}, {} as AvailableNodesMetaData['nodesMap']), [availableNodesMetaData])
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
nodes: availableNodesMetaData,
|
||||||
|
nodesMap: availableNodesMetaDataMap,
|
||||||
|
}
|
||||||
|
}, [availableNodesMetaData, availableNodesMetaDataMap])
|
||||||
|
}
|
||||||
@ -1,171 +1,36 @@
|
|||||||
import type {
|
import { useMemo } from 'react'
|
||||||
FC,
|
import type { NodeSelectorProps } from './main'
|
||||||
MouseEventHandler,
|
import NodeSelector from './main'
|
||||||
} from 'react'
|
import { useHooksStore } from '@/app/components/workflow/hooks-store/store'
|
||||||
import {
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import type {
|
|
||||||
OffsetOptions,
|
|
||||||
Placement,
|
|
||||||
} from '@floating-ui/react'
|
|
||||||
import type { BlockEnum, OnSelectBlock } from '../types'
|
|
||||||
import Tabs from './tabs'
|
|
||||||
import { TabsEnum } from './types'
|
|
||||||
import {
|
|
||||||
PortalToFollowElem,
|
|
||||||
PortalToFollowElemContent,
|
|
||||||
PortalToFollowElemTrigger,
|
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
|
||||||
import Input from '@/app/components/base/input'
|
|
||||||
import SearchBox from '@/app/components/plugins/marketplace/search-box'
|
|
||||||
|
|
||||||
import {
|
const NodeSelectorWrapper = (props: NodeSelectorProps) => {
|
||||||
Plus02,
|
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)
|
||||||
} from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
|
|
||||||
type NodeSelectorProps = {
|
const blocks = useMemo(() => {
|
||||||
open?: boolean
|
const result = availableNodesMetaData?.nodes || []
|
||||||
onOpenChange?: (open: boolean) => void
|
console.log(result, 'result')
|
||||||
onSelect: OnSelectBlock
|
|
||||||
trigger?: (open: boolean) => React.ReactNode
|
|
||||||
placement?: Placement
|
|
||||||
offset?: OffsetOptions
|
|
||||||
triggerStyle?: React.CSSProperties
|
|
||||||
triggerClassName?: (open: boolean) => string
|
|
||||||
triggerInnerClassName?: string
|
|
||||||
popupClassName?: string
|
|
||||||
asChild?: boolean
|
|
||||||
availableBlocksTypes?: BlockEnum[]
|
|
||||||
disabled?: boolean
|
|
||||||
noBlocks?: boolean
|
|
||||||
}
|
|
||||||
const NodeSelector: FC<NodeSelectorProps> = ({
|
|
||||||
open: openFromProps,
|
|
||||||
onOpenChange,
|
|
||||||
onSelect,
|
|
||||||
trigger,
|
|
||||||
placement = 'right',
|
|
||||||
offset = 6,
|
|
||||||
triggerClassName,
|
|
||||||
triggerInnerClassName,
|
|
||||||
triggerStyle,
|
|
||||||
popupClassName,
|
|
||||||
asChild,
|
|
||||||
availableBlocksTypes,
|
|
||||||
disabled,
|
|
||||||
noBlocks = false,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const [searchText, setSearchText] = useState('')
|
|
||||||
const [tags, setTags] = useState<string[]>([])
|
|
||||||
const [localOpen, setLocalOpen] = useState(false)
|
|
||||||
const open = openFromProps === undefined ? localOpen : openFromProps
|
|
||||||
const handleOpenChange = useCallback((newOpen: boolean) => {
|
|
||||||
setLocalOpen(newOpen)
|
|
||||||
|
|
||||||
if (!newOpen)
|
return result.filter((block) => {
|
||||||
setSearchText('')
|
if (block.type === BlockEnum.Start)
|
||||||
|
return false
|
||||||
|
|
||||||
if (onOpenChange)
|
if (block.type === BlockEnum.IterationStart)
|
||||||
onOpenChange(newOpen)
|
return false
|
||||||
}, [onOpenChange])
|
|
||||||
const handleTrigger = useCallback<MouseEventHandler<HTMLDivElement>>((e) => {
|
|
||||||
if (disabled)
|
|
||||||
return
|
|
||||||
e.stopPropagation()
|
|
||||||
handleOpenChange(!open)
|
|
||||||
}, [handleOpenChange, open, disabled])
|
|
||||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
|
||||||
handleOpenChange(false)
|
|
||||||
onSelect(type, toolDefaultValue)
|
|
||||||
}, [handleOpenChange, onSelect])
|
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(noBlocks ? TabsEnum.Tools : TabsEnum.Blocks)
|
if (block.type === BlockEnum.LoopStart)
|
||||||
const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => {
|
return false
|
||||||
setActiveTab(newActiveTab)
|
|
||||||
}, [])
|
|
||||||
const searchPlaceholder = useMemo(() => {
|
|
||||||
if (activeTab === TabsEnum.Blocks)
|
|
||||||
return t('workflow.tabs.searchBlock')
|
|
||||||
|
|
||||||
if (activeTab === TabsEnum.Tools)
|
return true
|
||||||
return t('workflow.tabs.searchTool')
|
})
|
||||||
return ''
|
}, [availableNodesMetaData?.nodes])
|
||||||
}, [activeTab, t])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PortalToFollowElem
|
<NodeSelector
|
||||||
placement={placement}
|
{...props}
|
||||||
offset={offset}
|
blocks={blocks}
|
||||||
open={open}
|
/>
|
||||||
onOpenChange={handleOpenChange}
|
|
||||||
>
|
|
||||||
<PortalToFollowElemTrigger
|
|
||||||
asChild={asChild}
|
|
||||||
onClick={handleTrigger}
|
|
||||||
className={triggerInnerClassName}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
trigger
|
|
||||||
? trigger(open)
|
|
||||||
: (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
z-10 flex h-4
|
|
||||||
w-4 cursor-pointer items-center justify-center rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover
|
|
||||||
${triggerClassName?.(open)}
|
|
||||||
`}
|
|
||||||
style={triggerStyle}
|
|
||||||
>
|
|
||||||
<Plus02 className='h-2.5 w-2.5' />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</PortalToFollowElemTrigger>
|
|
||||||
<PortalToFollowElemContent className='z-[1000]'>
|
|
||||||
<div className={`rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg ${popupClassName}`}>
|
|
||||||
<div className='px-2 pt-2' onClick={e => e.stopPropagation()}>
|
|
||||||
{activeTab === TabsEnum.Blocks && (
|
|
||||||
<Input
|
|
||||||
showLeftIcon
|
|
||||||
showClearIcon
|
|
||||||
autoFocus
|
|
||||||
value={searchText}
|
|
||||||
placeholder={searchPlaceholder}
|
|
||||||
onChange={e => setSearchText(e.target.value)}
|
|
||||||
onClear={() => setSearchText('')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{activeTab === TabsEnum.Tools && (
|
|
||||||
<SearchBox
|
|
||||||
search={searchText}
|
|
||||||
onSearchChange={setSearchText}
|
|
||||||
tags={tags}
|
|
||||||
onTagsChange={setTags}
|
|
||||||
size='small'
|
|
||||||
placeholder={t('plugin.searchTools')!}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<Tabs
|
|
||||||
activeTab={activeTab}
|
|
||||||
onActiveTabChange={handleActiveTabChange}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
searchText={searchText}
|
|
||||||
tags={tags}
|
|
||||||
availableBlocksTypes={availableBlocksTypes}
|
|
||||||
noBlocks={noBlocks}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</PortalToFollowElemContent>
|
|
||||||
</PortalToFollowElem>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(NodeSelector)
|
export default NodeSelectorWrapper
|
||||||
|
|||||||
@ -0,0 +1,175 @@
|
|||||||
|
import type {
|
||||||
|
FC,
|
||||||
|
MouseEventHandler,
|
||||||
|
} from 'react'
|
||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type {
|
||||||
|
OffsetOptions,
|
||||||
|
Placement,
|
||||||
|
} from '@floating-ui/react'
|
||||||
|
import type {
|
||||||
|
BlockEnum,
|
||||||
|
NodeDefault,
|
||||||
|
OnSelectBlock,
|
||||||
|
} from '../types'
|
||||||
|
import Tabs from './tabs'
|
||||||
|
import { TabsEnum } from './types'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import SearchBox from '@/app/components/plugins/marketplace/search-box'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Plus02,
|
||||||
|
} from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
|
||||||
|
export type NodeSelectorProps = {
|
||||||
|
open?: boolean
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
|
onSelect: OnSelectBlock
|
||||||
|
trigger?: (open: boolean) => React.ReactNode
|
||||||
|
placement?: Placement
|
||||||
|
offset?: OffsetOptions
|
||||||
|
triggerStyle?: React.CSSProperties
|
||||||
|
triggerClassName?: (open: boolean) => string
|
||||||
|
triggerInnerClassName?: string
|
||||||
|
popupClassName?: string
|
||||||
|
asChild?: boolean
|
||||||
|
availableBlocksTypes?: BlockEnum[]
|
||||||
|
disabled?: boolean
|
||||||
|
blocks?: NodeDefault[]
|
||||||
|
}
|
||||||
|
const NodeSelector: FC<NodeSelectorProps> = ({
|
||||||
|
open: openFromProps,
|
||||||
|
onOpenChange,
|
||||||
|
onSelect,
|
||||||
|
trigger,
|
||||||
|
placement = 'right',
|
||||||
|
offset = 6,
|
||||||
|
triggerClassName,
|
||||||
|
triggerInnerClassName,
|
||||||
|
triggerStyle,
|
||||||
|
popupClassName,
|
||||||
|
asChild,
|
||||||
|
availableBlocksTypes,
|
||||||
|
disabled,
|
||||||
|
blocks = [],
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const [tags, setTags] = useState<string[]>([])
|
||||||
|
const [localOpen, setLocalOpen] = useState(false)
|
||||||
|
const open = openFromProps === undefined ? localOpen : openFromProps
|
||||||
|
const handleOpenChange = useCallback((newOpen: boolean) => {
|
||||||
|
setLocalOpen(newOpen)
|
||||||
|
|
||||||
|
if (!newOpen)
|
||||||
|
setSearchText('')
|
||||||
|
|
||||||
|
if (onOpenChange)
|
||||||
|
onOpenChange(newOpen)
|
||||||
|
}, [onOpenChange])
|
||||||
|
const handleTrigger = useCallback<MouseEventHandler<HTMLDivElement>>((e) => {
|
||||||
|
if (disabled)
|
||||||
|
return
|
||||||
|
e.stopPropagation()
|
||||||
|
handleOpenChange(!open)
|
||||||
|
}, [handleOpenChange, open, disabled])
|
||||||
|
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||||
|
handleOpenChange(false)
|
||||||
|
onSelect(type, toolDefaultValue)
|
||||||
|
}, [handleOpenChange, onSelect])
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState(!blocks.length ? TabsEnum.Tools : TabsEnum.Blocks)
|
||||||
|
const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => {
|
||||||
|
setActiveTab(newActiveTab)
|
||||||
|
}, [])
|
||||||
|
const searchPlaceholder = useMemo(() => {
|
||||||
|
if (activeTab === TabsEnum.Blocks)
|
||||||
|
return t('workflow.tabs.searchBlock')
|
||||||
|
|
||||||
|
if (activeTab === TabsEnum.Tools)
|
||||||
|
return t('workflow.tabs.searchTool')
|
||||||
|
return ''
|
||||||
|
}, [activeTab, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem
|
||||||
|
placement={placement}
|
||||||
|
offset={offset}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger
|
||||||
|
asChild={asChild}
|
||||||
|
onClick={handleTrigger}
|
||||||
|
className={triggerInnerClassName}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
trigger
|
||||||
|
? trigger(open)
|
||||||
|
: (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
z-10 flex h-4
|
||||||
|
w-4 cursor-pointer items-center justify-center rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover
|
||||||
|
${triggerClassName?.(open)}
|
||||||
|
`}
|
||||||
|
style={triggerStyle}
|
||||||
|
>
|
||||||
|
<Plus02 className='h-2.5 w-2.5' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[1000]'>
|
||||||
|
<div className={`rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg ${popupClassName}`}>
|
||||||
|
<div className='px-2 pt-2' onClick={e => e.stopPropagation()}>
|
||||||
|
{activeTab === TabsEnum.Blocks && (
|
||||||
|
<Input
|
||||||
|
showLeftIcon
|
||||||
|
showClearIcon
|
||||||
|
autoFocus
|
||||||
|
value={searchText}
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
onChange={e => setSearchText(e.target.value)}
|
||||||
|
onClear={() => setSearchText('')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeTab === TabsEnum.Tools && (
|
||||||
|
<SearchBox
|
||||||
|
search={searchText}
|
||||||
|
onSearchChange={setSearchText}
|
||||||
|
tags={tags}
|
||||||
|
onTagsChange={setTags}
|
||||||
|
size='small'
|
||||||
|
placeholder={t('plugin.searchTools')!}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Tabs
|
||||||
|
activeTab={activeTab}
|
||||||
|
onActiveTabChange={handleActiveTabChange}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
searchText={searchText}
|
||||||
|
tags={tags}
|
||||||
|
availableBlocksTypes={availableBlocksTypes}
|
||||||
|
blocks={blocks}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(NodeSelector)
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { BlockEnum } from './types'
|
|
||||||
|
|
||||||
export const ALL_AVAILABLE_BLOCKS = Object.values(BlockEnum)
|
|
||||||
export const ALL_CHAT_AVAILABLE_BLOCKS = ALL_AVAILABLE_BLOCKS.filter(key => key !== BlockEnum.End && key !== BlockEnum.Start) as BlockEnum[]
|
|
||||||
export const ALL_COMPLETION_AVAILABLE_BLOCKS = ALL_AVAILABLE_BLOCKS.filter(key => key !== BlockEnum.Answer && key !== BlockEnum.Start) as BlockEnum[]
|
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import llmDefault from '@/app/components/workflow/nodes/llm/default'
|
||||||
|
import knowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
||||||
|
import agentDefault from '@/app/components/workflow/nodes/agent/default'
|
||||||
|
|
||||||
|
import questionClassifierDefault from '@/app/components/workflow/nodes/question-classifier/default'
|
||||||
|
|
||||||
|
import ifElseDefault from '@/app/components/workflow/nodes/if-else/default'
|
||||||
|
import iterationDefault from '@/app/components/workflow/nodes/iteration/default'
|
||||||
|
import iterationStartDefault from '@/app/components/workflow/nodes/iteration-start/default'
|
||||||
|
import loopDefault from '@/app/components/workflow/nodes/loop/default'
|
||||||
|
import loopStartDefault from '@/app/components/workflow/nodes/loop-start/default'
|
||||||
|
import loopEndDefault from '@/app/components/workflow/nodes/loop-end/default'
|
||||||
|
|
||||||
|
import codeDefault from '@/app/components/workflow/nodes/code/default'
|
||||||
|
import templateTransformDefault from '@/app/components/workflow/nodes/template-transform/default'
|
||||||
|
import variableAggregatorDefault from '@/app/components/workflow/nodes/variable-assigner/default'
|
||||||
|
import documentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
|
||||||
|
import assignerDefault from '@/app/components/workflow/nodes/assigner/default'
|
||||||
|
import httpRequestDefault from '@/app/components/workflow/nodes/http/default'
|
||||||
|
import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
|
||||||
|
import listOperatorDefault from '@/app/components/workflow/nodes/list-operator/default'
|
||||||
|
|
||||||
|
export const WORKFLOW_COMMON_NODES = [
|
||||||
|
llmDefault,
|
||||||
|
knowledgeRetrievalDefault,
|
||||||
|
agentDefault,
|
||||||
|
questionClassifierDefault,
|
||||||
|
ifElseDefault,
|
||||||
|
iterationDefault,
|
||||||
|
iterationStartDefault,
|
||||||
|
loopDefault,
|
||||||
|
loopStartDefault,
|
||||||
|
loopEndDefault,
|
||||||
|
codeDefault,
|
||||||
|
templateTransformDefault,
|
||||||
|
variableAggregatorDefault,
|
||||||
|
documentExtractorDefault,
|
||||||
|
assignerDefault,
|
||||||
|
parameterExtractorDefault,
|
||||||
|
httpRequestDefault,
|
||||||
|
listOperatorDefault,
|
||||||
|
]
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
} from 'react'
|
||||||
|
import { BlockEnum } from '../types'
|
||||||
|
import { useNodesMetaData } from './use-nodes-meta-data'
|
||||||
|
|
||||||
|
const availableBlocksFilter = (nodeType: BlockEnum, inContainer?: boolean) => {
|
||||||
|
if (inContainer && (nodeType === BlockEnum.Iteration || nodeType === BlockEnum.Loop || nodeType === BlockEnum.End))
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (!inContainer && nodeType === BlockEnum.LoopEnd)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean) => {
|
||||||
|
const {
|
||||||
|
nodes: availableNodes,
|
||||||
|
} = useNodesMetaData()
|
||||||
|
const availableNodesType = useMemo(() => availableNodes.map(node => node.type), [availableNodes])
|
||||||
|
const availablePrevBlocks = useMemo(() => {
|
||||||
|
if (!nodeType || nodeType === BlockEnum.Start)
|
||||||
|
return []
|
||||||
|
|
||||||
|
return availableNodesType
|
||||||
|
}, [availableNodesType, nodeType])
|
||||||
|
const availableNextBlocks = useMemo(() => {
|
||||||
|
if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd)
|
||||||
|
return []
|
||||||
|
|
||||||
|
return availableNodesType
|
||||||
|
}, [availableNodesType, nodeType])
|
||||||
|
|
||||||
|
const getAvailableBlocks = useCallback((nodeType?: BlockEnum, inContainer?: boolean) => {
|
||||||
|
let availablePrevBlocks = availableNodesType
|
||||||
|
if (!nodeType || nodeType === BlockEnum.Start)
|
||||||
|
availablePrevBlocks = []
|
||||||
|
|
||||||
|
let availableNextBlocks = availableNodesType
|
||||||
|
if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd)
|
||||||
|
availableNextBlocks = []
|
||||||
|
|
||||||
|
return {
|
||||||
|
availablePrevBlocks: availablePrevBlocks.filter(nType => availableBlocksFilter(nType, inContainer)),
|
||||||
|
availableNextBlocks: availableNextBlocks.filter(nType => availableBlocksFilter(nType, inContainer)),
|
||||||
|
}
|
||||||
|
}, [availableNodesType])
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
getAvailableBlocks,
|
||||||
|
availablePrevBlocks: availablePrevBlocks.filter(nType => availableBlocksFilter(nType, inContainer)),
|
||||||
|
availableNextBlocks: availableNextBlocks.filter(nType => availableBlocksFilter(nType, inContainer)),
|
||||||
|
}
|
||||||
|
}, [getAvailableBlocks, availablePrevBlocks, availableNextBlocks, inContainer])
|
||||||
|
}
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import { useMemo } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import produce from 'immer'
|
|
||||||
import { BlockEnum } from '../types'
|
|
||||||
import {
|
|
||||||
NODES_EXTRA_DATA,
|
|
||||||
NODES_INITIAL_DATA,
|
|
||||||
} from '../constants'
|
|
||||||
import { useIsChatMode } from './use-workflow'
|
|
||||||
|
|
||||||
export const useNodesInitialData = () => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return useMemo(() => produce(NODES_INITIAL_DATA, (draft) => {
|
|
||||||
Object.keys(draft).forEach((key) => {
|
|
||||||
draft[key as BlockEnum].title = t(`workflow.blocks.${key}`)
|
|
||||||
})
|
|
||||||
}), [t])
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useNodesExtraData = () => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const isChatMode = useIsChatMode()
|
|
||||||
|
|
||||||
return useMemo(() => produce(NODES_EXTRA_DATA, (draft) => {
|
|
||||||
Object.keys(draft).forEach((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, isChatMode])
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean, isInLoop?: boolean) => {
|
|
||||||
const nodesExtraData = useNodesExtraData()
|
|
||||||
const availablePrevBlocks = useMemo(() => {
|
|
||||||
if (!nodeType)
|
|
||||||
return []
|
|
||||||
return nodesExtraData[nodeType].availablePrevNodes || []
|
|
||||||
}, [nodeType, nodesExtraData])
|
|
||||||
|
|
||||||
const availableNextBlocks = useMemo(() => {
|
|
||||||
if (!nodeType)
|
|
||||||
return []
|
|
||||||
|
|
||||||
return nodesExtraData[nodeType].availableNextNodes || []
|
|
||||||
}, [nodeType, nodesExtraData])
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return {
|
|
||||||
availablePrevBlocks: availablePrevBlocks.filter((nType) => {
|
|
||||||
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (!isInLoop && nType === BlockEnum.LoopEnd)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}),
|
|
||||||
availableNextBlocks: availableNextBlocks.filter((nType) => {
|
|
||||||
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (!isInLoop && nType === BlockEnum.LoopEnd)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}, [isInIteration, availablePrevBlocks, availableNextBlocks, isInLoop])
|
|
||||||
}
|
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store'
|
||||||
|
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||||
|
|
||||||
|
export const useNodesMetaData = () => {
|
||||||
|
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
nodes: availableNodesMetaData?.nodes || [],
|
||||||
|
nodesMap: availableNodesMetaData?.nodesMap || {},
|
||||||
|
} as AvailableNodesMetaData
|
||||||
|
}, [availableNodesMetaData])
|
||||||
|
}
|
||||||
@ -1,69 +1,15 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useGetLanguage } from '@/context/i18n'
|
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { useNodesMetaData } from '@/app/components/workflow/hooks'
|
||||||
|
|
||||||
export const useNodeHelpLink = (nodeType: BlockEnum) => {
|
export const useNodeHelpLink = (nodeType: BlockEnum) => {
|
||||||
const language = useGetLanguage()
|
const availableNodesMetaData = useNodesMetaData()
|
||||||
const prefixLink = useMemo(() => {
|
|
||||||
if (language === 'zh_Hans')
|
|
||||||
return 'https://docs.dify.ai/zh-hans/guides/workflow/node/'
|
|
||||||
|
|
||||||
return 'https://docs.dify.ai/guides/workflow/node/'
|
const link = useMemo(() => {
|
||||||
}, [language])
|
const result = availableNodesMetaData?.nodesMap?.[nodeType]?.helpLinkUri || ''
|
||||||
const linkMap = useMemo(() => {
|
|
||||||
if (language === 'zh_Hans') {
|
|
||||||
return {
|
|
||||||
[BlockEnum.Start]: 'start',
|
|
||||||
[BlockEnum.End]: 'end',
|
|
||||||
[BlockEnum.Answer]: 'answer',
|
|
||||||
[BlockEnum.LLM]: 'llm',
|
|
||||||
[BlockEnum.KnowledgeRetrieval]: 'knowledge-retrieval',
|
|
||||||
[BlockEnum.QuestionClassifier]: 'question-classifier',
|
|
||||||
[BlockEnum.IfElse]: 'ifelse',
|
|
||||||
[BlockEnum.Code]: 'code',
|
|
||||||
[BlockEnum.TemplateTransform]: 'template',
|
|
||||||
[BlockEnum.VariableAssigner]: 'variable-assigner',
|
|
||||||
[BlockEnum.VariableAggregator]: 'variable-aggregator',
|
|
||||||
[BlockEnum.Assigner]: 'variable-assigner',
|
|
||||||
[BlockEnum.Iteration]: 'iteration',
|
|
||||||
[BlockEnum.Loop]: 'loop',
|
|
||||||
[BlockEnum.ParameterExtractor]: 'parameter-extractor',
|
|
||||||
[BlockEnum.HttpRequest]: 'http-request',
|
|
||||||
[BlockEnum.Tool]: 'tools',
|
|
||||||
[BlockEnum.DocExtractor]: 'doc-extractor',
|
|
||||||
[BlockEnum.ListFilter]: 'list-operator',
|
|
||||||
[BlockEnum.Agent]: 'agent',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return result
|
||||||
[BlockEnum.Start]: 'start',
|
}, [availableNodesMetaData, nodeType])
|
||||||
[BlockEnum.End]: 'end',
|
|
||||||
[BlockEnum.Answer]: 'answer',
|
|
||||||
[BlockEnum.LLM]: 'llm',
|
|
||||||
[BlockEnum.KnowledgeRetrieval]: 'knowledge-retrieval',
|
|
||||||
[BlockEnum.QuestionClassifier]: 'question-classifier',
|
|
||||||
[BlockEnum.IfElse]: 'ifelse',
|
|
||||||
[BlockEnum.Code]: 'code',
|
|
||||||
[BlockEnum.TemplateTransform]: 'template',
|
|
||||||
[BlockEnum.VariableAssigner]: 'variable-assigner',
|
|
||||||
[BlockEnum.VariableAggregator]: 'variable-aggregator',
|
|
||||||
[BlockEnum.Assigner]: 'variable-assigner',
|
|
||||||
[BlockEnum.Iteration]: 'iteration',
|
|
||||||
[BlockEnum.Loop]: 'loop',
|
|
||||||
[BlockEnum.ParameterExtractor]: 'parameter-extractor',
|
|
||||||
[BlockEnum.HttpRequest]: 'http-request',
|
|
||||||
[BlockEnum.Tool]: 'tools',
|
|
||||||
[BlockEnum.DocExtractor]: 'doc-extractor',
|
|
||||||
[BlockEnum.ListFilter]: 'list-operator',
|
|
||||||
[BlockEnum.Agent]: 'agent',
|
|
||||||
}
|
|
||||||
}, [language]) as Record<string, string>
|
|
||||||
|
|
||||||
const link = linkMap[nodeType]
|
return link
|
||||||
|
|
||||||
if (!link)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
return `${prefixLink}${link}`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
|
||||||
|
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
|
export type GenNodeMetaDataParams = {
|
||||||
|
classification?: BlockClassificationEnum
|
||||||
|
sort: number
|
||||||
|
type: BlockEnum
|
||||||
|
title?: string
|
||||||
|
author?: string
|
||||||
|
helpLinkUri?: string
|
||||||
|
}
|
||||||
|
export const genNodeMetaData = ({
|
||||||
|
classification = BlockClassificationEnum.Default,
|
||||||
|
sort,
|
||||||
|
type,
|
||||||
|
title = '',
|
||||||
|
author = 'Dify',
|
||||||
|
helpLinkUri,
|
||||||
|
}: GenNodeMetaDataParams) => {
|
||||||
|
return {
|
||||||
|
classification,
|
||||||
|
sort,
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
author,
|
||||||
|
helpLinkUri: helpLinkUri || type,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue