feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
|
||||
.stop {
|
||||
width: 40px;
|
||||
margin-right: 16px;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.generate {
|
||||
:global {
|
||||
.semi-button-content-right {
|
||||
background-image: linear-gradient(90deg, #4D53E8, #4DCCE8);
|
||||
background-clip: text;
|
||||
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 { useRef, type CSSProperties } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tooltip, UIButton } from '@coze-arch/bot-semi';
|
||||
import { IconEffects, IconStopOutlined } from '@coze-arch/bot-icons';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type NodeFormSchema } from '../../types';
|
||||
import { useInnerStore } from '../../store';
|
||||
import { useTestsetManageStore } from '../../hooks';
|
||||
import { TestsetManageEventName } from '../../events';
|
||||
import { toNodeFormSchemas } from './utils';
|
||||
|
||||
import s from './auto-fill.module.less';
|
||||
|
||||
interface AutoFillButtonProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
onAutoFill?: (schemas: NodeFormSchema[]) => void;
|
||||
}
|
||||
|
||||
/** AI生成节点数据的按钮 */
|
||||
export function AutoFillButton({
|
||||
className,
|
||||
style,
|
||||
onAutoFill,
|
||||
}: AutoFillButtonProps) {
|
||||
const [FLAGS] = useFlags();
|
||||
const { generating, patch } = useInnerStore();
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const { bizComponentSubject, bizCtx, reportEvent } = useTestsetManageStore(
|
||||
store => store,
|
||||
);
|
||||
const onClick = async () => {
|
||||
// report event
|
||||
reportEvent?.(TestsetManageEventName.AIGC_PARAMS_CLICK, {
|
||||
path: 'testset',
|
||||
});
|
||||
|
||||
patch({ generating: true });
|
||||
try {
|
||||
abortRef.current = new AbortController();
|
||||
const { genCaseData } = await debuggerApi.AutoGenerateCaseData(
|
||||
{ bizComponentSubject, bizCtx, count: 1 },
|
||||
{ signal: abortRef.current.signal },
|
||||
);
|
||||
|
||||
if (!genCaseData?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fill form values
|
||||
const autoSchemas = toNodeFormSchemas(genCaseData[0].input);
|
||||
onAutoFill?.(autoSchemas);
|
||||
} finally {
|
||||
patch({ generating: false });
|
||||
}
|
||||
};
|
||||
|
||||
const onStop = () => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
|
||||
// 社区版暂不支持该功能
|
||||
if (!FLAGS['bot.devops.testset_auto_gen'] || !(IS_OVERSEA || IS_BOE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cls(s.wrapper, className)} style={style}>
|
||||
<Tooltip content={I18n.t('workflow_testset_stopgen')}>
|
||||
<UIButton
|
||||
icon={<IconStopOutlined />}
|
||||
className={cls(s.stop, generating || s.hidden)}
|
||||
onClick={onStop}
|
||||
/>
|
||||
</Tooltip>
|
||||
<UIButton
|
||||
loading={generating}
|
||||
icon={<IconEffects />}
|
||||
className={s.generate}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>
|
||||
{generating
|
||||
? I18n.t('workflow_testset_generating')
|
||||
: I18n.t('workflow_testset_aigenerate')}
|
||||
</UIButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.wrapper {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
color: var(--semi-color-text-0);
|
||||
|
||||
&.required::after {
|
||||
content: "*";
|
||||
font-weight: 600;
|
||||
color: var(--semi-color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
.type-label {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
color: var(--light-usage-text-color-text-3, rgb(29 28 35 / 35%));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type CSSProperties } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
|
||||
import s from './form-label.module.less';
|
||||
|
||||
interface FormLabelProps {
|
||||
label: string;
|
||||
typeLabel?: string;
|
||||
required?: boolean;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
// 内置的FormLabel样式不支持 typeLabel,所以简单自定义
|
||||
export function FormLabel({
|
||||
label,
|
||||
typeLabel,
|
||||
required,
|
||||
className,
|
||||
style,
|
||||
}: FormLabelProps) {
|
||||
return (
|
||||
<div className={cls(s.wrapper, className)} style={style}>
|
||||
<div className={cls(s.label, required && s.required)}>{label}</div>
|
||||
{typeLabel ? <div className={s['type-label']}>{typeLabel}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25" fill="none">
|
||||
<path d="M10.6777 4.83398L4.01387 11.9738C3.8246 12.1766 3.8246 12.4913 4.01387 12.6941L10.6777 19.834"
|
||||
stroke="#1D1C23" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M20 12.334H4" stroke="#1D1C23" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
@@ -0,0 +1,60 @@
|
||||
.sidesheet {
|
||||
:global {
|
||||
.semi-sidesheet-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.semi-sidesheet-content {
|
||||
background-color: var(--light-color-grey-grey-0, #F7F7FA);
|
||||
}
|
||||
|
||||
.semi-form-field-label-text {
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
|
||||
&::after {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 不要红色感叹号 与workflow对齐
|
||||
.semi-form-field-error-message .semi-form-field-validate-status-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.testset-desc {
|
||||
:global {
|
||||
.semi-input-textarea-counter {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
color: var(--light-usage-text-color-text-3, rgb(29 28 35 / 35%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-data-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 12px 0 16px;
|
||||
|
||||
>span {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 24px;
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
}
|
||||
|
||||
.auto-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* 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 { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type FormApi } from '@coze-arch/bot-semi/Form';
|
||||
import {
|
||||
UIButton,
|
||||
Form,
|
||||
UIFormTextArea,
|
||||
SideSheet,
|
||||
Spin,
|
||||
Tooltip,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { SideSheetTitle } from '../sidesheet-title';
|
||||
import {
|
||||
type FormItemSchema,
|
||||
type TestsetData,
|
||||
type TestsetDatabase,
|
||||
FormItemSchemaType,
|
||||
type NodeFormSchema,
|
||||
} from '../../types';
|
||||
import { useInnerStore } from '../../store';
|
||||
import { useTestsetManageStore } from '../../hooks';
|
||||
import { TestsetManageEventName } from '../../events';
|
||||
import {
|
||||
toNodeFormSchemas,
|
||||
isNil,
|
||||
traverseNodeFormSchemas,
|
||||
getTestsetNameRules,
|
||||
getSubFieldName,
|
||||
isSameType,
|
||||
transBoolSelect2Bool,
|
||||
type ValuesForBoolSelect,
|
||||
transFormItemSchema2Form,
|
||||
} from './utils';
|
||||
import { TestsetNameInput } from './testset-name-input';
|
||||
import { NodeFormSection } from './node-form-section';
|
||||
import { ReactComponent as IconBack } from './icon-back.svg';
|
||||
import { AutoFillButton } from './auto-fill';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface TestsetEditState {
|
||||
visible?: boolean;
|
||||
mode?: 'edit' | 'create';
|
||||
testset?: TestsetData;
|
||||
}
|
||||
|
||||
interface TestsetEditSideSheetProps extends TestsetEditState {
|
||||
mask?: boolean;
|
||||
onClose?: () => void;
|
||||
onSuccess?: (testset?: TestsetData) => void;
|
||||
onCancel?: () => void;
|
||||
/** 是否为多人协作模式 */
|
||||
isExpertMode?: boolean;
|
||||
}
|
||||
|
||||
const TESTSET_NAME_FIELD = '__TESTSET_NAME__';
|
||||
const TESTSET_DESC_FIELD = '__TESTSET_DESC__';
|
||||
|
||||
/**
|
||||
* 特化逻辑:表单项赋默认值
|
||||
* - Boolean类型:`false` 因为undefined的表现上和false一样,容易引发用户误解
|
||||
* - Object类型:`{}`
|
||||
* - Array类型: `[]`
|
||||
*/
|
||||
function assignDefaultValue(ipt: FormItemSchema) {
|
||||
if (!isNil(ipt.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ipt.type) {
|
||||
case FormItemSchemaType.BOOLEAN:
|
||||
// ipt.value = true;
|
||||
break;
|
||||
case FormItemSchemaType.OBJECT:
|
||||
ipt.value = '{}';
|
||||
break;
|
||||
case FormItemSchemaType.LIST:
|
||||
ipt.value = '[]';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
interface TestsetFormValue {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function -- form function component
|
||||
export function TestsetEditSideSheet({
|
||||
visible,
|
||||
mode,
|
||||
testset,
|
||||
mask,
|
||||
onClose,
|
||||
onSuccess,
|
||||
isExpertMode,
|
||||
}: TestsetEditSideSheetProps) {
|
||||
const { generating: autoGenerating } = useInnerStore();
|
||||
const { bizComponentSubject, bizCtx, reportEvent } = useTestsetManageStore(
|
||||
store => store,
|
||||
);
|
||||
|
||||
const testsetFormApi = useRef<FormApi<TestsetFormValue>>();
|
||||
const [validating, setValidating] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const { data: nodeSchemas, loading: loadingSchema } = useRequest(
|
||||
async () => {
|
||||
if (!visible) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const localSchemas = toNodeFormSchemas(testset?.caseBase?.input);
|
||||
const res = await debuggerApi.GetSchemaByID({
|
||||
bizComponentSubject,
|
||||
bizCtx,
|
||||
});
|
||||
const remoteSchemas = toNodeFormSchemas(res.schemaJson);
|
||||
|
||||
if (localSchemas.length) {
|
||||
// 编辑模式:比对本地和远程schema并尝试赋值
|
||||
const localSchemaMap: Record<string, FormItemSchema | undefined> = {};
|
||||
traverseNodeFormSchemas(
|
||||
localSchemas,
|
||||
(schema, ipt) => (localSchemaMap[getSubFieldName(schema, ipt)] = ipt),
|
||||
);
|
||||
|
||||
traverseNodeFormSchemas(remoteSchemas, (schema, ipt) => {
|
||||
const subName = getSubFieldName(schema, ipt);
|
||||
const field = localSchemaMap[subName];
|
||||
|
||||
if (isSameType(ipt.type, field?.type) && !isNil(field?.value)) {
|
||||
ipt.value = field?.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 创建模式:赋默认值
|
||||
traverseNodeFormSchemas(remoteSchemas, (schema, ipt) => {
|
||||
assignDefaultValue(ipt);
|
||||
});
|
||||
}
|
||||
|
||||
return remoteSchemas;
|
||||
},
|
||||
{ refreshDeps: [testset, visible] },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
testsetFormApi.current?.setValues({
|
||||
[TESTSET_NAME_FIELD]: testset?.caseBase?.name ?? '',
|
||||
[TESTSET_DESC_FIELD]: testset?.caseBase?.description,
|
||||
});
|
||||
}, [visible, testset]);
|
||||
|
||||
// 给节点表单设置值
|
||||
useEffect(() => {
|
||||
if (typeof nodeSchemas === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const values = testsetFormApi.current?.getValues() ?? {};
|
||||
traverseNodeFormSchemas(
|
||||
nodeSchemas,
|
||||
(schema, ipt) =>
|
||||
(values[getSubFieldName(schema, ipt)] =
|
||||
transFormItemSchema2Form(ipt)?.value),
|
||||
);
|
||||
testsetFormApi.current?.setValues(values);
|
||||
}, [nodeSchemas]);
|
||||
|
||||
const renderTitle = () => (
|
||||
<SideSheetTitle
|
||||
icon={<IconBack />}
|
||||
title={
|
||||
mode
|
||||
? mode === 'create'
|
||||
? I18n.t('workflow_testset_create_title')
|
||||
: I18n.t('workflow_testset_edit_title')
|
||||
: ''
|
||||
}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderFooter = () => (
|
||||
<div className="text-right">
|
||||
<Tooltip
|
||||
trigger={isExpertMode ? 'hover' : 'custom'}
|
||||
content={I18n.t('workflow_testset_submit_tooltip_for_expert_mode')}
|
||||
>
|
||||
<UIButton
|
||||
theme="solid"
|
||||
disabled={autoGenerating}
|
||||
loading={validating || submitting}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{I18n.t('workflow_testset_edit_confirm')}
|
||||
</UIButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderNodeForm = () => {
|
||||
if (loadingSchema) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
if (!nodeSchemas?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodeSchemas.map(schema => (
|
||||
<NodeFormSection
|
||||
key={schema.component_id}
|
||||
schema={schema}
|
||||
autoGenerating={autoGenerating}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const onConfirm = async () => {
|
||||
setValidating(true);
|
||||
try {
|
||||
await testsetFormApi.current?.validate();
|
||||
const errors = testsetFormApi.current?.getFormState().errors;
|
||||
|
||||
if (Object.keys(errors ?? {}).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSubmit();
|
||||
} finally {
|
||||
setValidating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const testsetFormValues = testsetFormApi.current?.getValues();
|
||||
|
||||
if (!testsetFormValues) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputSchemas = cloneDeep(nodeSchemas ?? []);
|
||||
traverseNodeFormSchemas(inputSchemas, (schema, ipt) => {
|
||||
const val = testsetFormValues[getSubFieldName(schema, ipt)];
|
||||
|
||||
if (!isNil(val)) {
|
||||
ipt.value = val;
|
||||
}
|
||||
|
||||
// 清除 object/array的空值,包括空字符串
|
||||
if (
|
||||
!val &&
|
||||
(ipt.type === FormItemSchemaType.LIST ||
|
||||
ipt.type === FormItemSchemaType.OBJECT)
|
||||
) {
|
||||
ipt.value = undefined;
|
||||
}
|
||||
|
||||
// bool 类型 需要将枚举转为布尔值
|
||||
if (ipt.type === FormItemSchemaType.BOOLEAN) {
|
||||
ipt.value = transBoolSelect2Bool(ipt.value as ValuesForBoolSelect);
|
||||
}
|
||||
});
|
||||
|
||||
const caseBase: TestsetDatabase = {
|
||||
name: testsetFormValues[TESTSET_NAME_FIELD],
|
||||
caseID: testset?.caseBase?.caseID,
|
||||
description: testsetFormValues[TESTSET_DESC_FIELD],
|
||||
input: JSON.stringify(inputSchemas),
|
||||
};
|
||||
|
||||
const saveResp = await debuggerApi.SaveCaseData({
|
||||
bizComponentSubject,
|
||||
bizCtx,
|
||||
caseBase,
|
||||
});
|
||||
|
||||
if (mode === 'create') {
|
||||
reportEvent?.(TestsetManageEventName.CREATE_TESTSET_SUCCESS);
|
||||
}
|
||||
onSuccess?.(saveResp.caseDetail);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** auto fill form values */
|
||||
const onAutoFill = (autoSchemas: NodeFormSchema[]) => {
|
||||
const formValues = testsetFormApi.current?.getValues() || {};
|
||||
const validateFields: string[] = [];
|
||||
|
||||
traverseNodeFormSchemas(autoSchemas, (schema, ipt) => {
|
||||
const fieldName = getSubFieldName(schema, ipt);
|
||||
const value = transFormItemSchema2Form(ipt)?.value;
|
||||
if (!isNil(value)) {
|
||||
formValues[fieldName] = value;
|
||||
validateFields.push(fieldName);
|
||||
}
|
||||
});
|
||||
|
||||
testsetFormApi.current?.setValues(formValues);
|
||||
// 设置值之后再校验一次
|
||||
testsetFormApi.current?.validate(validateFields);
|
||||
};
|
||||
|
||||
return (
|
||||
<SideSheet
|
||||
title={renderTitle()}
|
||||
footer={renderFooter()}
|
||||
visible={visible}
|
||||
mask={mask}
|
||||
className={s.sidesheet}
|
||||
width={600}
|
||||
closable={false}
|
||||
onCancel={onClose}
|
||||
>
|
||||
<Form<TestsetFormValue>
|
||||
getFormApi={api => (testsetFormApi.current = api)}
|
||||
>
|
||||
<TestsetNameInput
|
||||
field={TESTSET_NAME_FIELD}
|
||||
trigger="blur"
|
||||
stopValidateWithError={true}
|
||||
label={I18n.t('workflow_testset_name')}
|
||||
placeholder={I18n.t('workflow_testset_name_placeholder')}
|
||||
rules={getTestsetNameRules({
|
||||
bizCtx,
|
||||
bizComponentSubject,
|
||||
originVal: testset?.caseBase?.name,
|
||||
isOversea: IS_OVERSEA,
|
||||
})}
|
||||
/>
|
||||
<UIFormTextArea
|
||||
field={TESTSET_DESC_FIELD}
|
||||
className={s['testset-desc']}
|
||||
label={I18n.t('workflow_testset_desc')}
|
||||
placeholder={I18n.t('workflow_testset_desc_placeholder')}
|
||||
autosize={true}
|
||||
maxCount={200}
|
||||
maxLength={200}
|
||||
rows={2}
|
||||
/>
|
||||
<div className={s['node-data-title']}>
|
||||
<span>{I18n.t('workflow_testset_node_data')}</span>
|
||||
<AutoFillButton className={s['auto-btn']} onAutoFill={onAutoFill} />
|
||||
</div>
|
||||
{renderNodeForm()}
|
||||
</Form>
|
||||
</SideSheet>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
.section {
|
||||
:global .semi-form-section-text {
|
||||
border-bottom: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
}
|
||||
|
||||
.icon {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
|
||||
font-size: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
|
||||
.select-container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type CSSProperties, Fragment } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Form, UIFormSelect, UIFormTextArea } from '@coze-arch/bot-semi';
|
||||
import { ComponentType } from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
import {
|
||||
type NodeFormSchema,
|
||||
type FormItemSchema,
|
||||
FormItemSchemaType,
|
||||
} from '../../types';
|
||||
import { useTestsetManageStore } from '../../hooks';
|
||||
import {
|
||||
getCustomProps,
|
||||
getLabel,
|
||||
getPlaceholder,
|
||||
getSubFieldName,
|
||||
getTypeLabel,
|
||||
optionsForBoolSelect,
|
||||
} from './utils';
|
||||
import { FormLabel } from './form-label';
|
||||
|
||||
import s from './node-form-section.module.less';
|
||||
|
||||
interface NodeFormSectionProps {
|
||||
schema: NodeFormSchema;
|
||||
/** AI生成中 */
|
||||
autoGenerating?: boolean;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const { Section, InputNumber } = Form;
|
||||
|
||||
/** 整数类型表单精度 */
|
||||
const INTEGER_PRECISION = 0.1;
|
||||
|
||||
export function NodeFormSection({
|
||||
schema,
|
||||
autoGenerating,
|
||||
className,
|
||||
style,
|
||||
}: NodeFormSectionProps) {
|
||||
const formRenders = useTestsetManageStore(store => store.formRenders);
|
||||
|
||||
const renderSectionTitle = () => {
|
||||
let sectionName = schema.component_name;
|
||||
// 目前只有start和variable两种节点
|
||||
switch (schema.component_type) {
|
||||
case ComponentType.CozeStartNode:
|
||||
sectionName = I18n.t('workflow_testset_start_node');
|
||||
break;
|
||||
case ComponentType.CozeVariableBot:
|
||||
sectionName = I18n.t('workflow_testset_vardatabase_node');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.title}>
|
||||
{schema.component_icon ? (
|
||||
<div className={s.icon}>
|
||||
<img src={schema.component_icon} />
|
||||
</div>
|
||||
) : null}
|
||||
{sectionName}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFormItem = (formItemSchema: FormItemSchema) => {
|
||||
const { type, name, required } = formItemSchema;
|
||||
const CustomFormItem = formRenders?.[type];
|
||||
const fieldName = getSubFieldName(schema, formItemSchema);
|
||||
const placeholder = getPlaceholder(formItemSchema);
|
||||
const requiredMsg = I18n.t('workflow_testset_required_tip', {
|
||||
param_name: formItemSchema.type === FormItemSchemaType.BOT ? '' : name,
|
||||
});
|
||||
|
||||
if (typeof CustomFormItem !== 'undefined') {
|
||||
return (
|
||||
<CustomFormItem
|
||||
field={fieldName}
|
||||
disabled={autoGenerating}
|
||||
rules={[{ required, message: requiredMsg }]}
|
||||
noLabel={true}
|
||||
placeholder={placeholder}
|
||||
{...getCustomProps(formItemSchema)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case FormItemSchemaType.BOOLEAN:
|
||||
return (
|
||||
<UIFormSelect
|
||||
className={s['select-container']}
|
||||
field={fieldName}
|
||||
disabled={autoGenerating}
|
||||
rules={[{ required, message: requiredMsg }]}
|
||||
optionList={optionsForBoolSelect}
|
||||
noLabel={true}
|
||||
placeholder={placeholder}
|
||||
showClear={!required}
|
||||
/>
|
||||
);
|
||||
case FormItemSchemaType.INTEGER:
|
||||
case FormItemSchemaType.FLOAT:
|
||||
case FormItemSchemaType.NUMBER:
|
||||
return (
|
||||
<InputNumber
|
||||
field={fieldName}
|
||||
trigger={['change', 'blur']}
|
||||
precision={
|
||||
type === FormItemSchemaType.INTEGER
|
||||
? INTEGER_PRECISION
|
||||
: undefined
|
||||
}
|
||||
rules={[{ required, message: requiredMsg }]}
|
||||
disabled={autoGenerating}
|
||||
noLabel={true}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
case FormItemSchemaType.OBJECT:
|
||||
case FormItemSchemaType.LIST:
|
||||
return (
|
||||
<UIFormTextArea
|
||||
field={fieldName}
|
||||
trigger={['change', 'blur']}
|
||||
rules={[{ required, message: requiredMsg }]}
|
||||
disabled={autoGenerating}
|
||||
noLabel={true}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
case FormItemSchemaType.STRING:
|
||||
default:
|
||||
return (
|
||||
<UIFormTextArea
|
||||
field={fieldName}
|
||||
autosize={{ minRows: 2, maxRows: 5 }}
|
||||
trigger={['change', 'blur']}
|
||||
rules={[{ required, message: requiredMsg }]}
|
||||
disabled={autoGenerating}
|
||||
noLabel={true}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Section
|
||||
className={cls(s.section, className)}
|
||||
style={style}
|
||||
text={renderSectionTitle()}
|
||||
>
|
||||
{schema.inputs.map((formItemSchema, i) => (
|
||||
<Fragment key={i}>
|
||||
<FormLabel
|
||||
className={s.label}
|
||||
label={getLabel(formItemSchema)}
|
||||
typeLabel={getTypeLabel(formItemSchema)}
|
||||
required={formItemSchema.required}
|
||||
/>
|
||||
{renderFormItem(formItemSchema)}
|
||||
</Fragment>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
.suffix {
|
||||
margin-right: 12px;
|
||||
margin-left: 8px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
color: var(--light-usage-text-color-text-3, rgb(29 28 35 / 35%));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 ChangeEvent, type FocusEvent } from 'react';
|
||||
|
||||
import { type InputProps } from '@coze-arch/bot-semi/Input';
|
||||
import { type CommonFieldProps } from '@coze-arch/bot-semi/Form';
|
||||
import { withField, UIInput } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './testset-name-input.module.less';
|
||||
|
||||
const TESTSET_NAME_MAX_LEN = 50;
|
||||
|
||||
function count(val: unknown) {
|
||||
return val ? `${val}`.length : 0;
|
||||
}
|
||||
|
||||
/** 需要后缀 & blur trim,扩展下原始的input */
|
||||
function InnerInput(props: InputProps) {
|
||||
const onBlur = (evt: FocusEvent<HTMLInputElement>) => {
|
||||
props.onChange?.(
|
||||
`${props.value ?? ''}`.trim(),
|
||||
{} as unknown as ChangeEvent<HTMLInputElement>,
|
||||
);
|
||||
props.onBlur?.(evt);
|
||||
};
|
||||
|
||||
return (
|
||||
<UIInput
|
||||
{...props}
|
||||
maxLength={props.maxLength ?? TESTSET_NAME_MAX_LEN}
|
||||
autoComplete="off"
|
||||
onBlur={onBlur}
|
||||
suffix={
|
||||
<div className={s.suffix}>
|
||||
{count(props.value)}/{props.maxLength ?? TESTSET_NAME_MAX_LEN}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const TestsetNameInput = withField(InnerInput, {}) as (
|
||||
props: CommonFieldProps & InputProps,
|
||||
) => JSX.Element;
|
||||
@@ -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