发布 v1.10 #5
@@ -49,10 +49,13 @@ describe('workflow node fields', () => {
|
|||||||
it('appends custom start form field into schema source of truth', () => {
|
it('appends custom start form field into schema source of truth', () => {
|
||||||
const initial = createInitialWorkflowData();
|
const initial = createInitialWorkflowData();
|
||||||
const startNode = initial.nodes[0]!;
|
const startNode = initial.nodes[0]!;
|
||||||
const nextData = appendStartFormField(startNode.data as Record<string, any>, {
|
const nextData = appendStartFormField(
|
||||||
type: 'select',
|
startNode.data as Record<string, any>,
|
||||||
options: ['售前', '售后'],
|
{
|
||||||
});
|
type: 'select',
|
||||||
|
options: ['售前', '售后'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(nextData.startFormSchema).toHaveLength(2);
|
expect(nextData.startFormSchema).toHaveLength(2);
|
||||||
expect(nextData.startFormSchema?.[1]).toMatchObject({
|
expect(nextData.startFormSchema?.[1]).toMatchObject({
|
||||||
@@ -71,54 +74,64 @@ describe('workflow node fields', () => {
|
|||||||
|
|
||||||
it('updates generated field key when switching field type', () => {
|
it('updates generated field key when switching field type', () => {
|
||||||
const initial = createInitialWorkflowData();
|
const initial = createInitialWorkflowData();
|
||||||
const appended = appendStartFormField(initial.nodes[0]?.data as Record<string, any>, {
|
const appended = appendStartFormField(
|
||||||
type: 'text',
|
initial.nodes[0]?.data as Record<string, any>,
|
||||||
});
|
{
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const updated = updateStartFormField(appended, 'text_field', {
|
const updated = updateStartFormField(appended, 'text_field', {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
placeholder: '请选择文件',
|
placeholder: '请选择文件',
|
||||||
});
|
});
|
||||||
expect(updated.startFormSchema?.find((item: any) => item.key === 'file_field'))
|
expect(
|
||||||
.toMatchObject({
|
updated.startFormSchema?.find((item: any) => item.key === 'file_field'),
|
||||||
key: 'file_field',
|
).toMatchObject({
|
||||||
label: '文件字段',
|
key: 'file_field',
|
||||||
type: 'file',
|
label: '文件字段',
|
||||||
contentType: 'file',
|
type: 'file',
|
||||||
placeholder: '请选择文件',
|
contentType: 'file',
|
||||||
});
|
placeholder: '请选择文件',
|
||||||
expect((updated.parameters as any[]).find((item) => item.name === 'file_field'))
|
});
|
||||||
.toMatchObject({
|
expect(
|
||||||
name: 'file_field',
|
(updated.parameters as any[]).find((item) => item.name === 'file_field'),
|
||||||
dataType: 'File',
|
).toMatchObject({
|
||||||
contentType: 'file',
|
name: 'file_field',
|
||||||
});
|
dataType: 'File',
|
||||||
|
contentType: 'file',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates and removes custom start form fields through schema source of truth', () => {
|
it('updates and removes custom start form fields through schema source of truth', () => {
|
||||||
const initial = createInitialWorkflowData();
|
const initial = createInitialWorkflowData();
|
||||||
const appended = appendStartFormField(initial.nodes[0]?.data as Record<string, any>, {
|
const appended = appendStartFormField(
|
||||||
key: 'attachments',
|
initial.nodes[0]?.data as Record<string, any>,
|
||||||
label: '附件',
|
{
|
||||||
type: 'text',
|
key: 'attachments',
|
||||||
placeholder: '请输入内容',
|
label: '附件',
|
||||||
});
|
type: 'text',
|
||||||
|
placeholder: '请输入内容',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const updated = updateStartFormField(appended, 'attachments', {
|
const updated = updateStartFormField(appended, 'attachments', {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
placeholder: '请选择文件',
|
placeholder: '请选择文件',
|
||||||
});
|
});
|
||||||
expect(updated.startFormSchema?.find((item: any) => item.key === 'attachments'))
|
expect(
|
||||||
.toMatchObject({
|
updated.startFormSchema?.find((item: any) => item.key === 'attachments'),
|
||||||
type: 'file',
|
).toMatchObject({
|
||||||
contentType: 'file',
|
type: 'file',
|
||||||
placeholder: '请选择文件',
|
contentType: 'file',
|
||||||
});
|
placeholder: '请选择文件',
|
||||||
expect((updated.parameters as any[]).find((item) => item.name === 'attachments'))
|
});
|
||||||
.toMatchObject({
|
expect(
|
||||||
dataType: 'File',
|
(updated.parameters as any[]).find((item) => item.name === 'attachments'),
|
||||||
contentType: 'file',
|
).toMatchObject({
|
||||||
});
|
dataType: 'File',
|
||||||
|
contentType: 'file',
|
||||||
|
});
|
||||||
|
|
||||||
const removed = removeStartFormField(updated, 'attachments');
|
const removed = removeStartFormField(updated, 'attachments');
|
||||||
expect(removed.startFormSchema).toHaveLength(1);
|
expect(removed.startFormSchema).toHaveLength(1);
|
||||||
@@ -129,10 +142,15 @@ describe('workflow node fields', () => {
|
|||||||
|
|
||||||
it('keeps custom start field parameter id stable when renaming key', () => {
|
it('keeps custom start field parameter id stable when renaming key', () => {
|
||||||
const initial = createInitialWorkflowData();
|
const initial = createInitialWorkflowData();
|
||||||
const appended = appendStartFormField(initial.nodes[0]?.data as Record<string, any>, {
|
const appended = appendStartFormField(
|
||||||
type: 'text',
|
initial.nodes[0]?.data as Record<string, any>,
|
||||||
});
|
{
|
||||||
const previousField = appended.startFormSchema?.find((item: any) => item.key === 'text_field');
|
type: 'text',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const previousField = appended.startFormSchema?.find(
|
||||||
|
(item: any) => item.key === 'text_field',
|
||||||
|
);
|
||||||
const previousParameter = (appended.parameters as any[]).find(
|
const previousParameter = (appended.parameters as any[]).find(
|
||||||
(item) => item.name === 'text_field',
|
(item) => item.name === 'text_field',
|
||||||
);
|
);
|
||||||
@@ -141,7 +159,9 @@ describe('workflow node fields', () => {
|
|||||||
key: 'topic',
|
key: 'topic',
|
||||||
label: '主题',
|
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(
|
const nextParameter = (updated.parameters as any[]).find(
|
||||||
(item) => item.name === 'topic',
|
(item) => item.name === 'topic',
|
||||||
);
|
);
|
||||||
@@ -151,6 +171,161 @@ describe('workflow node fields', () => {
|
|||||||
expect(nextParameter?.id).toBe(previousParameter?.id);
|
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<string, any>,
|
||||||
|
{
|
||||||
|
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<string, any>,
|
||||||
|
{
|
||||||
|
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<string, any>,
|
||||||
|
{
|
||||||
|
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<string, any>,
|
||||||
|
'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<string, any>),
|
||||||
|
...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', () => {
|
it('preserves resource content type independently from input type', () => {
|
||||||
for (const contentType of ['image', 'video', 'audio', 'other'] as const) {
|
for (const contentType of ['image', 'video', 'audio', 'other'] as const) {
|
||||||
const key = `preview_${contentType}`;
|
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<string, any>,
|
|
||||||
{
|
|
||||||
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<string, any>,
|
|
||||||
{
|
|
||||||
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<string, any>,
|
|
||||||
'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<string, any>),
|
|
||||||
...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', () => {
|
it('uses custom start parameter name for reference display', () => {
|
||||||
const startData = appendStartFormField(
|
const startData = appendStartFormField(
|
||||||
createInitialWorkflowData().nodes[0]?.data as Record<string, any>,
|
createInitialWorkflowData().nodes[0]?.data as Record<string, any>,
|
||||||
@@ -406,7 +457,9 @@ describe('workflow node fields', () => {
|
|||||||
edges,
|
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?.displayName).toBe('开始节点 > topic_name');
|
||||||
expect(topicParameter?.formLabel).toBe('开始节点 > topic_name');
|
expect(topicParameter?.formLabel).toBe('开始节点 > topic_name');
|
||||||
@@ -491,8 +544,12 @@ describe('workflow node fields', () => {
|
|||||||
edges,
|
edges,
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const documentsParameter = parameters.find((item) => item.name === 'knowledge_1.documents');
|
const documentsParameter = parameters.find(
|
||||||
const contentParameter = parameters.find((item) => item.name === 'knowledge_1.documents.content');
|
(item) => item.name === 'knowledge_1.documents',
|
||||||
|
);
|
||||||
|
const contentParameter = parameters.find(
|
||||||
|
(item) => item.name === 'knowledge_1.documents.content',
|
||||||
|
);
|
||||||
|
|
||||||
expect(documentsParameter?.displayName).toBe('知识库 > documents');
|
expect(documentsParameter?.displayName).toBe('知识库 > documents');
|
||||||
expect(documentsParameter?.formLabel).toBe('知识库 > documents');
|
expect(documentsParameter?.formLabel).toBe('知识库 > documents');
|
||||||
@@ -558,12 +615,7 @@ describe('workflow node fields', () => {
|
|||||||
position: { x: 240, y: 120 },
|
position: { x: 240, y: 120 },
|
||||||
data: { title: '循环外模型', parameters: [] },
|
data: { title: '循环外模型', parameters: [] },
|
||||||
};
|
};
|
||||||
const nodes = [
|
const nodes = [knowledgeNode, loopNode, insideNode, outsideNode];
|
||||||
knowledgeNode,
|
|
||||||
loopNode,
|
|
||||||
insideNode,
|
|
||||||
outsideNode,
|
|
||||||
];
|
|
||||||
|
|
||||||
const insideParameters = buildEditorReferenceParameters(
|
const insideParameters = buildEditorReferenceParameters(
|
||||||
'llm_inside',
|
'llm_inside',
|
||||||
@@ -591,13 +643,10 @@ describe('workflow node fields', () => {
|
|||||||
'loop_1.loopItem.content',
|
'loop_1.loopItem.content',
|
||||||
]);
|
]);
|
||||||
expect(
|
expect(
|
||||||
insideParameters.find(
|
insideParameters.find((item) => item.name === 'loop_1.loopItem.content')
|
||||||
(item) => item.name === 'loop_1.loopItem.content',
|
?.dataType,
|
||||||
)?.dataType,
|
|
||||||
).toBe('String');
|
).toBe('String');
|
||||||
expect(
|
expect(outsideParameters.map((item) => item.name)).toEqual(['loop_1.res']);
|
||||||
outsideParameters.map((item) => item.name),
|
|
||||||
).toEqual(['loop_1.res']);
|
|
||||||
expect(
|
expect(
|
||||||
outsideParameters.some((item) =>
|
outsideParameters.some((item) =>
|
||||||
['loop_1.index', 'loop_1.loopItem'].includes(item.name || ''),
|
['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)
|
expect(
|
||||||
.toBe('文档解析 > documents');
|
parameters.find((item) => item.name === 'doc_1.documents')?.displayName,
|
||||||
expect(parameters.find((item) => item.name === 'doc_1.documents.fileName')?.displayName)
|
).toBe('文档解析 > documents');
|
||||||
.toBe('文档解析 > documents.[].fileName');
|
expect(
|
||||||
expect(parameters.find((item) => item.name === 'doc_1.documents.content')?.displayName)
|
parameters.find((item) => item.name === 'doc_1.documents.fileName')
|
||||||
.toBe('文档解析 > documents.[].content');
|
?.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', () => {
|
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);
|
const patch = buildAutoBindingPatch(llmNode, [startNode, llmNode], edges);
|
||||||
expect(patch?.userPrompt).toBe('{{start_1.user_input}}');
|
expect(patch?.userPrompt).toBe('{{start_1.user_input}}');
|
||||||
expect((patch?.parameters as any[])?.[0]?.name).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)
|
expect(
|
||||||
.toBe(false);
|
(patch?.[FIELD_BINDING_META_KEY] as any)?.userPrompt?.userModified,
|
||||||
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not auto-bind from legacy start nodes without user_input', () => {
|
it('does not auto-bind from legacy start nodes without user_input', () => {
|
||||||
@@ -821,7 +876,9 @@ describe('workflow node fields', () => {
|
|||||||
[startNode, knowledgeNode, llmNode],
|
[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]?.disconnected).toBe(true);
|
||||||
expect((patch?.parameters as any[])?.[0]?.displayName).toBe('documents');
|
expect((patch?.parameters as any[])?.[0]?.displayName).toBe('documents');
|
||||||
expect(patch).not.toHaveProperty('systemPrompt');
|
expect(patch).not.toHaveProperty('systemPrompt');
|
||||||
@@ -1138,9 +1195,7 @@ describe('workflow node fields', () => {
|
|||||||
expect(
|
expect(
|
||||||
(normalizedWorkflow.nodes[0]?.data as Record<string, any> | undefined)
|
(normalizedWorkflow.nodes[0]?.data as Record<string, any> | undefined)
|
||||||
?.startFormSchema?.[0]?.key,
|
?.startFormSchema?.[0]?.key,
|
||||||
).toBe(
|
).toBe('user_input');
|
||||||
'user_input',
|
|
||||||
);
|
|
||||||
expect(normalizedWorkflow.nodes[1]?.data?.parameters).toEqual([]);
|
expect(normalizedWorkflow.nodes[1]?.data?.parameters).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,15 +95,19 @@ const OPTION_FIELD_TYPE_SET = new Set<StartFormFieldType>([
|
|||||||
const DEFAULT_START_FORM_TITLE = '开始问答';
|
const DEFAULT_START_FORM_TITLE = '开始问答';
|
||||||
const DEFAULT_START_FORM_DESCRIPTION = '请先补充必要信息,再开始执行工作流。';
|
const DEFAULT_START_FORM_DESCRIPTION = '请先补充必要信息,再开始执行工作流。';
|
||||||
const DEFAULT_START_FORM_SUBMIT_TEXT = '开始';
|
const DEFAULT_START_FORM_SUBMIT_TEXT = '开始';
|
||||||
const START_FORM_DEFAULT_FIELD_KEY_BY_TYPE: Record<StartFormFieldType, string> = {
|
const START_FORM_DEFAULT_FIELD_KEY_BY_TYPE: Record<StartFormFieldType, string> =
|
||||||
text: 'text_field',
|
{
|
||||||
textarea: 'textarea_field',
|
text: 'text_field',
|
||||||
radio: 'radio_field',
|
textarea: 'textarea_field',
|
||||||
checkbox: 'checkbox_field',
|
radio: 'radio_field',
|
||||||
select: 'select_field',
|
checkbox: 'checkbox_field',
|
||||||
file: 'file_field',
|
select: 'select_field',
|
||||||
};
|
file: 'file_field',
|
||||||
const START_FORM_DEFAULT_FIELD_LABEL_BY_TYPE: Record<StartFormFieldType, string> = {
|
};
|
||||||
|
const START_FORM_DEFAULT_FIELD_LABEL_BY_TYPE: Record<
|
||||||
|
StartFormFieldType,
|
||||||
|
string
|
||||||
|
> = {
|
||||||
text: '文本字段',
|
text: '文本字段',
|
||||||
textarea: '长文本字段',
|
textarea: '长文本字段',
|
||||||
radio: '单选字段',
|
radio: '单选字段',
|
||||||
@@ -135,12 +139,18 @@ function ensureStringArray(value: unknown) {
|
|||||||
return value.map((item) => asString(item).trim()).filter(Boolean);
|
return value.map((item) => asString(item).trim()).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSupportedStartFormFieldType(value: unknown): value is StartFormFieldType {
|
function isSupportedStartFormFieldType(
|
||||||
return START_FORM_FIELD_TYPE_SET.has(asString(value).trim() as StartFormFieldType);
|
value: unknown,
|
||||||
|
): value is StartFormFieldType {
|
||||||
|
return START_FORM_FIELD_TYPE_SET.has(
|
||||||
|
asString(value).trim() as StartFormFieldType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptionFieldType(value: unknown): value is 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(
|
function normalizeStartFormContentType(
|
||||||
@@ -193,10 +203,7 @@ function isCollectionOutputParameter(parameter?: Parameter | null) {
|
|||||||
const dataType = asString(parameter?.dataType).trim();
|
const dataType = asString(parameter?.dataType).trim();
|
||||||
return (
|
return (
|
||||||
!!parameter?.children?.length &&
|
!!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 path = parentPath ? `${parentPath}.${rawName}` : rawName;
|
||||||
const referenceLabel = getReferenceParameterLabel(parameter);
|
const referenceLabel = getReferenceParameterLabel(parameter);
|
||||||
const label = buildCollectionAwareLabel(referenceLabel, parentLabel, parentIsCollection);
|
const label = buildCollectionAwareLabel(
|
||||||
|
referenceLabel,
|
||||||
|
parentLabel,
|
||||||
|
parentIsCollection,
|
||||||
|
);
|
||||||
const displayName = `${getNodeTitle(node)} > ${label}`;
|
const displayName = `${getNodeTitle(node)} > ${label}`;
|
||||||
const isCollection = isCollectionOutputParameter(parameter);
|
const isCollection = isCollectionOutputParameter(parameter);
|
||||||
const fullRef = `${node.id}.${path}`;
|
const fullRef = `${node.id}.${path}`;
|
||||||
@@ -376,10 +387,15 @@ function uniqueParameters(parameters: Parameter[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findParameterByName(parameters: Parameter[], name: string) {
|
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({
|
return ensureParameterId({
|
||||||
name: refPath,
|
name: refPath,
|
||||||
ref: candidate?.ref || refPath,
|
ref: candidate?.ref || refPath,
|
||||||
@@ -448,7 +464,10 @@ function isAutoGeneratedStartFormFieldKey(key: unknown) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return Object.values(START_FORM_DEFAULT_FIELD_KEY_BY_TYPE).some((baseKey) => {
|
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 === '新字段') {
|
if (normalizedLabel === '新字段') {
|
||||||
return true;
|
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(
|
function ensureStartFormFieldId(
|
||||||
fieldId: unknown,
|
fieldId: unknown,
|
||||||
existingParameter?: Parameter | null,
|
existingParameter?: Parameter | null,
|
||||||
) {
|
) {
|
||||||
return trimString(fieldId) || trimString(existingParameter?.id) || genShortId();
|
return (
|
||||||
|
trimString(fieldId) || trimString(existingParameter?.id) || genShortId()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDefaultStartFormMeta(): StartFormMeta {
|
function createDefaultStartFormMeta(): StartFormMeta {
|
||||||
@@ -478,17 +501,24 @@ function createDefaultStartFormMeta(): StartFormMeta {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeStartFormMeta(meta?: Partial<StartFormMeta> | null): StartFormMeta {
|
export function normalizeStartFormMeta(
|
||||||
|
meta?: Partial<StartFormMeta> | null,
|
||||||
|
): StartFormMeta {
|
||||||
const fallback = createDefaultStartFormMeta();
|
const fallback = createDefaultStartFormMeta();
|
||||||
return {
|
return {
|
||||||
// Missing values use defaults; an explicit empty string means the user cleared the field.
|
// Missing values use defaults; an explicit empty string means the user cleared the field.
|
||||||
title: meta?.title == null ? fallback.title : trimString(meta.title),
|
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,
|
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 fallbackLabel = SYSTEM_START_PARAM_LABEL;
|
||||||
const formLabel = trimString(parameter?.formLabel) || fallbackLabel;
|
const formLabel = trimString(parameter?.formLabel) || fallbackLabel;
|
||||||
const formType = normalizeSystemStartFormType(parameter?.formType);
|
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 (
|
if (
|
||||||
trimString(parameter?.contentType) === 'file' ||
|
trimString(parameter?.contentType) === 'file' ||
|
||||||
trimString(parameter?.dataType).toLowerCase() === 'file'
|
trimString(parameter?.dataType).toLowerCase() === 'file'
|
||||||
@@ -605,6 +637,17 @@ function normalizeStartFormField(
|
|||||||
const options = isOptionFieldType(type)
|
const options = isOptionFieldType(type)
|
||||||
? ensureStringArray(field?.options || existingParameter?.enums)
|
? 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)
|
const defaultValue = Array.isArray(field?.defaultValue)
|
||||||
? ensureStringArray(field?.defaultValue)
|
? ensureStringArray(field?.defaultValue)
|
||||||
@@ -613,18 +656,18 @@ function normalizeStartFormField(
|
|||||||
return {
|
return {
|
||||||
id: ensureStartFormFieldId(field?.id, existingParameter),
|
id: ensureStartFormFieldId(field?.id, existingParameter),
|
||||||
key,
|
key,
|
||||||
label:
|
label,
|
||||||
trimString(field?.label) ||
|
|
||||||
trimString(existingParameter?.formLabel) ||
|
|
||||||
trimString(existingParameter?.displayName) ||
|
|
||||||
key,
|
|
||||||
type,
|
type,
|
||||||
contentType,
|
contentType,
|
||||||
required: isSystemField ? true : Boolean(field?.required ?? existingParameter?.required),
|
required: isSystemField
|
||||||
|
? true
|
||||||
|
: Boolean(field?.required ?? existingParameter?.required),
|
||||||
placeholder:
|
placeholder:
|
||||||
trimString(field?.placeholder) || trimString(existingParameter?.formPlaceholder),
|
trimString(field?.placeholder) ||
|
||||||
|
trimString(existingParameter?.formPlaceholder),
|
||||||
description:
|
description:
|
||||||
trimString(field?.description) || trimString(existingParameter?.formDescription),
|
trimString(field?.description) ||
|
||||||
|
trimString(existingParameter?.formDescription),
|
||||||
defaultValue,
|
defaultValue,
|
||||||
options,
|
options,
|
||||||
systemReserved: isSystemField,
|
systemReserved: isSystemField,
|
||||||
@@ -684,7 +727,9 @@ function startFormFieldToParameter(
|
|||||||
defaultValue: Array.isArray(field.defaultValue)
|
defaultValue: Array.isArray(field.defaultValue)
|
||||||
? field.defaultValue.join('\n')
|
? field.defaultValue.join('\n')
|
||||||
: asString(field.defaultValue),
|
: asString(field.defaultValue),
|
||||||
enums: isOptionFieldType(field.type) ? ensureStringArray(field.options) : [],
|
enums: isOptionFieldType(field.type)
|
||||||
|
? ensureStringArray(field.options)
|
||||||
|
: [],
|
||||||
displayName: `流程开始 > ${formLabel}`,
|
displayName: `流程开始 > ${formLabel}`,
|
||||||
systemReserved: false,
|
systemReserved: false,
|
||||||
});
|
});
|
||||||
@@ -708,11 +753,12 @@ export function normalizeStartFormSchema(
|
|||||||
.filter(([key]) => key.length > 0),
|
.filter(([key]) => key.length > 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
const source = Array.isArray(schema) && schema.length > 0
|
const source =
|
||||||
? schema
|
Array.isArray(schema) && schema.length > 0
|
||||||
: parameterCandidates
|
? schema
|
||||||
.map((parameter) => parameterToStartFormField(parameter))
|
: parameterCandidates
|
||||||
.filter(Boolean);
|
.map((parameter) => parameterToStartFormField(parameter))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
const result: StartFormFieldSchema[] = [];
|
const result: StartFormFieldSchema[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
@@ -727,7 +773,8 @@ export function normalizeStartFormSchema(
|
|||||||
}
|
}
|
||||||
const normalized = normalizeStartFormField(
|
const normalized = normalizeStartFormField(
|
||||||
item as Partial<StartFormFieldSchema>,
|
item as Partial<StartFormFieldSchema>,
|
||||||
(fieldId ? parameterIdMap.get(fieldId) : undefined) || parameterMap.get(key),
|
(fieldId ? parameterIdMap.get(fieldId) : undefined) ||
|
||||||
|
parameterMap.get(key),
|
||||||
);
|
);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
continue;
|
continue;
|
||||||
@@ -749,7 +796,8 @@ export function normalizeStartFormSchema(
|
|||||||
{
|
{
|
||||||
id: trimString(systemParameter?.id),
|
id: trimString(systemParameter?.id),
|
||||||
key: SYSTEM_START_PARAM_NAME,
|
key: SYSTEM_START_PARAM_NAME,
|
||||||
label: trimString(systemParameter?.formLabel) || SYSTEM_START_PARAM_LABEL,
|
label:
|
||||||
|
trimString(systemParameter?.formLabel) || SYSTEM_START_PARAM_LABEL,
|
||||||
type:
|
type:
|
||||||
normalizeSystemStartFormType(systemParameter?.formType) === 'input'
|
normalizeSystemStartFormType(systemParameter?.formType) === 'input'
|
||||||
? 'text'
|
? 'text'
|
||||||
@@ -810,7 +858,8 @@ export function createCustomStartFormField(
|
|||||||
requestedType === 'file' ? 'file' : 'text',
|
requestedType === 'file' ? 'file' : 'text',
|
||||||
);
|
);
|
||||||
const type = contentType === 'file' ? 'file' : requestedType;
|
const type = contentType === 'file' ? 'file' : requestedType;
|
||||||
const key = trimString(field?.key) || buildUniqueStartFormFieldKey(type, existingKeys);
|
const key =
|
||||||
|
trimString(field?.key) || buildUniqueStartFormFieldKey(type, existingKeys);
|
||||||
return {
|
return {
|
||||||
id: ensureStartFormFieldId(field?.id),
|
id: ensureStartFormFieldId(field?.id),
|
||||||
key,
|
key,
|
||||||
@@ -838,7 +887,10 @@ export function appendStartFormField(
|
|||||||
const currentParameters = Array.isArray(currentData.parameters)
|
const currentParameters = Array.isArray(currentData.parameters)
|
||||||
? (currentData.parameters as Parameter[])
|
? (currentData.parameters as Parameter[])
|
||||||
: [];
|
: [];
|
||||||
const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters);
|
const schema = normalizeStartFormSchema(
|
||||||
|
currentData.startFormSchema,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
const nextField = createCustomStartFormField(
|
const nextField = createCustomStartFormField(
|
||||||
field,
|
field,
|
||||||
schema.map((item) => item.key),
|
schema.map((item) => item.key),
|
||||||
@@ -858,7 +910,10 @@ export function updateStartFormField(
|
|||||||
const currentParameters = Array.isArray(currentData.parameters)
|
const currentParameters = Array.isArray(currentData.parameters)
|
||||||
? (currentData.parameters as Parameter[])
|
? (currentData.parameters as Parameter[])
|
||||||
: [];
|
: [];
|
||||||
const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters);
|
const schema = normalizeStartFormSchema(
|
||||||
|
currentData.startFormSchema,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
patch.key !== undefined &&
|
patch.key !== undefined &&
|
||||||
!isStartFormFieldKeyAvailable(currentData, currentKey, patch.key)
|
!isStartFormFieldKeyAvailable(currentData, currentKey, patch.key)
|
||||||
@@ -881,19 +936,21 @@ export function updateStartFormField(
|
|||||||
return {
|
return {
|
||||||
...field,
|
...field,
|
||||||
...patch,
|
...patch,
|
||||||
key: trimString(patch.key) || (
|
key:
|
||||||
shouldAutoRenameKey
|
trimString(patch.key) ||
|
||||||
|
(shouldAutoRenameKey
|
||||||
? buildUniqueStartFormFieldKey(
|
? buildUniqueStartFormFieldKey(
|
||||||
nextType,
|
nextType,
|
||||||
schema
|
schema
|
||||||
.filter((item) => item.key !== field.key)
|
.filter((item) => item.key !== field.key)
|
||||||
.map((item) => item.key),
|
.map((item) => item.key),
|
||||||
)
|
)
|
||||||
: field.key
|
: field.key),
|
||||||
),
|
label:
|
||||||
label: trimString(patch.label) || (
|
trimString(patch.label) ||
|
||||||
shouldAutoRenameLabel ? getDefaultStartFormFieldLabel(nextType) : field.label
|
(shouldAutoRenameLabel
|
||||||
),
|
? getDefaultStartFormFieldLabel(nextType)
|
||||||
|
: field.label),
|
||||||
type: nextType,
|
type: nextType,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -931,7 +988,10 @@ export function removeStartFormField(
|
|||||||
const currentParameters = Array.isArray(currentData.parameters)
|
const currentParameters = Array.isArray(currentData.parameters)
|
||||||
? (currentData.parameters as Parameter[])
|
? (currentData.parameters as Parameter[])
|
||||||
: [];
|
: [];
|
||||||
const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters);
|
const schema = normalizeStartFormSchema(
|
||||||
|
currentData.startFormSchema,
|
||||||
|
currentParameters,
|
||||||
|
);
|
||||||
return normalizeStartNodeData({
|
return normalizeStartNodeData({
|
||||||
...currentData,
|
...currentData,
|
||||||
startFormSchema: schema.filter((field) => field.key !== currentKey),
|
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(
|
const parameterMap = new Map(
|
||||||
currentParameters
|
currentParameters
|
||||||
.map((parameter) => [trimString(parameter.name), parameter] as const)
|
.map((parameter) => [trimString(parameter.name), parameter] as const)
|
||||||
@@ -973,8 +1036,9 @@ export function normalizeStartNodeData(
|
|||||||
const parameters = schema.map((field) =>
|
const parameters = schema.map((field) =>
|
||||||
startFormFieldToParameter(
|
startFormFieldToParameter(
|
||||||
field,
|
field,
|
||||||
(trimString(field.id) ? parameterIdMap.get(trimString(field.id)) : undefined) ||
|
(trimString(field.id)
|
||||||
parameterMap.get(field.key),
|
? parameterIdMap.get(trimString(field.id))
|
||||||
|
: undefined) || parameterMap.get(field.key),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1010,7 +1074,9 @@ export function ensureStartNodeParameters(parameters?: Parameter[]) {
|
|||||||
const source = Array.isArray(parameters)
|
const source = Array.isArray(parameters)
|
||||||
? parameters.map(cloneParameter)
|
? parameters.map(cloneParameter)
|
||||||
: [];
|
: [];
|
||||||
const index = source.findIndex((parameter) => isSystemStartParameter(parameter));
|
const index = source.findIndex((parameter) =>
|
||||||
|
isSystemStartParameter(parameter),
|
||||||
|
);
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
source[index] = normalizeSystemStartParameter(source[index]!);
|
source[index] = normalizeSystemStartParameter(source[index]!);
|
||||||
@@ -1022,7 +1088,10 @@ export function ensureStartNodeParameters(parameters?: Parameter[]) {
|
|||||||
.filter((parameter) => !isSystemStartParameter(parameter))
|
.filter((parameter) => !isSystemStartParameter(parameter))
|
||||||
.map((parameter) => ensureParameterId(parameter));
|
.map((parameter) => ensureParameterId(parameter));
|
||||||
|
|
||||||
return [source.find((parameter) => isSystemStartParameter(parameter))!, ...customParameters];
|
return [
|
||||||
|
source.find((parameter) => isSystemStartParameter(parameter))!,
|
||||||
|
...customParameters,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInitialWorkflowData() {
|
export function createInitialWorkflowData() {
|
||||||
@@ -1055,7 +1124,9 @@ export function createInitialWorkflowData() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeWorkflowStartNodes<T extends Record<string, any>>(data: T): T {
|
export function normalizeWorkflowStartNodes<T extends Record<string, any>>(
|
||||||
|
data: T,
|
||||||
|
): T {
|
||||||
if (!data || typeof data !== 'object' || !Array.isArray(data.nodes)) {
|
if (!data || typeof data !== 'object' || !Array.isArray(data.nodes)) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -1127,13 +1198,11 @@ export function buildEditorReferenceParameters(
|
|||||||
}
|
}
|
||||||
return Boolean(
|
return Boolean(
|
||||||
currentNode &&
|
currentNode &&
|
||||||
node.type === 'loopNode' &&
|
node.type === 'loopNode' &&
|
||||||
buildLoopScopeParameters(node, currentNode, nodes).length,
|
buildLoopScopeParameters(node, currentNode, nodes).length,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.flatMap((node) =>
|
.flatMap((node) => getNodeReferenceParameters(node, currentNode, nodes));
|
||||||
getNodeReferenceParameters(node, currentNode, nodes),
|
|
||||||
);
|
|
||||||
|
|
||||||
const upstreamNameSet = new Set(
|
const upstreamNameSet = new Set(
|
||||||
upstreamParameters.map((parameter) => asString(parameter.name).trim()),
|
upstreamParameters.map((parameter) => asString(parameter.name).trim()),
|
||||||
@@ -1172,7 +1241,9 @@ export function syncManagedParametersForFields(
|
|||||||
const tokenKeys = Array.from(
|
const tokenKeys = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
Object.values(fieldValues)
|
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),
|
.filter((value) => value.trim().length > 0),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1188,7 +1259,9 @@ export function syncManagedParametersForFields(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const parameterName = asString(parameter.name).trim();
|
const parameterName = asString(parameter.name).trim();
|
||||||
return usedTokenKeySet.has(parameterName) && candidateKeySet.has(parameterName);
|
return (
|
||||||
|
usedTokenKeySet.has(parameterName) && candidateKeySet.has(parameterName)
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.map((parameter) => {
|
.map((parameter) => {
|
||||||
if (parameter.autoManaged !== true) {
|
if (parameter.autoManaged !== true) {
|
||||||
@@ -1276,7 +1349,7 @@ function resolveNearestStartNodeRef(
|
|||||||
}
|
}
|
||||||
if (sourceNode.type === START_NODE_TYPE) {
|
if (sourceNode.type === START_NODE_TYPE) {
|
||||||
const parameters = Array.isArray(sourceNode.data?.parameters)
|
const parameters = Array.isArray(sourceNode.data?.parameters)
|
||||||
? ((sourceNode.data?.parameters as Parameter[]) || [])
|
? (sourceNode.data?.parameters as Parameter[]) || []
|
||||||
: [];
|
: [];
|
||||||
const systemParam = parameters.find((parameter) =>
|
const systemParam = parameters.find((parameter) =>
|
||||||
isSystemStartParameter(parameter),
|
isSystemStartParameter(parameter),
|
||||||
@@ -1341,7 +1414,11 @@ function replaceStartFieldReferenceValue(
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
let changed = false;
|
let changed = false;
|
||||||
const nextValue = value.map((item) => {
|
const nextValue = value.map((item) => {
|
||||||
const nextItem = replaceStartFieldReferenceValue(item, oldRefPath, newRefPath);
|
const nextItem = replaceStartFieldReferenceValue(
|
||||||
|
item,
|
||||||
|
oldRefPath,
|
||||||
|
newRefPath,
|
||||||
|
);
|
||||||
if (nextItem !== item) {
|
if (nextItem !== item) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -1371,10 +1448,7 @@ function replaceStartFieldReferenceValue(
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectDownstreamNodeIds(
|
export function collectDownstreamNodeIds(rootNodeId: string, edges: Edge[]) {
|
||||||
rootNodeId: string,
|
|
||||||
edges: Edge[],
|
|
||||||
) {
|
|
||||||
const nodeIds = new Set<string>();
|
const nodeIds = new Set<string>();
|
||||||
|
|
||||||
const visit = (nodeId: string) => {
|
const visit = (nodeId: string) => {
|
||||||
@@ -1383,7 +1457,9 @@ export function collectDownstreamNodeIds(
|
|||||||
}
|
}
|
||||||
nodeIds.add(nodeId);
|
nodeIds.add(nodeId);
|
||||||
edges
|
edges
|
||||||
.filter((edge) => edge.source === nodeId && edge.sourceHandle !== 'loop_handle')
|
.filter(
|
||||||
|
(edge) => edge.source === nodeId && edge.sourceHandle !== 'loop_handle',
|
||||||
|
)
|
||||||
.forEach((edge) => {
|
.forEach((edge) => {
|
||||||
if (edge.target) {
|
if (edge.target) {
|
||||||
visit(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) {
|
if (affectedNodeIds.length === 0) {
|
||||||
return nextNodes;
|
return nextNodes;
|
||||||
}
|
}
|
||||||
@@ -1450,7 +1529,9 @@ export function renameStartFieldReferencesInNodes(
|
|||||||
return nextNodes;
|
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) => {
|
return nextNodes.map((node) => {
|
||||||
const patch = patchMap.get(node.id);
|
const patch = patchMap.get(node.id);
|
||||||
if (!patch) {
|
if (!patch) {
|
||||||
@@ -1539,9 +1620,10 @@ export function buildFieldBindingPatch(
|
|||||||
const data = (node.data || {}) as Record<string, any>;
|
const data = (node.data || {}) as Record<string, any>;
|
||||||
const startRef = resolveNearestStartNodeRef(node.id, nodes, edges);
|
const startRef = resolveNearestStartNodeRef(node.id, nodes, edges);
|
||||||
const patch: Record<string, any> = {};
|
const patch: Record<string, any> = {};
|
||||||
const nextFieldValues: Record<string, string | undefined> = Object.fromEntries(
|
const nextFieldValues: Record<string, string | undefined> =
|
||||||
fieldKeys.map((fieldKey) => [fieldKey, asString(data[fieldKey])]),
|
Object.fromEntries(
|
||||||
);
|
fieldKeys.map((fieldKey) => [fieldKey, asString(data[fieldKey])]),
|
||||||
|
);
|
||||||
const nextMeta: FieldBindingMeta = {
|
const nextMeta: FieldBindingMeta = {
|
||||||
...getFieldBindingMeta(data),
|
...getFieldBindingMeta(data),
|
||||||
};
|
};
|
||||||
@@ -1551,7 +1633,13 @@ export function buildFieldBindingPatch(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fieldMeta = nextMeta[fieldName];
|
const fieldMeta = nextMeta[fieldName];
|
||||||
if (!canApplyAutoBinding(nextFieldValues[fieldName], fieldMeta, startRef.refPath)) {
|
if (
|
||||||
|
!canApplyAutoBinding(
|
||||||
|
nextFieldValues[fieldName],
|
||||||
|
fieldMeta,
|
||||||
|
startRef.refPath,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tokenValue = toToken(startRef.refPath);
|
const tokenValue = toToken(startRef.refPath);
|
||||||
@@ -1565,7 +1653,10 @@ export function buildFieldBindingPatch(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearField = (fieldName: string, fieldMeta: NonNullable<FieldBindingMeta[string]>) => {
|
const clearField = (
|
||||||
|
fieldName: string,
|
||||||
|
fieldMeta: NonNullable<FieldBindingMeta[string]>,
|
||||||
|
) => {
|
||||||
const currentValue = asString(nextFieldValues[fieldName]).trim();
|
const currentValue = asString(nextFieldValues[fieldName]).trim();
|
||||||
const autoTokenValue = toToken(fieldMeta.autoFilledFrom || '');
|
const autoTokenValue = toToken(fieldMeta.autoFilledFrom || '');
|
||||||
if (currentValue && currentValue !== autoTokenValue) {
|
if (currentValue && currentValue !== autoTokenValue) {
|
||||||
@@ -1606,14 +1697,14 @@ export function buildFieldBindingPatch(
|
|||||||
);
|
);
|
||||||
patch[FIELD_BINDING_META_KEY] = nextMeta;
|
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) {
|
if (JSON.stringify(patch.parameters) === currentParameters) {
|
||||||
delete patch.parameters;
|
delete patch.parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (JSON.stringify(nextMeta) === JSON.stringify(getFieldBindingMeta(data))) {
|
||||||
JSON.stringify(nextMeta) === JSON.stringify(getFieldBindingMeta(data))
|
|
||||||
) {
|
|
||||||
delete patch[FIELD_BINDING_META_KEY];
|
delete patch[FIELD_BINDING_META_KEY];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1675,7 +1766,9 @@ export function buildSequentialFieldBindingPatches(
|
|||||||
return patches;
|
return patches;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildSingleRunParameters(node: Pick<Node, 'type' | 'data'> | null | undefined) {
|
export function buildSingleRunParameters(
|
||||||
|
node: Pick<Node, 'type' | 'data'> | null | undefined,
|
||||||
|
) {
|
||||||
if (!node?.data) {
|
if (!node?.data) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -1710,21 +1803,23 @@ export function buildSingleRunParameters(node: Pick<Node, 'type' | 'data'> | nul
|
|||||||
const usedTokenKeys = Array.from(
|
const usedTokenKeys = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
fieldKeys.flatMap((fieldKey) =>
|
fieldKeys.flatMap((fieldKey) =>
|
||||||
getTokenRanges(asString((node.data as Record<string, any>)[fieldKey])).map(
|
getTokenRanges(
|
||||||
(token) => token.key,
|
asString((node.data as Record<string, any>)[fieldKey]),
|
||||||
),
|
).map((token) => token.key),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return parameters.filter((parameter) =>
|
return parameters
|
||||||
usedTokenKeys.includes(asString(parameter.name).trim()),
|
.filter((parameter) =>
|
||||||
).map((parameter) =>
|
usedTokenKeys.includes(asString(parameter.name).trim()),
|
||||||
ensureParameterId({
|
)
|
||||||
...parameter,
|
.map((parameter) =>
|
||||||
required: parameter.required ?? true,
|
ensureParameterId({
|
||||||
}),
|
...parameter,
|
||||||
);
|
required: parameter.required ?? true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildSingleRunModel(
|
export function buildSingleRunModel(
|
||||||
|
|||||||
Reference in New Issue
Block a user