mirror of https://github.com/langgenius/dify.git
feat: Add @agent icon and implement agent alias variables in workflow
inspector
This commit is contained in:
parent
772dbe620d
commit
54fce5e903
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "14",
|
||||
"height": "14",
|
||||
"viewBox": "0 0 14 14",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M11.6665 7.00008C11.6665 4.42275 9.57718 2.33341 6.99984 2.33341C4.42251 2.33341 2.33317 4.42275 2.33317 7.00008C2.33317 9.57742 4.42251 11.6667 6.99984 11.6667C7.95755 11.6667 8.8479 11.3782 9.58873 10.8834L10.2359 11.8542C9.30995 12.4728 8.19701 12.8334 6.99984 12.8334C3.77817 12.8334 1.1665 10.2217 1.1665 7.00008C1.1665 3.77842 3.77817 1.16675 6.99984 1.16675C10.2215 1.16675 12.8332 3.77842 12.8332 7.00008V7.87508C12.8332 9.00266 11.9191 9.91675 10.7915 9.91675C10.0891 9.91675 9.46944 9.56196 9.10205 9.02186C8.57145 9.57346 7.82572 9.91675 6.99984 9.91675C5.38901 9.91675 4.08317 8.6109 4.08317 7.00008C4.08317 5.38925 5.38901 4.08341 6.99984 4.08341C7.65655 4.08341 8.26258 4.30047 8.75013 4.66675H9.9165V7.87508C9.9165 8.35831 10.3083 8.75008 10.7915 8.75008C11.2747 8.75008 11.6665 8.35831 11.6665 7.87508V7.00008ZM6.99984 5.25008C6.03331 5.25008 5.24984 6.03356 5.24984 7.00008C5.24984 7.96661 6.03331 8.75008 6.99984 8.75008C7.96636 8.75008 8.74984 7.96661 8.74984 7.00008C8.74984 6.03356 7.96636 5.25008 6.99984 5.25008Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "AtSign"
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './AtSign.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'AtSign'
|
||||
|
||||
export default Icon
|
||||
|
|
@ -3,6 +3,7 @@ export { default as Answer } from './Answer'
|
|||
export { default as ApiAggregate } from './ApiAggregate'
|
||||
export { default as Assigner } from './Assigner'
|
||||
export { default as Asterisk } from './Asterisk'
|
||||
export { default as AtSign } from './AtSign'
|
||||
export { default as CalendarCheckLine } from './CalendarCheckLine'
|
||||
export { default as Code } from './Code'
|
||||
export { default as Datasource } from './Datasource'
|
||||
|
|
|
|||
|
|
@ -58,10 +58,12 @@ const SubGraphMain: FC<SubGraphMainProps> = (props) => {
|
|||
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
||||
flowType,
|
||||
flowId,
|
||||
interactionMode: InteractionMode.Subgraph,
|
||||
})
|
||||
const inspectVarsCrud = useInspectVarsCrudCommon({
|
||||
flowType,
|
||||
flowId,
|
||||
interactionMode: InteractionMode.Subgraph,
|
||||
})
|
||||
|
||||
const handleSyncSubGraphDraft = useCallback(async () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,265 @@
|
|||
import type { Node } from '@/app/components/workflow/types'
|
||||
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
|
||||
import { BlockEnum, VarType } from '@/app/components/workflow/types'
|
||||
import { AGENT_CONTEXT_VAR_PATTERN, getAgentNodeIdFromContextVar } from '@/app/components/workflow/utils/agent-context'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
|
||||
const buildAliasVarId = (mapping: AgentAliasMapping) => {
|
||||
const outputPath = mapping.outputSelector.join('.')
|
||||
return `alias:${mapping.parentNodeId}:${mapping.extractorNodeId}:${outputPath}`
|
||||
}
|
||||
|
||||
type AgentAliasMapping = {
|
||||
parentNodeId: string
|
||||
extractorNodeId: string
|
||||
outputSelector: string[]
|
||||
aliasName: string
|
||||
}
|
||||
|
||||
type ToolParameterShape = {
|
||||
value?: unknown
|
||||
nested_node_config?: {
|
||||
extractor_node_id?: string
|
||||
output_selector?: unknown
|
||||
}
|
||||
}
|
||||
|
||||
type ToolNodeDataShape = {
|
||||
tool_parameters?: Record<string, ToolParameterShape>
|
||||
}
|
||||
|
||||
type AliasSource = {
|
||||
sourceVar: VarInInspect
|
||||
matchedSelector: string[]
|
||||
resolvedValue: unknown
|
||||
resolvedValueFound: boolean
|
||||
usedFallback: boolean
|
||||
}
|
||||
|
||||
const toSelectorKey = (selector: string[]) => selector.join('.')
|
||||
|
||||
const resolveOutputSelector = (extractorNodeId: string, rawSelector?: unknown): string[] => {
|
||||
if (!Array.isArray(rawSelector))
|
||||
return []
|
||||
if (rawSelector[0] === extractorNodeId)
|
||||
return rawSelector.slice(1)
|
||||
return rawSelector as string[]
|
||||
}
|
||||
|
||||
const collectAgentAliasMappings = (nodes: Node[]) => {
|
||||
const nodesById = new Map(nodes.map(node => [node.id, node]))
|
||||
const mappings: AgentAliasMapping[] = []
|
||||
const extractorNodeIds = new Set<string>()
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (node.data.type !== BlockEnum.Tool)
|
||||
return
|
||||
const toolData = node.data as ToolNodeDataShape
|
||||
const toolParams = toolData.tool_parameters || {}
|
||||
Object.entries(toolParams).forEach(([paramKey, param]) => {
|
||||
const value = param?.value
|
||||
if (typeof value !== 'string')
|
||||
return
|
||||
const matches = Array.from(value.matchAll(AGENT_CONTEXT_VAR_PATTERN))
|
||||
if (!matches.length)
|
||||
return
|
||||
const agentNodeId = getAgentNodeIdFromContextVar(matches[0][0])
|
||||
if (!agentNodeId)
|
||||
return
|
||||
const extractorNodeId = param?.nested_node_config?.extractor_node_id || `${node.id}_ext_${paramKey}`
|
||||
extractorNodeIds.add(extractorNodeId)
|
||||
const resolvedOutputSelector = resolveOutputSelector(extractorNodeId, param?.nested_node_config?.output_selector)
|
||||
const outputSelector = resolvedOutputSelector.length > 0
|
||||
? resolvedOutputSelector
|
||||
: ['structured_output', paramKey]
|
||||
const agentNode = nodesById.get(agentNodeId)
|
||||
const aliasName = `@${agentNode?.data.title || agentNodeId}`
|
||||
|
||||
mappings.push({
|
||||
parentNodeId: node.id,
|
||||
extractorNodeId,
|
||||
outputSelector,
|
||||
aliasName,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return { mappings, extractorNodeIds, nodesById }
|
||||
}
|
||||
|
||||
const findAliasSourceVar = (vars: VarInInspect[], extractorNodeId: string, outputSelector: string[]) => {
|
||||
const selectorKey = toSelectorKey(outputSelector)
|
||||
return vars.find((varItem) => {
|
||||
const selector = Array.isArray(varItem.selector) ? varItem.selector : []
|
||||
if (selector[0] !== extractorNodeId)
|
||||
return false
|
||||
return toSelectorKey(selector.slice(1)) === selectorKey
|
||||
})
|
||||
}
|
||||
|
||||
const resolveNestedValue = (value: unknown, path: string[]): { found: boolean, value: unknown } => {
|
||||
let current: unknown = value
|
||||
for (const key of path) {
|
||||
if (Array.isArray(current)) {
|
||||
const index = Number.parseInt(key, 10)
|
||||
if (!Number.isNaN(index) && index >= 0 && index < current.length) {
|
||||
current = current[index]
|
||||
continue
|
||||
}
|
||||
return { found: false, value: undefined }
|
||||
}
|
||||
if (current && typeof current === 'object') {
|
||||
if (Object.prototype.hasOwnProperty.call(current, key)) {
|
||||
current = (current as Record<string, unknown>)[key]
|
||||
continue
|
||||
}
|
||||
}
|
||||
return { found: false, value: undefined }
|
||||
}
|
||||
return { found: true, value: current }
|
||||
}
|
||||
|
||||
const resolveAliasSourceVar = (
|
||||
vars: VarInInspect[],
|
||||
extractorNodeId: string,
|
||||
outputSelector: string[],
|
||||
): AliasSource | undefined => {
|
||||
const directMatch = findAliasSourceVar(vars, extractorNodeId, outputSelector)
|
||||
if (directMatch) {
|
||||
return {
|
||||
sourceVar: directMatch,
|
||||
matchedSelector: directMatch.selector as string[],
|
||||
resolvedValue: directMatch.value,
|
||||
resolvedValueFound: true,
|
||||
usedFallback: false,
|
||||
}
|
||||
}
|
||||
if (outputSelector.length === 0)
|
||||
return undefined
|
||||
const prefixKey = outputSelector[0]
|
||||
const prefixVar = vars.find((varItem) => {
|
||||
const selector = Array.isArray(varItem.selector) ? varItem.selector : []
|
||||
if (selector[0] !== extractorNodeId)
|
||||
return false
|
||||
return selector[1] === prefixKey && selector.length === 2
|
||||
})
|
||||
if (!prefixVar)
|
||||
return undefined
|
||||
if (outputSelector.length === 1) {
|
||||
return {
|
||||
sourceVar: prefixVar,
|
||||
matchedSelector: prefixVar.selector as string[],
|
||||
resolvedValue: prefixVar.value,
|
||||
resolvedValueFound: true,
|
||||
usedFallback: true,
|
||||
}
|
||||
}
|
||||
const resolved = resolveNestedValue(prefixVar.value, outputSelector.slice(1))
|
||||
return {
|
||||
sourceVar: prefixVar,
|
||||
matchedSelector: prefixVar.selector as string[],
|
||||
resolvedValue: resolved.value,
|
||||
resolvedValueFound: resolved.found,
|
||||
usedFallback: true,
|
||||
}
|
||||
}
|
||||
|
||||
export const applyAgentSubgraphInspectVars = (nodesWithInspectVars: NodeWithVar[], allNodes: Node[]) => {
|
||||
const hideExtractorNodes = true
|
||||
const { mappings, extractorNodeIds, nodesById } = collectAgentAliasMappings(allNodes)
|
||||
if (mappings.length === 0 && extractorNodeIds.size === 0) {
|
||||
return nodesWithInspectVars
|
||||
}
|
||||
|
||||
const resultMap = new Map<string, NodeWithVar>()
|
||||
nodesWithInspectVars.forEach((node) => {
|
||||
const isExtractorNode = extractorNodeIds.has(node.nodeId)
|
||||
resultMap.set(node.nodeId, {
|
||||
...node,
|
||||
vars: [...node.vars],
|
||||
isHidden: hideExtractorNodes && isExtractorNode,
|
||||
})
|
||||
})
|
||||
|
||||
const getOrCreateParentNode = (parentNodeId: string): NodeWithVar | undefined => {
|
||||
const existing = resultMap.get(parentNodeId)
|
||||
if (existing)
|
||||
return existing
|
||||
const parentNode = nodesById.get(parentNodeId)
|
||||
if (!parentNode)
|
||||
return undefined
|
||||
return {
|
||||
nodeId: parentNode.id,
|
||||
nodeType: parentNode.data.type,
|
||||
title: parentNode.data.title,
|
||||
nodePayload: parentNode.data,
|
||||
vars: [] as VarInInspect[],
|
||||
isValueFetched: false,
|
||||
isHidden: false,
|
||||
}
|
||||
}
|
||||
|
||||
mappings.forEach((mapping) => {
|
||||
const parent = getOrCreateParentNode(mapping.parentNodeId)
|
||||
if (!parent)
|
||||
return
|
||||
const parentVars = parent.vars as VarInInspect[]
|
||||
const aliasId = buildAliasVarId(mapping)
|
||||
const upsertAliasVar = (aliasVar: VarInInspect, shouldOverwrite: boolean) => {
|
||||
const existingIndex = parentVars.findIndex(varItem => varItem.id === aliasVar.id)
|
||||
if (existingIndex === -1)
|
||||
parentVars.unshift(aliasVar)
|
||||
else if (shouldOverwrite)
|
||||
parentVars[existingIndex] = { ...parentVars[existingIndex], ...aliasVar }
|
||||
}
|
||||
const placeholderAliasVar: VarInInspect = {
|
||||
id: aliasId,
|
||||
type: VarInInspectType.node,
|
||||
name: mapping.aliasName,
|
||||
description: '',
|
||||
selector: [mapping.parentNodeId, mapping.aliasName],
|
||||
value_type: VarType.any,
|
||||
value: undefined,
|
||||
edited: false,
|
||||
visible: true,
|
||||
is_truncated: false,
|
||||
full_content: { size_bytes: 0, download_url: '' },
|
||||
aliasMeta: {
|
||||
extractorNodeId: mapping.extractorNodeId,
|
||||
outputSelector: mapping.outputSelector,
|
||||
sourceVarId: aliasId,
|
||||
},
|
||||
}
|
||||
const extractorGroup = nodesWithInspectVars.find(node => node.nodeId === mapping.extractorNodeId)
|
||||
if (!extractorGroup?.vars?.length) {
|
||||
upsertAliasVar(placeholderAliasVar, false)
|
||||
resultMap.set(mapping.parentNodeId, parent)
|
||||
return
|
||||
}
|
||||
const resolved = resolveAliasSourceVar(extractorGroup.vars, mapping.extractorNodeId, mapping.outputSelector)
|
||||
if (!resolved) {
|
||||
upsertAliasVar(placeholderAliasVar, false)
|
||||
resultMap.set(mapping.parentNodeId, parent)
|
||||
return
|
||||
}
|
||||
const resolvedValue = resolved.resolvedValueFound ? resolved.resolvedValue : resolved.sourceVar.value
|
||||
const aliasVar: VarInInspect = {
|
||||
...resolved.sourceVar,
|
||||
id: aliasId,
|
||||
name: mapping.aliasName,
|
||||
selector: [mapping.parentNodeId, mapping.aliasName],
|
||||
value: resolvedValue,
|
||||
visible: true,
|
||||
aliasMeta: {
|
||||
extractorNodeId: mapping.extractorNodeId,
|
||||
outputSelector: mapping.outputSelector,
|
||||
sourceVarId: resolved.sourceVar.id,
|
||||
},
|
||||
}
|
||||
upsertAliasVar(aliasVar, true)
|
||||
resultMap.set(mapping.parentNodeId, parent)
|
||||
})
|
||||
|
||||
// TODO: handle assemble sub-graph output mapping.
|
||||
return Array.from(resultMap.values())
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import type { FlowType } from '@/types/common'
|
|||
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { InteractionMode } from '@/app/components/workflow'
|
||||
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
|
|
@ -16,15 +17,18 @@ import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@
|
|||
import { fetchAllInspectVars } from '@/service/workflow'
|
||||
import useMatchSchemaType, { getMatchedSchemaType } from '../nodes/_base/components/variable/use-match-schema-type'
|
||||
import { toNodeOutputVars } from '../nodes/_base/components/variable/utils'
|
||||
import { applyAgentSubgraphInspectVars } from './inspect-vars-agent-alias'
|
||||
|
||||
type Params = {
|
||||
flowType: FlowType
|
||||
flowId: string
|
||||
interactionMode?: InteractionMode
|
||||
}
|
||||
|
||||
export const useSetWorkflowVarsWithValue = ({
|
||||
flowType,
|
||||
flowId,
|
||||
interactionMode,
|
||||
}: Params) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const store = useStoreApi()
|
||||
|
|
@ -94,7 +98,10 @@ export const useSetWorkflowVarsWithValue = ({
|
|||
}
|
||||
return nodeWithVar
|
||||
})
|
||||
setNodesWithInspectVars(res)
|
||||
const resolvedInteractionMode = interactionMode ?? InteractionMode.Default
|
||||
const shouldApplyAlias = resolvedInteractionMode !== InteractionMode.Subgraph
|
||||
const nextNodes = shouldApplyAlias ? applyAgentSubgraphInspectVars(res, nodeArr) : res
|
||||
setNodesWithInspectVars(nextNodes)
|
||||
}
|
||||
|
||||
const fetchInspectVars = useCallback(async (params: {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { VarInInspect } from '@/types/workflow'
|
|||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { InteractionMode } from '@/app/components/workflow'
|
||||
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
|
||||
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
||||
import {
|
||||
|
|
@ -23,14 +24,17 @@ import {
|
|||
} from '@/service/use-tools'
|
||||
import { fetchNodeInspectVars } from '@/service/workflow'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
import { applyAgentSubgraphInspectVars } from './inspect-vars-agent-alias'
|
||||
|
||||
type Params = {
|
||||
flowId: string
|
||||
flowType: FlowType
|
||||
interactionMode?: InteractionMode
|
||||
}
|
||||
export const useInspectVarsCrudCommon = ({
|
||||
flowId,
|
||||
flowType,
|
||||
interactionMode,
|
||||
}: Params) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const store = useStoreApi()
|
||||
|
|
@ -108,6 +112,7 @@ export const useInspectVarsCrudCommon = ({
|
|||
const fetchInspectVarValue = useCallback(async (selector: ValueSelector, schemaTypeDefinitions: SchemaTypeDefinition[]) => {
|
||||
const {
|
||||
setNodeInspectVars,
|
||||
setNodesWithInspectVars,
|
||||
dataSourceList,
|
||||
} = workflowStore.getState()
|
||||
const nodeId = selector[0]
|
||||
|
|
@ -141,7 +146,13 @@ export const useInspectVarsCrudCommon = ({
|
|||
}
|
||||
})
|
||||
setNodeInspectVars(nodeId, varsWithSchemaType)
|
||||
}, [workflowStore, flowType, flowId, invalidateSysVarValues, invalidateConversationVarValues, buildInTools, customTools, workflowTools, mcpTools])
|
||||
const resolvedInteractionMode = interactionMode ?? InteractionMode.Default
|
||||
if (resolvedInteractionMode !== InteractionMode.Subgraph) {
|
||||
const { nodesWithInspectVars } = workflowStore.getState()
|
||||
const nextNodes = applyAgentSubgraphInspectVars(nodesWithInspectVars, nodeArr)
|
||||
setNodesWithInspectVars(nextNodes)
|
||||
}
|
||||
}, [workflowStore, flowType, flowId, invalidateSysVarValues, invalidateConversationVarValues, buildInTools, customTools, workflowTools, mcpTools, interactionMode])
|
||||
|
||||
// after last run would call this
|
||||
const appendNodeInspectVars = useCallback((nodeId: string, payload: VarInInspect[], allNodes: Node[]) => {
|
||||
|
|
@ -169,9 +180,12 @@ export const useInspectVarsCrudCommon = ({
|
|||
}
|
||||
}
|
||||
})
|
||||
setNodesWithInspectVars(nodes)
|
||||
const resolvedInteractionMode = interactionMode ?? InteractionMode.Default
|
||||
const shouldApplyAlias = resolvedInteractionMode !== InteractionMode.Subgraph
|
||||
const nextNodes = shouldApplyAlias ? applyAgentSubgraphInspectVars(nodes, allNodes) : nodes
|
||||
setNodesWithInspectVars(nextNodes)
|
||||
handleCancelNodeSuccessStatus(nodeId)
|
||||
}, [workflowStore, handleCancelNodeSuccessStatus])
|
||||
}, [workflowStore, handleCancelNodeSuccessStatus, interactionMode])
|
||||
|
||||
const hasNodeInspectVar = useCallback((nodeId: string, varId: string) => {
|
||||
const { nodesWithInspectVars } = workflowStore.getState()
|
||||
|
|
|
|||
|
|
@ -18,15 +18,7 @@ import { FlowType } from '@/types/common'
|
|||
|
||||
// Constants
|
||||
|
||||
export const AGENT_CONTEXT_VAR_PATTERN = /\{\{@[^.@#]+\.context@\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PREFIX = '{{@'
|
||||
const AGENT_CONTEXT_VAR_SUFFIX = '.context@}}'
|
||||
|
||||
export const getAgentNodeIdFromContextVar = (placeholder: string): string => {
|
||||
if (!placeholder.startsWith(AGENT_CONTEXT_VAR_PREFIX) || !placeholder.endsWith(AGENT_CONTEXT_VAR_SUFFIX))
|
||||
return ''
|
||||
return placeholder.slice(AGENT_CONTEXT_VAR_PREFIX.length, -AGENT_CONTEXT_VAR_SUFFIX.length)
|
||||
}
|
||||
export { AGENT_CONTEXT_VAR_PATTERN, getAgentNodeIdFromContextVar } from '@/app/components/workflow/utils/agent-context'
|
||||
|
||||
export const buildAssemblePlaceholder = (toolNodeId?: string, paramKey?: string): string => {
|
||||
if (!toolNodeId || !paramKey)
|
||||
|
|
|
|||
|
|
@ -15,19 +15,12 @@ import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data
|
|||
import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation'
|
||||
import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { AGENT_CONTEXT_VAR_PATTERN, getAgentNodeIdFromContextVar } from '@/app/components/workflow/utils/agent-context'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
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_PREFIX = '{{@'
|
||||
const AGENT_CONTEXT_VAR_SUFFIX = '.context@}}'
|
||||
const getAgentNodeIdFromContextVar = (placeholder: string) => {
|
||||
if (!placeholder.startsWith(AGENT_CONTEXT_VAR_PREFIX) || !placeholder.endsWith(AGENT_CONTEXT_VAR_SUFFIX))
|
||||
return ''
|
||||
return placeholder.slice(AGENT_CONTEXT_VAR_PREFIX.length, -AGENT_CONTEXT_VAR_SUFFIX.length)
|
||||
}
|
||||
type AgentCheckValidContext = {
|
||||
provider?: StrategyPluginDetail
|
||||
strategy?: StrategyDetail
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export const AGENT_CONTEXT_VAR_PATTERN = /\{\{@[^.@#]+\.context@\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PREFIX = '{{@'
|
||||
const AGENT_CONTEXT_VAR_SUFFIX = '.context@}}'
|
||||
|
||||
export const getAgentNodeIdFromContextVar = (placeholder: string): string => {
|
||||
if (!placeholder.startsWith(AGENT_CONTEXT_VAR_PREFIX) || !placeholder.endsWith(AGENT_CONTEXT_VAR_SUFFIX))
|
||||
return ''
|
||||
return placeholder.slice(AGENT_CONTEXT_VAR_PREFIX.length, -AGENT_CONTEXT_VAR_SUFFIX.length)
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import { useState } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
// import Button from '@/app/components/base/button'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { AtSign } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
|
||||
|
|
@ -147,24 +148,32 @@ const Group = ({
|
|||
{/* var item list */}
|
||||
{!isCollapsed && !nodeData?.isSingRunRunning && (
|
||||
<div className="px-0.5">
|
||||
{visibleVarList.length > 0 && visibleVarList.map(varItem => (
|
||||
<div
|
||||
key={varItem.id}
|
||||
className={cn(
|
||||
'relative flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover',
|
||||
varItem.id === currentVar?.var?.id && 'bg-state-base-hover-alt hover:bg-state-base-hover-alt',
|
||||
)}
|
||||
onClick={() => handleSelectVar(varItem, varType)}
|
||||
>
|
||||
<VariableIconWithColor
|
||||
variableCategory={varType}
|
||||
isExceptionVariable={['error_type', 'error_message'].includes(varItem.name)}
|
||||
className="size-4"
|
||||
/>
|
||||
<div className="system-sm-medium grow truncate text-text-secondary">{varItem.name}</div>
|
||||
<div className="system-xs-regular shrink-0 text-text-tertiary">{formatVarTypeLabel(varItem.value_type)}</div>
|
||||
</div>
|
||||
))}
|
||||
{visibleVarList.length > 0 && visibleVarList.map((varItem) => {
|
||||
const isAgentAliasVar = typeof varItem.name === 'string' && varItem.name.startsWith('@')
|
||||
const displayName = isAgentAliasVar ? varItem.name.slice(1) : varItem.name
|
||||
return (
|
||||
<div
|
||||
key={varItem.id}
|
||||
className={cn(
|
||||
'relative flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover',
|
||||
varItem.id === currentVar?.var?.id && 'bg-state-base-hover-alt hover:bg-state-base-hover-alt',
|
||||
)}
|
||||
onClick={() => handleSelectVar(varItem, varType)}
|
||||
>
|
||||
{isAgentAliasVar
|
||||
? <AtSign className="size-4 shrink-0 text-util-colors-violet-violet-600" />
|
||||
: (
|
||||
<VariableIconWithColor
|
||||
variableCategory={varType}
|
||||
isExceptionVariable={['error_type', 'error_message'].includes(varItem.name)}
|
||||
className="size-4"
|
||||
/>
|
||||
)}
|
||||
<div className="system-sm-medium grow truncate text-text-secondary">{displayName}</div>
|
||||
<div className="system-xs-regular shrink-0 text-text-tertiary">{formatVarTypeLabel(varItem.value_type)}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ const Left = ({
|
|||
} = useCurrentVars()
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
|
||||
const visibleNodesWithInspectVars = nodesWithInspectVars.filter(node => !node.isHidden)
|
||||
|
||||
const showDivider = environmentVariables.length > 0 || conversationVars.length > 0 || systemVars.length > 0
|
||||
|
||||
const handleClearAll = () => {
|
||||
|
|
@ -91,7 +93,7 @@ const Left = ({
|
|||
</div>
|
||||
)}
|
||||
{/* group nodes */}
|
||||
{nodesWithInspectVars.length > 0 && nodesWithInspectVars.map(group => (
|
||||
{visibleNodesWithInspectVars.length > 0 && visibleNodesWithInspectVars.map(group => (
|
||||
<Group
|
||||
key={group.nodeId}
|
||||
varType={VarInInspectType.node}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@ import Empty from './empty'
|
|||
import Left from './left'
|
||||
import Listening from './listening'
|
||||
import Right from './right'
|
||||
import { toEnvVarInInspect } from './utils'
|
||||
|
||||
export type currentVarType = {
|
||||
nodeId: string
|
||||
nodeType: string
|
||||
title: string
|
||||
isValueFetched?: boolean
|
||||
var: VarInInspect
|
||||
nodeData: NodeProps['data']
|
||||
var?: VarInInspect
|
||||
nodeData?: NodeProps['data']
|
||||
}
|
||||
|
||||
const Panel: FC = () => {
|
||||
|
|
@ -59,23 +60,12 @@ const Panel: FC = () => {
|
|||
return
|
||||
if (currentFocusNodeId === VarInInspectType.environment) {
|
||||
const currentVar = environmentVariables.find(v => v.id === currentVarId)
|
||||
const res = {
|
||||
return {
|
||||
nodeId: VarInInspectType.environment,
|
||||
title: VarInInspectType.environment,
|
||||
nodeType: VarInInspectType.environment,
|
||||
var: currentVar ? toEnvVarInInspect(currentVar) : undefined,
|
||||
}
|
||||
if (currentVar) {
|
||||
return {
|
||||
...res,
|
||||
var: {
|
||||
...currentVar,
|
||||
type: VarInInspectType.environment,
|
||||
visible: true,
|
||||
...(currentVar.value_type === 'secret' ? { value: '******************' } : {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
if (currentFocusNodeId === VarInInspectType.conversation) {
|
||||
const currentVar = conversationVars.find(v => v.id === currentVarId)
|
||||
|
|
@ -83,15 +73,12 @@ const Panel: FC = () => {
|
|||
nodeId: VarInInspectType.conversation,
|
||||
title: VarInInspectType.conversation,
|
||||
nodeType: VarInInspectType.conversation,
|
||||
}
|
||||
if (currentVar) {
|
||||
return {
|
||||
...res,
|
||||
var: {
|
||||
...currentVar,
|
||||
type: VarInInspectType.conversation,
|
||||
},
|
||||
}
|
||||
var: currentVar
|
||||
? {
|
||||
...currentVar,
|
||||
type: VarInInspectType.conversation,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
@ -101,15 +88,12 @@ const Panel: FC = () => {
|
|||
nodeId: VarInInspectType.system,
|
||||
title: VarInInspectType.system,
|
||||
nodeType: VarInInspectType.system,
|
||||
}
|
||||
if (currentVar) {
|
||||
return {
|
||||
...res,
|
||||
var: {
|
||||
...currentVar,
|
||||
type: VarInInspectType.system,
|
||||
},
|
||||
}
|
||||
var: currentVar
|
||||
? {
|
||||
...currentVar,
|
||||
type: VarInInspectType.system,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
@ -124,22 +108,32 @@ const Panel: FC = () => {
|
|||
isSingRunRunning: targetNode.isSingRunRunning,
|
||||
isValueFetched: targetNode.isValueFetched,
|
||||
nodeData: targetNode.nodePayload,
|
||||
...(currentVar ? { var: currentVar } : {}),
|
||||
var: currentVar,
|
||||
}
|
||||
}, [currentFocusNodeId, currentVarId, environmentVariables, conversationVars, systemVars, nodesWithInspectVars])
|
||||
|
||||
const currentAliasMeta = useMemo(() => {
|
||||
if (!currentFocusNodeId || !currentVarId)
|
||||
return undefined
|
||||
const targetNode = nodesWithInspectVars.find(node => node.nodeId === currentFocusNodeId)
|
||||
const targetVar = targetNode?.vars.find(v => v.id === currentVarId)
|
||||
return targetVar?.aliasMeta
|
||||
}, [currentFocusNodeId, currentVarId, nodesWithInspectVars])
|
||||
const fetchNodeId = currentAliasMeta?.extractorNodeId || currentFocusNodeId
|
||||
|
||||
const isCurrentNodeVarValueFetching = useMemo(() => {
|
||||
if (!currentNodeInfo)
|
||||
if (!fetchNodeId)
|
||||
return false
|
||||
const targetNode = nodesWithInspectVars.find(node => node.nodeId === currentNodeInfo.nodeId)
|
||||
const targetNode = nodesWithInspectVars.find(node => node.nodeId === fetchNodeId)
|
||||
if (!targetNode)
|
||||
return false
|
||||
return !targetNode.isValueFetched
|
||||
}, [currentNodeInfo, nodesWithInspectVars])
|
||||
}, [fetchNodeId, nodesWithInspectVars])
|
||||
|
||||
const handleNodeVarSelect = useCallback((node: currentVarType) => {
|
||||
setCurrentFocusNodeId(node.nodeId)
|
||||
setCurrentVarId(node.var.id)
|
||||
if (node.var)
|
||||
setCurrentVarId(node.var.id)
|
||||
}, [setCurrentFocusNodeId, setCurrentVarId])
|
||||
|
||||
const { isLoading, schemaTypeDefinitions } = useMatchSchemaType()
|
||||
|
|
@ -150,12 +144,12 @@ const Panel: FC = () => {
|
|||
}, [eventEmitter])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentFocusNodeId && currentVarId && !isLoading) {
|
||||
const targetNode = nodesWithInspectVars.find(node => node.nodeId === currentFocusNodeId)
|
||||
if (currentFocusNodeId && currentVarId && !isLoading && fetchNodeId) {
|
||||
const targetNode = nodesWithInspectVars.find(node => node.nodeId === fetchNodeId)
|
||||
if (targetNode && !targetNode.isValueFetched)
|
||||
fetchInspectVarValue([currentFocusNodeId], schemaTypeDefinitions!)
|
||||
fetchInspectVarValue([fetchNodeId], schemaTypeDefinitions!)
|
||||
}
|
||||
}, [currentFocusNodeId, currentVarId, nodesWithInspectVars, fetchInspectVarValue, schemaTypeDefinitions, isLoading])
|
||||
}, [currentFocusNodeId, currentVarId, nodesWithInspectVars, fetchInspectVarValue, schemaTypeDefinitions, isLoading, fetchNodeId])
|
||||
|
||||
if (isListening) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -55,8 +55,14 @@ const Right = ({
|
|||
const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel)
|
||||
const setCurrentFocusNodeId = useStore(s => s.setCurrentFocusNodeId)
|
||||
const toolIcon = useToolIcon(currentNodeVar?.nodeData)
|
||||
const isTruncated = currentNodeVar?.var.is_truncated
|
||||
const fullContent = currentNodeVar?.var.full_content
|
||||
const currentVar = currentNodeVar?.var
|
||||
const currentNodeType = currentNodeVar?.nodeType
|
||||
const currentNodeTitle = currentNodeVar?.title
|
||||
const currentNodeId = currentNodeVar?.nodeId
|
||||
const isTruncated = !!currentVar?.is_truncated
|
||||
const fullContent = currentVar?.full_content
|
||||
const isAgentAliasVar = currentVar?.name?.startsWith('@')
|
||||
const displayVarName = isAgentAliasVar ? currentVar?.name?.slice(1) : currentVar?.name
|
||||
|
||||
const {
|
||||
resetConversationVar,
|
||||
|
|
@ -65,15 +71,15 @@ const Right = ({
|
|||
} = useCurrentVars()
|
||||
|
||||
const handleValueChange = (varId: string, value: any) => {
|
||||
if (!currentNodeVar)
|
||||
if (!currentNodeVar || !currentVar)
|
||||
return
|
||||
editInspectVarValue(currentNodeVar.nodeId, varId, value)
|
||||
}
|
||||
|
||||
const resetValue = () => {
|
||||
if (!currentNodeVar)
|
||||
if (!currentNodeVar || !currentVar)
|
||||
return
|
||||
resetToLastRunVar(currentNodeVar.nodeId, currentNodeVar.var.id)
|
||||
resetToLastRunVar(currentNodeVar.nodeId, currentVar.id)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
|
|
@ -82,13 +88,13 @@ const Right = ({
|
|||
}
|
||||
|
||||
const handleClear = () => {
|
||||
if (!currentNodeVar)
|
||||
if (!currentNodeVar || !currentVar)
|
||||
return
|
||||
resetConversationVar(currentNodeVar.var.id)
|
||||
resetConversationVar(currentVar.id)
|
||||
}
|
||||
|
||||
const getCopyContent = () => {
|
||||
const value = currentNodeVar?.var.value
|
||||
const value = currentVar?.value
|
||||
if (value === null || value === undefined)
|
||||
return ''
|
||||
|
||||
|
|
@ -160,8 +166,8 @@ const Right = ({
|
|||
handleHidePromptGenerator()
|
||||
}, [setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator])
|
||||
|
||||
const schemaType = currentNodeVar?.var.schemaType
|
||||
const valueType = currentNodeVar?.var.value_type
|
||||
const schemaType = currentVar?.schemaType
|
||||
const valueType = currentVar?.value_type
|
||||
const valueTypeLabel = formatVarTypeLabel(valueType)
|
||||
const shouldShowSchemaType = !!schemaType
|
||||
&& schemaType !== valueType
|
||||
|
|
@ -178,32 +184,34 @@ const Right = ({
|
|||
</ActionButton>
|
||||
)}
|
||||
<div className="flex w-0 grow items-center gap-1">
|
||||
{currentNodeVar?.var && (
|
||||
{currentVar && (
|
||||
<>
|
||||
{
|
||||
[VarInInspectType.environment, VarInInspectType.conversation, VarInInspectType.system].includes(currentNodeVar.nodeType as VarInInspectType) && (
|
||||
currentNodeType
|
||||
&& [VarInInspectType.environment, VarInInspectType.conversation, VarInInspectType.system].includes(currentNodeType as VarInInspectType) && (
|
||||
<VariableIconWithColor
|
||||
variableCategory={currentNodeVar.nodeType as VarInInspectType}
|
||||
variableCategory={currentNodeType as VarInInspectType}
|
||||
className="size-4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{currentNodeVar.nodeType !== VarInInspectType.environment
|
||||
&& currentNodeVar.nodeType !== VarInInspectType.conversation
|
||||
&& currentNodeVar.nodeType !== VarInInspectType.system
|
||||
{currentNodeType
|
||||
&& currentNodeType !== VarInInspectType.environment
|
||||
&& currentNodeType !== VarInInspectType.conversation
|
||||
&& currentNodeType !== VarInInspectType.system
|
||||
&& (
|
||||
<>
|
||||
<BlockIcon
|
||||
className="shrink-0"
|
||||
type={currentNodeVar.nodeType as BlockEnum}
|
||||
type={currentNodeType as BlockEnum}
|
||||
size="xs"
|
||||
toolIcon={toolIcon}
|
||||
/>
|
||||
<div className="system-sm-regular shrink-0 text-text-secondary">{currentNodeVar.title}</div>
|
||||
<div className="system-sm-regular shrink-0 text-text-secondary">{currentNodeTitle}</div>
|
||||
<div className="system-sm-regular shrink-0 text-text-quaternary">/</div>
|
||||
</>
|
||||
)}
|
||||
<div title={currentNodeVar.var.name} className="system-sm-semibold truncate text-text-secondary">{currentNodeVar.var.name}</div>
|
||||
<div title={displayVarName} className="system-sm-semibold truncate text-text-secondary">{displayVarName}</div>
|
||||
<div className="system-xs-medium ml-1 shrink-0 space-x-2 text-text-tertiary">
|
||||
<span>{`${valueTypeLabel}${displaySchemaType}`}</span>
|
||||
{isTruncated && (
|
||||
|
|
@ -221,7 +229,7 @@ const Right = ({
|
|||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{currentNodeVar && (
|
||||
{currentVar && (
|
||||
<>
|
||||
{canShowPromptGenerator && (
|
||||
<Tooltip popupContent={t('generate.optimizePromptTooltip', { ns: 'appDebug' })}>
|
||||
|
|
@ -245,27 +253,27 @@ const Right = ({
|
|||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!isTruncated && currentNodeVar.var.edited && (
|
||||
{!isTruncated && currentVar.edited && (
|
||||
<Badge>
|
||||
<span className="ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary"></span>
|
||||
<span className="system-2xs-semibold-uupercase">{t('debug.variableInspect.edited', { ns: 'workflow' })}</span>
|
||||
</Badge>
|
||||
)}
|
||||
{!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type !== VarInInspectType.conversation && (
|
||||
{!isTruncated && currentVar.edited && currentVar.type !== VarInInspectType.conversation && (
|
||||
<Tooltip popupContent={t('debug.variableInspect.reset', { ns: 'workflow' })}>
|
||||
<ActionButton onClick={resetValue}>
|
||||
<RiArrowGoBackLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type === VarInInspectType.conversation && (
|
||||
{!isTruncated && currentVar.edited && currentVar.type === VarInInspectType.conversation && (
|
||||
<Tooltip popupContent={t('debug.variableInspect.resetConversationVar', { ns: 'workflow' })}>
|
||||
<ActionButton onClick={handleClear}>
|
||||
<RiArrowGoBackLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{currentNodeVar.var.value_type !== 'secret' && (
|
||||
{currentVar.value_type !== 'secret' && (
|
||||
<CopyFeedback content={getCopyContent()} />
|
||||
)}
|
||||
</>
|
||||
|
|
@ -277,16 +285,16 @@ const Right = ({
|
|||
</div>
|
||||
{/* content */}
|
||||
<div className="grow p-2">
|
||||
{!currentNodeVar?.var && <Empty />}
|
||||
{!currentVar && <Empty />}
|
||||
{isValueFetching && (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentNodeVar?.var && !isValueFetching && (
|
||||
{currentVar && currentNodeId && !isValueFetching && (
|
||||
<ValueContent
|
||||
key={`${currentNodeVar.nodeId}-${currentNodeVar.var.id}`}
|
||||
currentVar={currentNodeVar.var}
|
||||
key={`${currentNodeId}-${currentVar.id}`}
|
||||
currentVar={currentVar}
|
||||
handleValueChange={handleValueChange}
|
||||
isTruncated={!!isTruncated}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import type { VarInInspect } from '@/types/workflow'
|
||||
import { z } from 'zod'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
|
||||
const arrayStringSchemaParttern = z.array(z.string())
|
||||
const arrayNumberSchemaParttern = z.array(z.number())
|
||||
|
|
@ -10,6 +14,34 @@ type Json = Literal | { [key: string]: Json } | Json[]
|
|||
const jsonSchema: z.ZodType<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]))
|
||||
const arrayJsonSchema: z.ZodType<Json[]> = z.lazy(() => z.array(jsonSchema))
|
||||
|
||||
const toEnvVarType = (valueType: EnvironmentVariable['value_type']): VarInInspect['value_type'] => {
|
||||
switch (valueType) {
|
||||
case 'number':
|
||||
return VarType.number
|
||||
case 'secret':
|
||||
return VarType.secret
|
||||
default:
|
||||
return VarType.string
|
||||
}
|
||||
}
|
||||
|
||||
export const toEnvVarInInspect = (envVar: EnvironmentVariable): VarInInspect => {
|
||||
const valueType = envVar.value_type
|
||||
return {
|
||||
id: envVar.id,
|
||||
type: VarInInspectType.environment,
|
||||
name: envVar.name,
|
||||
description: envVar.description,
|
||||
selector: [VarInInspectType.environment, envVar.name],
|
||||
value_type: toEnvVarType(valueType),
|
||||
value: valueType === 'secret' ? '******************' : envVar.value,
|
||||
edited: false,
|
||||
visible: true,
|
||||
is_truncated: false,
|
||||
full_content: { size_bytes: 0, download_url: '' },
|
||||
}
|
||||
}
|
||||
|
||||
export const validateJSONSchema = (schema: any, type: string) => {
|
||||
if (type === 'array[string]') {
|
||||
const result = arrayStringSchemaParttern.safeParse(schema)
|
||||
|
|
|
|||
|
|
@ -477,6 +477,12 @@ export type FullContent = {
|
|||
download_url: string
|
||||
}
|
||||
|
||||
export type VarInInspectAliasMeta = {
|
||||
extractorNodeId: string
|
||||
outputSelector: string[]
|
||||
sourceVarId: string
|
||||
}
|
||||
|
||||
export type VarInInspect = {
|
||||
id: string
|
||||
type: VarInInspectType
|
||||
|
|
@ -490,6 +496,7 @@ export type VarInInspect = {
|
|||
is_truncated: boolean
|
||||
full_content: FullContent
|
||||
schemaType?: string
|
||||
aliasMeta?: VarInInspectAliasMeta
|
||||
}
|
||||
|
||||
export type NodeWithVar = {
|
||||
|
|
@ -500,4 +507,5 @@ export type NodeWithVar = {
|
|||
vars: VarInInspect[]
|
||||
isSingRunRunning?: boolean
|
||||
isValueFetched?: boolean
|
||||
isHidden?: boolean
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue