mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-24 10:13:01 +08:00
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:
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
|
||||
type Params = {
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
}
|
||||
const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar,
|
||||
filterVar,
|
||||
}: Params = {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
}) => {
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const availableNodes = onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId)
|
||||
const availableVars = toNodeOutputVars(availableNodes, isChatMode, filterVar)
|
||||
return {
|
||||
availableVars,
|
||||
availableNodes,
|
||||
}
|
||||
}
|
||||
|
||||
export default useAvailableVarList
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => {
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
|
||||
const setInputs = (newInputs: CommonNodeType<T>) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: newInputs,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
inputs: data,
|
||||
setInputs,
|
||||
}
|
||||
}
|
||||
|
||||
export default useNodeCrud
|
||||
@@ -0,0 +1,275 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { unionBy } from 'lodash-es'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { getNodeInfoById, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { singleNodeRun } from '@/service/workflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import LLMDefault from '@/app/components/workflow/nodes/llm/default'
|
||||
import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
||||
import IfElseDefault from '@/app/components/workflow/nodes/if-else/default'
|
||||
import CodeDefault from '@/app/components/workflow/nodes/code/default'
|
||||
import TemplateTransformDefault from '@/app/components/workflow/nodes/template-transform/default'
|
||||
import QuestionClassifyDefault from '@/app/components/workflow/nodes/question-classifier/default'
|
||||
import HTTPDefault from '@/app/components/workflow/nodes/http/default'
|
||||
import ToolDefault from '@/app/components/workflow/nodes/tool/default'
|
||||
import VariableAssigner from '@/app/components/workflow/nodes/variable-assigner/default'
|
||||
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
|
||||
const { checkValid: checkLLMValid } = LLMDefault
|
||||
const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
|
||||
const { checkValid: checkIfElseValid } = IfElseDefault
|
||||
const { checkValid: checkCodeValid } = CodeDefault
|
||||
const { checkValid: checkTemplateTransformValid } = TemplateTransformDefault
|
||||
const { checkValid: checkQuestionClassifyValid } = QuestionClassifyDefault
|
||||
const { checkValid: checkHttpValid } = HTTPDefault
|
||||
const { checkValid: checkToolValid } = ToolDefault
|
||||
const { checkValid: checkVariableAssignerValid } = VariableAssigner
|
||||
|
||||
const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.LLM]: checkLLMValid,
|
||||
[BlockEnum.KnowledgeRetrieval]: checkKnowledgeRetrievalValid,
|
||||
[BlockEnum.IfElse]: checkIfElseValid,
|
||||
[BlockEnum.Code]: checkCodeValid,
|
||||
[BlockEnum.TemplateTransform]: checkTemplateTransformValid,
|
||||
[BlockEnum.QuestionClassifier]: checkQuestionClassifyValid,
|
||||
[BlockEnum.HttpRequest]: checkHttpValid,
|
||||
[BlockEnum.Tool]: checkToolValid,
|
||||
[BlockEnum.VariableAssigner]: checkVariableAssignerValid,
|
||||
} as any
|
||||
|
||||
type Params<T> = {
|
||||
id: string
|
||||
data: CommonNodeType<T>
|
||||
defaultRunInputData: Record<string, any>
|
||||
moreDataForCheckValid?: any
|
||||
}
|
||||
|
||||
const varTypeToInputVarType = (type: VarType, {
|
||||
isSelect,
|
||||
isParagraph,
|
||||
}: {
|
||||
isSelect: boolean
|
||||
isParagraph: boolean
|
||||
}) => {
|
||||
if (isSelect)
|
||||
return InputVarType.select
|
||||
if (isParagraph)
|
||||
return InputVarType.paragraph
|
||||
if (type === VarType.number)
|
||||
return InputVarType.number
|
||||
if ([VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(type))
|
||||
return InputVarType.json
|
||||
if (type === VarType.arrayFile)
|
||||
return InputVarType.files
|
||||
|
||||
return InputVarType.textInput
|
||||
}
|
||||
|
||||
const useOneStepRun = <T>({
|
||||
id,
|
||||
data,
|
||||
defaultRunInputData,
|
||||
moreDataForCheckValid,
|
||||
}: Params<T>) => {
|
||||
const { t } = useTranslation()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow() as any
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||
const allOutputVars = toNodeOutputVars(getBeforeNodesInSameBranch(id), isChatMode)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
let res: Var | undefined
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
const targetVar = isSystem ? allOutputVars.find(item => !!item.isStartNode) : allOutputVars.find(v => v.nodeId === valueSelector[0])
|
||||
if (!targetVar)
|
||||
return undefined
|
||||
if (isSystem)
|
||||
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
valueSelector.slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
res = curr
|
||||
}
|
||||
else {
|
||||
if (curr.type === VarType.object)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const checkValid = checkValidFns[data.type]
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
||||
const [runResult, setRunResult] = useState<any>(null)
|
||||
|
||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
||||
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
||||
useEffect(() => {
|
||||
if (!checkValid) {
|
||||
setCanShowSingleRun(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (data._isSingleRun) {
|
||||
const { isValid, errorMessage } = checkValid(data, t, moreDataForCheckValid)
|
||||
setCanShowSingleRun(isValid)
|
||||
if (!isValid) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_isSingleRun: false,
|
||||
},
|
||||
})
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: errorMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data._isSingleRun])
|
||||
const hideSingleRun = () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_isSingleRun: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
||||
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
||||
|
||||
const handleRun = async (submitData: Record<string, any>) => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Running,
|
||||
},
|
||||
})
|
||||
let res: any
|
||||
try {
|
||||
res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
|
||||
if (res.error)
|
||||
throw new Error(res.error)
|
||||
}
|
||||
catch (e: any) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
setRunResult({
|
||||
...res,
|
||||
created_by: res.created_by_account?.name || '',
|
||||
})
|
||||
}
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.NotStart,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const toVarInputs = (variables: Variable[]): InputVar[] => {
|
||||
if (!variables)
|
||||
return []
|
||||
|
||||
const varInputs = variables.map((item) => {
|
||||
const originalVar = getVar(item.value_selector)
|
||||
if (!originalVar) {
|
||||
return {
|
||||
label: item.label || item.variable,
|
||||
variable: item.variable,
|
||||
type: InputVarType.textInput,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: item.label || item.variable,
|
||||
variable: item.variable,
|
||||
type: varTypeToInputVarType(originalVar.type, {
|
||||
isSelect: !!originalVar.isSelect,
|
||||
isParagraph: !!originalVar.isParagraph,
|
||||
}),
|
||||
required: item.required !== false,
|
||||
options: originalVar.options,
|
||||
}
|
||||
})
|
||||
|
||||
return varInputs
|
||||
}
|
||||
|
||||
const getInputVars = (textList: string[]) => {
|
||||
const valueSelectors: ValueSelector[] = []
|
||||
textList.forEach((text) => {
|
||||
valueSelectors.push(...doGetInputVars(text))
|
||||
})
|
||||
|
||||
const variables = unionBy(valueSelectors, item => item.join('.')).map((item) => {
|
||||
const varInfo = getNodeInfoById(availableNodes, item[0])?.data
|
||||
|
||||
return {
|
||||
label: {
|
||||
nodeType: varInfo?.type,
|
||||
nodeName: varInfo?.title || availableNodes[0]?.data.title, // default start node title
|
||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||
},
|
||||
variable: `#${item.join('.')}#`,
|
||||
value_selector: item,
|
||||
}
|
||||
})
|
||||
|
||||
const varInputs = toVarInputs(variables)
|
||||
return varInputs
|
||||
}
|
||||
|
||||
return {
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
toVarInputs,
|
||||
getInputVars,
|
||||
runningStatus,
|
||||
isCompleted,
|
||||
handleRun,
|
||||
handleStop,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
}
|
||||
}
|
||||
|
||||
export default useOneStepRun
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { type OutputVar } from '../../code/types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import {
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
|
||||
type Params<T> = {
|
||||
id: string
|
||||
inputs: T
|
||||
setInputs: (newInputs: T) => void
|
||||
varKey?: string
|
||||
outputKeyOrders: string[]
|
||||
onOutputKeyOrdersChange: (newOutputKeyOrders: string[]) => void
|
||||
}
|
||||
function useOutputVarList<T>({
|
||||
id,
|
||||
inputs,
|
||||
setInputs,
|
||||
varKey = 'outputs',
|
||||
outputKeyOrders = [],
|
||||
onOutputKeyOrdersChange,
|
||||
}: Params<T>) {
|
||||
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
||||
|
||||
const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => {
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
draft[varKey] = newVars
|
||||
})
|
||||
setInputs(newInputs)
|
||||
|
||||
if (changedIndex !== undefined) {
|
||||
const newOutputKeyOrders = produce(outputKeyOrders, (draft) => {
|
||||
draft[changedIndex] = newKey!
|
||||
})
|
||||
onOutputKeyOrdersChange(newOutputKeyOrders)
|
||||
}
|
||||
|
||||
if (newKey)
|
||||
handleOutVarRenameChange(id, [id, outputKeyOrders[changedIndex!]], [id, newKey])
|
||||
}, [inputs, setInputs, handleOutVarRenameChange, id, outputKeyOrders, varKey, onOutputKeyOrdersChange])
|
||||
|
||||
const handleAddVariable = useCallback(() => {
|
||||
const newKey = `var_${Object.keys((inputs as any)[varKey]).length + 1}`
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
draft[varKey] = {
|
||||
...draft[varKey],
|
||||
[newKey]: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
}
|
||||
})
|
||||
setInputs(newInputs)
|
||||
onOutputKeyOrdersChange([...outputKeyOrders, newKey])
|
||||
}, [inputs, setInputs, varKey, outputKeyOrders, onOutputKeyOrdersChange])
|
||||
|
||||
const [isShowRemoveVarConfirm, {
|
||||
setTrue: showRemoveVarConfirm,
|
||||
setFalse: hideRemoveVarConfirm,
|
||||
}] = useBoolean(false)
|
||||
const [removedVar, setRemovedVar] = useState<ValueSelector>([])
|
||||
const removeVarInNode = useCallback(() => {
|
||||
removeUsedVarInNodes(removedVar)
|
||||
hideRemoveVarConfirm()
|
||||
}, [hideRemoveVarConfirm, removeUsedVarInNodes, removedVar])
|
||||
const handleRemoveVariable = useCallback((index: number) => {
|
||||
const key = outputKeyOrders[index]
|
||||
|
||||
if (isVarUsedInNodes([id, key])) {
|
||||
showRemoveVarConfirm()
|
||||
setRemovedVar([id, key])
|
||||
return
|
||||
}
|
||||
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
delete draft[varKey][key]
|
||||
})
|
||||
setInputs(newInputs)
|
||||
onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index))
|
||||
}, [outputKeyOrders, isVarUsedInNodes, id, inputs, setInputs, onOutputKeyOrdersChange, showRemoveVarConfirm, varKey])
|
||||
|
||||
return {
|
||||
handleVarsChange,
|
||||
handleAddVariable,
|
||||
handleRemoveVariable,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm: removeVarInNode,
|
||||
}
|
||||
}
|
||||
|
||||
export default useOutputVarList
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export type UseResizePanelPrarams = {
|
||||
direction?: 'horizontal' | 'vertical' | 'both'
|
||||
triggerDirection?: 'top' | 'right' | 'bottom' | 'left' | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
|
||||
minWidth?: number
|
||||
maxWidth?: number
|
||||
minHeight?: number
|
||||
maxHeight?: number
|
||||
onResized?: (width: number, height: number) => void
|
||||
}
|
||||
export const useResizePanel = (params?: UseResizePanelPrarams) => {
|
||||
const {
|
||||
direction = 'both',
|
||||
triggerDirection = 'bottom-right',
|
||||
minWidth = -Infinity,
|
||||
maxWidth = Infinity,
|
||||
minHeight = -Infinity,
|
||||
maxHeight = Infinity,
|
||||
onResized,
|
||||
} = params || {}
|
||||
const triggerRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const initXRef = useRef(0)
|
||||
const initYRef = useRef(0)
|
||||
const initContainerWidthRef = useRef(0)
|
||||
const initContainerHeightRef = useRef(0)
|
||||
const isResizingRef = useRef(false)
|
||||
const [prevUserSelectStyle, setPrevUserSelectStyle] = useState(getComputedStyle(document.body).userSelect)
|
||||
|
||||
const handleStartResize = useCallback((e: MouseEvent) => {
|
||||
initXRef.current = e.clientX
|
||||
initYRef.current = e.clientY
|
||||
initContainerWidthRef.current = containerRef.current?.offsetWidth || minWidth
|
||||
initContainerHeightRef.current = containerRef.current?.offsetHeight || minHeight
|
||||
isResizingRef.current = true
|
||||
setPrevUserSelectStyle(getComputedStyle(document.body).userSelect)
|
||||
document.body.style.userSelect = 'none'
|
||||
}, [minWidth, minHeight])
|
||||
|
||||
const handleResize = useCallback((e: MouseEvent) => {
|
||||
if (!isResizingRef.current)
|
||||
return
|
||||
|
||||
if (!containerRef.current)
|
||||
return
|
||||
|
||||
if (direction === 'horizontal' || direction === 'both') {
|
||||
const offsetX = e.clientX - initXRef.current
|
||||
let width = 0
|
||||
if (triggerDirection === 'left' || triggerDirection === 'top-left' || triggerDirection === 'bottom-left')
|
||||
width = initContainerWidthRef.current - offsetX
|
||||
else if (triggerDirection === 'right' || triggerDirection === 'top-right' || triggerDirection === 'bottom-right')
|
||||
width = initContainerWidthRef.current + offsetX
|
||||
|
||||
if (width < minWidth)
|
||||
width = minWidth
|
||||
if (width > maxWidth)
|
||||
width = maxWidth
|
||||
containerRef.current.style.width = `${width}px`
|
||||
}
|
||||
|
||||
if (direction === 'vertical' || direction === 'both') {
|
||||
const offsetY = e.clientY - initYRef.current
|
||||
let height = 0
|
||||
if (triggerDirection === 'top' || triggerDirection === 'top-left' || triggerDirection === 'top-right')
|
||||
height = initContainerHeightRef.current - offsetY
|
||||
else if (triggerDirection === 'bottom' || triggerDirection === 'bottom-left' || triggerDirection === 'bottom-right')
|
||||
height = initContainerHeightRef.current + offsetY
|
||||
|
||||
if (height < minHeight)
|
||||
height = minHeight
|
||||
if (height > maxHeight)
|
||||
height = maxHeight
|
||||
|
||||
containerRef.current.style.height = `${height}px`
|
||||
}
|
||||
}, [
|
||||
direction,
|
||||
triggerDirection,
|
||||
minWidth,
|
||||
maxWidth,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
])
|
||||
|
||||
const handleStopResize = useCallback(() => {
|
||||
isResizingRef.current = false
|
||||
document.body.style.userSelect = prevUserSelectStyle
|
||||
|
||||
if (onResized && containerRef.current)
|
||||
onResized(containerRef.current.offsetWidth, containerRef.current.offsetHeight)
|
||||
}, [prevUserSelectStyle, onResized])
|
||||
|
||||
useEffect(() => {
|
||||
const element = triggerRef.current
|
||||
element?.addEventListener('mousedown', handleStartResize)
|
||||
document.addEventListener('mousemove', handleResize)
|
||||
document.addEventListener('mouseup', handleStopResize)
|
||||
return () => {
|
||||
if (element)
|
||||
element.removeEventListener('mousedown', handleStartResize)
|
||||
document.removeEventListener('mousemove', handleResize)
|
||||
}
|
||||
}, [handleStartResize, handleResize, handleStopResize])
|
||||
|
||||
return {
|
||||
triggerRef,
|
||||
containerRef,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type Params = {
|
||||
ref: React.RefObject<HTMLDivElement>
|
||||
hasFooter?: boolean
|
||||
}
|
||||
|
||||
const useToggleExpend = ({ ref, hasFooter = true }: Params) => {
|
||||
const [isExpand, setIsExpand] = useState(false)
|
||||
const [wrapHeight, setWrapHeight] = useState(ref.current?.clientHeight)
|
||||
const editorExpandHeight = isExpand ? wrapHeight! - (hasFooter ? 56 : 29) : 0
|
||||
useEffect(() => {
|
||||
setWrapHeight(ref.current?.clientHeight)
|
||||
}, [isExpand])
|
||||
|
||||
const wrapClassName = isExpand && 'absolute z-10 left-4 right-6 top-[52px] bottom-0 pb-4 bg-white'
|
||||
|
||||
return {
|
||||
wrapClassName,
|
||||
editorExpandHeight,
|
||||
isExpand,
|
||||
setIsExpand,
|
||||
}
|
||||
}
|
||||
|
||||
export default useToggleExpend
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useCallback } from 'react'
|
||||
import produce from 'immer'
|
||||
import type { Variable } from '@/app/components/workflow/types'
|
||||
|
||||
type Params<T> = {
|
||||
inputs: T
|
||||
setInputs: (newInputs: T) => void
|
||||
varKey?: string
|
||||
}
|
||||
function useVarList<T>({
|
||||
inputs,
|
||||
setInputs,
|
||||
varKey = 'variables',
|
||||
}: Params<T>) {
|
||||
const handleVarListChange = useCallback((newList: Variable[] | string) => {
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
draft[varKey] = newList as Variable[]
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs, varKey])
|
||||
|
||||
const handleAddVariable = useCallback(() => {
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
draft[varKey].push({
|
||||
variable: '',
|
||||
value_selector: [],
|
||||
})
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs, varKey])
|
||||
return {
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
}
|
||||
}
|
||||
|
||||
export default useVarList
|
||||
Reference in New Issue
Block a user