feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { SubWorkflowLink, createSubWorkflowLink } from './sub-workflow-link';

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useMemo, type FC } from 'react';
import { type WorkflowDetailInfoData } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { IconCozExit } from '@coze-arch/coze-design/icons';
import { useGlobalState } from '@/hooks';
import { type SubWorkflowDetailDTO, type Identifier } from '../types';
export const SubWorkflowLink: FC<{
workflowDetail: SubWorkflowDetailDTO;
identifier: Identifier;
}> = props => {
const { workflowDetail, identifier } = props;
const { workflowId } = identifier;
const { getProjectApi, spaceId } = useGlobalState();
const projectApi = getProjectApi();
const isProjectWorkflow = Boolean(
(workflowDetail as WorkflowDetailInfoData)?.project_id,
);
const subWorkflowProjectId = isProjectWorkflow
? (workflowDetail as WorkflowDetailInfoData)?.project_id
: undefined;
// 获取最新的 commitId
const commitId = useMemo(() => {
/** 来自资源库的流程需要获取最新的版本号 */
const latestVersion = isProjectWorkflow
? undefined
: workflowDetail.latest_flow_version;
const { workflowVersion } = identifier;
/** 是否是最新版本的子流程 */
const isLatest = !workflowVersion || workflowVersion === latestVersion;
return isLatest ? undefined : workflowDetail.commit_id;
}, [workflowDetail, isProjectWorkflow]);
const handleClick = (e: React.MouseEvent<HTMLSpanElement>) => {
if (IS_BOT_OP) {
window.open(
`${window.location.pathname}?workflow_id=${workflowId}`,
'_blank',
);
e.stopPropagation();
return;
}
// 存在 project id 时,是引用的 project 内的 workflow
if (subWorkflowProjectId && projectApi?.navigate) {
projectApi?.navigate(`/workflow/${workflowId}`);
} else {
let url = `/work_flow?space_id=${spaceId}&workflow_id=${workflowId}`;
if (commitId) {
url += `&version=${commitId}`;
}
window.open(url, '_blank');
}
};
return (
<span
className="cursor-pointer flex items-center w-full justify-between"
onClick={e => handleClick(e)}
>
{I18n.t('workflow_subwf_jump_detail', {}, 'Workflow Detail')}
<IconCozExit className="text-xs" />
</span>
);
};
export const createSubWorkflowLink = (
workflowDetail: SubWorkflowDetailDTO,
identifier: Identifier,
) => (
<SubWorkflowLink workflowDetail={workflowDetail} identifier={identifier} />
);

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { nanoid } from 'nanoid';
import { ViewVariableType } from '@coze-workflow/variable';
import { I18n } from '@coze-arch/i18n';
// 入参路径,试运行等功能依赖该路径提取参数
export const INPUT_PATH = 'inputs.inputParameters';
export const BATCH_MODE_PATH = 'inputs.batchMode';
export const BATCH_INPUT_LIST_PATH = 'inputs.batch.inputLists';
// 定义固定出参
export const OUTPUTS = [
{
key: nanoid(),
name: 'outputList',
type: ViewVariableType.ArrayObject,
children: [
{
key: nanoid(),
name: 'id',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'content',
type: ViewVariableType.String,
},
],
},
];
export const COLUMNS = [
{
label: I18n.t('workflow_detail_node_parameter_name'),
style: { width: 148 },
},
{ label: I18n.t('workflow_detail_end_output_value') },
];

View File

@@ -0,0 +1,205 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { get, omit } from 'lodash-es';
import {
type FlowNodeEntity,
type NodeFormContext,
} from '@flowgram-adapter/free-layout-editor';
import {
WorkflowNodeData,
getSortedInputParameters,
nodeUtils,
} from '@coze-workflow/nodes';
import {
StandardNodeType,
ValueExpressionType,
type WorkflowDetailInfoData,
} from '@coze-workflow/base';
import { type WorkflowPlaygroundContext } from '@/workflow-playground-context';
import { type NodeMeta } from '@/typing';
import { getInputDefaultValue, syncToLatestReleaseState } from './utils';
import {
type SubWorkflowDetailDTO,
type SubWorkflowNodeDTODataWhenOnInit,
type SubWorkflowNodeFormData,
type Identifier,
type SubWorkflowNodeDTOData,
} from './types';
import { SubWorkflowNodeService } from './services';
const getSubWorkflowService = (context: WorkflowPlaygroundContext) =>
context.entityManager.getService<SubWorkflowNodeService>(
SubWorkflowNodeService,
);
const getSubWorkflowDetail = (
identifier: Identifier,
context: WorkflowPlaygroundContext,
) => {
const subWorkflowService = getSubWorkflowService(context);
return subWorkflowService.getApiDetail(identifier);
};
const syncToSubWorkflowNodeData = ({
node,
context,
workflow,
nodeMeta,
}: {
node: FlowNodeEntity;
context: WorkflowPlaygroundContext;
workflow: SubWorkflowDetailDTO;
nodeMeta: NodeMeta;
identifier: Identifier;
}) => {
const { getNodeTemplateInfoByType } = context;
const nodeDataEntity = node.getData<WorkflowNodeData>(WorkflowNodeData);
// 对于插件来说,初始化表单数据也需要重置 nodeData 数据重新设置
nodeDataEntity.init();
// 这里如果插件被删除后,也需要将 nodeMeta 设置上去,便于展示具体的错误信息
if (!workflow && nodeMeta) {
nodeDataEntity.setNodeData<StandardNodeType.Api>({
...nodeMeta,
});
return;
}
const isProjectWorkflow = Boolean(
(workflow as WorkflowDetailInfoData)?.project_id,
);
/** 来自资源库的流程需要获取最新的版本号 */
const latestVersion = isProjectWorkflow
? undefined
: workflow.latest_flow_version;
const subWorkflowProjectId = isProjectWorkflow
? (workflow as WorkflowDetailInfoData)?.project_id
: undefined;
/**
* 从 workflow 数据中提取出 SubWorkflow 节点的一些 NodeData设置进NodeDataEntity
*/
nodeDataEntity.setNodeData<StandardNodeType.SubWorkflow>({
...nodeMeta,
...omit(workflow, ['inputs', 'outputs', 'project_id']),
// 保留子流程输入定义
inputsDefinition: workflow?.inputs ?? [],
description: workflow.desc || '',
projectId: subWorkflowProjectId,
flow_mode: workflow.flow_mode,
latestVersion,
mainColor:
getNodeTemplateInfoByType(StandardNodeType.SubWorkflow)?.mainColor ?? '',
});
};
/**
* 节点后端数据 -> 前端表单数据
*/
export const transformOnInit = (
value: SubWorkflowNodeDTODataWhenOnInit,
context: NodeFormContext,
) => {
const { node, playgroundContext } = context;
const identifier = {
workflowId: value?.inputs?.workflowId ?? '',
workflowVersion: value?.inputs?.workflowVersion ?? '',
};
const subWorkflowDetail = getSubWorkflowDetail(identifier, playgroundContext);
// 同步 apiDetail 数据到 WorkflowNodeData很多业务逻辑从这里取数据包括报错界面
syncToSubWorkflowNodeData({
node,
context: playgroundContext,
workflow: subWorkflowDetail,
nodeMeta: value.nodeMeta,
identifier,
});
if (!subWorkflowDetail) {
return value as unknown as SubWorkflowNodeFormData;
}
syncToLatestReleaseState(value, subWorkflowDetail);
const inputParameters = value?.inputs?.inputParameters ?? [];
// 由于在提交时,会将没有填值的变量给过滤掉,所以需要在初始化时,将默认值补充进来
// 参见packages/workflow/nodes/src/workflow-json-format.ts:241
const refillInputParamters = getSortedInputParameters(
subWorkflowDetail.inputs,
).map(inputParam => {
const newValue = {
name: inputParam.name,
input: {
type: ValueExpressionType.REF,
},
};
const target = inputParameters.find(item => item.name === inputParam.name);
if (target) {
// 如果存在
return target;
}
// 读取参数的默认值,并回填
newValue.input = getInputDefaultValue(inputParam);
return newValue;
});
value.inputs = {
...value.inputs,
inputParameters: refillInputParamters,
};
value = nodeUtils.dtoToformValue(value, context);
return value;
};
/**
* 前端表单数据 -> 节点后端数据
* @param value
* @returns
*/
export const transformOnSubmit = (
value: SubWorkflowNodeFormData,
context: NodeFormContext,
): SubWorkflowNodeDTOData => {
const { playgroundContext } = context;
const identifier = {
workflowId: value?.inputs?.workflowId ?? '',
workflowVersion: value?.inputs?.workflowVersion ?? '',
};
const workflow = getSubWorkflowDetail(identifier, playgroundContext);
if (!get(workflow, 'outputs')) {
value.outputs = [];
}
value = nodeUtils.formValueToDto(value, context);
return value as unknown as SubWorkflowNodeDTOData;
};

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { get } from 'lodash-es';
import {
ValidateTrigger,
type FormMetaV2,
type FlowNodeEntity,
} from '@flowgram-adapter/free-layout-editor';
import { WorkflowNodeData } from '@coze-workflow/nodes';
import { type StandardNodeType } from '@coze-workflow/base';
import { settingOnErrorValidate } from '@/nodes-v2/materials/setting-on-error-validate';
import { createProvideNodeBatchVariables } from '@/nodes-v2/materials/provide-node-batch-variable';
import { nodeMetaValidate } from '@/nodes-v2/materials/node-meta-validate';
import { createNodeInputNameValidate } from '@/nodes-v2/components/node-input-name/validate';
import { createValueExpressionInputValidate } from '@/node-registries/common/validators';
import {
fireNodeTitleChange,
provideNodeOutputVariablesEffect,
} from '@/node-registries/common/effects';
import { type FormData } from './types';
import { FormRender } from './form';
import { transformOnInit, transformOnSubmit } from './data-transformer';
import { BATCH_INPUT_LIST_PATH, BATCH_MODE_PATH } from './constants';
export const SUB_WORKFLOW_FORM_META: FormMetaV2<FormData> = {
// 节点表单渲染
render: props => <FormRender {...props} />,
// 验证触发时机
validateTrigger: ValidateTrigger.onChange,
// 验证规则
validate: {
nodeMeta: nodeMetaValidate,
// 校验入参
'inputs.inputParameters.*': createValueExpressionInputValidate({
// 是否必填需要根据函数来计算,获取对应字段的必填值
required: ({ name, context }) => {
const { node } = context;
const subWorkflow = (node as FlowNodeEntity)
.getData<WorkflowNodeData>(WorkflowNodeData)
.getNodeData<StandardNodeType.SubWorkflow>();
const fieldName = (name as string).replace(
'inputs.inputParameters.',
'',
);
const inputDef = subWorkflow?.inputsDefinition?.find(
v => v.name === fieldName,
);
return Boolean(inputDef?.required);
},
}),
// 校验批处理入参的名称
'inputs.batch.inputLists.*.name': createNodeInputNameValidate({
getNames: ({ formValues }) =>
(get(formValues, 'batch.inputLists') || []).map(item => item.name),
skipValidate: ({ formValues }) =>
formValues?.inputs?.batchMode === 'single',
}),
// 校验批处理入参的值
'inputs.batch.inputLists.*.input': createValueExpressionInputValidate({
required: true,
skipValidate: ({ formValues }) =>
formValues?.inputs?.batchMode === 'single',
}),
settingOnError: settingOnErrorValidate,
},
// 副作用管理
effect: {
nodeMeta: fireNodeTitleChange,
outputs: provideNodeOutputVariablesEffect,
[BATCH_MODE_PATH]: createProvideNodeBatchVariables(
BATCH_MODE_PATH,
BATCH_INPUT_LIST_PATH,
),
[BATCH_INPUT_LIST_PATH]: createProvideNodeBatchVariables(
BATCH_MODE_PATH,
BATCH_INPUT_LIST_PATH,
),
},
// 节点后端数据 -> 前端表单数据
formatOnInit: transformOnInit,
// 前端表单数据 -> 节点后端数据
formatOnSubmit: transformOnSubmit,
};

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useShallow } from 'zustand/react/shallow';
import {
type FormRenderProps,
useWatch,
} from '@flowgram-adapter/free-layout-editor';
import { I18n } from '@coze-arch/i18n';
import { Batch } from '@/nodes-v2/components/batch/batch';
import { NodeConfigForm } from '@/node-registries/common/components';
import {
OutputsField,
BatchModeField,
SettingOnError,
InputsKVField,
} from '../common/fields';
import { type SubWorkflowNodeFormData } from './types';
import { useSubWorkflowNodeStore } from './hooks/use-subworkflow-node-service';
import { BATCH_MODE_PATH, INPUT_PATH } from './constants';
import { SubWorkflowLink } from './components';
export const FormRender = ({
form,
}: FormRenderProps<SubWorkflowNodeFormData>) => {
const { loading, getSubWorkflow } = useSubWorkflowNodeStore(
useShallow(s => ({
loading: s.loading,
getSubWorkflow: s.getData,
})),
);
const identifier = {
workflowId: form?.initialValues?.inputs?.workflowId ?? '',
workflowVersion: form?.initialValues?.inputs?.workflowVersion ?? '',
};
const workflowDetail = getSubWorkflow(identifier);
const inputsDef = workflowDetail?.inputs ?? [];
const batchMode = useWatch<string>(BATCH_MODE_PATH);
if (loading) {
return null;
}
return (
<NodeConfigForm
extraOperation={
<SubWorkflowLink
workflowDetail={workflowDetail}
identifier={identifier}
/>
}
>
<BatchModeField name={BATCH_MODE_PATH} />
<Batch batchModeName={BATCH_MODE_PATH} name={'inputs.batch'} />
<InputsKVField
name={INPUT_PATH}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inputsDef={inputsDef as any}
hasFeedback={false}
/>
<OutputsField
title={I18n.t('workflow_detail_node_output')}
tooltip={I18n.t('node_http_response_data')}
id="subWorkflow-node-outputs"
name="outputs"
batchMode={batchMode}
topLevelReadonly={true}
customReadonly
/>
<SettingOnError name="settingOnError" batchModePath={BATCH_MODE_PATH} />
<div className="text-[12px] coz-fg-dim hidden">
Powered by Flow Engine V2
</div>
</NodeConfigForm>
);
};

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
useSubWorkflowNodeService,
useSubWorkflowNodeStore,
} from './use-subworkflow-node-service';

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useService } from '@flowgram-adapter/free-layout-editor';
import { SubWorkflowNodeService, type SubWorkflowNodeStore } from '../services';
export const useSubWorkflowNodeService = () =>
useService<SubWorkflowNodeService>(SubWorkflowNodeService);
export const useSubWorkflowNodeStore = <T>(
selector: (s: SubWorkflowNodeStore) => T,
) => {
const subWorkflowService = useSubWorkflowNodeService();
return subWorkflowService.store(selector);
};

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { SUB_WORKFLOW_NODE_REGISTRY } from './node-registry';
export { SubWorkflowContent } from './node-content';

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import {
useCurrentEntity,
useService,
} from '@flowgram-adapter/free-layout-editor';
import { WorkflowNodeData } from '@coze-workflow/nodes';
import {
type StandardNodeType,
useWorkflowNode,
type WorkflowDetailInfoData,
} from '@coze-workflow/base';
import { WorkflowPlaygroundContext } from '@/workflow-playground-context';
import { recreateNodeForm } from '@/services/node-version-service';
import { useDependencyService } from '@/hooks';
import { InputParameters, Outputs } from '../common/components';
import { getIdentifier } from './utils';
import { useSubWorkflowNodeService } from './hooks';
export function SubWorkflowContent() {
const dependencyService = useDependencyService();
const playgroundContext = useService<WorkflowPlaygroundContext>(
WorkflowPlaygroundContext,
);
const { data } = useWorkflowNode();
const node = useCurrentEntity();
const nodeDataEntity = node?.getData<WorkflowNodeData>(WorkflowNodeData);
const nodeData = nodeDataEntity.getNodeData<StandardNodeType.SubWorkflow>();
const identifier = getIdentifier(data?.inputs);
const subWorkflowService = useSubWorkflowNodeService();
useEffect(() => {
if (!identifier) {
return;
}
const disposable = dependencyService.onDependencyChange(async props => {
if (!props?.extra?.nodeIds?.includes(data?.inputs?.workflowId)) {
return;
}
await subWorkflowService.load(identifier, data?.nodeMeta?.title);
const subWorkflowDetail = subWorkflowService.getApiDetail(
identifier,
) as WorkflowDetailInfoData;
// 应用内的工作流不带版本 或 其他没有版本号的情况,直接刷新
if (subWorkflowDetail?.project_id || !subWorkflowDetail?.flow_version) {
recreateNodeForm(node, playgroundContext);
return;
}
nodeDataEntity.init();
nodeDataEntity.setNodeData<StandardNodeType.SubWorkflow>({
...nodeData,
latest_flow_version: subWorkflowDetail?.latest_flow_version,
latest_flow_version_desc: subWorkflowDetail?.latest_flow_version_desc,
latestVersion: subWorkflowDetail?.latest_flow_version,
});
dependencyService.onSubWrokflowVersionChangeEmitter.fire({
subWorkflowId: data?.inputs?.workflowId,
});
});
return () => {
disposable?.dispose?.();
};
}, [identifier, data?.inputs?.workflowId]);
return (
<>
<InputParameters />
<Outputs />
</>
);
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep } from 'lodash-es';
import {
type FlowNodeEntity,
FlowNodeFormData,
type FormModelV2,
PlaygroundContext,
} from '@flowgram-adapter/free-layout-editor';
import {
DEFAULT_BATCH_PATH,
DEFAULT_NODE_META_PATH,
DEFAULT_OUTPUTS_PATH,
WorkflowNodeData,
} from '@coze-workflow/nodes';
import {
CONVERSATION_NAME,
StandardNodeType,
ValueExpression,
WorkflowMode,
type WorkflowNodeRegistry,
} from '@coze-workflow/base';
import { type WorkflowPlaygroundContext } from '@/workflow-playground-context';
import { type NodeTestMeta } from '@/test-run-kit';
import { getIdentifier } from './utils';
import type { SubWorkflowNodeDTOData } from './types';
import { SubWorkflowNodeService } from './services';
import { test } from './node-test';
import { SUB_WORKFLOW_FORM_META } from './form-meta';
import { INPUT_PATH } from './constants';
import { createSubWorkflowLink } from './components';
const getSubWorkflowService = (context: WorkflowPlaygroundContext) =>
context.entityManager.getService<SubWorkflowNodeService>(
SubWorkflowNodeService,
);
export const SUB_WORKFLOW_NODE_REGISTRY: WorkflowNodeRegistry<NodeTestMeta> = {
type: StandardNodeType.SubWorkflow,
meta: {
nodeDTOType: StandardNodeType.SubWorkflow,
size: { width: 360, height: 130.7 },
nodeMetaPath: DEFAULT_NODE_META_PATH,
outputsPath: DEFAULT_OUTPUTS_PATH,
batchPath: DEFAULT_BATCH_PATH,
inputParametersPath: INPUT_PATH, // 入参路径,试运行等功能依赖该路径提取参数
test,
helpLink: '/open/docs/guides/workflow_node',
},
formMeta: SUB_WORKFLOW_FORM_META,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onInit: async (nodeJson: any, context: WorkflowPlaygroundContext) => {
if (!nodeJson) {
return;
}
const { inputs, nodeMeta } = nodeJson.data || nodeJson;
const subWorkflowService = getSubWorkflowService(context);
const identifier = getIdentifier(inputs);
await subWorkflowService.load(identifier, nodeMeta?.title);
},
checkError: (nodeJson, context: WorkflowPlaygroundContext) => {
if (!nodeJson) {
return undefined;
}
const { inputs } = nodeJson.data || nodeJson;
const subWorkflowService = getSubWorkflowService(context);
const identifier = getIdentifier(inputs);
return subWorkflowService.getApiError(identifier);
},
onDispose: (nodeJson, context: WorkflowPlaygroundContext) => {
if (!nodeJson) {
return;
}
const { inputs } = nodeJson.data || nodeJson;
const subWorkflowService = getSubWorkflowService(context);
const identifier = getIdentifier(inputs);
subWorkflowService.clearApiError(identifier);
},
getHeaderExtraOperation: (
formValues: SubWorkflowNodeDTOData,
node: FlowNodeEntity,
) => {
const identifier = getIdentifier(formValues?.inputs ?? []);
const subWorkflowService = node.getService<SubWorkflowNodeService>(
SubWorkflowNodeService,
);
const subWorkflow = subWorkflowService.getApiDetail(identifier);
return createSubWorkflowLink(subWorkflow, identifier);
},
onCreate(node: FlowNodeEntity, _json: unknown) {
const formModel = node
.getData(FlowNodeFormData)
.getFormModel<FormModelV2>();
const playgroundContext =
node.getService<PlaygroundContext>(PlaygroundContext);
const { variableService, nodesService } = playgroundContext;
const startNode = nodesService.getStartNode();
const DELAY_TIME = 1000;
setTimeout(() => {
if (!node) {
return;
}
const nodeData = node.getData<WorkflowNodeData>(WorkflowNodeData);
const subWorkflowDetail =
nodeData?.getNodeData<StandardNodeType.SubWorkflow>();
const nodeIsChatflow =
subWorkflowDetail?.flow_mode === WorkflowMode.ChatFlow;
const startConversationNameVar =
variableService.getWorkflowVariableByKeyPath(
[startNode.id, CONVERSATION_NAME],
{
node,
checkScope: true,
},
);
// 如果能够找到开始节点的 CONVERSATION_NAME 参数
if (startConversationNameVar && nodeIsChatflow && formModel) {
const inputParameters = formModel.getValueIn('inputs.inputParameters');
if (inputParameters) {
const originValue = cloneDeep(inputParameters);
if (
originValue[CONVERSATION_NAME] &&
ValueExpression.isEmpty(originValue[CONVERSATION_NAME])
) {
// 如果 CONVERSATION_NAME 为空,才回填
originValue[CONVERSATION_NAME] = {
type: 'ref',
content: {
keyPath: [startNode.id, CONVERSATION_NAME],
},
};
formModel.setValueIn('inputs.inputParameters', originValue);
}
}
}
// 这个延时时间需要比较长,暂时先设置为 1s
}, DELAY_TIME);
},
};

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
import { WorkflowNodeData } from '@coze-workflow/nodes';
import {
CONVERSATION_NAME,
type StandardNodeType,
ValueExpression,
} from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { WorkflowMode } from '@coze-arch/bot-api/workflow_api';
import {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
getRelatedInfo,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
async generateRelatedContext(node, context) {
const { isInProject, workflowId, spaceId } = context;
const nodeData = node.getData<WorkflowNodeData>(WorkflowNodeData);
const detail = nodeData.getNodeData<StandardNodeType.SubWorkflow>();
const isChatflowNode = detail?.flow_mode === WorkflowMode.ChatFlow;
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
const inputData = formData?.inputs?.inputParameters || {};
const conversationName = inputData?.[CONVERSATION_NAME];
/** 具有引用的会话名称 */
const isConversationNameRef =
conversationName && ValueExpression.isRef(conversationName);
if (isInProject) {
return generateEnvToRelatedContextProperties({
isNeedBot: false,
isNeedConversation: isChatflowNode && isConversationNameRef,
});
}
const related = await getRelatedInfo({ workflowId, spaceId });
if (isChatflowNode) {
// 如果是 chatflow 则必需要应用选择器和会话选择器
related.isNeedBot = true;
related.isNeedConversation = true;
// chatflow 不能选择 bot 作为关联环境
related.disableBot = true;
related.disableBotTooltip = I18n.t('wf_chatflow_141');
}
return generateEnvToRelatedContextProperties(related);
},
generateFormBatchProperties(node) {
const batchModePath = '/inputs/batchMode';
const batchMode = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath(batchModePath);
const path = node.getNodeMeta()?.batchPath;
if (batchMode !== 'batch' || !path) {
return {};
}
const batchData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath(path);
return generateParametersToProperties(batchData, { node });
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
const inputDefs = formData?.inputs?.inputDefs;
if (!inputDefs || !Array.isArray(inputDefs)) {
return {};
}
const nodeData = node.getData<WorkflowNodeData>(WorkflowNodeData);
const detail = nodeData.getNodeData<StandardNodeType.SubWorkflow>();
const isChatflowNode = detail?.flow_mode === WorkflowMode.ChatFlow;
const inputData = formData?.inputs?.inputParameters || {};
const inputParameters = inputDefs
// chatflow 中 CONVERSATION_NAME 参数不需要提取,需要有专门的会话选择组件
.filter(i => (isChatflowNode ? i.name !== CONVERSATION_NAME : true))
.map(i => ({
input: inputData[i.name],
name: i.name,
required: i.required,
}));
return generateParametersToProperties(inputParameters, { node });
},
};

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { SubWorkflowNodeService } from './subworkflow-node-service';
export {
type SubWorkflowNodeStore,
createStore,
} from './subworkflow-node-store';

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { inject, injectable } from 'inversify';
import { workflowApi } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { WorkflowGlobalStateEntity } from '@/entities';
import { type SubWorkflowDetailDTO, type Identifier } from '../types';
import { createStore } from './subworkflow-node-store';
@injectable()
export class SubWorkflowNodeService {
@inject(WorkflowGlobalStateEntity) globalState: WorkflowGlobalStateEntity;
store = createStore();
set loading(v: boolean) {
this.store.setState({
loading: v,
});
}
get state() {
return this.store.getState();
}
getApiDetail(identifier: Identifier) {
return this.state.getData(identifier);
}
getApiError(identifier: Identifier) {
return this.state.getError(identifier);
}
clearApiError(identifier: Identifier) {
this.state.clearError(identifier);
}
async fetchData(
identifier: Identifier,
): Promise<SubWorkflowDetailDTO | undefined> {
const { spaceId } = this.globalState;
const { workflowId, workflowVersion } = identifier;
const resp = await workflowApi.GetWorkflowDetailInfo(
{
space_id: spaceId,
workflow_filter_list: [
{
workflow_id: workflowId,
workflow_version: workflowVersion ? workflowVersion : undefined,
},
],
},
{
__disableErrorToast: true,
},
);
const workflowInfo = resp?.data?.[0] as SubWorkflowDetailDTO;
return workflowInfo
? {
...workflowInfo,
// 未发布的工作流可能存在没有输入 name 的参数,需要过滤掉
inputs: workflowInfo.inputs?.filter(i => i.name),
}
: undefined;
}
async load(identifier: Identifier, workflowTitle: string) {
let subWorkflowDetail: SubWorkflowDetailDTO | undefined = undefined;
let errorMessage = '';
try {
this.loading = true;
const response = await this.fetchData(identifier);
if (response) {
subWorkflowDetail = response;
}
} catch (error) {
errorMessage = error.message;
} finally {
this.loading = false;
}
if (errorMessage) {
this.state.setError(identifier, errorMessage);
}
if (!subWorkflowDetail) {
const notFoundMessage = I18n.t('workflow_node_lose_efficacy_wf', {
name: workflowTitle,
});
this.state.setError(identifier, errorMessage || notFoundMessage);
return;
} else {
this.state.setData(identifier, {
...subWorkflowDetail,
});
}
return subWorkflowDetail;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createWithEqualityFn } from 'zustand/traditional';
import { shallow } from 'zustand/shallow';
import { type SubWorkflowDetailDTO, type Identifier } from '../types';
interface SubWorkflowNodeServiceState {
loading: boolean;
/**
* 子流程节点数据key 为子流程具体工具的唯一标识value 为子流程节点数据
*/
data: Record<string, SubWorkflowDetailDTO>;
/**
* 子流程节点数据加载错误信息key 为子流程具体工具的唯一标识value 为错误信息
*/
error: Record<string, string | undefined>;
}
interface SubWorkflowNodeServiceAction {
getData: (identifier: Identifier) => SubWorkflowDetailDTO;
setData: (identifier: Identifier, value: SubWorkflowDetailDTO) => void;
getError: (identifier: Identifier) => string | undefined;
setError: (identifier: Identifier, value: string | undefined) => void;
clearError: (identifier: Identifier) => void;
}
export function getCacheKey(identifier: Identifier): string {
return `${identifier.workflowId}_${identifier.workflowVersion}`;
}
export type SubWorkflowNodeStore = SubWorkflowNodeServiceState &
SubWorkflowNodeServiceAction;
export const createStore = () =>
createWithEqualityFn<SubWorkflowNodeStore>(
(set, get) => ({
loading: false,
data: {},
error: {},
getData(identifier) {
const key = getCacheKey(identifier);
return get().data[key];
},
setData(identifier, value) {
const key = getCacheKey(identifier);
set({
data: {
...get().data,
[key]: value,
},
});
},
getError(identifier) {
const key = getCacheKey(identifier);
return get().error[key];
},
setError(identifier, value) {
const key = getCacheKey(identifier);
set({
error: {
...get().error,
[key]: value,
},
});
},
clearError(identifier) {
const key = getCacheKey(identifier);
set({
error: {
...get().error,
[key]: undefined,
},
});
},
}),
shallow,
);

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type InputValueVO,
type ViewVariableMeta,
type VariableMetaDTO,
type InputValueDTO,
type BatchVO,
type BatchDTO,
type ValueExpression,
type ReleasedWorkflow,
type WorkflowDetailInfoData,
type DTODefine,
} from '@coze-workflow/base';
import type { NodeMeta, SettingOnErrorDTO, SettingOnErrorVO } from '@/typing';
export interface FormData {
inputs: { inputParameters: InputValueVO[] };
}
interface BaseInputsOutputsType {
inputs: DTODefine.InputVariableDTO[]; // name, type, schema, required, description
outputs: VariableMetaDTO[];
}
export interface Identifier {
workflowId: string;
workflowVersion: string;
}
export type SubWorkflowDetailDTO =
| (ReleasedWorkflow & BaseInputsOutputsType)
| (WorkflowDetailInfoData & BaseInputsOutputsType);
// 输入参数对应的类型,不过需要注意的是,自定义扩展的字段,值不一定是 ValueExpression 类型
export type InputParametersMap = Record<string, ValueExpression>;
/** 子流程节点前端表单结构 */
export interface SubWorkflowNodeFormData {
nodeMeta: NodeMeta;
inputs: {
inputDefs?: SubWorkflowDetailDTO['inputs'][]; // name, required, type, defaultValue, schema...
inputParameters?: InputParametersMap;
batch?: BatchVO;
batchMode?: string;
settingOnError?: SettingOnErrorDTO;
workflowId?: string;
workflowVersion?: string;
};
outputs: ViewVariableMeta[];
settingOnError?: SettingOnErrorVO;
}
/**
* 子流程节点数据部分结构定义
*/
export interface SubWorkflowNodeDTOData<
InputType = InputValueDTO,
OutputType = VariableMetaDTO,
> {
nodeMeta: NodeMeta;
inputs: {
// 一个例子:
// {
// "input": {},
// "name": "obj",
// "required": false,
// "schema": [
// {
// "name": "arr_str",
// "required": false,
// "schema": {
// "type": "string"
// },
// "type": "list"
// },
// {
// "name": "int",
// "required": false,
// "type": "integer"
// }
// ],
// "type": "object"
// }
inputDefs?: SubWorkflowDetailDTO['inputs'][];
inputParameters?: InputType[];
batch?: BatchDTO & { batchEnable: boolean };
batchMode?: string;
settingOnError?: SettingOnErrorDTO;
// 一些额外附加参数
spaceId?: string;
type?: number;
workflowId?: string;
workflowVersion?: string;
};
outputs: OutputType[];
}
/**
* 子流程节点数据部分结构定义,经过 workflow-json-format 转换后的数据结构
* - outputs 从 VariableMetaDTO 转换为 ViewVariableMeta
* - inputs.inputParameters 从 BlockInput 转换为 InputValueVO
*/
export type SubWorkflowNodeDTODataWhenOnInit = SubWorkflowNodeDTOData<
InputValueVO,
ViewVariableMeta
>;

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { SubWorkflowNodeDTOData } from '../types';
export const getIdentifier = (inputs: SubWorkflowNodeDTOData['inputs']) => ({
workflowId: inputs?.workflowId ?? '',
workflowVersion: inputs?.workflowVersion ?? '',
});

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { variableUtils } from '@coze-workflow/variable';
import {
type DTODefine,
ValueExpressionType,
VariableTypeDTO,
type ValueExpression,
type LiteralExpression,
} from '@coze-workflow/base';
const parseUploadURLFileName = (url: string) => {
try {
return new URL(url)?.searchParams?.get('x-wf-file_name') ?? 'unknown';
} catch (e) {
console.error(e);
return '';
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getFileDefaultValue = (input: any): LiteralExpression => {
const { defaultValue, assistType, type } = input;
return {
type: ValueExpressionType.LITERAL,
content: defaultValue,
rawMeta: {
type: variableUtils.DTOTypeToViewType(type as VariableTypeDTO, {
assistType,
}),
fileName: parseUploadURLFileName(defaultValue),
},
};
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getFileListDefaultValue = (input: any): LiteralExpression => {
const { defaultValue, type, schema } = input;
const fileList = JSON.parse(defaultValue as string) as string[];
return {
type: ValueExpressionType.LITERAL,
content: fileList,
rawMeta: {
type: variableUtils.DTOTypeToViewType(type as VariableTypeDTO, {
arrayItemType: schema?.type,
assistType: schema?.assistType,
}),
fileName: fileList.map(parseUploadURLFileName),
},
};
};
/**
* 获取子 workflow 节点入参的默认值,定义在子 workflow start 节点参数的 defaultValue
* @param input 子 workflow 参数定义
* @returns
*/
export const getInputDefaultValue = (
input: DTODefine.InputVariableDTO,
): ValueExpression => {
const { defaultValue } = input;
if (!defaultValue) {
return {
type: ValueExpressionType.REF,
};
}
// Array<File>
if (input.type === VariableTypeDTO.list && input.schema?.assistType) {
return getFileListDefaultValue(input);
// File
} else if (input.type === VariableTypeDTO.string && input.assistType) {
return getFileDefaultValue(input);
} else if (
input.type === VariableTypeDTO.list ||
input.type === VariableTypeDTO.object
) {
return {
type: ValueExpressionType.REF,
};
} else {
return {
type: ValueExpressionType.LITERAL,
content: variableUtils.getLiteralValueWithType(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input.type as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input.defaultValue as any,
) as string | number | boolean,
};
}
};

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { syncToLatestReleaseState } from './sync-to-latest-release';
export { getInputDefaultValue } from './get-input-default-value';
export { getIdentifier } from './get-identifier';

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { nanoid } from 'nanoid';
import { get } from 'lodash-es';
import { ViewVariableType, variableUtils } from '@coze-workflow/variable';
function syncNodeMeta(value, workflow) {
const { desc } = workflow;
if (!value.nodeMeta.description) {
value.nodeMeta.description = desc;
}
}
function syncInputs(value, workflow) {
const inputNames = new Set(workflow?.inputs?.map(input => input.name));
if (value?.inputs?.inputParameters) {
value.inputs.inputParameters = value.inputs.inputParameters.filter(input =>
inputNames.has(input.name),
);
}
}
function syncHiddenFields(value, workflow) {
const { workflow_id, end_type, space_id, inputs: inputDefs } = workflow;
value.inputs.workflowId = workflow_id;
value.inputs.spaceId = space_id;
value.inputs.inputDefs = inputDefs;
value.inputs.type = end_type;
}
function syncOutputValueKeys(currentOutputsValue, prevOutputsValue) {
// 同步同名output的key 防止引用失效
currentOutputsValue.map(item => {
const sameNameItem = prevOutputsValue.find(
prevItem => prevItem.name === item.name,
);
if (sameNameItem) {
item.key = sameNameItem.key;
if (item.children && sameNameItem.children) {
syncOutputValueKeys(item.children, sameNameItem.children);
}
}
return item;
});
}
function syncOutputs(value, workflow) {
const workflowOutputs = get(workflow, 'outputs');
const workflowOutputsExisted = !!workflowOutputs;
const valueOutputsExisted = !!get(value, 'outputs');
const isBatchMode = get(value, 'inputs.batch.batchEnable');
if (!workflowOutputsExisted || workflowOutputs.length === 0) {
/**
* 若无输出则赋值空数组
* ps: 不可赋值为 undefined新表单引擎不会触发副作用导致变量引擎不更新
*/
value.outputs = [];
return;
}
if (!valueOutputsExisted) {
if (isBatchMode) {
value.outputs = [
{
name: 'outputList',
type: ViewVariableType.ArrayObject,
key: nanoid(),
children: [],
},
];
} else {
value.outputs = [];
}
}
const { outputs } = workflow;
const outputsValue = outputs.map(variableUtils.dtoMetaToViewMeta);
// 如果开启了异常处理,需要把 errorbody 拼回去
const errorIgnoreIsOpen = get(value, 'inputs.settingOnError.switch');
if (errorIgnoreIsOpen) {
let errorBody = get(value, 'outputs')?.find(
item => item.name === 'errorBody',
);
if (isBatchMode) {
errorBody = get(value, 'outputs[0].children')?.find(
item => item.name === 'errorBody',
);
}
if (errorBody) {
outputsValue.push(errorBody);
}
}
const prevOutputsValue = isBatchMode
? value.outputs[0].children
: value.outputs;
if (prevOutputsValue) {
syncOutputValueKeys(outputsValue, prevOutputsValue);
}
if (isBatchMode) {
value.outputs[0].children = outputsValue;
} else {
value.outputs = outputsValue;
}
}
export function syncToLatestReleaseState(value, workflow) {
syncNodeMeta(value, workflow);
syncInputs(value, workflow);
syncHiddenFields(value, workflow);
syncOutputs(value, workflow);
}