/* * 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, isEmpty, pick, set, isFunction } from 'lodash-es'; import { injectable } from 'inversify'; import { PlaygroundContext, lazyInject, } from '@flowgram-adapter/free-layout-editor'; import { type WorkflowNodeEntity, type WorkflowDocument, type WorkflowJSON, type WorkflowJSONFormatContribution, type WorkflowNodeJSON, } from '@flowgram-adapter/free-layout-editor'; import { WorkflowBatchService, WorkflowVariableService, variableUtils, type ViewVariableMeta, } from '@coze-workflow/variable'; import { StandardNodeType, type BatchDTOInputList, type InputValueDTO, type InputValueVO, type ValueExpressionDTO, type VariableMetaDTO, } from '@coze-workflow/base'; import { WorkflowNodeVariablesMeta, type WorkflowNodeRegistry, } from './typings'; /** * 全局转换表单数据,这里主要处理变量生成逻辑 */ @injectable() export class WorkflowJSONFormat implements WorkflowJSONFormatContribution { @lazyInject(WorkflowVariableService) declare variableService: WorkflowVariableService; @lazyInject(PlaygroundContext) declare playgroundContext: PlaygroundContext; @lazyInject(WorkflowBatchService) batchService: WorkflowBatchService; protected getVariablesMeta( nodeType: string | number, document: WorkflowDocument, ): WorkflowNodeVariablesMeta { const { variablesMeta = WorkflowNodeVariablesMeta.DEFAULT } = document.getNodeRegister(nodeType); return variablesMeta || WorkflowNodeVariablesMeta.DEFAULT; } /** * 转换节点变量 * @param json * @protected */ protected formatOutputVariables( json: WorkflowNodeJSON, doc: WorkflowDocument, ): void { const variablesMeta = this.getVariablesMeta(json.type, doc); if (json.data) { variablesMeta.outputsPathList!.forEach(outputsPath => { const variableMetas = get(json.data, outputsPath) as VariableMetaDTO[]; if (variableMetas && Array.isArray(variableMetas)) { variableMetas.forEach((meta: VariableMetaDTO, index) => { if (!meta.type) { return; } const newData = variableUtils.dtoMetaToViewMeta(meta); set(variableMetas, index, newData); }); } }); } } /** * 兼容仅有单个batch变量的历史数据 */ protected transformBatchVariable( json: WorkflowNodeJSON, doc: WorkflowDocument, ): void { if (!json.data) { return; } const input: ValueExpressionDTO = get(json.data, 'inputs.batch.inputList'); if (!input || isEmpty(input)) { // 无需兼容 return; } const inputList: BatchDTOInputList = { name: 'item', // 按照PRD,写死即可 input, }; // 在json中填充新数据 json.data.inputs.batch.inputLists = [inputList]; // 删掉旧数据 delete json.data.inputs.batch.inputList; } /** * 修复变量引用逻辑 * @param json * @param doc * @protected */ protected formatInputVariables( json: WorkflowNodeJSON, doc: WorkflowDocument, ): void { const variablesMeta = this.getVariablesMeta(json.type, doc); if (json.data) { variablesMeta.inputsPathList!.forEach(inputsPath => { const inputValues: InputValueDTO[] = get( json.data, inputsPath, ) as InputValueDTO[]; if (inputValues && Array.isArray(inputValues)) { inputValues.map((inputValue, index) => { set( inputValues, index, variableUtils.inputValueToVO(inputValue, this.variableService), ); }); } }); } } /** * 处理节点元数据 & 静态文案 * @param json * @param doc * @protected */ protected formatNodeMeta( json: WorkflowNodeJSON, doc: WorkflowDocument, ): void { // API 插件节点暂时不需要处理文案 if ( json?.type && [StandardNodeType.Api, StandardNodeType.SubWorkflow].includes( json.type as StandardNodeType, ) ) { return; } const nodeMeta = get(json, 'data.nodeMeta'); // 拉取的最新配置数据 const meta = this.playgroundContext.getNodeTemplateInfoByType(json.type); if ( nodeMeta && typeof nodeMeta === 'object' && meta && typeof meta === 'object' ) { // 根据后端配置确定,不由用户控制节点元数据 const staticMeta = pick(meta, ['icon', 'subTitle']); set(json, 'data.nodeMeta', { ...nodeMeta, ...staticMeta, }); } } /** * 初始化节点 * @param json * @param doc * @param isClone */ formatNodeOnInit( json: WorkflowNodeJSON, doc: WorkflowDocument, isClone?: boolean, ): WorkflowNodeJSON { // 非 clone 在 formatOnInit 已经触发 if (!isClone) { return json; } this.formatOutputVariables(json, doc); this.formatInputVariables(json, doc); return json; } /** * 提交节点 * @param json * @param doc */ formatNodeOnSubmit( json: WorkflowNodeJSON, doc: WorkflowDocument, node: WorkflowNodeEntity, ): WorkflowNodeJSON { const { nodeDTOType } = doc.getNodeRegister( json.type, ).meta; const variablesMeta = this.getVariablesMeta(json.type, doc); if (json.data) { // 转换 output variablesMeta.outputsPathList!.forEach(outputsPath => { const variableMetas = get(json.data, outputsPath) as ViewVariableMeta[]; if (variableMetas && Array.isArray(variableMetas)) { variableMetas.forEach((meta: ViewVariableMeta, index) => { if (!meta.type) { return; } const newData = variableUtils.viewMetaToDTOMeta(meta); set(variableMetas, index, newData); }); } }); // 转换 input variablesMeta.inputsPathList!.forEach(inputsPath => { const inputValues: InputValueVO[] = get( json.data, inputsPath, ) as InputValueVO[]; if (inputValues && Array.isArray(inputValues)) { inputValues.map((inputValue, index) => { if (!inputValue) { return; } set( inputValues, index, variableUtils.inputValueToDTO(inputValue, this.variableService, { node, }), ); }); // 过滤掉空值 set(json.data, inputsPath, inputValues.filter(Boolean)); } }); } json.type = String(nodeDTOType || json.type); return json; } /** * 初始化时候转换变量数据 * @param json * @param document */ formatOnInit(json: WorkflowJSON, document: WorkflowDocument): WorkflowJSON { // Step0: batch 兼容处理 json.nodes.forEach(node => this.transformBatchVariable(node, document)); // Step1: 创建输出变量 json.nodes.forEach(node => this.formatOutputVariables(node, document)); // Step2: 处理input中值转换 json.nodes.forEach(node => this.formatInputVariables(node, document)); // Step3: 处理节点 description & subTitle 等静态文案数据 (不从落库数据中取,而是根据最新节点数据中获取) json.nodes.forEach(node => this.formatNodeMeta(node, document)); return json; } /** * 提交时候转换变量数据 * @param json */ formatOnSubmit(json: WorkflowJSON, document: WorkflowDocument): WorkflowJSON { json.nodes = (json.nodes || []).map(node => { const registry = document.getNodeRegister( node.type, ); if (isFunction(registry?.beforeNodeSubmit)) { return registry.beforeNodeSubmit(node); } return node; }); console.log('------------------ save ----------------------', json); return json; } }