feat: add json-stringify node (#215)

Co-authored-by: zengxiaohui <zengxiaohui@bytedance.com>
This commit is contained in:
ski 2025-07-29 14:11:49 +08:00 committed by GitHub
parent 183d0324bb
commit 0965d69acc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 249 additions and 5 deletions

View File

@ -56,7 +56,7 @@ export const getEnabledNodeTypes = (_params: {
[StandardNodeType.DatabaseDelete]: true,
[StandardNodeType.DatabaseCreate]: true,
// [StandardNodeType.JsonParser]: true,
// [StandardNodeType.JsonStringify]: true,
[StandardNodeType.JsonStringify]: true,
// [StandardNodeType.UpdateConversation]: true,
// [StandardNodeType.DeleteConversation]: true,
// [StandardNodeType.QueryConversationList]: true,

View File

@ -27,6 +27,7 @@ import { PluginContent } from '@/node-registries/plugin';
import { OutputContent } from '@/node-registries/output';
import { LtmContent } from '@/node-registries/ltm';
import { LoopContent } from '@/node-registries/loop';
import { JsonStringifyContent } from '@/node-registries/json-stringify';
import { IntentContent } from '@/node-registries/intent';
import { InputContent } from '@/node-registries/input';
import { ImageCanvasContent } from '@/node-registries/image-canvas';
@ -89,6 +90,7 @@ const ContentMap = {
[StandardNodeType.TriggerRead]: TriggerReadContent,
[StandardNodeType.Api]: PluginContent,
[StandardNodeType.Variable]: VariableContent,
[StandardNodeType.JsonStringify]: JsonStringifyContent,
// cli 脚本插入标识registry请勿修改/删除此行注释
};

View File

@ -47,4 +47,5 @@ export { IF_NODE_REGISTRY } from './if';
export { PLUGIN_NODE_REGISTRY } from './plugin';
export { SUB_WORKFLOW_NODE_REGISTRY } from './sub-workflow';
export { VARIABLE_NODE_REGISTRY } from './variable';
export { JSON_STRINGIFY_NODE_REGISTRY } from './json-stringify';
// cli 脚本插入标识registry请勿修改/删除此行注释

View File

@ -0,0 +1,68 @@
import {
FieldArray,
type FieldArrayRenderProps,
} from '@flowgram-adapter/free-layout-editor';
import type { ViewVariableType, InputValueVO } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { ValueExpressionInputField } from '@/node-registries/common/fields';
import { FieldArrayItem, FieldRows, Section, type FieldProps } from '@/form';
interface InputsFieldProps extends FieldProps<InputValueVO[]> {
title?: string;
paramsTitle?: string;
expressionTitle?: string;
disabledTypes?: ViewVariableType[];
onAppend?: () => InputValueVO;
inputPlaceholder?: string;
literalDisabled?: boolean;
showEmptyText?: boolean;
nthCannotDeleted?: number;
}
export const InputsField = ({
name,
defaultValue,
title,
tooltip,
disabledTypes,
inputPlaceholder,
literalDisabled,
showEmptyText = true,
}: InputsFieldProps) => {
const readonly = useReadonly();
return (
<FieldArray<InputValueVO> name={name} defaultValue={defaultValue}>
{({ field }: FieldArrayRenderProps<InputValueVO>) => {
const { value = [] } = field;
const length = value?.length ?? 0;
const isEmpty = !length;
return (
<Section
title={title}
tooltip={tooltip}
isEmpty={showEmptyText && isEmpty}
emptyText={I18n.t('workflow_inputs_empty')}
>
<FieldRows>
{field.map((item, index) => (
<FieldArrayItem key={item.key} disableRemove hiddenRemove>
<div style={{ flex: 3 }}>
<ValueExpressionInputField
name={`${name}.${index}.input`}
disabledTypes={disabledTypes}
readonly={readonly}
inputPlaceholder={inputPlaceholder}
literalDisabled={literalDisabled}
/>
</div>
</FieldArrayItem>
))}
</FieldRows>
</Section>
);
}}
</FieldArray>
);
};

View File

@ -0,0 +1,16 @@
import { nanoid } from 'nanoid';
import { ViewVariableType } from '@coze-workflow/variable';
// 入参路径,试运行等功能依赖该路径提取参数
export const INPUT_PATH = 'inputs.inputParameters';
// 定义固定出参
export const OUTPUTS = [
{
key: nanoid(),
name: 'output',
type: ViewVariableType.String,
},
];
export const DEFAULT_INPUTS = [{ name: 'input' }];

View File

@ -0,0 +1,20 @@
import { type NodeDataDTO } from '@coze-workflow/base';
import { type FormData } from './types';
import { OUTPUTS } from './constants';
/**
* ->
*/
export const transformOnInit = (value: NodeDataDTO) => ({
...(value ?? {}),
outputs: value?.outputs ?? OUTPUTS,
});
/**
* ->
* @param value
* @returns
*/
export const transformOnSubmit = (value: FormData): NodeDataDTO =>
value as unknown as NodeDataDTO;

View File

@ -0,0 +1,42 @@
import {
ValidateTrigger,
type FormMetaV2,
} from '@flowgram-adapter/free-layout-editor';
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';
export const JSON_STRINGIFY_FORM_META: FormMetaV2<FormData> = {
// 节点表单渲染
render: () => <FormRender />,
// 验证触发时机
validateTrigger: ValidateTrigger.onChange,
// 验证规则
validate: {
// 必填
'inputs.inputParameters.0.input': createValueExpressionInputValidate({
required: true,
}),
},
// 副作用管理
effect: {
nodeMeta: fireNodeTitleChange,
outputs: provideNodeOutputVariablesEffect,
},
// 节点后端数据 -> 前端表单数据
formatOnInit: transformOnInit,
// 前端表单数据 -> 节点后端数据
formatOnSubmit: transformOnSubmit,
};

View File

@ -0,0 +1,30 @@
import { I18n } from '@coze-arch/i18n';
import { NodeConfigForm } from '@/node-registries/common/components';
import { OutputsField } from '../common/fields';
import { INPUT_PATH } from './constants';
import { InputsField } from './components/inputs';
export const FormRender = () => (
<NodeConfigForm>
<InputsField
name={INPUT_PATH}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defaultValue={[{ name: 'input' } as any]}
title={I18n.t('node_http_request_params')}
tooltip={I18n.t('workflow_250429_03')}
required={false}
layout="horizontal"
/>
<OutputsField
title={I18n.t('workflow_detail_node_output')}
tooltip={I18n.t('node_http_response_data')}
id="jsonStringify-node-outputs"
name="outputs"
topLevelReadonly={true}
customReadonly
/>
</NodeConfigForm>
);

View File

@ -0,0 +1,2 @@
export { JSON_STRINGIFY_NODE_REGISTRY } from './node-registry';
export { JsonStringifyContent } from './node-content';

View File

@ -0,0 +1,10 @@
import { InputParameters, Outputs } from '../common/components';
export function JsonStringifyContent() {
return (
<>
<InputParameters />
<Outputs />
</>
);
}

View File

@ -0,0 +1,26 @@
import {
DEFAULT_NODE_META_PATH,
DEFAULT_OUTPUTS_PATH,
} from '@coze-workflow/nodes';
import {
StandardNodeType,
type WorkflowNodeRegistry,
} from '@coze-workflow/base';
import { test, type NodeTestMeta } from './node-test';
import { JSON_STRINGIFY_FORM_META } from './form-meta';
import { INPUT_PATH } from './constants';
export const JSON_STRINGIFY_NODE_REGISTRY: WorkflowNodeRegistry<NodeTestMeta> =
{
type: StandardNodeType.JsonStringify,
meta: {
nodeDTOType: StandardNodeType.JsonStringify,
size: { width: 360, height: 130.7 },
nodeMetaPath: DEFAULT_NODE_META_PATH,
outputsPath: DEFAULT_OUTPUTS_PATH,
inputParametersPath: INPUT_PATH,
test,
},
formMeta: JSON_STRINGIFY_FORM_META,
};

View File

@ -0,0 +1,20 @@
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
import {
type NodeTestMeta,
generateParametersToProperties,
} from '@/test-run-kit';
export const test: NodeTestMeta = {
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
const parameters = formData?.inputs?.inputParameters;
return generateParametersToProperties(parameters, {
node,
});
},
};
export type { NodeTestMeta };

View File

@ -0,0 +1,5 @@
import { type InputValueVO } from '@coze-workflow/base';
export interface FormData {
inputs: { inputParameters: InputValueVO[] };
}

View File

@ -51,6 +51,7 @@ import {
PLUGIN_NODE_REGISTRY,
SUB_WORKFLOW_NODE_REGISTRY,
VARIABLE_NODE_REGISTRY,
JSON_STRINGIFY_NODE_REGISTRY,
// cli 脚本插入标识import请勿修改/删除此行注释
} from '@/node-registries';
@ -69,6 +70,7 @@ import {
export const NODES_V2 = [
// cli 脚本插入标识registry请勿修改/删除此行注释
JSON_STRINGIFY_NODE_REGISTRY,
IF_NODE_REGISTRY,
INTENT_NODE_REGISTRY,
SUB_WORKFLOW_NODE_REGISTRY,