mirror of
https://github.com/langgenius/dify.git
synced 2026-04-30 13:37:24 +08:00
feat: add adn update condition
This commit is contained in:
parent
4519c6ab29
commit
0fb47fed9e
@ -0,0 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
text: string
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddButton: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
text,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(className, 'flex items-center h-7 justify-center bg-gray-100 rounded-lg cursor-pointer text-xs font-medium text-gray-700 space-x-1')}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Plus className='w-3.5 h-3.5' />
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(AddButton)
|
||||||
@ -22,8 +22,8 @@ const Filed: FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={cn(inline && 'flex justify-between items-center')}>
|
<div className={cn(inline && 'flex justify-between items-center')}>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center h-6'>
|
||||||
<div className='h-6 text-xs font-medium text-gray-500 uppercase'>{title}</div>
|
<div className='text-xs font-medium text-gray-700 uppercase'>{title}</div>
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
<TooltipPlus popupContent='tooltip'>
|
<TooltipPlus popupContent='tooltip'>
|
||||||
<HelpCircle className='w-3.5 h-3.5 ml-0.5 text-gray-400' />
|
<HelpCircle className='w-3.5 h-3.5 ml-0.5 text-gray-400' />
|
||||||
|
|||||||
@ -0,0 +1,113 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import VarReferencePicker from '../../_base/components/variable/var-reference-picker'
|
||||||
|
import type { Condition } from '@/app/components/workflow/nodes/if-else/types'
|
||||||
|
import { LogicalOperator } from '@/app/components/workflow/nodes/if-else/types'
|
||||||
|
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||||
|
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
import { RefreshCw05 } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||||
|
const i18nPrefix = 'workflow.nodes.ifElse'
|
||||||
|
|
||||||
|
const Line = (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="163" height="2" viewBox="0 0 163 2" fill="none">
|
||||||
|
<path d="M0 1H162.5" stroke="url(#paint0_linear_641_36452)" />
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_641_36452" x1="162.5" y1="9.99584" x2="6.6086e-06" y2="9.94317" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stopColor="#F3F4F6" />
|
||||||
|
<stop offset="1" stopColor="#F3F4F6" stopOpacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
readonly: boolean
|
||||||
|
payload: Condition
|
||||||
|
onChange: (newItem: Condition) => void
|
||||||
|
canRemove: boolean
|
||||||
|
onRemove?: () => void
|
||||||
|
isShowLogicalOperator?: boolean
|
||||||
|
logicalOperator: LogicalOperator
|
||||||
|
onLogicalOperatorToggle: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item: FC<ItemProps> = ({
|
||||||
|
readonly,
|
||||||
|
payload,
|
||||||
|
onChange,
|
||||||
|
canRemove,
|
||||||
|
onRemove = () => { },
|
||||||
|
isShowLogicalOperator,
|
||||||
|
logicalOperator,
|
||||||
|
onLogicalOperatorToggle,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const handleVarReferenceChange = useCallback((value: ValueSelector) => {
|
||||||
|
onChange({
|
||||||
|
...payload,
|
||||||
|
variable_selector: value,
|
||||||
|
})
|
||||||
|
}, [onChange, payload])
|
||||||
|
|
||||||
|
const handleValueChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange({
|
||||||
|
...payload,
|
||||||
|
value: e.target.value,
|
||||||
|
})
|
||||||
|
}, [onChange, payload])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='space-y-2'>
|
||||||
|
{isShowLogicalOperator && (
|
||||||
|
<div className='flex items-center select-none'>
|
||||||
|
<div className='flex items-center '>
|
||||||
|
{Line}
|
||||||
|
<div
|
||||||
|
className='shrink-0 mx-1 flex items-center h-[22px] pl-2 pr-1.5 border border-gray-200 rounded-lg bg-white shadow-xs space-x-0.5 text-primary-600 cursor-pointer'
|
||||||
|
onClick={onLogicalOperatorToggle}
|
||||||
|
>
|
||||||
|
<div className='text-xs font-semibold uppercase'>{t(`${i18nPrefix}.${logicalOperator === LogicalOperator.and ? 'and' : 'or'}`)}</div>
|
||||||
|
<RefreshCw05 className='w-3 h-3' />
|
||||||
|
</div>
|
||||||
|
<div className=' rotate-180'>
|
||||||
|
{Line}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className='flex items-center space-x-1'>
|
||||||
|
<VarReferencePicker
|
||||||
|
readonly={readonly}
|
||||||
|
isShowNodeName
|
||||||
|
className='grow'
|
||||||
|
value={payload.variable_selector}
|
||||||
|
onChange={handleVarReferenceChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
readOnly={readonly}
|
||||||
|
value={payload.value}
|
||||||
|
onChange={handleValueChange}
|
||||||
|
placeholder={t(`${i18nPrefix}.enterValue`)!}
|
||||||
|
className='w-[144px] h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(canRemove ? 'text-gray-500 bg-gray-100 hover:bg-gray-200 cursor-pointer' : 'bg-gray-25 text-gray-300', 'p-2 rounded-lg ')}
|
||||||
|
onClick={canRemove ? onRemove : () => { }}
|
||||||
|
>
|
||||||
|
<Trash03 className='w-4 h-4 ' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Item)
|
||||||
@ -2,84 +2,26 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import VarReferencePicker from '../../_base/components/variable/var-reference-picker'
|
import Item from './condition-item'
|
||||||
import type { Condition } from '@/app/components/workflow/nodes/if-else/types'
|
import type { Condition, LogicalOperator } from '@/app/components/workflow/nodes/if-else/types'
|
||||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
|
||||||
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
const i18nPrefix = 'workflow.nodes.ifElse'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
className?: string
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
list: Condition[]
|
list: Condition[]
|
||||||
onChange: (newList: Condition[]) => void
|
onChange: (newList: Condition[]) => void
|
||||||
}
|
logicalOperator: LogicalOperator
|
||||||
|
onLogicalOperatorToggle: () => void
|
||||||
type ItemProps = {
|
|
||||||
readonly: boolean
|
|
||||||
payload: Condition
|
|
||||||
onChange: (newItem: Condition) => void
|
|
||||||
canRemove: boolean
|
|
||||||
onRemove?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const Item: FC<ItemProps> = ({
|
|
||||||
readonly,
|
|
||||||
payload,
|
|
||||||
onChange,
|
|
||||||
canRemove,
|
|
||||||
onRemove = () => { },
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const handleVarReferenceChange = useCallback((value: ValueSelector) => {
|
|
||||||
onChange({
|
|
||||||
...payload,
|
|
||||||
variable_selector: value,
|
|
||||||
})
|
|
||||||
}, [onChange, payload])
|
|
||||||
|
|
||||||
const handleValueChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
onChange({
|
|
||||||
...payload,
|
|
||||||
value: e.target.value,
|
|
||||||
})
|
|
||||||
}, [onChange, payload])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex items-center space-x-1'>
|
|
||||||
<VarReferencePicker
|
|
||||||
readonly={readonly}
|
|
||||||
isShowNodeName
|
|
||||||
className='grow'
|
|
||||||
value={payload.variable_selector}
|
|
||||||
onChange={handleVarReferenceChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
readOnly={readonly}
|
|
||||||
value={payload.value}
|
|
||||||
onChange={handleValueChange}
|
|
||||||
placeholder={t(`${i18nPrefix}.enterValue`)!}
|
|
||||||
className='w-[144px] h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
|
||||||
type='text'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cn(canRemove ? 'text-gray-500 bg-gray-100 hover:bg-gray-200 cursor-pointer' : 'bg-gray-25 text-gray-300', 'p-2 rounded-lg ')}
|
|
||||||
onClick={onRemove}
|
|
||||||
>
|
|
||||||
<Trash03 className='w-4 h-4 ' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConditionList: FC<Props> = ({
|
const ConditionList: FC<Props> = ({
|
||||||
|
className,
|
||||||
readonly,
|
readonly,
|
||||||
list,
|
list,
|
||||||
onChange,
|
onChange,
|
||||||
|
logicalOperator,
|
||||||
|
onLogicalOperatorToggle,
|
||||||
}) => {
|
}) => {
|
||||||
const handleItemChange = useCallback((index: number) => {
|
const handleItemChange = useCallback((index: number) => {
|
||||||
return (newItem: Condition) => {
|
return (newItem: Condition) => {
|
||||||
@ -99,37 +41,37 @@ const ConditionList: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}, [list, onChange])
|
}, [list, onChange])
|
||||||
|
|
||||||
|
const canRemove = list.length > 1
|
||||||
|
|
||||||
if (list.length === 0)
|
if (list.length === 0)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={cn(className, 'space-y-2')}>
|
||||||
<Item
|
<Item
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
payload={list[0]}
|
payload={list[0]}
|
||||||
onChange={handleItemChange(0)}
|
onChange={handleItemChange(0)}
|
||||||
canRemove={false}
|
canRemove={canRemove}
|
||||||
|
onRemove={handleItemRemove(0)}
|
||||||
|
logicalOperator={logicalOperator}
|
||||||
|
onLogicalOperatorToggle={onLogicalOperatorToggle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
list.length > 1 && (
|
list.length > 1 && (
|
||||||
<>
|
list.slice(1).map((item, i) => (
|
||||||
<div className='flex items-center justify-center h-6 text-gray-500'>
|
<Item
|
||||||
AND
|
key={item.id}
|
||||||
</div>
|
readonly={readonly}
|
||||||
{
|
payload={item}
|
||||||
list.slice(1).map((item, i) => (
|
onChange={handleItemChange(i + 1)}
|
||||||
<Item
|
canRemove={canRemove}
|
||||||
key={item.id}
|
onRemove={handleItemRemove(i + 1)}
|
||||||
readonly={readonly}
|
isShowLogicalOperator
|
||||||
payload={item}
|
logicalOperator={logicalOperator}
|
||||||
onChange={handleItemChange(i + 1)}
|
onLogicalOperatorToggle={onLogicalOperatorToggle}
|
||||||
canRemove
|
/>
|
||||||
onRemove={handleItemRemove(i + 1)}
|
)))
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</>)
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Split from '../_base/components/split'
|
||||||
|
import AddButton from '../_base/components/add-button'
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import { mockData } from './mock'
|
import { mockData } from './mock'
|
||||||
import ConditionList from './components/condition-list'
|
import ConditionList from './components/condition-list'
|
||||||
@ -14,18 +16,35 @@ const Panel: FC = () => {
|
|||||||
inputs,
|
inputs,
|
||||||
handleConditionsChange,
|
handleConditionsChange,
|
||||||
handleAddCondition,
|
handleAddCondition,
|
||||||
|
handleLogicalOperatorToggle,
|
||||||
} = useConfig(mockData)
|
} = useConfig(mockData)
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='mt-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 pb-4 space-y-4'>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.conditions`)}
|
title={t(`${i18nPrefix}.if`)}
|
||||||
>
|
>
|
||||||
<ConditionList
|
<>
|
||||||
readonly={readOnly}
|
<ConditionList
|
||||||
list={inputs.conditions}
|
className='mt-2'
|
||||||
onChange={handleConditionsChange}
|
readonly={readOnly}
|
||||||
/>
|
list={inputs.conditions}
|
||||||
|
onChange={handleConditionsChange}
|
||||||
|
logicalOperator={inputs.logical_operator}
|
||||||
|
onLogicalOperatorToggle={handleLogicalOperatorToggle}
|
||||||
|
/>
|
||||||
|
<AddButton
|
||||||
|
className='mt-3'
|
||||||
|
text={t(`${i18nPrefix}.addCondition`)}
|
||||||
|
onClick={handleAddCondition}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
</Field>
|
||||||
|
<Split />
|
||||||
|
<Field
|
||||||
|
title={t(`${i18nPrefix}.else`)}
|
||||||
|
>
|
||||||
|
<div className='leading-[18px] text-xs font-normal text-gray-400'>{t(`${i18nPrefix}.elseDescription`)}</div>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import type { Condition, IfElseNodeType, LogicalOperator } from './types'
|
import { ComparisonOperator, LogicalOperator } from './types'
|
||||||
|
import type { Condition, IfElseNodeType } from './types'
|
||||||
|
|
||||||
const useConfig = (initInputs: IfElseNodeType) => {
|
const useConfig = (initInputs: IfElseNodeType) => {
|
||||||
const [inputs, setInputs] = useState<IfElseNodeType>(initInputs)
|
const [inputs, setInputs] = useState<IfElseNodeType>(initInputs)
|
||||||
@ -14,16 +15,21 @@ const useConfig = (initInputs: IfElseNodeType) => {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleAddCondition = useCallback((condition: Condition) => {
|
const handleAddCondition = useCallback(() => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.conditions.push(condition)
|
draft.conditions.push({
|
||||||
|
id: `${Date.now()}`,
|
||||||
|
variable_selector: [],
|
||||||
|
comparison_operator: ComparisonOperator.equal,
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs])
|
}, [inputs])
|
||||||
|
|
||||||
const handleLogicalOperatorChange = useCallback((newOperator: LogicalOperator) => {
|
const handleLogicalOperatorToggle = useCallback(() => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.logical_operator = newOperator
|
draft.logical_operator = draft.logical_operator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs])
|
}, [inputs])
|
||||||
@ -32,7 +38,7 @@ const useConfig = (initInputs: IfElseNodeType) => {
|
|||||||
inputs,
|
inputs,
|
||||||
handleConditionsChange,
|
handleConditionsChange,
|
||||||
handleAddCondition,
|
handleAddCondition,
|
||||||
handleLogicalOperatorChange,
|
handleLogicalOperatorToggle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,6 +72,9 @@ const translation = {
|
|||||||
},
|
},
|
||||||
ifElse: {
|
ifElse: {
|
||||||
conditions: 'Conditions',
|
conditions: 'Conditions',
|
||||||
|
if: 'If',
|
||||||
|
else: 'Else',
|
||||||
|
elseDescription: 'Used to define the logic that should be executed when the if condition is not met.',
|
||||||
and: 'and',
|
and: 'and',
|
||||||
or: 'or',
|
or: 'or',
|
||||||
comparisonOperator: {
|
comparisonOperator: {
|
||||||
@ -87,6 +90,7 @@ const translation = {
|
|||||||
'not null': 'is not null',
|
'not null': 'is not null',
|
||||||
},
|
},
|
||||||
enterValue: 'Enter value',
|
enterValue: 'Enter value',
|
||||||
|
addCondition: 'Add Condition',
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Assign variables',
|
title: 'Assign variables',
|
||||||
|
|||||||
@ -71,6 +71,9 @@ const translation = {
|
|||||||
},
|
},
|
||||||
ifElse: {
|
ifElse: {
|
||||||
conditions: '条件',
|
conditions: '条件',
|
||||||
|
if: 'If',
|
||||||
|
else: 'Else',
|
||||||
|
elseDescription: '用于定义当 if 条件不满足时应执行的逻辑。',
|
||||||
and: 'and',
|
and: 'and',
|
||||||
or: 'or',
|
or: 'or',
|
||||||
comparisonOperator: {
|
comparisonOperator: {
|
||||||
@ -86,6 +89,7 @@ const translation = {
|
|||||||
'not null': '不为空',
|
'not null': '不为空',
|
||||||
},
|
},
|
||||||
enterValue: '输入值',
|
enterValue: '输入值',
|
||||||
|
addCondition: '添加条件',
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: '变量赋值',
|
title: '变量赋值',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user