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,113 @@
/*
* 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, useState } from 'react';
import { useRequest } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import { Space, UIButton, UIToast } from '@coze-arch/bot-semi';
import {
type DebugExample,
type PluginAPIInfo,
DebugExampleStatus,
type UpdateAPIRequest,
PluginType,
} from '@coze-arch/bot-api/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { usePluginStore } from '@coze-studio/bot-plugin-store';
import { STATUS } from '../../components/plugin_modal/types';
import { ExampleCheckbox } from '../../components/example-checkbox';
interface DebugFooterProps {
btnLoading: boolean;
apiInfo: PluginAPIInfo | undefined;
dugStatus: STATUS | undefined;
loading: boolean;
nextStep: () => void;
previousStep?: () => void;
editVersion?: number;
isModal?: boolean;
}
export const useDebugFooter = ({
btnLoading,
apiInfo,
dugStatus,
loading,
nextStep,
editVersion,
}: DebugFooterProps) => {
const [saveExample, setSaveExample] = useState(
apiInfo?.debug_example_status === DebugExampleStatus.Enable,
);
const [debugExample, setDebugExample] = useState<DebugExample>();
const { loading: saveLoading, runAsync: runSaveExample } = useRequest(
(info: UpdateAPIRequest) => PluginDevelopApi.UpdateAPI(info),
{
manual: true,
},
);
const { pluginInfo } = usePluginStore(store => ({
pluginInfo: store.pluginInfo,
}));
const onSave = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
await runSaveExample({
plugin_id: pluginInfo?.plugin_id ?? '',
api_id: apiInfo?.api_id ?? '',
edit_version: editVersion ?? pluginInfo?.edit_version,
save_example: saveExample,
debug_example: debugExample,
});
UIToast.success(I18n.t('Save_success'));
nextStep();
};
useEffect(() => {
setSaveExample(apiInfo?.debug_example_status === DebugExampleStatus.Enable);
setDebugExample(
apiInfo?.debug_example_status === DebugExampleStatus.Enable
? apiInfo?.debug_example
: undefined,
);
}, [apiInfo]);
return {
debugFooterNode: (
<Space spacing={12}>
<ExampleCheckbox value={saveExample} onValueChange={setSaveExample} />
<UIButton
disabled={
loading ||
(dugStatus !== STATUS.PASS &&
pluginInfo?.plugin_type !== PluginType.LOCAL)
}
style={{ minWidth: 98, margin: 0 }}
loading={btnLoading || saveLoading}
type="primary"
theme="solid"
onClick={onSave}
>
{I18n.t('Create_newtool_s4_done')}
</UIButton>
</Space>
),
debugExample,
setDebugExample,
};
};

View File

@@ -0,0 +1,84 @@
/*
* 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, useState } from 'react';
import { cloneDeep } from 'lodash-es';
import {
type PluginAPIInfo,
DebugExampleStatus,
} from '@coze-arch/bot-api/plugin_develop';
import { usePluginStore } from '@coze-studio/bot-plugin-store';
import { addDepthAndValue } from '../../components/plugin_modal/utils';
import { ExampleModal } from '../../components/example-modal';
import { setEditToolExampleValue } from './utils';
// @ts-expect-error -- linter-disable-autofix
export const useEditExample = ({ onUpdate }) => {
const [visible, setVisible] = useState(false);
const [apiInfo, setApiInfo] = useState<PluginAPIInfo>();
const { pluginInfo } = usePluginStore(store => ({
pluginInfo: store.pluginInfo,
}));
const openExample = (info: PluginAPIInfo) => {
setVisible(true);
if (
info?.debug_example?.req_example &&
info?.debug_example_status === DebugExampleStatus.Enable
) {
const requestParams = cloneDeep(info?.request_params ?? []);
setEditToolExampleValue(
requestParams,
JSON.parse(info?.debug_example?.req_example),
);
addDepthAndValue(requestParams);
setApiInfo({ ...info, request_params: requestParams });
} else {
addDepthAndValue(info.request_params);
setApiInfo(info);
}
};
const closeExample = () => {
setVisible(false);
};
const onSave = () => {
onUpdate?.();
closeExample();
};
useEffect(() => {
if (!visible) {
setApiInfo(undefined);
}
}, [visible]);
return {
exampleNode: (
<ExampleModal
visible={visible}
onCancel={closeExample}
pluginId={pluginInfo?.plugin_id ?? ''}
apiInfo={apiInfo as PluginAPIInfo}
pluginName={pluginInfo?.meta_info?.name ?? ''}
onSave={onSave}
/>
),
openExample,
};
};

View File

@@ -0,0 +1,123 @@
/*
* 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, useState, Suspense, lazy } from 'react';
import { cloneDeep } from 'lodash-es';
import { I18n } from '@coze-arch/i18n';
import { UIModal } from '@coze-arch/bot-semi';
import {
type PluginParameter,
type DebugExample,
type PluginAPIInfo,
DebugExampleStatus,
} from '@coze-arch/bot-api/plugin_develop';
import { addDepthAndValue } from '../../components/plugin_modal/utils';
import { setStoreExampleValue, setWorkflowExampleValue } from './utils';
const LazyDebug = lazy(async () => {
const { Debug } = await import('../../components/plugin_modal/debug');
return {
default: Debug,
};
});
interface ShowExampleParams {
scene: 'workflow' | 'bot';
requestParams: PluginParameter[];
debugExample: DebugExample;
}
interface ViewExampleProps {
getPopupContainer?: () => HTMLElement;
}
export const useViewExample = (props?: ViewExampleProps) => {
const [visible, setVisible] = useState(false);
const [apiInfo, setApiInfo] = useState<PluginAPIInfo>();
const doShowExample = ({
scene,
requestParams,
debugExample,
}: ShowExampleParams) => {
if (!requestParams || !debugExample?.req_example) {
return;
}
const requestParamsData = cloneDeep(requestParams);
if (scene === 'workflow') {
setWorkflowExampleValue(
requestParamsData,
JSON.parse(debugExample?.req_example),
);
} else if (scene === 'bot') {
setStoreExampleValue(
requestParamsData,
JSON.parse(debugExample?.req_example),
);
} else {
return;
}
addDepthAndValue(requestParamsData);
setApiInfo({
debug_example_status: DebugExampleStatus.Enable,
request_params:
requestParamsData as unknown as PluginAPIInfo['request_params'],
debug_example: debugExample,
});
setVisible(true);
};
const closeExample = () => {
setVisible(false);
};
useEffect(() => {
if (!visible) {
setApiInfo(undefined);
}
}, [visible]);
return {
exampleNode: (
<UIModal
title={I18n.t('plugin_edit_tool_test_run_example_tip')}
visible={visible}
width={1280}
style={{ height: 'calc(100vh - 140px)', minWidth: '1040px' }}
centered
onCancel={closeExample}
footer={null}
getPopupContainer={props?.getPopupContainer}
>
{apiInfo ? (
<Suspense fallback={null}>
<LazyDebug
disabled={true}
pluginId={''}
apiId={apiInfo?.api_id ?? ''}
apiInfo={apiInfo as PluginAPIInfo}
pluginName={''}
debugExample={apiInfo?.debug_example}
isViewExample={true}
/>
</Suspense>
) : null}
</UIModal>
),
doShowExample,
};
};

View File

@@ -0,0 +1,171 @@
/*
* 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 { nanoid } from 'nanoid';
import { get } from 'lodash-es';
import {
ParameterType,
type APIParameter,
} from '@coze-arch/bot-api/plugin_develop';
export const typesConfig = {
string: ParameterType.String,
integer: ParameterType.Integer,
number: ParameterType.Number,
object: ParameterType.Object,
array: ParameterType.Array,
bool: ParameterType.Bool,
list: ParameterType.Array,
float: ParameterType.Number,
boolean: ParameterType.Bool,
};
// tool 数据回显用
interface ExampleReqParamsType {
[key: string]: string | number | null | object | boolean;
}
export const setEditToolExampleValue = (
requestParams: APIParameter[],
exampleReqParams: ExampleReqParamsType[],
) => {
// @ts-expect-error -- linter-disable-autofix
const setDefault = (originData: APIParameter[], currentJsonData) => {
originData.forEach((paramItem: APIParameter) => {
const currentPathValue = get(currentJsonData, paramItem?.name ?? '');
if (currentPathValue !== undefined) {
if (paramItem.type === ParameterType.Object) {
setDefault(paramItem.sub_parameters ?? [], currentPathValue);
} else if (paramItem.type === ParameterType.Array) {
paramItem.global_default = JSON.stringify(currentPathValue);
} else {
paramItem.global_default = currentPathValue;
}
}
});
};
setDefault(requestParams, exampleReqParams);
};
// 重置 type is_required sub_parameters
// @ts-expect-error -- linter-disable-autofix
export const resetWorkflowKey = currentTarget => {
if (Array.isArray(currentTarget)) {
currentTarget.forEach(obj => {
resetWorkflowKey(obj);
});
} else {
// @ts-expect-error -- linter-disable-autofix
currentTarget.type = typesConfig[currentTarget.type];
// @ts-expect-error -- linter-disable-autofix
currentTarget.sub_type = typesConfig[currentTarget.sub_type];
currentTarget.is_required = currentTarget.required;
currentTarget.global_disable = false;
currentTarget.local_disable = false;
currentTarget.location = undefined;
currentTarget.id = nanoid();
currentTarget.desc = currentTarget.description;
if ('schema' in currentTarget) {
if (currentTarget.type === ParameterType.Array) {
currentTarget.sub_parameters = [
{
name: '[Array Item]',
is_required: currentTarget.required,
// @ts-expect-error -- linter-disable-autofix
type: typesConfig[currentTarget.schema?.type],
global_disable: false,
local_disable: false,
location: undefined,
sub_type: 0,
sub_parameters: currentTarget.schema?.schema ?? [],
},
];
} else if (currentTarget.type === ParameterType.Object) {
currentTarget.sub_parameters = [...currentTarget.schema];
} else {
currentTarget.sub_parameters = [];
}
} else {
currentTarget.sub_parameters = [];
}
resetWorkflowKey(
currentTarget.type === ParameterType.Array
? currentTarget.sub_parameters[0].sub_parameters
: currentTarget?.sub_parameters,
);
}
};
// @ts-expect-error -- linter-disable-autofix
export const resetStoreKey = currentTarget => {
if (Array.isArray(currentTarget)) {
currentTarget.forEach(obj => {
resetStoreKey(obj);
});
} else {
// @ts-expect-error -- linter-disable-autofix
currentTarget.type = typesConfig[currentTarget.type];
// @ts-expect-error -- linter-disable-autofix
currentTarget.sub_type = typesConfig[currentTarget.sub_type];
currentTarget.is_required = currentTarget.required;
currentTarget.global_disable = false;
currentTarget.local_disable = false;
currentTarget.location = undefined;
currentTarget.id = nanoid();
// store 那边是 sub_params 字段 个人 的是 sub_parameters
if (!('sub_parameters' in currentTarget)) {
currentTarget.sub_parameters = [];
}
if ('sub_params' in currentTarget) {
currentTarget.sub_parameters = currentTarget.sub_params;
}
if (currentTarget.type === ParameterType.Array) {
currentTarget.sub_parameters = [
{
name: '[Array Item]',
is_required: currentTarget.required,
type: currentTarget.sub_type,
global_disable: false,
local_disable: false,
location: undefined,
sub_type: 0,
sub_parameters: [...currentTarget.sub_parameters],
},
];
resetStoreKey(currentTarget.sub_parameters[0].sub_parameters);
} else {
resetStoreKey(currentTarget.sub_parameters);
}
}
};
export const setStoreExampleValue = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
requestParams: any,
exampleReqParams: ExampleReqParamsType[],
) => {
resetStoreKey(requestParams);
setEditToolExampleValue(requestParams, exampleReqParams);
};
export const setWorkflowExampleValue = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
requestParams: any,
exampleReqParams: ExampleReqParamsType[],
) => {
resetWorkflowKey(requestParams);
setEditToolExampleValue(requestParams, exampleReqParams);
};

View File

@@ -0,0 +1,134 @@
/*
* 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/no-explicit-any */
import { useEffect, useMemo, useState } from 'react';
import { cloneDeep } from 'lodash-es';
import { useErrorHandler } from '@coze-arch/logger';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { type APIParameter } from '@coze-arch/bot-api/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import {
addDepthAndValue,
doRemoveDefaultFromResponseParams,
initParamsDefault,
updateNodeById,
} from '../../components/plugin_modal/utils';
import { ROWKEY } from '../../components/plugin_modal/config';
export interface SettingParamsProps {
botId?: string;
pluginId?: string;
devId?: string;
apiName?: string;
}
const useParametersInSettingModalController = (props: SettingParamsProps) => {
const capture = useErrorHandler();
const [isUpdateLoading, setIsUpdateLoading] = useState(!!0);
const [requestParams, setRequestParams] = useState<Array<APIParameter>>([]);
const [responseParams, setResponseParams] = useState<Array<APIParameter>>([]);
const [activeTab, setActiveTab] = useState(0);
const [loaded, setLoaded] = useState(!!0);
const commonParams = useMemo(
() => ({
bot_id: props.botId || '',
dev_id: props.devId || '',
plugin_id: props.pluginId || '',
api_name: props.apiName || '',
space_id: useSpaceStore.getState().getSpaceId(),
}),
[props],
);
const getColumnClass = (record: APIParameter) =>
record.global_disable ? 'disable' : 'normal';
const handleOpen = async () => {
try {
const { request_params, response_params } =
await PluginDevelopApi.GetBotDefaultParams({
...commonParams,
});
if (request_params && response_params) {
const reqParams = initParamsDefault(request_params, 'local_default');
addDepthAndValue(response_params);
addDepthAndValue(reqParams, 'local_default');
setRequestParams(reqParams);
const resParams = initParamsDefault(response_params, 'local_default');
setResponseParams(resParams);
setLoaded(true);
}
} catch (error) {
setLoaded(true);
capture(error);
}
};
const handleUpdate = async () => {
setIsUpdateLoading(!0);
await PluginDevelopApi.UpdateBotDefaultParams({
...commonParams,
request_params: requestParams,
response_params: doRemoveDefaultFromResponseParams(responseParams, false),
});
setIsUpdateLoading(!!0);
};
const updateNodeWithData = ({
record,
key,
value,
isForResponse = false,
}: {
record: APIParameter;
key: string;
value: any;
isForResponse?: boolean;
}) => {
updateNodeById({
data: isForResponse ? responseParams : requestParams,
targetKey: record[ROWKEY] as string,
field: key,
value,
});
const cloneData = cloneDeep(isForResponse ? responseParams : requestParams);
isForResponse ? setResponseParams(cloneData) : setRequestParams(cloneData);
};
useEffect(() => {
handleOpen();
}, []);
return {
doSetActive: setActiveTab,
doSetReqParams: setRequestParams,
doUpdateParams: handleUpdate,
doUpdateNodeWithData: updateNodeWithData,
getColumnClass,
loaded,
activeTab,
responseParams,
requestParams,
isUpdateLoading,
};
};
export { useParametersInSettingModalController };