mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-20 00:49:05 +08:00
feat: list filter
This commit is contained in:
parent
6fafd410d2
commit
394f06a27a
@ -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',
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -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<BlockEnum, NodesExtraData> = {
|
||||
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
|
||||
|
@ -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 {
|
||||
|
@ -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<string, ComponentType<any>> = {
|
||||
[BlockEnum.Start]: StartNode,
|
||||
@ -48,6 +50,7 @@ export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.ParameterExtractor]: ParameterExtractorNode,
|
||||
[BlockEnum.Iteration]: IterationNode,
|
||||
[BlockEnum.DocExtractor]: DocExtractorNode,
|
||||
[BlockEnum.ListFilter]: ListFilterNode,
|
||||
}
|
||||
|
||||
export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
||||
@ -67,6 +70,7 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.ParameterExtractor]: ParameterExtractorPanel,
|
||||
[BlockEnum.Iteration]: IterationPanel,
|
||||
[BlockEnum.DocExtractor]: DocExtractorPanel,
|
||||
[BlockEnum.ListFilter]: ListFilterPanel,
|
||||
}
|
||||
|
||||
export const CUSTOM_NODE_TYPE = 'custom'
|
||||
|
35
web/app/components/workflow/nodes/list-filter/default.ts
Normal file
35
web/app/components/workflow/nodes/list-filter/default.ts
Normal file
@ -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<ListFilterNodeType> = {
|
||||
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
|
40
web/app/components/workflow/nodes/list-filter/node.tsx
Normal file
40
web/app/components/workflow/nodes/list-filter/node.tsx
Normal file
@ -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<NodeProps<ListFilterNodeType>> = ({
|
||||
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 (
|
||||
<div className='relative px-3'>
|
||||
<div className='mb-1 system-2xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.assignedVariable`)}</div>
|
||||
<NodeVariableItem
|
||||
node={node as Node}
|
||||
isEnv={isEnv}
|
||||
varName={varName}
|
||||
className='bg-workflow-block-parma-bg'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(NodeComponent)
|
45
web/app/components/workflow/nodes/list-filter/panel.tsx
Normal file
45
web/app/components/workflow/nodes/list-filter/panel.tsx
Normal file
@ -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<NodePanelProps<ListFilterNodeType>> = ({
|
||||
id,
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleVarChanges,
|
||||
filterVar,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.assignedVariable`)}
|
||||
>
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
nodeId={id}
|
||||
isShowNodeName
|
||||
value={inputs.variable || []}
|
||||
onChange={handleVarChanges}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Panel)
|
5
web/app/components/workflow/nodes/list-filter/types.ts
Normal file
5
web/app/components/workflow/nodes/list-filter/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
export type ListFilterNodeType = CommonNodeType & {
|
||||
variable: ValueSelector
|
||||
}
|
35
web/app/components/workflow/nodes/list-filter/use-config.ts
Normal file
35
web/app/components/workflow/nodes/list-filter/use-config.ts
Normal file
@ -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<ListFilterNodeType>(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
|
@ -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',
|
||||
|
@ -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: '放大',
|
||||
|
Loading…
x
Reference in New Issue
Block a user