mirror of
https://github.com/langgenius/dify.git
synced 2026-04-22 03:37:44 +08:00
fix: Fix assemble variables insertion in prompt editor
This commit is contained in:
@ -14,6 +14,7 @@ import type {
|
||||
} from '../../types'
|
||||
import type { PickerBlockMenuOption } from './menu'
|
||||
import type { AgentNode } from '@/app/components/base/prompt-editor/types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import {
|
||||
flip,
|
||||
offset,
|
||||
@ -163,7 +164,7 @@ const ComponentPicker = ({
|
||||
editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent)
|
||||
}, [editor])
|
||||
|
||||
const handleSelectAssembleVariables = useCallback(() => {
|
||||
const handleSelectAssembleVariables = useCallback((): ValueSelector | null => {
|
||||
editor.update(() => {
|
||||
const match = checkForTriggerMatch(triggerString, editor)
|
||||
if (!match)
|
||||
@ -172,8 +173,11 @@ const ComponentPicker = ({
|
||||
if (needRemove)
|
||||
needRemove.remove()
|
||||
})
|
||||
workflowVariableBlock?.onAssembleVariables?.()
|
||||
const assembleVariables = workflowVariableBlock?.onAssembleVariables?.()
|
||||
if (assembleVariables && assembleVariables.length)
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, assembleVariables)
|
||||
handleClose()
|
||||
return assembleVariables ?? null
|
||||
}, [editor, checkForTriggerMatch, triggerString, workflowVariableBlock, handleClose])
|
||||
|
||||
const handleSelectAgent = useCallback((agent: { id: string, title: string }) => {
|
||||
|
||||
@ -72,7 +72,7 @@ export type WorkflowVariableBlockType = {
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
showAssembleVariables?: boolean
|
||||
onAssembleVariables?: () => void
|
||||
onAssembleVariables?: () => ValueSelector | null
|
||||
}
|
||||
|
||||
export type AgentNode = {
|
||||
|
||||
@ -235,7 +235,15 @@ const FormInputItem: FC<Props> = ({
|
||||
|
||||
const handleValueChange = (newValue: any, newType?: VarKindType, mentionConfig?: MentionConfig | null) => {
|
||||
const normalizedValue = isNumber ? Number.parseFloat(newValue) : newValue
|
||||
const resolvedType = newType ?? (varInput?.type === VarKindType.mention ? VarKindType.mention : getVarKindType())
|
||||
const assemblePlaceholder = nodeId && variable
|
||||
? `{{#${nodeId}_ext_${variable}.result#}}`
|
||||
: ''
|
||||
const isAssembleValue = typeof normalizedValue === 'string'
|
||||
&& assemblePlaceholder
|
||||
&& normalizedValue.includes(assemblePlaceholder)
|
||||
const resolvedType = isAssembleValue
|
||||
? VarKindType.mixed
|
||||
: newType ?? (varInput?.type === VarKindType.mention ? VarKindType.mention : getVarKindType())
|
||||
const resolvedMentionConfig = resolvedType === VarKindType.mention
|
||||
? (mentionConfig ?? varInput?.mention_config ?? {
|
||||
extractor_node_id: '',
|
||||
|
||||
@ -256,7 +256,7 @@ type Props = {
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
showAssembleVariables?: boolean
|
||||
onAssembleVariables?: () => void
|
||||
onAssembleVariables?: () => ValueSelector | null
|
||||
autoFocus?: boolean
|
||||
preferSchemaType?: boolean
|
||||
}
|
||||
|
||||
@ -38,7 +38,8 @@ import Placeholder from './placeholder'
|
||||
* Matches agent context variable syntax: {{@nodeId.context@}}
|
||||
* Example: {{@agent-123.context@}} -> captures "agent-123"
|
||||
*/
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{[@#]([^.@#]+)\.context[@#]\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{@([^.@#]+)\.context@\}\}/g
|
||||
|
||||
const buildAssemblePlaceholder = (toolNodeId?: string, paramKey?: string) => {
|
||||
if (!toolNodeId || !paramKey)
|
||||
return ''
|
||||
@ -179,7 +180,7 @@ const MixedVariableTextInput = ({
|
||||
const isAssembleValue = useMemo(() => {
|
||||
if (!assemblePlaceholder)
|
||||
return false
|
||||
return value.trim() === assemblePlaceholder
|
||||
return value.includes(assemblePlaceholder)
|
||||
}, [assemblePlaceholder, value])
|
||||
|
||||
const contextNodeIds = useMemo(() => {
|
||||
@ -204,6 +205,99 @@ const MixedVariableTextInput = ({
|
||||
return `${toolNodeId}_ext_${paramKey}`
|
||||
}, [paramKey, toolNodeId])
|
||||
|
||||
const ensureExtractorNode = useCallback((payload: {
|
||||
extractorNodeId: string
|
||||
nodeType: BlockEnum
|
||||
data: Partial<LLMNodeType | CodeNodeType>
|
||||
}) => {
|
||||
if (!toolNodeId)
|
||||
return null
|
||||
const defaultValue = nodesMetaDataMap?.[payload.nodeType]?.defaultValue as Partial<LLMNodeType | CodeNodeType> | undefined
|
||||
if (!defaultValue)
|
||||
return null
|
||||
|
||||
const { getNodes, setNodes } = reactFlowStore.getState()
|
||||
const currentNodes = getNodes()
|
||||
const existingNode = currentNodes.find(node => node.id === payload.extractorNodeId)
|
||||
const shouldReplace = existingNode && existingNode.data.type !== payload.nodeType
|
||||
if (!existingNode || shouldReplace) {
|
||||
const nextNodes = shouldReplace
|
||||
? currentNodes.filter(node => node.id !== payload.extractorNodeId)
|
||||
: currentNodes
|
||||
const { newNode } = generateNewNode({
|
||||
id: payload.extractorNodeId,
|
||||
type: getNodeCustomTypeByNodeDataType(payload.nodeType),
|
||||
data: {
|
||||
...defaultValue,
|
||||
...payload.data,
|
||||
type: payload.nodeType,
|
||||
title: defaultValue?.title ?? '',
|
||||
desc: defaultValue.desc || '',
|
||||
parent_node_id: toolNodeId,
|
||||
},
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
hidden: true,
|
||||
})
|
||||
setNodes([...nextNodes, newNode])
|
||||
handleSyncWorkflowDraft()
|
||||
return newNode
|
||||
}
|
||||
|
||||
return existingNode
|
||||
}, [handleSyncWorkflowDraft, nodesMetaDataMap, reactFlowStore, toolNodeId])
|
||||
|
||||
const ensureAssembleExtractorNode = useCallback(() => {
|
||||
if (!assembleExtractorNodeId)
|
||||
return ''
|
||||
const extractorNode = ensureExtractorNode({
|
||||
extractorNodeId: assembleExtractorNodeId,
|
||||
nodeType: BlockEnum.Code,
|
||||
data: {
|
||||
outputs: {
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!extractorNode)
|
||||
return ''
|
||||
if (extractorNode.data.type !== BlockEnum.Code)
|
||||
return assembleExtractorNodeId
|
||||
|
||||
const outputs = (extractorNode.data as CodeNodeType).outputs || {}
|
||||
const resultOutput = outputs.result
|
||||
if (!resultOutput || resultOutput.type !== VarType.string) {
|
||||
const { getNodes, setNodes } = reactFlowStore.getState()
|
||||
const currentNodes = getNodes()
|
||||
const nextOutputs = {
|
||||
...outputs,
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
}
|
||||
setNodes(currentNodes.map((node) => {
|
||||
if (node.id !== assembleExtractorNodeId)
|
||||
return node
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
outputs: nextOutputs,
|
||||
},
|
||||
}
|
||||
}))
|
||||
handleSyncWorkflowDraft()
|
||||
}
|
||||
|
||||
return assembleExtractorNodeId
|
||||
}, [assembleExtractorNodeId, ensureExtractorNode, handleSyncWorkflowDraft, reactFlowStore])
|
||||
|
||||
type DetectedAgent = {
|
||||
nodeId: string
|
||||
name: string
|
||||
@ -315,7 +409,7 @@ const MixedVariableTextInput = ({
|
||||
return
|
||||
|
||||
const escapedAgentId = detectedAgent.nodeId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const leadingPattern = new RegExp(`^\\{\\{[@#]${escapedAgentId}\\.context[@#]\\}\\}`)
|
||||
const leadingPattern = new RegExp(`^\\{\\{@${escapedAgentId}\\.context@\\}\\}`)
|
||||
const promptText = text.replace(leadingPattern, '')
|
||||
|
||||
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||
@ -385,45 +479,25 @@ const MixedVariableTextInput = ({
|
||||
const newValue = `{{@${agent.id}.context@}}${valueWithoutTrigger}`
|
||||
|
||||
if (toolNodeId && paramKey) {
|
||||
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||
const defaultValue = nodesMetaDataMap?.[BlockEnum.LLM]?.defaultValue as Partial<LLMNodeType> | undefined
|
||||
const { getNodes, setNodes } = reactFlowStore.getState()
|
||||
const nodes = getNodes()
|
||||
const hasExtractorNode = nodes.some(node => node.id === extractorNodeId)
|
||||
|
||||
if (!hasExtractorNode && defaultValue) {
|
||||
const { newNode } = generateNewNode({
|
||||
id: extractorNodeId,
|
||||
type: getNodeCustomTypeByNodeDataType(BlockEnum.LLM),
|
||||
data: {
|
||||
...defaultValue,
|
||||
type: BlockEnum.LLM,
|
||||
title: defaultValue?.title ?? '',
|
||||
desc: defaultValue.desc || '',
|
||||
parent_node_id: toolNodeId,
|
||||
structured_output_enabled: true,
|
||||
structured_output: {
|
||||
schema: {
|
||||
type: Type.object,
|
||||
properties: {
|
||||
[paramKey]: {
|
||||
type: Type.string,
|
||||
},
|
||||
ensureExtractorNode({
|
||||
extractorNodeId: `${toolNodeId}_ext_${paramKey}`,
|
||||
nodeType: BlockEnum.LLM,
|
||||
data: {
|
||||
structured_output_enabled: true,
|
||||
structured_output: {
|
||||
schema: {
|
||||
type: Type.object,
|
||||
properties: {
|
||||
[paramKey]: {
|
||||
type: Type.string,
|
||||
},
|
||||
required: [paramKey],
|
||||
additionalProperties: false,
|
||||
},
|
||||
required: [paramKey],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
hidden: true,
|
||||
})
|
||||
setNodes([...nodes, newNode])
|
||||
handleSyncWorkflowDraft()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const mentionConfigWithOutputSelector: MentionConfig = {
|
||||
@ -434,71 +508,26 @@ const MixedVariableTextInput = ({
|
||||
onChange(newValue, VarKindTypeEnum.mention, mentionConfigWithOutputSelector)
|
||||
syncExtractorPromptFromText(newValue)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [handleSyncWorkflowDraft, nodesMetaDataMap, onChange, paramKey, reactFlowStore, setControlPromptEditorRerenderKey, syncExtractorPromptFromText, toolNodeId, value])
|
||||
}, [ensureExtractorNode, onChange, paramKey, setControlPromptEditorRerenderKey, syncExtractorPromptFromText, toolNodeId, value])
|
||||
|
||||
const handleAssembleSelect = useCallback(() => {
|
||||
if (!onChange || !toolNodeId || !paramKey || !assemblePlaceholder)
|
||||
return
|
||||
|
||||
const defaultValue = nodesMetaDataMap?.[BlockEnum.Code]?.defaultValue as Partial<CodeNodeType> | undefined
|
||||
if (!defaultValue)
|
||||
return
|
||||
|
||||
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||
const { getNodes, setNodes } = reactFlowStore.getState()
|
||||
const currentNodes = getNodes()
|
||||
const existingNode = currentNodes.find(node => node.id === extractorNodeId)
|
||||
const shouldReplace = existingNode && existingNode.data.type !== BlockEnum.Code
|
||||
const shouldCreate = !existingNode || shouldReplace
|
||||
|
||||
if (shouldCreate) {
|
||||
const nextNodes = shouldReplace
|
||||
? currentNodes.filter(node => node.id !== extractorNodeId)
|
||||
: currentNodes
|
||||
const { newNode } = generateNewNode({
|
||||
id: extractorNodeId,
|
||||
type: getNodeCustomTypeByNodeDataType(BlockEnum.Code),
|
||||
data: {
|
||||
...defaultValue,
|
||||
type: BlockEnum.Code,
|
||||
title: defaultValue?.title ?? '',
|
||||
desc: defaultValue?.desc || '',
|
||||
parent_node_id: toolNodeId,
|
||||
outputs: {
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
hidden: true,
|
||||
})
|
||||
setNodes([...nextNodes, newNode])
|
||||
handleSyncWorkflowDraft()
|
||||
}
|
||||
|
||||
const mentionConfigWithOutputSelector: MentionConfig = {
|
||||
...DEFAULT_MENTION_CONFIG,
|
||||
extractor_node_id: extractorNodeId,
|
||||
output_selector: ['result'],
|
||||
}
|
||||
onChange(assemblePlaceholder, VarKindTypeEnum.mention, mentionConfigWithOutputSelector)
|
||||
const handleAssembleSelect = useCallback((): ValueSelector | null => {
|
||||
if (!toolNodeId || !paramKey || !assemblePlaceholder)
|
||||
return null
|
||||
const extractorNodeId = assembleExtractorNodeId || `${toolNodeId}_ext_${paramKey}`
|
||||
ensureAssembleExtractorNode()
|
||||
onChange?.(assemblePlaceholder, VarKindTypeEnum.mixed, null)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [assemblePlaceholder, handleSyncWorkflowDraft, nodesMetaDataMap, onChange, paramKey, reactFlowStore, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
return [extractorNodeId, 'result']
|
||||
}, [assembleExtractorNodeId, assemblePlaceholder, ensureAssembleExtractorNode, onChange, paramKey, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
|
||||
const handleAssembleRemove = useCallback(() => {
|
||||
if (!onChange || !assemblePlaceholder)
|
||||
return
|
||||
|
||||
const nextValue = value.replace(assemblePlaceholder, '')
|
||||
removeExtractorNode()
|
||||
onChange(nextValue, VarKindTypeEnum.mixed, null)
|
||||
onChange('', VarKindTypeEnum.mixed, null)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [assemblePlaceholder, onChange, removeExtractorNode, setControlPromptEditorRerenderKey, value])
|
||||
}, [assemblePlaceholder, onChange, removeExtractorNode, setControlPromptEditorRerenderKey])
|
||||
|
||||
const handleOpenSubGraphModal = useCallback(() => {
|
||||
setIsSubGraphModalOpen(true)
|
||||
|
||||
@ -20,7 +20,7 @@ import { useStrategyProviders } from '@/service/use-strategy'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { VarType } from './types'
|
||||
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{[@#]([^.@#]+)\.context[@#]\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{@([^.@#]+)\.context@\}\}/g
|
||||
type AgentCheckValidContext = {
|
||||
provider?: StrategyPluginDetail
|
||||
strategy?: StrategyDetail
|
||||
|
||||
Reference in New Issue
Block a user