mirror of
https://github.com/langgenius/dify.git
synced 2026-02-16 07:54:40 +08:00
refactor: streamline HITL input components by consolidating props and enhancing variable handling
This commit is contained in:
parent
ec0c144eb2
commit
1c4c1b5cb1
@ -304,15 +304,8 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
{
|
||||
hitlInputBlock?.show && (
|
||||
<>
|
||||
<HITLInputBlock />
|
||||
<HITLInputBlockReplacementBlock
|
||||
nodeId={hitlInputBlock.nodeId}
|
||||
nodeTitle={hitlInputBlock.nodeTitle}
|
||||
formInputs={hitlInputBlock.formInputs}
|
||||
onFormInputsChange={hitlInputBlock.onFormInputsChange}
|
||||
onFormInputItemRename={hitlInputBlock.onFormInputItemRename}
|
||||
onFormInputItemRemove={hitlInputBlock.onFormInputItemRemove}
|
||||
/>
|
||||
<HITLInputBlock {...hitlInputBlock} />
|
||||
<HITLInputBlockReplacementBlock {...hitlInputBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,45 +1,56 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useRef } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { VariableX } from '../../../icons/src/vender/workflow'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
|
||||
import { Variable02 } from '../../../icons/src/vender/solid/development'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import ActionButton from '../../../action-button'
|
||||
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import InputField from './input-field'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import Modal from '../../../modal'
|
||||
import type { WorkflowNodesMap } from '../workflow-variable-block/node'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
import VariableBlock from './variable-block'
|
||||
|
||||
type Props = {
|
||||
type HITLInputComponentUIProps = {
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
varName: string
|
||||
isSelected: boolean
|
||||
formInput?: FormInputItem
|
||||
onChange: (input: FormInputItem) => void
|
||||
onRename: (payload: FormInputItem, oldName: string) => void
|
||||
onRemove: (varName: string) => void
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
environmentVariables?: Var[]
|
||||
conversationVariables?: Var[]
|
||||
ragVariables?: Var[]
|
||||
getVarType?: (payload: {
|
||||
nodeId: string,
|
||||
valueSelector: ValueSelector,
|
||||
}) => Type
|
||||
}
|
||||
|
||||
const ComponentUI: FC<Props> = ({
|
||||
const HITLInputComponentUI: FC<HITLInputComponentUIProps> = ({
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
varName,
|
||||
// isSelected,
|
||||
formInput = {
|
||||
type: InputVarType.textInput,
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: varName,
|
||||
placeholder: {
|
||||
type: 'constant',
|
||||
selector: [],
|
||||
value: '',
|
||||
},
|
||||
} as FormInputItem,
|
||||
},
|
||||
onChange,
|
||||
onRename,
|
||||
onRemove,
|
||||
workflowNodesMap = {},
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
}) => {
|
||||
const [isShowEditModal, {
|
||||
setTrue: showEditModal,
|
||||
@ -79,6 +90,10 @@ const ComponentUI: FC<Props> = ({
|
||||
hideEditModal()
|
||||
}, [hideEditModal, onChange, onRename, varName])
|
||||
|
||||
const isPlaceholderVariable = useMemo(() => {
|
||||
return formInput.placeholder.type === 'variable'
|
||||
}, [formInput.placeholder.type])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative flex h-8 w-full select-none items-center rounded-[8px] border-[1.5px] border-components-input-border-active bg-background-default-hover pl-1.5 pr-0.5'
|
||||
@ -92,18 +107,20 @@ const ComponentUI: FC<Props> = ({
|
||||
</div>
|
||||
|
||||
<div className='flex w-full items-center justify-between'>
|
||||
{/* Node info */}
|
||||
<div className='flex h-[18px] items-center rounded-[5px] border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1 shadow-xs'>
|
||||
<div className='flex items-center space-x-0.5 text-text-secondary'>
|
||||
<VarBlockIcon type={BlockEnum.HumanInput} />
|
||||
<div className='system-xs-medium'>{nodeTitle}</div>
|
||||
</div>
|
||||
<div className='system-xs-regular mx-px text-divider-deep'>/</div>
|
||||
<div className='flex items-center space-x-0.5 text-text-accent'>
|
||||
<Variable02 className='size-3.5' />
|
||||
<div className='system-xs-medium'>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Placeholder Info */}
|
||||
{isPlaceholderVariable && (
|
||||
<VariableBlock
|
||||
variables={formInput.placeholder.selector}
|
||||
workflowNodesMap={workflowNodesMap}
|
||||
getVarType={getVarType}
|
||||
environmentVariables={environmentVariables}
|
||||
conversationVariables={conversationVariables}
|
||||
ragVariables={ragVariables}
|
||||
/>
|
||||
)}
|
||||
{!isPlaceholderVariable && (
|
||||
<div className='system-xs-medium text-text-quaternary'>{formInput.placeholder.value}</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className='flex h-full items-center space-x-1 pr-[24px]'>
|
||||
@ -141,4 +158,4 @@ const ComponentUI: FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ComponentUI)
|
||||
export default React.memo(HITLInputComponentUI)
|
||||
|
||||
@ -4,29 +4,43 @@ import { DELETE_HITL_INPUT_BLOCK_COMMAND } from './'
|
||||
import ComponentUi from './component-ui'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import { produce } from 'immer'
|
||||
import type { WorkflowNodesMap } from '../workflow-variable-block/node'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
|
||||
type Props = {
|
||||
type HITLInputComponentProps = {
|
||||
nodeKey: string
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
varName: string
|
||||
formInputs?: FormInputItem[]
|
||||
onChange: (inputs: FormInputItem[]) => void
|
||||
onRename: (payload: FormInputItem, oldName: string) => void
|
||||
onRemove: (varName: string) => void
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
environmentVariables?: Var[]
|
||||
conversationVariables?: Var[]
|
||||
ragVariables?: Var[]
|
||||
getVarType?: (payload: {
|
||||
nodeId: string,
|
||||
valueSelector: ValueSelector,
|
||||
}) => Type
|
||||
}
|
||||
|
||||
const HITLInputComponent: FC<Props> = ({
|
||||
const HITLInputComponent: FC<HITLInputComponentProps> = ({
|
||||
nodeKey,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
varName,
|
||||
formInputs = [],
|
||||
onChange,
|
||||
onRename,
|
||||
onRemove,
|
||||
workflowNodesMap = {},
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
}) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_HITL_INPUT_BLOCK_COMMAND)
|
||||
const [ref] = useSelectOrDelete(nodeKey, DELETE_HITL_INPUT_BLOCK_COMMAND)
|
||||
const payload = formInputs.find(item => item.output_variable_name === varName)
|
||||
|
||||
const handleChange = useCallback((newPayload: FormInputItem) => {
|
||||
@ -50,13 +64,16 @@ const HITLInputComponent: FC<Props> = ({
|
||||
>
|
||||
<ComponentUi
|
||||
nodeId={nodeId}
|
||||
nodeTitle={nodeTitle}
|
||||
varName={varName}
|
||||
isSelected={isSelected}
|
||||
formInput={payload}
|
||||
onChange={handleChange}
|
||||
onRename={onRename}
|
||||
onRemove={onRemove}
|
||||
workflowNodesMap={workflowNodesMap}
|
||||
getVarType={getVarType}
|
||||
environmentVariables={environmentVariables}
|
||||
conversationVariables={conversationVariables}
|
||||
ragVariables={ragVariables}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
@ -9,43 +10,55 @@ import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import type { HITLInputBlockType } from '../../types'
|
||||
import { $createHITLInputNode } from './node'
|
||||
import {
|
||||
QueryBlockNode,
|
||||
} from '../query-block/node'
|
||||
import { $createHITLInputNode, HITLInputNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
import { HITL_INPUT_REG } from '@/config'
|
||||
|
||||
const REGEX = new RegExp(HITL_INPUT_REG)
|
||||
|
||||
const HITLInputReplacementBlock = ({
|
||||
// onInsert,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
variables,
|
||||
}: HITLInputBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const environmentVariables = useMemo(() => variables?.find(o => o.nodeId === 'env')?.vars || [], [variables])
|
||||
const conversationVariables = useMemo(() => variables?.find(o => o.nodeId === 'conversation')?.vars || [], [variables])
|
||||
const ragVariables = useMemo(() => variables?.reduce<any[]>((acc, curr) => {
|
||||
if (curr.nodeId === 'rag')
|
||||
acc.push(...curr.vars)
|
||||
else
|
||||
acc.push(...curr.vars.filter(v => v.isRagVariable))
|
||||
return acc
|
||||
}, []), [variables])
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([QueryBlockNode]))
|
||||
throw new Error('QueryBlockNodePlugin: QueryBlockNode not registered on editor')
|
||||
if (!editor.hasNodes([HITLInputNode]))
|
||||
throw new Error('HITLInputNodePlugin: HITLInputNode not registered on editor')
|
||||
}, [editor])
|
||||
|
||||
const createHITLInputBlockNode = useCallback((textNode: TextNode): QueryBlockNode => {
|
||||
const createHITLInputBlockNode = useCallback((textNode: TextNode): HITLInputNode => {
|
||||
const varName = textNode.getTextContent().split('.')[1].replace(/#}}$/, '')
|
||||
return $applyNodeReplacement($createHITLInputNode(
|
||||
varName,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs || [],
|
||||
onFormInputsChange!,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove!,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
))
|
||||
}, [nodeId, nodeTitle, formInputs, onFormInputsChange, onFormInputItemRename, onFormInputItemRemove])
|
||||
}, [nodeId, formInputs, onFormInputsChange, onFormInputItemRename, onFormInputItemRemove, workflowNodesMap, getVarType, environmentVariables, conversationVariables, ragVariables])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { QueryBlockType } from '../../types'
|
||||
import type { HITLInputBlockType } from '../../types'
|
||||
import type {
|
||||
HITLNodeProps,
|
||||
} from './node'
|
||||
@ -20,6 +20,7 @@ import { mergeRegister } from '@lexical/utils'
|
||||
|
||||
export const INSERT_HITL_INPUT_BLOCK_COMMAND = createCommand('INSERT_HITL_INPUT_BLOCK_COMMAND')
|
||||
export const DELETE_HITL_INPUT_BLOCK_COMMAND = createCommand('DELETE_HITL_INPUT_BLOCK_COMMAND')
|
||||
export const UPDATE_WORKFLOW_NODES_MAP = createCommand('UPDATE_WORKFLOW_NODES_MAP')
|
||||
|
||||
export type HITLInputProps = {
|
||||
onInsert?: () => void
|
||||
@ -28,9 +29,17 @@ export type HITLInputProps = {
|
||||
const HITLInputBlock = memo(({
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: QueryBlockType) => {
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
}: HITLInputBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
editor.update(() => {
|
||||
editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap)
|
||||
})
|
||||
}, [editor, workflowNodesMap])
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HITLInputNode]))
|
||||
throw new Error('HITLInputBlockPlugin: HITLInputBlock not registered on editor')
|
||||
@ -41,7 +50,6 @@ const HITLInputBlock = memo(({
|
||||
const {
|
||||
variableName,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
@ -50,11 +58,12 @@ const HITLInputBlock = memo(({
|
||||
const currentHITLNode = $createHITLInputNode(
|
||||
variableName,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
)
|
||||
|
||||
$insertNodes([currentHITLNode])
|
||||
|
||||
@ -2,15 +2,22 @@ import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
|
||||
import { DecoratorNode } from 'lexical'
|
||||
import HILTInputBlockComponent from './component'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { WorkflowNodesMap } from '../workflow-variable-block/node'
|
||||
import type { GetVarType } from '../../types'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
|
||||
export type HITLNodeProps = {
|
||||
variableName: string
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
formInputs: FormInputItem[]
|
||||
onFormInputsChange: (inputs: FormInputItem[]) => void
|
||||
onFormInputItemRename: (payload: FormInputItem, oldName: string) => void
|
||||
onFormInputItemRemove: (varName: string) => void
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
getVarType?: GetVarType
|
||||
environmentVariables?: Var[]
|
||||
conversationVariables?: Var[]
|
||||
ragVariables?: Var[]
|
||||
}
|
||||
|
||||
export type SerializedNode = SerializedLexicalNode & HITLNodeProps
|
||||
@ -18,11 +25,15 @@ export type SerializedNode = SerializedLexicalNode & HITLNodeProps
|
||||
export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
__variableName: string
|
||||
__nodeId: string
|
||||
__nodeTitle: string
|
||||
__formInputs?: FormInputItem[]
|
||||
__onFormInputsChange: (inputs: FormInputItem[]) => void
|
||||
__onFormInputItemRename: (payload: FormInputItem, oldName: string) => void
|
||||
__onFormInputItemRemove: (varName: string) => void
|
||||
__workflowNodesMap: WorkflowNodesMap
|
||||
__getVarType?: GetVarType
|
||||
__environmentVariables?: Var[]
|
||||
__conversationVariables?: Var[]
|
||||
__ragVariables?: Var[]
|
||||
|
||||
isIsolated(): boolean {
|
||||
return true // This is necessary for drag-and-drop to work correctly
|
||||
@ -41,11 +52,6 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
return self.__variableName
|
||||
}
|
||||
|
||||
getNodeTitle(): string {
|
||||
const self = this.getLatest()
|
||||
return self.__nodeTitle
|
||||
}
|
||||
|
||||
getNodeId(): string {
|
||||
const self = this.getLatest()
|
||||
return self.__nodeId
|
||||
@ -71,15 +77,44 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
return self.__onFormInputItemRemove
|
||||
}
|
||||
|
||||
getWorkflowNodesMap(): WorkflowNodesMap {
|
||||
const self = this.getLatest()
|
||||
return self.__workflowNodesMap
|
||||
}
|
||||
|
||||
getGetVarType(): GetVarType | undefined {
|
||||
const self = this.getLatest()
|
||||
return self.__getVarType
|
||||
}
|
||||
|
||||
getEnvironmentVariables(): Var[] {
|
||||
const self = this.getLatest()
|
||||
return self.__environmentVariables || []
|
||||
}
|
||||
|
||||
getConversationVariables(): Var[] {
|
||||
const self = this.getLatest()
|
||||
return self.__conversationVariables || []
|
||||
}
|
||||
|
||||
getRagVariables(): Var[] {
|
||||
const self = this.getLatest()
|
||||
return self.__ragVariables || []
|
||||
}
|
||||
|
||||
static clone(node: HITLInputNode): HITLInputNode {
|
||||
return new HITLInputNode(
|
||||
node.__variableName,
|
||||
node.__nodeId,
|
||||
node.__nodeTitle,
|
||||
node.__formInputs || [],
|
||||
node.__onFormInputsChange,
|
||||
node.__onFormInputItemRename,
|
||||
node.__onFormInputItemRemove,
|
||||
node.__workflowNodesMap,
|
||||
node.__getVarType,
|
||||
node.__environmentVariables,
|
||||
node.__conversationVariables,
|
||||
node.__ragVariables,
|
||||
node.__key,
|
||||
)
|
||||
}
|
||||
@ -91,22 +126,30 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
constructor(
|
||||
varName: string,
|
||||
nodeId: string,
|
||||
nodeTitle: string,
|
||||
formInputs: FormInputItem[],
|
||||
onFormInputsChange: (inputs: FormInputItem[]) => void,
|
||||
onFormInputItemRename: (payload: FormInputItem, oldName: string) => void,
|
||||
onFormInputItemRemove: (varName: string) => void,
|
||||
workflowNodesMap: WorkflowNodesMap,
|
||||
getVarType?: GetVarType,
|
||||
environmentVariables?: Var[],
|
||||
conversationVariables?: Var[],
|
||||
ragVariables?: Var[],
|
||||
key?: NodeKey,
|
||||
) {
|
||||
super(key)
|
||||
|
||||
this.__variableName = varName
|
||||
this.__nodeId = nodeId
|
||||
this.__nodeTitle = nodeTitle
|
||||
this.__formInputs = formInputs
|
||||
this.__onFormInputsChange = onFormInputsChange
|
||||
this.__onFormInputItemRename = onFormInputItemRename
|
||||
this.__onFormInputItemRemove = onFormInputItemRemove
|
||||
this.__workflowNodesMap = workflowNodesMap
|
||||
this.__getVarType = getVarType
|
||||
this.__environmentVariables = environmentVariables
|
||||
this.__conversationVariables = conversationVariables
|
||||
this.__ragVariables = ragVariables
|
||||
}
|
||||
|
||||
createDOM(): HTMLElement {
|
||||
@ -124,11 +167,15 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
nodeKey={this.getKey()}
|
||||
varName={this.getVariableName()}
|
||||
nodeId={this.getNodeId()}
|
||||
nodeTitle={this.getNodeTitle()}
|
||||
formInputs={this.getFormInputs()}
|
||||
onChange={this.getOnFormInputsChange()}
|
||||
onRename={this.getOnFormInputItemRename()}
|
||||
onRemove={this.getOnFormInputItemRemove()}
|
||||
workflowNodesMap={this.getWorkflowNodesMap()}
|
||||
getVarType={this.getGetVarType()}
|
||||
environmentVariables={this.getEnvironmentVariables()}
|
||||
conversationVariables={this.getConversationVariables()}
|
||||
ragVariables={this.getRagVariables()}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -136,11 +183,15 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
const node = $createHITLInputNode(
|
||||
serializedNode.variableName,
|
||||
serializedNode.nodeId,
|
||||
serializedNode.nodeTitle,
|
||||
serializedNode.formInputs,
|
||||
serializedNode.onFormInputsChange,
|
||||
serializedNode.onFormInputItemRename,
|
||||
serializedNode.onFormInputItemRemove,
|
||||
serializedNode.workflowNodesMap,
|
||||
serializedNode.getVarType,
|
||||
serializedNode.environmentVariables,
|
||||
serializedNode.conversationVariables,
|
||||
serializedNode.ragVariables,
|
||||
)
|
||||
|
||||
return node
|
||||
@ -152,11 +203,15 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
version: 1,
|
||||
variableName: this.getVariableName(),
|
||||
nodeId: this.getNodeId(),
|
||||
nodeTitle: this.getNodeTitle(),
|
||||
formInputs: this.getFormInputs(),
|
||||
onFormInputsChange: this.getOnFormInputsChange(),
|
||||
onFormInputItemRename: this.getOnFormInputItemRename(),
|
||||
onFormInputItemRemove: this.getOnFormInputItemRemove(),
|
||||
workflowNodesMap: this.getWorkflowNodesMap(),
|
||||
getVarType: this.getGetVarType(),
|
||||
environmentVariables: this.getEnvironmentVariables(),
|
||||
conversationVariables: this.getConversationVariables(),
|
||||
ragVariables: this.getRagVariables(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,20 +223,28 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
export function $createHITLInputNode(
|
||||
variableName: string,
|
||||
nodeId: string,
|
||||
nodeTitle: string,
|
||||
formInputs: FormInputItem[],
|
||||
onFormInputsChange: (inputs: FormInputItem[]) => void,
|
||||
onFormInputItemRename: (payload: FormInputItem, oldName: string) => void,
|
||||
onFormInputItemRemove: (varName: string) => void,
|
||||
workflowNodesMap: WorkflowNodesMap,
|
||||
getVarType?: GetVarType,
|
||||
environmentVariables?: Var[],
|
||||
conversationVariables?: Var[],
|
||||
ragVariables?: Var[],
|
||||
): HITLInputNode {
|
||||
return new HITLInputNode(
|
||||
variableName,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
} from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { WorkflowNodesMap } from '../workflow-variable-block/node'
|
||||
import {
|
||||
isConversationVar,
|
||||
isENV,
|
||||
isGlobalVar,
|
||||
isRagVariableVar,
|
||||
isSystemVar,
|
||||
} from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel'
|
||||
import { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import {
|
||||
VariableLabelInEditor,
|
||||
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
|
||||
import { UPDATE_WORKFLOW_NODES_MAP } from '../workflow-variable-block'
|
||||
import { HITLInputNode } from './node'
|
||||
|
||||
type HITLInputVariableBlockComponentProps = {
|
||||
variables: string[]
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
environmentVariables?: Var[]
|
||||
conversationVariables?: Var[]
|
||||
ragVariables?: Var[]
|
||||
getVarType?: (payload: {
|
||||
nodeId: string,
|
||||
valueSelector: ValueSelector,
|
||||
}) => Type
|
||||
}
|
||||
|
||||
const HITLInputVariableBlockComponent = ({
|
||||
variables,
|
||||
workflowNodesMap = {},
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
}: HITLInputVariableBlockComponentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const variablesLength = variables.length
|
||||
const isRagVar = isRagVariableVar(variables)
|
||||
const isShowAPart = variablesLength > 2 && !isRagVar
|
||||
const varName = (
|
||||
() => {
|
||||
const isSystem = isSystemVar(variables)
|
||||
const varName = variables[variablesLength - 1]
|
||||
return `${isSystem ? 'sys.' : ''}${varName}`
|
||||
}
|
||||
)()
|
||||
const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState<WorkflowNodesMap>(workflowNodesMap)
|
||||
const node = localWorkflowNodesMap![variables[isRagVar ? 1 : 0]]
|
||||
|
||||
const isException = isExceptionVariable(varName, node?.type)
|
||||
const variableValid = useMemo(() => {
|
||||
let variableValid = true
|
||||
const isEnv = isENV(variables)
|
||||
const isChatVar = isConversationVar(variables)
|
||||
const isGlobal = isGlobalVar(variables)
|
||||
if (isGlobal)
|
||||
return true
|
||||
|
||||
if (isEnv) {
|
||||
if (environmentVariables)
|
||||
variableValid = environmentVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
}
|
||||
else if (isChatVar) {
|
||||
if (conversationVariables)
|
||||
variableValid = conversationVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
}
|
||||
else if (isRagVar) {
|
||||
if (ragVariables)
|
||||
variableValid = ragVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}.${variables?.[2] ?? ''}`)
|
||||
}
|
||||
else {
|
||||
variableValid = !!node
|
||||
}
|
||||
return variableValid
|
||||
}, [variables, node, environmentVariables, conversationVariables, isRagVar, ragVariables])
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HITLInputNode]))
|
||||
throw new Error('HITLInputNodePlugin: HITLInputNode not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
UPDATE_WORKFLOW_NODES_MAP,
|
||||
(workflowNodesMap: WorkflowNodesMap) => {
|
||||
setLocalWorkflowNodesMap(workflowNodesMap)
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
const Item = (
|
||||
<VariableLabelInEditor
|
||||
nodeType={node?.type}
|
||||
nodeTitle={node?.title}
|
||||
variables={variables}
|
||||
isExceptionVariable={isException}
|
||||
errorMsg={!variableValid ? t('workflow.errorMsg.invalidVariable') : undefined}
|
||||
notShowFullPath={isShowAPart}
|
||||
/>
|
||||
)
|
||||
|
||||
if (!node)
|
||||
return Item
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
noDecoration
|
||||
popupContent={
|
||||
<VarFullPathPanel
|
||||
nodeName={node.title}
|
||||
path={variables.slice(1)}
|
||||
varType={getVarType ? getVarType({
|
||||
nodeId: variables[0],
|
||||
valueSelector: variables,
|
||||
}) : Type.string}
|
||||
nodeType={node?.type}
|
||||
/>}
|
||||
disabled={!isShowAPart}
|
||||
>
|
||||
<div>{Item}</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(HITLInputVariableBlockComponent)
|
||||
@ -40,7 +40,7 @@ const WorkflowVariableBlockReplacementBlock = ({
|
||||
|
||||
const nodePathString = textNode.getTextContent().slice(3, -3)
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap, getVarType, variables?.find(o => o.nodeId === 'env')?.vars || [], variables?.find(o => o.nodeId === 'conversation')?.vars || [], ragVariables))
|
||||
}, [onInsert, workflowNodesMap, getVarType, variables])
|
||||
}, [onInsert, workflowNodesMap, getVarType, variables, ragVariables])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
@ -84,11 +84,15 @@ export type WorkflowVariableBlockType = {
|
||||
export type HITLInputBlockType = {
|
||||
show?: boolean
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
formInputs?: FormInputItem[]
|
||||
variables?: NodeOutPutVar[]
|
||||
workflowNodesMap?: Record<string, Pick<Node['data'], 'title' | 'type' | 'height' | 'width' | 'position'>>
|
||||
getVarType?: GetVarType
|
||||
onFormInputsChange?: (inputs: FormInputItem[]) => void
|
||||
onFormInputItemRemove: (varName: string) => void
|
||||
onFormInputItemRename: (payload: FormInputItem, oldName: string) => void
|
||||
onInsert?: () => void
|
||||
onDelete?: () => void
|
||||
}
|
||||
|
||||
export type MenuTextMatch = {
|
||||
|
||||
@ -14,9 +14,8 @@ import { isMac } from '../../../utils'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
type FormContentProps = {
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
formInputs: FormInputItem[]
|
||||
@ -35,9 +34,8 @@ const CtrlKey: FC = () => {
|
||||
return <Key className={cn('mr-0', !isMac() && 'w-7')}>{isMac() ? '⌘' : 'Ctrl'}</Key>
|
||||
}
|
||||
|
||||
const FormContent: FC<Props> = ({
|
||||
const FormContent: FC<FormContentProps> = ({
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
value,
|
||||
onChange,
|
||||
formInputs,
|
||||
@ -67,7 +65,6 @@ const FormContent: FC<Props> = ({
|
||||
onInsert(INSERT_HITL_INPUT_BLOCK_COMMAND, {
|
||||
variableName: payload.output_variable_name,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs: newFormInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
@ -91,6 +88,23 @@ const FormContent: FC<Props> = ({
|
||||
setFalse: setBlur,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const workflowNodesMap = availableNodes.reduce((acc: any, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<div className={cn('flex grow flex-col rounded-[10px] border border-components-input-bg-normal bg-components-input-bg-normal pt-1', isFocus && 'border-components-input-border-active bg-components-input-bg-active', !isFocus && 'pb-[32px]')}>
|
||||
<PromptEditor
|
||||
@ -105,31 +119,18 @@ const FormContent: FC<Props> = ({
|
||||
show: true,
|
||||
formInputs,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
variables: availableVars || [],
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
}}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: availableVars || [],
|
||||
getVarType: getVarType as any,
|
||||
workflowNodesMap: availableNodes.reduce((acc: any, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {}),
|
||||
workflowNodesMap,
|
||||
}}
|
||||
editable
|
||||
shortcutPopups={[{
|
||||
|
||||
@ -115,7 +115,6 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
|
||||
nodeId={id}
|
||||
value={inputs.form_content}
|
||||
onChange={handleFormContentChange}
|
||||
nodeTitle={inputs.title}
|
||||
formInputs={inputs.inputs}
|
||||
onFormInputsChange={handleFormInputsChange}
|
||||
onFormInputItemRename={handleFormInputItemRename}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user