From c757eeed50eaedf91c0c8c96ed3f988d0214444a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Mon, 3 Aug 2026 14:54:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=9D=E6=8C=81=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=AD=97=E6=AE=B5=E9=85=8D=E7=BD=AE=E4=B8=80?= =?UTF-8?q?=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 同步字段重命名后的默认标签 - 保留图片等资源参数的内容类型 --- .../src/utils/workflowNodeFields.test.ts | 443 ++++++++++-------- .../src/utils/workflowNodeFields.ts | 287 ++++++++---- 2 files changed, 440 insertions(+), 290 deletions(-) diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts index 432ec25e..45d84296 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts @@ -49,10 +49,13 @@ describe('workflow node fields', () => { it('appends custom start form field into schema source of truth', () => { const initial = createInitialWorkflowData(); const startNode = initial.nodes[0]!; - const nextData = appendStartFormField(startNode.data as Record, { - type: 'select', - options: ['售前', '售后'], - }); + const nextData = appendStartFormField( + startNode.data as Record, + { + type: 'select', + options: ['售前', '售后'], + }, + ); expect(nextData.startFormSchema).toHaveLength(2); expect(nextData.startFormSchema?.[1]).toMatchObject({ @@ -71,54 +74,64 @@ describe('workflow node fields', () => { it('updates generated field key when switching field type', () => { const initial = createInitialWorkflowData(); - const appended = appendStartFormField(initial.nodes[0]?.data as Record, { - type: 'text', - }); + const appended = appendStartFormField( + initial.nodes[0]?.data as Record, + { + type: 'text', + }, + ); const updated = updateStartFormField(appended, 'text_field', { type: 'file', placeholder: '请选择文件', }); - expect(updated.startFormSchema?.find((item: any) => item.key === 'file_field')) - .toMatchObject({ - key: 'file_field', - label: '文件字段', - type: 'file', - contentType: 'file', - placeholder: '请选择文件', - }); - expect((updated.parameters as any[]).find((item) => item.name === 'file_field')) - .toMatchObject({ - name: 'file_field', - dataType: 'File', - contentType: 'file', - }); + expect( + updated.startFormSchema?.find((item: any) => item.key === 'file_field'), + ).toMatchObject({ + key: 'file_field', + label: '文件字段', + type: 'file', + contentType: 'file', + placeholder: '请选择文件', + }); + expect( + (updated.parameters as any[]).find((item) => item.name === 'file_field'), + ).toMatchObject({ + name: 'file_field', + dataType: 'File', + contentType: 'file', + }); }); it('updates and removes custom start form fields through schema source of truth', () => { const initial = createInitialWorkflowData(); - const appended = appendStartFormField(initial.nodes[0]?.data as Record, { - key: 'attachments', - label: '附件', - type: 'text', - placeholder: '请输入内容', - }); + const appended = appendStartFormField( + initial.nodes[0]?.data as Record, + { + key: 'attachments', + label: '附件', + type: 'text', + placeholder: '请输入内容', + }, + ); const updated = updateStartFormField(appended, 'attachments', { type: 'file', placeholder: '请选择文件', }); - expect(updated.startFormSchema?.find((item: any) => item.key === 'attachments')) - .toMatchObject({ - type: 'file', - contentType: 'file', - placeholder: '请选择文件', - }); - expect((updated.parameters as any[]).find((item) => item.name === 'attachments')) - .toMatchObject({ - dataType: 'File', - contentType: 'file', - }); + expect( + updated.startFormSchema?.find((item: any) => item.key === 'attachments'), + ).toMatchObject({ + type: 'file', + contentType: 'file', + placeholder: '请选择文件', + }); + expect( + (updated.parameters as any[]).find((item) => item.name === 'attachments'), + ).toMatchObject({ + dataType: 'File', + contentType: 'file', + }); const removed = removeStartFormField(updated, 'attachments'); expect(removed.startFormSchema).toHaveLength(1); @@ -129,10 +142,15 @@ describe('workflow node fields', () => { it('keeps custom start field parameter id stable when renaming key', () => { const initial = createInitialWorkflowData(); - const appended = appendStartFormField(initial.nodes[0]?.data as Record, { - type: 'text', - }); - const previousField = appended.startFormSchema?.find((item: any) => item.key === 'text_field'); + const appended = appendStartFormField( + initial.nodes[0]?.data as Record, + { + type: 'text', + }, + ); + const previousField = appended.startFormSchema?.find( + (item: any) => item.key === 'text_field', + ); const previousParameter = (appended.parameters as any[]).find( (item) => item.name === 'text_field', ); @@ -141,7 +159,9 @@ describe('workflow node fields', () => { key: 'topic', label: '主题', }); - const nextField = updated.startFormSchema?.find((item: any) => item.key === 'topic'); + const nextField = updated.startFormSchema?.find( + (item: any) => item.key === 'topic', + ); const nextParameter = (updated.parameters as any[]).find( (item) => item.name === 'topic', ); @@ -151,6 +171,161 @@ describe('workflow node fields', () => { expect(nextParameter?.id).toBe(previousParameter?.id); }); + it('aligns the default field label when renaming a generated key', () => { + const initial = createInitialWorkflowData(); + const appended = appendStartFormField( + initial.nodes[0]?.data as Record, + { + type: 'text', + }, + ); + + const updated = updateStartFormField(appended, 'text_field', { + key: '补充信息', + }); + + expect( + updated.startFormSchema?.find((item: any) => item.key === '补充信息'), + ).toMatchObject({ + key: '补充信息', + label: '补充信息', + }); + expect( + (updated.parameters as any[]).find((item) => item.name === '补充信息'), + ).toMatchObject({ + name: '补充信息', + formLabel: '补充信息', + }); + }); + + it('keeps start fields unchanged when renaming to an existing key', () => { + const initial = createInitialWorkflowData(); + const withTopic = appendStartFormField( + initial.nodes[0]?.data as Record, + { + key: 'topic', + label: '主题', + type: 'text', + }, + ); + const withDetails = appendStartFormField(withTopic, { + key: 'details', + label: '详情', + type: 'textarea', + }); + const previousFields = withDetails.startFormSchema; + const previousParameters = withDetails.parameters; + + expect( + isStartFormFieldKeyAvailable(withDetails, 'details', 'summary'), + ).toBe(true); + expect(isStartFormFieldKeyAvailable(withDetails, 'details', 'topic')).toBe( + false, + ); + expect( + isStartFormFieldKeyAvailable(withDetails, 'details', 'user_input'), + ).toBe(false); + + const duplicatedCustomKey = updateStartFormField(withDetails, 'details', { + key: 'topic', + }); + const duplicatedSystemKey = updateStartFormField(withDetails, 'details', { + key: 'user_input', + }); + + expect(duplicatedCustomKey.startFormSchema).toEqual(previousFields); + expect(duplicatedCustomKey.parameters).toEqual(previousParameters); + expect(duplicatedSystemKey.startFormSchema).toEqual(previousFields); + expect(duplicatedSystemKey.parameters).toEqual(previousParameters); + }); + + it('renames downstream token and managed references when start field key changes', () => { + const initialStartData = appendStartFormField( + createInitialWorkflowData().nodes[0]?.data as Record, + { + type: 'text', + }, + ); + const startNode: Node = { + id: 'start_1', + type: 'startNode', + position: { x: 0, y: 0 }, + data: { + title: '开始节点', + ...initialStartData, + }, + }; + const renamedStartData = updateStartFormField( + startNode.data as Record, + 'text_field', + { + key: 'topic', + label: '主题', + }, + ); + const llmNode: Node = { + id: 'llm_1', + type: 'llmNode', + position: { x: 120, y: 0 }, + data: { + title: '大模型', + userPrompt: '请围绕 {{start_1.text_field}} 生成内容', + parameters: [ + { + id: 'param_1', + name: 'start_1.text_field', + ref: 'start_1.text_field', + refType: 'ref', + autoManaged: true, + formLabel: '开始节点 > 文本字段', + displayName: '开始节点 > 文本字段', + }, + ], + [FIELD_BINDING_META_KEY]: { + userPrompt: { + autoFilledFrom: 'start_1.text_field', + userModified: false, + }, + }, + }, + }; + const edges: Edge[] = [ + { id: 'edge_1', source: 'start_1', target: 'llm_1' } as Edge, + ]; + + const nextNodes = renameStartFieldReferencesInNodes( + [ + { + ...startNode, + data: { + ...(startNode.data as Record), + ...renamedStartData, + }, + }, + llmNode, + ], + edges, + 'start_1', + 'text_field', + 'topic', + ); + const nextLlmNode = nextNodes.find((node) => node.id === 'llm_1')!; + const nextParameter = (nextLlmNode.data?.parameters as any[])?.[0]; + + expect(nextLlmNode.data?.userPrompt).toBe( + '请围绕 {{start_1.topic}} 生成内容', + ); + expect(nextParameter?.name).toBe('start_1.topic'); + expect(nextParameter?.ref).toBe('start_1.topic'); + expect(nextParameter?.displayName).toBe('开始节点 > topic'); + expect( + (nextLlmNode.data as any)?.[FIELD_BINDING_META_KEY]?.userPrompt, + ).toMatchObject({ + autoFilledFrom: 'start_1.topic', + userModified: false, + }); + }); + it('preserves resource content type independently from input type', () => { for (const contentType of ['image', 'video', 'audio', 'other'] as const) { const key = `preview_${contentType}`; @@ -244,130 +419,6 @@ describe('workflow node fields', () => { }); }); - it('keeps start fields unchanged when renaming to an existing key', () => { - const initial = createInitialWorkflowData(); - const withTopic = appendStartFormField( - initial.nodes[0]?.data as Record, - { - key: 'topic', - label: '主题', - type: 'text', - }, - ); - const withDetails = appendStartFormField(withTopic, { - key: 'details', - label: '详情', - type: 'textarea', - }); - const previousFields = withDetails.startFormSchema; - const previousParameters = withDetails.parameters; - - expect( - isStartFormFieldKeyAvailable(withDetails, 'details', 'summary'), - ).toBe(true); - expect( - isStartFormFieldKeyAvailable(withDetails, 'details', 'topic'), - ).toBe(false); - expect( - isStartFormFieldKeyAvailable(withDetails, 'details', 'user_input'), - ).toBe(false); - - const duplicatedCustomKey = updateStartFormField(withDetails, 'details', { - key: 'topic', - }); - const duplicatedSystemKey = updateStartFormField(withDetails, 'details', { - key: 'user_input', - }); - - expect(duplicatedCustomKey.startFormSchema).toEqual(previousFields); - expect(duplicatedCustomKey.parameters).toEqual(previousParameters); - expect(duplicatedSystemKey.startFormSchema).toEqual(previousFields); - expect(duplicatedSystemKey.parameters).toEqual(previousParameters); - }); - - it('renames downstream token and managed references when start field key changes', () => { - const initialStartData = appendStartFormField( - createInitialWorkflowData().nodes[0]?.data as Record, - { - type: 'text', - }, - ); - const startNode: Node = { - id: 'start_1', - type: 'startNode', - position: { x: 0, y: 0 }, - data: { - title: '开始节点', - ...initialStartData, - }, - }; - const renamedStartData = updateStartFormField( - startNode.data as Record, - 'text_field', - { - key: 'topic', - label: '主题', - }, - ); - const llmNode: Node = { - id: 'llm_1', - type: 'llmNode', - position: { x: 120, y: 0 }, - data: { - title: '大模型', - userPrompt: '请围绕 {{start_1.text_field}} 生成内容', - parameters: [ - { - id: 'param_1', - name: 'start_1.text_field', - ref: 'start_1.text_field', - refType: 'ref', - autoManaged: true, - formLabel: '开始节点 > 文本字段', - displayName: '开始节点 > 文本字段', - }, - ], - [FIELD_BINDING_META_KEY]: { - userPrompt: { - autoFilledFrom: 'start_1.text_field', - userModified: false, - }, - }, - }, - }; - const edges: Edge[] = [ - { id: 'edge_1', source: 'start_1', target: 'llm_1' } as Edge, - ]; - - const nextNodes = renameStartFieldReferencesInNodes( - [ - { - ...startNode, - data: { - ...(startNode.data as Record), - ...renamedStartData, - }, - }, - llmNode, - ], - edges, - 'start_1', - 'text_field', - 'topic', - ); - const nextLlmNode = nextNodes.find((node) => node.id === 'llm_1')!; - const nextParameter = (nextLlmNode.data?.parameters as any[])?.[0]; - - expect(nextLlmNode.data?.userPrompt).toBe('请围绕 {{start_1.topic}} 生成内容'); - expect(nextParameter?.name).toBe('start_1.topic'); - expect(nextParameter?.ref).toBe('start_1.topic'); - expect(nextParameter?.displayName).toBe('开始节点 > topic'); - expect((nextLlmNode.data as any)?.[FIELD_BINDING_META_KEY]?.userPrompt).toMatchObject({ - autoFilledFrom: 'start_1.topic', - userModified: false, - }); - }); - it('uses custom start parameter name for reference display', () => { const startData = appendStartFormField( createInitialWorkflowData().nodes[0]?.data as Record, @@ -406,7 +457,9 @@ describe('workflow node fields', () => { edges, [], ); - const topicParameter = parameters.find((item) => item.name === 'start_1.topic_name'); + const topicParameter = parameters.find( + (item) => item.name === 'start_1.topic_name', + ); expect(topicParameter?.displayName).toBe('开始节点 > topic_name'); expect(topicParameter?.formLabel).toBe('开始节点 > topic_name'); @@ -491,8 +544,12 @@ describe('workflow node fields', () => { edges, [], ); - const documentsParameter = parameters.find((item) => item.name === 'knowledge_1.documents'); - const contentParameter = parameters.find((item) => item.name === 'knowledge_1.documents.content'); + const documentsParameter = parameters.find( + (item) => item.name === 'knowledge_1.documents', + ); + const contentParameter = parameters.find( + (item) => item.name === 'knowledge_1.documents.content', + ); expect(documentsParameter?.displayName).toBe('知识库 > documents'); expect(documentsParameter?.formLabel).toBe('知识库 > documents'); @@ -558,12 +615,7 @@ describe('workflow node fields', () => { position: { x: 240, y: 120 }, data: { title: '循环外模型', parameters: [] }, }; - const nodes = [ - knowledgeNode, - loopNode, - insideNode, - outsideNode, - ]; + const nodes = [knowledgeNode, loopNode, insideNode, outsideNode]; const insideParameters = buildEditorReferenceParameters( 'llm_inside', @@ -591,13 +643,10 @@ describe('workflow node fields', () => { 'loop_1.loopItem.content', ]); expect( - insideParameters.find( - (item) => item.name === 'loop_1.loopItem.content', - )?.dataType, + insideParameters.find((item) => item.name === 'loop_1.loopItem.content') + ?.dataType, ).toBe('String'); - expect( - outsideParameters.map((item) => item.name), - ).toEqual(['loop_1.res']); + expect(outsideParameters.map((item) => item.name)).toEqual(['loop_1.res']); expect( outsideParameters.some((item) => ['loop_1.index', 'loop_1.loopItem'].includes(item.name || ''), @@ -650,12 +699,17 @@ describe('workflow node fields', () => { [], ); - expect(parameters.find((item) => item.name === 'doc_1.documents')?.displayName) - .toBe('文档解析 > documents'); - expect(parameters.find((item) => item.name === 'doc_1.documents.fileName')?.displayName) - .toBe('文档解析 > documents.[].fileName'); - expect(parameters.find((item) => item.name === 'doc_1.documents.content')?.displayName) - .toBe('文档解析 > documents.[].content'); + expect( + parameters.find((item) => item.name === 'doc_1.documents')?.displayName, + ).toBe('文档解析 > documents'); + expect( + parameters.find((item) => item.name === 'doc_1.documents.fileName') + ?.displayName, + ).toBe('文档解析 > documents.[].fileName'); + expect( + parameters.find((item) => item.name === 'doc_1.documents.content') + ?.displayName, + ).toBe('文档解析 > documents.[].content'); }); it('applies default binding to llm user prompt after connect', () => { @@ -685,8 +739,9 @@ describe('workflow node fields', () => { const patch = buildAutoBindingPatch(llmNode, [startNode, llmNode], edges); expect(patch?.userPrompt).toBe('{{start_1.user_input}}'); expect((patch?.parameters as any[])?.[0]?.name).toBe('start_1.user_input'); - expect((patch?.[FIELD_BINDING_META_KEY] as any)?.userPrompt?.userModified) - .toBe(false); + expect( + (patch?.[FIELD_BINDING_META_KEY] as any)?.userPrompt?.userModified, + ).toBe(false); }); it('does not auto-bind from legacy start nodes without user_input', () => { @@ -821,7 +876,9 @@ describe('workflow node fields', () => { [startNode, knowledgeNode, llmNode], [], ); - expect((patch?.parameters as any[])?.[0]?.name).toBe('knowledge_1.documents'); + expect((patch?.parameters as any[])?.[0]?.name).toBe( + 'knowledge_1.documents', + ); expect((patch?.parameters as any[])?.[0]?.disconnected).toBe(true); expect((patch?.parameters as any[])?.[0]?.displayName).toBe('documents'); expect(patch).not.toHaveProperty('systemPrompt'); @@ -1138,9 +1195,7 @@ describe('workflow node fields', () => { expect( (normalizedWorkflow.nodes[0]?.data as Record | undefined) ?.startFormSchema?.[0]?.key, - ).toBe( - 'user_input', - ); + ).toBe('user_input'); expect(normalizedWorkflow.nodes[1]?.data?.parameters).toEqual([]); }); }); diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts index 50c9f163..25ee2d6d 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts @@ -95,15 +95,19 @@ const OPTION_FIELD_TYPE_SET = new Set([ const DEFAULT_START_FORM_TITLE = '开始问答'; const DEFAULT_START_FORM_DESCRIPTION = '请先补充必要信息,再开始执行工作流。'; const DEFAULT_START_FORM_SUBMIT_TEXT = '开始'; -const START_FORM_DEFAULT_FIELD_KEY_BY_TYPE: Record = { - text: 'text_field', - textarea: 'textarea_field', - radio: 'radio_field', - checkbox: 'checkbox_field', - select: 'select_field', - file: 'file_field', -}; -const START_FORM_DEFAULT_FIELD_LABEL_BY_TYPE: Record = { +const START_FORM_DEFAULT_FIELD_KEY_BY_TYPE: Record = + { + text: 'text_field', + textarea: 'textarea_field', + radio: 'radio_field', + checkbox: 'checkbox_field', + select: 'select_field', + file: 'file_field', + }; +const START_FORM_DEFAULT_FIELD_LABEL_BY_TYPE: Record< + StartFormFieldType, + string +> = { text: '文本字段', textarea: '长文本字段', radio: '单选字段', @@ -135,12 +139,18 @@ function ensureStringArray(value: unknown) { return value.map((item) => asString(item).trim()).filter(Boolean); } -function isSupportedStartFormFieldType(value: unknown): value is StartFormFieldType { - return START_FORM_FIELD_TYPE_SET.has(asString(value).trim() as StartFormFieldType); +function isSupportedStartFormFieldType( + value: unknown, +): value is StartFormFieldType { + return START_FORM_FIELD_TYPE_SET.has( + asString(value).trim() as StartFormFieldType, + ); } function isOptionFieldType(value: unknown): value is StartFormFieldType { - return OPTION_FIELD_TYPE_SET.has(asString(value).trim() as StartFormFieldType); + return OPTION_FIELD_TYPE_SET.has( + asString(value).trim() as StartFormFieldType, + ); } function normalizeStartFormContentType( @@ -193,10 +203,7 @@ function isCollectionOutputParameter(parameter?: Parameter | null) { const dataType = asString(parameter?.dataType).trim(); return ( !!parameter?.children?.length && - ( - dataType === 'Array' || - dataType.length === 0 - ) + (dataType === 'Array' || dataType.length === 0) ); } @@ -271,7 +278,11 @@ function flattenOutputDefs( const path = parentPath ? `${parentPath}.${rawName}` : rawName; const referenceLabel = getReferenceParameterLabel(parameter); - const label = buildCollectionAwareLabel(referenceLabel, parentLabel, parentIsCollection); + const label = buildCollectionAwareLabel( + referenceLabel, + parentLabel, + parentIsCollection, + ); const displayName = `${getNodeTitle(node)} > ${label}`; const isCollection = isCollectionOutputParameter(parameter); const fullRef = `${node.id}.${path}`; @@ -376,10 +387,15 @@ function uniqueParameters(parameters: Parameter[]) { } function findParameterByName(parameters: Parameter[], name: string) { - return parameters.find((parameter) => asString(parameter.name).trim() === name); + return parameters.find( + (parameter) => asString(parameter.name).trim() === name, + ); } -function toManagedRefParameter(refPath: string, candidate?: Parameter): Parameter { +function toManagedRefParameter( + refPath: string, + candidate?: Parameter, +): Parameter { return ensureParameterId({ name: refPath, ref: candidate?.ref || refPath, @@ -448,7 +464,10 @@ function isAutoGeneratedStartFormFieldKey(key: unknown) { return true; } return Object.values(START_FORM_DEFAULT_FIELD_KEY_BY_TYPE).some((baseKey) => { - return normalizedKey === baseKey || new RegExp(`^${baseKey}_[0-9]+$`).test(normalizedKey); + return ( + normalizedKey === baseKey || + new RegExp(`^${baseKey}_[0-9]+$`).test(normalizedKey) + ); }); } @@ -460,14 +479,18 @@ function isDefaultStartFormFieldLabel(label: unknown) { if (normalizedLabel === '新字段') { return true; } - return Object.values(START_FORM_DEFAULT_FIELD_LABEL_BY_TYPE).includes(normalizedLabel); + return Object.values(START_FORM_DEFAULT_FIELD_LABEL_BY_TYPE).includes( + normalizedLabel, + ); } function ensureStartFormFieldId( fieldId: unknown, existingParameter?: Parameter | null, ) { - return trimString(fieldId) || trimString(existingParameter?.id) || genShortId(); + return ( + trimString(fieldId) || trimString(existingParameter?.id) || genShortId() + ); } function createDefaultStartFormMeta(): StartFormMeta { @@ -478,17 +501,24 @@ function createDefaultStartFormMeta(): StartFormMeta { }; } -export function normalizeStartFormMeta(meta?: Partial | null): StartFormMeta { +export function normalizeStartFormMeta( + meta?: Partial | null, +): StartFormMeta { const fallback = createDefaultStartFormMeta(); return { // Missing values use defaults; an explicit empty string means the user cleared the field. title: meta?.title == null ? fallback.title : trimString(meta.title), - description: meta?.description == null ? fallback.description : trimString(meta.description), + description: + meta?.description == null + ? fallback.description + : trimString(meta.description), submitText: trimString(meta?.submitText) || fallback.submitText, }; } -function normalizeSystemStartParameter(parameter?: Parameter | null): Parameter { +function normalizeSystemStartParameter( + parameter?: Parameter | null, +): Parameter { const fallbackLabel = SYSTEM_START_PARAM_LABEL; const formLabel = trimString(parameter?.formLabel) || fallbackLabel; const formType = normalizeSystemStartFormType(parameter?.formType); @@ -513,7 +543,9 @@ function normalizeSystemStartParameter(parameter?: Parameter | null): Parameter }); } -function parameterTypeToStartFormFieldType(parameter?: Parameter | null): StartFormFieldType { +function parameterTypeToStartFormFieldType( + parameter?: Parameter | null, +): StartFormFieldType { if ( trimString(parameter?.contentType) === 'file' || trimString(parameter?.dataType).toLowerCase() === 'file' @@ -605,6 +637,17 @@ function normalizeStartFormField( const options = isOptionFieldType(type) ? ensureStringArray(field?.options || existingParameter?.enums) : []; + const requestedLabel = + trimString(field?.label) || + trimString(existingParameter?.formLabel) || + trimString(existingParameter?.displayName) || + key; + const label = + !isSystemField && + isDefaultStartFormFieldLabel(requestedLabel) && + !isAutoGeneratedStartFormFieldKey(key) + ? key + : requestedLabel; const defaultValue = Array.isArray(field?.defaultValue) ? ensureStringArray(field?.defaultValue) @@ -613,18 +656,18 @@ function normalizeStartFormField( return { id: ensureStartFormFieldId(field?.id, existingParameter), key, - label: - trimString(field?.label) || - trimString(existingParameter?.formLabel) || - trimString(existingParameter?.displayName) || - key, + label, type, contentType, - required: isSystemField ? true : Boolean(field?.required ?? existingParameter?.required), + required: isSystemField + ? true + : Boolean(field?.required ?? existingParameter?.required), placeholder: - trimString(field?.placeholder) || trimString(existingParameter?.formPlaceholder), + trimString(field?.placeholder) || + trimString(existingParameter?.formPlaceholder), description: - trimString(field?.description) || trimString(existingParameter?.formDescription), + trimString(field?.description) || + trimString(existingParameter?.formDescription), defaultValue, options, systemReserved: isSystemField, @@ -684,7 +727,9 @@ function startFormFieldToParameter( defaultValue: Array.isArray(field.defaultValue) ? field.defaultValue.join('\n') : asString(field.defaultValue), - enums: isOptionFieldType(field.type) ? ensureStringArray(field.options) : [], + enums: isOptionFieldType(field.type) + ? ensureStringArray(field.options) + : [], displayName: `流程开始 > ${formLabel}`, systemReserved: false, }); @@ -708,11 +753,12 @@ export function normalizeStartFormSchema( .filter(([key]) => key.length > 0), ); - const source = Array.isArray(schema) && schema.length > 0 - ? schema - : parameterCandidates - .map((parameter) => parameterToStartFormField(parameter)) - .filter(Boolean); + const source = + Array.isArray(schema) && schema.length > 0 + ? schema + : parameterCandidates + .map((parameter) => parameterToStartFormField(parameter)) + .filter(Boolean); const result: StartFormFieldSchema[] = []; const seen = new Set(); @@ -727,7 +773,8 @@ export function normalizeStartFormSchema( } const normalized = normalizeStartFormField( item as Partial, - (fieldId ? parameterIdMap.get(fieldId) : undefined) || parameterMap.get(key), + (fieldId ? parameterIdMap.get(fieldId) : undefined) || + parameterMap.get(key), ); if (!normalized) { continue; @@ -749,7 +796,8 @@ export function normalizeStartFormSchema( { id: trimString(systemParameter?.id), key: SYSTEM_START_PARAM_NAME, - label: trimString(systemParameter?.formLabel) || SYSTEM_START_PARAM_LABEL, + label: + trimString(systemParameter?.formLabel) || SYSTEM_START_PARAM_LABEL, type: normalizeSystemStartFormType(systemParameter?.formType) === 'input' ? 'text' @@ -810,7 +858,8 @@ export function createCustomStartFormField( requestedType === 'file' ? 'file' : 'text', ); const type = contentType === 'file' ? 'file' : requestedType; - const key = trimString(field?.key) || buildUniqueStartFormFieldKey(type, existingKeys); + const key = + trimString(field?.key) || buildUniqueStartFormFieldKey(type, existingKeys); return { id: ensureStartFormFieldId(field?.id), key, @@ -838,7 +887,10 @@ export function appendStartFormField( const currentParameters = Array.isArray(currentData.parameters) ? (currentData.parameters as Parameter[]) : []; - const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters); + const schema = normalizeStartFormSchema( + currentData.startFormSchema, + currentParameters, + ); const nextField = createCustomStartFormField( field, schema.map((item) => item.key), @@ -858,7 +910,10 @@ export function updateStartFormField( const currentParameters = Array.isArray(currentData.parameters) ? (currentData.parameters as Parameter[]) : []; - const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters); + const schema = normalizeStartFormSchema( + currentData.startFormSchema, + currentParameters, + ); if ( patch.key !== undefined && !isStartFormFieldKeyAvailable(currentData, currentKey, patch.key) @@ -881,19 +936,21 @@ export function updateStartFormField( return { ...field, ...patch, - key: trimString(patch.key) || ( - shouldAutoRenameKey + key: + trimString(patch.key) || + (shouldAutoRenameKey ? buildUniqueStartFormFieldKey( nextType, schema .filter((item) => item.key !== field.key) .map((item) => item.key), ) - : field.key - ), - label: trimString(patch.label) || ( - shouldAutoRenameLabel ? getDefaultStartFormFieldLabel(nextType) : field.label - ), + : field.key), + label: + trimString(patch.label) || + (shouldAutoRenameLabel + ? getDefaultStartFormFieldLabel(nextType) + : field.label), type: nextType, }; }); @@ -931,7 +988,10 @@ export function removeStartFormField( const currentParameters = Array.isArray(currentData.parameters) ? (currentData.parameters as Parameter[]) : []; - const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters); + const schema = normalizeStartFormSchema( + currentData.startFormSchema, + currentParameters, + ); return normalizeStartNodeData({ ...currentData, startFormSchema: schema.filter((field) => field.key !== currentKey), @@ -959,7 +1019,10 @@ export function normalizeStartNodeData( }; } - const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters); + const schema = normalizeStartFormSchema( + currentData.startFormSchema, + currentParameters, + ); const parameterMap = new Map( currentParameters .map((parameter) => [trimString(parameter.name), parameter] as const) @@ -973,8 +1036,9 @@ export function normalizeStartNodeData( const parameters = schema.map((field) => startFormFieldToParameter( field, - (trimString(field.id) ? parameterIdMap.get(trimString(field.id)) : undefined) || - parameterMap.get(field.key), + (trimString(field.id) + ? parameterIdMap.get(trimString(field.id)) + : undefined) || parameterMap.get(field.key), ), ); @@ -1010,7 +1074,9 @@ export function ensureStartNodeParameters(parameters?: Parameter[]) { const source = Array.isArray(parameters) ? parameters.map(cloneParameter) : []; - const index = source.findIndex((parameter) => isSystemStartParameter(parameter)); + const index = source.findIndex((parameter) => + isSystemStartParameter(parameter), + ); if (index >= 0) { source[index] = normalizeSystemStartParameter(source[index]!); @@ -1022,7 +1088,10 @@ export function ensureStartNodeParameters(parameters?: Parameter[]) { .filter((parameter) => !isSystemStartParameter(parameter)) .map((parameter) => ensureParameterId(parameter)); - return [source.find((parameter) => isSystemStartParameter(parameter))!, ...customParameters]; + return [ + source.find((parameter) => isSystemStartParameter(parameter))!, + ...customParameters, + ]; } export function createInitialWorkflowData() { @@ -1055,7 +1124,9 @@ export function createInitialWorkflowData() { }; } -export function normalizeWorkflowStartNodes>(data: T): T { +export function normalizeWorkflowStartNodes>( + data: T, +): T { if (!data || typeof data !== 'object' || !Array.isArray(data.nodes)) { return data; } @@ -1127,13 +1198,11 @@ export function buildEditorReferenceParameters( } return Boolean( currentNode && - node.type === 'loopNode' && - buildLoopScopeParameters(node, currentNode, nodes).length, + node.type === 'loopNode' && + buildLoopScopeParameters(node, currentNode, nodes).length, ); }) - .flatMap((node) => - getNodeReferenceParameters(node, currentNode, nodes), - ); + .flatMap((node) => getNodeReferenceParameters(node, currentNode, nodes)); const upstreamNameSet = new Set( upstreamParameters.map((parameter) => asString(parameter.name).trim()), @@ -1172,7 +1241,9 @@ export function syncManagedParametersForFields( const tokenKeys = Array.from( new Set( Object.values(fieldValues) - .flatMap((value) => getTokenRanges(asString(value)).map((token) => token.key)) + .flatMap((value) => + getTokenRanges(asString(value)).map((token) => token.key), + ) .filter((value) => value.trim().length > 0), ), ); @@ -1188,7 +1259,9 @@ export function syncManagedParametersForFields( return true; } const parameterName = asString(parameter.name).trim(); - return usedTokenKeySet.has(parameterName) && candidateKeySet.has(parameterName); + return ( + usedTokenKeySet.has(parameterName) && candidateKeySet.has(parameterName) + ); }) .map((parameter) => { if (parameter.autoManaged !== true) { @@ -1276,7 +1349,7 @@ function resolveNearestStartNodeRef( } if (sourceNode.type === START_NODE_TYPE) { const parameters = Array.isArray(sourceNode.data?.parameters) - ? ((sourceNode.data?.parameters as Parameter[]) || []) + ? (sourceNode.data?.parameters as Parameter[]) || [] : []; const systemParam = parameters.find((parameter) => isSystemStartParameter(parameter), @@ -1341,7 +1414,11 @@ function replaceStartFieldReferenceValue( if (Array.isArray(value)) { let changed = false; const nextValue = value.map((item) => { - const nextItem = replaceStartFieldReferenceValue(item, oldRefPath, newRefPath); + const nextItem = replaceStartFieldReferenceValue( + item, + oldRefPath, + newRefPath, + ); if (nextItem !== item) { changed = true; } @@ -1371,10 +1448,7 @@ function replaceStartFieldReferenceValue( return value; } -export function collectDownstreamNodeIds( - rootNodeId: string, - edges: Edge[], -) { +export function collectDownstreamNodeIds(rootNodeId: string, edges: Edge[]) { const nodeIds = new Set(); const visit = (nodeId: string) => { @@ -1383,7 +1457,9 @@ export function collectDownstreamNodeIds( } nodeIds.add(nodeId); edges - .filter((edge) => edge.source === nodeId && edge.sourceHandle !== 'loop_handle') + .filter( + (edge) => edge.source === nodeId && edge.sourceHandle !== 'loop_handle', + ) .forEach((edge) => { if (edge.target) { visit(edge.target); @@ -1436,7 +1512,10 @@ export function renameStartFieldReferencesInNodes( }; }); - const affectedNodeIds = collectDownstreamNodeIds(normalizedStartNodeId, edges); + const affectedNodeIds = collectDownstreamNodeIds( + normalizedStartNodeId, + edges, + ); if (affectedNodeIds.length === 0) { return nextNodes; } @@ -1450,7 +1529,9 @@ export function renameStartFieldReferencesInNodes( return nextNodes; } - const patchMap = new Map(patches.map((item) => [item.nodeId, item.patch] as const)); + const patchMap = new Map( + patches.map((item) => [item.nodeId, item.patch] as const), + ); return nextNodes.map((node) => { const patch = patchMap.get(node.id); if (!patch) { @@ -1539,9 +1620,10 @@ export function buildFieldBindingPatch( const data = (node.data || {}) as Record; const startRef = resolveNearestStartNodeRef(node.id, nodes, edges); const patch: Record = {}; - const nextFieldValues: Record = Object.fromEntries( - fieldKeys.map((fieldKey) => [fieldKey, asString(data[fieldKey])]), - ); + const nextFieldValues: Record = + Object.fromEntries( + fieldKeys.map((fieldKey) => [fieldKey, asString(data[fieldKey])]), + ); const nextMeta: FieldBindingMeta = { ...getFieldBindingMeta(data), }; @@ -1551,7 +1633,13 @@ export function buildFieldBindingPatch( return; } const fieldMeta = nextMeta[fieldName]; - if (!canApplyAutoBinding(nextFieldValues[fieldName], fieldMeta, startRef.refPath)) { + if ( + !canApplyAutoBinding( + nextFieldValues[fieldName], + fieldMeta, + startRef.refPath, + ) + ) { return; } const tokenValue = toToken(startRef.refPath); @@ -1565,7 +1653,10 @@ export function buildFieldBindingPatch( }; }; - const clearField = (fieldName: string, fieldMeta: NonNullable) => { + const clearField = ( + fieldName: string, + fieldMeta: NonNullable, + ) => { const currentValue = asString(nextFieldValues[fieldName]).trim(); const autoTokenValue = toToken(fieldMeta.autoFilledFrom || ''); if (currentValue && currentValue !== autoTokenValue) { @@ -1606,14 +1697,14 @@ export function buildFieldBindingPatch( ); patch[FIELD_BINDING_META_KEY] = nextMeta; - const currentParameters = JSON.stringify((data.parameters as Parameter[]) || []); + const currentParameters = JSON.stringify( + (data.parameters as Parameter[]) || [], + ); if (JSON.stringify(patch.parameters) === currentParameters) { delete patch.parameters; } - if ( - JSON.stringify(nextMeta) === JSON.stringify(getFieldBindingMeta(data)) - ) { + if (JSON.stringify(nextMeta) === JSON.stringify(getFieldBindingMeta(data))) { delete patch[FIELD_BINDING_META_KEY]; } @@ -1675,7 +1766,9 @@ export function buildSequentialFieldBindingPatches( return patches; } -export function buildSingleRunParameters(node: Pick | null | undefined) { +export function buildSingleRunParameters( + node: Pick | null | undefined, +) { if (!node?.data) { return []; } @@ -1710,21 +1803,23 @@ export function buildSingleRunParameters(node: Pick | nul const usedTokenKeys = Array.from( new Set( fieldKeys.flatMap((fieldKey) => - getTokenRanges(asString((node.data as Record)[fieldKey])).map( - (token) => token.key, - ), + getTokenRanges( + asString((node.data as Record)[fieldKey]), + ).map((token) => token.key), ), ), ); - return parameters.filter((parameter) => - usedTokenKeys.includes(asString(parameter.name).trim()), - ).map((parameter) => - ensureParameterId({ - ...parameter, - required: parameter.required ?? true, - }), - ); + return parameters + .filter((parameter) => + usedTokenKeys.includes(asString(parameter.name).trim()), + ) + .map((parameter) => + ensureParameterId({ + ...parameter, + required: parameter.required ?? true, + }), + ); } export function buildSingleRunModel(