feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -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';
|
||||
@@ -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} />
|
||||
);
|
||||
@@ -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') },
|
||||
];
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
@@ -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 });
|
||||
},
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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
|
||||
>;
|
||||
@@ -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 ?? '',
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user