feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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/naming-convention -- copy */
|
||||
import Ajv from 'ajv';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type RuleItem } from '@coze-arch/bot-semi/Form';
|
||||
import {
|
||||
type ComponentSubject,
|
||||
type BizCtx,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
type NodeFormSchema,
|
||||
type FormItemSchema,
|
||||
type ArrayFieldSchema,
|
||||
FormItemSchemaType,
|
||||
} from '../../types';
|
||||
|
||||
let ajv: Ajv | undefined;
|
||||
/** jsonStr转为节点表单schema(简单的`JSON.parse`) */
|
||||
export function toNodeFormSchemas(jsonStr?: string): NodeFormSchema[] {
|
||||
if (!jsonStr) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const schemas = JSON.parse(jsonStr) as NodeFormSchema[];
|
||||
return schemas;
|
||||
} catch (e: any) {
|
||||
logger.error(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** 空值判断,null/undefined/NaN */
|
||||
export function isNil(val: unknown) {
|
||||
return (
|
||||
typeof val === 'undefined' ||
|
||||
val === null ||
|
||||
(typeof val === 'number' && isNaN(val))
|
||||
);
|
||||
}
|
||||
|
||||
function isNumberType(t: string) {
|
||||
return t === FormItemSchemaType.NUMBER || t === FormItemSchemaType.FLOAT;
|
||||
}
|
||||
|
||||
/** 判断类型一致,**特化:**`number`和`float`视为同一类型 */
|
||||
export function isSameType(t1?: string, t2?: string) {
|
||||
if (typeof t1 === 'undefined' || typeof t2 === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isNumberType(t1) ? isNumberType(t2) : t1 === t2;
|
||||
}
|
||||
|
||||
/** 两层for遍历schema (经常需要遍历,单独抽一个的函数) */
|
||||
export function traverseNodeFormSchemas(
|
||||
schemas: NodeFormSchema[],
|
||||
cb: (s: NodeFormSchema, ip: FormItemSchema) => any,
|
||||
) {
|
||||
for (const schema of schemas) {
|
||||
for (const ipt of schema.inputs) {
|
||||
cb(schema, ipt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验名称格式(参考插件名称)
|
||||
* - 海外:仅支持输入字母、数字、下划线或空格
|
||||
* - 国内:仅支持输入中文、字母、数字、下划线或空格
|
||||
*/
|
||||
function validateNamePattern(
|
||||
name: string,
|
||||
isOversea?: boolean,
|
||||
): string | undefined {
|
||||
try {
|
||||
const pattern = isOversea ? /^[\w\s]+$/ : /^[\w\s\u4e00-\u9fa5]+$/u;
|
||||
const msg = isOversea
|
||||
? I18n.t('create_plugin_modal_nameerror')
|
||||
: I18n.t('create_plugin_modal_nameerror_cn');
|
||||
|
||||
return pattern.test(name) ? undefined : msg;
|
||||
} catch (e: any) {
|
||||
logger.error(e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
interface GetTestsetNameRulesProps {
|
||||
/** bizCtx */
|
||||
bizCtx?: BizCtx;
|
||||
/** bizComponentSubject */
|
||||
bizComponentSubject?: ComponentSubject;
|
||||
/** 原始值 */
|
||||
originVal?: string;
|
||||
/** 是否为海外(海外不允许输入中文 ,与PluginName校验规则对齐) */
|
||||
isOversea?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Testset名称表单校验规则
|
||||
*
|
||||
* @param param.bizCtx - bizCtx
|
||||
* @param param.bizComponentSubject - bizComponentSubject
|
||||
* @param param.originVal - 原始值
|
||||
* @param param.isOversea - 是否为海外(海外不允许输入中文 ,与PluginName校验规则对齐)
|
||||
*/
|
||||
export function getTestsetNameRules({
|
||||
bizCtx,
|
||||
bizComponentSubject,
|
||||
originVal,
|
||||
isOversea,
|
||||
}: GetTestsetNameRulesProps): RuleItem[] {
|
||||
const requiredMsg = I18n.t('workflow_testset_required_tip', {
|
||||
param_name: I18n.t('workflow_testset_name'),
|
||||
});
|
||||
|
||||
return [
|
||||
{ required: true, message: requiredMsg },
|
||||
{
|
||||
asyncValidator: async (_rules, value: string, cb) => {
|
||||
// required
|
||||
if (!value) {
|
||||
cb(requiredMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑模式下,名称与原名相同时跳过
|
||||
if (originVal && value === originVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 中文、字母等等等等
|
||||
const formatMsg = validateNamePattern(value, isOversea);
|
||||
|
||||
if (formatMsg) {
|
||||
cb(formatMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重复
|
||||
try {
|
||||
const { isPass } = await debuggerApi.CheckCaseDuplicate({
|
||||
bizCtx,
|
||||
bizComponentSubject,
|
||||
caseName: value,
|
||||
});
|
||||
|
||||
if (isPass) {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
cb(I18n.t('workflow_testset_name_duplicated'));
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no catch
|
||||
} catch {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单label
|
||||
* - bot:选择你需要的Bot
|
||||
* - 其他:字段名
|
||||
*/
|
||||
export function getLabel(formSchema: FormItemSchema) {
|
||||
return formSchema.type === FormItemSchemaType.BOT
|
||||
? I18n.t('workflow_testset_vardatabase_tip')
|
||||
: formSchema.name;
|
||||
}
|
||||
|
||||
function getSubType(type: string) {
|
||||
switch (type) {
|
||||
case FormItemSchemaType.STRING:
|
||||
return 'String';
|
||||
case FormItemSchemaType.FLOAT:
|
||||
case FormItemSchemaType.NUMBER:
|
||||
return 'Number';
|
||||
case FormItemSchemaType.OBJECT:
|
||||
return 'Object';
|
||||
case FormItemSchemaType.BOOLEAN:
|
||||
return 'Boolean';
|
||||
case FormItemSchemaType.INTEGER:
|
||||
return 'Integer';
|
||||
default:
|
||||
return `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** 类型标签 */
|
||||
export function getTypeLabel(formSchema: FormItemSchema) {
|
||||
switch (formSchema.type) {
|
||||
case FormItemSchemaType.STRING:
|
||||
case FormItemSchemaType.FLOAT:
|
||||
case FormItemSchemaType.NUMBER:
|
||||
case FormItemSchemaType.OBJECT:
|
||||
case FormItemSchemaType.BOOLEAN:
|
||||
case FormItemSchemaType.INTEGER:
|
||||
return getSubType(formSchema.type);
|
||||
case FormItemSchemaType.LIST: {
|
||||
const subType = (formSchema.schema as ArrayFieldSchema).type;
|
||||
return subType ? `Array<${getSubType(subType)}>` : 'Array';
|
||||
}
|
||||
case FormItemSchemaType.BOT:
|
||||
return '';
|
||||
default:
|
||||
return formSchema.type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* placeholder
|
||||
* - bot:请选择bot
|
||||
* - 其他:xx必填
|
||||
*/
|
||||
export function getPlaceholder({ name, type }: FormItemSchema) {
|
||||
if (type === FormItemSchemaType.BOT) {
|
||||
return I18n.t('workflow_testset_vardatabase_placeholder');
|
||||
} else if (type === FormItemSchemaType.BOOLEAN) {
|
||||
return I18n.t('workflow_testset_please_select');
|
||||
}
|
||||
|
||||
return I18n.t('workflow_detail_title_testrun_error_input', {
|
||||
a: name || '',
|
||||
});
|
||||
}
|
||||
|
||||
/** 字段在表单中的唯一字段名 */
|
||||
export function getSubFieldName(
|
||||
formSchema: NodeFormSchema,
|
||||
itemSchema: FormItemSchema,
|
||||
) {
|
||||
return `${itemSchema.name}_${formSchema.component_id}`;
|
||||
}
|
||||
|
||||
enum VariableTypeDTO {
|
||||
object = 'object',
|
||||
list = 'list',
|
||||
string = 'string',
|
||||
integer = 'integer',
|
||||
float = 'float',
|
||||
number = 'number',
|
||||
boolean = 'boolean',
|
||||
}
|
||||
|
||||
const VariableType2JsonSchemaProps = {
|
||||
[VariableTypeDTO.object]: { type: 'object' },
|
||||
[VariableTypeDTO.list]: { type: 'array' },
|
||||
[VariableTypeDTO.float]: { type: 'number' },
|
||||
[VariableTypeDTO.number]: { type: 'number' },
|
||||
[VariableTypeDTO.integer]: { type: 'integer' },
|
||||
[VariableTypeDTO.boolean]: { type: 'boolean' },
|
||||
[VariableTypeDTO.string]: { type: 'string' },
|
||||
};
|
||||
|
||||
function workflowJsonToJsonSchema(workflowJson: any) {
|
||||
const { type, description } = workflowJson;
|
||||
const props = VariableType2JsonSchemaProps[type];
|
||||
if (type === VariableTypeDTO.object) {
|
||||
const properties = {};
|
||||
const required: string[] = [];
|
||||
for (const field of workflowJson.schema) {
|
||||
properties[field.name] = workflowJsonToJsonSchema(field);
|
||||
if (field.required) {
|
||||
required.push(field.name);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...props,
|
||||
description,
|
||||
required,
|
||||
properties,
|
||||
};
|
||||
} else if (type === VariableTypeDTO.list) {
|
||||
return {
|
||||
...props,
|
||||
description,
|
||||
items: workflowJsonToJsonSchema(workflowJson.schema),
|
||||
};
|
||||
}
|
||||
return { ...props, description };
|
||||
}
|
||||
|
||||
function validateByJsonSchema(val: any, jsonSchema: any) {
|
||||
if (!jsonSchema || !val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ajv) {
|
||||
ajv = new Ajv();
|
||||
}
|
||||
|
||||
try {
|
||||
const validate = ajv.compile(jsonSchema);
|
||||
const valid = validate(JSON.parse(val));
|
||||
|
||||
return valid;
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no-catch
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义表单的额外参数
|
||||
* 目前只对array和object表单加jsonSchema校验
|
||||
*/
|
||||
export function getCustomProps(formItemSchema: FormItemSchema) {
|
||||
switch (formItemSchema.type) {
|
||||
case FormItemSchemaType.LIST:
|
||||
case FormItemSchemaType.OBJECT: {
|
||||
const jsonSchema = workflowJsonToJsonSchema(formItemSchema);
|
||||
|
||||
return {
|
||||
trigger: ['blur'],
|
||||
jsonSchema,
|
||||
rules: [
|
||||
{
|
||||
validator: (_rules, v, cb) => {
|
||||
if (formItemSchema.required && !v) {
|
||||
cb(
|
||||
I18n.t('workflow_testset_required_tip', {
|
||||
param_name: formItemSchema.name,
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateByJsonSchema(v, jsonSchema)) {
|
||||
cb(I18n.t('workflow_debug_wrong_json'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
] as RuleItem[],
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export enum ValuesForBoolSelect {
|
||||
TRUE = 'true',
|
||||
FALSE = 'false',
|
||||
UNDEFINED = 'undefined',
|
||||
}
|
||||
|
||||
/** 布尔类型选项 */
|
||||
export const optionsForBoolSelect = [
|
||||
{
|
||||
value: ValuesForBoolSelect.TRUE,
|
||||
label: 'true',
|
||||
},
|
||||
{
|
||||
value: ValuesForBoolSelect.FALSE,
|
||||
label: 'false',
|
||||
},
|
||||
];
|
||||
|
||||
export function transBoolSelect2Bool(val?: ValuesForBoolSelect) {
|
||||
switch (val) {
|
||||
case ValuesForBoolSelect.TRUE:
|
||||
return true;
|
||||
case ValuesForBoolSelect.FALSE:
|
||||
return false;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function transBool2BoolSelect(val?: boolean) {
|
||||
switch (val) {
|
||||
case true:
|
||||
return ValuesForBoolSelect.TRUE;
|
||||
case false:
|
||||
return ValuesForBoolSelect.FALSE;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function transFormItemSchema2Form(ipt?: FormItemSchema) {
|
||||
if (ipt?.type === FormItemSchemaType.BOOLEAN) {
|
||||
return {
|
||||
...ipt,
|
||||
value: transBool2BoolSelect(ipt.value as boolean | undefined),
|
||||
};
|
||||
}
|
||||
|
||||
return ipt;
|
||||
}
|
||||
Reference in New Issue
Block a user