feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { getIsStructOutput } from '../utils';
|
||||
import type { FeishuBaseConfigFe } from '../../types';
|
||||
import { type ConfigStore } from '../../store';
|
||||
import { mutateOutputStruct } from './output-struct';
|
||||
|
||||
export const useSubscribeAndUpdateConfig = (store: ConfigStore) => {
|
||||
useEffect(() => {
|
||||
const unsub = store.subscribe((state, prevState) => {
|
||||
const curConfig = state.config;
|
||||
const preConfig = prevState.config;
|
||||
const updatedConfig = produce<FeishuBaseConfigFe | null>(cfg =>
|
||||
mutateFieldsInteraction(cfg, preConfig),
|
||||
)(curConfig);
|
||||
if (!updatedConfig || isEqual(curConfig, updatedConfig)) {
|
||||
return;
|
||||
}
|
||||
state.setConfig(updatedConfig);
|
||||
});
|
||||
return unsub;
|
||||
}, []);
|
||||
};
|
||||
|
||||
const mutateFieldsInteraction = (
|
||||
config: FeishuBaseConfigFe | null,
|
||||
preConfig: FeishuBaseConfigFe | null,
|
||||
) => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
if (!getIsStructOutput(config.output_type)) {
|
||||
return;
|
||||
}
|
||||
if (isEqual(config, preConfig)) {
|
||||
return;
|
||||
}
|
||||
mutateOutputStruct(
|
||||
config.output_sub_component,
|
||||
preConfig?.output_sub_component,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 {
|
||||
verifyOutputStructFieldAsGroupByKey,
|
||||
verifyOutputStructFieldAsPrimaryKey,
|
||||
} from '../utils';
|
||||
import type {
|
||||
BaseOutputStructLineType,
|
||||
OutputSubComponentFe,
|
||||
} from '../../types';
|
||||
|
||||
export const mutateOutputStruct = (
|
||||
outputStructConfig: OutputSubComponentFe,
|
||||
preOutputStructConfig?: OutputSubComponentFe,
|
||||
) => {
|
||||
const fields = outputStructConfig.item_list;
|
||||
if (!fields || !fields.length) {
|
||||
return;
|
||||
}
|
||||
mutateOutputStructPrimaryKey(fields, preOutputStructConfig?.item_list);
|
||||
mutateOutputStructGroupByKey(fields, preOutputStructConfig?.item_list);
|
||||
};
|
||||
|
||||
const getIsPrimaryField = (field: BaseOutputStructLineType) => field.is_primary;
|
||||
const getIsGroupByKeyField = (field: BaseOutputStructLineType) =>
|
||||
field.is_group_by_key;
|
||||
|
||||
const mutateOutputStructGroupByKey = (
|
||||
fields: BaseOutputStructLineType[],
|
||||
preFields?: BaseOutputStructLineType[],
|
||||
) => {
|
||||
const groupByKeyFields = fields.filter(getIsGroupByKeyField);
|
||||
if (!groupByKeyFields.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getter = (field: BaseOutputStructLineType) => field.is_group_by_key;
|
||||
|
||||
const setter = (field: BaseOutputStructLineType, val: boolean) =>
|
||||
(field.is_group_by_key = val);
|
||||
|
||||
mutatePositiveFieldsMoreThanOne({
|
||||
curFields: groupByKeyFields,
|
||||
getter,
|
||||
setter,
|
||||
matchFn: filed => !!filed.is_group_by_key,
|
||||
preFields,
|
||||
});
|
||||
|
||||
mutateOnlyPositiveField({
|
||||
curFields: groupByKeyFields,
|
||||
getter,
|
||||
setter,
|
||||
verify: verifyOutputStructFieldAsGroupByKey,
|
||||
});
|
||||
};
|
||||
|
||||
const mutateOutputStructPrimaryKey = (
|
||||
fields: BaseOutputStructLineType[],
|
||||
preFields?: BaseOutputStructLineType[],
|
||||
) => {
|
||||
const primaryFields = fields.filter(getIsPrimaryField);
|
||||
if (!primaryFields.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setter = (field: BaseOutputStructLineType, val: boolean) =>
|
||||
(field.is_primary = val);
|
||||
const getter = (field: BaseOutputStructLineType) => field.is_primary;
|
||||
|
||||
mutatePositiveFieldsMoreThanOne({
|
||||
curFields: primaryFields,
|
||||
getter,
|
||||
setter,
|
||||
matchFn: field => !!field.is_primary,
|
||||
preFields,
|
||||
});
|
||||
|
||||
mutateOnlyPositiveField({
|
||||
curFields: primaryFields,
|
||||
getter,
|
||||
setter,
|
||||
verify: verifyOutputStructFieldAsPrimaryKey,
|
||||
});
|
||||
};
|
||||
|
||||
const mutateOnlyPositiveField = ({
|
||||
curFields,
|
||||
getter,
|
||||
setter,
|
||||
verify,
|
||||
}: {
|
||||
curFields: BaseOutputStructLineType[];
|
||||
getter: (field: BaseOutputStructLineType) => boolean | undefined;
|
||||
setter: (field: BaseOutputStructLineType, val: boolean) => void;
|
||||
verify: (field: BaseOutputStructLineType) => boolean;
|
||||
}) => {
|
||||
const onlyField = curFields.at(0);
|
||||
if (!onlyField) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notValid = !verify(onlyField);
|
||||
if (notValid && getter(onlyField)) {
|
||||
setter(onlyField, false);
|
||||
}
|
||||
};
|
||||
|
||||
const mutatePositiveFieldsMoreThanOne = ({
|
||||
curFields,
|
||||
matchFn,
|
||||
setter,
|
||||
getter,
|
||||
preFields,
|
||||
}: {
|
||||
curFields: BaseOutputStructLineType[];
|
||||
matchFn: (filed: BaseOutputStructLineType) => boolean;
|
||||
setter: (field: BaseOutputStructLineType, val: boolean) => void;
|
||||
getter: (field: BaseOutputStructLineType) => boolean | undefined;
|
||||
preFields?: BaseOutputStructLineType[];
|
||||
}) => {
|
||||
if (curFields.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preMatchedFieldsId =
|
||||
preFields?.filter(matchFn).map(field => field._id) || [];
|
||||
|
||||
curFields.forEach(field => {
|
||||
if (preMatchedFieldsId.includes(field._id) && getter(field)) {
|
||||
setter(field, false);
|
||||
}
|
||||
});
|
||||
|
||||
const leftMatchedFields = curFields.filter(matchFn);
|
||||
if (leftMatchedFields.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
leftMatchedFields.forEach((field, idx) => {
|
||||
const targetVal = idx === leftMatchedFields.length - 1;
|
||||
if (getter(field) !== targetVal) {
|
||||
setter(field, targetVal);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 { isNumber } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { InputComponentType } from '@coze-arch/bot-api/connector_api';
|
||||
|
||||
import {
|
||||
type BaseOutputStructLineType,
|
||||
type FeishuBaseConfigFe,
|
||||
type InputConfigFe,
|
||||
} from '../types';
|
||||
import { INPUT_CONFIG_TEXT_MAX_CHAR } from '../constants';
|
||||
import {
|
||||
getIsSelectType,
|
||||
getIsStructOutput,
|
||||
verifyOutputStructFieldAsGroupByKey,
|
||||
verifyOutputStructFieldAsPrimaryKey,
|
||||
} from './utils';
|
||||
|
||||
export const validateFullConfig = (config: FeishuBaseConfigFe): boolean => {
|
||||
if (!validateOutputConfig(config)) {
|
||||
return false;
|
||||
}
|
||||
const inputValid = validateInputConfig(config);
|
||||
// console.log('inputValid', inputValid);
|
||||
return inputValid;
|
||||
};
|
||||
|
||||
const validateOutputConfig = (config: FeishuBaseConfigFe) => {
|
||||
if (!getIsStructOutput(config.output_type)) {
|
||||
return isNumber(config.output_type);
|
||||
}
|
||||
const structFields = config.output_sub_component.item_list || [];
|
||||
const structPatternVerified = validateOutputStructPattern(structFields);
|
||||
if (!structPatternVerified) {
|
||||
return false;
|
||||
}
|
||||
return validateOutputStructGroupByKeyAndPrimaryKey(structFields);
|
||||
};
|
||||
|
||||
const validateOutputStructPattern = (
|
||||
structFields: BaseOutputStructLineType[],
|
||||
) => {
|
||||
if (structFields.length < 1) {
|
||||
return false;
|
||||
}
|
||||
return structFields.every(
|
||||
field => !!field.key && isNumber(field.output_type),
|
||||
);
|
||||
};
|
||||
|
||||
const validateOutputStructGroupByKeyAndPrimaryKey = (
|
||||
structFields: BaseOutputStructLineType[],
|
||||
): boolean => {
|
||||
const groupByKeyVerifyRes = validateOutputStructGroupByKey(structFields);
|
||||
if (!groupByKeyVerifyRes.ok) {
|
||||
return false;
|
||||
}
|
||||
const primaryKeyVerifyRes = validateOutputStructPrimaryKey(structFields);
|
||||
return primaryKeyVerifyRes.ok;
|
||||
};
|
||||
|
||||
interface StructOutputGroupByOrPrimaryKeyVerifyRes {
|
||||
ok: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export const validateOutputStructGroupByKey = (
|
||||
structFields: BaseOutputStructLineType[],
|
||||
): StructOutputGroupByOrPrimaryKeyVerifyRes => {
|
||||
const fields = structFields.filter(field => field.is_group_by_key);
|
||||
if (fields.length > 1) {
|
||||
return {
|
||||
ok: false,
|
||||
error: I18n.t('publish_base_configFields_requiredWarn'),
|
||||
};
|
||||
}
|
||||
const field = fields.at(0);
|
||||
if (!field) {
|
||||
return {
|
||||
ok: false,
|
||||
error: I18n.t('publish_base_configFields_requiredWarn'),
|
||||
};
|
||||
}
|
||||
if (!verifyOutputStructFieldAsGroupByKey(field)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: '',
|
||||
};
|
||||
}
|
||||
return { ok: true, error: '' };
|
||||
};
|
||||
|
||||
export const validateOutputStructPrimaryKey = (
|
||||
structFields: BaseOutputStructLineType[],
|
||||
): StructOutputGroupByOrPrimaryKeyVerifyRes => {
|
||||
const fields = structFields.filter(field => field.is_primary);
|
||||
if (fields.length > 1) {
|
||||
return {
|
||||
ok: false,
|
||||
error: I18n.t('publish_base_configFields_requiredWarn'),
|
||||
};
|
||||
}
|
||||
const field = fields.at(0);
|
||||
if (!field) {
|
||||
return {
|
||||
ok: false,
|
||||
error: I18n.t('publish_base_configFields_requiredWarn'),
|
||||
};
|
||||
}
|
||||
if (verifyOutputStructFieldAsPrimaryKey(field)) {
|
||||
return { ok: true, error: '' };
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: '',
|
||||
};
|
||||
};
|
||||
|
||||
const validateInputConfig = (config: FeishuBaseConfigFe) => {
|
||||
if (!config.input_config.length) {
|
||||
return false;
|
||||
}
|
||||
if (!validateInputFieldsCommonPattern(config.input_config)) {
|
||||
return false;
|
||||
}
|
||||
return config.input_config.every(validateSingleInputFieldControl);
|
||||
};
|
||||
|
||||
const validateInputFieldsCommonPattern = (inputConfigs: InputConfigFe[]) => {
|
||||
if (!inputConfigs.every(cfg => cfg.title && cfg.input_component)) {
|
||||
return false;
|
||||
}
|
||||
return !inputConfigs.some(cfg => cfg.invalid);
|
||||
};
|
||||
|
||||
export const validateSingleInputFieldControl = (
|
||||
inputControlConfig: InputConfigFe,
|
||||
): boolean => {
|
||||
const { type } = inputControlConfig.input_component;
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
if (type === InputComponentType.Text) {
|
||||
const maxChar = inputControlConfig.input_component.max_char;
|
||||
return (
|
||||
maxChar !== undefined &&
|
||||
Number.isInteger(maxChar) &&
|
||||
maxChar > 0 &&
|
||||
maxChar <= INPUT_CONFIG_TEXT_MAX_CHAR
|
||||
);
|
||||
}
|
||||
if (getIsSelectType(type)) {
|
||||
return (
|
||||
inputControlConfig.input_component.choice?.length > 0 &&
|
||||
inputControlConfig.input_component.choice.every(x => !!x.name.trim())
|
||||
);
|
||||
}
|
||||
return !!inputControlConfig.input_component.supported_type?.length;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 { InputComponentType } from '@coze-arch/bot-api/connector_api';
|
||||
|
||||
import { type BaseOutputStructLineType } from '../types';
|
||||
|
||||
const OUTPUT_TYPE_STRUCT = 25;
|
||||
export const OUTPUT_TYPE_TEXT = 1;
|
||||
const OUTPUT_TYPE_NUMBER = 2;
|
||||
|
||||
export const getIsStructOutput = (id: number): boolean =>
|
||||
id === OUTPUT_TYPE_STRUCT;
|
||||
|
||||
export const getIsTextOutput = (id: number | undefined): boolean =>
|
||||
id === OUTPUT_TYPE_TEXT;
|
||||
|
||||
export const getIsNumberOutput = (id: number | undefined): boolean =>
|
||||
id === OUTPUT_TYPE_NUMBER;
|
||||
|
||||
export const getIsSelectType = (type: InputComponentType) =>
|
||||
[InputComponentType.SingleSelect, InputComponentType.MultiSelect].includes(
|
||||
type,
|
||||
);
|
||||
|
||||
export const verifyOutputStructFieldAsGroupByKey = (
|
||||
field: BaseOutputStructLineType,
|
||||
) => getIsTextOutput(field.output_type);
|
||||
|
||||
export const verifyOutputStructFieldAsPrimaryKey = (
|
||||
field: BaseOutputStructLineType,
|
||||
) => {
|
||||
const outputType = field.output_type;
|
||||
return getIsTextOutput(outputType) || getIsNumberOutput(outputType);
|
||||
};
|
||||
Reference in New Issue
Block a user