feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* 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 mockLLMModels = {
|
||||
code: 0,
|
||||
msg: 'success',
|
||||
data: {
|
||||
model_list: [
|
||||
{
|
||||
name: '豆包·1.5·Pro·32k',
|
||||
model_type: 1737521813,
|
||||
model_class: 2,
|
||||
model_icon: 'doubao_v2.png',
|
||||
model_input_price: 0,
|
||||
model_output_price: 0,
|
||||
model_quota: {
|
||||
token_limit: 32768,
|
||||
token_resp: 4096,
|
||||
token_system: 0,
|
||||
token_user_in: 32768,
|
||||
token_tools_in: 0,
|
||||
token_tools_out: 0,
|
||||
token_data: 0,
|
||||
token_history: 0,
|
||||
token_cut_switch: false,
|
||||
price_in: 0,
|
||||
price_out: 0,
|
||||
},
|
||||
model_name: 'ep-20250122125445-ck9wp',
|
||||
model_class_name: '豆包系列模型',
|
||||
is_offline: false,
|
||||
model_params: [
|
||||
{
|
||||
name: 'temperature',
|
||||
label: '生成随机性',
|
||||
desc: '- **temperature**: 调高温度会使得模型的输出更多样性和创新性,反之,降低温度会使输出内容更加遵循指令要求但减少多样性。建议不要与“Top p”同时调整。',
|
||||
type: 1,
|
||||
min: '0',
|
||||
max: '1',
|
||||
precision: 1,
|
||||
default_val: {
|
||||
default_val: '1',
|
||||
creative: '1',
|
||||
balance: '0.8',
|
||||
precise: '0.3',
|
||||
},
|
||||
options: [],
|
||||
param_class: {
|
||||
class_id: 1,
|
||||
label: '生成多样性',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: '最大回复长度',
|
||||
desc: '控制模型输出的Tokens 长度上限。通常 100 Tokens 约等于 150 个中文汉字。',
|
||||
type: 2,
|
||||
min: '1',
|
||||
max: '12288',
|
||||
precision: 0,
|
||||
default_val: {
|
||||
default_val: '4096',
|
||||
},
|
||||
options: [],
|
||||
param_class: {
|
||||
class_id: 2,
|
||||
label: '输入及输出设置',
|
||||
},
|
||||
},
|
||||
],
|
||||
model_desc: [
|
||||
{
|
||||
group_name: '### 功能特点:',
|
||||
desc: [
|
||||
'- 支持Function calling能力(提供更准确、稳定的工具调用能力)',
|
||||
'- 输入的长度支持最长32768个Tokens(约49152个中文字符)',
|
||||
'- 节点ID:ep-20250122125445-ck9wp',
|
||||
],
|
||||
},
|
||||
],
|
||||
model_tag_list: [
|
||||
{
|
||||
tag_name: '文本模型',
|
||||
tag_class: 1,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
{
|
||||
tag_name: '旗舰',
|
||||
tag_class: 3,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
{
|
||||
tag_name: '工具调用',
|
||||
tag_class: 16,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
],
|
||||
is_up_required: false,
|
||||
model_brief_desc:
|
||||
'Doubao-1.5-pro-32k,全新一代主力模型,性能全面升级,在知识、代码、推理、等方面表现卓越。支持32k上下文窗口,输出长度支持最大12k tokens。',
|
||||
model_series: {
|
||||
series_name: '热门模型',
|
||||
icon_url: 'doubao_v2.png',
|
||||
model_vendor: '扣子',
|
||||
},
|
||||
model_status_details: {
|
||||
is_new_model: false,
|
||||
is_advanced_model: false,
|
||||
is_free_model: false,
|
||||
is_upcoming_deprecated: false,
|
||||
deprecated_date: '',
|
||||
replace_model_name: '',
|
||||
update_info: '',
|
||||
model_feature: 1,
|
||||
},
|
||||
model_ability: {
|
||||
function_call: true,
|
||||
image_understanding: false,
|
||||
video_understanding: false,
|
||||
audio_understanding: false,
|
||||
},
|
||||
model_show_family_id: '7287388636726274',
|
||||
hot_flag: 1,
|
||||
hot_ranking: 100,
|
||||
online_time: 1737874220,
|
||||
offline_time: 0,
|
||||
},
|
||||
{
|
||||
name: '豆包·工具调用',
|
||||
model_type: 1706077826,
|
||||
model_class: 2,
|
||||
model_icon: 'doubao_v2.png',
|
||||
model_input_price: 0,
|
||||
model_output_price: 0,
|
||||
model_quota: {
|
||||
token_limit: 32768,
|
||||
token_resp: 4096,
|
||||
token_system: 0,
|
||||
token_user_in: 0,
|
||||
token_tools_in: 0,
|
||||
token_tools_out: 0,
|
||||
token_data: 0,
|
||||
token_history: 0,
|
||||
token_cut_switch: false,
|
||||
price_in: 0,
|
||||
price_out: 0,
|
||||
},
|
||||
model_name: 'ep-20250103114050-hgnz5',
|
||||
model_class_name: '豆包系列模型',
|
||||
is_offline: false,
|
||||
model_params: [
|
||||
{
|
||||
name: 'temperature',
|
||||
label: '生成随机性',
|
||||
desc: '- **temperature**: 调高温度会使得模型的输出更多样性和创新性,反之,降低温度会使输出内容更加遵循指令要求但减少多样性。建议不要与“Top p”同时调整。',
|
||||
type: 1,
|
||||
min: '0',
|
||||
max: '1',
|
||||
precision: 2,
|
||||
default_val: {
|
||||
default_val: '1',
|
||||
creative: '1',
|
||||
balance: '1',
|
||||
precise: '0.1',
|
||||
},
|
||||
options: [],
|
||||
param_class: {
|
||||
class_id: 1,
|
||||
label: '生成多样性',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'top_p',
|
||||
label: 'Top P',
|
||||
desc: '- **Top p 为累计概率**: 模型在生成输出时会从概率最高的词汇开始选择,直到这些词汇的总概率累积达到Top p 值。这样可以限制模型只选择这些高概率的词汇,从而控制输出内容的多样性。建议不要与“生成随机性”同时调整。',
|
||||
type: 1,
|
||||
min: '0',
|
||||
max: '1',
|
||||
precision: 2,
|
||||
default_val: {
|
||||
default_val: '0.7',
|
||||
creative: '0.8',
|
||||
balance: '0.7',
|
||||
precise: '0.7',
|
||||
},
|
||||
options: [],
|
||||
param_class: {
|
||||
class_id: 1,
|
||||
label: '生成多样性',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'response_format',
|
||||
label: '输出格式',
|
||||
desc: '- **文本**: 使用普通文本格式回复\n- **Markdown**: 将引导模型使用Markdown格式输出回复\n- **JSON**: 将引导模型使用JSON格式输出',
|
||||
type: 2,
|
||||
min: '',
|
||||
max: '',
|
||||
precision: 0,
|
||||
default_val: {
|
||||
default_val: '0',
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: '文本',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: 'Markdown',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
param_class: {
|
||||
class_id: 2,
|
||||
label: '输入及输出设置',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: '最大回复长度',
|
||||
desc: '控制模型输出的Tokens 长度上限。通常 100 Tokens 约等于 150 个中文汉字。',
|
||||
type: 2,
|
||||
min: '5',
|
||||
max: '4096',
|
||||
precision: 0,
|
||||
default_val: {
|
||||
default_val: '1024',
|
||||
},
|
||||
options: [],
|
||||
param_class: {
|
||||
class_id: 2,
|
||||
label: '输入及输出设置',
|
||||
},
|
||||
},
|
||||
],
|
||||
model_desc: [
|
||||
{
|
||||
group_name: '### 功能特点:',
|
||||
desc: [
|
||||
'- 支持Function calling能力(提供更准确、稳定的工具调用能力)',
|
||||
'- 节点ID:ep-20250103114050-hgnz5',
|
||||
],
|
||||
},
|
||||
],
|
||||
model_tag_list: [
|
||||
{
|
||||
tag_name: '文本模型',
|
||||
tag_class: 1,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
{
|
||||
tag_name: '工具调用',
|
||||
tag_class: 3,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
{
|
||||
tag_name: '支持微调',
|
||||
tag_class: 4,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
{
|
||||
tag_name: 'functionCall',
|
||||
tag_class: 4,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
{
|
||||
tag_name: '工具调用',
|
||||
tag_class: 16,
|
||||
tag_icon: '',
|
||||
tag_descriptions: '',
|
||||
},
|
||||
],
|
||||
is_up_required: false,
|
||||
model_brief_desc:
|
||||
'Doubao-pro-32k/241215,主力模型,适合处理复杂任务,在参考问答、总结摘要、创作、文本分类、角色扮演等场景都有很好的效果。支持32k上下文窗口的推理和精调。',
|
||||
model_series: {
|
||||
series_name: '豆包系列',
|
||||
icon_url: 'doubao_v2.png',
|
||||
model_vendor: '字节跳动',
|
||||
},
|
||||
model_status_details: {
|
||||
is_new_model: false,
|
||||
is_advanced_model: false,
|
||||
is_free_model: false,
|
||||
is_upcoming_deprecated: false,
|
||||
deprecated_date: '',
|
||||
replace_model_name: '',
|
||||
update_info: '',
|
||||
model_feature: 3,
|
||||
},
|
||||
model_ability: {
|
||||
function_call: true,
|
||||
image_understanding: false,
|
||||
video_understanding: false,
|
||||
audio_understanding: false,
|
||||
},
|
||||
model_show_family_id: '7287388636726274',
|
||||
hot_flag: 0,
|
||||
hot_ranking: 0,
|
||||
online_time: 1706247332,
|
||||
offline_time: 0,
|
||||
},
|
||||
],
|
||||
voice_list: null,
|
||||
raw_model_list: null,
|
||||
model_show_family_list: [
|
||||
{
|
||||
id: 7287388636726274,
|
||||
icon: 'MODEL_ICON/CUSTOM/af087406da7641beacf2b33c538d64fd_豆包.png',
|
||||
iconUrl: 'CUSTOM/af087406da7641beacf2b33c538d64fd_豆包.png',
|
||||
name: '豆包大模型',
|
||||
ranking: 1,
|
||||
},
|
||||
],
|
||||
default_model_id: 1737521813,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,661 @@
|
||||
/*
|
||||
* 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 mockSchemaForLLM = {
|
||||
nodes: [
|
||||
{
|
||||
blocks: [],
|
||||
data: {
|
||||
nodeMeta: {
|
||||
description: '工作流的起始节点,用于设定启动工作流需要的信息',
|
||||
icon: 'icon-Start-v2.jpg',
|
||||
subTitle: '',
|
||||
title: '开始',
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
name: 'input',
|
||||
required: false,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'arr_input',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'list',
|
||||
},
|
||||
{
|
||||
assistType: 2,
|
||||
name: 'img',
|
||||
required: false,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
trigger_parameters: [
|
||||
{
|
||||
name: 'input',
|
||||
required: false,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'arr_input',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'list',
|
||||
},
|
||||
{
|
||||
assistType: 2,
|
||||
name: 'img',
|
||||
required: false,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
edges: null,
|
||||
id: '100001',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 39,
|
||||
},
|
||||
},
|
||||
type: '1',
|
||||
},
|
||||
{
|
||||
blocks: [],
|
||||
data: {
|
||||
inputs: {
|
||||
inputParameters: [
|
||||
{
|
||||
input: {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'list',
|
||||
value: {
|
||||
content: {
|
||||
blockID: '160281',
|
||||
name: 'output',
|
||||
source: 'block-output',
|
||||
},
|
||||
rawMeta: {
|
||||
type: 99,
|
||||
},
|
||||
type: 'ref',
|
||||
},
|
||||
},
|
||||
name: 'output',
|
||||
},
|
||||
],
|
||||
terminatePlan: 'returnVariables',
|
||||
},
|
||||
nodeMeta: {
|
||||
description: '工作流的最终节点,用于返回工作流运行后的结果信息',
|
||||
icon: 'icon-End-v2.jpg',
|
||||
subTitle: '',
|
||||
title: '结束',
|
||||
},
|
||||
},
|
||||
edges: null,
|
||||
id: '900001',
|
||||
meta: {
|
||||
position: {
|
||||
x: 1760,
|
||||
y: 26,
|
||||
},
|
||||
},
|
||||
type: '2',
|
||||
},
|
||||
{
|
||||
blocks: [],
|
||||
data: {
|
||||
inputs: {
|
||||
inputParameters: [
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: {
|
||||
blockID: '100001',
|
||||
name: 'input',
|
||||
source: 'block-output',
|
||||
},
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'ref',
|
||||
},
|
||||
},
|
||||
name: 'input',
|
||||
},
|
||||
],
|
||||
llmParam: [
|
||||
{
|
||||
input: {
|
||||
type: 'float',
|
||||
value: {
|
||||
content: '0.8',
|
||||
rawMeta: {
|
||||
type: 4,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'temperature',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '4096',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'maxTokens',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '2',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'responseFormat',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: '豆包·1.5·Pro·32k',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'modleName',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '1737521813',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'modelType',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: 'balance',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'generationDiversity',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: '{{input}}',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'prompt',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'boolean',
|
||||
value: {
|
||||
content: false,
|
||||
rawMeta: {
|
||||
type: 3,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'enableChatHistory',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '3',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'chatHistoryRound',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: '',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'systemPrompt',
|
||||
},
|
||||
],
|
||||
settingOnError: {
|
||||
processType: 1,
|
||||
retryTimes: 0,
|
||||
timeoutMs: 180000,
|
||||
},
|
||||
},
|
||||
nodeMeta: {
|
||||
description: '调用大语言模型,使用变量和提示词生成回复',
|
||||
icon: 'icon-LLM-v2.jpg',
|
||||
mainColor: '#5C62FF',
|
||||
subTitle: '大模型',
|
||||
title: '大模型',
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
name: 'output',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
version: '3',
|
||||
},
|
||||
edges: null,
|
||||
id: '181515',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
type: '3',
|
||||
},
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
blocks: [],
|
||||
data: {
|
||||
inputs: {
|
||||
inputParameters: [
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: {
|
||||
blockID: '100001',
|
||||
name: 'input',
|
||||
source: 'block-output',
|
||||
},
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'ref',
|
||||
},
|
||||
},
|
||||
name: 'input',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
assistType: 2,
|
||||
type: 'string',
|
||||
value: {
|
||||
content: {
|
||||
blockID: '100001',
|
||||
name: 'img',
|
||||
source: 'block-output',
|
||||
},
|
||||
rawMeta: {
|
||||
isVision: true,
|
||||
type: 7,
|
||||
},
|
||||
type: 'ref',
|
||||
},
|
||||
},
|
||||
name: 'img',
|
||||
},
|
||||
],
|
||||
llmParam: [
|
||||
{
|
||||
input: {
|
||||
type: 'float',
|
||||
value: {
|
||||
content: '0.8',
|
||||
rawMeta: {
|
||||
type: 4,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'temperature',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'float',
|
||||
value: {
|
||||
content: '0.7',
|
||||
rawMeta: {
|
||||
type: 4,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'topP',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'float',
|
||||
value: {
|
||||
content: '0',
|
||||
rawMeta: {
|
||||
type: 4,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'frequencyPenalty',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '4096',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'maxTokens',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '2',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'responseFormat',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: '豆包·1.5·Pro·视觉推理·128K\t',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'modleName',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '1745219190',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'modelType',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: 'balance',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'generationDiversity',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: '图片{{img}}中有什么?',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'prompt',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'boolean',
|
||||
value: {
|
||||
content: false,
|
||||
rawMeta: {
|
||||
type: 3,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'enableChatHistory',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '3',
|
||||
rawMeta: {
|
||||
type: 2,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'chatHistoryRound',
|
||||
},
|
||||
{
|
||||
input: {
|
||||
type: 'string',
|
||||
value: {
|
||||
content: '',
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
name: 'systemPrompt',
|
||||
},
|
||||
],
|
||||
settingOnError: {
|
||||
processType: 1,
|
||||
retryTimes: 0,
|
||||
timeoutMs: 180000,
|
||||
},
|
||||
},
|
||||
nodeMeta: {
|
||||
description: '调用大语言模型,使用变量和提示词生成回复',
|
||||
icon: 'icon-LLM-v2.jpg',
|
||||
mainColor: '#5C62FF',
|
||||
subTitle: '大模型',
|
||||
title: '大模型_1',
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
name: 'output',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'reasoning_content',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
version: '3',
|
||||
},
|
||||
edges: null,
|
||||
id: '189239',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
type: '3',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
inputs: {
|
||||
inputParameters: [
|
||||
{
|
||||
input: {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'list',
|
||||
value: {
|
||||
content: {
|
||||
blockID: '100001',
|
||||
name: 'arr_input',
|
||||
source: 'block-output',
|
||||
},
|
||||
rawMeta: {
|
||||
type: 99,
|
||||
},
|
||||
type: 'ref',
|
||||
},
|
||||
},
|
||||
name: 'input',
|
||||
},
|
||||
],
|
||||
loopCount: {
|
||||
type: 'integer',
|
||||
value: {
|
||||
content: '10',
|
||||
type: 'literal',
|
||||
},
|
||||
},
|
||||
loopType: 'array',
|
||||
variableParameters: [],
|
||||
},
|
||||
nodeMeta: {
|
||||
description: '用于通过设定循环次数和逻辑,重复执行一系列任务',
|
||||
icon: 'icon-Loop-v2.jpg',
|
||||
mainColor: '#00B2B2',
|
||||
subTitle: '循环',
|
||||
title: '循环',
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
input: {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'list',
|
||||
value: {
|
||||
content: {
|
||||
blockID: '189239',
|
||||
name: 'output',
|
||||
source: 'block-output',
|
||||
},
|
||||
rawMeta: {
|
||||
type: 1,
|
||||
},
|
||||
type: 'ref',
|
||||
},
|
||||
},
|
||||
name: 'output',
|
||||
},
|
||||
],
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '160281',
|
||||
targetNodeID: '189239',
|
||||
sourcePortID: 'loop-function-inline-output',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '189239',
|
||||
targetNodeID: '160281',
|
||||
sourcePortID: '',
|
||||
targetPortID: 'loop-function-inline-input',
|
||||
},
|
||||
],
|
||||
id: '160281',
|
||||
meta: {
|
||||
canvasPosition: {
|
||||
x: 1020,
|
||||
y: 331,
|
||||
},
|
||||
position: {
|
||||
x: 1200,
|
||||
y: 13,
|
||||
},
|
||||
},
|
||||
type: '21',
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '100001',
|
||||
targetNodeID: '181515',
|
||||
sourcePortID: '',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '160281',
|
||||
targetNodeID: '900001',
|
||||
sourcePortID: 'loop-output',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '181515',
|
||||
targetNodeID: '160281',
|
||||
sourcePortID: '',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
loop: 'v2',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import type { FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import type { StandardNodeType } from '@coze-workflow/base/types';
|
||||
|
||||
import { addBasicNodeData } from '../add-node-data';
|
||||
import type { PlaygroundContext } from '../../typings';
|
||||
import { WorkflowNodeData } from '../../entity-datas';
|
||||
|
||||
// Mocks
|
||||
vi.mock('../../entity-datas', () => {
|
||||
const WorkflowNodeData1 = vi.fn();
|
||||
WorkflowNodeData1.prototype.getNodeData = vi.fn();
|
||||
WorkflowNodeData1.prototype.setNodeData = vi.fn();
|
||||
return { WorkflowNodeData: WorkflowNodeData1 };
|
||||
});
|
||||
|
||||
const mockGetNodeTemplateInfoByType = vi.fn();
|
||||
|
||||
describe('addBasicNodeData', () => {
|
||||
let mockNode: Partial<FlowNodeEntity>;
|
||||
let mockPlaygroundContext: Partial<PlaygroundContext>;
|
||||
let mockNodeDataEntity: WorkflowNodeData;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Re-instantiate mocks for WorkflowNodeData for each test
|
||||
mockNodeDataEntity = new WorkflowNodeData({} as any, {} as any);
|
||||
|
||||
mockNode = {
|
||||
flowNodeType: 'start' as StandardNodeType,
|
||||
getData: vi.fn().mockReturnValue(mockNodeDataEntity),
|
||||
};
|
||||
|
||||
mockPlaygroundContext = {
|
||||
getNodeTemplateInfoByType: mockGetNodeTemplateInfoByType,
|
||||
};
|
||||
});
|
||||
|
||||
it('should not set node data if nodeData already exists', () => {
|
||||
(mockNodeDataEntity.getNodeData as Mock).mockReturnValue({}); // Simulate existing data
|
||||
mockGetNodeTemplateInfoByType.mockReturnValue({
|
||||
icon: 'icon-path',
|
||||
description: 'description',
|
||||
title: 'title',
|
||||
mainColor: 'color',
|
||||
});
|
||||
|
||||
addBasicNodeData(
|
||||
mockNode as FlowNodeEntity,
|
||||
mockPlaygroundContext as PlaygroundContext,
|
||||
);
|
||||
|
||||
expect(mockNode.getData).toHaveBeenCalledWith(WorkflowNodeData);
|
||||
expect(mockNodeDataEntity.getNodeData).toHaveBeenCalled();
|
||||
expect(
|
||||
mockPlaygroundContext.getNodeTemplateInfoByType,
|
||||
).toHaveBeenCalledWith('start');
|
||||
expect(mockNodeDataEntity.setNodeData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not set node data if meta is undefined', () => {
|
||||
(mockNodeDataEntity.getNodeData as Mock).mockReturnValue(undefined); // Simulate no existing data
|
||||
mockGetNodeTemplateInfoByType.mockReturnValue(undefined); // Simulate meta not found
|
||||
|
||||
addBasicNodeData(
|
||||
mockNode as FlowNodeEntity,
|
||||
mockPlaygroundContext as PlaygroundContext,
|
||||
);
|
||||
|
||||
expect(mockNodeDataEntity.setNodeData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set node data if nodeData is undefined and meta is provided', () => {
|
||||
(mockNodeDataEntity.getNodeData as Mock).mockReturnValue(undefined); // Simulate no existing data
|
||||
const metaInfo = {
|
||||
icon: 'icon-path-new',
|
||||
description: 'new description',
|
||||
title: 'new title',
|
||||
mainColor: 'new-color',
|
||||
};
|
||||
mockGetNodeTemplateInfoByType.mockReturnValue(metaInfo);
|
||||
|
||||
addBasicNodeData(
|
||||
mockNode as FlowNodeEntity,
|
||||
mockPlaygroundContext as PlaygroundContext,
|
||||
);
|
||||
|
||||
expect(mockNodeDataEntity.setNodeData).toHaveBeenCalledWith({
|
||||
icon: metaInfo.icon,
|
||||
description: metaInfo.description,
|
||||
title: metaInfo.title,
|
||||
mainColor: metaInfo.mainColor,
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly get node type from node.flowNodeType', () => {
|
||||
(mockNodeDataEntity.getNodeData as Mock).mockReturnValue(undefined);
|
||||
const metaInfo = {
|
||||
icon: 'test',
|
||||
description: 'test',
|
||||
title: 'test',
|
||||
mainColor: 'test',
|
||||
};
|
||||
mockGetNodeTemplateInfoByType.mockReturnValue(metaInfo);
|
||||
(mockNode as FlowNodeEntity).flowNodeType =
|
||||
'customType' as StandardNodeType;
|
||||
|
||||
addBasicNodeData(
|
||||
mockNode as FlowNodeEntity,
|
||||
mockPlaygroundContext as PlaygroundContext,
|
||||
);
|
||||
|
||||
expect(
|
||||
mockPlaygroundContext.getNodeTemplateInfoByType,
|
||||
).toHaveBeenCalledWith('customType');
|
||||
expect(mockNodeDataEntity.setNodeData).toHaveBeenCalledWith(metaInfo);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { describe, it, expect, vi } from 'vitest';
|
||||
import { variableUtils } from '@coze-workflow/variable';
|
||||
import { ViewVariableType, type DTODefine } from '@coze-workflow/base';
|
||||
|
||||
import { getInputTypeBase, getInputType } from '../get-input-type';
|
||||
|
||||
// Mock @coze-workflow/variable
|
||||
vi.mock('@coze-workflow/variable', () => ({
|
||||
variableUtils: {
|
||||
dtoMetaToViewMeta: vi.fn(),
|
||||
// Mock other functions from variableUtils if needed by the tests or the module
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock @coze-workflow/base specifically for ViewVariableType.getLabel if it's complex
|
||||
// Otherwise, direct usage is fine if it's simple enum/object lookup
|
||||
vi.mock('@coze-workflow/base', async importOriginal => {
|
||||
const actual: object = await importOriginal();
|
||||
return {
|
||||
...actual, // Preserve other exports from @coze-workflow/base
|
||||
ViewVariableType: {
|
||||
...(actual as any).ViewVariableType,
|
||||
getLabel: vi.fn(type => `Label for ${type}`), // Simple mock for getLabel
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('getInputTypeBase', () => {
|
||||
it('should return correct structure for a given ViewVariableType', () => {
|
||||
const inputType = ViewVariableType.String;
|
||||
const result = getInputTypeBase(inputType);
|
||||
|
||||
expect(ViewVariableType.getLabel).toHaveBeenCalledWith(inputType);
|
||||
expect(result).toEqual({
|
||||
inputType: ViewVariableType.String,
|
||||
viewType: `Label for ${ViewVariableType.String}`,
|
||||
disabledTypes: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with different ViewVariableTypes', () => {
|
||||
const inputType = ViewVariableType.Number;
|
||||
getInputTypeBase(inputType);
|
||||
expect(ViewVariableType.getLabel).toHaveBeenCalledWith(inputType);
|
||||
|
||||
const inputTypeBool = ViewVariableType.Boolean;
|
||||
const resultBool = getInputTypeBase(inputTypeBool);
|
||||
expect(ViewVariableType.getLabel).toHaveBeenCalledWith(inputTypeBool);
|
||||
expect(resultBool.inputType).toBe(ViewVariableType.Boolean);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInputType', () => {
|
||||
it('should call dtoMetaToViewMeta and return result from getInputTypeBase', () => {
|
||||
const mockInputDTO = {
|
||||
id: 'test-id',
|
||||
name: 'test-name',
|
||||
} as unknown as DTODefine.InputVariableDTO;
|
||||
const mockViewMetaType = ViewVariableType.Integer;
|
||||
|
||||
(variableUtils.dtoMetaToViewMeta as any).mockReturnValue({
|
||||
type: mockViewMetaType,
|
||||
});
|
||||
|
||||
const result = getInputType(mockInputDTO);
|
||||
|
||||
expect(variableUtils.dtoMetaToViewMeta).toHaveBeenCalledWith(mockInputDTO);
|
||||
expect(ViewVariableType.getLabel).toHaveBeenCalledWith(mockViewMetaType);
|
||||
expect(result).toEqual({
|
||||
inputType: mockViewMetaType,
|
||||
viewType: `Label for ${mockViewMetaType}`,
|
||||
disabledTypes: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import type {
|
||||
WorkflowJSON,
|
||||
WorkflowDocument,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { StandardNodeType } from '@coze-workflow/base/types';
|
||||
|
||||
import { getLLMModelIds } from '../get-llm-model-ids';
|
||||
import { mockSchemaForLLM } from './__mocks__/mock-schema';
|
||||
|
||||
describe('getLLMModelIds (implicitly testing getLLMModelIdsByNodeJSON)', () => {
|
||||
let mockDocument: WorkflowDocument;
|
||||
let mockGetNodeRegistry: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockGetNodeRegistry = vi.fn().mockReturnValue({
|
||||
meta: {
|
||||
getLLMModelIdsByNodeJSON: nodeJSON => {
|
||||
if (nodeJSON.type === StandardNodeType.Intent) {
|
||||
return nodeJSON?.data?.inputs?.llmParam?.modelType;
|
||||
}
|
||||
|
||||
if (nodeJSON.type === StandardNodeType.Question) {
|
||||
return nodeJSON?.data?.inputs?.llmParam?.modelType;
|
||||
}
|
||||
|
||||
if (nodeJSON.type === StandardNodeType.LLM) {
|
||||
return nodeJSON.data.inputs.llmParam.find(
|
||||
p => p.name === 'modelType',
|
||||
)?.input.value.content;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
mockDocument = {
|
||||
getNodeRegistry: mockGetNodeRegistry,
|
||||
} as unknown as WorkflowDocument;
|
||||
});
|
||||
|
||||
it('should return empty array if document is empty', () => {
|
||||
const json: WorkflowJSON = { nodes: [], edges: [] };
|
||||
expect(getLLMModelIds(json, mockDocument)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array if json.nodes is empty', () => {
|
||||
const json: WorkflowJSON = { nodes: [], edges: [] };
|
||||
expect(getLLMModelIds(json, mockDocument)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return correct llm ids if json.nodes is not empty', () => {
|
||||
expect(
|
||||
getLLMModelIds(mockSchemaForLLM as unknown as WorkflowJSON, mockDocument),
|
||||
).toEqual(['1737521813', '1745219190']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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 {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import type { WorkflowDocument } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
captureException,
|
||||
RESPONSE_FORMAT_NAME,
|
||||
ResponseFormat,
|
||||
StandardNodeType,
|
||||
} from '@coze-workflow/base';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { DeveloperApi as developerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { getLLMModels } from '../get-llm-models';
|
||||
import { mockSchemaForLLM } from './__mocks__/mock-schema';
|
||||
import { mockLLMModels } from './__mocks__/mock-models';
|
||||
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
DeveloperApi: {
|
||||
GetTypeList: vi.fn(),
|
||||
},
|
||||
ModelScene: {
|
||||
Douyin: 'douyin_scene',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/logger', () => {
|
||||
const mockCreatedLogger = {
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
log: vi.fn(),
|
||||
};
|
||||
const mockLogger = {
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
log: vi.fn(),
|
||||
|
||||
createLoggerWith: vi.fn(() => mockCreatedLogger),
|
||||
};
|
||||
return {
|
||||
__esModule: true, // Indicates that this is an ES module mock
|
||||
default: mockLogger, // Mock for `import logger from '@coze-arch/logger'`
|
||||
logger: mockLogger, // Mock for `import { logger } from '@coze-arch/logger'`
|
||||
reporter: {
|
||||
createReporterWithPreset: vi.fn(),
|
||||
slardarInstance: vi.fn(),
|
||||
},
|
||||
createLoggerWith: vi.fn(() => mockCreatedLogger), // Mock for `import { createLoggerWith } from '@coze-arch/logger'`
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: {
|
||||
t: vi.fn(key => key), // Simple mock for I18n.t
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-workflow/base', async () => {
|
||||
const actual = await vi.importActual('@coze-workflow/base');
|
||||
return {
|
||||
...actual,
|
||||
captureException: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockSpaceId = 'space-123';
|
||||
|
||||
describe('getLLMModels', () => {
|
||||
let mockDocument: WorkflowDocument;
|
||||
let mockGetNodeRegistry: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetNodeRegistry = vi.fn().mockReturnValue({
|
||||
meta: {
|
||||
getLLMModelIdsByNodeJSON: nodeJSON => {
|
||||
if (nodeJSON.type === StandardNodeType.Intent) {
|
||||
return nodeJSON?.data?.inputs?.llmParam?.modelType;
|
||||
}
|
||||
|
||||
if (nodeJSON.type === StandardNodeType.Question) {
|
||||
return nodeJSON?.data?.inputs?.llmParam?.modelType;
|
||||
}
|
||||
|
||||
if (nodeJSON.type === StandardNodeType.LLM) {
|
||||
return nodeJSON.data.inputs.llmParam.find(
|
||||
p => p.name === 'modelType',
|
||||
)?.input.value.content;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
mockDocument = {
|
||||
getNodeRegistry: mockGetNodeRegistry,
|
||||
} as unknown as WorkflowDocument;
|
||||
vi.mocked(developerApi.GetTypeList).mockResolvedValue(
|
||||
JSON.parse(JSON.stringify(mockLLMModels)),
|
||||
);
|
||||
vi.mocked(I18n.t).mockImplementation(key => key);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch and process model list correctly', async () => {
|
||||
const mockInfo = { schema_json: JSON.stringify(mockSchemaForLLM) };
|
||||
|
||||
// Act
|
||||
const models = await getLLMModels({
|
||||
info: mockInfo,
|
||||
spaceId: mockSpaceId,
|
||||
document: mockDocument,
|
||||
isBindDouyin: false,
|
||||
});
|
||||
|
||||
expect(developerApi.GetTypeList).toHaveBeenCalledWith({
|
||||
space_id: mockSpaceId,
|
||||
model: true,
|
||||
cur_model_ids: ['1737521813', '1745219190'],
|
||||
});
|
||||
expect(models).toBeInstanceOf(Array);
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
|
||||
// Check repairResponseFormatInModelList logic
|
||||
models.forEach(model => {
|
||||
const responseFormatParam = model.model_params?.find(
|
||||
p => p.name === RESPONSE_FORMAT_NAME,
|
||||
);
|
||||
expect(responseFormatParam).toBeDefined();
|
||||
expect(responseFormatParam?.default_val?.default_val).toBe(
|
||||
ResponseFormat.JSON,
|
||||
);
|
||||
expect(responseFormatParam?.options).toEqual([
|
||||
{ label: 'model_config_history_text', value: ResponseFormat.Text },
|
||||
{
|
||||
label: 'model_config_history_markdown',
|
||||
value: ResponseFormat.Markdown,
|
||||
},
|
||||
{ label: 'model_config_history_json', value: ResponseFormat.JSON },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set model_scene when isBindDouyin is true', async () => {
|
||||
const mockInfo = { schema_json: JSON.stringify(mockSchemaForLLM) };
|
||||
|
||||
// 上一个接口有 3s 缓存,需要等待 3s 后再调用
|
||||
await new Promise(resolve => setTimeout(resolve, 3100));
|
||||
|
||||
// Act
|
||||
await getLLMModels({
|
||||
info: mockInfo,
|
||||
spaceId: mockSpaceId,
|
||||
document: mockDocument,
|
||||
isBindDouyin: true,
|
||||
});
|
||||
|
||||
expect(developerApi.GetTypeList).toHaveBeenCalledWith({
|
||||
space_id: mockSpaceId,
|
||||
model: true,
|
||||
cur_model_ids: ['1737521813', '1745219190'],
|
||||
model_scene: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle API error gracefully', async () => {
|
||||
const mockInfo = { schema_json: JSON.stringify(mockSchemaForLLM) };
|
||||
const apiError = new Error('API Error');
|
||||
vi.mocked(developerApi.GetTypeList).mockRejectedValue(apiError);
|
||||
|
||||
// 上一个接口有 3s 缓存,需要等待 3s 后再调用
|
||||
await new Promise(resolve => setTimeout(resolve, 3100));
|
||||
|
||||
const models = await getLLMModels({
|
||||
info: mockInfo as any,
|
||||
spaceId: mockSpaceId,
|
||||
document: mockDocument,
|
||||
isBindDouyin: false,
|
||||
});
|
||||
|
||||
expect(models).toEqual([]);
|
||||
expect(logger.error).toHaveBeenCalledWith({
|
||||
error: apiError,
|
||||
eventName: 'api/bot/get_type_list fetch error',
|
||||
});
|
||||
expect(captureException).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(I18n.t).toHaveBeenCalledWith('workflow_detail_error_message', {
|
||||
msg: 'fetch error',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 { describe, it, expect } from 'vitest';
|
||||
|
||||
import { getSortedInputParameters } from '../get-sorted-input-parameters';
|
||||
|
||||
interface TestInputItem {
|
||||
name?: string;
|
||||
required?: boolean;
|
||||
id: number; // Additional property for stable sort testing if names are same
|
||||
}
|
||||
|
||||
describe('getSortedInputParameters', () => {
|
||||
it('should return an empty array if inputs is null or undefined', () => {
|
||||
expect(getSortedInputParameters(null as any)).toEqual([]);
|
||||
expect(getSortedInputParameters(undefined as any)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an empty array if inputs is an empty array', () => {
|
||||
expect(getSortedInputParameters([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should sort items with required=true before required=false', () => {
|
||||
const inputs: TestInputItem[] = [
|
||||
{ id: 1, name: 'a', required: false },
|
||||
{ id: 2, name: 'b', required: true },
|
||||
];
|
||||
const expected: TestInputItem[] = [
|
||||
{ id: 2, name: 'b', required: true },
|
||||
{ id: 1, name: 'a', required: false },
|
||||
];
|
||||
expect(getSortedInputParameters(inputs)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should sort items by name within each required group (true then false)', () => {
|
||||
const inputs: TestInputItem[] = [
|
||||
{ id: 1, name: 'z', required: false },
|
||||
{ id: 2, name: 'a', required: true },
|
||||
{ id: 3, name: 'x', required: false },
|
||||
{ id: 4, name: 'b', required: true },
|
||||
];
|
||||
const expected: TestInputItem[] = [
|
||||
{ id: 2, name: 'a', required: true },
|
||||
{ id: 4, name: 'b', required: true },
|
||||
{ id: 3, name: 'x', required: false },
|
||||
{ id: 1, name: 'z', required: false },
|
||||
];
|
||||
expect(getSortedInputParameters(inputs)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should treat items with undefined required as false by default', () => {
|
||||
const inputs: TestInputItem[] = [
|
||||
{ id: 1, name: 'a' }, // required is undefined
|
||||
{ id: 2, name: 'b', required: true },
|
||||
];
|
||||
const expected: TestInputItem[] = [
|
||||
{ id: 2, name: 'b', required: true },
|
||||
{ id: 1, name: 'a', required: false }, // Processed to required: false
|
||||
];
|
||||
expect(getSortedInputParameters(inputs)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle items with undefined names (they should be sorted according to lodash sortBy behavior)', () => {
|
||||
const inputs: TestInputItem[] = [
|
||||
{ id: 1, name: 'a', required: true },
|
||||
{ id: 2, required: true }, // name is undefined
|
||||
{ id: 3, name: 'b', required: false },
|
||||
{ id: 4, required: false }, // name is undefined
|
||||
];
|
||||
// Lodash sortBy typically places undefined values first when sorting in ascending order.
|
||||
const expected: TestInputItem[] = [
|
||||
{ id: 1, name: 'a', required: true },
|
||||
{ id: 2, required: true },
|
||||
{ id: 3, name: 'b', required: false },
|
||||
{ id: 4, required: false },
|
||||
];
|
||||
expect(getSortedInputParameters(inputs)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should maintain original properties of items', () => {
|
||||
const inputs: TestInputItem[] = [
|
||||
{ id: 1, name: 'a', required: false, otherProp: 'value1' } as any,
|
||||
{ id: 2, name: 'b', required: true, otherProp: 'value2' } as any,
|
||||
];
|
||||
const result = getSortedInputParameters(inputs);
|
||||
expect(result[0]).toHaveProperty('otherProp', 'value2');
|
||||
expect(result[1]).toHaveProperty('otherProp', 'value1');
|
||||
});
|
||||
|
||||
it('should use custom groupKey and sortKey if provided (though the function signature does not expose this)', () => {
|
||||
const inputs: TestInputItem[] = [
|
||||
{ id: 1, name: 'a', required: false },
|
||||
{ id: 2, name: 'b', required: true },
|
||||
];
|
||||
const expected: TestInputItem[] = [
|
||||
{ id: 2, name: 'b', required: true },
|
||||
{ id: 1, name: 'a', required: false },
|
||||
];
|
||||
expect(getSortedInputParameters(inputs, 'required', 'name')).toEqual(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
GenerationDiversity,
|
||||
type InputValueDTO,
|
||||
VariableTypeDTO,
|
||||
} from '@coze-workflow/base';
|
||||
|
||||
import {
|
||||
formatModelData,
|
||||
getDefaultLLMParams,
|
||||
reviseLLMParamPair,
|
||||
} from '../llm-utils';
|
||||
import { mockLLMModels } from './__mocks__/mock-models';
|
||||
|
||||
const mockModels = mockLLMModels.data.model_list;
|
||||
|
||||
describe('llm-utils', () => {
|
||||
describe('formatModelData', () => {
|
||||
it('should convert string values to number based on modelMeta', () => {
|
||||
const model = {
|
||||
temperature: '0.8',
|
||||
maxTokens: '1024',
|
||||
modelType: '1737521813',
|
||||
otherParam: '保持字符串',
|
||||
};
|
||||
const modelMeta = mockModels[0];
|
||||
|
||||
const result = formatModelData(model, modelMeta);
|
||||
|
||||
expect(result.temperature).toBe(0.8);
|
||||
expect(result.maxTokens).toBe(1024);
|
||||
expect(result.modelType).toBe('1737521813');
|
||||
expect(result.otherParam).toBe('保持字符串');
|
||||
});
|
||||
|
||||
it('should return original value when modelMeta is undefined', () => {
|
||||
const model = { temperature: '0.8' };
|
||||
const result = formatModelData(model, undefined);
|
||||
expect(result.temperature).toBe('0.8');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultLLMParams', () => {
|
||||
it('should select default model by DEFAULT_MODEL_TYPE', () => {
|
||||
const params = getDefaultLLMParams(mockModels);
|
||||
|
||||
expect(params.modelType).toBe(mockModels[0].model_type);
|
||||
expect(params.modelName).toBe(mockModels[0].name);
|
||||
expect(params.generationDiversity).toBe(GenerationDiversity.Balance);
|
||||
expect(params.temperature).toBe(0.8); // 来自mockModels[0]的balance默认值
|
||||
});
|
||||
});
|
||||
|
||||
describe('reviseLLMParamPair', () => {
|
||||
it('should fix typo "modleName" to "modelName"', () => {
|
||||
const input = {
|
||||
name: 'modleName',
|
||||
input: {
|
||||
type: VariableTypeDTO.string,
|
||||
value: { content: '豆包·1.5·Pro·32k' },
|
||||
},
|
||||
};
|
||||
const [key, value] = reviseLLMParamPair(input as InputValueDTO);
|
||||
expect(key).toBe('modelName');
|
||||
expect(value).toBe('豆包·1.5·Pro·32k');
|
||||
});
|
||||
|
||||
it('should convert number types to number', () => {
|
||||
const floatInput = {
|
||||
name: 'temperature',
|
||||
input: { type: VariableTypeDTO.float, value: { content: '0.8' } },
|
||||
};
|
||||
const [_, floatValue] = reviseLLMParamPair(floatInput as InputValueDTO);
|
||||
expect(floatValue).toBe(0.8);
|
||||
|
||||
const intInput = {
|
||||
name: 'maxTokens',
|
||||
input: { type: VariableTypeDTO.integer, value: { content: '1024' } },
|
||||
};
|
||||
const [__, intValue] = reviseLLMParamPair(intInput as InputValueDTO);
|
||||
expect(intValue).toBe(1024);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 { describe, it, expect } from 'vitest';
|
||||
|
||||
import { getTriggerId, setTriggerId } from '../trigger-form';
|
||||
|
||||
describe('trigger-form', () => {
|
||||
it('should set and get a triggerId for a given workflowId', () => {
|
||||
const wfId = 'workflow123';
|
||||
const triggerId = 'triggerABC';
|
||||
|
||||
setTriggerId(wfId, triggerId);
|
||||
const retrievedTriggerId = getTriggerId(wfId);
|
||||
|
||||
expect(retrievedTriggerId).toBe(triggerId);
|
||||
});
|
||||
|
||||
it('should return undefined if a triggerId is not set for a workflowId', () => {
|
||||
const wfId = 'workflowUnset';
|
||||
const retrievedTriggerId = getTriggerId(wfId);
|
||||
|
||||
expect(retrievedTriggerId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should overwrite an existing triggerId if set again for the same workflowId', () => {
|
||||
const wfId = 'workflowOverwrite';
|
||||
const initialTriggerId = 'triggerInitial';
|
||||
const newTriggerId = 'triggerNew';
|
||||
|
||||
setTriggerId(wfId, initialTriggerId);
|
||||
expect(getTriggerId(wfId)).toBe(initialTriggerId); // Verify initial set
|
||||
|
||||
setTriggerId(wfId, newTriggerId);
|
||||
const retrievedTriggerId = getTriggerId(wfId);
|
||||
|
||||
expect(retrievedTriggerId).toBe(newTriggerId);
|
||||
});
|
||||
|
||||
it('should handle multiple workflowIds independently', () => {
|
||||
const wfId1 = 'workflowA';
|
||||
const triggerId1 = 'triggerA';
|
||||
const wfId2 = 'workflowB';
|
||||
const triggerId2 = 'triggerB';
|
||||
|
||||
setTriggerId(wfId1, triggerId1);
|
||||
setTriggerId(wfId2, triggerId2);
|
||||
|
||||
expect(getTriggerId(wfId1)).toBe(triggerId1);
|
||||
expect(getTriggerId(wfId2)).toBe(triggerId2);
|
||||
});
|
||||
|
||||
it('should handle empty string as workflowId and triggerId', () => {
|
||||
const wfId = '';
|
||||
const triggerId = '';
|
||||
|
||||
setTriggerId(wfId, triggerId);
|
||||
expect(getTriggerId(wfId)).toBe(triggerId);
|
||||
|
||||
const wfId2 = 'workflowC';
|
||||
const triggerId2 = '';
|
||||
setTriggerId(wfId2, triggerId2);
|
||||
expect(getTriggerId(wfId2)).toBe(triggerId2);
|
||||
|
||||
const wfId3 = '';
|
||||
const triggerId3 = 'triggerD';
|
||||
setTriggerId(wfId3, triggerId3); // This will overwrite the previous '' wfId
|
||||
expect(getTriggerId('')).toBe(triggerId3);
|
||||
});
|
||||
});
|
||||
51
frontend/packages/workflow/nodes/src/utils/add-node-data.ts
Normal file
51
frontend/packages/workflow/nodes/src/utils/add-node-data.ts
Normal 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 { type FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
type BasicStandardNodeTypes,
|
||||
type StandardNodeType,
|
||||
} from '@coze-workflow/base/types';
|
||||
|
||||
import { type PlaygroundContext } from '../typings';
|
||||
import { type NodeData, WorkflowNodeData } from '../entity-datas';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node
|
||||
* @param data
|
||||
* 给基础类型节点设置节点数据,不要随意修改
|
||||
*/
|
||||
export const addBasicNodeData = (
|
||||
node: FlowNodeEntity,
|
||||
playgroundContext: PlaygroundContext,
|
||||
) => {
|
||||
const nodeDataEntity = node.getData<WorkflowNodeData>(WorkflowNodeData);
|
||||
const meta = playgroundContext.getNodeTemplateInfoByType(
|
||||
node.flowNodeType as StandardNodeType,
|
||||
);
|
||||
const nodeData = nodeDataEntity.getNodeData<keyof NodeData>();
|
||||
|
||||
// 在部分节点的 formMeta 方法,会重复执行,因此这里加个检测
|
||||
if (!nodeData && meta) {
|
||||
nodeDataEntity.setNodeData<BasicStandardNodeTypes>({
|
||||
icon: meta.icon,
|
||||
description: meta.description,
|
||||
title: meta.title,
|
||||
mainColor: meta.mainColor,
|
||||
});
|
||||
}
|
||||
};
|
||||
40
frontend/packages/workflow/nodes/src/utils/get-input-type.ts
Normal file
40
frontend/packages/workflow/nodes/src/utils/get-input-type.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { variableUtils } from '@coze-workflow/variable';
|
||||
import {
|
||||
type DTODefine,
|
||||
type VariableMetaDTO,
|
||||
ViewVariableType,
|
||||
} from '@coze-workflow/base';
|
||||
|
||||
export const getInputTypeBase = (inputType: ViewVariableType) => {
|
||||
const viewType = ViewVariableType.getLabel(inputType);
|
||||
|
||||
return {
|
||||
inputType,
|
||||
viewType,
|
||||
disabledTypes: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInputType = (input: DTODefine.InputVariableDTO) => {
|
||||
const { type: inputType } = variableUtils.dtoMetaToViewMeta(
|
||||
input as VariableMetaDTO,
|
||||
);
|
||||
|
||||
return getInputTypeBase(inputType);
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 WorkflowNodeJSON,
|
||||
type WorkflowJSON,
|
||||
type WorkflowDocument,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type WorkflowNodeRegistry } from '@coze-workflow/base';
|
||||
|
||||
/**
|
||||
* 根据node meta中定义的getLLMModelIdsByNodeJSON方法获取大模型id
|
||||
* @param nodeJSON
|
||||
* @param ids
|
||||
* @param document
|
||||
*/
|
||||
function getLLMModelIdsByNodeJSON(
|
||||
nodeJSON: WorkflowNodeJSON,
|
||||
ids: string[],
|
||||
document: WorkflowDocument,
|
||||
) {
|
||||
const registry = document.getNodeRegistry(
|
||||
nodeJSON.type,
|
||||
) as WorkflowNodeRegistry;
|
||||
|
||||
const res = registry?.meta?.getLLMModelIdsByNodeJSON?.(nodeJSON);
|
||||
|
||||
if (res) {
|
||||
const modelIds = Array.isArray(res) ? res : [res];
|
||||
modelIds.filter(Boolean).forEach(modelId => {
|
||||
const idstr = `${modelId}`;
|
||||
if (!ids.includes(idstr)) {
|
||||
ids.push(idstr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (nodeJSON.blocks) {
|
||||
nodeJSON.blocks.forEach(block =>
|
||||
getLLMModelIdsByNodeJSON(block, ids, document),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型ids
|
||||
* @param json
|
||||
* @param document
|
||||
* @returns
|
||||
*/
|
||||
export function getLLMModelIds(
|
||||
json: WorkflowJSON,
|
||||
document: WorkflowDocument,
|
||||
): string[] {
|
||||
const ids = [];
|
||||
|
||||
if (!document) {
|
||||
return ids;
|
||||
}
|
||||
|
||||
json.nodes.forEach(node => {
|
||||
getLLMModelIdsByNodeJSON(node, ids, document);
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
180
frontend/packages/workflow/nodes/src/utils/get-llm-models.ts
Normal file
180
frontend/packages/workflow/nodes/src/utils/get-llm-models.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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 { QueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
captureException,
|
||||
RESPONSE_FORMAT_NAME,
|
||||
ResponseFormat,
|
||||
} from '@coze-workflow/base';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { ModelScene } from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
type GetTypeListRequest,
|
||||
type Model,
|
||||
type ModelParameter,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi as developerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { getLLMModelIds } from './get-llm-model-ids';
|
||||
|
||||
/** 默认的 response format 值 */
|
||||
export const getDefaultResponseFormat = () => ({
|
||||
name: RESPONSE_FORMAT_NAME,
|
||||
label: I18n.t('model_config_response_format'),
|
||||
desc: I18n.t('model_config_response_format_explain'),
|
||||
type: 2,
|
||||
min: '',
|
||||
max: '',
|
||||
precision: 0,
|
||||
default_val: {
|
||||
default_val: '0',
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: I18n.t('model_config_history_text'),
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: I18n.t('model_config_history_markdown'),
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
param_class: {
|
||||
class_id: 2,
|
||||
},
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: Infinity,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 1. 给模型列表中每个模型的 response_format 参数项补全
|
||||
* 2. 硬编码设置 response_format 的默认值为 JSON
|
||||
* @param modelList 模型列表
|
||||
* @returns 补全 response_format 参数后的模型列表
|
||||
*/
|
||||
const repairResponseFormatInModelList = (modelList: Model[]) => {
|
||||
// 找到模型列表中 model_params 的第一个 response_format 参数项
|
||||
// 这段代码从下边循环中提出来,不需要每次循环计算一次
|
||||
const modelHasResponseFormatItem = modelList
|
||||
.find(_m => _m.model_params?.find(p => p.name === RESPONSE_FORMAT_NAME))
|
||||
?.model_params?.find(p => p.name === RESPONSE_FORMAT_NAME);
|
||||
|
||||
return modelList.map(m => {
|
||||
// 兼容后端未刷带的数据,没有 responseFormat 就补上
|
||||
const responseFormat = m.model_params?.find(
|
||||
p => p?.name === RESPONSE_FORMAT_NAME,
|
||||
) as ModelParameter;
|
||||
|
||||
if (!responseFormat) {
|
||||
if (modelHasResponseFormatItem) {
|
||||
m.model_params?.push(modelHasResponseFormatItem as ModelParameter);
|
||||
} else {
|
||||
// 填充一个默认的 response_format 参数
|
||||
m.model_params?.push(getDefaultResponseFormat());
|
||||
}
|
||||
}
|
||||
|
||||
// 此时再找一次 responseFormat,因为上边补全了 responseFormat
|
||||
const newResponseFormat = m.model_params?.find(
|
||||
p => p?.name === RESPONSE_FORMAT_NAME,
|
||||
) as ModelParameter;
|
||||
|
||||
// 重置默认值为 JSON
|
||||
Object.keys(newResponseFormat?.default_val ?? {}).forEach(k => {
|
||||
newResponseFormat.default_val[k] = ResponseFormat.JSON;
|
||||
});
|
||||
|
||||
if (newResponseFormat) {
|
||||
// 重置选项,text markdown json 都要支持
|
||||
newResponseFormat.options = [
|
||||
{
|
||||
label: I18n.t('model_config_history_text'),
|
||||
value: ResponseFormat.Text,
|
||||
},
|
||||
{
|
||||
label: I18n.t('model_config_history_markdown'),
|
||||
value: ResponseFormat.Markdown,
|
||||
},
|
||||
{
|
||||
label: I18n.t('model_config_history_json'),
|
||||
value: ResponseFormat.JSON,
|
||||
},
|
||||
] as unknown as ModelParameter[];
|
||||
}
|
||||
|
||||
return m;
|
||||
});
|
||||
};
|
||||
|
||||
export const getLLMModels = async ({
|
||||
info,
|
||||
spaceId,
|
||||
document,
|
||||
isBindDouyin,
|
||||
}): Promise<Model[]> => {
|
||||
try {
|
||||
const modelList = await queryClient.fetchQuery({
|
||||
queryKey: ['llm-model'],
|
||||
queryFn: async () => {
|
||||
const schema = JSON.parse(info?.schema_json || '{}');
|
||||
|
||||
const llmModelIds = getLLMModelIds(schema, document);
|
||||
|
||||
const getTypeListParams: GetTypeListRequest = {
|
||||
space_id: spaceId,
|
||||
model: true,
|
||||
cur_model_ids: llmModelIds,
|
||||
};
|
||||
|
||||
if (isBindDouyin) {
|
||||
getTypeListParams.model_scene = ModelScene.Douyin;
|
||||
}
|
||||
|
||||
const resp = await developerApi.GetTypeList(getTypeListParams);
|
||||
const _modelList: Model[] = resp?.data?.model_list ?? [];
|
||||
|
||||
// 从这里开始到 return modelList 全是给后端擦屁股
|
||||
// 这里有 hard code ,需要把输出格式的默认值设置为 JSON
|
||||
return repairResponseFormatInModelList(_modelList);
|
||||
},
|
||||
staleTime: 3000,
|
||||
});
|
||||
return modelList;
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
error: error as Error,
|
||||
eventName: 'api/bot/get_type_list fetch error',
|
||||
});
|
||||
// 上报js错误
|
||||
captureException(
|
||||
new Error(
|
||||
I18n.t('workflow_detail_error_message', {
|
||||
msg: 'fetch error',
|
||||
}),
|
||||
),
|
||||
);
|
||||
// 兜底返回空数组
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 { groupBy, sortBy } from 'lodash-es';
|
||||
import { type DTODefine } from '@coze-workflow/base';
|
||||
|
||||
export type InputVariableDTO = DTODefine.InputVariableDTO;
|
||||
|
||||
/**
|
||||
* 对输入参数进行排序,然后按照 required 字段进行分组,必填的放最前边
|
||||
* @param inputs
|
||||
* @param groupKey
|
||||
* @param sortKey
|
||||
* @returns
|
||||
*/
|
||||
export const getSortedInputParameters = <
|
||||
T extends { name?: string; required?: boolean },
|
||||
>(
|
||||
inputs: T[],
|
||||
groupKey = 'required',
|
||||
sortKey = 'name',
|
||||
): T[] => {
|
||||
const processedItems = (inputs || []).map(item => ({
|
||||
...item,
|
||||
required: item.required !== undefined ? item.required : false, // 默认设置为 false
|
||||
}));
|
||||
|
||||
// 先按照 required 属性分组
|
||||
const grouped = groupBy(processedItems, groupKey);
|
||||
|
||||
// 在每个组内按照 name 属性进行排序
|
||||
const sortedTrueGroup = sortBy(grouped.true, sortKey) || [];
|
||||
const sortedFalseGroup = sortBy(grouped.false, sortKey) || [];
|
||||
|
||||
// 合并 true 分组和 false 分组
|
||||
const mergedArray = [...sortedTrueGroup, ...sortedFalseGroup];
|
||||
|
||||
return mergedArray;
|
||||
};
|
||||
30
frontend/packages/workflow/nodes/src/utils/index.ts
Normal file
30
frontend/packages/workflow/nodes/src/utils/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 * from './node-utils';
|
||||
|
||||
export { getLLMModels } from './get-llm-models';
|
||||
|
||||
export { getInputType } from './get-input-type';
|
||||
export { addBasicNodeData } from './add-node-data';
|
||||
export { getTriggerId, setTriggerId } from './trigger-form';
|
||||
|
||||
export { getSortedInputParameters } from './get-sorted-input-parameters';
|
||||
export {
|
||||
formatModelData,
|
||||
getDefaultLLMParams,
|
||||
reviseLLMParamPair,
|
||||
} from './llm-utils';
|
||||
114
frontend/packages/workflow/nodes/src/utils/llm-utils.ts
Normal file
114
frontend/packages/workflow/nodes/src/utils/llm-utils.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 { mapValues, keyBy, snakeCase, isString, camelCase } from 'lodash-es';
|
||||
import {
|
||||
GenerationDiversity,
|
||||
VariableTypeDTO,
|
||||
type InputValueDTO,
|
||||
} from '@coze-workflow/base';
|
||||
import { ModelParamType, type Model } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { DEFAULT_MODEL_TYPE } from '../constants';
|
||||
|
||||
const getDefaultModels = (modelMeta: Model): Record<string, unknown> => {
|
||||
const defaultModel: Record<string, unknown> = {};
|
||||
|
||||
modelMeta?.model_params?.forEach(p => {
|
||||
const k = camelCase(p.name) as string;
|
||||
const { type } = p;
|
||||
|
||||
// 优先取平衡,自定义兜底
|
||||
const defaultValue =
|
||||
p.default_val[GenerationDiversity.Balance] ??
|
||||
p.default_val[GenerationDiversity.Customize];
|
||||
|
||||
if (defaultValue !== undefined) {
|
||||
if (
|
||||
[ModelParamType.Float, ModelParamType.Int].includes(type) ||
|
||||
['modelType'].includes(k)
|
||||
) {
|
||||
defaultModel[k] = Number(defaultValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return defaultModel;
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化模型数据,根据 modelMeta 将特定字符串转化成数字
|
||||
* @param model
|
||||
* @param modelMeta
|
||||
* @returns
|
||||
*/
|
||||
export const formatModelData = (
|
||||
model: Record<string, unknown>,
|
||||
modelMeta: Model | undefined,
|
||||
): Record<string, unknown> => {
|
||||
const modelParamMap = keyBy(modelMeta?.model_params ?? [], 'name');
|
||||
return mapValues(model, (value, key) => {
|
||||
const modelParam = modelParamMap[snakeCase(key)];
|
||||
if (!modelParam || !isString(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const { type } = modelParam;
|
||||
|
||||
if (
|
||||
[ModelParamType.Float, ModelParamType.Int].includes(type) ||
|
||||
['modelType'].includes(key)
|
||||
) {
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
export const getDefaultLLMParams = (
|
||||
models: Model[],
|
||||
): Record<string, unknown> => {
|
||||
const modelMeta =
|
||||
models.find(m => m.model_type === DEFAULT_MODEL_TYPE) ?? models[0];
|
||||
|
||||
const llmParam = {
|
||||
modelType: modelMeta?.model_type,
|
||||
modelName: modelMeta?.name,
|
||||
generationDiversity: GenerationDiversity.Balance,
|
||||
...getDefaultModels(modelMeta),
|
||||
};
|
||||
|
||||
return llmParam;
|
||||
};
|
||||
|
||||
export const reviseLLMParamPair = (d: InputValueDTO): [string, unknown] => {
|
||||
let k = d?.name || '';
|
||||
|
||||
if (k === 'modleName') {
|
||||
k = 'modelName';
|
||||
}
|
||||
let v = d.input.value.content;
|
||||
if (
|
||||
[VariableTypeDTO.float, VariableTypeDTO.integer].includes(
|
||||
d.input.type as VariableTypeDTO,
|
||||
)
|
||||
) {
|
||||
v = Number(d.input.value.content);
|
||||
}
|
||||
|
||||
return [k, v];
|
||||
};
|
||||
356
frontend/packages/workflow/nodes/src/utils/node-utils.ts
Normal file
356
frontend/packages/workflow/nodes/src/utils/node-utils.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* 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 { isBoolean, isInteger, isNumber, isNil, get, set } from 'lodash-es';
|
||||
import {
|
||||
type SetterOrDecoratorContext,
|
||||
type IFormItemMeta,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { FlowNodeBaseType } from '@flowgram-adapter/free-layout-editor';
|
||||
import { nanoid } from '@flowgram-adapter/free-layout-editor';
|
||||
import { variableUtils } from '@coze-workflow/variable';
|
||||
import {
|
||||
type InputValueVO,
|
||||
type LiteralExpression,
|
||||
ValueExpressionType,
|
||||
BatchMode,
|
||||
type BatchDTO,
|
||||
type BatchVO,
|
||||
ViewVariableType,
|
||||
type BatchVOInputList,
|
||||
type ValueExpression,
|
||||
} from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { settingOnErrorInit, settingOnErrorSave } from '../setting-on-error';
|
||||
import {
|
||||
DEFAULT_BATCH_CONCURRENT_SIZE,
|
||||
DEFAULT_BATCH_SIZE,
|
||||
} from '../constants';
|
||||
export namespace nodeUtils {
|
||||
export const INPUT_PARAMS_PATH = 'inputs.inputParameters';
|
||||
const BATCH_MODE_PATH = 'inputs.batchMode';
|
||||
const BATCH_PATH = 'inputs.batch';
|
||||
const SETTING_ON_ERROR_PATH = 'inputs.settingOnError';
|
||||
const NODE_SETTING_ON_ERROR_PATH = 'settingOnError';
|
||||
|
||||
export type MapToArrayHandler<MapItem, ArrayItem> = (
|
||||
key: string,
|
||||
value: MapItem,
|
||||
) => ArrayItem;
|
||||
export type ArrayToMapHandler<ArrayItem, MapItem> = (
|
||||
item: ArrayItem,
|
||||
) => MapItem;
|
||||
|
||||
export function mapToArray<
|
||||
MapItem = InputValueVO['input'],
|
||||
ArrayItem = InputValueVO,
|
||||
>(
|
||||
map: Record<string, MapItem>,
|
||||
handle: MapToArrayHandler<MapItem, ArrayItem>,
|
||||
) {
|
||||
return Object.keys(map).map((key: string) => handle(key, map[key]));
|
||||
}
|
||||
|
||||
export function arrayToMap<
|
||||
ArrayItem = InputValueVO,
|
||||
MapItem = InputValueVO['input'],
|
||||
>(
|
||||
array: ArrayItem[],
|
||||
key: keyof ArrayItem,
|
||||
handler: ArrayToMapHandler<ArrayItem, MapItem>,
|
||||
) {
|
||||
const map: Record<string, MapItem> = {};
|
||||
array.forEach((item: ArrayItem): void => {
|
||||
map[item[key] as string] = handler(item);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export function batchToDTO(
|
||||
batchVO: BatchVO | undefined,
|
||||
nodeFormContext: any,
|
||||
): BatchDTO | undefined {
|
||||
if (!batchVO) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
playgroundContext: { variableService },
|
||||
} = nodeFormContext;
|
||||
|
||||
const {
|
||||
batchSize = DEFAULT_BATCH_SIZE,
|
||||
concurrentSize = DEFAULT_BATCH_CONCURRENT_SIZE,
|
||||
inputLists,
|
||||
} = batchVO;
|
||||
const inputListsDTO = inputLists.map(inputList => ({
|
||||
name: inputList.name,
|
||||
input: variableUtils.valueExpressionToDTO(
|
||||
inputList.input,
|
||||
variableService,
|
||||
{
|
||||
node: nodeFormContext?.node,
|
||||
},
|
||||
),
|
||||
}));
|
||||
return {
|
||||
batchSize,
|
||||
concurrentSize,
|
||||
inputLists: inputListsDTO,
|
||||
};
|
||||
}
|
||||
|
||||
export function batchToVO(
|
||||
batchDTO: BatchDTO | undefined,
|
||||
nodeFormContext: any,
|
||||
): BatchVO | undefined {
|
||||
if (!batchDTO) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
playgroundContext: { variableService },
|
||||
} = nodeFormContext;
|
||||
const { batchSize, concurrentSize, inputLists } = batchDTO;
|
||||
const inputListsVO = (inputLists || []).map(inputList => ({
|
||||
name: inputList.name,
|
||||
id: inputList.id,
|
||||
input: variableUtils.valueExpressionToVO(
|
||||
inputList.input,
|
||||
variableService,
|
||||
),
|
||||
}));
|
||||
return {
|
||||
batchSize,
|
||||
concurrentSize,
|
||||
inputLists: inputListsVO as BatchVOInputList[],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 variableUtils.valueExpressionToDTO)
|
||||
* @param value
|
||||
* @param nodeFormContext
|
||||
* @returns
|
||||
*/
|
||||
export function refExpressionToValueDTO(
|
||||
value: ValueExpression,
|
||||
nodeFormContext: any,
|
||||
) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
playgroundContext: { variableService },
|
||||
} = nodeFormContext;
|
||||
|
||||
return {
|
||||
input: variableUtils.valueExpressionToDTO(value, variableService, {
|
||||
node: nodeFormContext?.node,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 variableUtils.valueExpressionToDTO
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
export function literalExpressionToValueDTO(value: LiteralExpression) {
|
||||
if (isNil(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
type: variableUtils.getLiteralExpressionValueDTOType(value.content),
|
||||
value: {
|
||||
type: 'literal',
|
||||
content: !isNil(value.content) ? String(value.content) : '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getLiteralExpressionViewVariableType(
|
||||
content: LiteralExpression['content'],
|
||||
) {
|
||||
if (isNil(content)) {
|
||||
return ViewVariableType.String;
|
||||
}
|
||||
if (isInteger(content)) {
|
||||
return ViewVariableType.Integer;
|
||||
} else if (isNumber(content)) {
|
||||
return ViewVariableType.Number;
|
||||
} else if (isBoolean(content)) {
|
||||
return ViewVariableType.Boolean;
|
||||
} else {
|
||||
return ViewVariableType.String;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 variableUtils.valueExpressionToVO
|
||||
* @param value
|
||||
* @param nodeFormContext
|
||||
* @returns
|
||||
*/
|
||||
export function refExpressionDTOToVO(value: any, nodeFormContext: any) {
|
||||
if (isNil(value)) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
playgroundContext: { variableService },
|
||||
} = nodeFormContext;
|
||||
return variableUtils.valueExpressionToVO(value.input, variableService);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 variableUtils.valueExpressionToVO
|
||||
* @param input
|
||||
* @returns
|
||||
*/
|
||||
export function literalExpressionDTOToVO(input: any) {
|
||||
if (isNil(input)) {
|
||||
return;
|
||||
}
|
||||
const { type, value } = input;
|
||||
|
||||
return {
|
||||
type: 'literal',
|
||||
content: variableUtils.getLiteralValueWithType(type, value?.content),
|
||||
};
|
||||
}
|
||||
|
||||
// 获取batch表单项默认值
|
||||
export function getBatchInputListFormDefaultValue(index: number) {
|
||||
return {
|
||||
name: `item${index}`,
|
||||
id: nanoid(),
|
||||
input: {
|
||||
type: ValueExpressionType.REF,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 节点支持批量
|
||||
export function getBatchModeFormMeta(isBatchV2: boolean): IFormItemMeta {
|
||||
// TODO DELETE schemaGray 临时字段,后端灰度刷数据标记,全量后删除
|
||||
return {
|
||||
name: 'batchMode',
|
||||
type: 'string',
|
||||
default: 'single',
|
||||
abilities: [
|
||||
{
|
||||
type: 'setter',
|
||||
options: {
|
||||
key: 'Radio',
|
||||
type: 'button',
|
||||
options: [
|
||||
{
|
||||
value: 'single',
|
||||
label: I18n.t('workflow_batch_tab_single_radio'),
|
||||
},
|
||||
{
|
||||
value: 'batch',
|
||||
label: I18n.t('workflow_batch_tab_batch_radio'),
|
||||
disabled: (context: SetterOrDecoratorContext) => {
|
||||
const { node } = context;
|
||||
if (
|
||||
node.parent?.flowNodeType === FlowNodeBaseType.SUB_CANVAS
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'decorator',
|
||||
options: {
|
||||
key: 'FormCard',
|
||||
collapsible: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'visibility',
|
||||
options: {
|
||||
hidden: isBatchV2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// formValueToDto & dtoToFormValue 只迁移了api-node中对inputParameters、batch的适配
|
||||
export function formValueToDto(value: any, context) {
|
||||
const inputParams = get(value, INPUT_PARAMS_PATH);
|
||||
const formattedInputParams = inputParams
|
||||
? nodeUtils.mapToArray(inputParams, (key, mapValue) => ({
|
||||
name: key,
|
||||
input: mapValue,
|
||||
}))
|
||||
: [];
|
||||
|
||||
const batchMode = get(value, BATCH_MODE_PATH);
|
||||
const batch = get(value, BATCH_PATH);
|
||||
|
||||
const formattedBatch =
|
||||
batchMode === BatchMode.Batch
|
||||
? {
|
||||
batchEnable: true,
|
||||
...nodeUtils.batchToDTO(batch, context),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
set(value, INPUT_PARAMS_PATH, formattedInputParams);
|
||||
set(value, BATCH_PATH, formattedBatch);
|
||||
set(value, BATCH_MODE_PATH, undefined);
|
||||
set(value, SETTING_ON_ERROR_PATH, settingOnErrorSave(value).settingOnError);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function dtoToformValue(value, context) {
|
||||
const inputParams = get(value, INPUT_PARAMS_PATH);
|
||||
if (!inputParams || !Array.isArray(inputParams)) {
|
||||
return value;
|
||||
}
|
||||
const formattedInputParams = nodeUtils.arrayToMap(
|
||||
inputParams,
|
||||
'name',
|
||||
(arrayItem: InputValueVO) => arrayItem.input,
|
||||
);
|
||||
|
||||
const batch = get(value, BATCH_PATH);
|
||||
|
||||
const formattedBatchMode = batch?.batchEnable
|
||||
? BatchMode.Batch
|
||||
: BatchMode.Single;
|
||||
const formattedBatch = batch?.batchEnable
|
||||
? nodeUtils.batchToVO(batch, context)
|
||||
: undefined;
|
||||
|
||||
set(value, INPUT_PARAMS_PATH, formattedInputParams);
|
||||
set(value, BATCH_MODE_PATH, formattedBatchMode);
|
||||
set(value, BATCH_PATH, formattedBatch);
|
||||
set(
|
||||
value,
|
||||
NODE_SETTING_ON_ERROR_PATH,
|
||||
settingOnErrorInit(value).settingOnError,
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
28
frontend/packages/workflow/nodes/src/utils/trigger-form.ts
Normal file
28
frontend/packages/workflow/nodes/src/utils/trigger-form.ts
Normal 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 为什么要这么维护 triggerId?
|
||||
* 新的流程 fetchStartNodeTriggerFormValue 时没有 triggerId ,初次保存后,后端返回 triggerId
|
||||
* 已经保存过的流程, fetchStartNodeTriggerFormValue 时,会返回 triggerId
|
||||
* 获取时机不同,把 triggerId 硬塞到 formData 中比较麻烦,所以直接维护在 cacheTriggerId 中
|
||||
*/
|
||||
const cacheTriggerId: Record<string, string> = {};
|
||||
export const setTriggerId = (wfId: string, triggerId: string) => {
|
||||
cacheTriggerId[wfId] = triggerId;
|
||||
};
|
||||
|
||||
export const getTriggerId = (wfId: string) => cacheTriggerId[wfId];
|
||||
Reference in New Issue
Block a user