feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,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;
}