feat: Enhance sub-graph components with context handling and variable management

This commit is contained in:
zhsama
2026-01-14 23:23:09 +08:00
parent 4828348532
commit 810f9eaaad
19 changed files with 528 additions and 103 deletions

View File

@ -67,6 +67,9 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
description: t('subGraphModal.whenOutputNone.defaultDesc', { ns: 'workflow' }),
},
]), [t])
const selectedWhenOutputNoneOption = useMemo(() => (
whenOutputNoneOptions.find(item => item.value === mentionConfig.null_strategy) ?? whenOutputNoneOptions[0]
), [mentionConfig.null_strategy, whenOutputNoneOptions])
const handleNullStrategyChange = useCallback((item: Item) => {
if (typeof item.value !== 'string')
@ -94,6 +97,8 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
default_value: nextValue,
})
}, [mentionConfig, onMentionConfigChange])
const defaultValue = mentionConfig.default_value ?? ''
const shouldFormatDefaultValue = typeof defaultValue !== 'string'
return (
<div className="flex h-full flex-col">
@ -131,45 +136,54 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
</Field>
</div>
<div className="space-y-4 px-4 py-4">
<Field title={t('subGraphModal.whenOutputIsNone', { ns: 'workflow' })}>
<SimpleSelect
items={whenOutputNoneOptions}
defaultValue={mentionConfig.null_strategy}
allowSearch={false}
notClearable
onSelect={handleNullStrategyChange}
renderOption={({ item, selected }) => (
<div className="flex items-start gap-2">
<div className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center">
{selected && (
<RiCheckLine className="h-4 w-4 text-[14px] text-text-accent" />
)}
</div>
<div className="min-w-0">
<div className="system-sm-medium text-text-secondary">{item.name}</div>
<div className="system-xs-regular mt-0.5 text-text-tertiary">{item.description}</div>
</div>
</div>
)}
/>
</Field>
{mentionConfig.null_strategy === 'use_default' && (
<div>
<div className="system-xs-regular text-text-tertiary">
{t('subGraphModal.defaultValueHint', { ns: 'workflow' })}
</div>
<div className={cn('mt-2 overflow-hidden rounded-lg border border-components-input-border-active bg-components-input-bg-normal p-1')}>
<CodeEditor
noWrapper
language={CodeLanguage.json}
value={mentionConfig.default_value ?? ''}
onChange={handleDefaultValueChange}
isJSONStringifyBeauty
className="min-h-[160px]"
<Field
title={t('subGraphModal.whenOutputIsNone', { ns: 'workflow' })}
operations={(
<div className="flex items-center">
<SimpleSelect
items={whenOutputNoneOptions}
defaultValue={mentionConfig.null_strategy}
allowSearch={false}
notClearable
wrapperClassName="min-w-[160px]"
onSelect={handleNullStrategyChange}
renderOption={({ item, selected }) => (
<div className="flex items-start gap-2">
<div className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center">
{selected && (
<RiCheckLine className="h-4 w-4 text-[14px] text-text-accent" />
)}
</div>
<div className="min-w-0">
<div className="system-sm-medium text-text-secondary">{item.name}</div>
<div className="system-xs-regular mt-0.5 text-text-tertiary">{item.description}</div>
</div>
</div>
)}
/>
</div>
)}
>
<div className="space-y-2">
{selectedWhenOutputNoneOption?.description && (
<div className="system-xs-regular text-text-tertiary">
{selectedWhenOutputNoneOption.description}
</div>
)}
{mentionConfig.null_strategy === 'use_default' && (
<div className={cn('overflow-hidden rounded-lg border border-components-input-border-active bg-components-input-bg-normal p-1')}>
<CodeEditor
noWrapper
language={CodeLanguage.json}
value={defaultValue}
onChange={handleDefaultValueChange}
isJSONStringifyBeauty={shouldFormatDefaultValue}
className="min-h-[160px]"
/>
</div>
)}
</div>
)}
</Field>
</div>
</div>
)}

View File

@ -2,12 +2,13 @@ import type { FC } from 'react'
import type { Viewport } from 'reactflow'
import type { SubGraphProps } from './types'
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
import type { PromptItem } from '@/app/components/workflow/types'
import { memo, useMemo } from 'react'
import type { PromptItem, PromptTemplateItem } from '@/app/components/workflow/types'
import { memo, useEffect, useMemo } from 'react'
import WorkflowWithDefaultContext from '@/app/components/workflow'
import { NODE_WIDTH_X_OFFSET, START_INITIAL_POSITION } from '@/app/components/workflow/constants'
import { WorkflowContextProvider } from '@/app/components/workflow/context'
import { BlockEnum, EditionType, PromptRole } from '@/app/components/workflow/types'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole } from '@/app/components/workflow/types'
import SubGraphMain from './components/sub-graph-main'
import { useSubGraphNodes } from './hooks'
import { createSubGraphSlice } from './store'
@ -38,9 +39,19 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
onMentionConfigChange,
extractorNode,
toolParamValue,
parentAvailableNodes,
parentAvailableVars,
onSave,
} = props
const setParentAvailableVars = useStore(state => state.setParentAvailableVars)
const setParentAvailableNodes = useStore(state => state.setParentAvailableNodes)
useEffect(() => {
setParentAvailableVars?.(parentAvailableVars || [])
setParentAvailableNodes?.(parentAvailableNodes || [])
}, [parentAvailableNodes, parentAvailableVars, setParentAvailableNodes, setParentAvailableVars])
const promptText = useMemo(() => {
if (!toolParamValue)
return ''
@ -95,16 +106,18 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
if (!Array.isArray(template))
return applyPromptText(template as PromptItem)
const userIndex = template.findIndex(item => item.role === PromptRole.user)
const promptItems = template.filter((item): item is PromptItem => !isPromptMessageContext(item))
const userIndex = promptItems.findIndex(item => item.role === PromptRole.user)
if (userIndex >= 0) {
return template.map((item, index) => {
return promptItems.map((item, index) => {
if (index !== userIndex)
return item
return applyPromptText(item)
})
}) as PromptTemplateItem[]
}
const useJinja = template.some((item: PromptItem) => item.edition_type === EditionType.jinja2)
const useJinja = promptItems.some((item: PromptItem) => item.edition_type === EditionType.jinja2)
const defaultUserPrompt: PromptItem = useJinja
? {
role: PromptRole.user,
@ -113,13 +126,13 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
edition_type: EditionType.jinja2,
}
: { role: PromptRole.user, text: promptText }
const systemIndex = template.findIndex(item => item.role === PromptRole.system)
const nextTemplate = [...template]
const systemIndex = promptItems.findIndex(item => item.role === PromptRole.system)
const nextTemplate = [...promptItems]
if (systemIndex >= 0)
nextTemplate.splice(systemIndex + 1, 0, defaultUserPrompt)
else
nextTemplate.unshift(defaultUserPrompt)
return nextTemplate
return nextTemplate as PromptTemplateItem[]
})()
return {

View File

@ -1,6 +1,6 @@
import type { CreateSubGraphSlice, SubGraphSliceShape } from '../types'
const initialState: Omit<SubGraphSliceShape, 'setSubGraphContext' | 'setSubGraphNodes' | 'setSubGraphEdges' | 'setSelectedOutputVar' | 'setWhenOutputNone' | 'setDefaultValue' | 'setShowDebugPanel' | 'setIsRunning' | 'setParentAvailableVars' | 'resetSubGraph'> = {
const initialState: Omit<SubGraphSliceShape, 'setSubGraphContext' | 'setSubGraphNodes' | 'setSubGraphEdges' | 'setSelectedOutputVar' | 'setWhenOutputNone' | 'setDefaultValue' | 'setShowDebugPanel' | 'setIsRunning' | 'setParentAvailableVars' | 'setParentAvailableNodes' | 'resetSubGraph'> = {
parentToolNodeId: '',
parameterKey: '',
sourceAgentNodeId: '',
@ -18,6 +18,7 @@ const initialState: Omit<SubGraphSliceShape, 'setSubGraphContext' | 'setSubGraph
isRunning: false,
parentAvailableVars: [],
parentAvailableNodes: [],
}
export const createSubGraphSlice: CreateSubGraphSlice = set => ({
@ -46,5 +47,7 @@ export const createSubGraphSlice: CreateSubGraphSlice = set => ({
setParentAvailableVars: vars => set(() => ({ parentAvailableVars: vars })),
setParentAvailableNodes: nodes => set(() => ({ parentAvailableNodes: nodes })),
resetSubGraph: () => set(() => ({ ...initialState })),
})

View File

@ -31,6 +31,8 @@ export type SubGraphProps = {
onMentionConfigChange: (config: MentionConfig) => void
extractorNode?: Node<LLMNodeType>
toolParamValue?: string
parentAvailableNodes?: Node[]
parentAvailableVars?: NodeOutPutVar[]
onSave?: (nodes: Node[], edges: Edge[]) => void
}
@ -52,6 +54,7 @@ export type SubGraphSliceShape = {
isRunning: boolean
parentAvailableVars: NodeOutPutVar[]
parentAvailableNodes: Node[]
setSubGraphContext: (context: {
parentToolNodeId: string
@ -67,6 +70,7 @@ export type SubGraphSliceShape = {
setShowDebugPanel: (show: boolean) => void
setIsRunning: (running: boolean) => void
setParentAvailableVars: (vars: NodeOutPutVar[]) => void
setParentAvailableNodes: (nodes: Node[]) => void
resetSubGraph: () => void
}