diff --git a/web/app/(commonLayout)/workflow/nodes/page.tsx b/web/app/(commonLayout)/workflow/nodes/page.tsx index d823f4145c..88296aac13 100644 --- a/web/app/(commonLayout)/workflow/nodes/page.tsx +++ b/web/app/(commonLayout)/workflow/nodes/page.tsx @@ -42,7 +42,7 @@ const nodes = [ type: 'custom', position: { x: 330, y: 30 + i * 300 }, data: { - _selected: i === 0, // for test: always select the first node + selected: i === 0, // for test: always select the first node name: item, ...payload, }, diff --git a/web/app/components/workflow/block-icon.tsx b/web/app/components/workflow/block-icon.tsx index 69adb2f107..6b99447996 100644 --- a/web/app/components/workflow/block-icon.tsx +++ b/web/app/components/workflow/block-icon.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import { memo } from 'react' import { BlockEnum } from './types' +import { useStore } from './store' import { Code, DirectAnswer, @@ -20,7 +21,7 @@ type BlockIconProps = { type: BlockEnum size?: string className?: string - icon?: any + toolProviderId?: string } const ICON_CONTAINER_CLASSNAME_SIZE_MAP: Record = { sm: 'w-5 h-5 rounded-md shadow-xs', @@ -59,8 +60,11 @@ const BlockIcon: FC = ({ type, size = 'sm', className, - icon, + toolProviderId, }) => { + const toolsets = useStore(s => s.toolsets) + const icon = toolsets.find(toolset => toolset.id === toolProviderId)?.icon + return (
{ const index = draft.findIndex(toolset => toolset.id === toolId) - if (!toolsMap[toolId].length && !currentToolset.fetching) + if (!toolsMap[toolId]?.length && !currentToolset.fetching) draft[index].fetching = true draft[index].expanded = true diff --git a/web/app/components/workflow/block-selector/tools/item.tsx b/web/app/components/workflow/block-selector/tools/item.tsx index 5b98156f70..e9e4606944 100644 --- a/web/app/components/workflow/block-selector/tools/item.tsx +++ b/web/app/components/workflow/block-selector/tools/item.tsx @@ -106,9 +106,6 @@ const Item = ({ provider_type: data.type, tool_name: tool.name, title: tool.label[language], - _icon: data.icon, - _about: tool.description[language], - _author: data.author, })} >
diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 6d746ca623..4f4c60d30a 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -30,7 +30,4 @@ export type ToolDefaultValue = { provider_type: string tool_name: string title: string - _icon: Collection['icon'] - _about: string - _author: string } diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 1091299628..53b60413a6 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -12,30 +12,77 @@ import ToolDefault from './nodes/tool/default' import VariableAssignerDefault from './nodes/variable-assigner/default' import EndNodeDefault from './nodes/end/default' +export const NODES_EXTRA_DATA = { + [BlockEnum.Start]: { + author: 'Dify', + about: '', + }, + [BlockEnum.End]: { + author: 'Dify', + about: '', + }, + [BlockEnum.DirectAnswer]: { + author: 'Dify', + about: '', + }, + [BlockEnum.LLM]: { + author: 'Dify', + about: '', + }, + [BlockEnum.KnowledgeRetrieval]: { + author: 'Dify', + about: '', + }, + [BlockEnum.IfElse]: { + author: 'Dify', + about: '', + }, + [BlockEnum.Code]: { + author: 'Dify', + about: '', + }, + [BlockEnum.TemplateTransform]: { + author: 'Dify', + about: '', + }, + [BlockEnum.QuestionClassifier]: { + author: 'Dify', + about: '', + }, + [BlockEnum.HttpRequest]: { + author: 'Dify', + about: '', + }, + [BlockEnum.VariableAssigner]: { + author: 'Dify', + about: '', + }, + [BlockEnum.Tool]: { + author: 'Dify', + about: '', + }, +} + export const NODES_INITIAL_DATA = { [BlockEnum.Start]: { - _author: 'Dify', type: BlockEnum.Start, title: '', desc: '', ...StartNodeDefault.defaultValue, }, [BlockEnum.End]: { - _author: 'Dify', type: BlockEnum.End, title: '', desc: '', ...EndNodeDefault.defaultValue, }, [BlockEnum.DirectAnswer]: { - _author: 'Dify', type: BlockEnum.DirectAnswer, title: '', desc: '', ...DirectAnswerDefault.defaultValue, }, [BlockEnum.LLM]: { - _author: 'Dify', type: BlockEnum.LLM, title: '', desc: '', @@ -43,7 +90,6 @@ export const NODES_INITIAL_DATA = { ...LLMDefault.defaultValue, }, [BlockEnum.KnowledgeRetrieval]: { - _author: 'Dify', type: BlockEnum.KnowledgeRetrieval, title: '', desc: '', @@ -53,14 +99,12 @@ export const NODES_INITIAL_DATA = { ...KnowledgeRetrievalDefault.defaultValue, }, [BlockEnum.IfElse]: { - _author: 'Dify', type: BlockEnum.IfElse, title: '', desc: '', ...IfElseDefault.defaultValue, }, [BlockEnum.Code]: { - _author: 'Dify', type: BlockEnum.Code, title: '', desc: '', @@ -71,7 +115,6 @@ export const NODES_INITIAL_DATA = { ...CodeDefault.defaultValue, }, [BlockEnum.TemplateTransform]: { - _author: 'Dify', type: BlockEnum.TemplateTransform, title: '', desc: '', @@ -80,7 +123,6 @@ export const NODES_INITIAL_DATA = { ...TemplateTransformDefault.defaultValue, }, [BlockEnum.QuestionClassifier]: { - _author: 'Dify', type: BlockEnum.QuestionClassifier, title: '', desc: '', @@ -89,7 +131,6 @@ export const NODES_INITIAL_DATA = { ...QuestionClassifierDefault.defaultValue, }, [BlockEnum.HttpRequest]: { - _author: 'Dify', type: BlockEnum.HttpRequest, title: '', desc: '', @@ -97,7 +138,6 @@ export const NODES_INITIAL_DATA = { ...HttpRequestDefault.defaultValue, }, [BlockEnum.VariableAssigner]: { - _author: 'Dify', type: BlockEnum.VariableAssigner, title: '', desc: '', diff --git a/web/app/components/workflow/hooks.ts b/web/app/components/workflow/hooks.ts index 204be61405..44343c32c4 100644 --- a/web/app/components/workflow/hooks.ts +++ b/web/app/components/workflow/hooks.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import produce from 'immer' -import { debounce } from 'lodash-es' +import { useDebounceFn } from 'ahooks' import type { EdgeMouseHandler, NodeDragHandler, @@ -21,7 +21,10 @@ import type { BlockEnum, Node, } from './types' -import { NODES_INITIAL_DATA } from './constants' +import { + NODES_EXTRA_DATA, + NODES_INITIAL_DATA, +} from './constants' import { getLayoutByDagre } from './utils' import { useStore } from './store' import type { ToolDefaultValue } from './block-selector/types' @@ -45,13 +48,23 @@ export const useNodesInitialData = () => { }) } +export const useNodesExtraData = () => { + const { t } = useTranslation() + + return produce(NODES_EXTRA_DATA, (draft) => { + Object.keys(draft).forEach((key) => { + draft[key as BlockEnum].about = t(`workflow.blocksAbout.${key}`) + }) + }) +} + export const useWorkflow = () => { const store = useStoreApi() const reactFlow = useReactFlow() const nodesInitialData = useNodesInitialData() const featuresStore = useFeaturesStore() - const handleSyncWorkflowDraft = useCallback(debounce(() => { + const shouldDebouncedSyncWorkflowDraft = () => { const { getNodes, edges, @@ -84,7 +97,12 @@ export const useWorkflow = () => { useStore.setState({ draftUpdatedAt: res.updated_at }) }) } - }, 2000, { trailing: true }), [store, reactFlow, featuresStore]) + } + + const { run: handleSyncWorkflowDraft } = useDebounceFn(shouldDebouncedSyncWorkflowDraft, { + wait: 2000, + trailing: true, + }) const handleLayout = useCallback(async () => { const { @@ -203,17 +221,17 @@ export const useWorkflow = () => { } = store.getState() const nodes = getNodes() - const selectedNode = nodes.find(node => node.data._selected) + const selectedNode = nodes.find(node => node.data.selected) if (!cancelSelection && selectedNode?.id === nodeId) return const newNodes = produce(getNodes(), (draft) => { - draft.forEach(node => node.data._selected = false) + draft.forEach(node => node.data.selected = false) const selectedNode = draft.find(node => node.id === nodeId)! if (!cancelSelection) - selectedNode.data._selected = true + selectedNode.data.selected = true }) setNodes(newNodes) handleSyncWorkflowDraft() @@ -312,7 +330,7 @@ export const useWorkflow = () => { data: { ...nodesInitialData[nodeType], ...(toolDefaultValue || {}), - _selected: true, + selected: true, }, position: { x: currentNode.position.x + 304, @@ -330,7 +348,7 @@ export const useWorkflow = () => { } const newNodes = produce(nodes, (draft) => { draft.forEach((node) => { - node.data._selected = false + node.data.selected = false }) draft.push(nextNode) }) @@ -364,7 +382,7 @@ export const useWorkflow = () => { data: { ...nodesInitialData[nodeType], ...(toolDefaultValue || {}), - _selected: currentNode.data._selected, + selected: currentNode.data.selected, }, position: { x: currentNode.position.x, diff --git a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx index 28abc616f3..c892fb6a73 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx @@ -40,7 +40,7 @@ const NextStep = ({
diff --git a/web/app/components/workflow/nodes/_base/components/next-step/item.tsx b/web/app/components/workflow/nodes/_base/components/next-step/item.tsx index 3f0a4ec93c..82630a83fd 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/item.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/item.tsx @@ -55,7 +55,7 @@ const Item = ({ }
{data.title}
diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx index 7bcf397319..289c4e5380 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx @@ -1,11 +1,20 @@ import { memo, + useCallback, + useEffect, + useMemo, useState, } from 'react' +import produce from 'immer' +import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { useEdges } from 'reactflow' import ChangeBlock from './change-block' -import { useWorkflow } from '@/app/components/workflow/hooks' +import { useStore } from '@/app/components/workflow/store' +import { + useNodesExtraData, + useWorkflow, +} from '@/app/components/workflow/hooks' import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, @@ -14,6 +23,12 @@ import { } from '@/app/components/base/portal-to-follow-elem' import type { Node } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import { + fetchBuiltInToolList, + fetchCustomToolList, +} from '@/service/tools' type PanelOperatorProps = { id: string @@ -24,12 +39,53 @@ const PanelOperator = ({ data, }: PanelOperatorProps) => { const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) const edges = useEdges() const { handleNodeDelete } = useWorkflow() + const nodesExtraData = useNodesExtraData() + const toolsets = useStore(s => s.toolsets) + const toolsMap = useStore(s => s.toolsMap) + const setToolsMap = useStore(s => s.setToolsMap) const [open, setOpen] = useState(false) + const fetchToolList = useMemo(() => { + const toolset = toolsets.find(toolset => toolset.id === data.provider_id) + return toolset?.type === 'api' ? fetchCustomToolList : fetchBuiltInToolList + }, [toolsets, data.provider_id]) + + const handleGetAbout = useCallback(() => { + if (data.provider_id && !toolsMap[data.provider_id]?.length) { + fetchToolList(data.provider_id).then((list: any) => { + setToolsMap(produce(toolsMap, (draft) => { + draft[data.provider_id as string] = list + })) + }) + } + }, [data, toolsMap, fetchToolList, setToolsMap]) + useEffect(() => { + handleGetAbout() + }, [handleGetAbout]) const edge = edges.find(edge => edge.target === id) + const author = useMemo(() => { + if (data.type !== BlockEnum.Tool) + return nodesExtraData[data.type].author + + const toolset = toolsets.find(toolset => toolset.id === data.provider_id) + + return toolset?.author + }, [data, nodesExtraData, toolsets]) + + const about = useMemo(() => { + if (data.type !== BlockEnum.Tool) + return nodesExtraData[data.type].about + + const tool = toolsMap[data.provider_id as string]?.find(tool => tool.name === data.tool_name) + + return tool?.description[language] || '' + }, [data, nodesExtraData, toolsMap, language]) + return ( {t('workflow.panel.about')}
-
{data._about}
+
{about}
- {t('workflow.panel.createdBy')} {data._author} + {t('workflow.panel.createdBy')} {author}
diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index 2f1391c8d8..8fa6f3827e 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -29,7 +29,7 @@ const BaseNode: FC = ({
= ({ className='shrink-0 mr-2' type={data.type} size='md' - icon={data._icon} + toolProviderId={data.provider_id} />
= ({ { const isChatMode = useIsChatMode() const runTaskId = useStore(state => state.runTaskId) const nodes = useNodes() - const selectedNode = nodes.find(node => node.data._selected) + const selectedNode = nodes.find(node => node.data.selected) const showRunHistory = useStore(state => state.showRunHistory) const { showWorkflowInfoPanel, diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 05fa3be68d..87e3409f94 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -2,7 +2,6 @@ import type { Edge as ReactFlowEdge, Node as ReactFlowNode, } from 'reactflow' -import type { Collection } from '@/app/components/tools/types' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' export enum BlockEnum { @@ -26,16 +25,13 @@ export type Branch = { } export type CommonNodeType = { - _selected?: boolean _targetBranches?: Branch[] _isSingleRun?: boolean - _icon?: Collection['icon'] - _about?: string - _author?: string + selected?: boolean title: string desc: string type: BlockEnum -} & T +} & T & Partial> export type CommonEdgeType = { _hovering: boolean diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index bebb5cac6b..16067ca28a 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -50,6 +50,19 @@ const translation = { 'http-request': 'HTTP Request', 'variable-assigner': 'Variable Assigner', }, + blocksAbout: { + 'start': 'Define the initial parameters for launching a workflow', + 'end': 'Define the end and result type of a workflow', + 'direct-answer': 'Specify a custom text reply', + 'llm': 'Invoking large language models to answer questions or process natural language', + 'knowledge-retrieval': 'Allows you to query text content related to user questions from the Knowledge', + 'question-classifier': 'Define the classification conditions of user questions, LLM can define how the conversation progresses based on the classification description', + 'if-else': 'Allows you to split the workflow into two branches based on if/else conditions', + 'code': 'Execute a piece of Python or NodeJS code to implement custom logic', + 'template-transform': 'Convert data to string using Jinja template syntax', + 'http-request': 'Allow server requests to be sent over the HTTP protocol', + 'variable-assigner': 'Assign variables in different branches to the same variable to achieve unified configuration of post-nodes', + }, operator: { zoomIn: 'Zoom In', zoomOut: 'Zoom Out', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 4826fe36d5..37c7072f88 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -50,6 +50,19 @@ const translation = { 'http-request': 'HTTP 请求', 'variable-assigner': '变量赋值', }, + blocksAbout: { + 'start': '定义一个 workflow 流程启动的初始参数', + 'end': '定义一个 workflow 流程的结束和结果类型', + 'direct-answer': '指定一段自定义的文本回复', + 'llm': '调用大语言模型回答问题或者对自然语言进行处理', + 'knowledge-retrieval': '允许你从知识库中查询与用户问题相关的文本内容', + 'question-classifier': '定义用户问题的分类条件,LLM 能够根据分类描述定义对话的进展方式', + 'if-else': '允许你根据 if/else 条件将 workflow 拆分成两个分支', + 'code': '执行一段 Python 或 NodeJS 代码实现自定义逻辑', + 'template-transform': '使用 Jinja 模板语法将数据转换为字符串', + 'http-request': '允许通过 HTTP 协议发送服务器请求', + 'variable-assigner': '将不同分支中的变量指派给同一个变量,以实现后置节点统一配置', + }, operator: { zoomIn: '放大', zoomOut: '缩小',