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,47 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('wf_chatflow_24'),
name: CONVERSATION_NAME,
required: true,
type: 'string',
},
};
export const DEFAULT_CONVERSATION_VALUE = [
{
name: CONVERSATION_NAME,
input: {
type: ValueExpressionType.REF,
},
},
];
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
];

View File

@@ -0,0 +1,67 @@
/*
* 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 {
ValidateTrigger,
type FormMetaV2,
} from '@flowgram-adapter/free-layout-editor';
import { type InputValueVO } from '@coze-workflow/base';
import { fireNodeTitleChange } from '@/nodes-v2/materials/fire-node-title-change';
import { createValueExpressionInputValidate } from '@/nodes-v2/materials/create-value-expression-input-validate';
import { transformOnSubmit } from '../transform-on-submit';
import { createTransformOnInit } from '../transform-on-init';
import { syncConversationNameEffect } from '../sync-conversation-name-effect';
import { provideNodeOutputVariablesEffect } from '../../materials/provide-node-output-variables';
import FormRender from './form-render';
import { DEFAULT_CONVERSATION_VALUE, DEFAULT_OUTPUTS } from './constants';
interface FormData {
inputParameters: InputValueVO[];
}
export const CLEAR_CONTEXT_FORM_META: FormMetaV2<FormData> = {
// 节点表单渲染
render: () => <FormRender />,
// 验证触发时机
validateTrigger: ValidateTrigger.onChange,
// 验证规则
validate: {
// 必填
'inputParameters.0.input': createValueExpressionInputValidate({
required: true,
}),
},
// 副作用管理
effect: {
nodeMeta: fireNodeTitleChange,
outputs: provideNodeOutputVariablesEffect,
inputParameters: syncConversationNameEffect,
},
// 节点后端数据 -> 前端表单数据
formatOnInit: createTransformOnInit(
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
),
// 前端表单数据 -> 节点后端数据
formatOnSubmit: transformOnSubmit,
};

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('wf_chatflow_23'),
outputTooltip: I18n.t('wf_chatflow_25'),
});
};
export default Render;

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 { CLEAR_CONTEXT_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { CLEAR_CONTEXT_FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const CLEAR_CONTEXT_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.ClearContext,
CLEAR_CONTEXT_FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,41 @@
/*
* 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 {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
export const CONVERSATION_NAME = 'conversationName';
export const PARAMS_COLUMNS = [
{
title: I18n.t('workflow_detail_node_parameter_name'),
style: { width: 180 },
},
{
title: I18n.t('workflow_detail_node_parameter_value'),
style: { flex: '1' },
},
];
export const INPUT_COLUMNS_NARROW = [
{
title: I18n.t('workflow_detail_node_parameter_name'),
style: { width: 140 },
},
{
title: I18n.t('workflow_detail_node_parameter_value'),
style: { flex: '1' },
},
];

View File

@@ -0,0 +1,57 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('wf_chatflow_14'),
name: CONVERSATION_NAME,
required: true,
type: 'string',
},
};
export const DEFAULT_CONVERSATION_VALUE = [
{
name: CONVERSATION_NAME,
input: {
type: ValueExpressionType.REF,
},
},
];
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
{
key: nanoid(),
name: 'isExisted',
type: ViewVariableType.Boolean,
},
{
key: nanoid(),
name: 'conversationId',
type: ViewVariableType.String,
},
];

View File

@@ -0,0 +1,65 @@
/*
* 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 {
ValidateTrigger,
type FormMetaV2,
} from '@flowgram-adapter/free-layout-editor';
import { type InputValueVO } from '@coze-workflow/base';
import { fireNodeTitleChange } from '@/nodes-v2/materials/fire-node-title-change';
import { createValueExpressionInputValidate } from '@/nodes-v2/materials/create-value-expression-input-validate';
import { transformOnSubmit } from '../transform-on-submit';
import { createTransformOnInit } from '../transform-on-init';
import { provideNodeOutputVariablesEffect } from '../../materials/provide-node-output-variables';
import FormRender from './form-render';
import { DEFAULT_CONVERSATION_VALUE, DEFAULT_OUTPUTS } from './constants';
interface FormData {
inputParameters: InputValueVO[];
}
export const CREATE_CONVERSATION_FORM_META: FormMetaV2<FormData> = {
// 节点表单渲染
render: () => <FormRender />,
// 验证触发时机
validateTrigger: ValidateTrigger.onChange,
// 验证规则
validate: {
// 必填
'inputParameters.0.input': createValueExpressionInputValidate({
required: true,
}),
},
// 副作用管理
effect: {
nodeMeta: fireNodeTitleChange,
outputs: provideNodeOutputVariablesEffect,
},
// 节点后端数据 -> 前端表单数据
formatOnInit: createTransformOnInit(
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
),
// 前端表单数据 -> 节点后端数据
formatOnSubmit: transformOnSubmit,
};

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('wf_chatflow_13'),
outputTooltip: I18n.t('wf_chatflow_15'),
});
};
export default Render;

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 { CREATE_CONVERSATION_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { CREATE_CONVERSATION_FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const CREATE_CONVERSATION_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.CreateConversation,
CREATE_CONVERSATION_FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,45 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
import {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
hasConversationNode: true,
disableBot: true,
disableBotTooltip: I18n.t('wf_chatflow_141'),
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,93 @@
/*
* 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 from 'react';
import {
ValidateTrigger,
type FormMetaV2,
} from '@flowgram-adapter/free-layout-editor';
import { type InputValueVO } from '@coze-workflow/base';
import { fireNodeTitleChange } from '@/nodes-v2/materials/fire-node-title-change';
import { createValueExpressionInputValidate } from '@/nodes-v2/materials/create-value-expression-input-validate';
import { provideNodeOutputVariablesEffect } from '../materials/provide-node-output-variables';
import { transformOnSubmit } from './transform-on-submit';
import { createTransformOnInit } from './transform-on-init';
import { syncConversationNameEffect } from './sync-conversation-name-effect';
interface ChatFormData {
inputParameters: InputValueVO[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
export const createFormMeta = ({
fieldConfig,
needSyncConversationName,
defaultInputValue,
defaultOutputValue,
formRenderComponent,
customValidators = {},
}): FormMetaV2<ChatFormData> => {
// 定义首字母大写的变量引用组件
const FormRender = formRenderComponent;
const formMeta = {
// 节点表单渲染
render: () => <FormRender />,
// 验证触发时机
validateTrigger: ValidateTrigger.onChange,
// 验证规则
validate: {
// 必填
'inputParameters.*.input': createValueExpressionInputValidate({
required: ({ name }) => {
const fieldName = name
.replace('inputParameters.', '')
.replace('.input', '');
return Boolean(fieldConfig[fieldName]?.required);
},
}),
...customValidators,
},
// 副作用管理
effect: {
nodeMeta: fireNodeTitleChange,
outputs: provideNodeOutputVariablesEffect,
},
// 节点后端数据 -> 前端表单数据
formatOnInit: createTransformOnInit(defaultInputValue, defaultOutputValue),
// 前端表单数据 -> 节点后端数据
formatOnSubmit: transformOnSubmit,
};
// 需要同步联动 CONVERSATION_NAME 字段的值
if (needSyncConversationName) {
Object.assign(formMeta.effect, {
inputParameters: syncConversationNameEffect,
});
}
return formMeta;
};

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 { Field } from '@flowgram-adapter/free-layout-editor';
import { PublicScopeProvider } from '@coze-workflow/variable';
import {
type InputValueVO,
type ViewVariableTreeNode,
} from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { Outputs } from '@/nodes-v2/components/outputs';
import NodeMeta from '../components/node-meta';
import FixedInputParameters from '../components/fixed-input-parameters';
import { INPUT_COLUMNS_NARROW } from './constants';
export interface FormRenderProps {
defaultInputValue: InputValueVO[] | undefined;
defaultOutputValue: ViewVariableTreeNode[] | undefined;
fieldConfig: Record<
string,
{
description: string;
name: string;
required: boolean;
type: string;
optionsList?: {
label: string;
value: string;
}[];
}
>;
readonly: boolean;
inputTooltip: string;
outputTooltip: string;
hasInputs?: boolean;
}
export const createFormRender = ({
defaultInputValue,
defaultOutputValue,
fieldConfig,
readonly,
inputTooltip = '',
outputTooltip = '',
hasInputs = true,
}: FormRenderProps) => (
<PublicScopeProvider>
<>
<NodeMeta fieldName="nodeMeta" />
{hasInputs ? (
<FixedInputParameters
fieldName="inputParameters"
defaultValue={defaultInputValue}
headerTitle={I18n.t('workflow_detail_node_parameter_input')}
headerTootip={inputTooltip}
columns={INPUT_COLUMNS_NARROW}
fieldConfig={fieldConfig}
readonly={readonly}
/>
) : null}
<Field name="outputs" defaultValue={defaultOutputValue}>
{({ field, fieldState }) => (
<Outputs
id={'create-conversation-node-output'}
value={field.value}
onChange={field.onChange}
titleTooltip={outputTooltip}
readonly
needErrorBody={false}
errors={fieldState?.errors}
/>
)}
</Field>
</>
</PublicScopeProvider>
);

View File

@@ -0,0 +1,111 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { type InputValueVO, VariableTypeDTO } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('workflow_250407_005'),
name: CONVERSATION_NAME,
required: true,
type: VariableTypeDTO.string,
},
role: {
description: I18n.t('workflow_250407_006'),
name: 'role',
required: true,
type: VariableTypeDTO.string,
// 选项默认值
defaultValue: 'user',
// 选项列表
optionsList: [
{ label: 'user', value: 'user' },
{ label: 'assistant', value: 'assistant' },
],
},
content: {
description: I18n.t('workflow_250407_007'),
name: 'content',
required: true,
type: VariableTypeDTO.string,
},
};
export const DEFAULT_CONVERSATION_VALUE: InputValueVO[] = Object.keys(
FIELD_CONFIG,
).map(fieldName => {
// 针对 role 字段,需要设置字面量默认值
if (fieldName === 'role') {
return {
name: fieldName,
input: {
type: ValueExpressionType.LITERAL,
content: FIELD_CONFIG[fieldName].defaultValue,
},
};
}
return {
name: fieldName,
input: {
type: ValueExpressionType.REF,
},
};
});
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
{
key: nanoid(),
name: 'message',
type: ViewVariableType.Object,
children: [
{
key: nanoid(),
name: 'messageId',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'role',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'contentType',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'content',
type: ViewVariableType.String,
},
],
},
];

View File

@@ -0,0 +1,33 @@
/*
* 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 FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import {
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
FIELD_CONFIG,
} from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: FIELD_CONFIG,
needSyncConversationName: true,
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
});
};
export default Render;

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 { CREATE_MESSAGE_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const CREATE_MESSAGE_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.CreateMessage,
FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,41 @@
/*
* 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 {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,131 @@
/*
* 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, set } from 'lodash-es';
import { type WorkflowNodeFormMeta } from '@flowgram-adapter/free-layout-editor';
import { variableUtils } from '@coze-workflow/variable';
import {
DEFAULT_NODE_META_PATH,
DEFAULT_NODE_SIZE,
type WorkflowNodeRegistry,
} from '@coze-workflow/nodes';
import {
StandardNodeType,
ValueExpression,
type VariableTypeDTO,
ViewVariableType,
type NodeMeta,
type InputValueDTO,
type RefExpressionContent,
} from '@coze-workflow/base';
const NODE_WIDTH = DEFAULT_NODE_SIZE.width;
const NODE_HEIGHT = 113;
const INPUT_PATH = '/inputParameters';
const helpLinkMap = {
[StandardNodeType.ClearContext]:
'/open/docs/guides/clear_conversation_history',
[StandardNodeType.CreateConversation]:
'/open/docs/guides/create_conversation',
[StandardNodeType.QueryMessageList]: '/open/docs/guides/query_message_list',
[StandardNodeType.UpdateConversation]: '/open/docs/guides/edit_conversation',
[StandardNodeType.DeleteConversation]:
'/open/docs/guides/delete_conversation',
[StandardNodeType.QueryConversationList]:
'/open/docs/guides/query_conversation_list',
[StandardNodeType.QueryConversationHistory]:
'/open/docs/guides/query_conversation_history',
[StandardNodeType.CreateMessage]: '/open/docs/guides/create_message',
[StandardNodeType.UpdateMessage]: '/open/docs/guides/edit_message',
[StandardNodeType.DeleteMessage]: '/open/docs/guides/delete_message',
};
export const createNodeRegistry = (
nodeType: StandardNodeType,
formMeta: WorkflowNodeFormMeta,
fieldConfig: Record<
string,
{
description: string;
name: string;
required: boolean;
type: string;
}
>,
nodeMeta?: Partial<NodeMeta>,
// eslint-disable-next-line max-params
): WorkflowNodeRegistry => ({
type: nodeType,
meta: {
nodeDTOType: nodeType,
style: {
width: NODE_WIDTH,
},
size: { width: NODE_WIDTH, height: NODE_HEIGHT },
nodeMetaPath: DEFAULT_NODE_META_PATH,
inputParametersPath: INPUT_PATH, // 入参路径,试运行等功能依赖该路径提取参数
getInputVariableTag(name, input, extra) {
const field = fieldConfig[name || ''];
let invalid = false;
if (field?.required) {
const content = input?.content as RefExpressionContent;
const isRef = content?.keyPath?.length > 0;
// 初始化流程时,可能变量模块还没有初始化,这里会获取不到变量...
const variable = extra?.variableService.getWorkflowVariableByKeyPath(
content?.keyPath,
{ node: extra?.node, checkScope: true },
);
// 必填场景,如果:
// 1. 为空,报错
// 2. 不为空
// 2.1 引用变量,且变量不存在,报错
invalid = ValueExpression.isEmpty(input) || (isRef && !variable);
}
return {
label: name,
type: field?.type
? variableUtils.DTOTypeToViewType(field.type as VariableTypeDTO)
: ViewVariableType.String,
invalid,
};
},
helpLink: helpLinkMap[nodeType],
...nodeMeta,
},
beforeNodeSubmit: nodeData => {
const inputParameters = get(
nodeData,
'data.inputs.inputParameters',
[],
) as InputValueDTO[];
// 对于固定参数类型,需要将 type 字段设置为预定义的类型,而不是右侧填入的变量类型
inputParameters.forEach(param => {
const config = fieldConfig[param.name || ''];
if (config && param.input.type) {
set(param, 'input.type', config.type);
}
});
return nodeData;
},
formMeta,
});

View File

@@ -0,0 +1,47 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('workflow_250407_023'),
name: CONVERSATION_NAME,
required: true,
type: 'string',
},
};
export const DEFAULT_CONVERSATION_VALUE = Object.keys(FIELD_CONFIG).map(
fieldName => ({
name: fieldName,
input: {
type: ValueExpressionType.REF,
},
}),
);
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
];

View File

@@ -0,0 +1,33 @@
/*
* 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 FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import {
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
FIELD_CONFIG,
} from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: FIELD_CONFIG,
needSyncConversationName: false,
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
});
};
export default Render;

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 { DELETE_CONVERSATION_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const DELETE_CONVERSATION_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.DeleteConversation,
FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,45 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
import {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
hasConversationNode: true,
disableBot: true,
disableBotTooltip: I18n.t('wf_chatflow_141'),
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,55 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { VariableTypeDTO } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('workflow_250407_015'),
name: CONVERSATION_NAME,
required: true,
type: VariableTypeDTO.string,
},
messageId: {
description: I18n.t('workflow_250407_016'),
name: 'messageId',
required: true,
type: VariableTypeDTO.string,
},
};
export const DEFAULT_CONVERSATION_VALUE = Object.keys(FIELD_CONFIG).map(
fieldName => ({
name: fieldName,
input: {
type: ValueExpressionType.REF,
},
}),
);
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
];

View File

@@ -0,0 +1,33 @@
/*
* 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 FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import {
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
FIELD_CONFIG,
} from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: FIELD_CONFIG,
needSyncConversationName: true,
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
});
};
export default Render;

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 { DELETE_MESSAGE_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const DELETE_MESSAGE_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.DeleteMessage,
FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,41 @@
/*
* 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 {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,26 @@
/*
* 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 { QUERY_MESSAGE_LIST_NODE_REGISTRY } from './query-message-list';
export { CREATE_CONVERSATION_NODE_REGISTRY } from './create-conversation';
export { CLEAR_CONTEXT_NODE_REGISTRY } from './clear-conversation-history';
export { UPDATE_CONVERSATION_NODE_REGISTRY } from './update-conversation';
export { DELETE_CONVERSATION_NODE_REGISTRY } from './delete-conversation';
export { QUERY_CONVERSATION_LIST_NODE_REGISTRY } from './query-conversation-list';
export { QUERY_CONVERSATION_HISTORY_NODE_REGISTRY } from './query-conversation-history';
export { CREATE_MESSAGE_NODE_REGISTRY } from './create-message';
export { UPDATE_MESSAGE_NODE_REGISTRY } from './update-message';
export { DELETE_MESSAGE_NODE_REGISTRY } from './delete-message';

View File

@@ -0,0 +1,66 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { VariableTypeDTO } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('workflow_250407_028'),
name: CONVERSATION_NAME,
required: true,
type: VariableTypeDTO.string,
},
rounds: {
description: I18n.t('workflow_250407_029'),
name: 'rounds',
required: true,
type: VariableTypeDTO.integer,
},
};
export const DEFAULT_CONVERSATION_VALUE = Object.keys(FIELD_CONFIG).map(
fieldName => ({
name: fieldName,
input: {
type: ValueExpressionType.REF,
},
}),
);
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'messageList',
type: ViewVariableType.ArrayObject,
children: [
{
key: nanoid(),
name: 'role',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'content',
type: ViewVariableType.String,
},
],
},
];

View File

@@ -0,0 +1,33 @@
/*
* 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 FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import {
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
FIELD_CONFIG,
} from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: FIELD_CONFIG,
needSyncConversationName: true,
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
});
};
export default Render;

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 { QUERY_CONVERSATION_HISTORY_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const QUERY_CONVERSATION_HISTORY_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.QueryConversationHistory,
FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,41 @@
/*
* 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 {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,38 @@
/*
* 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';
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'conversationList',
type: ViewVariableType.ArrayObject,
children: [
{
key: nanoid(),
name: 'conversationName',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'conversationId',
type: ViewVariableType.String,
},
],
},
];

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 { type FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import { DEFAULT_OUTPUTS } from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: {},
needSyncConversationName: false,
defaultInputValue: [],
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,38 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import { DEFAULT_OUTPUTS } from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: [],
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: {},
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
hasInputs: false,
});
};
export default Render;

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 { QUERY_CONVERSATION_LIST_NODE_REGISTRY } from './node-registry';

View File

@@ -0,0 +1,28 @@
/*
* 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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
export const QUERY_CONVERSATION_LIST_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.QueryConversationList,
FORM_META,
{},
{ test },
);

View File

@@ -0,0 +1,35 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { generateEnvToRelatedContextProperties } from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
hasConversationNode: true,
disableBot: true,
disableBotTooltip: I18n.t('wf_chatflow_141'),
});
},
};

View File

@@ -0,0 +1,120 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
[CONVERSATION_NAME]: {
description: I18n.t('wf_chatflow_24'),
name: CONVERSATION_NAME,
required: true,
type: 'string',
},
limit: {
description: I18n.t('wf_chatflow_34'),
name: 'limit',
required: false,
type: 'integer',
},
beforeId: {
description: I18n.t('wf_chatflow_35'),
name: 'beforeId',
required: false,
type: 'string',
},
afterId: {
description: I18n.t('wf_chatflow_36'),
name: 'afterId',
required: false,
type: 'string',
},
};
export const DEFAULT_CONVERSATION_VALUE = [
{
name: CONVERSATION_NAME,
input: {
type: ValueExpressionType.REF,
},
},
{
name: 'limit',
input: {
type: ValueExpressionType.REF,
},
},
{
name: 'beforeId',
input: {
type: ValueExpressionType.REF,
},
},
{
name: 'afterId',
input: {
type: ValueExpressionType.REF,
},
},
];
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'messageList',
type: ViewVariableType.ArrayObject,
children: [
{
key: nanoid(),
name: 'messageId',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'role',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'contentType',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'content',
type: ViewVariableType.String,
},
],
},
{
key: nanoid(),
name: 'firstId',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'lastId',
type: ViewVariableType.String,
},
{
key: nanoid(),
name: 'hasMore',
type: ViewVariableType.Boolean,
},
];

View File

@@ -0,0 +1,67 @@
/*
* 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 {
ValidateTrigger,
type FormMetaV2,
} from '@flowgram-adapter/free-layout-editor';
import { type InputValueVO } from '@coze-workflow/base';
import { fireNodeTitleChange } from '@/nodes-v2/materials/fire-node-title-change';
import { createValueExpressionInputValidate } from '@/nodes-v2/materials/create-value-expression-input-validate';
import { transformOnSubmit } from '../transform-on-submit';
import { createTransformOnInit } from '../transform-on-init';
import { syncConversationNameEffect } from '../sync-conversation-name-effect';
import { provideNodeOutputVariablesEffect } from '../../materials/provide-node-output-variables';
import FormRender from './form-render';
import { DEFAULT_CONVERSATION_VALUE, DEFAULT_OUTPUTS } from './constants';
interface FormData {
inputParameters: InputValueVO[];
}
export const QUERY_MESSAGE_LIST_FORM_META: FormMetaV2<FormData> = {
// 节点表单渲染
render: props => <FormRender {...props} />,
// 验证触发时机
validateTrigger: ValidateTrigger.onChange,
// 验证规则
validate: {
// 必填
'inputParameters.0.input': createValueExpressionInputValidate({
required: true,
}),
},
// 副作用管理
effect: {
nodeMeta: fireNodeTitleChange,
outputs: provideNodeOutputVariablesEffect,
inputParameters: syncConversationNameEffect,
},
// 节点后端数据 -> 前端表单数据
formatOnInit: createTransformOnInit(
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
),
// 前端表单数据 -> 节点后端数据
formatOnSubmit: transformOnSubmit,
};

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = ({ form }) => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('wf_chatflow_33'),
outputTooltip: I18n.t('wf_chatflow_37'),
});
};
export default Render;

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 { QUERY_MESSAGE_LIST_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { QUERY_MESSAGE_LIST_FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const QUERY_MESSAGE_LIST_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.QueryMessageList,
QUERY_MESSAGE_LIST_FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,41 @@
/*
* 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 {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,96 @@
/*
* 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 { set, cloneDeep } from 'lodash-es';
import {
DataEvent,
type EffectOptions,
type Effect,
FlowNodeFormData,
type FormModelV2,
} from '@flowgram-adapter/free-layout-editor';
import { ValueExpression, WorkflowMode } from '@coze-workflow/base';
import { CONVERSATION_NAME } from './constants';
/** 延迟200ms此时等边连上后才能检测变量作用域 */
const DELAY = 200;
const effect: Effect = ({ value, context }) => {
if (!context) {
return;
}
const { node, playgroundContext } = context;
const { variableService, nodesService, globalState } = playgroundContext;
const startNode = nodesService.getStartNode();
const formModel = node.getData(FlowNodeFormData).getFormModel<FormModelV2>();
const isChatflow = globalState.flowMode === WorkflowMode.ChatFlow;
const { isInIDE } = globalState;
setTimeout(() => {
const startConversationNameVar =
variableService.getWorkflowVariableByKeyPath(
[startNode.id, 'CONVERSATION_NAME'],
{
node,
checkScope: true,
},
);
const clonedValue = cloneDeep(value);
const conversationNameItem = clonedValue.find(
v => v.name === CONVERSATION_NAME,
);
const noValue = ValueExpression.isEmpty(
conversationNameItem?.input as ValueExpression,
);
// 如果能够找到开始节点的 CONVERSATION_NAME 参数
if (
startConversationNameVar &&
conversationNameItem &&
isChatflow &&
noValue
) {
if (formModel) {
set(conversationNameItem, 'input', {
type: 'ref',
content: {
keyPath: ['100001', 'CONVERSATION_NAME'],
},
});
formModel.setValueIn('inputParameters', clonedValue);
}
} else if (!isInIDE && !isChatflow && conversationNameItem && noValue) {
// 非项目中的工作流,如果存在没有值的 CONVERSATION_NAME 字段,填入默认值 default
if (formModel) {
set(conversationNameItem, 'input', {
type: 'literal',
content: 'Default',
});
formModel.setValueIn('inputParameters', clonedValue);
}
}
}, DELAY);
};
export const syncConversationNameEffect: EffectOptions[] = [
{
event: DataEvent.onValueInit,
effect,
},
];

View File

@@ -0,0 +1,57 @@
/*
* 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 omit from 'lodash-es/omit';
import {
type InputValueVO,
type ViewVariableTreeNode,
type NodeDataDTO,
} from '@coze-workflow/base';
const isEmptyArrayOrNil = (value: unknown) =>
// eslint-disable-next-line eqeqeq
(Array.isArray(value) && value.length === 0) || value == null;
/**
* 节点后端数据 -> 前端表单数据
*/
export const createTransformOnInit =
(
defaultInputValue: InputValueVO[] = [],
defaultOutputValue: ViewVariableTreeNode[] = [],
) =>
(value: NodeDataDTO) => {
const { inputs, outputs } = value || {};
const inputParameters = inputs?.inputParameters || [];
// 由于在提交时,会将没有填值的变量给过滤掉,所以需要在初始化时,将默认值补充进来
// 参见packages/workflow/nodes/src/workflow-json-format.ts:241
const refillInputParamters = defaultInputValue.map(cur => {
const { name } = cur;
const target = inputParameters.find(item => item.name === name);
if (target) {
return target;
}
return cur;
}, []);
const initValue = {
...omit(value, ['inputs']),
inputParameters: refillInputParamters,
outputs: isEmptyArrayOrNil(outputs) ? defaultOutputValue : outputs,
};
return initValue;
};

View File

@@ -0,0 +1,38 @@
/*
* 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 omit from 'lodash-es/omit';
import { type NodeDataDTO, type InputValueVO } from '@coze-workflow/base';
interface FormData {
inputParameters: InputValueVO[];
}
/**
* 前端表单数据 -> 节点后端数据
* @param value
* @returns
*/
export const transformOnSubmit = (value: FormData): NodeDataDTO => {
const formattedValue: Record<string, unknown> = {
...value,
inputs: {
inputParameters: value?.inputParameters || [],
},
};
return omit(formattedValue, ['inputParameters']) as unknown as NodeDataDTO;
};

View File

@@ -0,0 +1,63 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('workflow_250407_019'),
name: CONVERSATION_NAME,
required: true,
type: 'string',
},
newConversationName: {
description: I18n.t('workflow_250407_020'),
name: 'newConversationName',
required: true,
type: 'string',
},
};
export const DEFAULT_CONVERSATION_VALUE = Object.keys(FIELD_CONFIG).map(
fieldName => ({
name: fieldName,
input: {
type: ValueExpressionType.REF,
},
}),
);
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
{
key: nanoid(),
name: 'isExisted',
type: ViewVariableType.Boolean,
},
{
key: nanoid(),
name: 'conversationId',
type: ViewVariableType.String,
},
];

View File

@@ -0,0 +1,33 @@
/*
* 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 FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import {
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
FIELD_CONFIG,
} from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: FIELD_CONFIG,
needSyncConversationName: false,
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
});
};
export default Render;

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 { UPDATE_CONVERSATION_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const UPDATE_CONVERSATION_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.UpdateConversation,
FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,45 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
import {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
hasConversationNode: true,
disableBot: true,
disableBotTooltip: I18n.t('wf_chatflow_141'),
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,62 @@
/*
* 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 { ValueExpressionType, ViewVariableType } from '@coze-workflow/variable';
import { VariableTypeDTO } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { CONVERSATION_NAME } from '../constants';
export const FIELD_CONFIG = {
conversationName: {
description: I18n.t('workflow_250407_010'),
name: CONVERSATION_NAME,
required: true,
type: VariableTypeDTO.string,
},
messageId: {
description: I18n.t('workflow_250407_011'),
name: 'messageId',
required: true,
type: VariableTypeDTO.string,
},
newContent: {
description: I18n.t('workflow_250407_012'),
name: 'newContent',
required: true,
type: VariableTypeDTO.string,
},
};
export const DEFAULT_CONVERSATION_VALUE = Object.keys(FIELD_CONFIG).map(
fieldName => ({
name: fieldName,
input: {
type: ValueExpressionType.REF,
},
}),
);
export const DEFAULT_OUTPUTS = [
{
key: nanoid(),
name: 'isSuccess',
type: ViewVariableType.Boolean,
},
];

View File

@@ -0,0 +1,33 @@
/*
* 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 FormMetaV2 } from '@flowgram-adapter/free-layout-editor';
import { createFormMeta } from '../create-form-meta';
import FormRender from './form-render';
import {
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
FIELD_CONFIG,
} from './constants';
export const FORM_META: FormMetaV2 = createFormMeta({
fieldConfig: FIELD_CONFIG,
needSyncConversationName: true,
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
formRenderComponent: FormRender,
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { createFormRender } from '../create-form-render';
import {
FIELD_CONFIG,
DEFAULT_CONVERSATION_VALUE,
DEFAULT_OUTPUTS,
} from './constants';
const Render = () => {
const readonly = useReadonly();
return createFormRender({
defaultInputValue: DEFAULT_CONVERSATION_VALUE,
defaultOutputValue: DEFAULT_OUTPUTS,
fieldConfig: FIELD_CONFIG,
readonly,
inputTooltip: I18n.t('Input'),
outputTooltip: I18n.t('Output'),
});
};
export default Render;

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 { UPDATE_MESSAGE_NODE_REGISTRY } from './node-registry';

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 { StandardNodeType } from '@coze-workflow/base';
import { createNodeRegistry } from '../create-node-registry';
import { test } from './node-test';
import { FORM_META } from './form-meta';
import { FIELD_CONFIG } from './constants';
export const UPDATE_MESSAGE_NODE_REGISTRY = createNodeRegistry(
StandardNodeType.UpdateMessage,
FORM_META,
FIELD_CONFIG,
{ test },
);

View File

@@ -0,0 +1,41 @@
/*
* 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 {
generateParametersToProperties,
generateEnvToRelatedContextProperties,
} from '@/test-run-kit';
import { type NodeTestMeta } from '@/test-run-kit';
export const test: NodeTestMeta = {
generateRelatedContext(node, context) {
const { isInProject } = context;
if (isInProject) {
return {};
}
return generateEnvToRelatedContextProperties({
isNeedBot: true,
});
},
generateFormInputProperties(node) {
const formData = node
.getData(FlowNodeFormData)
.formModel.getFormItemValueByPath('/');
return generateParametersToProperties(formData?.inputParameters, { node });
},
};

View File

@@ -0,0 +1,45 @@
/*
* 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 FC } from 'react';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { IconButton, Tooltip } from '@coze-arch/coze-design';
interface AddIconProps {
disabledTooltip?: string;
onClick?: (e) => void;
}
export const AddIcon: FC<AddIconProps> = ({ disabledTooltip, onClick }) =>
disabledTooltip ? (
<Tooltip content={disabledTooltip}>
<IconButton
disabled={!!disabledTooltip}
color="highlight"
size="small"
icon={<IconCozPlus className="text-sm" />}
onClick={onClick}
/>
</Tooltip>
) : (
<IconButton
color="highlight"
size="small"
icon={<IconCozPlus className="text-sm" />}
onClick={onClick}
/>
);

View File

@@ -0,0 +1,98 @@
/*
* 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 } from 'react';
import {
useForm,
type FlowNodeEntity,
} from '@flowgram-adapter/free-layout-editor';
import { FlowNodeBaseType } from '@flowgram-adapter/free-layout-editor';
import {
useEntityFromContext,
usePlayground,
} from '@flowgram-adapter/free-layout-editor';
import { SettingOnErrorProcessType } from '@coze-workflow/nodes';
import { I18n } from '@coze-arch/i18n';
import { Tooltip } from '@coze-arch/coze-design';
import { type ComponentProps } from '@/nodes-v2/components/types';
import { Radio } from '@/nodes-v2/components/radio';
import { FormCard } from '@/form-extensions/components/form-card';
export const BatchMode = ({
name,
value,
onChange,
onBlur,
}: ComponentProps<string>) => {
const node = useEntityFromContext() as FlowNodeEntity;
const playground = usePlayground();
const { isBatchV2 } = playground.context.schemaGray;
const form = useForm();
const processType = form.getValueIn('settingOnError.processType');
const options = useMemo(() => {
const isExceptionSetting =
processType === SettingOnErrorProcessType.EXCEPTION;
const isBatchDisabled =
node.parent?.flowNodeType === FlowNodeBaseType.SUB_CANVAS ||
isExceptionSetting;
const disabledTooltip = isExceptionSetting
? I18n.t(
'workflow_250416_05',
undefined,
'需要先把节点的异常处理方式改为中断流程或者返回设定内容,才能改为批处理模式',
)
: '';
return [
{
value: 'single',
label: I18n.t('workflow_batch_tab_single_radio'),
},
{
value: 'batch',
label: disabledTooltip ? (
<Tooltip content={disabledTooltip}>
<div>{I18n.t('workflow_batch_tab_batch_radio')}</div>
</Tooltip>
) : (
I18n.t('workflow_batch_tab_batch_radio')
),
disabled: isBatchDisabled,
},
];
}, [node, processType]);
if (isBatchV2) {
return <></>;
}
return (
<FormCard collapsible={false}>
<Radio
name={name}
mode={'button'}
options={options}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
</FormCard>
);
};

View File

@@ -0,0 +1,43 @@
/* stylelint-disable declaration-no-important */
.workflow-batch-setting-panel {
position: relative;
padding: 24px;
background: rgba(247, 247, 250, 100%);
border-radius: 12px;
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 30%);
}
.workflow-batch-setting-panel-title {
padding-bottom: 32px;
font-size: 18px !important;
line-height: 24px !important;
}
.form-field {
display: flex;
&:first-child {
margin-bottom: 16px !important;
}
:global {
.semi-form-field-label {
width: 200px;
padding-top: 4px;
}
.rc-slider {
width: 180px;
}
.rc-slider-disabled {
background-color: unset;
}
.rc-slider+div {
width: 108px;
}
}
}

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 React, { useRef, useEffect } from 'react';
import { debounce } from 'lodash-es';
import {
BATCH_SIZE_MAX,
BATCH_SIZE_MIN,
BATCH_CONCURRENT_SIZE_MIN,
BATCH_CONCURRENT_SIZE_MAX,
} from '@coze-workflow/nodes';
import { I18n } from '@coze-arch/i18n';
import { type FormState } from '@coze-arch/bot-semi/Form';
import { Form as BotSemiForm, Typography } from '@coze-arch/bot-semi';
import { InputSlider } from '@coze-agent-ide/space-bot/input-slider';
import { LabelWithTooltip } from '@/form-extensions/components/label-with-tooltip';
import s from './batch-setting-form.module.less';
interface BatchSetting {
batchSize: number;
concurrentSize: number;
}
export type BatchSettingOnChangeValue = FormState<BatchSetting>;
export interface BatchSettingProps {
readonly?: boolean;
value: BatchSetting;
onChange: (value: FormState<BatchSetting>) => void;
}
const DELAY_TIME = 10;
export const BatchSettingForm = ({
value,
readonly = false,
onChange,
}: BatchSettingProps) => {
const formRef = useRef<BotSemiForm>(null);
const isSemiFormDestroyed = useRef(false);
const debouncedChange = debounce((v: BatchSettingOnChangeValue) => {
// semi form 在销毁时会额外触发onChange从而污染数据这里避免这样的情况发生
if (isSemiFormDestroyed.current) {
return;
}
onChange(v);
}, DELAY_TIME);
const commonSliderProps = {
useRcSlider: true,
fieldClassName: s['form-field'],
disabled: readonly,
decimalPlaces: 0,
step: 1,
};
useEffect(
() => () => {
isSemiFormDestroyed.current = true;
},
[],
);
return (
// 防止触发节点选中
<div
className={s['workflow-batch-setting-panel']}
onClick={e => e.stopPropagation()}
>
<Typography.Title className={s['workflow-batch-setting-panel-title']}>
{I18n.t('workflow_batch_settings')}
</Typography.Title>
<BotSemiForm<BatchSetting>
ref={formRef}
initValues={value}
onChange={debouncedChange}
>
<InputSlider
{...commonSliderProps}
field="batchSize"
label={
<LabelWithTooltip
label={I18n.t('workflow_maximum_run_count')}
tooltip={I18n.t('workflow_maximum_run_count_tips')}
></LabelWithTooltip>
}
max={BATCH_SIZE_MAX}
min={BATCH_SIZE_MIN}
/>
<InputSlider
{...commonSliderProps}
field="concurrentSize"
label={
<LabelWithTooltip
label={I18n.t('workflow_maximum_parallel_runs')}
tooltip={I18n.t('workflow_maximum_parallel_runs_tips')}
></LabelWithTooltip>
}
max={BATCH_CONCURRENT_SIZE_MAX}
min={BATCH_CONCURRENT_SIZE_MIN}
/>
</BotSemiForm>
</div>
);
};

View File

@@ -0,0 +1,242 @@
/*
* 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 } from 'react';
import { get } from 'lodash-es';
import {
useField,
FieldArray,
type FieldRenderProps,
type FieldArrayRenderProps,
useForm,
Field,
usePlayground,
} from '@flowgram-adapter/free-layout-editor';
import {
DEFAULT_BATCH_CONCURRENT_SIZE,
DEFAULT_BATCH_SIZE,
} from '@coze-workflow/nodes';
import {
type BatchVOInputList,
concatTestId,
type RefExpression,
type ValueExpression,
ValueExpressionType,
ViewVariableType,
} from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { Popover } from '@coze-arch/bot-semi';
import { IconSetting } from '@coze-arch/bot-icons';
import { IconCozMinus, IconCozPlus } from '@coze-arch/coze-design/icons';
import { IconButton } from '@coze-arch/coze-design';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { ValueExpressionInput } from '@/nodes-v2/components/value-expression-input';
import { NodeInputName } from '@/nodes-v2/components/node-input-name';
import { FormItemFeedback } from '@/nodes-v2/components/form-item-feedback';
import { FormCard } from '@/form-extensions/components/form-card';
import { ColumnsTitleWithAction } from '@/form-extensions/components/columns-title-with-action';
import {
BatchSettingForm,
type BatchSettingOnChangeValue,
} from './batch-setting-form';
import s from './index.module.less';
interface BatchProps {
batchModeName: string;
name: string;
}
export const Batch = ({ batchModeName, name }: BatchProps) => {
const batchMode = useField(batchModeName)?.value;
const readonly = useReadonly();
const form = useForm();
const playground = usePlayground();
const { isBatchV2 } = playground.context.schemaGray;
const actionButtonContent = useMemo(
() => (
<div className={s.actionButtonContent}>
<Popover
keepDOM
stopPropagation
trigger="click"
position="bottomRight"
content={
<BatchSettingForm
readonly={readonly}
value={{
batchSize:
form.getValueIn(`${name}.batchSize`) ?? DEFAULT_BATCH_SIZE,
concurrentSize:
form.getValueIn(`${name}.concurrentSize`) ??
DEFAULT_BATCH_CONCURRENT_SIZE,
}}
onChange={(value: BatchSettingOnChangeValue) => {
form.setValueIn(
`${name}.batchSize`,
get(value.values, 'batchSize'),
);
form.setValueIn(
`${name}.concurrentSize`,
get(value.values, 'concurrentSize'),
);
}}
/>
}
>
<IconButton
color="secondary"
size={'small'}
icon={<IconSetting size="small" />}
style={{ marginRight: 26 }}
/>
</Popover>
</div>
),
[form, name, readonly],
);
if (isBatchV2) {
return <></>;
}
return batchMode === 'batch' ? (
<div className={s['batch-container']}>
<FieldArray
name={`${name}.inputLists`}
defaultValue={[
{ name: 'item1', input: { type: ValueExpressionType.REF }, id: '0' },
]}
>
{({ field }: FieldArrayRenderProps<BatchVOInputList>) => {
const disableDelete = field.value && field.value.length < 2;
return (
<FormCard
className={s['batch-content']}
header={I18n.t('workflow_detail_node_batch')}
tooltip={I18n.t('workflow_detail_node_batch_tooltip')}
actionButton={actionButtonContent}
>
<div className={s['columns-title']}>
<ColumnsTitleWithAction
columns={[
{
title: I18n.t('workflow_detail_variable_input_name'),
style: {
flex: 2,
},
},
{
title: I18n.t('workflow_detail_variable_input_value'),
style: {
flex: 3,
},
},
]}
readonly={readonly}
/>
</div>
{field.map((child, index) => (
<div key={child.key} className={s['input-item']}>
<Field name={`${child.name}.name`}>
{({
field: childNameField,
fieldState: childNameState,
}: FieldRenderProps<string>) => (
<div
style={{
flex: 2,
}}
>
<NodeInputName
{...childNameField}
input={form.getValueIn<RefExpression>(
`${child.name}.input`,
)}
inputParameters={field.value || []}
isError={!!childNameState?.errors?.length}
/>
<FormItemFeedback errors={childNameState?.errors} />
</div>
)}
</Field>
<Field name={`${child.name}.input`}>
{({
field: childInputField,
fieldState: childInputState,
}: FieldRenderProps<ValueExpression | undefined>) => (
<div style={{ flex: 3, overflow: 'hidden' }}>
<ValueExpressionInput
{...childInputField}
key="ValueExpressionInput"
literalDisabled={false}
disabledTypes={ViewVariableType.getComplement(
ViewVariableType.getAllArrayType(),
)}
/>
<FormItemFeedback errors={childInputState?.errors} />
</div>
)}
</Field>
{readonly ? (
<></>
) : (
<div className="leading-none">
<IconButton
size="small"
color="secondary"
disabled={disableDelete}
data-testid={concatTestId(child.name, 'remove')}
icon={<IconCozMinus />}
onClick={() => {
if (disableDelete) {
return;
}
field.delete(index);
}}
/>
</div>
)}
</div>
))}
<div className={s['input-add-icon']}>
<IconButton
className="!block"
color="highlight"
size="small"
icon={<IconCozPlus />}
onClick={() => {
field.append({
id: `${field.value?.length ?? 0}`,
name: '',
input: { type: ValueExpressionType.REF },
});
}}
/>
</div>
</FormCard>
);
}}
</FieldArray>
</div>
) : null;
};

View File

@@ -0,0 +1,85 @@
/* stylelint-disable max-nesting-depth */
/* stylelint-disable no-descending-specificity */
.batch-container {
.input-item {
display: flex;
flex-direction: row;
gap: 4px;
align-items: flex-start;
margin-bottom: 8px;
.input-item-name {
width: 100px;
margin-right: 16px;
text-align: right;
}
.input-item-input {
flex: 1;
}
}
.input-add-icon {
position: absolute;
top: 14px;
right: 0
}
}
.batch-content {
padding: 12px 0 0;
}
.del {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
height: 32px;
.icon {
padding: 4px;
&.disabled {
cursor: not-allowed;
color: var(--semi-color-disabled-text);
>svg {
>path {
fill: var(--semi-color-disabled-text);
}
}
}
>svg {
width: 20px;
height: 20px;
color: var(--light-usage-text-color-text-3, rgb(28 29 35 / 35%));
}
}
}
.columns-title{
padding-bottom: 8px;
}
.action-button-content {
display: flex;
}
.batch-form-card {
:global(.custom-action-button) {
margin-right: 32px;
}
:global(.array-render-rehaje-add-btn) {
position: absolute;
top: 12px;
right: 12px;
}
}

View File

@@ -0,0 +1,93 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable selector-class-pattern */
// lineHeight: 每一行的高度
@lineHeight: 16px;
// reserved: 多行场景需要多留出一点高度,便于用户感知到还有其他行
@reserved: 12px;
.setMinRows(@minRows) {
&-minRows-@{minRows} {
&>div[data-slate-editor="true"] {
min-height: unit(@minRows * @lineHeight + @reserved, px) !important;
}
}
&-cmMinRows-@{minRows} {
min-height: unit(@minRows * @lineHeight + @reserved, px) !important;
}
}
.expression-editor-container {
position: relative;
display: inline-block;
box-sizing: border-box;
width: 100%;
word-break: break-all;
vertical-align: bottom;
background-color: var(--semi-color-white);
border: 1px solid var(--Stroke-COZ-stroke-plus, rgba(84, 97, 156, 27%));
border-radius: var(--small, 6px);
transition: background-color var(--semi-transition_duration-none) var(--semi-transition_function-easeIn) var(--semi-transition_delay-none), border var(--semi-transition_duration-none) var(--semi-transition_function-easeIn) var(--semi-transition_delay-none);
.editor-render {
cursor: text;
resize: none;
position: relative;
box-sizing: border-box;
width: 100%;
padding: 5px 12px;
font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12px;
line-height: 18px;
color: var(--semi-color-text-0);
vertical-align: bottom;
background-color: transparent;
border: 0 solid transparent;
outline: none;
box-shadow: none;
&-bottom-padding {
padding: 5px 12px 22px;
}
&-minRows-1 {
&>div[data-slate-editor="true"] {
min-height: unit(@lineHeight, px) !important;
}
}
&-cm-content {
box-sizing: content-box;
}
&-cmMinRows-1 {
min-height: unit(@lineHeight, px) !important;
}
// 根据最小行数预设最小高度
.setMinRows(2);
.setMinRows(3);
.setMinRows(4);
.setMinRows(5);
}
}
.expression-editor-error {
border: 1px var(--semi-color-danger) solid !important;
}
.expression-editor-focused {
border: 1px var(--semi-color-primary) solid;
}

View File

@@ -0,0 +1,216 @@
/*
* 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 {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type FC,
} from 'react';
import { debounce } from 'lodash-es';
import classNames from 'classnames';
import { SelectorBoxConfigEntity } from '@flowgram-adapter/free-layout-editor';
import { useEntity } from '@flowgram-adapter/free-layout-editor';
import {
ExpressionEditorCounter,
ExpressionEditorEvent,
ExpressionEditorModel,
type ExpressionEditorTreeNode,
Expression,
} from '@coze-workflow/components';
import { type InputValueVO, useNodeTestId } from '@coze-workflow/base';
import { useParseText, useVariableTree } from '../hooks';
import styles from './index.module.less';
export interface ExpressionEditorContainerProps {
name: string;
value: string;
key?: string;
placeholder?: string | (() => string);
readonly?: boolean;
disableSuggestion?: boolean;
disableCounter?: boolean;
onChange?: (value: string) => void;
minRows?: number;
maxLength?: number;
onBlur?: () => void;
onFocus?: () => void;
isError?: boolean;
inputParameters?: InputValueVO[];
className?: string;
containerClassName?: string;
shouldUseContainerRef?: boolean;
testId?: string;
onChangeTrigger?: 'onChange' | 'onBlur';
}
/**
* 业务逻辑和编辑器逻辑的聚合层
*/
export const ExpressionEditorContainer: FC<
ExpressionEditorContainerProps
> = props => {
const containerRef = useRef<HTMLDivElement>(null);
const {
name,
key,
onChange,
onBlur,
onFocus,
isError,
readonly = false,
disableSuggestion = false,
disableCounter = true,
minRows = 4,
className,
containerClassName,
shouldUseContainerRef,
testId,
onChangeTrigger = 'onBlur',
} = props;
const maxLength = undefined; // 临时禁用
const variableTree: ExpressionEditorTreeNode[] = useVariableTree();
const [focus, _setFocus] = useState<boolean>(false);
const { getNodeSetterId } = useNodeTestId();
const selectorBoxConfig = useEntity<SelectorBoxConfigEntity>(
SelectorBoxConfigEntity,
);
const [curEditorVal, setCurEditorVal] = useState<string>(props.value || '');
const placeholder = useParseText(props.placeholder);
const dataTestID = getNodeSetterId(testId ?? name);
const formValue: string = props.value || '';
const [model] = useState<ExpressionEditorModel>(
() => new ExpressionEditorModel(formValue),
);
model.setVariableTree(variableTree);
model.setFocus(focus);
// 设置防抖防止 onFocus / onBlur 在点击时出现抖动
const setFocus = useCallback(
debounce((newFocusValue: boolean) => {
_setFocus(newFocusValue);
}, 50),
[],
);
const overflow = useMemo(() => {
if (typeof maxLength !== 'number') {
return false;
}
return model.value.length > maxLength;
}, [model.value.length, maxLength]);
useEffect(() => {
const disposer = model.on<ExpressionEditorEvent.Change>(
ExpressionEditorEvent.Change,
params => {
onChange && onChange(params.value);
},
);
return () => {
disposer();
};
}, [onChange]);
function handlePopoverVisibilityChange(visible: boolean) {
if (visible) {
selectorBoxConfig.disabled = true;
} else {
selectorBoxConfig.disabled = false;
}
}
/**
* 存在输入中文时 value 和 editor.getValue() 始终不一致,导致重渲染的情况
* 所以改为 onBlur 时更新表单数据
*/
const handleOnBlur = () => {
if (onChangeTrigger === 'onBlur') {
onChange?.(curEditorVal);
}
setFocus(false);
onBlur?.();
};
return (
<div
key={key}
className={classNames(
containerClassName,
styles['expression-editor-container'],
{
[styles['expression-editor-focused']]: focus,
[styles['expression-editor-error']]: isError || overflow,
},
)}
onFocus={() => {
setFocus(true);
onFocus?.();
}}
onBlur={handleOnBlur}
ref={containerRef}
>
<Expression.EditorProvider>
<Expression.Renderer
value={formValue}
variableTree={variableTree}
className={classNames(
className,
styles['editor-render'],
styles['editor-render-cm-content'],
styles[`editor-render-cmMinRows-${minRows}`],
{
[styles['editor-render-bottom-padding']]:
!disableCounter || overflow,
},
)}
readonly={readonly}
placeholder={placeholder}
dataTestID={dataTestID}
onChange={v => {
onChangeTrigger === 'onBlur' ? setCurEditorVal(v) : onChange?.(v);
}}
/>
{readonly || disableSuggestion ? null : (
<Expression.Popover
variableTree={variableTree}
getPopupContainer={() =>
shouldUseContainerRef
? (containerRef.current ?? document.body)
: document.body
}
onVisibilityChange={handlePopoverVisibilityChange}
/>
)}
</Expression.EditorProvider>
<ExpressionEditorCounter
model={model}
maxLength={maxLength}
disabled={disableCounter && !overflow}
isError={overflow}
/>
</div>
);
};

View File

@@ -0,0 +1,65 @@
/*
* 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 { useMemo } from 'react';
import {
type ExpressionEditorTreeNode,
ExpressionEditorTreeHelper,
} from '@coze-workflow/components';
import { useWorkflowNode } from '@coze-workflow/base';
import { convertInputs } from '@/form-extensions/setters/expression-editor/utils/convert-inputs';
import { useNodeAvailableVariablesWithNode } from '@/form-extensions/hooks';
const useInputs = (): {
name: string;
keyPath?: string[];
}[] => {
const workflowNode = useWorkflowNode();
const inputs = workflowNode?.inputParameters ?? [];
return convertInputs(inputs);
};
export const useVariableTree = (): ExpressionEditorTreeNode[] => {
const variables = useNodeAvailableVariablesWithNode();
const inputs = useInputs();
const availableVariables = ExpressionEditorTreeHelper.findAvailableVariables({
variables,
inputs,
});
const variableTree =
ExpressionEditorTreeHelper.createVariableTree(availableVariables);
return variableTree;
};
export const useParseText = (
text?: string | (() => string),
): string | undefined =>
useMemo((): string | undefined => {
if (!text) {
return;
}
if (typeof text === 'string') {
return text;
}
if (typeof text === 'function') {
return text();
}
return;
}, [text]);

View File

@@ -0,0 +1,32 @@
/*
* 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 { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { type ComponentProps } from '@/nodes-v2/components/types';
import {
ExpressionEditorContainer,
type ExpressionEditorContainerProps,
} from './container';
export type ExpressionEditorProps = ComponentProps<string> &
ExpressionEditorContainerProps;
export const ExpressionEditor = (props: ExpressionEditorProps) => {
const readonly = useReadonly();
return <ExpressionEditorContainer {...props} readonly={readonly} />;
};

View File

@@ -0,0 +1,37 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import type { Validate } from '@flowgram-adapter/free-layout-editor';
interface CreateExpressionEditorValidatorOptions {
maxLength?: number;
}
export const createExpressionEditorValidator =
(options?: CreateExpressionEditorValidatorOptions): Validate<string> =>
({ value }): string | undefined => {
if (!value) {
return I18n.t('workflow_detail_node_error_empty');
}
const { maxLength } = options || {};
if (maxLength && value.length > maxLength) {
return I18n.t('workflow_derail_node_detail_title_max', {
max: maxLength,
});
}
};

View File

@@ -0,0 +1,130 @@
/*
* 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 {
Field,
FieldArray,
type FieldArrayRenderProps,
type FieldRenderProps,
} from '@flowgram-adapter/free-layout-editor';
import { variableUtils } from '@coze-workflow/variable';
import {
ViewVariableType,
type InputValueVO,
type ValueExpression,
type VariableTypeDTO,
} from '@coze-workflow/base';
import { ValueExpressionInput } from '@/nodes-v2/components/value-expression-input';
import { FormItemFeedback } from '@/nodes-v2/components/form-item-feedback';
import { FormCard } from '@/form-extensions/components/form-card';
import { ColumnsTitle } from '@/form-extensions/components/columns-title';
import InputLabel from '../input-label';
export interface FixedInputParametersProps {
fieldName?: string;
defaultValue?: InputValueVO[];
headerTitle: string;
headerTootip: string;
columns?: {
title: string;
style: React.CSSProperties;
}[];
fieldConfig?: Record<
string,
{
description: string;
name: string;
required: boolean;
type: string;
optionsList?: {
label: string;
value: string;
}[];
}
>;
readonly?: boolean;
}
const FixedInputParameters = (props: FixedInputParametersProps) => {
const {
fieldName = 'inputParameters',
defaultValue = [],
headerTitle,
headerTootip,
columns,
fieldConfig = {},
readonly = false,
} = props;
return (
<FieldArray name={fieldName} defaultValue={defaultValue}>
{({ field: _field }: FieldArrayRenderProps<InputValueVO>) => (
<>
<FormCard header={headerTitle} tooltip={headerTootip}>
<div className="pb-[8px]">
<ColumnsTitle columns={columns ?? []} />
</div>
{Object.keys(fieldConfig).map((_fieldName, index) => (
<div
key={_fieldName}
className="array-item-wrapper flex items-start pb-[8px]"
>
<div className={'w-[140px]'}>
<InputLabel
required={fieldConfig[_fieldName]?.required}
label={fieldConfig[_fieldName]?.name}
tooltip={fieldConfig[_fieldName]?.description}
tootipIconClassName="coz-fg-secondary"
tag={null}
/>
</div>
<Field name={`inputParameters.${index}.input`}>
{({
field: childField,
fieldState: childState,
}: FieldRenderProps<ValueExpression | undefined>) => (
<div className="flex-1 min-w-0">
<ValueExpressionInput
{...childField}
readonly={readonly}
disabledTypes={ViewVariableType.getComplement([
variableUtils.DTOTypeToViewType(
fieldConfig[_fieldName].type as VariableTypeDTO,
),
])}
literalConfig={{
// 下拉选择数据源
optionsList: fieldConfig[_fieldName]?.optionsList,
}}
isError={!!childState?.errors?.length}
/>
<FormItemFeedback errors={childState?.errors} />
</div>
)}
</Field>
</div>
))}
</FormCard>
</>
)}
</FieldArray>
);
};
export default FixedInputParameters;

View File

@@ -0,0 +1,7 @@
.form-item-error {
font-family: "SF Pro Display",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: var(--coze-12);
line-height: 16px;
color: var(--semi-color-danger)
}

View File

@@ -0,0 +1,47 @@
/*
* 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 from 'react';
import classnames from 'classnames';
import {
type FieldState,
type FieldError,
type FieldWarning,
} from '@flowgram-adapter/free-layout-editor';
import { type WithCustomStyle } from '@coze-workflow/base/types';
import s from './index.module.less';
export interface FormItemErrorProps extends WithCustomStyle {
errors?: FieldState['errors'];
// coze 暂无warnings
// warnings?: FieldState['warnings'];
}
export const FormItemFeedback = ({
errors,
className,
style,
}: FormItemErrorProps) => {
const renderFeedbacks = (fs: FieldError[] | FieldWarning[]) =>
fs.map(f => <span key={f.field}>{f.message}</span>);
return errors ? (
<div className={classnames(s.formItemError, className)} style={style}>
{renderFeedbacks(errors)}
</div>
) : null;
};

View File

@@ -0,0 +1,37 @@
.global-var-option {
:global .option-text-wrapper {
overflow: hidden;
}
.tag {
height: 16px;
margin-left: 8px;
padding: 1px 4px;
font-size: 12px;
font-weight: 500;
line-height: 14px;
color: rgba(6, 7, 9, 80%);
background: rgba(6, 7, 9, 8%);
}
}
.empty-block {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 40px 0;
.text {
margin-top: 10px;
font-size: 12px;
line-height: 16px;
color: rgba(52, 60, 87, 72%);
text-align: center;
}
}

View File

@@ -0,0 +1,314 @@
/*
* 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, {
type CSSProperties,
useEffect,
useMemo,
useCallback,
useRef,
} from 'react';
import { get } from 'lodash-es';
import { useService } from '@flowgram-adapter/free-layout-editor';
import {
type RefExpression,
WorkflowVariableService,
useGlobalVariableServiceState,
isGlobalVariableKey,
} from '@coze-workflow/variable';
import {
type ViewVariableType,
ValueExpressionType,
VARIABLE_TYPE_ALIAS_MAP,
} from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { type Select as SemiSelect } from '@coze-arch/bot-semi';
import {
IconCozCross,
IconCozEmpty,
IconCozWarningCircle,
} from '@coze-arch/coze-design/icons';
import { Select, Tag, Typography } from '@coze-arch/coze-design';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { useGlobalState } from '@/hooks';
import { type VariableMetaWithNode } from '@/form-extensions/typings';
import { useNodeAvailableVariablesWithNode } from '@/form-extensions/hooks';
import { BotProjectVariableSelect } from '@/components/test-run/bot-project-variable-select';
import { VARIABLE_TYPE_ICON_MAP } from '@/components/node-render/node-render-new/fields/constants';
import s from './index.module.less';
const { Text } = Typography;
interface Props {
value?: RefExpression;
onChange?: (value?: RefExpression) => void;
disabled?: boolean;
readonly?: boolean;
style?: CSSProperties;
onBlur?: () => void;
name?: string;
placeholder?: string;
disabledTypes?: ViewVariableType[];
isError?: boolean;
variablesFilter?: (
variables: VariableMetaWithNode[],
) => VariableMetaWithNode[];
useMatchType?: boolean;
matchType?: ViewVariableType;
}
type ValueType = string[];
const encodeValue = (data?: ValueType) =>
data ? JSON.stringify(data) : undefined;
const decodeValue = (data: string) =>
data ? (JSON.parse(data) as ValueType) : undefined;
const getIconByType = (type?: ViewVariableType) =>
type ? (
VARIABLE_TYPE_ICON_MAP[type] || <IconCozWarningCircle />
) : (
<IconCozWarningCircle />
);
export const GlobalVariableSelect = (props: Props) => {
const {
value,
onChange,
onBlur,
readonly: setterReadonly,
placeholder = I18n.t('variable_assignment_node_select_placeholder'),
disabledTypes,
isError,
variablesFilter = data => data,
matchType,
useMatchType,
} = props;
const selectRef = useRef<SemiSelect | null>(null);
const globalReadonly = useReadonly();
const readonly = globalReadonly || setterReadonly;
const { projectId } = useGlobalState();
const { type } = useGlobalVariableServiceState();
const isProject = Boolean(projectId);
const isSelectedBotOrProject = !isProject && Boolean(type);
const useNewGlobalVariableCache = !isProject;
const availableVariables = useNodeAvailableVariablesWithNode();
const variableService: WorkflowVariableService = useService(
WorkflowVariableService,
);
const keyPath = get(value, 'content.keyPath') as string[] | undefined;
// 监听联动变量变化,从而重新触发 effect
useEffect(() => {
const hasDisabledTypes =
Array.isArray(disabledTypes) && disabledTypes.length > 0;
if (!keyPath || !hasDisabledTypes) {
return;
}
const listener = variableService.onListenVariableTypeChange(
keyPath,
v => {
// 如果变量类型变化后,位于 disabledTypes 中,那么需要清空
if (v && (disabledTypes || []).includes(v.type)) {
onChange?.({
type: ValueExpressionType.REF,
});
}
},
{},
);
return () => {
listener?.dispose();
};
}, [keyPath, variableService, onChange, disabledTypes]);
const optionList = useMemo(
() =>
variablesFilter(
availableVariables.filter(
item => item.nodeId && isGlobalVariableKey(item.nodeId),
),
).map(item => ({
value: encodeValue([item.nodeId as string, item.key]),
disabled: useMatchType && matchType && matchType !== item.type,
...item,
})),
[availableVariables, variablesFilter, matchType, useMatchType],
);
const handleChange = useCallback(
(v): void => {
const data = v ? decodeValue(v) : undefined;
if (data === undefined) {
onChange?.(undefined);
} else {
onChange?.({
type: ValueExpressionType.REF,
content: { keyPath: data },
});
}
},
[onChange],
);
const emptyContent = useMemo(() => {
const getEmptyMsg = () => {
if (isProject) {
return I18n.t('variable_select_empty_appide_tips');
}
if (isSelectedBotOrProject) {
return I18n.t('variable_select_empty_library_tips_02');
}
return I18n.t('variable_select_empty_library_tips');
};
return (
<div className={s['empty-block']}>
<IconCozEmpty
style={{ fontSize: '32px', color: 'rgba(52, 60, 87, 0.72)' }}
/>
<span className={s.text}>{getEmptyMsg()}</span>
</div>
);
}, [isProject, isSelectedBotOrProject]);
const handleVariableSelect = useCallback(
(v?: string) => {
handleChange(v);
selectRef.current?.close();
},
[handleChange],
);
return (
<Select
ref={selectRef}
hasError={isError}
dropdownStyle={{
width: useNewGlobalVariableCache ? '326px' : '228px',
}}
showClear
size={'small'}
disabled={readonly}
clearIcon={<IconCozCross style={{ fontSize: '12px' }} />}
emptyContent={useNewGlobalVariableCache ? null : emptyContent}
value={encodeValue(value?.content?.keyPath)}
onChange={handleChange}
onBlur={onBlur}
style={{
width: '100%',
}}
placeholder={placeholder}
renderSelectedItem={item => {
const selectedItem = optionList.find(op => op.value === item.value);
return (
<div className={'flex items-center'}>
<span
className={'flex items-center mr-4px'}
style={{
fontSize: '14px',
color: selectedItem?.name
? 'rgba(var(--coze-fg-1),var(--coze-fg-1-alpha))'
: 'rgba(var(--coze-yellow-5), 1)',
}}
>
{getIconByType(selectedItem?.type)}
</span>
<span
style={{
color: selectedItem?.name
? 'rgba(var(--coze-fg-3),var(--coze-fg-3-alpha))'
: 'rgba(var(--coze-yellow-5), 1)',
}}
>
{selectedItem?.name || I18n.t('workflow_variable_undefined')}
</span>
</div>
);
}}
outerBottomSlot={
useNewGlobalVariableCache ? (
<BotProjectVariableSelect
onVariableSelect={handleVariableSelect}
variableValue={encodeValue(value?.content?.keyPath)}
variablesFormatter={arr =>
variablesFilter(
arr.map(item => ({
value: encodeValue([item.nodeId as string, item.key]),
disabled:
useMatchType && matchType && matchType !== item.type,
...item,
})),
)
}
/>
) : undefined
}
>
{useNewGlobalVariableCache
? null
: optionList.map(option => (
<Select.Option
// disabled 变化 Option 不会重新渲染,先通过设置 key 的方式兼容一下
key={`${option.value}-${option.disabled}`}
value={option.value}
className={s['global-var-option']}
disabled={option.disabled}
>
<div
className={
'flex w-full items-center justify-between pl-8px pr-8px'
}
>
<Text
disabled={option.disabled}
ellipsis={{ showTooltip: true }}
style={{ flex: 1 }}
>
{option.name}
</Text>
<Tag
disabled={option.disabled}
className={s.tag}
size={'small'}
>
{VARIABLE_TYPE_ALIAS_MAP[option.type]}
</Tag>
</div>
</Select.Option>
))}
</Select>
);
};

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 const nameValidationRule =
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/;

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 { type ReactNode, type CSSProperties } from 'react';
import classNames from 'classnames';
import { IconInfo } from '@coze-arch/bot-icons';
import AutoSizeTooltip from '@/ui-components/auto-size-tooltip';
export interface InputLabelProps {
required?: boolean;
hideRequiredTag?: boolean;
label: ReactNode;
tooltip?: ReactNode;
tag?: ReactNode;
className?: string;
labelStyle?: CSSProperties;
labelClassName?: string;
tootipPopoverClassName?: string;
tootipIconClassName?: string;
}
const InputLabel = ({
required,
hideRequiredTag = false,
label,
tooltip,
tag,
labelStyle = {
fontSize: 12,
marginRight: 0,
},
className,
labelClassName,
tootipPopoverClassName,
tootipIconClassName,
}: InputLabelProps) => (
<div className={classNames('flex mr-2 items-baseline', className)}>
<div className="flex overflow-hidden">
<AutoSizeTooltip
content={label}
showArrow
position="top"
className="flex-1 grow-1 truncate"
>
<span
className={classNames('flex-1 grow-1 truncate', labelClassName)}
style={labelStyle}
>
{label}
</span>
</AutoSizeTooltip>
{required && !hideRequiredTag ? (
<span
style={{ color: 'var(--light-usage-danger-color-danger,#f93920)' }}
>
*
</span>
) : null}
{tooltip ? (
<div className="ml-[4px] mt-[2px]">
<AutoSizeTooltip
showArrow
position="top"
className={tootipPopoverClassName}
content={tooltip}
>
<IconInfo className={tootipIconClassName} />
</AutoSizeTooltip>
</div>
) : null}
</div>
{tag ? <div className="flex-1 shrink-0 grow-1">{tag}</div> : null}
</div>
);
export default InputLabel;

View File

@@ -0,0 +1,98 @@
/*
* 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.
*/
/**
* 组件迁移自 packages/workflow/playground/src/form-extensions/setters/node-header
* 仅做了对新版节点引擎接口适配
*/
import React from 'react';
import { type FieldError } from '@flowgram-adapter/free-layout-editor';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { type ComponentProps } from '@/nodes-v2/components/types';
import { useGlobalState, useNodeRenderScene } from '@/hooks';
import { NodeHeader as NodeHeaderComponent } from '@/form-extensions/components/node-header';
import { withValidation } from '../validation';
export interface NodeHeaderValue {
title: string;
icon: string;
subTitle: string;
description: string;
}
export type NodeHeaderProps = ComponentProps<NodeHeaderValue> & {
errors?: FieldError[];
readonly?: boolean;
hideTest?: boolean;
batchModePath?: string;
outputsPath?: string;
extraOperation?: React.ReactNode;
showTrigger?: boolean;
triggerIsOpen?: boolean;
nodeDisabled?: boolean;
readonlyAllowDeleteOperation?: boolean;
};
export const NodeHeader = withValidation<NodeHeaderProps>(
({
value,
onChange,
onBlur,
readonly = false,
hideTest = false,
extraOperation,
showTrigger = false,
triggerIsOpen = false,
nodeDisabled,
readonlyAllowDeleteOperation,
}: NodeHeaderProps) => {
const { title, icon, subTitle, description } = value || {};
const workflowReadonly = useReadonly();
const { projectId, projectCommitVersion } = useGlobalState();
const { isNodeSideSheet } = useNodeRenderScene();
return (
<NodeHeaderComponent
title={title}
subTitle={subTitle}
// 如果是coze2.0新版节点渲染 隐藏掉描述
description={description}
logo={icon}
onTitleChange={newTitle => {
onChange({ ...value, title: newTitle });
onBlur?.();
}}
onDescriptionChange={desc => {
onChange({ ...value, description: desc });
}}
readonly={readonly || workflowReadonly}
readonlyAllowDeleteOperation={
workflowReadonly ? false : readonlyAllowDeleteOperation
}
hideTest={
hideTest || IS_BOT_OP || !!(projectId && projectCommitVersion)
}
showTrigger={showTrigger}
triggerIsOpen={triggerIsOpen}
extraOperation={extraOperation}
showCloseButton={isNodeSideSheet}
nodeDisabled={nodeDisabled}
/>
);
},
);

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 { NodeInputName } from './node-input-name';

View File

@@ -0,0 +1,169 @@
/*
* 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, { useCallback, useEffect, useState } from 'react';
import {
useCurrentEntity,
useService,
} from '@flowgram-adapter/free-layout-editor';
import { WorkflowVariableFacadeService } from '@coze-workflow/variable';
import { useNodeTestId } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
import { Tooltip, Input, Typography } from '@coze-arch/coze-design';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import {
getVariableName,
getUniqueName,
} from '@/form-extensions/setters/node-input-name/utils';
import type { NodeInputNameProps } from './type';
// eslint-disable-next-line complexity
export const NodeInputName = ({
value,
onChange,
onBlur,
name,
style,
input,
inputParameters,
initValidate = false,
isPureText = false,
prefix = '',
suffix = '',
format,
tooltip,
isError,
inputPrefix,
disabled,
}: NodeInputNameProps) => {
const [initialized, setInitialized] = useState<boolean>(false);
const [userEdited, setUserEdited] = useState<boolean>(false);
const [variableName, setVariableName] = useState<string | undefined>(value);
const [text, setText] = useState<string | undefined>(value);
const readonly = useReadonly();
const node = useCurrentEntity();
const variableService = useService(WorkflowVariableFacadeService);
const { getNodeSetterId } = useNodeTestId();
// text 状态受控(删除节点时联动 text 的值)
useEffect(() => {
if (value !== text) {
setText(value);
}
}, [value]);
const computedVariableName = getVariableName({
input,
prefix,
suffix,
format,
node,
variableService,
});
const onInputChange = useCallback((newInputValue: string): void => {
setUserEdited(true);
setText(newInputValue || '');
setVariableName(undefined);
}, []);
const handleOnBlur = () => {
onChange(text || '');
onBlur?.();
};
useEffect(() => {
if (initValidate) {
// 初始化写值触发校验
onChange(value);
onBlur?.();
}
if (value) {
setUserEdited(true);
}
setInitialized(true);
}, [initValidate, onChange, value]);
if (initialized && !readonly && !userEdited) {
if (computedVariableName && computedVariableName !== variableName) {
const computedUniqueName = getUniqueName({
variableName: computedVariableName,
inputParameters,
});
onChange(computedUniqueName);
setVariableName(computedVariableName);
setText(computedUniqueName);
} else if (!computedVariableName && variableName) {
setVariableName(undefined);
setText(undefined);
}
}
return (
<div
className="flex items-center"
style={{
...style,
pointerEvents: readonly ? 'none' : 'auto',
}}
>
{isPureText ? (
<>
<Typography.Text className="h-8 leading-8">{value}</Typography.Text>
{tooltip ? (
<Tooltip content={tooltip}>
<IconCozInfoCircle
className="ml-1"
style={{
fontSize: 12,
}}
/>
</Tooltip>
) : null}
</>
) : (
<>
<Input
size={'small'}
data-testid={getNodeSetterId(name)}
value={text}
onChange={onInputChange}
onBlur={handleOnBlur}
validateStatus={isError ? 'error' : undefined}
placeholder={I18n.t('workflow_detail_node_input_entername')}
prefix={inputPrefix}
disabled={disabled}
/>
{tooltip ? (
<Tooltip content={tooltip}>
<IconCozInfoCircle
className="ml-1"
style={{
fontSize: 12,
}}
/>
</Tooltip>
) : null}
</>
)}
</div>
);
};

View File

@@ -0,0 +1,54 @@
/*
* 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 { CSSProperties } from 'react';
import type { InputValueVO, RefExpression } from '@coze-workflow/base';
import { type InputProps } from '@coze-arch/coze-design';
import { type ComponentProps } from '@/nodes-v2/components/types';
export type NodeInputNameFormat = (params: {
name: string;
prefix: string;
suffix: string;
input: RefExpression;
// context: SetterOrDecoratorContext;
}) => string;
export type NodeInputNameProps = Omit<
ComponentProps<string>,
'inputParameters'
> & {
readonly?: boolean;
initValidate?: boolean;
isPureText?: boolean;
style?: CSSProperties;
/** 同一层的变量表达式 */
input: RefExpression;
/** 当前输入列表中所有输入项 */
inputParameters: Array<InputValueVO>;
/** 前缀 */
prefix?: string;
/** 后缀 */
suffix?: string;
/** 名称自定义格式化 */
format?: NodeInputNameFormat;
tooltip?: string;
isError?: boolean;
inputPrefix?: InputProps['prefix'];
disabled?: boolean;
};

View File

@@ -0,0 +1,70 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { type Validate } from '@flowgram-adapter/free-layout-editor';
import { nameValidationRule } from '../helpers';
export interface CreateNodeInputNameValidateOptions {
getNames?: ({ value, formValues }) => string[];
validatorConfig?: {
rule?: RegExp;
errorMessage?: string;
};
invalidValues?: Record<string, string>;
skipValidate?: ({ value, formValues }) => boolean;
}
const defaultGetNames = ({ formValues }) =>
formValues.inputParameters.map(item => item.name);
export const createNodeInputNameValidate =
(options?: CreateNodeInputNameValidateOptions): Validate =>
({ value, formValues }) => {
const {
getNames = defaultGetNames,
validatorConfig,
invalidValues,
skipValidate,
} = options || {};
if (skipValidate?.({ value, formValues })) {
return;
}
const validatorRule = validatorConfig?.rule ?? nameValidationRule;
const validatorErrorMessage =
validatorConfig?.errorMessage ??
I18n.t('workflow_detail_node_error_format');
/** 命名校验 */
if (!validatorRule.test(value)) {
return validatorErrorMessage;
}
/** 非法值校验 */
if (invalidValues?.[value]) {
return invalidValues[value];
}
const names: string[] = getNames({ value, formValues });
const foundSames = names.filter((name: string) => name === value);
return foundSames.length > 1
? I18n.t('workflow_detail_node_input_duplicated')
: undefined;
};

View File

@@ -0,0 +1,64 @@
/*
* 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 {
Field,
type FieldRenderProps,
useCurrentEntity,
} from '@flowgram-adapter/free-layout-editor';
import { WorkflowNode } from '@coze-workflow/base';
import { useDefaultNodeMeta } from '@/nodes-v2/hooks/use-default-node-meta';
import { type NodeHeaderValue } from '@/nodes-v2/components/node-header';
import { NodeHeader } from '@/nodes-v2/components/node-header';
interface NodeMetaProps {
fieldName?: string;
deps?: string[];
outputsPath?: string;
batchModePath?: string;
}
const NodeMeta = ({
fieldName = 'nodeMeta',
deps,
outputsPath,
batchModePath,
}: NodeMetaProps) => {
const defaultNodeMeta = useDefaultNodeMeta();
const node = useCurrentEntity();
const wrappedNode = new WorkflowNode(node);
return (
<Field
name={fieldName}
deps={deps}
defaultValue={defaultNodeMeta as unknown as NodeHeaderValue}
>
{({ field, fieldState }: FieldRenderProps<NodeHeaderValue>) => (
<NodeHeader
{...field}
outputsPath={outputsPath}
batchModePath={batchModePath}
hideTest={!!wrappedNode?.registry?.meta?.hideTest}
errors={fieldState?.errors || []}
/>
)}
</Field>
);
};
export default NodeMeta;

View File

@@ -0,0 +1,19 @@
.content {
display: flex;
align-items: center;
padding: 10px 0;
font-size: 12px;
.tag {
height: 16px;
margin-left: 8px;
padding: 1px 4px;
font-size: 12px;
line-height: 14px;
color: rgba(6, 7, 9, 80%);
background: rgba(6, 7, 9, 8%);
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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, { type CSSProperties } from 'react';
import { Tag } from '@coze-arch/coze-design';
import styles from './index.module.less';
interface Props {
label?: string;
type?: string;
required?: boolean;
style?: CSSProperties;
}
export const OutputSingleText = ({ label, type, required, style }: Props) => (
<p className={styles.content} style={style}>
<span>{label}</span>
{required ? <span style={{ color: '#f93920' }}>*</span> : null}
{type ? <Tag className={styles.tag}>{type}</Tag> : null}
</p>
);

View File

@@ -0,0 +1,203 @@
/*
* 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.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useMemo, useRef } from 'react';
import { useForm, useRefresh } from '@flowgram-adapter/free-layout-editor';
import { useService } from '@flowgram-adapter/free-layout-editor';
import { WorkflowBatchService } from '@coze-workflow/variable';
import { sortErrorBody, useIsSettingOnErrorV2 } from '@coze-workflow/nodes';
import { HistoryService } from '@coze-workflow/history';
import { BatchMode, type ViewVariableTreeNode } from '@coze-workflow/base';
import { useNodeTestId } from '@coze-workflow/base';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { type ValidationProps } from '@/nodes-v2/components/validation/with-validation';
import { type ComponentProps } from '@/nodes-v2/components/types';
import {
OutputTree,
type OutputTreeProps,
} from '@/form-extensions/components/output-tree';
import { withValidation } from '../validation';
type OutputTreeValue = Array<ViewVariableTreeNode> | undefined;
type SortValue = (
value?: ViewVariableTreeNode[] | undefined,
isBatch?: boolean,
) => ViewVariableTreeNode[] | undefined;
interface OutputTreeOptions {
id: string;
batchMode?: BatchMode;
withDescription?: boolean;
withRequired?: boolean;
disabled?: boolean;
disabledTooltip?: string;
readonly?: boolean;
topLevelReadonly?: boolean;
allowDeleteLast?: boolean;
emptyPlaceholder?: string;
showResponseFormat?: boolean;
needErrorBody?: boolean;
hide?: boolean;
defaultCollapse?: boolean;
needAppendChildWhenNodeIsPreset?: boolean;
noCard?: boolean;
sortValue?: SortValue; // 排序函数
}
type OutputsProps = ComponentProps<OutputTreeValue> &
OutputTreeOptions &
Pick<
OutputTreeProps,
| 'hiddenTypes'
| 'addItemTitle'
| 'withDefaultValue'
| 'defaultExpandParams'
| 'columnsRatio'
| 'maxLimit'
> &
ValidationProps;
export const Outputs = withValidation<OutputsProps>((props: OutputsProps) => {
const {
name,
value,
onChange,
id,
disabled = false,
batchMode,
withDescription = false,
withRequired = false,
readonly = false,
topLevelReadonly = false,
allowDeleteLast = false,
emptyPlaceholder,
hiddenTypes,
showResponseFormat = false,
needErrorBody = false,
hide = false,
defaultCollapse,
needAppendChildWhenNodeIsPreset,
noCard,
sortValue,
} = props || {};
const form = useForm();
const workflowReadonly = useReadonly();
const isBatch = batchMode === BatchMode.Batch;
const historyService = useService<HistoryService>(HistoryService);
const refresh = useRefresh();
const isSettingOnErrorV2 = useIsSettingOnErrorV2();
// 记录当前batchMode
const curBatchMode = useRef<BatchMode>();
// 变更不记录历史
const onChangeWithoutHistory = (outputTreeValue: OutputTreeValue) => {
historyService.stop();
onChange(outputTreeValue);
historyService.start();
};
useEffect(() => {
/**
* 初始化时赋值curBatchMode但无需对值做修改
*/
if (!curBatchMode.current) {
curBatchMode.current = batchMode;
return;
}
/**
* 切换batch mode 需要将 single mode 的output 包成list, 或将 batch mode 的outputList 去掉list这层
*/
if (batchMode !== curBatchMode.current) {
curBatchMode.current = batchMode;
if (batchMode === BatchMode.Batch) {
onChangeWithoutHistory(
WorkflowBatchService.singleOutputMetasToList(value),
);
return;
}
if (batchMode === BatchMode.Single) {
onChangeWithoutHistory(
WorkflowBatchService.listOutputMetasToSingle(value),
);
return;
}
}
}, [batchMode, onChange]);
const { getNodeSetterId } = useNodeTestId();
// 要对 value 排序,保证 errorbody 这个属性在最下面
const _value = useMemo(() => {
const sortedValue = sortValue ? sortValue(value, isBatch) : value;
if (needErrorBody) {
return sortErrorBody({
value: sortedValue as ViewVariableTreeNode[],
isBatch,
isSettingOnErrorV2,
});
}
return sortedValue;
}, [value, needErrorBody, isBatch, isSettingOnErrorV2]);
if (hide) {
return null;
}
return (
<OutputTree
{...props}
id={id}
testId={getNodeSetterId(name)}
responseFormat={{
visible: showResponseFormat,
value: form.getValueIn('model.responseFormat'),
readonly: needErrorBody,
onChange: v => {
const model = form.getValueIn('model');
if (model) {
form.setValueIn('model', {
...model,
responseFormat: v,
});
}
refresh();
},
}}
readonly={readonly || workflowReadonly}
disabled={disabled}
value={_value as any}
onChange={onChange as any}
isBatch={isBatch}
withDescription={withDescription}
withRequired={withRequired}
topLevelReadonly={topLevelReadonly}
allowDeleteLast={allowDeleteLast}
emptyPlaceholder={emptyPlaceholder}
hiddenTypes={hiddenTypes}
defaultCollapse={defaultCollapse}
needAppendChildWhenNodeIsPreset={needAppendChildWhenNodeIsPreset}
noCard={noCard}
/>
);
});

View File

@@ -0,0 +1,14 @@
/* stylelint-disable selector-class-pattern */
.workflow-node-setter-radio {
&:global(.semi-radioGroup) {
display: flex;
}
:global(.semi-radio) {
flex: 1;
}
:global(.semi-radio-content) {
width: 100%;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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 FC, useMemo } from 'react';
import classNames from 'classnames';
import { useNodeTestId } from '@coze-workflow/base';
import type {
OptionItem,
RadioChangeEvent,
RadioType,
} from '@coze-arch/bot-semi/Radio';
import { Radio as RadioUI, RadioGroup } from '@coze-arch/bot-semi';
import { type ComponentProps } from '@/nodes-v2/components/types';
import { useReadonly } from '../../hooks/use-readonly';
import styles from './index.module.less';
type RadioItem = OptionItem & {
disabled?: boolean;
};
type RadioProps = ComponentProps<string> & {
name: string;
mode: RadioType;
options: RadioItem[];
};
export const Radio: FC<RadioProps> = props => {
const { value, onChange, options = [], mode, name } = props;
const { getNodeSetterId, concatTestId } = useNodeTestId();
const readonly = useReadonly();
const uiOptions = useMemo(
() =>
options.map(item => (
<RadioUI
className={classNames({
'border-[#1C1F23]/[8%]': mode === 'card' && item.value !== value,
'bg-[--semi-color-bg-0]': mode === 'card' && item.value !== value,
})}
key={item.value}
value={item.value}
disabled={item.disabled}
data-testid={concatTestId(getNodeSetterId(name), `${item.value}`)}
>
{item.label}
</RadioUI>
)),
[options, mode, value, concatTestId, getNodeSetterId, name],
);
return (
<RadioGroup
style={{
pointerEvents: readonly ? 'none' : 'auto',
}}
className={styles.workflowNodeSetterRadio}
type={mode}
value={value}
onChange={onChange as unknown as (event: RadioChangeEvent) => void}
>
{uiOptions}
</RadioGroup>
);
};

View File

@@ -0,0 +1,51 @@
/*
* 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 {
Field,
type FieldRenderProps,
} from '@flowgram-adapter/free-layout-editor';
import { type SettingOnErrorValue } from '@coze-workflow/nodes';
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
import { SettingOnError as SettingOnErrorComp } from '@/form-extensions/components/setting-on-error';
interface Props {
fieldName?: string;
batchModePath?: string;
outputsPath?: string;
}
export const SettingOnError = ({
fieldName = 'settingOnError',
batchModePath,
outputsPath,
}: Props) => {
const readonly = useReadonly();
return (
<Field name={fieldName}>
{({ field }: FieldRenderProps<SettingOnErrorValue>) => (
<SettingOnErrorComp
{...field}
batchModePath={batchModePath}
outputsPath={outputsPath}
readonly={readonly}
/>
)}
</Field>
);
};

View File

@@ -0,0 +1,3 @@
.spacing {
margin-bottom: 8px;
}

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.
*/
import React from 'react';
import s from './index.module.less';
export const Spacing = () => <div className={s.spacing} />;

Some files were not shown because too many files have changed in this diff Show More