工作流贴片布局节点输出可被下级引用(field=templates)

- getNodeOutputFields 重构为累积输出,开启贴片布局的节点额外提供「贴片布局」输出(field=templates、label=贴片布局),与模型返回参数等并列供下级引用
- upstreamNodes 的 hasOutput 放行开启贴片布局的节点(不依赖具体 nodeCode),保证可被前驱引用

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-20 14:48:20 +08:00
co-authored by Claude
parent e2a5480bf9
commit ba5ff9b87d
+34 -27
View File
@@ -873,19 +873,20 @@ const collectHttpOutputFields = (node: any, prefix = '', isRoot = true, suppress
// 取节点在设计时的可引用输出字段(运行时这些字段会产出实际值)
const getNodeOutputFields = (node: Node<NodeData, any, string>): UpstreamField[] => {
const nodeCode = node.data?.nodeCode || '';
const out: UpstreamField[] = [];
// 节点库自带输出项(outputField):非空时优先作为该节点的可引用输出(脚本转写等节点)
const ownOutput = nodeConfigMap.value.get(nodeCode)?.outputField || [];
if (ownOutput.length > 0) {
return ownOutput.map((o: any) => ({ field: o.field || o.label || '', label: o.label || o.field || '' }));
}
if (nodeCode === 'form') {
out.push(...ownOutput.map((o: any) => ({ field: o.field || o.label || '', label: o.label || o.field || '' })));
} else if (nodeCode === 'form') {
// form 节点:自定义表单字段即输出
return (node.data?.formConfig || []).map((f: any) => ({
field: f.field || f.label || '',
label: f.label || f.field || '',
}));
}
if (nodeCode === 'http') {
out.push(
...(node.data?.formConfig || []).map((f: any) => ({
field: f.field || f.label || '',
label: f.label || f.field || '',
})),
);
} else if (nodeCode === 'http') {
// http 节点:输出 = 结果返回结构(response schema)的叶子字段 + 对象/数组容器整体。
// 结果返回方式为主动拉取(responseType === 'pull')时,取主动拉取分支下配置的结果返回结构。
const formConfig = node.data?.formConfig || [];
@@ -895,32 +896,36 @@ const getNodeOutputFields = (node: Node<NodeData, any, string>): UpstreamField[]
? (responseTypeEntry?.expand || []).find((e: any) => e.field === 'response')
: formConfig.find((f: any) => f.field === 'response');
const schema = parseSchema(responseField?.value);
if (schema) return collectHttpOutputFields(schema);
return [];
}
if (nodeCode === 'model') {
if (schema) out.push(...collectHttpOutputFields(schema));
} else if (nodeCode === 'model') {
// 模型节点:模型的返回参数(responseBodyMapping)即可被下游引用的输出;
// value 为保存时从模型 responseMapping 填充的中文描述,展示优先用中文,无则兜底 key
const resp = node.data?.modelConfig?.modelResponseBodyMapping;
// 数组结构:元素带 field/label
if (Array.isArray(resp)) {
return resp
.filter((it: any) => it && typeof it === 'object' && (it.field || it.label || it.key))
.map((it: any) => ({ field: it.field || it.label || it.key || '', label: it.label || it.field || it.key || '' }));
out.push(
...resp
.filter((it: any) => it && typeof it === 'object' && (it.field || it.label || it.key))
.map((it: any) => ({ field: it.field || it.label || it.key || '', label: it.label || it.field || it.key || '' })),
);
}
// 对象结构:{ key: 中文描述 }
if (resp && typeof resp === 'object') {
return Object.keys(resp)
.filter((k: any) => typeof k === 'string' && k.trim() !== '')
.map((k: string) => ({ field: k, label: (typeof resp[k] === 'string' && resp[k]) || k }));
else if (resp && typeof resp === 'object') {
out.push(
...Object.keys(resp)
.filter((k: any) => typeof k === 'string' && k.trim() !== '')
.map((k: string) => ({ field: k, label: (typeof resp[k] === 'string' && resp[k]) || k })),
);
}
return [];
}
if (nodeCode === START_NODE_CODE) {
} else if (nodeCode === START_NODE_CODE) {
// 开始节点:运行表单字段(被勾选的模型参数 + form 自定义字段)
return (node.data?.runFormFields || []).map((f: any) => ({ field: f.field || f.path || '', label: f.label || f.field || f.path || '' }));
out.push(...(node.data?.runFormFields || []).map((f: any) => ({ field: f.field || f.path || '', label: f.label || f.field || f.path || '' })));
}
return [];
// 开启贴片布局的节点:额外提供「贴片布局」输出(field=templates),供下游节点引用(后端执行后产出 templates 字段)
if (node.data?.patchLayout) {
out.push({ field: 'templates', label: '贴片布局' });
}
return out;
};
// 当前选中节点的所有前驱链路节点(含各自可引用输出字段)
@@ -944,10 +949,12 @@ const upstreamNodes = computed<UpstreamNodeInfo[]>(() => {
visited.add(p);
const n = nodes.value.find((x) => x.id === p);
if (n) {
// 可作为下游引用来源:模型/HTTP/表单节点,或节点库声明了自带输出项(outputField)的节点(如脚本转写)
// 可作为下游引用来源:模型/HTTP/表单节点,或节点库声明了自带输出项(outputField)的节点(如脚本转写)
// 或开启了贴片布局的节点(产出 templates 供下游引用)
const hasOutput =
OUTPUT_NODE_CODES.includes(n.data?.nodeCode || '') ||
(nodeConfigMap.value.get(n.data?.nodeCode || '')?.outputField?.length ?? 0) > 0;
(nodeConfigMap.value.get(n.data?.nodeCode || '')?.outputField?.length ?? 0) > 0 ||
n.data?.patchLayout === true;
if (hasOutput) {
result.push({
id: n.id,