diff --git a/web/app/components/workflow/block-selector/constants.tsx b/web/app/components/workflow/block-selector/constants.tsx index 3b227a9853..de3efdd0eb 100644 --- a/web/app/components/workflow/block-selector/constants.tsx +++ b/web/app/components/workflow/block-selector/constants.tsx @@ -59,6 +59,11 @@ export const BLOCKS: Block[] = [ type: BlockEnum.VariableAggregator, title: 'Variable Aggregator', }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.DocExtractor, + title: 'Doc Extractor', + }, { classification: BlockClassificationEnum.Transform, type: BlockEnum.ParameterExtractor, @@ -71,8 +76,8 @@ export const BLOCKS: Block[] = [ }, { classification: BlockClassificationEnum.Utilities, - type: BlockEnum.DocExtractor, - title: 'Doc Extractor', + type: BlockEnum.ListFilter, + title: 'List Filter', }, ] diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index eb615a6651..de72ce6401 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -15,6 +15,7 @@ import VariableAssignerDefault from './nodes/variable-assigner/default' import EndNodeDefault from './nodes/end/default' import IterationDefault from './nodes/iteration/default' import DocExtractorDefault from './nodes/doc-extractor/default' +import ListFilterDefault from './nodes/list-filter/default' type NodesExtraData = { author: string @@ -170,6 +171,15 @@ export const NODES_EXTRA_DATA: Record = { getAvailableNextNodes: DocExtractorDefault.getAvailableNextNodes, checkValid: DocExtractorDefault.checkValid, }, + [BlockEnum.ListFilter]: { + author: 'Dify', + about: '', + availablePrevNodes: [], + availableNextNodes: [], + getAvailablePrevNodes: ListFilterDefault.getAvailablePrevNodes, + getAvailableNextNodes: ListFilterDefault.getAvailableNextNodes, + checkValid: ListFilterDefault.checkValid, + }, } @@ -291,6 +301,12 @@ export const NODES_INITIAL_DATA = { desc: '', ...DocExtractorDefault.defaultValue, }, + [BlockEnum.ListFilter]: { + type: BlockEnum.ListFilter, + title: '', + desc: '', + ...ListFilterDefault.defaultValue, + }, } export const NODE_WIDTH = 240 diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 9e99792085..f1a93f9988 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -245,6 +245,24 @@ const formatItem = ( break } + case BlockEnum.ListFilter: { + res.vars = [ + { + variable: 'result', + type: VarType.array, // TODO dyn value + }, + { + variable: 'first_record', + type: VarType.string, // TODO dyn value + }, + { + variable: 'last_record', + type: VarType.string, // TODO dyn value + }, + ] + break + } + case 'env': { res.vars = data.envList.map((env: EnvironmentVariable) => { return { diff --git a/web/app/components/workflow/nodes/constants.ts b/web/app/components/workflow/nodes/constants.ts index bed2d6069b..b983110d39 100644 --- a/web/app/components/workflow/nodes/constants.ts +++ b/web/app/components/workflow/nodes/constants.ts @@ -30,6 +30,8 @@ import IterationNode from './iteration/node' import IterationPanel from './iteration/panel' import DocExtractorNode from './doc-extractor/node' import DocExtractorPanel from './doc-extractor/panel' +import ListFilterNode from './list-filter/node' +import ListFilterPanel from './list-filter/panel' export const NodeComponentMap: Record> = { [BlockEnum.Start]: StartNode, @@ -48,6 +50,7 @@ export const NodeComponentMap: Record> = { [BlockEnum.ParameterExtractor]: ParameterExtractorNode, [BlockEnum.Iteration]: IterationNode, [BlockEnum.DocExtractor]: DocExtractorNode, + [BlockEnum.ListFilter]: ListFilterNode, } export const PanelComponentMap: Record> = { @@ -67,6 +70,7 @@ export const PanelComponentMap: Record> = { [BlockEnum.ParameterExtractor]: ParameterExtractorPanel, [BlockEnum.Iteration]: IterationPanel, [BlockEnum.DocExtractor]: DocExtractorPanel, + [BlockEnum.ListFilter]: ListFilterPanel, } export const CUSTOM_NODE_TYPE = 'custom' diff --git a/web/app/components/workflow/nodes/list-filter/default.ts b/web/app/components/workflow/nodes/list-filter/default.ts new file mode 100644 index 0000000000..5fe4b44a8d --- /dev/null +++ b/web/app/components/workflow/nodes/list-filter/default.ts @@ -0,0 +1,35 @@ +import { BlockEnum } from '../../types' +import type { NodeDefault } from '../../types' +import { type ListFilterNodeType } from './types' +import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants' +const i18nPrefix = 'workflow.errorMsg' + +const nodeDefault: NodeDefault = { + defaultValue: { + variable: [], + }, + getAvailablePrevNodes(isChatMode: boolean) { + const nodes = isChatMode + ? ALL_CHAT_AVAILABLE_BLOCKS + : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) + return nodes + }, + getAvailableNextNodes(isChatMode: boolean) { + const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS + return nodes + }, + checkValid(payload: ListFilterNodeType, t: any) { + let errorMessages = '' + const { variable } = payload + + if (!errorMessages && !variable?.length) + errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.assignedVariable') }) + + return { + isValid: !errorMessages, + errorMessage: errorMessages, + } + }, +} + +export default nodeDefault diff --git a/web/app/components/workflow/nodes/list-filter/node.tsx b/web/app/components/workflow/nodes/list-filter/node.tsx new file mode 100644 index 0000000000..8d0b5f7aae --- /dev/null +++ b/web/app/components/workflow/nodes/list-filter/node.tsx @@ -0,0 +1,40 @@ +import type { FC } from 'react' +import React from 'react' +import { useNodes } from 'reactflow' +import { useTranslation } from 'react-i18next' +import NodeVariableItem from '../variable-assigner/components/node-variable-item' +import { type ListFilterNodeType } from './types' +import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types' + +const i18nPrefix = 'workflow.nodes.assigner' + +const NodeComponent: FC> = ({ + data, +}) => { + const { t } = useTranslation() + + const nodes: Node[] = useNodes() + const { variable } = data + + if (!variable || variable.length === 0) + return null + + const isSystem = isSystemVar(variable) + const isEnv = isENV(variable) + const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) + const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') + return ( +
+
{t(`${i18nPrefix}.assignedVariable`)}
+ +
+ ) +} + +export default React.memo(NodeComponent) diff --git a/web/app/components/workflow/nodes/list-filter/panel.tsx b/web/app/components/workflow/nodes/list-filter/panel.tsx new file mode 100644 index 0000000000..f1da053b9e --- /dev/null +++ b/web/app/components/workflow/nodes/list-filter/panel.tsx @@ -0,0 +1,45 @@ +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import VarReferencePicker from '../_base/components/variable/var-reference-picker' +import useConfig from './use-config' +import type { ListFilterNodeType } from './types' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { type NodePanelProps } from '@/app/components/workflow/types' + +const i18nPrefix = 'workflow.nodes.docExtractor' + +const Panel: FC> = ({ + id, + data, +}) => { + const { t } = useTranslation() + + const { + readOnly, + inputs, + handleVarChanges, + filterVar, + } = useConfig(id, data) + + return ( +
+
+ + + +
+
+ ) +} + +export default React.memo(Panel) diff --git a/web/app/components/workflow/nodes/list-filter/types.ts b/web/app/components/workflow/nodes/list-filter/types.ts new file mode 100644 index 0000000000..cf0f1cac9b --- /dev/null +++ b/web/app/components/workflow/nodes/list-filter/types.ts @@ -0,0 +1,5 @@ +import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types' + +export type ListFilterNodeType = CommonNodeType & { + variable: ValueSelector +} diff --git a/web/app/components/workflow/nodes/list-filter/use-config.ts b/web/app/components/workflow/nodes/list-filter/use-config.ts new file mode 100644 index 0000000000..0dbf429057 --- /dev/null +++ b/web/app/components/workflow/nodes/list-filter/use-config.ts @@ -0,0 +1,35 @@ +import { useCallback } from 'react' +import produce from 'immer' +import type { ValueSelector, Var } from '../../types' +import { VarType } from '../../types' +import { type ListFilterNodeType } from './types' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { + useNodesReadOnly, +} from '@/app/components/workflow/hooks' + +const useConfig = (id: string, payload: ListFilterNodeType) => { + const { nodesReadOnly: readOnly } = useNodesReadOnly() + + const { inputs, setInputs } = useNodeCrud(id, payload) + + const handleVarChanges = useCallback((variable: ValueSelector | string) => { + const newInputs = produce(inputs, (draft) => { + draft.variable = variable as ValueSelector + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const filterVar = useCallback((varPayload: Var) => { + return varPayload.type !== VarType.file + }, []) + + return { + readOnly, + inputs, + filterVar, + handleVarChanges, + } +} + +export default useConfig diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index c416ca296d..9b6f538380 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -177,6 +177,7 @@ const translation = { 'iteration': 'Iteration', 'parameter-extractor': 'Parameter Extractor', 'doc-extractor': 'Doc Extractor', + 'list-filter': 'List Filter', }, blocksAbout: { 'start': 'Define the initial parameters for launching a workflow', @@ -193,7 +194,8 @@ const translation = { 'variable-aggregator': 'Aggregate multi-branch variables into a single variable for unified configuration of downstream nodes.', 'iteration': 'Perform multiple steps on a list object until all results are outputted.', 'parameter-extractor': 'Use LLM to extract structured parameters from natural language for tool invocations or HTTP requests.', - 'doc-extractor': 'TODO', + 'doc-extractor': 'doc-extractor TODO', + 'list-filter': 'List Filter TODO', }, operator: { zoomIn: 'Zoom In', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 648c7c6891..5b1574ac6c 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -176,6 +176,8 @@ const translation = { 'iteration-start': '迭代开始', 'iteration': '迭代', 'parameter-extractor': '参数提取器', + 'doc-extractor': '文档提取器', + 'list-filter': '列表过滤器', }, blocksAbout: { 'start': '定义一个 workflow 流程启动的初始参数', @@ -192,6 +194,8 @@ const translation = { 'variable-aggregator': '将多路分支的变量聚合为一个变量,以实现下游节点统一配置。', 'iteration': '对列表对象执行多次步骤直至输出所有结果。', 'parameter-extractor': '利用 LLM 从自然语言内推理提取出结构化参数,用于后置的工具调用或 HTTP 请求。', + 'doc-extractor': 'doc-extractor TODO', + 'list-filter': 'List Filter TODO', }, operator: { zoomIn: '放大',