feat: var name rename struct

This commit is contained in:
Joel 2024-03-21 15:07:34 +08:00
parent 267d9568c6
commit 02a059bdc6
7 changed files with 189 additions and 20 deletions

View File

@ -16,12 +16,23 @@ import { type InputVar, InputVarType } from '@/app/components/workflow/types'
import Modal from '@/app/components/base/modal'
import Switch from '@/app/components/base/switch'
export enum ChangeType {
changeVarName = 'changeVarName',
}
export type MoreInfo = {
type: ChangeType
payload?: {
beforeKey: string
afterKey: string
}
}
export type IConfigModalProps = {
isCreate?: boolean
payload?: InputVar
isShow: boolean
onClose: () => void
onConfirm: (newValue: InputVar) => void
onConfirm: (newValue: InputVar, moreInfo?: MoreInfo) => void
}
const inputClassName = 'w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
@ -39,7 +50,6 @@ const ConfigModal: FC<IConfigModalProps> = ({
const { type, label, variable, options, max_length } = tempPayload
const isStringInput = type === InputVarType.textInput || type === InputVarType.paragraph
const handlePayloadChange = useCallback((key: string) => {
return (value: any) => {
setTempPayload((prev) => {
@ -52,6 +62,12 @@ const ConfigModal: FC<IConfigModalProps> = ({
}, [])
const handleConfirm = () => {
const moreInfo = tempPayload.variable === payload?.variable
? undefined
: {
type: ChangeType.changeVarName,
payload: { beforeKey: payload?.variable || '', afterKey: tempPayload.variable },
}
if (!tempPayload.variable) {
Toast.notify({ type: 'error', message: t('appDebug.variableConig.errorMsg.varNameRequired') })
return
@ -61,7 +77,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
return
}
if (isStringInput || type === InputVarType.number) {
onConfirm(tempPayload)
onConfirm(tempPayload, moreInfo)
}
else {
if (options?.length === 0) {
@ -81,7 +97,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
Toast.notify({ type: 'error', message: t('appDebug.variableConig.errorMsg.optionRepeat') })
return
}
onConfirm(tempPayload)
onConfirm(tempPayload, moreInfo)
}
}

View File

@ -27,6 +27,7 @@ import {
import type {
Edge,
Node,
ValueSelector,
} from '../types'
import {
BlockEnum,
@ -41,6 +42,7 @@ import {
START_INITIAL_POSITION,
SUPPORT_OUTPUT_VARS_NODE,
} from '../constants'
import { findUsedVarNodes, updateNodeVars } from '../nodes/_base/components/variable/utils'
import {
useNodesExtraData,
useNodesInitialData,
@ -55,7 +57,6 @@ import {
} from '@/service/workflow'
import { fetchCollectionList } from '@/service/tools'
import I18n from '@/context/i18n'
export const useIsChatMode = () => {
const appDetail = useAppStore(s => s.appDetail)
@ -211,6 +212,25 @@ export const useWorkflow = () => {
return uniqBy(list, 'id')
}, [store])
const handleOutVarRenameChange = useCallback((nodeId: string, oldValeSelector: ValueSelector, newVarSelector: ValueSelector) => {
const { getNodes, setNodes } = store.getState()
const afterNodes = getAfterNodesInSameBranch(nodeId)
const effectNodes = findUsedVarNodes(oldValeSelector, afterNodes)
console.log(effectNodes)
if (effectNodes.length > 0) {
// debugger
const newNodes = getNodes().map((node) => {
if (effectNodes.find(n => n.id === node.id))
return updateNodeVars(node, oldValeSelector, newVarSelector)
return node
})
setNodes(newNodes)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [store])
const isValidConnection = useCallback(({ source, target }: Connection) => {
const { getNodes } = store.getState()
const nodes = getNodes()
@ -294,6 +314,7 @@ export const useWorkflow = () => {
getTreeLeafNodes,
getBeforeNodesInSameBranch,
getAfterNodesInSameBranch,
handleOutVarRenameChange,
isValidConnection,
formatTimeFromNow,
getValidTreeNodes,

View File

@ -1,7 +1,18 @@
import produce from 'immer'
import type { CodeNodeType } from '../../../code/types'
import type { EndNodeType } from '../../../end/types'
import type { AnswerNodeType } from '../../../answer/types'
import type { LLMNodeType } from '../../../llm/types'
import type { KnowledgeRetrievalNodeType } from '../../../knowledge-retrieval/types'
import type { IfElseNodeType } from '../../../if-else/types'
import type { TemplateTransformNodeType } from '../../../template-transform/types'
import type { QuestionClassifierNodeType } from '../../../question-classifier/types'
import type { HttpNodeType } from '../../../http/types'
import type { ToolNodeType } from '../../../tool/types'
import { VarType as VarKindType } from '../../../tool/types'
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
import {
CHAT_QUESTION_CLASSIFIER_OUTPUT_STRUCT,
@ -199,3 +210,116 @@ export const getVarType = (value: ValueSelector, availableNodes: any[], isChatMo
return type
}
}
const getNodeUsedVars = (node: Node): ValueSelector[] => {
const { data } = node
const { type } = data
let res: ValueSelector[] = []
switch (type) {
case BlockEnum.End: {
res = (data as EndNodeType).outputs?.map((output) => {
return output.value_selector
})
break
}
case BlockEnum.Answer: {
res = (data as AnswerNodeType).variables?.map((v) => {
return v.value_selector
})
break
}
case BlockEnum.LLM: {
const inputVars = (data as LLMNodeType)?.variables.map((v) => {
return v.value_selector
})
const contextVar = (data as LLMNodeType).context?.variable_selector ? [(data as LLMNodeType).context?.variable_selector] : []
res = [...inputVars, ...contextVar]
break
}
case BlockEnum.KnowledgeRetrieval: {
res = [(data as KnowledgeRetrievalNodeType).query_variable_selector]
break
}
case BlockEnum.IfElse: {
res = (data as IfElseNodeType).conditions?.map((c) => {
return c.variable_selector
})
break
}
case BlockEnum.Code: {
res = (data as CodeNodeType).variables?.map((v) => {
return v.value_selector
})
break
}
case BlockEnum.TemplateTransform: {
res = (data as TemplateTransformNodeType).variables?.map((v: any) => {
return v.value_selector
})
break
}
case BlockEnum.QuestionClassifier: {
res = [(data as QuestionClassifierNodeType).query_variable_selector]
break
}
case BlockEnum.HttpRequest: {
res = (data as HttpNodeType).variables?.map((v) => {
return v.value_selector
})
break
}
case BlockEnum.Tool: {
res = (data as ToolNodeType).tool_parameters?.filter((v) => {
return v.variable_type === VarKindType.static
}).map((v) => {
return v.value_selector || []
})
break
}
case BlockEnum.VariableAssigner: {
res = (data as VariableAssignerNodeType)?.variables
}
}
return res || []
}
export const findUsedVarNodes = (varSelector: ValueSelector, availableNodes: Node[]): Node[] => {
const res: Node[] = []
availableNodes.forEach((node) => {
const vars = getNodeUsedVars(node)
if (vars.find(v => v.join('.') === varSelector.join('.')))
res.push(node)
})
return res
}
export const updateNodeVars = (oldNode: Node, oldVarSelector: ValueSelector, newVarSelector: ValueSelector): Node => {
const newNode = produce(oldNode, (draft: any) => {
const { data } = draft
const { type } = data
switch (type) {
case BlockEnum.End: {
if ((data as EndNodeType).outputs) {
(data as EndNodeType).outputs = (data as EndNodeType).outputs.map((output) => {
if (output.value_selector.join('.') === oldVarSelector.join('.'))
output.value_selector = newVarSelector
return output
})
}
break
}
case BlockEnum.Answer: {
if ((data as AnswerNodeType).variables) {
(data as AnswerNodeType).variables = (data as AnswerNodeType).variables.map((v) => {
if (v.value_selector.join('.') === oldVarSelector.join('.'))
v.value_selector = newVarSelector
return v
})
}
}
}
})
return newNode
}

View File

@ -187,10 +187,10 @@ const VarReferencePicker: FC<Props> = ({
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 5 buff
const availableWidth = triggerWidth - 47
const [maxNodeNameWidth, maxVarNameWidth, maxTypeWidth] = (() => {
const totalTextLength = ((outputVarNode?.title) + (varName) + type).length
const maxNodeNameWidth = Math.ceil(outputVarNode?.title.length / totalTextLength * availableWidth)
const maxVarNameWidth = Math.ceil(varName.length / totalTextLength * availableWidth)
const maxTypeWidth = Math.ceil(type.length / totalTextLength * availableWidth)
const totalTextLength = ((outputVarNode?.title || '') + (varName || '') + (type || '')).length
const maxNodeNameWidth = Math.floor((outputVarNode?.title?.length || 0) / totalTextLength * availableWidth)
const maxVarNameWidth = Math.floor((varName?.length || 0) / totalTextLength * availableWidth)
const maxTypeWidth = Math.floor((type?.length || 0) / totalTextLength * availableWidth)
return [maxNodeNameWidth, maxVarNameWidth, maxTypeWidth]
})()

View File

@ -8,12 +8,13 @@ import type { InputVar } from '@/app/components/workflow/types'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general'
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
import type { MoreInfo } from '@/app/components/app/configuration/config-var/config-modal'
import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal'
type Props = {
readonly: boolean
payload: InputVar
onChange: (item: InputVar) => void
onChange: (item: InputVar, moreInfo?: MoreInfo) => void
onRemove: () => void
}
@ -32,8 +33,8 @@ const VarItem: FC<Props> = ({
setFalse: hideEditVarModal,
}] = useBoolean(false)
const handlePayloadChange = useCallback((payload: InputVar) => {
onChange(payload)
const handlePayloadChange = useCallback((payload: InputVar, moreInfo?: MoreInfo) => {
onChange(payload, moreInfo)
hideEditVarModal()
}, [onChange, hideEditVarModal])
return (

View File

@ -5,11 +5,11 @@ import produce from 'immer'
import { useTranslation } from 'react-i18next'
import VarItem from './var-item'
import type { InputVar } from '@/app/components/workflow/types'
import type { MoreInfo } from '@/app/components/app/configuration/config-var/config-modal'
type Props = {
readonly: boolean
list: InputVar[]
onChange: (list: InputVar[]) => void
onChange: (list: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => void
}
const VarList: FC<Props> = ({
@ -19,12 +19,12 @@ const VarList: FC<Props> = ({
}) => {
const { t } = useTranslation()
const handleVarNameChange = useCallback((index: number) => {
return (payload: InputVar) => {
const handleVarChange = useCallback((index: number) => {
return (payload: InputVar, moreInfo?: MoreInfo) => {
const newList = produce(list, (draft) => {
draft[index] = payload
})
onChange(newList)
onChange(newList, moreInfo ? { index, payload: moreInfo } : undefined)
}
}, [list, onChange])
@ -52,7 +52,7 @@ const VarList: FC<Props> = ({
key={index}
readonly={readonly}
payload={item}
onChange={handleVarNameChange(index)}
onChange={handleVarChange(index)}
onRemove={handleVarRemove(index)}
/>
))}

View File

@ -7,10 +7,13 @@ import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-cr
import {
useIsChatMode,
useNodesReadOnly,
useWorkflow,
} from '@/app/components/workflow/hooks'
import type { MoreInfo } from '@/app/components/app/configuration/config-var/config-modal'
const useConfig = (id: string, payload: StartNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const { handleOutVarRenameChange } = useWorkflow()
const isChatMode = useIsChatMode()
const { inputs, setInputs } = useNodeCrud<StartNodeType>(id, payload)
@ -20,11 +23,15 @@ const useConfig = (id: string, payload: StartNodeType) => {
setFalse: hideAddVarModal,
}] = useBoolean(false)
const handleVarListChange = useCallback((newList: InputVar[]) => {
const handleVarListChange = useCallback((newList: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => {
const newInputs = produce(inputs, (draft: any) => {
draft.variables = newList
})
setInputs(newInputs)
if (moreInfo) {
const changedVar = newList[moreInfo.index]
handleOutVarRenameChange(id, [id, inputs.variables[moreInfo.index].variable], [id, changedVar.variable])
}
}, [inputs, setInputs])
const handleAddVariable = useCallback((payload: InputVar) => {