mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
refactor: Replace SimpleSelect with PortalToFollowElem in sub-graph
config panel
This commit is contained in:
@ -328,12 +328,12 @@ const FormInputItem: FC<Props> = ({
|
||||
&& assemblePlaceholder
|
||||
&& normalizedValue.includes(assemblePlaceholder)
|
||||
const resolvedType = isAssembleValue
|
||||
? VarKindType.mixed
|
||||
? VarKindType.nested_node
|
||||
: newType ?? (varInput?.type === VarKindType.nested_node ? VarKindType.nested_node : getVarKindType())
|
||||
const resolvedNestedNodeConfig = resolvedType === VarKindType.nested_node
|
||||
? (nestedNodeConfig ?? varInput?.nested_node_config ?? {
|
||||
extractor_node_id: '',
|
||||
output_selector: [],
|
||||
extractor_node_id: nodeId && variable ? `${nodeId}_ext_${variable}` : '',
|
||||
output_selector: ['result'],
|
||||
null_strategy: 'use_default',
|
||||
default_value: '',
|
||||
})
|
||||
|
||||
@ -13,7 +13,7 @@ import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { languages } from '@/i18n-config/language'
|
||||
import { fetchContextGenerateSuggestedQuestions, generateContext } from '@/service/debug'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import useContextGenData from '../use-context-gen-data'
|
||||
import useContextGenData from './use-context-gen-data'
|
||||
|
||||
export type ContextGenerateChatMessage = ContextGenerateMessage & {
|
||||
durationMs?: number
|
||||
@ -68,6 +68,7 @@ type UseContextGenerateResult = {
|
||||
handleReset: () => void
|
||||
handleFetchSuggestedQuestions: () => Promise<void>
|
||||
abortSuggestedQuestions: () => void
|
||||
resetSuggestions: () => void
|
||||
defaultAssistantMessage: string
|
||||
versionOptions: VersionOption[]
|
||||
currentVersionLabel: string
|
||||
@ -98,15 +99,8 @@ const useContextGenerate = ({
|
||||
{ defaultValue: [] },
|
||||
)
|
||||
|
||||
const [suggestedQuestions, setSuggestedQuestions] = useSessionStorageState<string[]>(
|
||||
`${storageKey}-suggested-questions`,
|
||||
{ defaultValue: [] },
|
||||
)
|
||||
|
||||
const [hasFetchedSuggestions, setHasFetchedSuggestions] = useSessionStorageState<boolean>(
|
||||
`${storageKey}-suggested-questions-fetched`,
|
||||
{ defaultValue: false },
|
||||
)
|
||||
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([])
|
||||
const [hasFetchedSuggestions, setHasFetchedSuggestions] = useState<boolean>(false)
|
||||
|
||||
const [isFetchingSuggestions, { setTrue: setFetchingSuggestionsTrue, setFalse: setFetchingSuggestionsFalse }] = useBoolean(false)
|
||||
const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
|
||||
@ -269,8 +263,6 @@ const useContextGenerate = ({
|
||||
promptLanguage,
|
||||
setFetchingSuggestionsFalse,
|
||||
setFetchingSuggestionsTrue,
|
||||
setHasFetchedSuggestions,
|
||||
setSuggestedQuestions,
|
||||
t,
|
||||
toolNodeId,
|
||||
])
|
||||
@ -279,6 +271,11 @@ const useContextGenerate = ({
|
||||
suggestedQuestionsAbortControllerRef.current?.abort()
|
||||
}, [])
|
||||
|
||||
const resetSuggestions = useCallback(() => {
|
||||
setSuggestedQuestions([])
|
||||
setHasFetchedSuggestions(false)
|
||||
}, [])
|
||||
|
||||
const generateStartRef = useRef<number | null>(null)
|
||||
const handleGenerate = useCallback(async () => {
|
||||
const trimmed = inputValue.trim()
|
||||
@ -356,8 +353,8 @@ const useContextGenerate = ({
|
||||
promptMessages: promptMessages ?? [],
|
||||
inputValue,
|
||||
setInputValue,
|
||||
suggestedQuestions: suggestedQuestions ?? [],
|
||||
hasFetchedSuggestions: hasFetchedSuggestions ?? false,
|
||||
suggestedQuestions,
|
||||
hasFetchedSuggestions,
|
||||
isGenerating,
|
||||
model,
|
||||
handleModelChange,
|
||||
@ -366,6 +363,7 @@ const useContextGenerate = ({
|
||||
handleReset,
|
||||
handleFetchSuggestedQuestions,
|
||||
abortSuggestedQuestions,
|
||||
resetSuggestions,
|
||||
defaultAssistantMessage,
|
||||
versionOptions,
|
||||
currentVersionLabel,
|
||||
|
||||
@ -104,6 +104,7 @@ const ContextGenerateModal = forwardRef<ContextGenerateModalHandle, Props>(({
|
||||
handleReset,
|
||||
handleFetchSuggestedQuestions,
|
||||
abortSuggestedQuestions,
|
||||
resetSuggestions,
|
||||
defaultAssistantMessage,
|
||||
versionOptions,
|
||||
currentVersionLabel,
|
||||
@ -118,8 +119,9 @@ const ContextGenerateModal = forwardRef<ContextGenerateModalHandle, Props>(({
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
abortSuggestedQuestions()
|
||||
resetSuggestions()
|
||||
onClose()
|
||||
}, [abortSuggestedQuestions, onClose])
|
||||
}, [abortSuggestedQuestions, onClose, resetSuggestions])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onOpen: () => {
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
// Storage key prefix used by useContextGenData
|
||||
const CONTEXT_GEN_PREFIX = 'context-gen-'
|
||||
|
||||
/**
|
||||
* Build storage key from flowId, toolNodeId, and paramKey.
|
||||
* Mirrors the logic in context-generate-modal/index.tsx.
|
||||
*/
|
||||
export const buildContextGenStorageKey = (
|
||||
flowId: string | undefined,
|
||||
toolNodeId: string,
|
||||
paramKey: string,
|
||||
): string => {
|
||||
const segments = [flowId || 'unknown', toolNodeId, paramKey].filter(Boolean)
|
||||
return segments.join('-')
|
||||
}
|
||||
|
||||
export const getContextGenStorageKeys = (storageKey: string): string[] => {
|
||||
return [
|
||||
`${CONTEXT_GEN_PREFIX}${storageKey}-versions`,
|
||||
`${CONTEXT_GEN_PREFIX}${storageKey}-version-index`,
|
||||
`${storageKey}-messages`,
|
||||
`${storageKey}-suggested-questions`,
|
||||
`${storageKey}-suggested-questions-fetched`,
|
||||
]
|
||||
}
|
||||
|
||||
export const clearContextGenStorage = (storageKey: string): void => {
|
||||
const keys = getContextGenStorageKeys(storageKey)
|
||||
keys.forEach((key) => {
|
||||
try {
|
||||
sessionStorage.removeItem(key)
|
||||
}
|
||||
catch {
|
||||
// Ignore errors (e.g., SSR or private browsing)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
export {
|
||||
AGENT_CONTEXT_VAR_PATTERN,
|
||||
buildAssembleNestedNodeConfig,
|
||||
buildAssemblePlaceholder,
|
||||
getAgentNodeIdFromContextVar,
|
||||
getDefaultOutputKey,
|
||||
useMixedVariableExtractor,
|
||||
} from './use-mixed-variable-extractor'
|
||||
export type { DetectedAgent } from './use-mixed-variable-extractor'
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { ReactFlowState } from 'reactflow'
|
||||
import type { ToolParameter } from '@/app/components/tools/types'
|
||||
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { CodeNodeType, OutputVar } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
@ -33,6 +34,31 @@ export const buildAssemblePlaceholder = (toolNodeId?: string, paramKey?: string)
|
||||
return `{{#${toolNodeId}_ext_${paramKey}.result#}}`
|
||||
}
|
||||
|
||||
export const getDefaultOutputKey = (outputs?: OutputVar): string => {
|
||||
if (!outputs)
|
||||
return ''
|
||||
const keys = Object.keys(outputs)
|
||||
if (keys.length === 0)
|
||||
return ''
|
||||
// Reason: 'result' is the conventional default output key for code nodes
|
||||
if (keys.includes('result'))
|
||||
return 'result'
|
||||
return keys[0]
|
||||
}
|
||||
|
||||
export const buildAssembleNestedNodeConfig = (
|
||||
extractorNodeId: string,
|
||||
outputs?: OutputVar,
|
||||
): NestedNodeConfig => {
|
||||
const defaultOutputKey = getDefaultOutputKey(outputs)
|
||||
return {
|
||||
extractor_node_id: extractorNodeId,
|
||||
output_selector: defaultOutputKey ? [defaultOutputKey] : [],
|
||||
null_strategy: 'use_default',
|
||||
default_value: '',
|
||||
}
|
||||
}
|
||||
|
||||
const resolvePromptText = (item?: PromptItem): string => {
|
||||
if (!item)
|
||||
return ''
|
||||
|
||||
@ -30,10 +30,12 @@ import { useGetLanguage } from '@/context/i18n'
|
||||
import { useStrategyProviders } from '@/service/use-strategy'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import ContextGenerateModal from '../context-generate-modal'
|
||||
import { buildContextGenStorageKey, clearContextGenStorage } from '../context-generate-modal/utils/storage'
|
||||
import SubGraphModal from '../sub-graph-modal'
|
||||
import { AgentHeaderBar, Placeholder } from './components'
|
||||
import {
|
||||
AGENT_CONTEXT_VAR_PATTERN,
|
||||
buildAssembleNestedNodeConfig,
|
||||
buildAssemblePlaceholder,
|
||||
getAgentNodeIdFromContextVar,
|
||||
useMixedVariableExtractor,
|
||||
@ -319,23 +321,30 @@ const MixedVariableTextInput = ({
|
||||
return null
|
||||
const extractorNodeId = assembleExtractorNodeId || `${toolNodeId}_ext_${paramKey}`
|
||||
ensureAssembleExtractorNode()
|
||||
onChange?.(assemblePlaceholder, VarKindTypeEnum.mixed, null)
|
||||
const { getNodes } = reactFlowStore.getState()
|
||||
const extractorNode = getNodes().find(node => node.id === extractorNodeId)
|
||||
const outputs = (extractorNode?.data as { outputs?: Record<string, unknown> } | undefined)?.outputs
|
||||
const nestedNodeConfig = buildAssembleNestedNodeConfig(extractorNodeId, outputs as import('@/app/components/workflow/nodes/code/types').OutputVar | undefined)
|
||||
onChange?.(assemblePlaceholder, VarKindTypeEnum.nested_node, nestedNodeConfig)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
setIsContextGenerateModalOpen(true)
|
||||
setTimeout(() => {
|
||||
contextGenerateModalRef.current?.onOpen()
|
||||
}, 0)
|
||||
return [extractorNodeId, 'result']
|
||||
}, [assembleExtractorNodeId, assemblePlaceholder, ensureAssembleExtractorNode, onChange, paramKey, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
}, [assembleExtractorNodeId, assemblePlaceholder, ensureAssembleExtractorNode, onChange, paramKey, reactFlowStore, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
|
||||
const handleAssembleRemove = useCallback(() => {
|
||||
if (!onChange || !assemblePlaceholder)
|
||||
if (!onChange || !assemblePlaceholder || !toolNodeId)
|
||||
return
|
||||
|
||||
removeExtractorNode()
|
||||
onChange('', VarKindTypeEnum.mixed, null)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [assemblePlaceholder, onChange, removeExtractorNode, setControlPromptEditorRerenderKey])
|
||||
|
||||
const storageKey = buildContextGenStorageKey(configsMap?.flowId, toolNodeId, paramKey)
|
||||
clearContextGenStorage(storageKey)
|
||||
}, [assemblePlaceholder, configsMap?.flowId, onChange, paramKey, removeExtractorNode, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
|
||||
const handleOpenSubGraphModal = useCallback(() => {
|
||||
setIsSubGraphModalOpen(true)
|
||||
|
||||
@ -92,7 +92,9 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
const current = toolParam?.nested_node_config
|
||||
const rawSelector = Array.isArray(current?.output_selector) ? current!.output_selector : []
|
||||
const outputSelector = rawSelector[0] === extractorNodeId ? rawSelector.slice(1) : rawSelector
|
||||
const defaultOutputSelector = ['structured_output', paramKey]
|
||||
const defaultOutputSelector = isAgentVariant
|
||||
? ['structured_output', paramKey]
|
||||
: ['result']
|
||||
|
||||
return {
|
||||
extractor_node_id: current?.extractor_node_id || extractorNodeId,
|
||||
@ -100,12 +102,9 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
null_strategy: current?.null_strategy || 'use_default',
|
||||
default_value: current?.default_value ?? '',
|
||||
}
|
||||
}, [extractorNodeId, paramKey, toolParam?.nested_node_config])
|
||||
}, [extractorNodeId, isAgentVariant, paramKey, toolParam?.nested_node_config])
|
||||
|
||||
const handleNestedNodeConfigChange = useCallback((config: NestedNodeConfig) => {
|
||||
if (!isAgentVariant)
|
||||
return
|
||||
|
||||
const { getNodes, setNodes } = reactflowStore.getState()
|
||||
const nextNodes = getNodes().map((node) => {
|
||||
if (node.id !== toolNodeId)
|
||||
@ -124,7 +123,7 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
...toolData.tool_parameters,
|
||||
[paramKey]: {
|
||||
...currentParam,
|
||||
type: currentParam.type || VarKindType.nested_node,
|
||||
type: VarKindType.nested_node,
|
||||
nested_node_config: config,
|
||||
},
|
||||
},
|
||||
@ -133,10 +132,10 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
})
|
||||
setNodes(nextNodes)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [handleSyncWorkflowDraft, isAgentVariant, paramKey, reactflowStore, toolNodeId])
|
||||
}, [handleSyncWorkflowDraft, paramKey, reactflowStore, toolNodeId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAgentVariant || !toolParam || (toolParam.type && toolParam.type !== VarKindType.nested_node))
|
||||
if (!toolParam || (toolParam.type && toolParam.type !== VarKindType.nested_node))
|
||||
return
|
||||
|
||||
const current = toolParam.nested_node_config
|
||||
@ -147,7 +146,7 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
|
||||
if (needsExtractor || needsNullStrategy || needsOutputSelector || needsDefaultValue)
|
||||
handleNestedNodeConfigChange(nestedNodeConfig)
|
||||
}, [handleNestedNodeConfigChange, isAgentVariant, nestedNodeConfig, toolParam])
|
||||
}, [handleNestedNodeConfigChange, nestedNodeConfig, toolParam])
|
||||
|
||||
const getUserPromptText = useCallback((promptTemplate?: PromptTemplateItem[] | PromptItem) => {
|
||||
if (!promptTemplate)
|
||||
@ -220,6 +219,7 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
if (!toolData.tool_parameters?.[paramKey])
|
||||
return node
|
||||
|
||||
const currentParam = toolData.tool_parameters[paramKey]
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
@ -227,8 +227,10 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
tool_parameters: {
|
||||
...toolData.tool_parameters,
|
||||
[paramKey]: {
|
||||
...toolData.tool_parameters[paramKey],
|
||||
...currentParam,
|
||||
type: VarKindType.nested_node,
|
||||
value: nextValue,
|
||||
nested_node_config: currentParam.nested_node_config ?? nestedNodeConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -238,7 +240,7 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
})
|
||||
setNodes(nextNodes)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [assemblePlaceholder, extractorNodeId, getUserPromptText, isAgentVariant, paramKey, reactflowStore, resolvedAgentNodeId, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
}, [assemblePlaceholder, extractorNodeId, getUserPromptText, isAgentVariant, nestedNodeConfig, paramKey, reactflowStore, resolvedAgentNodeId, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
@ -298,6 +300,8 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
paramKey={paramKey}
|
||||
title={props.title}
|
||||
configsMap={configsMap}
|
||||
nestedNodeConfig={nestedNodeConfig}
|
||||
onNestedNodeConfigChange={handleNestedNodeConfigChange}
|
||||
extractorNode={extractorNode as Node<CodeNodeType> | undefined}
|
||||
toolParamValue={toolParamValue}
|
||||
parentAvailableNodes={parentAvailableNodes}
|
||||
|
||||
Reference in New Issue
Block a user