FEAT: NEW WORKFLOW ENGINE (#3160)

Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Yeuoly <admin@srmxy.cn>
Co-authored-by: JzoNg <jzongcode@gmail.com>
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
Co-authored-by: jyong <jyong@dify.ai>
Co-authored-by: nite-knite <nkCoding@gmail.com>
Co-authored-by: jyong <718720800@qq.com>
This commit is contained in:
takatost
2024-04-08 18:51:46 +08:00
committed by GitHub
parent 2fb9850af5
commit 7753ba2d37
1161 changed files with 103836 additions and 10327 deletions

View File

@@ -0,0 +1,61 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import TextEditor from '@/app/components/workflow/nodes/_base/components/editor/text-editor'
import { LayoutGrid02 } from '@/app/components/base/icons/src/vender/line/layout'
const i18nPrefix = 'workflow.nodes.http'
type Props = {
value: string
onChange: (value: string) => void
onSwitchToKeyValueEdit: () => void
}
const BulkEdit: FC<Props> = ({
value,
onChange,
onSwitchToKeyValueEdit,
}) => {
const { t } = useTranslation()
const [tempValue, setTempValue] = React.useState(value)
const handleChange = useCallback((value: string) => {
setTempValue(value)
}, [])
const handleBlur = useCallback(() => {
onChange(tempValue)
}, [tempValue, onChange])
const handleSwitchToKeyValueEdit = useCallback(() => {
onChange(tempValue)
onSwitchToKeyValueEdit()
}, [tempValue, onChange, onSwitchToKeyValueEdit])
return (
<div>
<TextEditor
title={<div className='uppercase'>{t(`${i18nPrefix}.bulkEdit`)}</div>}
value={tempValue}
onChange={handleChange}
onBlur={handleBlur}
headerRight={
<div className='flex items-center h-[18px]'>
<div
className='flex items-center space-x-1 cursor-pointer'
onClick={handleSwitchToKeyValueEdit}
>
<LayoutGrid02 className='w-3 h-3 text-gray-500' />
<div className='leading-[18px] text-xs font-normal text-gray-500'>{t(`${i18nPrefix}.keyValueEdit`)}</div>
</div>
<div className='ml-3 mr-1.5 w-px h-3 bg-gray-200'></div>
</div>
}
minHeight={150}
/>
</div>
)
}
export default React.memo(BulkEdit)

View File

@@ -0,0 +1,59 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { KeyValue } from '../../types'
import KeyValueEdit from './key-value-edit'
type Props = {
readonly: boolean
nodeId: string
list: KeyValue[]
onChange: (newList: KeyValue[]) => void
onAdd: () => void
// toggleKeyValueEdit: () => void
}
const KeyValueList: FC<Props> = ({
readonly,
nodeId,
list,
onChange,
onAdd,
// toggleKeyValueEdit,
}) => {
// const handleBulkValueChange = useCallback((value: string) => {
// const newList = value.split('\n').map((item) => {
// const [key, value] = item.split(':')
// return {
// key: key ? key.trim() : '',
// value: value ? value.trim() : '',
// }
// })
// onChange(newList)
// }, [onChange])
// const bulkList = (() => {
// const res = list.map((item) => {
// if (!item.key && !item.value)
// return ''
// if (!item.value)
// return item.key
// return `${item.key}:${item.value}`
// }).join('\n')
// return res
// })()
return <KeyValueEdit
readonly={readonly}
nodeId={nodeId}
list={list}
onChange={onChange}
onAdd={onAdd}
// onSwitchToBulkEdit={toggleKeyValueEdit}
/>
// : <BulkEdit
// value={bulkList}
// onChange={handleBulkValueChange}
// onSwitchToKeyValueEdit={toggleKeyValueEdit}
// />
}
export default React.memo(KeyValueList)

View File

@@ -0,0 +1,88 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import produce from 'immer'
import { useTranslation } from 'react-i18next'
import type { KeyValue } from '../../../types'
import KeyValueItem from './item'
// import TooltipPlus from '@/app/components/base/tooltip-plus'
// import { EditList } from '@/app/components/base/icons/src/vender/solid/communication'
const i18nPrefix = 'workflow.nodes.http'
type Props = {
readonly: boolean
nodeId: string
list: KeyValue[]
onChange: (newList: KeyValue[]) => void
onAdd: () => void
// onSwitchToBulkEdit: () => void
}
const KeyValueList: FC<Props> = ({
readonly,
nodeId,
list,
onChange,
onAdd,
// onSwitchToBulkEdit,
}) => {
const { t } = useTranslation()
const handleChange = useCallback((index: number) => {
return (newItem: KeyValue) => {
const newList = produce(list, (draft: any) => {
draft[index] = newItem
})
onChange(newList)
}
}, [list, onChange])
const handleRemove = useCallback((index: number) => {
return () => {
const newList = produce(list, (draft: any) => {
draft.splice(index, 1)
})
onChange(newList)
}
}, [list, onChange])
return (
<div className='border border-gray-200 rounded-lg overflow-hidden'>
<div className='flex items-center h-7 leading-7 text-xs font-medium text-gray-500 uppercase'>
<div className='w-1/2 h-full pl-3 border-r border-gray-200'>{t(`${i18nPrefix}.key`)}</div>
<div className='flex w-1/2 h-full pl-3 pr-1 items-center justify-between'>
<div>{t(`${i18nPrefix}.value`)}</div>
{/* {!readonly && (
<TooltipPlus
popupContent={t(`${i18nPrefix}.bulkEdit`)}
>
<div
className='p-1 cursor-pointer rounded-md hover:bg-black/5 text-gray-500 hover:text-gray-800'
onClick={onSwitchToBulkEdit}
>
<EditList className='w-3 h-3' />
</div>
</TooltipPlus>)} */}
</div>
</div>
{
list.map((item, index) => (
<KeyValueItem
key={item.id}
instanceId={item.id!}
nodeId={nodeId}
payload={item}
onChange={handleChange(index)}
onRemove={handleRemove(index)}
isLastItem={index === list.length - 1}
onAdd={onAdd}
readonly={readonly}
canRemove={list.length > 1}
/>
))
}
</div>
)
}
export default React.memo(KeyValueList)

View File

@@ -0,0 +1,97 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import useAvailableVarList from '../../../../_base/hooks/use-available-var-list'
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
import type { Var } from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
type Props = {
className?: string
instanceId?: string
nodeId: string
value: string
onChange: (newValue: string) => void
hasRemove: boolean
onRemove?: () => void
placeholder?: string
readOnly?: boolean
}
const InputItem: FC<Props> = ({
className,
instanceId,
nodeId,
value,
onChange,
hasRemove,
onRemove,
placeholder,
readOnly,
}) => {
const { t } = useTranslation()
const hasValue = !!value
const [isFocus, setIsFocus] = useState(false)
const { availableVars, availableNodes } = useAvailableVarList(nodeId, {
onlyLeafNodeVar: false,
filterVar: (varPayload: Var) => {
return [VarType.string, VarType.number].includes(varPayload.type)
},
})
const handleRemove = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
onRemove?.()
}, [onRemove])
return (
<div className={cn(className, 'hover:bg-gray-50 hover:cursor-text', 'relative flex h-full items-center')}>
{(!readOnly)
? (
<Input
instanceId={instanceId}
className={cn(isFocus ? 'bg-gray-100' : 'bg-width', 'w-0 grow px-3 py-1')}
value={value}
onChange={onChange}
readOnly={readOnly}
nodesOutputVars={availableVars}
availableNodes={availableNodes}
onFocusChange={setIsFocus}
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
placeholderClassName='!leading-[21px]'
/>
)
: <div
className="pl-0.5 w-full h-[18px] leading-[18px]"
>
{!hasValue && <div className='text-gray-300 text-xs font-normal'>{placeholder}</div>}
{hasValue && (
<Input
instanceId={instanceId}
className={cn(isFocus ? 'shadow-xs bg-gray-50 border-gray-300' : 'bg-gray-100 border-gray-100', 'w-0 grow rounded-lg px-3 py-[6px] border')}
value={value}
onChange={onChange}
readOnly={readOnly}
nodesOutputVars={availableVars}
availableNodes={availableNodes}
onFocusChange={setIsFocus}
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
placeholderClassName='!leading-[21px]'
/>
)}
</div>}
{hasRemove && !isFocus && (
<RemoveButton
className='group-hover:block hidden absolute right-1 top-0.5'
onClick={handleRemove}
/>
)}
</div>
)
}
export default React.memo(InputItem)

View File

@@ -0,0 +1,79 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import produce from 'immer'
import type { KeyValue } from '../../../types'
import InputItem from './input-item'
const i18nPrefix = 'workflow.nodes.http'
type Props = {
instanceId: string
className?: string
nodeId: string
readonly: boolean
canRemove: boolean
payload: KeyValue
onChange: (newPayload: KeyValue) => void
onRemove: () => void
isLastItem: boolean
onAdd: () => void
}
const KeyValueItem: FC<Props> = ({
instanceId,
className,
nodeId,
readonly,
canRemove,
payload,
onChange,
onRemove,
isLastItem,
onAdd,
}) => {
const { t } = useTranslation()
const handleChange = useCallback((key: string) => {
return (value: string) => {
const newPayload = produce(payload, (draft: any) => {
draft[key] = value
})
onChange(newPayload)
if (key === 'value' && isLastItem)
onAdd()
}
}, [onChange, onAdd, isLastItem, payload])
return (
// group class name is for hover row show remove button
<div className={cn(className, 'group flex items-start h-min-7 border-t border-gray-200')}>
<div className='w-1/2 h-full border-r border-gray-200'>
<InputItem
instanceId={`http-key-${instanceId}`}
nodeId={nodeId}
value={payload.key}
onChange={handleChange('key')}
hasRemove={false}
placeholder={t(`${i18nPrefix}.key`)!}
readOnly={readonly}
/>
</div>
<div className='w-1/2 h-full'>
<InputItem
instanceId={`http-value-${instanceId}`}
nodeId={nodeId}
value={payload.value}
onChange={handleChange('value')}
hasRemove={!readonly && canRemove}
onRemove={onRemove}
placeholder={t(`${i18nPrefix}.value`)!}
readOnly={readonly}
/>
</div>
</div>
)
}
export default React.memo(KeyValueItem)