feat: change sub-graph prompt handling to use user role

This commit is contained in:
zhsama 2026-01-13 23:23:18 +08:00
parent c5482c2503
commit b7025ad9d6
3 changed files with 90 additions and 30 deletions

View File

@ -48,6 +48,8 @@ const SubGraph: FC<SubGraphProps> = (props) => {
desc: '',
_connectedSourceHandleIds: ['source'],
_connectedTargetHandleIds: [],
_subGraphEntry: true,
_iconTypeOverride: BlockEnum.Agent,
variables: [],
},
selectable: false,
@ -62,9 +64,7 @@ const SubGraph: FC<SubGraphProps> = (props) => {
if (!extractorNode)
return null
const updateSystemPrompt = (item: PromptItem) => {
if (item.role !== PromptRole.system)
return item
const applyPromptText = (item: PromptItem) => {
if (item.edition_type === EditionType.jinja2) {
return {
...item,
@ -75,36 +75,45 @@ const SubGraph: FC<SubGraphProps> = (props) => {
return { ...item, text: promptText }
}
const nextPromptTemplate = Array.isArray(extractorNode.data.prompt_template)
? extractorNode.data.prompt_template.map(updateSystemPrompt)
: updateSystemPrompt(extractorNode.data.prompt_template as PromptItem)
const nextPromptTemplate = (() => {
const template = extractorNode.data.prompt_template
if (!Array.isArray(template))
return applyPromptText(template as PromptItem)
const hasSystemPrompt = Array.isArray(nextPromptTemplate)
&& nextPromptTemplate.some((item: PromptItem) => item.role === PromptRole.system)
const defaultSystemPrompt: PromptItem = (() => {
const useJinja = Array.isArray(nextPromptTemplate)
&& nextPromptTemplate.some((item: PromptItem) => item.edition_type === EditionType.jinja2)
if (useJinja) {
return {
role: PromptRole.system,
text: promptText,
jinja2_text: promptText,
edition_type: EditionType.jinja2,
}
const userIndex = template.findIndex(item => item.role === PromptRole.user)
if (userIndex >= 0) {
return template.map((item, index) => {
if (index !== userIndex)
return item
return applyPromptText(item)
})
}
return { role: PromptRole.system, text: promptText }
const useJinja = template.some((item: PromptItem) => item.edition_type === EditionType.jinja2)
const defaultUserPrompt: PromptItem = useJinja
? {
role: PromptRole.user,
text: promptText,
jinja2_text: promptText,
edition_type: EditionType.jinja2,
}
: { role: PromptRole.user, text: promptText }
const systemIndex = template.findIndex(item => item.role === PromptRole.system)
const nextTemplate = [...template]
if (systemIndex >= 0)
nextTemplate.splice(systemIndex + 1, 0, defaultUserPrompt)
else
nextTemplate.unshift(defaultUserPrompt)
return nextTemplate
})()
const normalizedPromptTemplate = Array.isArray(nextPromptTemplate)
? (hasSystemPrompt ? nextPromptTemplate : [defaultSystemPrompt, ...nextPromptTemplate])
: nextPromptTemplate
return {
...extractorNode,
hidden: false,
position: { x: 450, y: 150 },
position: { x: 320, y: 150 },
data: {
...extractorNode.data,
prompt_template: normalizedPromptTemplate,
prompt_template: nextPromptTemplate,
},
}
}, [extractorNode, promptText])

View File

@ -63,6 +63,11 @@ const BaseNode: FC<BaseNodeProps> = ({
const { t } = useTranslation()
const nodeRef = useRef<HTMLDivElement>(null)
const { nodesReadOnly } = useNodesReadOnly()
const { _subGraphEntry, _iconTypeOverride } = data as {
_subGraphEntry?: boolean
_iconTypeOverride?: BlockEnum
}
const iconType = _iconTypeOverride ?? data.type
const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
const { handleNodeLoopChildSizeChange } = useNodeLoopInteractions()
@ -138,6 +143,48 @@ const BaseNode: FC<BaseNodeProps> = ({
return null
}, [data._loopIndex, data._runningStatus, t])
if (_subGraphEntry) {
return (
<div
className="relative"
ref={nodeRef}
>
<NodeSourceHandle
id={id}
data={data}
handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2 opacity-0 pointer-events-none after:opacity-0"
handleId="source"
/>
<div
className={cn(
'flex rounded-2xl border p-0.5',
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-workflow-block-border',
data._waitingRun && 'opacity-70',
showRunningBorder && '!border-state-accent-solid',
showSuccessBorder && '!border-state-success-solid',
showFailedBorder && '!border-state-destructive-solid',
showExceptionBorder && '!border-state-warning-solid',
)}
>
<div className="flex items-center gap-2 rounded-[15px] bg-workflow-block-bg px-3 py-2 shadow-xs">
<BlockIcon
className="shrink-0"
type={iconType}
size="md"
toolIcon={toolIcon}
/>
<div
title={data.title}
className="system-sm-semibold-uppercase text-text-primary"
>
{data.title}
</div>
</div>
</div>
</div>
)
}
const nodeContent = (
<div
className={cn(
@ -245,7 +292,7 @@ const BaseNode: FC<BaseNodeProps> = ({
>
<BlockIcon
className="mr-2 shrink-0"
type={data.type}
type={iconType}
size="md"
toolIcon={toolIcon}
/>
@ -344,8 +391,9 @@ const BaseNode: FC<BaseNodeProps> = ({
const isStartNode = data.type === BlockEnum.Start
const isEntryNode = isTriggerNode(data.type as any) || isStartNode
const shouldWrapEntryNode = isEntryNode && !(isStartNode && _subGraphEntry)
return isEntryNode
return shouldWrapEntryNode
? (
<EntryNodeContainer
nodeType={isStartNode ? StartNodeTypeEnum.Start : StartNodeTypeEnum.Trigger}

View File

@ -40,7 +40,7 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
}, [toolNodeId, workflowNodes])
const toolParamValue = (toolNode?.data as ToolNodeType | undefined)?.tool_parameters?.[paramKey]?.value as string | undefined
const getSystemPromptText = useCallback((promptTemplate?: PromptItem[] | PromptItem) => {
const getUserPromptText = useCallback((promptTemplate?: PromptItem[] | PromptItem) => {
if (!promptTemplate)
return ''
const resolveText = (item?: PromptItem) => {
@ -51,6 +51,9 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
return item.text || ''
}
if (Array.isArray(promptTemplate)) {
const userPrompt = promptTemplate.find(item => item.role === PromptRole.user)
if (userPrompt)
return resolveText(userPrompt)
const systemPrompt = promptTemplate.find(item => item.role === PromptRole.system)
return resolveText(systemPrompt)
}
@ -62,9 +65,9 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
if (!extractorNodeData)
return
const systemPromptText = getSystemPromptText(extractorNodeData.data?.prompt_template)
const userPromptText = getUserPromptText(extractorNodeData.data?.prompt_template)
const placeholder = `{{@${agentNodeId}.context@}}`
const nextValue = `${placeholder}${systemPromptText}`
const nextValue = `${placeholder}${userPromptText}`
const { getNodes, setNodes } = reactflowStore.getState()
const nextNodes = getNodes().map((node) => {
@ -104,7 +107,7 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
// Trigger main graph draft sync to persist changes to backend
handleSyncWorkflowDraft(true)
setControlPromptEditorRerenderKey(Date.now())
}, [agentNodeId, extractorNodeId, getSystemPromptText, handleSyncWorkflowDraft, paramKey, reactflowStore, setControlPromptEditorRerenderKey, toolNodeId])
}, [agentNodeId, extractorNodeId, getUserPromptText, handleSyncWorkflowDraft, paramKey, reactflowStore, setControlPromptEditorRerenderKey, toolNodeId])
return (
<Transition appear show={isOpen} as={Fragment}>