非子流程节点表单展示补 valueSource,修复子流程生成次数回显
- http/form/其它 presetOption 节点勾选表单展示的字段保存时补 valueSource={nodeId:开始节点id, fieldName},与子流程契约统一
- model 节点勾选字段按 path 补 valueSource,回显时清除指向开始节点的引用防误删
- 修复 sub_flow 节点 outputConfig 为空时参数面板(含生成次数)无法回显:回显逻辑改为节点库有 presetOption 定义即构建参数,值从开始节点运行表单反查
This commit is contained in:
@@ -232,13 +232,31 @@ export function collectExposedFields(params: any, prefix = ''): ExposedField[] {
|
||||
}
|
||||
|
||||
// 按暴露清单的 path 反解,把对应叶子 def 的 runtimeShow 置 true(加载时还原勾选)
|
||||
export function restoreRuntimeShow(params: any, fields: ExposedField[] | null | undefined): void {
|
||||
// startNodeId:勾选「表单展示」的字段保存时带 valueSource 指向开始节点(后端契约);
|
||||
// 回显时该 valueSource 无编辑器引用语义,清除避免 ModelField 误判引用/误删
|
||||
export function restoreRuntimeShow(params: any, fields: ExposedField[] | null | undefined, startNodeId = ''): void {
|
||||
if (!params || typeof params !== 'object' || !Array.isArray(fields) || fields.length === 0) return;
|
||||
for (const f of fields) {
|
||||
if (!f || typeof f.path !== 'string') continue;
|
||||
const cur = resolvePath(params, f.path);
|
||||
if (cur && typeof cur === 'object' && !Array.isArray(cur)) {
|
||||
cur.runtimeShow = true;
|
||||
if (startNodeId && cur.valueSource && typeof cur.valueSource === 'object' && cur.valueSource.nodeId === startNodeId) {
|
||||
delete cur.valueSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 勾选「表单展示」的字段:保存前按后端契约补 valueSource={nodeId:开始节点id, fieldName:path},
|
||||
// 标记字段值来自主工作流开始节点(首页表单)。startNodeId 为空(无开始节点)时跳过。
|
||||
export function attachFormFieldValueSources(params: any, fields: ExposedField[] | null | undefined, startNodeId = ''): void {
|
||||
if (!params || typeof params !== 'object' || !Array.isArray(fields) || fields.length === 0 || !startNodeId) return;
|
||||
for (const f of fields) {
|
||||
if (!f || typeof f.path !== 'string') continue;
|
||||
const cur = resolvePath(params, f.path);
|
||||
if (cur && typeof cur === 'object' && !Array.isArray(cur)) {
|
||||
cur.valueSource = { nodeId: startNodeId, fieldName: f.path };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ import {
|
||||
stripReadonlyFields,
|
||||
collectExposedFields,
|
||||
restoreRuntimeShow,
|
||||
attachFormFieldValueSources,
|
||||
isEqual,
|
||||
deepClone,
|
||||
removeArrayValueInstances,
|
||||
@@ -1006,30 +1007,34 @@ const buildNodeFormConfigFromDsl = (n: any, startRunFields: any[] = []) => {
|
||||
};
|
||||
});
|
||||
}
|
||||
// 新格式:HTTP 节点 outputConfig 为 {type,field,label,value} 数组
|
||||
if (Array.isArray(n.outputConfig) && n.outputConfig.length > 0 && n.nodeCode !== '__start__') {
|
||||
// 非开始节点:优先按节点库 presetOption 定义回显参数。
|
||||
// outputConfig 可能为 null/空数组(如 sub_flow 勾选参数全部聚合进开始节点,outputConfig 被过滤),
|
||||
// 只要节点有 presetOption 定义就按定义构建,值从 outputConfig 或开始节点运行表单反查
|
||||
if (n.nodeCode !== '__start__') {
|
||||
const defs = nodeConfigMap.value.get(n.nodeCode)?.formConfig || [];
|
||||
// 无 presetOption 定义(如 form 节点为命名值数组)时直接透传
|
||||
if (defs.length === 0) return n.outputConfig;
|
||||
|
||||
return defs.map((def: any) => {
|
||||
const out = n.outputConfig.find((o: any) => o.field === def.field);
|
||||
if (def.field === 'responseType') {
|
||||
// 恢复嵌套配置:定义取自 presetOption 选中选项的 config,值取自 options[0].config
|
||||
const savedConfig = out?.options?.[0]?.config || [];
|
||||
const selectedOpt = (def.options || []).find((o: any) => o.key === out?.value || o.value === out?.value);
|
||||
const expandDefs = selectedOpt?.config || [];
|
||||
const expand = expandDefs.map((cd: any) => {
|
||||
const saved = savedConfig.find((c: any) => c.field === cd.field);
|
||||
return { ...cd, value: saved?.value ?? '' };
|
||||
});
|
||||
return { ...def, value: out?.value ?? '', expand };
|
||||
}
|
||||
// isFormField:true 字段不随节点 outputConfig 保存(值在开始节点运行表单),
|
||||
// 从开始节点反查恢复,保证编辑器参数面板回显与运行表单一致
|
||||
const startValue = startRunFields.find((r: any) => r && r.field === def.field)?.value;
|
||||
return { ...def, value: out?.value ?? startValue ?? '' };
|
||||
});
|
||||
if (defs.length > 0) {
|
||||
const outList = Array.isArray(n.outputConfig) ? n.outputConfig : [];
|
||||
return defs.map((def: any) => {
|
||||
const out = outList.find((o: any) => o.field === def.field);
|
||||
if (def.field === 'responseType') {
|
||||
// 恢复嵌套配置:定义取自 presetOption 选中选项的 config,值取自 options[0].config
|
||||
const savedConfig = out?.options?.[0]?.config || [];
|
||||
const selectedOpt = (def.options || []).find((o: any) => o.key === out?.value || o.value === out?.value);
|
||||
const expandDefs = selectedOpt?.config || [];
|
||||
const expand = expandDefs.map((cd: any) => {
|
||||
const saved = savedConfig.find((c: any) => c.field === cd.field);
|
||||
return { ...cd, value: saved?.value ?? '' };
|
||||
});
|
||||
return { ...def, value: out?.value ?? '', expand };
|
||||
}
|
||||
// isFormField:true 字段不随节点 outputConfig 保存(值在开始节点运行表单),
|
||||
// 从开始节点反查恢复,保证编辑器参数面板回显与运行表单一致
|
||||
const startValue = startRunFields.find((r: any) => r && r.field === def.field)?.value;
|
||||
return { ...def, value: out?.value ?? startValue ?? '' };
|
||||
});
|
||||
}
|
||||
// 无 presetOption 定义:outputConfig 为数组时透传(form 节点已提前处理)
|
||||
if (Array.isArray(n.outputConfig) && n.outputConfig.length > 0) return n.outputConfig;
|
||||
}
|
||||
// 旧格式兜底
|
||||
if (Array.isArray(n.formConfig)) return n.formConfig;
|
||||
@@ -1037,7 +1042,7 @@ const buildNodeFormConfigFromDsl = (n: any, startRunFields: any[] = []) => {
|
||||
};
|
||||
|
||||
// 构建保存用的 outputConfig(HTTP/form 节点),其余返回 null(开始节点的运行表单字段由保存处单独写入)
|
||||
const buildOutputConfig = (node: Node<NodeData>) => {
|
||||
const buildOutputConfig = (node: Node<NodeData>, startNodeId = '') => {
|
||||
const nodeCode = node.data?.nodeCode || '';
|
||||
if (nodeCode === 'http') {
|
||||
const defs = nodeConfigMap.value.get(nodeCode)?.formConfig || [];
|
||||
@@ -1045,6 +1050,8 @@ const buildOutputConfig = (node: Node<NodeData>) => {
|
||||
return defs.map((def: any) => {
|
||||
const entry = formConfig.find((f: any) => f.field === def.field);
|
||||
const value = entry?.value ?? '';
|
||||
// 勾选「表单展示」的字段:值来自开始节点表单,补 valueSource 标记(后端契约)
|
||||
const valueSource = def.isFormField === true ? { nodeId: startNodeId, fieldName: def.field } : undefined;
|
||||
if (def.field === 'responseType') {
|
||||
const expand = entry?.expand || [];
|
||||
return {
|
||||
@@ -1052,44 +1059,67 @@ const buildOutputConfig = (node: Node<NodeData>) => {
|
||||
field: 'responseType',
|
||||
label: def.label,
|
||||
value,
|
||||
...(valueSource ? { valueSource } : {}),
|
||||
options: expand.length
|
||||
? [{ config: expand.map((e: any) => ({ type: e.type, field: e.field, label: e.label, value: e.value })) }]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
return { type: def.type, field: def.field, label: def.label, value };
|
||||
return { type: def.type, field: def.field, label: def.label, value, ...(valueSource ? { valueSource } : {}) };
|
||||
});
|
||||
}
|
||||
if (nodeCode === 'form') {
|
||||
// form 节点 outputConfig 为自定义字段完整结构(含 type/required,便于完整回显)
|
||||
// 全部字段均为运行表单字段(首页可填),保存时补 valueSource 指向开始节点(后端契约)
|
||||
const fields = Array.isArray(node.data?.formConfig) ? node.data.formConfig : [];
|
||||
return fields.map((f: any) => ({
|
||||
type: f.type || 'input',
|
||||
field: f.label || f.field || '',
|
||||
label: f.label || f.field || '',
|
||||
value: f.value ?? '',
|
||||
required: Boolean(f.required),
|
||||
...(f.type === 'uploadMultiple'
|
||||
? {
|
||||
fieldConstraint: {
|
||||
...(f.fileTypes ? { fileTypes: f.fileTypes } : {}),
|
||||
...(f.maxFileSize !== undefined && f.maxFileSize !== null ? { maxFileSize: f.maxFileSize } : {}),
|
||||
...(f.maxFileCount !== undefined && f.maxFileCount !== null ? { maxFileCount: f.maxFileCount } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
return fields.map((f: any) => {
|
||||
const fieldName = f.field || f.label || '';
|
||||
return {
|
||||
type: f.type || 'input',
|
||||
field: f.label || f.field || '',
|
||||
label: f.label || f.field || '',
|
||||
value: f.value ?? '',
|
||||
required: Boolean(f.required),
|
||||
...(fieldName ? { valueSource: { nodeId: startNodeId, fieldName } } : {}),
|
||||
...(f.type === 'uploadMultiple'
|
||||
? {
|
||||
fieldConstraint: {
|
||||
...(f.fileTypes ? { fileTypes: f.fileTypes } : {}),
|
||||
...(f.maxFileSize !== undefined && f.maxFileSize !== null ? { maxFileSize: f.maxFileSize } : {}),
|
||||
...(f.maxFileCount !== undefined && f.maxFileCount !== null ? { maxFileCount: f.maxFileCount } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
});
|
||||
}
|
||||
// 其它带 presetOption 的节点(如 sub_flow):isFormField:true 的字段已聚合进开始节点运行表单,
|
||||
// 此处仅序列化 isFormField !== true 的节点配置参数,保证重开后编辑器参数面板回显
|
||||
// 其它带 presetOption 的节点:
|
||||
// - sub_flow:isFormField:true 字段已聚合进开始节点并存入 subConfig(如 maxConcurrency),保持原过滤
|
||||
// - 其它节点(数字人等):勾选「表单展示」的字段值来自开始节点表单,
|
||||
// 序列化时补 valueSource 标记(后端契约);值不重复保存(回显从开始节点反查)
|
||||
const defs = nodeConfigMap.value.get(nodeCode)?.formConfig || [];
|
||||
if (defs.length === 0) return null;
|
||||
const formConfig = Array.isArray(node.data?.formConfig) ? node.data.formConfig : [];
|
||||
if (nodeCode === 'sub_flow') {
|
||||
return defs
|
||||
.filter((def: any) => def && typeof def === 'object' && def.isFormField !== true)
|
||||
.map((def: any) => {
|
||||
const entry = formConfig.find((f: any) => f && f.field === def.field);
|
||||
return { type: def.type || 'input', field: def.field, label: def.label || def.field, value: entry?.value ?? '' };
|
||||
});
|
||||
}
|
||||
return defs
|
||||
.filter((def: any) => def && typeof def === 'object' && def.isFormField !== true)
|
||||
.map((def: any) => {
|
||||
const entry = formConfig.find((f: any) => f && f.field === def.field);
|
||||
return { type: def.type || 'input', field: def.field, label: def.label || def.field, value: entry?.value ?? '' };
|
||||
const isForm = Boolean(def && typeof def === 'object' && def.isFormField === true);
|
||||
return {
|
||||
type: def.type || 'input',
|
||||
field: def.field,
|
||||
label: def.label || def.field,
|
||||
// isFormField 字段值在开始节点运行表单,不重复保存(回显从开始节点反查)
|
||||
...(isForm ? {} : { value: entry?.value ?? '' }),
|
||||
...(isForm ? { valueSource: { nodeId: startNodeId, fieldName: def.field } } : {}),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1163,11 +1193,11 @@ const buildSubFlowConfigFromDsl = (subConfig: any, startNodeId = '') => {
|
||||
};
|
||||
|
||||
// 从 DSL 构建模型配置:剔除只读字段 + 用 modelFormFields 还原勾选(幂等,兼容旧 DSL)
|
||||
const buildModelConfigFromDsl = (n: any) => {
|
||||
const buildModelConfigFromDsl = (n: any, startNodeId = '') => {
|
||||
const modelRequestParams = stripReadonlyFields(n.modelConfig?.modelRequestParams ?? null);
|
||||
const modelFormFields = n.modelConfig?.modelFormFields ?? null;
|
||||
if (modelRequestParams && typeof modelRequestParams === 'object' && Array.isArray(modelFormFields)) {
|
||||
restoreRuntimeShow(modelRequestParams, modelFormFields);
|
||||
restoreRuntimeShow(modelRequestParams, modelFormFields, startNodeId);
|
||||
}
|
||||
return {
|
||||
modelId: n.modelConfig?.modelId || '',
|
||||
@@ -1227,7 +1257,7 @@ const loadWorkflowFromDsl = (dsl: any) => {
|
||||
nodeCode: n.nodeCode,
|
||||
desc: n.desc || '',
|
||||
formConfig,
|
||||
modelConfig: buildModelConfigFromDsl(n),
|
||||
modelConfig: buildModelConfigFromDsl(n, startNodeId),
|
||||
skillName: n.skillName || null,
|
||||
prompt: n.prompt || '',
|
||||
negativePrompt: n.negativePrompt || '',
|
||||
@@ -1332,6 +1362,11 @@ const confirmSaveWorkflow = async () => {
|
||||
// 数组字段的实例值(value)由前端从模板复制而来,与 enumValues 模板重复,
|
||||
// 保存时不提交;deepClone 避免污染节点运行时状态
|
||||
const savedModelRequestParams = rawModelParams ? removeArrayValueInstances(deepClone(rawModelParams)) : null;
|
||||
// 勾选「表单展示」的字段:补 valueSource={nodeId:开始节点id, fieldName:path}(后端契约,
|
||||
// 标记字段值来自主工作流开始节点首页表单)
|
||||
if (savedModelRequestParams && startNode?.id) {
|
||||
attachFormFieldValueSources(savedModelRequestParams, savedModelFormFields, startNode.id);
|
||||
}
|
||||
return {
|
||||
id: n.id,
|
||||
nodeCode,
|
||||
@@ -1361,7 +1396,7 @@ const confirmSaveWorkflow = async () => {
|
||||
modelResponseBodyMapping: n.data?.modelConfig?.modelResponseBodyMapping ?? null,
|
||||
},
|
||||
// 开始节点:运行表单字段存入 outputConfig(不再单独存顶层 runFormFields 字段)
|
||||
outputConfig: isStartNode(n) ? (n.data?.runFormFields ?? []) : buildOutputConfig(n),
|
||||
outputConfig: isStartNode(n) ? (n.data?.runFormFields ?? []) : buildOutputConfig(n, startNode?.id || ''),
|
||||
...(n.data?.skillName ? { skillName: n.data.skillName } : {}),
|
||||
...(n.data?.prompt ? { prompt: n.data.prompt } : {}),
|
||||
...(n.data?.negativePrompt ? { negativePrompt: n.data.negativePrompt } : {}),
|
||||
|
||||
Reference in New Issue
Block a user