feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
color: var(--Light-usage-text---color-text-0, #1D1C24);
|
||||
}
|
||||
|
||||
.line {
|
||||
align-items: center;
|
||||
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
margin-left: 12px;
|
||||
|
||||
background-color: var(--Light-usage-border---color-border-1, rgba(29, 28, 37, 12%));
|
||||
}
|
||||
|
||||
.tip {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 FC, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Checkbox, Tooltip } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface ExampleCheckboxProps {
|
||||
value: boolean;
|
||||
onValueChange: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export const ExampleCheckbox: FC<ExampleCheckboxProps> = ({
|
||||
value,
|
||||
onValueChange,
|
||||
}) => {
|
||||
const [showTip, setShowTip] = useState(false);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const onChange = e => {
|
||||
onValueChange(e.target.checked);
|
||||
if (!e.target.checked) {
|
||||
setShowTip(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.checkbox}>
|
||||
<div className={styles.content}>
|
||||
<Tooltip
|
||||
content={I18n.t('plugin_edit_tool_test_run_cancel_example')}
|
||||
visible={showTip}
|
||||
trigger="custom"
|
||||
>
|
||||
<Checkbox
|
||||
onChange={onChange}
|
||||
checked={value}
|
||||
onMouseEnter={() => value && setShowTip(true)}
|
||||
onMouseLeave={() => setShowTip(false)}
|
||||
></Checkbox>
|
||||
</Tooltip>
|
||||
|
||||
<div className={styles.label}>
|
||||
{I18n.t('plugin_edit_tool_test_run_save_results_as_example')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.line}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { useState, type FC } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIModal } from '@coze-arch/bot-semi';
|
||||
import { type PluginAPIInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { STATUS } from '../plugin_modal/types/modal';
|
||||
import { Debug } from '../plugin_modal/debug';
|
||||
import { useDebugFooter } from '../../hooks/example/use-debug-footer';
|
||||
|
||||
export enum ExampleScene {
|
||||
ViewExample,
|
||||
EditExample,
|
||||
ReadonlyExample,
|
||||
}
|
||||
|
||||
interface ExampleModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
apiInfo: PluginAPIInfo;
|
||||
pluginId: string;
|
||||
pluginName: string;
|
||||
onSave?: () => void;
|
||||
}
|
||||
|
||||
export const ExampleModal: FC<ExampleModalProps> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
apiInfo,
|
||||
pluginId,
|
||||
pluginName,
|
||||
onSave,
|
||||
}) => {
|
||||
const [dugStatus, setDebugStatus] = useState<STATUS | undefined>(STATUS.FAIL);
|
||||
const onNextStep = () => {
|
||||
onSave?.();
|
||||
setDebugStatus(undefined);
|
||||
};
|
||||
const cancelHandle = () => {
|
||||
onCancel();
|
||||
setDebugStatus(undefined);
|
||||
};
|
||||
const { debugFooterNode, setDebugExample, debugExample } = useDebugFooter({
|
||||
apiInfo,
|
||||
loading: false,
|
||||
dugStatus,
|
||||
btnLoading: false,
|
||||
nextStep: onNextStep,
|
||||
});
|
||||
return (
|
||||
<UIModal
|
||||
title={I18n.t('plugin_edit_tool_edit_example')}
|
||||
visible={visible}
|
||||
width={1280}
|
||||
style={{ height: 'calc(100vh - 140px)', minWidth: '1040px' }}
|
||||
centered
|
||||
onCancel={cancelHandle}
|
||||
footer={<div>{debugFooterNode}</div>}
|
||||
>
|
||||
{apiInfo ? (
|
||||
<Debug
|
||||
disabled={false}
|
||||
isViewExample={true}
|
||||
setDebugStatus={setDebugStatus}
|
||||
pluginId={pluginId}
|
||||
apiId={apiInfo?.api_id ?? ''}
|
||||
apiInfo={apiInfo as PluginAPIInfo}
|
||||
pluginName={pluginName}
|
||||
setDebugExample={setDebugExample}
|
||||
debugExample={debugExample}
|
||||
/>
|
||||
) : null}
|
||||
</UIModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { InfoPopover } from './info-popover';
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 { Fragment } from 'react';
|
||||
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, Typography } from '@coze-arch/coze-design';
|
||||
import { type ExtInfoText } from '@coze-studio/plugin-shared';
|
||||
|
||||
interface InfoPopoverProps {
|
||||
data: ExtInfoText[];
|
||||
}
|
||||
|
||||
export const InfoPopover: React.FC<InfoPopoverProps> = props => {
|
||||
const { data } = props;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
showArrow
|
||||
theme="dark"
|
||||
position="right"
|
||||
arrowPointAtCenter
|
||||
className="!max-w-[320px]"
|
||||
content={data?.map((item, index) => (
|
||||
<Fragment key={`${item.type}${index}`}>
|
||||
{/* 加粗标题 */}
|
||||
{item.type === 'title' ? (
|
||||
<Typography.Text fontSize="14px" className="dark coz-fg-primary">
|
||||
{item.text}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{/* 文本 */}
|
||||
{item.type === 'text' ? (
|
||||
<Typography.Paragraph
|
||||
fontSize="12px"
|
||||
className="dark coz-fg-secondary"
|
||||
>
|
||||
{item.text}
|
||||
</Typography.Paragraph>
|
||||
) : null}
|
||||
{/* 换行 */}
|
||||
{item.type === 'br' ? <div className="h-[8px]" /> : null}
|
||||
{/* 示例,边框内展示 */}
|
||||
{item.type === 'demo' ? (
|
||||
<div className="dark mt-[4px] p-[10px] border border-solid coz-stroke-primary">
|
||||
<Typography.Paragraph
|
||||
fontSize="12px"
|
||||
className="dark coz-fg-secondary"
|
||||
>
|
||||
{item.text}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
>
|
||||
<IconCozInfoCircle className="coz-fg-secondary" />
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import {
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIFormTextArea, Toast, Form } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
APIMethod,
|
||||
PluginType,
|
||||
type UpdateAPIResponse,
|
||||
type CreateAPIRequest,
|
||||
type CreateAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { ERROR_CODE, type RenderEnhancedComponentProps } from './types';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface UseBaseInfoRequest {
|
||||
space_id: string;
|
||||
pluginId: string;
|
||||
apiId?: string;
|
||||
baseInfo?: {
|
||||
name?: string;
|
||||
desc?: string;
|
||||
};
|
||||
setApiId?: (id: string) => void;
|
||||
showSecurityCheckFailedMsg?: boolean;
|
||||
setShowSecurityCheckFailedMsg?: Dispatch<SetStateAction<boolean>>;
|
||||
showModal: boolean;
|
||||
disabled: boolean;
|
||||
editVersion?: number;
|
||||
showFunctionName?: boolean;
|
||||
pluginType?: PluginType;
|
||||
onSuccess?: (params: UpdateAPIResponse | CreateAPIResponse) => void;
|
||||
renderEnhancedComponent?: RenderEnhancedComponentProps['renderDescComponent'];
|
||||
}
|
||||
|
||||
export interface UseBaseInfoReturnValue {
|
||||
submitBaseInfo: () => Promise<boolean>;
|
||||
baseInfoNode: JSX.Element;
|
||||
}
|
||||
|
||||
const ENTER_KEY_CODE = 13;
|
||||
|
||||
export const useBaseInfo = ({
|
||||
space_id,
|
||||
pluginId,
|
||||
apiId = '',
|
||||
baseInfo = {},
|
||||
setApiId,
|
||||
showModal,
|
||||
disabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
showFunctionName = false,
|
||||
pluginType,
|
||||
onSuccess,
|
||||
renderEnhancedComponent,
|
||||
}: UseBaseInfoRequest): UseBaseInfoReturnValue => {
|
||||
const formRef = useRef<Form>(null);
|
||||
const [originDesc, setOriginDesc] = useState<string | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
setOriginDesc(baseInfo?.desc);
|
||||
formRef.current?.formApi.setValues({
|
||||
name: baseInfo.name,
|
||||
desc: baseInfo.desc,
|
||||
});
|
||||
}, [baseInfo.name, baseInfo.desc, showModal, disabled]);
|
||||
const doSetDesc = useMemoizedFn((desc: string) => {
|
||||
formRef.current?.formApi.setValue('desc', desc);
|
||||
});
|
||||
|
||||
// 提交基础信息
|
||||
const submitBaseInfo = async () => {
|
||||
const status = await formRef.current?.formApi
|
||||
.validate()
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let baseResData;
|
||||
const formValues = formRef.current?.formApi.getValues();
|
||||
const params: CreateAPIRequest = {
|
||||
plugin_id: pluginId,
|
||||
name: formValues.name,
|
||||
desc: formValues.desc,
|
||||
edit_version: editVersion,
|
||||
function_name: formValues.function_name,
|
||||
};
|
||||
try {
|
||||
if (apiId) {
|
||||
baseResData = await PluginDevelopApi.UpdateAPI(
|
||||
{
|
||||
...params,
|
||||
api_id: apiId,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
baseResData = await PluginDevelopApi.CreateAPI(
|
||||
{
|
||||
...params,
|
||||
method: APIMethod.POST,
|
||||
path: `/${params.name}`,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
setApiId?.((baseResData as CreateAPIResponse).api_id || '');
|
||||
}
|
||||
onSuccess?.(baseResData);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code, msg } = error;
|
||||
if (Number(code) === ERROR_CODE.SAFE_CHECK) {
|
||||
setShowSecurityCheckFailedMsg?.(true);
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const changeVal = () => {
|
||||
if (showSecurityCheckFailedMsg) {
|
||||
setShowSecurityCheckFailedMsg?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
submitBaseInfo,
|
||||
baseInfoNode: (
|
||||
<>
|
||||
<Form<Record<string, unknown>>
|
||||
showValidateIcon={false}
|
||||
ref={formRef}
|
||||
disabled={disabled}
|
||||
className={s['base-info-form']}
|
||||
>
|
||||
{() =>
|
||||
disabled ? (
|
||||
<>
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('Create_newtool_s1_name'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
{baseInfo.name}
|
||||
</Form.Slot>
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('Create_newtool_s1_dercribe'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
{baseInfo.desc}
|
||||
</Form.Slot>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<UIFormTextArea
|
||||
data-testid="plugin-create-tool-base-info-name"
|
||||
className={s['textarea-single-line']}
|
||||
field="name"
|
||||
label={I18n.t('Create_newtool_s1_name')}
|
||||
placeholder={I18n.t('Create_newtool_s1_title_empty')}
|
||||
trigger={['blur', 'change']}
|
||||
maxCount={30}
|
||||
maxLength={30}
|
||||
rows={1}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onKeyDown={(ele: any) => {
|
||||
const e = window.event || ele;
|
||||
if (
|
||||
e.key === 'Enter' ||
|
||||
e.code === 'Enter' ||
|
||||
e.keyCode === ENTER_KEY_CODE
|
||||
) {
|
||||
e.returnValue = false;
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
onChange={changeVal}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('Create_newtool_s1_title_empty'),
|
||||
},
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_]+$/,
|
||||
message: I18n.t('Create_newtool_s1_title_error1'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="relative">
|
||||
{renderEnhancedComponent?.({
|
||||
disabled: !originDesc,
|
||||
originDesc,
|
||||
className: 'absolute right-[0] top-[12px]',
|
||||
plugin_id: pluginId,
|
||||
space_id,
|
||||
onSetDescription: doSetDesc,
|
||||
})}
|
||||
<UIFormTextArea
|
||||
data-testid="plugin-create-tool-base-info-desc"
|
||||
field="desc"
|
||||
label={I18n.t('Create_newtool_s1_dercribe')}
|
||||
placeholder={I18n.t('Create_newtool_s1_dercribe_error')}
|
||||
rows={2}
|
||||
trigger={['blur', 'change']}
|
||||
maxCount={600}
|
||||
maxLength={600}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('Create_newtool_s1_dercribe_empty'),
|
||||
},
|
||||
IS_OVERSEA && {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
pattern: /^[\x00-\x7F]+$/,
|
||||
message: I18n.t('create_plugin_modal_descrip_error'),
|
||||
},
|
||||
]}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
onChange={v => {
|
||||
changeVal();
|
||||
setOriginDesc(v);
|
||||
}}
|
||||
/>
|
||||
{showFunctionName && pluginType === PluginType.LOCAL ? (
|
||||
<UIFormTextArea
|
||||
className={s['textarea-single-line']}
|
||||
field="function_name"
|
||||
label={I18n.t('create_local_plugin_basic_tool_function')}
|
||||
placeholder={I18n.t(
|
||||
'create_local_plugin_basic_tool_function_input_placeholder',
|
||||
)}
|
||||
rows={1}
|
||||
trigger={['blur', 'change']}
|
||||
maxCount={30}
|
||||
maxLength={30}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t(
|
||||
'create_local_plugin_basic_warning_no_tool_function_entered',
|
||||
),
|
||||
},
|
||||
]}
|
||||
onChange={changeVal}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Form>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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 max-lines-per-function */
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { type Dispatch, type SetStateAction, useEffect, useRef } from 'react';
|
||||
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
UIFormInput,
|
||||
UIFormSelect,
|
||||
UIFormTextArea,
|
||||
Typography,
|
||||
Toast,
|
||||
Form,
|
||||
Tooltip,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
APIMethod,
|
||||
type PluginMetaInfo,
|
||||
AuthorizationType,
|
||||
PluginToolAuthType,
|
||||
type APIExtend,
|
||||
PluginType,
|
||||
type UpdateAPIRequest,
|
||||
type UpdateAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { InfoPopover } from '../info_popover';
|
||||
import { ERROR_CODE } from './types';
|
||||
import { methodType } from './config';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const { Option } = UIFormSelect;
|
||||
|
||||
export interface UseBaseInfoRequest {
|
||||
pluginId: string;
|
||||
pluginMeta: PluginMetaInfo;
|
||||
apiId?: string;
|
||||
step?: number;
|
||||
baseInfo?: {
|
||||
name?: string;
|
||||
desc?: string;
|
||||
path?: string;
|
||||
method?: APIMethod;
|
||||
api_extend?: APIExtend;
|
||||
function_name?: string;
|
||||
};
|
||||
showSecurityCheckFailedMsg?: boolean;
|
||||
setShowSecurityCheckFailedMsg?: Dispatch<SetStateAction<boolean>>;
|
||||
showModal: boolean;
|
||||
disabled: boolean;
|
||||
editVersion?: number;
|
||||
pluginType?: PluginType;
|
||||
spaceId?: string;
|
||||
onSuccess?: (params: UpdateAPIResponse) => void;
|
||||
}
|
||||
|
||||
export interface UseBaseInfoReturnValue {
|
||||
submitBaseInfo: () => Promise<boolean>;
|
||||
baseInfoNode: JSX.Element;
|
||||
}
|
||||
|
||||
export const useBaseMore = ({
|
||||
pluginId,
|
||||
pluginMeta,
|
||||
apiId = '',
|
||||
baseInfo = {},
|
||||
showModal,
|
||||
disabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
pluginType,
|
||||
onSuccess,
|
||||
}: UseBaseInfoRequest): UseBaseInfoReturnValue => {
|
||||
const { url: pluginUrl } = pluginMeta;
|
||||
const formRef = useRef<Form>(null);
|
||||
useEffect(() => {
|
||||
formRef.current?.formApi.setValues({
|
||||
path: baseInfo.path,
|
||||
method: baseInfo.method || APIMethod.GET,
|
||||
function_name: baseInfo.function_name,
|
||||
auth_mode: baseInfo.api_extend?.auth_mode || PluginToolAuthType.Required,
|
||||
});
|
||||
}, [
|
||||
baseInfo.path,
|
||||
showModal,
|
||||
disabled,
|
||||
pluginMeta,
|
||||
baseInfo.method,
|
||||
baseInfo.function_name,
|
||||
baseInfo.api_extend?.auth_mode,
|
||||
]);
|
||||
|
||||
// 提交基础信息
|
||||
const submitBaseInfo = async () => {
|
||||
const status = await formRef.current?.formApi
|
||||
.validate()
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!status || !apiId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let baseResData;
|
||||
const formValues = formRef.current?.formApi.getValues();
|
||||
const params: UpdateAPIRequest = {
|
||||
api_id: apiId,
|
||||
plugin_id: pluginId,
|
||||
path: formValues.path,
|
||||
method: formValues.method,
|
||||
api_extend: {
|
||||
auth_mode: formValues.auth_mode,
|
||||
},
|
||||
edit_version: editVersion,
|
||||
function_name: formValues.function_name,
|
||||
};
|
||||
try {
|
||||
baseResData = await PluginDevelopApi.UpdateAPI(params, {
|
||||
__disableErrorToast: true,
|
||||
});
|
||||
onSuccess?.(baseResData);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code, msg } = error;
|
||||
if (Number(code) === ERROR_CODE.SAFE_CHECK) {
|
||||
setShowSecurityCheckFailedMsg?.(true);
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const changeVal = () => {
|
||||
if (showSecurityCheckFailedMsg) {
|
||||
setShowSecurityCheckFailedMsg?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
submitBaseInfo,
|
||||
baseInfoNode: (
|
||||
<>
|
||||
<Form<Record<string, unknown>>
|
||||
showValidateIcon={false}
|
||||
ref={formRef}
|
||||
disabled={disabled}
|
||||
className={s['base-info-form']}
|
||||
>
|
||||
{() =>
|
||||
disabled ? (
|
||||
<>
|
||||
{pluginType === PluginType.LOCAL && (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_local_plugin_basic_tool_function'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
{baseInfo.function_name ?? '-'}
|
||||
</Form.Slot>
|
||||
)}
|
||||
{pluginType === PluginType.PLUGIN && (
|
||||
<>
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('Create_newtool_s1_url'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
{String(pluginUrl) + baseInfo.path}
|
||||
</Form.Slot>
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('Create_newtool_s1_method'),
|
||||
required: true,
|
||||
extra: <InfoPopover data={methodType} />,
|
||||
}}
|
||||
>
|
||||
{API_METHOD_LABEL_MAP[baseInfo?.method || APIMethod.GET]}
|
||||
</Form.Slot>
|
||||
</>
|
||||
)}
|
||||
{pluginMeta?.auth_type?.includes(AuthorizationType.OAuth) ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('plugin_edit_tool_oauth_enabled_title'),
|
||||
required: true,
|
||||
extra: (
|
||||
<Tooltip
|
||||
content={I18n.t(
|
||||
'plugin_edit_tool_oauth_enabled_title_hover_tip',
|
||||
)}
|
||||
>
|
||||
<IconInfo
|
||||
style={{ color: 'rgba(28, 29, 35, 0.35)' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{
|
||||
API_MODE_LABEL_MAP[
|
||||
baseInfo.api_extend?.auth_mode ||
|
||||
PluginToolAuthType.Required
|
||||
]
|
||||
}
|
||||
</Form.Slot>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{pluginType === PluginType.LOCAL && (
|
||||
<UIFormTextArea
|
||||
className={s['textarea-single-line']}
|
||||
field="function_name"
|
||||
label={I18n.t('create_local_plugin_basic_tool_function')}
|
||||
placeholder={I18n.t(
|
||||
'create_local_plugin_basic_tool_function_input_placeholder',
|
||||
)}
|
||||
rows={1}
|
||||
trigger={['blur', 'change']}
|
||||
maxCount={30}
|
||||
maxLength={30}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t(
|
||||
'create_local_plugin_basic_warning_no_tool_function_entered',
|
||||
),
|
||||
},
|
||||
]}
|
||||
onChange={changeVal}
|
||||
/>
|
||||
)}
|
||||
{pluginType === PluginType.PLUGIN && (
|
||||
<>
|
||||
<UIFormInput
|
||||
field="path"
|
||||
label={{
|
||||
text: I18n.t('Create_newtool_s1_url'),
|
||||
}}
|
||||
trigger={['blur', 'change']}
|
||||
addonBefore={
|
||||
<div className={s['plugin-url-prefix']}>
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: {
|
||||
content: pluginUrl,
|
||||
style: {
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{pluginUrl}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
}
|
||||
style={{ width: '100%' }}
|
||||
className={s['plugin-url-input']}
|
||||
placeholder={I18n.t('Create_newtool_s1_url_empty')}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('Create_newtool_s1_url_error2'),
|
||||
},
|
||||
{
|
||||
pattern: /^\//,
|
||||
message: I18n.t('Create_newtool_s1_url_error1'),
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line no-control-regex
|
||||
pattern: /^[\x00-\x7F]+$/,
|
||||
message: I18n.t('tool_new_S1_URL_error'),
|
||||
},
|
||||
]}
|
||||
></UIFormInput>
|
||||
<UIFormSelect
|
||||
field="method"
|
||||
initValue={APIMethod.GET}
|
||||
label={{
|
||||
text: I18n.t('Create_newtool_s1_method'),
|
||||
extra: <InfoPopover data={methodType} />,
|
||||
}}
|
||||
showClear
|
||||
trigger={['blur', 'change']}
|
||||
style={{ width: '100%', borderRadius: '8px' }}
|
||||
placeholder={I18n.t(
|
||||
'workflow_detail_condition_pleaseselect',
|
||||
)}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t(
|
||||
'workflow_detail_condition_pleaseselect',
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{[
|
||||
APIMethod.GET,
|
||||
APIMethod.POST,
|
||||
APIMethod.PUT,
|
||||
APIMethod.DELETE,
|
||||
APIMethod.PATCH,
|
||||
].map(method => (
|
||||
<Option value={method} key={method}>
|
||||
{API_METHOD_LABEL_MAP[method]}
|
||||
</Option>
|
||||
))}
|
||||
</UIFormSelect>
|
||||
{pluginMeta?.auth_type?.includes(
|
||||
AuthorizationType.OAuth,
|
||||
) ? (
|
||||
<UIFormSelect
|
||||
field="auth_mode"
|
||||
initValue={PluginToolAuthType.Required}
|
||||
label={{
|
||||
text: I18n.t('plugin_edit_tool_oauth_enabled_title'),
|
||||
extra: (
|
||||
<Tooltip
|
||||
content={I18n.t(
|
||||
'plugin_edit_tool_oauth_enabled_title_hover_tip',
|
||||
)}
|
||||
>
|
||||
<IconInfo
|
||||
style={{ color: 'rgba(28, 29, 35, 0.35)' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
}}
|
||||
showClear
|
||||
trigger={['blur', 'change']}
|
||||
style={{ width: '100%', borderRadius: '8px' }}
|
||||
placeholder={I18n.t(
|
||||
'workflow_detail_condition_pleaseselect',
|
||||
)}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t(
|
||||
'workflow_detail_condition_pleaseselect',
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{[
|
||||
PluginToolAuthType.Required,
|
||||
PluginToolAuthType.Supported,
|
||||
PluginToolAuthType.Disable,
|
||||
].map(mode => (
|
||||
<Option value={mode} key={mode}>
|
||||
{API_MODE_LABEL_MAP[mode]}
|
||||
</Option>
|
||||
))}
|
||||
</UIFormSelect>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Form>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const API_METHOD_LABEL_MAP: Record<APIMethod, string> = {
|
||||
[APIMethod.GET]: I18n.t('Create_newtool_s1_method_get'),
|
||||
[APIMethod.POST]: I18n.t('Create_newtool_s1_method_post'),
|
||||
[APIMethod.PUT]: I18n.t('Create_newtool_s1_method_put'),
|
||||
[APIMethod.DELETE]: I18n.t('Create_newtool_s1_method_delete'),
|
||||
[APIMethod.PATCH]: I18n.t('Create_tool_s1_method_patch_name'),
|
||||
};
|
||||
|
||||
const API_MODE_LABEL_MAP: Record<PluginToolAuthType, string> = {
|
||||
[PluginToolAuthType.Required]: I18n.t(
|
||||
'plugin_edit_tool_oauth_enabled_status_auth_required',
|
||||
),
|
||||
[PluginToolAuthType.Supported]: I18n.t(
|
||||
'plugin_edit_tool_oauth_enabled_status_auth_optional',
|
||||
),
|
||||
[PluginToolAuthType.Disable]: I18n.t(
|
||||
'plugin_edit_tool_oauth_enabled_status_auth_disabled',
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 FC, useEffect, useState } from 'react';
|
||||
|
||||
import cl from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type CascaderProps } from '@coze-arch/coze-design';
|
||||
import { Typography, UICascader } from '@coze-arch/bot-semi';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
|
||||
import {
|
||||
type APIParameterRecord,
|
||||
type CascaderOnChangValueType,
|
||||
type CascaderValueType,
|
||||
type InputItemProps,
|
||||
} from '../../types/params';
|
||||
import s from '../../index.module.less';
|
||||
import {
|
||||
ARRAYTAG,
|
||||
assistToExtend,
|
||||
extendToAssist,
|
||||
getParameterTypeLabel,
|
||||
getPluginParameterTypeOptions,
|
||||
ParameterTypeExtend,
|
||||
} from '../../config';
|
||||
|
||||
const getCascaderValueTypeFrom = (
|
||||
record?: APIParameterRecord,
|
||||
): CascaderValueType => {
|
||||
if (record?.assist_type) {
|
||||
return [ParameterTypeExtend.DEFAULT, assistToExtend(record.assist_type)];
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
return [record.type];
|
||||
};
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface CProps extends Omit<InputItemProps, 'selectCallback'> {
|
||||
selectCallback: (types: CascaderOnChangValueType) => void;
|
||||
enableFileType?: boolean;
|
||||
}
|
||||
|
||||
export const CascaderItem: FC<CProps> = ({
|
||||
check = 0,
|
||||
useBlockWrap = false,
|
||||
record,
|
||||
disabled,
|
||||
selectCallback,
|
||||
enableFileType = false,
|
||||
}) => {
|
||||
const [value, setValue] = useState<CascaderValueType>(
|
||||
getCascaderValueTypeFrom(record),
|
||||
);
|
||||
const [errorStatus, setErrorStatus] = useState<number>(0);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const isArrayType = record.name === ARRAYTAG;
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const isObjectField = (record.deep ?? 0) > 1 && record.name !== ARRAYTAG;
|
||||
|
||||
// 通过check触发校验(提交时)
|
||||
useEffect(() => {
|
||||
if (check === 0) {
|
||||
return;
|
||||
}
|
||||
handleCheck(value);
|
||||
}, [check]);
|
||||
|
||||
// 校验
|
||||
const handleCheck = (val?: CascaderValueType) => {
|
||||
const status = !val?.[0] ? 1 : 0;
|
||||
setErrorStatus(status);
|
||||
};
|
||||
|
||||
const onChange = (types: CascaderValueType) => {
|
||||
if (types[1]) {
|
||||
selectCallback([types[0], extendToAssist(types[1])]);
|
||||
} else {
|
||||
selectCallback([types[0]]);
|
||||
}
|
||||
setValue(types);
|
||||
handleCheck(types);
|
||||
};
|
||||
|
||||
const displayRender: CascaderProps['displayRender'] = (items, idx) => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
let inputValue: string = items[0];
|
||||
|
||||
if (value[1]) {
|
||||
if (value[1] === ParameterTypeExtend.DEFAULT) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
inputValue = getParameterTypeLabel(
|
||||
ParameterTypeExtend.DEFAULT,
|
||||
isArrayType,
|
||||
);
|
||||
} else {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
inputValue = items[1];
|
||||
}
|
||||
}
|
||||
|
||||
return <Text ellipsis={{ showTooltip: true }}>{inputValue}</Text>;
|
||||
};
|
||||
|
||||
const parameterTypeOptionsWithCustom = getPluginParameterTypeOptions(
|
||||
isArrayType,
|
||||
enableFileType && !isObjectField,
|
||||
);
|
||||
|
||||
return (
|
||||
<span
|
||||
style={useBlockWrap ? { display: 'inline-block', width: '100%' } : {}}
|
||||
>
|
||||
<UICascader
|
||||
treeData={parameterTypeOptionsWithCustom}
|
||||
validateStatus={errorStatus ? 'error' : 'default'}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={onChange as CascaderProps['onChange']}
|
||||
displayRender={displayRender}
|
||||
dropdownClassName={s.cascaderDropdown}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<br />
|
||||
{errorStatus !== 0 && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<span className={cl(s['form-check-tip'], 'errorClassTag', s.w110)}>
|
||||
<IconAlertCircle className={s['plugin-icon-error']} />
|
||||
<Text
|
||||
component="span"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: { style: { maxWidth: '100%' } },
|
||||
},
|
||||
}}
|
||||
className={s['plugin-tooltip-error']}
|
||||
>
|
||||
{errorStatus === 1 && (
|
||||
<span>{I18n.t('plugin_Parameter_type')}</span>
|
||||
)}
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.icon {
|
||||
> svg {
|
||||
width: 16px;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 React, { type FC, useEffect, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIButton, Typography, UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { AssistParameterType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { FileTypeEnum } from '@coze-studio/file-kit/logic';
|
||||
import { ACCEPT_UPLOAD_TYPES } from '@coze-studio/file-kit/config';
|
||||
import { IconDeleteOutline, IconUploadOutlined1 } from '@coze-arch/bot-icons';
|
||||
|
||||
import { ItemErrorTip } from '../item-error-tip';
|
||||
import { getFileAccept, getFileTypeFromAssistType } from '../../file';
|
||||
import { PluginFileUpload } from './upload';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const fileUnknownIcon = ACCEPT_UPLOAD_TYPES[FileTypeEnum.DEFAULT_UNKNOWN].icon;
|
||||
|
||||
export const FileUploadItem: FC<{
|
||||
assistParameterType: AssistParameterType;
|
||||
onChange?: (uri: string) => void;
|
||||
required?: boolean;
|
||||
withDescription?: boolean;
|
||||
defaultValue?: string;
|
||||
check?: number;
|
||||
disabled?: boolean;
|
||||
}> = ({
|
||||
onChange,
|
||||
required = false,
|
||||
withDescription = false,
|
||||
check = 0,
|
||||
defaultValue,
|
||||
disabled = false,
|
||||
assistParameterType,
|
||||
}) => {
|
||||
const [isErrorStatus, setIsErrorStatus] = useState(false);
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const defaultFileType = getFileTypeFromAssistType(assistParameterType);
|
||||
const isImageString = assistParameterType === AssistParameterType.IMAGE;
|
||||
const btnText = isImageString
|
||||
? I18n.t('plugin_file_upload_image')
|
||||
: I18n.t('plugin_file_upload');
|
||||
const errorTip = isImageString
|
||||
? I18n.t('plugin_file_upload_mention_image')
|
||||
: I18n.t('plugin_file_upload_mention');
|
||||
const accept = getFileAccept(assistParameterType);
|
||||
|
||||
useEffect(() => {
|
||||
if (check === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsErrorStatus(required && !value);
|
||||
}, [check]);
|
||||
|
||||
const onChangeHandler = (uri: string) => {
|
||||
setValue(uri);
|
||||
onChange?.(uri);
|
||||
setIsErrorStatus(required && !uri);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PluginFileUpload
|
||||
defaultUrl={value}
|
||||
defaultFileType={defaultFileType}
|
||||
onUploadSuccess={onChangeHandler}
|
||||
uploadProps={{
|
||||
accept,
|
||||
disabled,
|
||||
maxSize: 20480,
|
||||
}}
|
||||
render={({ fileState, clearFile }) => {
|
||||
const { uploading, uri, url, name, type } = fileState;
|
||||
|
||||
/**
|
||||
* 回显 只有一个url(string),需要兼容 => 不展示icon,url作为文件名
|
||||
*/
|
||||
const onlyUrlString = !!url && !uri;
|
||||
const displayName = onlyUrlString ? value : name;
|
||||
|
||||
let icon: string | undefined = url;
|
||||
|
||||
const uploadButton = (
|
||||
<UIButton
|
||||
icon={<IconUploadOutlined1 className={styles.icon} />}
|
||||
loading={uploading}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
>
|
||||
{uploading ? I18n.t('plugin_file_uploading') : btnText}
|
||||
</UIButton>
|
||||
);
|
||||
|
||||
if (uploading) {
|
||||
return uploadButton;
|
||||
} else if (onlyUrlString && type === FileTypeEnum.IMAGE) {
|
||||
/** image不是即时上传的无法确认其为合法资源路径 */
|
||||
icon = fileUnknownIcon;
|
||||
} else if (!isImageString) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const typeIcon = ACCEPT_UPLOAD_TYPES[type]?.icon;
|
||||
if (typeIcon) {
|
||||
icon = typeIcon;
|
||||
} else {
|
||||
icon = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyUrlString || uri) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center justify-between w-full h-[32px]',
|
||||
disabled ? 'cursor-not-allowed' : '',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center min-w-0">
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
className="w-[20px] h-[20px] mr-[5px] rounded-[0.5px]"
|
||||
/>
|
||||
) : null}
|
||||
<Text ellipsis={{ showTooltip: true }} className="mr-[2px]">
|
||||
{displayName}
|
||||
</Text>
|
||||
</div>
|
||||
<UIIconButton
|
||||
icon={<IconDeleteOutline />}
|
||||
disabled={disabled}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
clearFile();
|
||||
onChangeHandler('');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return uploadButton;
|
||||
}}
|
||||
/>
|
||||
{isErrorStatus ? (
|
||||
<ItemErrorTip withDescription={withDescription} tip={errorTip} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 FC, type ReactNode, useReducer } from 'react';
|
||||
|
||||
import { merge } from 'lodash-es';
|
||||
import { produce } from 'immer';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { uploadFileV2 } from '@coze-arch/bot-utils';
|
||||
import { FileTypeEnum, getFileInfo } from '@coze-studio/file-kit/logic';
|
||||
import { Upload, Toast, type UploadProps } from '@coze-arch/coze-design';
|
||||
|
||||
interface PluginFileUploadProps {
|
||||
render: (props: { fileState: FileState; clearFile: () => void }) => ReactNode;
|
||||
onUploadSuccess?: (uri: string) => void;
|
||||
uploadProps?: Partial<UploadProps>;
|
||||
disabled?: boolean;
|
||||
defaultUrl?: string;
|
||||
defaultFileType: FileTypeEnum | null;
|
||||
}
|
||||
|
||||
interface FileState {
|
||||
uri: string;
|
||||
url: string;
|
||||
name: string;
|
||||
type: FileTypeEnum | null;
|
||||
uploading: boolean;
|
||||
abortSignal: AbortSignal;
|
||||
}
|
||||
|
||||
const getDefaultFileState = (states?: Partial<FileState>): FileState =>
|
||||
merge(
|
||||
{
|
||||
uri: '',
|
||||
url: '',
|
||||
name: '',
|
||||
type: null,
|
||||
uploading: false,
|
||||
abortSignal: new AbortController().signal,
|
||||
} satisfies FileState,
|
||||
states,
|
||||
);
|
||||
|
||||
type Action = Partial<Omit<FileState, 'abortSignal'>>;
|
||||
|
||||
export const PluginFileUpload: FC<PluginFileUploadProps> = ({
|
||||
disabled = false,
|
||||
uploadProps,
|
||||
render,
|
||||
onUploadSuccess,
|
||||
defaultUrl,
|
||||
defaultFileType,
|
||||
}) => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const userId = userStoreService.useUserInfo().user_id_str;
|
||||
const [fileState, setFileState] = useReducer(
|
||||
(states: FileState, payload: Action) =>
|
||||
produce(states, draft => {
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(payload).forEach(key => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
draft[key] = payload[key] ?? draft[key];
|
||||
});
|
||||
}),
|
||||
getDefaultFileState({
|
||||
url: defaultUrl ?? '',
|
||||
type: defaultFileType ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
const clearFile = () => setFileState(getDefaultFileState());
|
||||
|
||||
const customRequest: UploadProps['customRequest'] = async ({
|
||||
file,
|
||||
fileInstance,
|
||||
}) => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const type = getFileInfo(fileInstance).fileType;
|
||||
setFileState({
|
||||
uploading: true,
|
||||
url: file.url,
|
||||
name: file.name,
|
||||
});
|
||||
|
||||
await uploadFileV2({
|
||||
userId,
|
||||
fileItemList: [
|
||||
{
|
||||
file: fileInstance,
|
||||
fileType: type === FileTypeEnum.IMAGE ? 'image' : 'object',
|
||||
},
|
||||
],
|
||||
signal: fileState.abortSignal,
|
||||
timeout: undefined,
|
||||
onSuccess: info => {
|
||||
const uri = info?.uploadResult?.Uri;
|
||||
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFileState({
|
||||
uploading: false,
|
||||
uri,
|
||||
type,
|
||||
});
|
||||
|
||||
onUploadSuccess?.(uri);
|
||||
},
|
||||
onUploadError: () => {
|
||||
setFileState({
|
||||
uploading: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof render !== 'function') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Upload
|
||||
className="w-full"
|
||||
draggable
|
||||
limit={1}
|
||||
disabled={disabled}
|
||||
onAcceptInvalid={() => {
|
||||
Toast.error(I18n.t('shortcut_Illegal_file_format'));
|
||||
}}
|
||||
onSizeError={() => {
|
||||
if (uploadProps?.maxSize) {
|
||||
Toast.error(
|
||||
I18n.t('file_too_large', {
|
||||
max_size: `${uploadProps.maxSize / 1024}MB`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
customRequest={customRequest}
|
||||
showUploadList={false}
|
||||
{...uploadProps}
|
||||
>
|
||||
{render({ fileState, clearFile })}
|
||||
</Upload>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { CascaderItem } from './cascader-item';
|
||||
export { FileUploadItem } from './file-upload-item';
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.check-box {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.form-check-tip {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
transform-origin: left;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
font-size: 12px !important;
|
||||
line-height: 16px;
|
||||
color: var(--semi-color-danger);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 React, { type FC } from 'react';
|
||||
|
||||
import cl from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const ItemErrorTip: FC<{ withDescription?: boolean; tip?: string }> = ({
|
||||
withDescription = false,
|
||||
tip = I18n.t('plugin_empty'),
|
||||
}) => (
|
||||
<div className={s['check-box']}>
|
||||
<span
|
||||
className={cl(
|
||||
'whitespace-nowrap',
|
||||
s['form-check-tip'],
|
||||
withDescription ? '!top-[16px]' : '!top-0',
|
||||
'errorDebugClassTag',
|
||||
)}
|
||||
>
|
||||
{tip}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* 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 { cloneDeep } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type AssistParameterType,
|
||||
ParameterLocation,
|
||||
ParameterType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { type ExtInfoText } from '@coze-studio/plugin-shared';
|
||||
import { FileTypeEnum } from '@coze-studio/file-kit/logic';
|
||||
|
||||
import { type APIParameterRecord } from './types/params';
|
||||
|
||||
export const childrenRecordName = 'sub_parameters'; // 子节点名称
|
||||
export const ROWKEY = 'id'; // 唯一标识符
|
||||
export const ARRAYTAG = '[Array Item]'; // 数组元素标识符
|
||||
export const ROOTTAG = '[Root Item]'; // root为数组的标识符
|
||||
export const STARTNODE = 0;
|
||||
export const REQUESTNODE = 1;
|
||||
export const RESPONSENODE = 2;
|
||||
export const DEBUGNODE = 3;
|
||||
export const ENDSTEP = 4;
|
||||
// 传入方法options
|
||||
export const parameterLocationOptions = [
|
||||
{
|
||||
label: 'Body',
|
||||
value: ParameterLocation.Body,
|
||||
},
|
||||
{
|
||||
label: 'Path',
|
||||
value: ParameterLocation.Path,
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
value: ParameterLocation.Query,
|
||||
},
|
||||
{
|
||||
label: 'Header',
|
||||
value: ParameterLocation.Header,
|
||||
},
|
||||
];
|
||||
|
||||
export enum ParameterTypeExtend {
|
||||
/**
|
||||
* 扩展类型
|
||||
* 与 AssistParameterType 一一对应
|
||||
*/
|
||||
DEFAULT = 10001,
|
||||
IMAGE,
|
||||
DOC,
|
||||
CODE,
|
||||
PPT,
|
||||
TXT,
|
||||
EXCEL,
|
||||
AUDIO,
|
||||
ZIP,
|
||||
VIDEO,
|
||||
}
|
||||
|
||||
const enumDomain = 10000;
|
||||
export const assistToExtend = (
|
||||
type: AssistParameterType,
|
||||
): ParameterTypeExtend => type + enumDomain;
|
||||
|
||||
export const extendToAssist = (
|
||||
type: ParameterTypeExtend,
|
||||
): AssistParameterType => type - enumDomain;
|
||||
|
||||
export type PluginParameterType = ParameterType | ParameterTypeExtend;
|
||||
|
||||
interface ParameterTypeOption {
|
||||
label: string;
|
||||
value: ParameterType | ParameterTypeExtend;
|
||||
children?: Array<{
|
||||
label: string;
|
||||
value: ParameterTypeExtend;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 未扩展File类型前的 基础类型,多处使用 需要保留 start
|
||||
*/
|
||||
export const parameterTypeOptions: Array<ParameterTypeOption> = [
|
||||
{
|
||||
label: 'String',
|
||||
value: ParameterType.String,
|
||||
},
|
||||
{
|
||||
label: 'Integer',
|
||||
value: ParameterType.Integer,
|
||||
},
|
||||
{
|
||||
label: 'Number',
|
||||
value: ParameterType.Number,
|
||||
},
|
||||
{
|
||||
label: 'Object',
|
||||
value: ParameterType.Object,
|
||||
},
|
||||
{
|
||||
label: 'Array',
|
||||
value: ParameterType.Array,
|
||||
},
|
||||
{
|
||||
label: 'Boolean',
|
||||
value: ParameterType.Bool,
|
||||
},
|
||||
];
|
||||
|
||||
export const parameterTypeOptionsSub: Array<ParameterTypeOption> = [
|
||||
{
|
||||
label: 'Array<String>',
|
||||
value: ParameterType.String,
|
||||
},
|
||||
{
|
||||
label: 'Array<Integer>',
|
||||
value: ParameterType.Integer,
|
||||
},
|
||||
{
|
||||
label: 'Array<Number>',
|
||||
value: ParameterType.Number,
|
||||
},
|
||||
{
|
||||
label: 'Array<Object>',
|
||||
value: ParameterType.Object,
|
||||
},
|
||||
{
|
||||
label: 'Array<Boolean>',
|
||||
value: ParameterType.Bool,
|
||||
},
|
||||
];
|
||||
/**
|
||||
* 未扩展File类型前的 基础类型,多处使用 需要保留 end
|
||||
*/
|
||||
|
||||
export const parameterTypeExtendMap: Record<
|
||||
ParameterTypeExtend,
|
||||
{
|
||||
label: string;
|
||||
listLabel: string;
|
||||
fileTypes: FileTypeEnum[];
|
||||
}
|
||||
> = {
|
||||
[ParameterTypeExtend.DEFAULT]: {
|
||||
label: 'File',
|
||||
listLabel: 'Array<File>',
|
||||
fileTypes: [FileTypeEnum.DEFAULT_UNKNOWN],
|
||||
},
|
||||
[ParameterTypeExtend.IMAGE]: {
|
||||
label: 'Image',
|
||||
listLabel: 'Array<Image>',
|
||||
fileTypes: [FileTypeEnum.IMAGE],
|
||||
},
|
||||
[ParameterTypeExtend.DOC]: {
|
||||
label: 'Doc',
|
||||
listLabel: 'Array<Doc>',
|
||||
fileTypes: [FileTypeEnum.DOCX, FileTypeEnum.PDF],
|
||||
},
|
||||
[ParameterTypeExtend.CODE]: {
|
||||
label: 'Code',
|
||||
listLabel: 'Array<Code>',
|
||||
fileTypes: [FileTypeEnum.CODE],
|
||||
},
|
||||
[ParameterTypeExtend.PPT]: {
|
||||
label: 'PPT',
|
||||
listLabel: 'Array<PPT>',
|
||||
fileTypes: [FileTypeEnum.PPT],
|
||||
},
|
||||
[ParameterTypeExtend.TXT]: {
|
||||
label: 'TXT',
|
||||
listLabel: 'Array<TXT>',
|
||||
fileTypes: [FileTypeEnum.TXT],
|
||||
},
|
||||
[ParameterTypeExtend.EXCEL]: {
|
||||
label: 'Excel',
|
||||
listLabel: 'Array<Excel>',
|
||||
fileTypes: [FileTypeEnum.EXCEL, FileTypeEnum.CSV],
|
||||
},
|
||||
[ParameterTypeExtend.AUDIO]: {
|
||||
label: 'Audio',
|
||||
listLabel: 'Array<Audio>',
|
||||
fileTypes: [FileTypeEnum.AUDIO],
|
||||
},
|
||||
[ParameterTypeExtend.ZIP]: {
|
||||
label: 'Zip',
|
||||
listLabel: 'Array<Zip>',
|
||||
fileTypes: [FileTypeEnum.ARCHIVE],
|
||||
},
|
||||
[ParameterTypeExtend.VIDEO]: {
|
||||
label: 'Video',
|
||||
listLabel: 'Array<Video>',
|
||||
fileTypes: [FileTypeEnum.VIDEO],
|
||||
},
|
||||
};
|
||||
|
||||
const getParameterTypeOptionsWithCustom = (enableFileType = false) => {
|
||||
if (!enableFileType) {
|
||||
return parameterTypeOptions;
|
||||
}
|
||||
|
||||
const parameterTypeOptionsWithCustom = cloneDeep(parameterTypeOptions);
|
||||
parameterTypeOptionsWithCustom.splice(1, 0, {
|
||||
label: 'File',
|
||||
value: ParameterTypeExtend.DEFAULT,
|
||||
children: Object.entries(parameterTypeExtendMap).map(
|
||||
([type, { label }]) => ({
|
||||
label,
|
||||
value: Number(type) as ParameterTypeExtend,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return parameterTypeOptionsWithCustom;
|
||||
};
|
||||
|
||||
const getParameterTypeOptionsSubWithCustom = (enableFileType = false) => {
|
||||
if (!enableFileType) {
|
||||
return parameterTypeOptionsSub;
|
||||
}
|
||||
|
||||
const parameterTypeOptionsSubWithCustom = cloneDeep(parameterTypeOptionsSub);
|
||||
parameterTypeOptionsSubWithCustom.splice(1, 0, {
|
||||
label: 'Array<File>',
|
||||
value: ParameterTypeExtend.DEFAULT,
|
||||
children: Object.entries(parameterTypeExtendMap).map(
|
||||
([type, { listLabel }]) => ({
|
||||
label: listLabel,
|
||||
value: Number(type) as ParameterTypeExtend,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return parameterTypeOptionsSubWithCustom;
|
||||
};
|
||||
|
||||
export const getPluginParameterTypeOptions = (
|
||||
isArrayType: boolean,
|
||||
enableFileType: boolean,
|
||||
) =>
|
||||
isArrayType
|
||||
? getParameterTypeOptionsSubWithCustom(enableFileType)
|
||||
: getParameterTypeOptionsWithCustom(enableFileType);
|
||||
|
||||
const parameterTypeOptionsMap = parameterTypeOptions.reduce(
|
||||
(prev: Partial<Record<PluginParameterType, string>>, curr) => {
|
||||
prev[curr.value] = curr.label;
|
||||
return prev;
|
||||
},
|
||||
{
|
||||
...Object.entries(parameterTypeExtendMap).reduce(
|
||||
(prev, [type, { label }]) => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
prev[type] = label;
|
||||
return prev;
|
||||
},
|
||||
{},
|
||||
),
|
||||
[ParameterTypeExtend.DEFAULT]: 'File',
|
||||
},
|
||||
);
|
||||
|
||||
const parameterTypeOptionsSubMap = parameterTypeOptionsSub.reduce(
|
||||
(prev: Partial<Record<PluginParameterType, string>>, curr) => {
|
||||
prev[curr.value] = curr.label;
|
||||
return prev;
|
||||
},
|
||||
{
|
||||
...Object.entries(parameterTypeExtendMap).reduce(
|
||||
(prev, [type, { listLabel }]) => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
prev[type] = listLabel;
|
||||
return prev;
|
||||
},
|
||||
{},
|
||||
),
|
||||
[ParameterTypeExtend.DEFAULT]: 'Array<File>',
|
||||
},
|
||||
);
|
||||
|
||||
export const getParameterTypeLabel = (
|
||||
type: PluginParameterType,
|
||||
isArrayType = false,
|
||||
) =>
|
||||
isArrayType
|
||||
? parameterTypeOptionsSubMap[type]
|
||||
: parameterTypeOptionsMap[type];
|
||||
|
||||
export const getParameterTypeLabelFromRecord = (
|
||||
record: APIParameterRecord,
|
||||
isArrayType = false,
|
||||
) => {
|
||||
let type: PluginParameterType = record.type as PluginParameterType;
|
||||
if (record?.assist_type) {
|
||||
type = assistToExtend(record.assist_type);
|
||||
}
|
||||
return getParameterTypeLabel(type, isArrayType);
|
||||
};
|
||||
|
||||
export const methodType: ExtInfoText[] = [
|
||||
{
|
||||
type: 'title',
|
||||
text: 'Get',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('plugin_tooltip_url'),
|
||||
},
|
||||
{
|
||||
type: 'demo',
|
||||
text: 'GET /users?userId=123',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('used_to_obtain_user_information_with_id_123'),
|
||||
},
|
||||
{
|
||||
type: 'br',
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
text: 'Post',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t(
|
||||
'submit_data_to_a_specified_resource__often_used_to_submit_forms_or_upload_files_',
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'demo',
|
||||
text: 'POST /users',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('attach_user_data_to_create_a_new_user_'),
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
text: 'Put',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t(
|
||||
'upload_data_or_resources_to_a_specified_location__often_used_to_update_existing_',
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'demo',
|
||||
text: 'PUT /users/123',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('used_to_update_user_information_with_id_123_'),
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
text: 'Delete',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t(
|
||||
'requests_the_server_to_delete_the_specified_resource__example_',
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'demo',
|
||||
text: 'DELETE /users/123',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('used_to_delete_the_user_with_id_123_'),
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
text: I18n.t('Create_tool_s1_method_patch_tooltip_title'),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('Create_tool_s1_method_patch_tooltip_desp'),
|
||||
},
|
||||
{
|
||||
type: 'demo',
|
||||
text: I18n.t('Create_tool_s1_method_patch_tooltip_url'),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: I18n.t('Create_tool_s1_method_patch_tooltip_explain'),
|
||||
},
|
||||
];
|
||||
|
||||
export enum ParamsFormErrorStatus {
|
||||
NO_ERROR = 0,
|
||||
NAME_EMPTY = 1,
|
||||
// 中文
|
||||
CHINESE = 2,
|
||||
// 重复
|
||||
REPEAT = 3,
|
||||
ASCII = 4,
|
||||
// 未填写
|
||||
DESC_EMPTY = 5,
|
||||
}
|
||||
|
||||
export const paramsFormErrorStatusText = {
|
||||
[ParamsFormErrorStatus.NO_ERROR]: '',
|
||||
[ParamsFormErrorStatus.NAME_EMPTY]: I18n.t(
|
||||
'Create_newtool_s2_table_name_error1',
|
||||
),
|
||||
[ParamsFormErrorStatus.CHINESE]: I18n.t(
|
||||
'Create_newtool_s2_table_name_error2',
|
||||
),
|
||||
[ParamsFormErrorStatus.REPEAT]: I18n.t('plugin_Parameter_name_error'),
|
||||
[ParamsFormErrorStatus.ASCII]: I18n.t('create_plugin_modal_descrip_error'),
|
||||
[ParamsFormErrorStatus.DESC_EMPTY]: I18n.t(
|
||||
'Create_newtool_s3_table_des_empty',
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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 { useState, type PropsWithChildren, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Banner, Space } from '@coze-arch/bot-semi';
|
||||
import { IconPullDown } from '@coze-arch/bot-icons';
|
||||
|
||||
import { type CheckParamsProps } from '../types';
|
||||
import { DiyMdBox, HeadingType } from './diy-mdbox';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const Header = ({
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
activeTab,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
setActiveTab,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
hideRawResponse,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
showRaw,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
setShowRaw,
|
||||
}) => {
|
||||
const handleOpenRawResponse = () => {
|
||||
setShowRaw(!showRaw);
|
||||
};
|
||||
return (
|
||||
<div className={s['debug-check-header']}>
|
||||
<div className={s['debug-check-tab']}>
|
||||
<div
|
||||
className={classNames(s['debug-check-tab-item'], {
|
||||
[s['debug-check-tab-item-active']]:
|
||||
activeTab === HeadingType.Request,
|
||||
})}
|
||||
onClick={() => setActiveTab(HeadingType.Request)}
|
||||
>
|
||||
Request
|
||||
</div>
|
||||
<div className={s['debug-check-tab-line']}></div>
|
||||
<div
|
||||
className={classNames(s['debug-check-tab-item'], {
|
||||
[s['debug-check-tab-item-active']]:
|
||||
activeTab === HeadingType.Response,
|
||||
})}
|
||||
onClick={() => setActiveTab(HeadingType.Response)}
|
||||
>
|
||||
Response
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === HeadingType.Response && !hideRawResponse ? (
|
||||
<Space spacing={8}>
|
||||
<span>Raw Response</span>
|
||||
<IconPullDown
|
||||
className={classNames(s.icon, {
|
||||
[s.open]: showRaw,
|
||||
})}
|
||||
onClick={handleOpenRawResponse}
|
||||
></IconPullDown>
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProcessContent: FC<PropsWithChildren> = ({ children }) => (
|
||||
<div className={s['process-content']}>{children}</div>
|
||||
);
|
||||
|
||||
/** stringify 缩进 */
|
||||
const INDENTATION_SPACES = 2;
|
||||
const LLMAndAPIContent: FC<{
|
||||
toolMessageUnit: CheckParamsProps;
|
||||
}> = ({ toolMessageUnit }) => {
|
||||
const { request, response, failReason, rawResp } = toolMessageUnit;
|
||||
const [activeTab, setActiveTab] = useState(1);
|
||||
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
return (
|
||||
<>
|
||||
{!request && !response ? (
|
||||
<div className={s['llm-debug-empty']}>
|
||||
<div className={s['llm-debug-empty-content']}>
|
||||
{I18n.t('plugin_s4_debug_empty')}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={s['debug-result-content']}>
|
||||
<Header
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
hideRawResponse={!(!failReason && rawResp)}
|
||||
showRaw={showRaw}
|
||||
setShowRaw={setShowRaw}
|
||||
/>
|
||||
{activeTab === 1 ? (
|
||||
<>
|
||||
<div className={s['llm-api-content']}>
|
||||
<DiyMdBox
|
||||
markDown={
|
||||
request
|
||||
? JSON.stringify(
|
||||
JSON.parse(request || '{}'),
|
||||
null,
|
||||
INDENTATION_SPACES,
|
||||
)
|
||||
: ''
|
||||
}
|
||||
headingType={activeTab}
|
||||
showRaw={showRaw}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={s['llm-api-content']}>
|
||||
{failReason ? (
|
||||
<div className={s['error-reason-box']}>
|
||||
<Banner
|
||||
className={s['error-reason']}
|
||||
fullMode={false}
|
||||
icon={null}
|
||||
closeIcon={null}
|
||||
type="danger"
|
||||
description={
|
||||
<div>
|
||||
<div>{I18n.t('plugin_s4_debug_detail')}</div>
|
||||
<div style={{ wordBreak: 'break-word' }}>
|
||||
{failReason}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<DiyMdBox
|
||||
headingType={activeTab}
|
||||
markDown={JSON.stringify(
|
||||
JSON.parse(response || '{}'),
|
||||
null,
|
||||
INDENTATION_SPACES,
|
||||
)}
|
||||
rawResponse={JSON.stringify(
|
||||
JSON.parse(rawResp || '{}'),
|
||||
null,
|
||||
INDENTATION_SPACES,
|
||||
)}
|
||||
showRaw={showRaw}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DebugCheck: FC<{
|
||||
checkParams: CheckParamsProps;
|
||||
}> = ({ checkParams }) => (
|
||||
<ProcessContent>
|
||||
<LLMAndAPIContent toolMessageUnit={checkParams} />
|
||||
</ProcessContent>
|
||||
);
|
||||
@@ -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 React, { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIButton, Toast } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
DebugExampleStatus,
|
||||
PluginType,
|
||||
type APIParameter,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
transformTreeToObj,
|
||||
sleep,
|
||||
scrollToErrorElement,
|
||||
transformParamsToTree,
|
||||
} from '../utils';
|
||||
import { type CheckParamsProps, STATUS } from '../types';
|
||||
import s from '../index.module.less';
|
||||
import ParamsForm from './params-form';
|
||||
|
||||
/** stringify 缩进 */
|
||||
const INDENTATION_SPACES = 2;
|
||||
const SLEEP_NUM = 100;
|
||||
|
||||
export const DebugParams: React.FC<{
|
||||
requestParams: APIParameter[] | undefined;
|
||||
pluginId: string;
|
||||
apiId: string;
|
||||
operation?: number;
|
||||
btnText?: string;
|
||||
callback?: (val: CheckParamsProps) => void;
|
||||
disabled: boolean;
|
||||
debugExampleStatus?: DebugExampleStatus;
|
||||
showExampleTag?: boolean;
|
||||
pluginType?: PluginType;
|
||||
}> = ({
|
||||
requestParams = [],
|
||||
pluginId,
|
||||
apiId,
|
||||
operation = 1,
|
||||
btnText = I18n.t('Create_newtool_s4_run'),
|
||||
callback,
|
||||
disabled,
|
||||
debugExampleStatus = DebugExampleStatus.Default,
|
||||
showExampleTag = false,
|
||||
pluginType,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [check, setCheck] = useState<number>(0);
|
||||
const paramsFormRef = useRef<{ data: Array<APIParameter> }>(null);
|
||||
|
||||
const handleAction = async () => {
|
||||
// 校验是否必填
|
||||
setCheck(check + 1);
|
||||
await sleep(SLEEP_NUM);
|
||||
const errorEle = document.getElementsByClassName('errorDebugClassTag');
|
||||
if (!apiId || errorEle.length > 0) {
|
||||
scrollToErrorElement('.errorDebugClassTag');
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('tool_new_S2_feedback_failed')),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let reqParams = {};
|
||||
setLoading(true);
|
||||
if (
|
||||
Array.isArray(paramsFormRef.current?.data) &&
|
||||
(paramsFormRef.current?.data || []).length > 0
|
||||
) {
|
||||
reqParams = transformTreeToObj(paramsFormRef.current?.data);
|
||||
}
|
||||
try {
|
||||
const resData = await PluginDevelopApi.DebugAPI({
|
||||
plugin_id: pluginId,
|
||||
api_id: apiId,
|
||||
parameters: JSON.stringify(reqParams),
|
||||
operation,
|
||||
});
|
||||
|
||||
callback?.({
|
||||
status: resData.success ? STATUS.PASS : STATUS.FAIL,
|
||||
request: resData.raw_req,
|
||||
response: resData.resp,
|
||||
failReason: resData.reason,
|
||||
response_params: resData.response_params,
|
||||
rawResp: resData.raw_resp,
|
||||
});
|
||||
} catch (e) {
|
||||
callback?.({
|
||||
status: STATUS.FAIL,
|
||||
request: JSON.stringify(reqParams, null, INDENTATION_SPACES),
|
||||
response: I18n.t('plugin_exception'),
|
||||
failReason: I18n.t('plugin_exception'),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
const requestParamsData = useMemo(
|
||||
() => transformParamsToTree(requestParams),
|
||||
[requestParams],
|
||||
);
|
||||
return (
|
||||
<div className={s['debug-params-box']}>
|
||||
<ParamsForm
|
||||
height={443}
|
||||
ref={paramsFormRef}
|
||||
requestParams={requestParamsData}
|
||||
defaultKey="global_default"
|
||||
disabled={disabled}
|
||||
check={check}
|
||||
debugExampleStatus={debugExampleStatus}
|
||||
showExampleTag={showExampleTag}
|
||||
supportFileTypeUpload
|
||||
/>
|
||||
{!disabled && (
|
||||
<div className={s.runbtn}>
|
||||
<UIButton
|
||||
disabled={disabled || pluginType === PluginType.LOCAL}
|
||||
style={{ width: 98 }}
|
||||
loading={loading}
|
||||
// theme="solid"
|
||||
type="tertiary"
|
||||
onClick={handleAction}
|
||||
>
|
||||
{btnText === I18n.t('Create_newtool_s3_button_auto') &&
|
||||
(loading
|
||||
? I18n.t('plugin_s3_Parsing')
|
||||
: I18n.t('Create_newtool_s3_button_auto'))}
|
||||
{btnText === I18n.t('Create_newtool_s4_run') &&
|
||||
(loading
|
||||
? I18n.t('plugin_s3_running')
|
||||
: I18n.t('Create_newtool_s4_run'))}
|
||||
</UIButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 copy from 'copy-to-clipboard';
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Space, Toast } from '@coze-arch/bot-semi';
|
||||
import { MdBoxLazy } from '@coze-arch/bot-md-box-adapter/lazy';
|
||||
import { IconCopy } from '@coze-arch/bot-icons';
|
||||
|
||||
import s from './index.module.less';
|
||||
export enum HeadingType {
|
||||
Request = 1,
|
||||
Response = 2,
|
||||
}
|
||||
interface MdBoxProps {
|
||||
markDown: string;
|
||||
headingType: HeadingType;
|
||||
rawResponse?: string;
|
||||
showRaw: boolean;
|
||||
}
|
||||
|
||||
const MAX_LENGTH = 30000;
|
||||
|
||||
export const DiyMdBox = ({
|
||||
markDown,
|
||||
headingType,
|
||||
rawResponse,
|
||||
showRaw,
|
||||
}: MdBoxProps) => {
|
||||
const getContent = () => {
|
||||
if (!rawResponse) {
|
||||
return '{}';
|
||||
}
|
||||
if (rawResponse.length < MAX_LENGTH) {
|
||||
return rawResponse;
|
||||
}
|
||||
return `${rawResponse.slice(0, MAX_LENGTH)}...`;
|
||||
};
|
||||
return (
|
||||
<div className={s['mb-content']}>
|
||||
<div className={s['mb-header']}>
|
||||
<Space spacing={8}>
|
||||
<span>Json</span>
|
||||
<IconCopy
|
||||
className={s['icon-copy']}
|
||||
onClick={() => {
|
||||
copy(markDown);
|
||||
Toast.success(I18n.t('copy_success'));
|
||||
}}
|
||||
></IconCopy>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={s['mb-main']}>
|
||||
<div
|
||||
className={classNames(s['mb-left'], {
|
||||
[s['half-width']]: showRaw && headingType === HeadingType.Response,
|
||||
})}
|
||||
>
|
||||
<MdBoxLazy markDown={`\`\`\`json\n${markDown}\n\`\`\``} />
|
||||
</div>
|
||||
{showRaw && headingType === HeadingType.Response ? (
|
||||
<div className={s['mb-right']}>
|
||||
<MdBoxLazy markDown={`\`\`\`json\n${getContent()}\n\`\`\``} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,271 @@
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.process-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 15px;
|
||||
|
||||
/* 125% */
|
||||
white-space: break-spaces;
|
||||
|
||||
background: var(--light-color-white-white, #fff);
|
||||
|
||||
.debug-result-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.llm-api-content {
|
||||
height: calc(100% - 40px);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.llm-debug-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 100%;
|
||||
|
||||
font-size: 14px;
|
||||
color: var(--light-usage-text-color-text-3,
|
||||
var(--light-usage-disabled-color-disabled-text, rgb(28 31 35 / 35%)));
|
||||
|
||||
.llm-debug-empty-content {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.error-reason-box {
|
||||
padding: 12px;
|
||||
border-top: 1px solid rgba(29, 28, 36, 8%);
|
||||
}
|
||||
|
||||
.mb-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.auto-hide-last-sibling-br>div {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.auto-hide-last-sibling-br>div>div:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flow-markdown-body {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mb-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 40px;
|
||||
color: #f7f7fa;
|
||||
|
||||
background-color: #41414d;
|
||||
|
||||
|
||||
|
||||
.icon-copy {
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
path {
|
||||
fill: #fff;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mb-main {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
|
||||
background-color: #12131b;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
.mb-left {
|
||||
width: 100%;
|
||||
|
||||
&.half-width {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.mb-right {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 50%;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
width: 1px;
|
||||
|
||||
background-color: #565563;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debug-params-table {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
|
||||
.empty {
|
||||
margin-top: 90px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-spin-block.semi-spin,
|
||||
.semi-spin-children,
|
||||
.semi-table-fixed-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.semi-table-body {
|
||||
max-height: calc(100% - 40px) !important;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.semi-table-row {
|
||||
&:has(.disable) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-header {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 16px;
|
||||
|
||||
width: calc(100% - 32px);
|
||||
height: 1px;
|
||||
|
||||
background: var(--semi-color-border);
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-placeholder {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row>.semi-table-row-cell {
|
||||
padding: 8px 16px;
|
||||
vertical-align: top;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.semi-table-thead>.semi-table-row>.semi-table-row-head {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row:hover>.semi-table-row-cell {
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debug-check-header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
|
||||
.debug-check-tab {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
|
||||
.debug-check-tab-line {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: var(--Light-usage-border---color-border, rgba(28, 29, 37, 12%));
|
||||
}
|
||||
|
||||
.debug-check-tab-item {
|
||||
cursor: pointer;
|
||||
color: var(--Light-usage-text---color-text-2, rgba(29, 28, 36, 60%));
|
||||
}
|
||||
|
||||
.debug-check-tab-item-active {
|
||||
color: var(--Light-color-brand---brand-5, #4C54F0);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
|
||||
border: 0.75px solid rgba(29, 28, 36, 12%);
|
||||
border-radius: 6px;
|
||||
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
&.open {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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 React, { type FC, useEffect, useState } from 'react';
|
||||
|
||||
import { Typography, UIInput } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type APIParameter,
|
||||
ParameterType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { updateNodeById } from '../../../utils';
|
||||
import { type APIParameterRecord } from '../../../types/params';
|
||||
import { ARRAYTAG, ROOTTAG, ROWKEY } from '../../../config';
|
||||
import { ItemErrorTip } from '../../../components/item-error-tip';
|
||||
import { FileUploadItem } from '../../../components/file-upload-item';
|
||||
import { getColumnClass } from './utils';
|
||||
|
||||
interface InputItemProps {
|
||||
val?: string;
|
||||
width?: number | string;
|
||||
height?: number;
|
||||
check?: number;
|
||||
callback: (val: string) => void;
|
||||
useCheck?: boolean;
|
||||
useBlockWrap?: boolean;
|
||||
disabled: boolean;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
const InputItem = ({
|
||||
val = '',
|
||||
callback,
|
||||
check = 0,
|
||||
width = '100%',
|
||||
useCheck = false,
|
||||
useBlockWrap = false,
|
||||
disabled,
|
||||
desc,
|
||||
}: InputItemProps): JSX.Element => {
|
||||
const [value, setValue] = useState(val);
|
||||
const [errorStatus, setErrorStatus] = useState(false);
|
||||
// 通过check触发校验(提交时)
|
||||
useEffect(() => {
|
||||
if (check === 0 || value === ARRAYTAG || value === ROOTTAG) {
|
||||
return;
|
||||
}
|
||||
handleCheck(value);
|
||||
}, [check]);
|
||||
const handleCheck = (v: string) => {
|
||||
if (!useCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterVal = v ? String(v).replace(/\s+/g, '') : '';
|
||||
setErrorStatus(filterVal === '');
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{ width, ...(useBlockWrap ? { display: 'inline-block' } : {}) }}
|
||||
>
|
||||
<UIInput
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
validateStatus={errorStatus ? 'error' : 'default'}
|
||||
onChange={(e: string) => {
|
||||
setValue(e);
|
||||
callback(e);
|
||||
handleCheck(e);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
{errorStatus ? <ItemErrorTip withDescription={!!desc} /> : null}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const ValueColRender: FC<{
|
||||
record: APIParameterRecord;
|
||||
disabled?: boolean;
|
||||
check: number;
|
||||
needCheck: boolean;
|
||||
defaultKey: string;
|
||||
data: Array<APIParameter>;
|
||||
supportFileTypeUpload: boolean;
|
||||
}> = ({
|
||||
record,
|
||||
data,
|
||||
disabled = false,
|
||||
check,
|
||||
needCheck,
|
||||
defaultKey,
|
||||
supportFileTypeUpload = false,
|
||||
}) => {
|
||||
const showInput = !(
|
||||
record?.type === ParameterType.Object ||
|
||||
record?.type === ParameterType.Array ||
|
||||
(disabled && record.value === undefined)
|
||||
);
|
||||
|
||||
const showFile =
|
||||
record?.type === ParameterType.String && !!record?.assist_type;
|
||||
|
||||
let renderItem = <></>;
|
||||
|
||||
if (supportFileTypeUpload && showFile) {
|
||||
renderItem = (
|
||||
<FileUploadItem
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
defaultValue={record.value || record?.[defaultKey]}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
assistParameterType={record.assist_type}
|
||||
onChange={uri => {
|
||||
updateNodeById({
|
||||
data,
|
||||
targetKey: record[ROWKEY] as string,
|
||||
field: 'value',
|
||||
value: uri ? uri : null,
|
||||
});
|
||||
}}
|
||||
withDescription={!!record?.desc}
|
||||
required={needCheck || record?.is_required}
|
||||
check={check}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (showInput) {
|
||||
renderItem = (
|
||||
<div className={getColumnClass(record)}>
|
||||
<InputItem
|
||||
disabled={disabled}
|
||||
useBlockWrap={true}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
val={record.value || record?.[defaultKey]}
|
||||
check={check}
|
||||
useCheck={needCheck || record?.is_required}
|
||||
callback={(e: string) => {
|
||||
updateNodeById({
|
||||
data,
|
||||
targetKey: record[ROWKEY] as string,
|
||||
field: 'value',
|
||||
value: e,
|
||||
});
|
||||
updateNodeById({
|
||||
data,
|
||||
targetKey: record[ROWKEY] as string,
|
||||
field: defaultKey,
|
||||
value: e,
|
||||
});
|
||||
}}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
desc={record.desc}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mr-[3px]">
|
||||
{renderItem}
|
||||
{record.desc ? (
|
||||
<Typography.Text
|
||||
size="small"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: record.desc },
|
||||
},
|
||||
}}
|
||||
style={{ verticalAlign: showInput ? 'top' : 'middle' }}
|
||||
>
|
||||
{record.desc}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 APIParameterRecord } from '../../../types/params';
|
||||
|
||||
export const getColumnClass = (record: APIParameterRecord) =>
|
||||
record.global_disable ? 'disable' : 'normal';
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* 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 React, {
|
||||
type Ref,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
|
||||
import { set as ObjectSet, get as ObjectGet, cloneDeep } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tag } from '@coze-arch/coze-design';
|
||||
import {
|
||||
UIButton,
|
||||
Table,
|
||||
Typography,
|
||||
UITag,
|
||||
Space,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconAddChildOutlined } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
type APIParameter,
|
||||
ParameterType,
|
||||
DebugExampleStatus,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { IconDeleteStroked } from '@douyinfe/semi-icons';
|
||||
|
||||
import styles from '../index.module.less';
|
||||
import {
|
||||
findPathById,
|
||||
deleteNode,
|
||||
findTemplateNodeByPath,
|
||||
cloneWithRandomKey,
|
||||
handleIsShowDelete,
|
||||
checkHasArray,
|
||||
maxDeep,
|
||||
} from '../../utils';
|
||||
import { type APIParameterRecord } from '../../types/params';
|
||||
import {
|
||||
ARRAYTAG,
|
||||
ROWKEY,
|
||||
childrenRecordName,
|
||||
getParameterTypeLabelFromRecord,
|
||||
} from '../../config';
|
||||
import { getColumnClass } from './columns/utils';
|
||||
import { ValueColRender } from './columns/param-value-col';
|
||||
|
||||
const getName = (record: APIParameterRecord) => {
|
||||
const paramType = getParameterTypeLabelFromRecord(record);
|
||||
|
||||
return (
|
||||
<span className={getColumnClass(record)}>
|
||||
<Typography.Text
|
||||
component="span"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: { style: { maxWidth: '100%' } },
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
maxWidth: `calc(100% - ${20 * (record.deep || 1) + 49}px)`,
|
||||
}}
|
||||
>
|
||||
{record?.name}
|
||||
</Typography.Text>
|
||||
{record?.is_required ? (
|
||||
<Typography.Text style={{ color: 'red' }}>{' * '}</Typography.Text>
|
||||
) : null}
|
||||
{paramType ? (
|
||||
<Tag
|
||||
size="mini"
|
||||
prefixIcon={null}
|
||||
className="!coz-fg-color-blue !coz-mg-color-blue shrink-0 font-normal px-6px rounded-[36px] ml-4px align-middle"
|
||||
>
|
||||
{paramType}
|
||||
</Tag>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ParamsFormProps {
|
||||
requestParams?: Array<APIParameter>;
|
||||
disabled: boolean;
|
||||
check: number;
|
||||
needCheck?: boolean;
|
||||
height?: number;
|
||||
defaultKey?: 'global_default' | 'local_default';
|
||||
debugExampleStatus?: DebugExampleStatus;
|
||||
showExampleTag?: boolean;
|
||||
supportFileTypeUpload?: boolean;
|
||||
}
|
||||
|
||||
const getParamsTitle = (isShowExampleTag: boolean, disabled: boolean) =>
|
||||
isShowExampleTag ? (
|
||||
<Space>
|
||||
<div>
|
||||
{I18n.t(
|
||||
disabled
|
||||
? 'mkpl_plugin_tool_parameter_description'
|
||||
: 'Create_newtool_s4_value',
|
||||
)}
|
||||
</div>
|
||||
<UITag>{I18n.t('plugin_edit_tool_test_run_example_tip')}</UITag>
|
||||
</Space>
|
||||
) : (
|
||||
I18n.t(
|
||||
disabled
|
||||
? 'mkpl_plugin_tool_parameter_description'
|
||||
: 'Create_newtool_s4_value',
|
||||
)
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function -- 已经在拆了
|
||||
const ParamsForm = (
|
||||
props: ParamsFormProps,
|
||||
ref: Ref<{ data: Array<APIParameter> } | null>,
|
||||
) => {
|
||||
const {
|
||||
requestParams,
|
||||
disabled,
|
||||
check,
|
||||
needCheck = false,
|
||||
height = 236,
|
||||
defaultKey = 'global_default',
|
||||
debugExampleStatus = DebugExampleStatus.Default,
|
||||
showExampleTag = false,
|
||||
supportFileTypeUpload = false,
|
||||
} = props;
|
||||
const [data, setData] = useState(
|
||||
cloneDeep(requestParams ? requestParams : []),
|
||||
);
|
||||
const [resourceData, setResourceData] = useState(
|
||||
cloneDeep(requestParams ? requestParams : []),
|
||||
);
|
||||
useEffect(() => {
|
||||
setData(requestParams ? cloneDeep(requestParams) : []);
|
||||
setResourceData(requestParams ? cloneDeep(requestParams) : []);
|
||||
}, [requestParams]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
data,
|
||||
}));
|
||||
|
||||
const [flag, setFlag] = useState<boolean>(false);
|
||||
// 添加子节点
|
||||
const addChildNode = (record: APIParameter) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let result: APIParameter & {
|
||||
path?: Array<number>;
|
||||
} = {};
|
||||
// 1.查找路径
|
||||
findPathById({
|
||||
data,
|
||||
callback: (item: APIParameter, path: Array<number>) => {
|
||||
if (item[ROWKEY] === record[ROWKEY]) {
|
||||
result = { ...item, path };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// 2.拼接路径
|
||||
const path = (result?.path || [])
|
||||
.map((v: number) => [v, childrenRecordName])
|
||||
.flat();
|
||||
// newPath是模版的路径,下面添加节点newNode可以直接从该路径引用
|
||||
const newPath = findTemplateNodeByPath(resourceData, path);
|
||||
// 3.添加节点
|
||||
const newData = cloneDeep(data);
|
||||
if (Array.isArray(ObjectGet(newData, path))) {
|
||||
// 这一步是为了根据newPath找到对应的根节点,并且克隆一个新节点
|
||||
const newNode = cloneWithRandomKey(ObjectGet(resourceData, newPath)[0]);
|
||||
ObjectSet(newData, path, [...ObjectGet(newData, path), newNode]);
|
||||
}
|
||||
setData(newData);
|
||||
};
|
||||
const isShowExampleTag =
|
||||
disabled &&
|
||||
showExampleTag &&
|
||||
debugExampleStatus === DebugExampleStatus.Enable;
|
||||
const maxNum = maxDeep(data);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: I18n.t('Create_newtool_s4_name'),
|
||||
key: 'name',
|
||||
className: styles['no-wrap'],
|
||||
width: 180 + 20 * (maxNum - 1),
|
||||
minWidth: 220,
|
||||
render: (record: APIParameterRecord) => getName(record),
|
||||
},
|
||||
{
|
||||
title: getParamsTitle(isShowExampleTag, disabled),
|
||||
key: 'value',
|
||||
className: styles['no-wrap'],
|
||||
width: 200,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
render: record => (
|
||||
<ValueColRender
|
||||
record={record}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
check={check}
|
||||
needCheck={needCheck}
|
||||
defaultKey={defaultKey}
|
||||
supportFileTypeUpload={supportFileTypeUpload}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('dataset_detail_tableTitle_actions'),
|
||||
key: 'operation',
|
||||
width: 120,
|
||||
render: (record: APIParameter) => (
|
||||
<div className={getColumnClass(record)}>
|
||||
{record?.type === ParameterType.Array && (
|
||||
<UIButton
|
||||
onClick={() => {
|
||||
addChildNode(record);
|
||||
setFlag(!flag);
|
||||
}}
|
||||
icon={<IconAddChildOutlined />}
|
||||
type="secondary"
|
||||
theme="borderless"
|
||||
/>
|
||||
)}
|
||||
{record?.name === ARRAYTAG &&
|
||||
handleIsShowDelete(data, record[ROWKEY]) && (
|
||||
<UIButton
|
||||
onClick={() => {
|
||||
const clone = cloneDeep(data);
|
||||
if (record?.id) {
|
||||
deleteNode(clone, record?.id);
|
||||
setData(clone);
|
||||
}
|
||||
}}
|
||||
icon={<IconDeleteStroked />}
|
||||
type="secondary"
|
||||
theme="borderless"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const filterColumns =
|
||||
disabled || !checkHasArray(requestParams)
|
||||
? columns.filter(item => item.key !== 'operation')
|
||||
: columns;
|
||||
|
||||
const scroll = useMemo(() => ({ y: height, x: '100%' }), []);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={styles['debug-params-table']}
|
||||
pagination={false}
|
||||
columns={filterColumns}
|
||||
dataSource={data}
|
||||
rowKey={ROWKEY}
|
||||
childrenRecordName={childrenRecordName}
|
||||
expandAllRows={true}
|
||||
scroll={scroll}
|
||||
empty={
|
||||
!disabled && (
|
||||
<div className={styles.empty}>
|
||||
{I18n.t('plugin_form_no_result_desc')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(ParamsForm);
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { UITag, Typography, Space, Col, Row } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type DebugExample,
|
||||
type PluginType,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { type CheckParamsProps, STATUS } from './types';
|
||||
import { DebugParams } from './debug-components/debug-params';
|
||||
import { DebugCheck } from './debug-components/debug-check';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const getApiTitle = (pluginName, name, labelKey) => (
|
||||
<Text
|
||||
className={s['card-title']}
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: `${pluginName}.${name}`,
|
||||
style: { wordBreak: 'break-word' },
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{pluginName}.{name} {I18n.t(labelKey)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const Debug: React.FC<{
|
||||
pluginType?: PluginType;
|
||||
disabled: boolean;
|
||||
apiInfo: PluginAPIInfo;
|
||||
pluginId: string;
|
||||
apiId: string;
|
||||
pluginName: string;
|
||||
debugExample?: DebugExample;
|
||||
setDebugStatus?: (status: STATUS | undefined) => void;
|
||||
setDebugExample?: (v: DebugExample) => void;
|
||||
isViewExample?: boolean; // 查看 example 模式 标题不一样
|
||||
onSuccessCallback?: () => void;
|
||||
}> = ({
|
||||
disabled,
|
||||
apiInfo,
|
||||
pluginId,
|
||||
apiId,
|
||||
pluginName,
|
||||
setDebugStatus,
|
||||
debugExample,
|
||||
setDebugExample,
|
||||
isViewExample = false,
|
||||
pluginType,
|
||||
onSuccessCallback,
|
||||
}) => {
|
||||
const [checkParams, setCheckParams] = useState<CheckParamsProps>({});
|
||||
const [status, setStatus] = useState<STATUS | undefined>();
|
||||
const handleAction = ({
|
||||
status: innerStatus,
|
||||
request,
|
||||
response,
|
||||
failReason,
|
||||
rawResp,
|
||||
}: CheckParamsProps) => {
|
||||
setStatus(innerStatus);
|
||||
setCheckParams({
|
||||
status: innerStatus,
|
||||
request,
|
||||
response,
|
||||
failReason,
|
||||
rawResp,
|
||||
});
|
||||
setDebugStatus?.(innerStatus);
|
||||
innerStatus === STATUS.PASS &&
|
||||
setDebugExample?.({ req_example: request, resp_example: response });
|
||||
// 调试成功后回调
|
||||
innerStatus === STATUS.PASS && onSuccessCallback?.();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (debugExample) {
|
||||
setCheckParams({
|
||||
...checkParams,
|
||||
request: debugExample?.req_example,
|
||||
response: debugExample?.resp_example,
|
||||
failReason: '',
|
||||
});
|
||||
} else {
|
||||
setCheckParams({});
|
||||
}
|
||||
}, [debugExample]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={s['debug-check']}
|
||||
data-testid="plugin.tool.debug-modal-content"
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div className={s['main-container']}>
|
||||
<div className={s['card-header']}>
|
||||
{isViewExample ? (
|
||||
<Text className={s['card-title']}>
|
||||
{I18n.t('Create_newtool_s4_title')}
|
||||
</Text>
|
||||
) : (
|
||||
getApiTitle(pluginName, apiInfo.name, 'Create_newtool_s4_title')
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
maxHeight: isViewExample ? 'calc(100% - 55px)' : 542,
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<DebugParams
|
||||
pluginType={pluginType}
|
||||
disabled={disabled}
|
||||
pluginId={pluginId}
|
||||
apiId={apiId}
|
||||
requestParams={apiInfo?.request_params}
|
||||
callback={handleAction}
|
||||
debugExampleStatus={apiInfo?.debug_example_status}
|
||||
showExampleTag={!isViewExample}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={s['main-container']}>
|
||||
<div className={s['card-header']}>
|
||||
<Space style={{ width: '100%' }}>
|
||||
{isViewExample ? (
|
||||
<Text className={s['card-title']}>
|
||||
{I18n.t('plugin_edit_tool_test_run_debugging_example')}
|
||||
</Text>
|
||||
) : (
|
||||
getApiTitle(
|
||||
pluginName,
|
||||
apiInfo.name,
|
||||
'Create_newtool_s4_result',
|
||||
)
|
||||
)}
|
||||
{status === STATUS.PASS && (
|
||||
<UITag color="green">{I18n.t('plugin_s4_debug_pass')}</UITag>
|
||||
)}
|
||||
{status === STATUS.FAIL && (
|
||||
<UITag color="red">{I18n.t('plugin_s4_debug_failed')}</UITag>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
<div
|
||||
className={s['card-debug-check']}
|
||||
style={{
|
||||
height: isViewExample ? '100%' : 542,
|
||||
}}
|
||||
>
|
||||
<DebugCheck checkParams={checkParams} />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 AssistParameterType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import {
|
||||
FILE_TYPE_CONFIG,
|
||||
type FileTypeEnum,
|
||||
} from '@coze-studio/file-kit/logic';
|
||||
import { ACCEPT_UPLOAD_TYPES } from '@coze-studio/file-kit/config';
|
||||
|
||||
import { assistToExtend, parameterTypeExtendMap } from './config';
|
||||
|
||||
export const getFileAccept = (type: AssistParameterType) => {
|
||||
const { fileTypes } = parameterTypeExtendMap[assistToExtend(type)];
|
||||
|
||||
const accept = fileTypes?.reduce((prev, curr) => {
|
||||
const config = FILE_TYPE_CONFIG.find(c => c.fileType === curr);
|
||||
|
||||
if (!config) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
prev = `${prev}${prev ? ',' : ''}${config.accept.join(',')}`;
|
||||
|
||||
return prev;
|
||||
}, '');
|
||||
|
||||
if (!accept || accept === '*') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return accept;
|
||||
};
|
||||
|
||||
export const getFileTypeFromAssistType = (
|
||||
type: AssistParameterType,
|
||||
): FileTypeEnum | null => {
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const extendType = assistToExtend(type);
|
||||
|
||||
const config = Object.entries(parameterTypeExtendMap).find(
|
||||
([key]) => Number(key) === extendType,
|
||||
);
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const fileType of config[1].fileTypes) {
|
||||
const iconConfig = ACCEPT_UPLOAD_TYPES[fileType];
|
||||
|
||||
if (iconConfig) {
|
||||
return fileType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,372 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.create-modal {
|
||||
:global {
|
||||
.semi-modal {
|
||||
max-width: 1800px;
|
||||
}
|
||||
|
||||
.semi-table-row-cell {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.big-modal {
|
||||
.modal-steps {
|
||||
width: 810px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-single-line {
|
||||
:global {
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.no-wrap-min-width {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.params-layout {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.params-tag {
|
||||
margin-bottom: 18px;
|
||||
padding-top: 22px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.request-params,
|
||||
.response-params {
|
||||
:global {
|
||||
.semi-table-placeholder {
|
||||
padding: 1px 12px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.request-params-edit,
|
||||
.response-params-edit {
|
||||
:global {
|
||||
.semi-table-thead .semi-table-row-head:first-child {
|
||||
padding-left: 32px !important;
|
||||
}
|
||||
|
||||
.semi-table-placeholder {
|
||||
padding: 1px 12px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.check-box {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.form-check-tip {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
transform-origin: left;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
font-size: 12px !important;
|
||||
line-height: 16px;
|
||||
color: var(--semi-color-danger);
|
||||
}
|
||||
|
||||
.w110 {
|
||||
width: 110%;
|
||||
}
|
||||
|
||||
.plugin-icon-error {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.plugin-tooltip-error {
|
||||
width: calc(100% - 20px);
|
||||
font-size: 12px !important;
|
||||
line-height: 16px !important;
|
||||
color: var(--semi-color-danger) !important;
|
||||
}
|
||||
|
||||
.add-params-btn-wrap {
|
||||
margin: 0 24px;
|
||||
padding-bottom: 12px;
|
||||
border-top: 1px solid var(--semi-color-border);
|
||||
}
|
||||
|
||||
.empty-content {
|
||||
margin: 36px 0 54px;
|
||||
font-size: 14px;
|
||||
color: var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// hover统一样式
|
||||
.table-style-list {
|
||||
:global {
|
||||
.semi-table-body {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.semi-select {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.semi-table-row-cell {
|
||||
padding: 12px 2px !important;
|
||||
}
|
||||
|
||||
.semi-table-expand-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.semi-table-header {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 24px;
|
||||
|
||||
width: calc(100% - 48px);
|
||||
height: 1px;
|
||||
|
||||
background: var(--semi-color-border);
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-thead .semi-table-row-head:first-child {
|
||||
padding-left: 32px !important;
|
||||
}
|
||||
|
||||
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.semi-table-thead > .semi-table-row > .semi-table-row-head {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
|
||||
|
||||
background: #f7f7fa;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.semi-table-row:hover > .semi-table-row-cell {
|
||||
background: transparent !important;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
}
|
||||
|
||||
.semi-table-tbody > .semi-table-row,
|
||||
.semi-table-tbody > .semi-table-row > .semi-table-cell-fixed-left,
|
||||
.semi-table-tbody > .semi-table-row > .semi-table-cell-fixed-right,
|
||||
.semi-table-thead
|
||||
> .semi-table-row
|
||||
> .semi-table-row-head.semi-table-cell-fixed-left::before,
|
||||
.semi-table-thead
|
||||
> .semi-table-row
|
||||
> .semi-table-row-head.semi-table-cell-fixed-right::before {
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
|
||||
|
||||
background: #f7f7fa;
|
||||
}
|
||||
|
||||
.semi-spin-block.semi-spin {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.semi-table-row:hover > .semi-table-row-cell:first-child {
|
||||
border-top-left-radius: 8px !important;
|
||||
border-bottom-left-radius: 8px !important;
|
||||
}
|
||||
|
||||
.semi-table-row:hover > .semi-table-row-cell:last-child {
|
||||
border-top-right-radius: 8px !important;
|
||||
border-bottom-right-radius: 8px !important;
|
||||
}
|
||||
|
||||
.semi-icon-chevron_down {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&.request-params,
|
||||
&.response-params {
|
||||
:global {
|
||||
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
|
||||
padding-left: 16px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-modal {
|
||||
.runbtn {
|
||||
padding: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-modal-footer {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.debug-params-box {
|
||||
:global {
|
||||
.semi-table-thead > .semi-table-row > .semi-table-row-head {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.semi-table-tbody > .semi-table-row:hover > .semi-table-row-cell {
|
||||
background: transparent !important;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debug-check {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
padding-bottom: 11px;
|
||||
|
||||
:global{
|
||||
.semi-row, .semi-col{
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 100vw;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-bottom: 14px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
color: var(--light-usage-text-color-text-0, #1c1f23);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card-debug-check {
|
||||
overflow: auto;
|
||||
|
||||
height: 100%;
|
||||
max-height: 542px;
|
||||
|
||||
background: #fff;
|
||||
border: 1px solid var(--Light-usage-border---color-border, rgba(29, 28, 37, 8%));
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
.markdown-body {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debug-params-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
|
||||
border: 1px solid rgb(29 28 35 / 8%);
|
||||
border-radius: 8px;
|
||||
|
||||
.runbtn {
|
||||
margin: 0 16px;
|
||||
padding: 12px 0;
|
||||
text-align: right;
|
||||
border-top: 1px solid var(--semi-color-border);
|
||||
|
||||
:global {
|
||||
.semi-button.semi-button-loading {
|
||||
color: rgb(29 28 35 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.safe-check-error {
|
||||
color: #f93920;
|
||||
|
||||
a {
|
||||
color: #4d53e8;
|
||||
}
|
||||
}
|
||||
|
||||
.base-info-form {
|
||||
:global {
|
||||
.semi-icon-chevron_down {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-url-input {
|
||||
:global {
|
||||
.semi-input-prepend {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-url-prefix {
|
||||
max-width: 480px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
border: 1px solid rgb(29 28 35 / 8%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cascader-dropdown {
|
||||
:global {
|
||||
.semi-cascader-option-label {
|
||||
font-weight: 400;
|
||||
color: #1d1c23;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.action-input-value-pre{
|
||||
width: 115px;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.action-input-value-content{
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.reference-option-item{
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
width: 200px;
|
||||
|
||||
.reference-option-subtext{
|
||||
color:rgba(29, 28, 35, 35%)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { type OptionProps } from '@coze-arch/bot-semi/Select';
|
||||
import {
|
||||
UIInput,
|
||||
UISelect,
|
||||
InputGroup,
|
||||
Typography,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import {
|
||||
DefaultParamSource,
|
||||
type APIParameter,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface InputAndVariableItemProps {
|
||||
record: APIParameter;
|
||||
disabled?: boolean;
|
||||
onSourceChange?: (val: number) => void;
|
||||
onReferenceChange?: (val: string) => void;
|
||||
onValueChange?: (val: string) => void;
|
||||
referenceOption?: OptionProps[];
|
||||
}
|
||||
|
||||
export const InputAndVariableItem = ({
|
||||
record,
|
||||
disabled,
|
||||
onSourceChange,
|
||||
onReferenceChange,
|
||||
onValueChange,
|
||||
referenceOption,
|
||||
}: InputAndVariableItemProps) => (
|
||||
<InputGroup style={{ width: '100%', flexWrap: 'nowrap' }}>
|
||||
<UISelect
|
||||
theme="light"
|
||||
className={styles['action-input-value-pre']}
|
||||
value={record.default_param_source || DefaultParamSource.Input}
|
||||
disabled={disabled}
|
||||
optionList={[
|
||||
{
|
||||
label: I18n.t(
|
||||
'bot_ide_plugin_setting_modal_default_value_select_mode_reference',
|
||||
),
|
||||
value: DefaultParamSource.Variable,
|
||||
},
|
||||
{
|
||||
label: I18n.t(
|
||||
'bot_ide_plugin_setting_modal_default_value_select_mode_input',
|
||||
),
|
||||
value: DefaultParamSource.Input,
|
||||
},
|
||||
]}
|
||||
onChange={val => {
|
||||
onSourceChange?.(Number(val));
|
||||
|
||||
// 切换来源,清空默认值
|
||||
onReferenceChange?.('');
|
||||
onValueChange?.('');
|
||||
}}
|
||||
/>
|
||||
{record.default_param_source === DefaultParamSource.Variable ? (
|
||||
<UISelect
|
||||
theme="light"
|
||||
disabled={disabled}
|
||||
style={{ width: '100%', overflow: 'hidden' }}
|
||||
className={styles['action-input-value-content']}
|
||||
placeholder={I18n.t(
|
||||
'bot_ide_plugin_setting_modal_default_value_select_mode_reference_placeholder',
|
||||
)}
|
||||
value={record.variable_ref}
|
||||
onChange={val => {
|
||||
onReferenceChange?.(String(val));
|
||||
}}
|
||||
>
|
||||
{referenceOption?.map(item => (
|
||||
<UISelect.Option key={String(item.label)} value={String(item.label)}>
|
||||
<div className={styles['reference-option-item']}>
|
||||
<Typography.Text
|
||||
className={styles['reference-option-text']}
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: item.label,
|
||||
style: { wordBreak: 'break-word' },
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
className={styles['reference-option-subtext']}
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: item.value,
|
||||
style: { wordBreak: 'break-word' },
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.value}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</UISelect.Option>
|
||||
))}
|
||||
</UISelect>
|
||||
) : (
|
||||
<UIInput
|
||||
disabled={disabled}
|
||||
className={styles['action-input-value-content']}
|
||||
placeholder={I18n.t(
|
||||
'bot_ide_plugin_setting_modal_default_value_select_mode_input_placeholder',
|
||||
)}
|
||||
value={record.local_default}
|
||||
onChange={val => {
|
||||
onValueChange?.(String(val));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</InputGroup>
|
||||
);
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
type APIParameter,
|
||||
ParameterType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { deleteAllChildNode } from '../../utils';
|
||||
import {
|
||||
type AddChildNodeFn,
|
||||
type APIParameterRecord,
|
||||
type UpdateNodeWithDataFn,
|
||||
} from '../../types/params';
|
||||
import {
|
||||
ARRAYTAG,
|
||||
getParameterTypeLabelFromRecord,
|
||||
ParameterTypeExtend,
|
||||
ROWKEY,
|
||||
} from '../../config';
|
||||
import { CascaderItem } from '../../components/cascader-item';
|
||||
import { type ColumnsProps } from '..';
|
||||
|
||||
interface ParamTypeProps
|
||||
extends Pick<
|
||||
ColumnsProps,
|
||||
'data' | 'setData' | 'disabled' | 'checkFlag' | 'isResponse'
|
||||
> {
|
||||
record: APIParameterRecord;
|
||||
updateNodeWithData: UpdateNodeWithDataFn;
|
||||
addChildNode: AddChildNodeFn;
|
||||
enableFileType?: boolean;
|
||||
}
|
||||
|
||||
const ParamTypeColRender: FC<ParamTypeProps> = ({
|
||||
record,
|
||||
disabled,
|
||||
data,
|
||||
setData,
|
||||
checkFlag,
|
||||
isResponse,
|
||||
updateNodeWithData,
|
||||
addChildNode,
|
||||
enableFileType = false,
|
||||
}) => {
|
||||
// 删除全部子节点;
|
||||
const handleDeleteAllChildNode = (r: APIParameter) => {
|
||||
const cloneData = cloneDeep(data);
|
||||
const delStatus = deleteAllChildNode(cloneData, r[ROWKEY] as string);
|
||||
if (delStatus) {
|
||||
setData(cloneData);
|
||||
}
|
||||
};
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<>{getParameterTypeLabelFromRecord(record, record.name === ARRAYTAG)}</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CascaderItem
|
||||
check={checkFlag}
|
||||
record={record}
|
||||
enableFileType={enableFileType}
|
||||
selectCallback={([cascaderType, assistType]) => {
|
||||
let type = cascaderType;
|
||||
if (cascaderType === ParameterTypeExtend.DEFAULT) {
|
||||
type = ParameterType.String;
|
||||
}
|
||||
|
||||
if (!isResponse) {
|
||||
// 切换类型,重置default value
|
||||
if (record.global_default) {
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: ['global_default', 'global_disable'],
|
||||
value: ['', false],
|
||||
updateData: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
record,
|
||||
key: ['type', 'assist_type'],
|
||||
value: [type, assistType ?? null],
|
||||
};
|
||||
|
||||
// updateNodeWithData 会变更type类型,保留原始的type
|
||||
const recordType = record?.type;
|
||||
|
||||
if (type === ParameterType.Array) {
|
||||
updateNodeWithData({
|
||||
...payload,
|
||||
updateData: true,
|
||||
});
|
||||
addChildNode({ record, isArray: true, type, recordType });
|
||||
} else if (type === ParameterType.Object) {
|
||||
updateNodeWithData({
|
||||
...payload,
|
||||
updateData: true,
|
||||
});
|
||||
addChildNode({ record, isArray: false, type, recordType });
|
||||
} else if (
|
||||
record?.type === ParameterType.Array ||
|
||||
record?.type === ParameterType.Object
|
||||
) {
|
||||
updateNodeWithData(payload);
|
||||
handleDeleteAllChildNode(record);
|
||||
} else {
|
||||
updateNodeWithData({
|
||||
...payload,
|
||||
updateData: true,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParamTypeColRender;
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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 { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type OptionProps } from '@coze-arch/bot-semi/Select';
|
||||
import { Toast, UIButton, UIModal } from '@coze-arch/bot-semi';
|
||||
import { IconEdit } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
ParameterType,
|
||||
type APIParameter,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import {
|
||||
scrollToErrorElement,
|
||||
transformArrayToTree,
|
||||
transformTreeToObj,
|
||||
updateNodeById,
|
||||
} from '../utils';
|
||||
import { InputAndVariableItem } from '../input-and-variable';
|
||||
import ParamsForm from '../debug-components/params-form';
|
||||
import { ROWKEY } from '../config';
|
||||
import { InputItem } from './form-components';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface DefaultValueInputProps {
|
||||
record: APIParameter;
|
||||
data: Array<APIParameter>;
|
||||
defaultKey?: 'global_default' | 'local_default';
|
||||
disableKey?: 'global_disable' | 'local_disable';
|
||||
setData: (val: Array<APIParameter>) => void;
|
||||
canReference?: boolean;
|
||||
referenceOption?: OptionProps[];
|
||||
}
|
||||
|
||||
interface DefaultModalProps {
|
||||
record: APIParameter;
|
||||
defaultKey: 'global_default' | 'local_default';
|
||||
disableKey: 'global_disable' | 'local_disable';
|
||||
updateNodeAndData: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
const DefaultValueModal = ({
|
||||
record,
|
||||
defaultKey,
|
||||
disableKey,
|
||||
updateNodeAndData,
|
||||
}: DefaultModalProps) => {
|
||||
const [check, setCheck] = useState(0);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const paramsFormRef = useRef<{ data: Array<APIParameter> }>(null);
|
||||
const [defRecord, setDefRecord] = useState<APIParameter>(
|
||||
{} satisfies APIParameter,
|
||||
);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
setVisible(true);
|
||||
const r = cloneDeep(record);
|
||||
if (r[defaultKey]) {
|
||||
const tree = transformArrayToTree(
|
||||
JSON.parse(r[defaultKey] || '[]'),
|
||||
r.sub_parameters || [],
|
||||
);
|
||||
r.sub_parameters = tree;
|
||||
}
|
||||
setDefRecord(r);
|
||||
}, [record]);
|
||||
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
setDefRecord({} satisfies APIParameter);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// 校验是否必填
|
||||
setCheck(check + 1);
|
||||
const errorEle = document.getElementsByClassName('errorDebugClassTag');
|
||||
if (errorEle.length > 0) {
|
||||
scrollToErrorElement('.errorDebugClassTag');
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('tool_new_S2_feedback_failed')),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const reqParams = Object.values(
|
||||
transformTreeToObj(paramsFormRef.current?.data, false),
|
||||
);
|
||||
updateNodeAndData(defaultKey, JSON.stringify(reqParams[0]));
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UIButton
|
||||
disabled={record.is_required && record[disableKey]}
|
||||
icon={<IconEdit />}
|
||||
className={styles['arr-edit-btn']}
|
||||
style={{ width: '100%' }}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
{I18n.t('plugin_edit_tool_default_value_array_edit_button')}
|
||||
</UIButton>
|
||||
{visible ? (
|
||||
<UIModal
|
||||
title={I18n.t(
|
||||
'plugin_edit_tool_default_value_array_edit_modal_title',
|
||||
)}
|
||||
width={792}
|
||||
okText={I18n.t('Save')}
|
||||
visible={visible}
|
||||
onCancel={handleClose}
|
||||
hasCancel={false}
|
||||
onOk={handleSave}
|
||||
zIndex={1050}
|
||||
>
|
||||
<ParamsForm
|
||||
ref={paramsFormRef}
|
||||
requestParams={[defRecord]}
|
||||
defaultKey={defaultKey}
|
||||
disabled={false}
|
||||
check={check}
|
||||
needCheck={false}
|
||||
height={400}
|
||||
/>
|
||||
</UIModal>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultValueInput = ({
|
||||
record,
|
||||
data,
|
||||
setData,
|
||||
canReference = false,
|
||||
defaultKey = 'global_default', //输入框的key
|
||||
disableKey = 'global_disable', //开启按钮key
|
||||
referenceOption,
|
||||
}: DefaultValueInputProps) => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const updateNodeAndData = (key, value) => {
|
||||
updateNodeById({
|
||||
data,
|
||||
targetKey: record[ROWKEY] as string,
|
||||
field: key,
|
||||
value,
|
||||
});
|
||||
const cloneData = cloneDeep(data);
|
||||
setData(cloneData);
|
||||
};
|
||||
|
||||
if (record[defaultKey] === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
// 复杂类型暂不支持引用变量
|
||||
if (record.type === ParameterType.Array) {
|
||||
return (
|
||||
<div className={styles['modal-wrapper']}>
|
||||
<DefaultValueModal
|
||||
record={record}
|
||||
defaultKey={defaultKey}
|
||||
disableKey={disableKey}
|
||||
updateNodeAndData={updateNodeAndData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{canReference ? (
|
||||
<InputAndVariableItem
|
||||
record={record}
|
||||
disabled={!!record[disableKey]}
|
||||
referenceOption={referenceOption}
|
||||
onSourceChange={val => {
|
||||
updateNodeAndData('default_param_source', val);
|
||||
}}
|
||||
onReferenceChange={val => {
|
||||
updateNodeAndData('variable_ref', val);
|
||||
}}
|
||||
onValueChange={val => {
|
||||
updateNodeAndData(defaultKey, val);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<InputItem
|
||||
width="100%"
|
||||
placeholder={I18n.t(
|
||||
'plugin_edit_tool_default_value_input_placeholder',
|
||||
)}
|
||||
max={2000}
|
||||
val={record[defaultKey]}
|
||||
useCheck={false}
|
||||
filterSpace={false}
|
||||
disabled={!!record[disableKey]}
|
||||
callback={(e: string) => {
|
||||
updateNodeAndData(defaultKey, e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import cl from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIInput, UISelect, Typography, Tooltip } from '@coze-arch/bot-semi';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { ParameterType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
|
||||
import { checkSameName } from '../utils';
|
||||
import { type InputItemProps } from '../types';
|
||||
import s from '../index.module.less';
|
||||
import {
|
||||
ARRAYTAG,
|
||||
ParamsFormErrorStatus,
|
||||
paramsFormErrorStatusText,
|
||||
ROOTTAG,
|
||||
} from '../config';
|
||||
|
||||
const DEEP_INDENT_NUM = 20;
|
||||
|
||||
export const InputItem = ({
|
||||
val = '',
|
||||
max = 500,
|
||||
check = 0,
|
||||
width = 200,
|
||||
useCheck = true,
|
||||
filterSpace = true,
|
||||
placeholder,
|
||||
callback,
|
||||
targetKey = '',
|
||||
checkSame = false,
|
||||
checkAscii = false,
|
||||
isRequired = false,
|
||||
data,
|
||||
useBlockWrap = false,
|
||||
disabled,
|
||||
dynamicWidth = false,
|
||||
deep = 1,
|
||||
}: InputItemProps): JSX.Element => {
|
||||
const [value, setValue] = useState(val);
|
||||
const [errorStatus, setErrorStatus] = useState<number>(0);
|
||||
useEffect(() => {
|
||||
setValue(val);
|
||||
}, [val]);
|
||||
// 通过check触发校验(提交时)
|
||||
useEffect(() => {
|
||||
if (check === 0 || value === ARRAYTAG || value === ROOTTAG) {
|
||||
return;
|
||||
}
|
||||
handleCheck(value);
|
||||
}, [check]);
|
||||
// 校验
|
||||
const handleCheck = (checkVal: string) => {
|
||||
let status =
|
||||
checkVal === ''
|
||||
? ParamsFormErrorStatus.NAME_EMPTY
|
||||
: ParamsFormErrorStatus.NO_ERROR;
|
||||
if (isRequired && checkVal === '') {
|
||||
setErrorStatus(ParamsFormErrorStatus.DESC_EMPTY);
|
||||
return;
|
||||
}
|
||||
if (checkAscii) {
|
||||
if (!IS_OVERSEA) {
|
||||
setErrorStatus(ParamsFormErrorStatus.NO_ERROR);
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-control-regex
|
||||
status = /^[\x00-\x7F]+$/.test(checkVal)
|
||||
? ParamsFormErrorStatus.NO_ERROR
|
||||
: ParamsFormErrorStatus.ASCII;
|
||||
status = checkVal === '' ? ParamsFormErrorStatus.NO_ERROR : status;
|
||||
setErrorStatus(status);
|
||||
}
|
||||
if (!useCheck) {
|
||||
return;
|
||||
}
|
||||
if (!status) {
|
||||
status = !/^[\w-]+$/.test(checkVal)
|
||||
? ParamsFormErrorStatus.CHINESE
|
||||
: ParamsFormErrorStatus.NO_ERROR;
|
||||
}
|
||||
if (!status && data && checkSame) {
|
||||
status = checkSameName(data, targetKey, checkVal)
|
||||
? ParamsFormErrorStatus.REPEAT
|
||||
: ParamsFormErrorStatus.NO_ERROR;
|
||||
}
|
||||
setErrorStatus(status);
|
||||
};
|
||||
// 过滤空格、限制输入长度
|
||||
const handleFilter = (v: string) => {
|
||||
if (filterSpace) {
|
||||
v = v.replace(/\s+/g, '');
|
||||
}
|
||||
if (max > 0) {
|
||||
v = v.slice(0, max);
|
||||
}
|
||||
return v;
|
||||
};
|
||||
const hasSub =
|
||||
deep === 1
|
||||
? data?.some(
|
||||
item =>
|
||||
item.type === ParameterType.Array ||
|
||||
item.type === ParameterType.Object,
|
||||
)
|
||||
: true;
|
||||
// 每增加一层,因为有展开icon,宽度减少20
|
||||
const vWidth = dynamicWidth
|
||||
? `calc(100% - ${DEEP_INDENT_NUM * deep}px)`
|
||||
: width;
|
||||
const tipWidth = dynamicWidth
|
||||
? `calc(100% - ${DEEP_INDENT_NUM * deep}px - 8px)`
|
||||
: width;
|
||||
|
||||
const errorStatusMsg = () => (
|
||||
<>
|
||||
{dynamicWidth && !hasSub ? (
|
||||
<span style={{ display: 'inline-block', width: 22 }}></span>
|
||||
) : null}
|
||||
<Typography.Text
|
||||
component="span"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: { style: { maxWidth: '100%' } },
|
||||
},
|
||||
}}
|
||||
className={s['plugin-tooltip-error']}
|
||||
>
|
||||
{/* @ts-expect-error -- linter-disable-autofix */}
|
||||
<span>{paramsFormErrorStatusText[errorStatus]}</span>
|
||||
</Typography.Text>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<span
|
||||
style={useBlockWrap ? { display: 'inline-block', width: '100%' } : {}}
|
||||
>
|
||||
{dynamicWidth && !hasSub ? (
|
||||
<span style={{ display: 'inline-block', width: 20 }}></span>
|
||||
) : null}
|
||||
<UIInput
|
||||
placeholder={placeholder}
|
||||
disabled={disabled || value === ARRAYTAG || value === ROOTTAG}
|
||||
style={{ width: vWidth }}
|
||||
value={value}
|
||||
validateStatus={errorStatus ? 'error' : 'default'}
|
||||
onChange={(e: string) => {
|
||||
const newVal = handleFilter(e);
|
||||
callback?.(newVal);
|
||||
setValue(newVal);
|
||||
handleCheck(newVal);
|
||||
}}
|
||||
onBlur={() => {
|
||||
handleCheck(value);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
{/* 参数名称设置动态列宽 */}
|
||||
{errorStatus !== 0 && dynamicWidth ? (
|
||||
<div className={s['check-box']} style={{ width: tipWidth }}>
|
||||
<span className={cl(s['form-check-tip'], 'errorClassTag', s.w110)}>
|
||||
{errorStatusMsg()}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{/* 非参数列表设置固定最大宽 */}
|
||||
{errorStatus !== 0 && !dynamicWidth && (
|
||||
<div className={s['check-box']} style={{ width: tipWidth }}>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: 4,
|
||||
right: -15,
|
||||
}}
|
||||
className={cl(s['form-check-tip'], 'errorClassTag')}
|
||||
>
|
||||
{errorStatusMsg()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectItem = ({
|
||||
check = 0,
|
||||
useBlockWrap = false,
|
||||
record,
|
||||
disabled,
|
||||
typeOptions,
|
||||
selectCallback,
|
||||
}: InputItemProps): JSX.Element => {
|
||||
const [value, setValue] = useState(!record?.type ? undefined : record?.type);
|
||||
const [errorStatus, setErrorStatus] = useState<number>(0);
|
||||
|
||||
// 通过check触发校验(提交时)
|
||||
useEffect(() => {
|
||||
if (check === 0) {
|
||||
return;
|
||||
}
|
||||
handleCheck(value);
|
||||
}, [check]);
|
||||
// 校验
|
||||
const handleCheck = (val: string | ParameterType | undefined) => {
|
||||
const status = val === undefined ? 1 : 0;
|
||||
setErrorStatus(status);
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
style={useBlockWrap ? { display: 'inline-block', width: '100%' } : {}}
|
||||
>
|
||||
<UISelect
|
||||
theme="light"
|
||||
validateStatus={errorStatus ? 'error' : 'default'}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={e => {
|
||||
selectCallback?.(e);
|
||||
setValue(e as ParameterType);
|
||||
handleCheck(e as ParameterType);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{typeOptions?.map(item => (
|
||||
<UISelect.Option
|
||||
key={(record?.id || '') + item.label}
|
||||
value={item.value}
|
||||
>
|
||||
{item.label}
|
||||
</UISelect.Option>
|
||||
))}
|
||||
</UISelect>
|
||||
<br />
|
||||
{errorStatus !== 0 && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<span className={cl(s['form-check-tip'], 'errorClassTag', s.w110)}>
|
||||
<IconAlertCircle className={s['plugin-icon-error']} />
|
||||
<Typography.Text
|
||||
component="span"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: { style: { maxWidth: '100%' } },
|
||||
},
|
||||
}}
|
||||
className={s['plugin-tooltip-error']}
|
||||
>
|
||||
{errorStatus === 1 && (
|
||||
<span>{I18n.t('plugin_Parameter_type')}</span>
|
||||
)}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface FormTitle {
|
||||
name: string;
|
||||
required?: boolean;
|
||||
toolTipText?: string;
|
||||
}
|
||||
|
||||
export const FormTitle = (titleInfo: FormTitle) => (
|
||||
<div className="whitespace-nowrap">
|
||||
{titleInfo.name}
|
||||
{titleInfo.required ? (
|
||||
<Typography.Text style={{ color: 'red', marginLeft: -3 }}>
|
||||
{' * '}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{titleInfo.toolTipText ? (
|
||||
<Tooltip content={titleInfo.toolTipText}>
|
||||
<IconInfo
|
||||
style={{
|
||||
color: '#5f5f5f9e',
|
||||
position: 'relative',
|
||||
top: 3,
|
||||
left: 2,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,36 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.modal-wrapper {
|
||||
.arr-edit-btn {
|
||||
background-color: #f7f7fa !important;
|
||||
border-color: rgba(29, 28, 37, 12%) !important;
|
||||
|
||||
svg {
|
||||
path:first-child {
|
||||
stroke: #4D53E8;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
fill: #4D53E8;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-button-disabled {
|
||||
color: rgba(56, 55, 67, 20%) !important;
|
||||
background-color: rgba(75, 74, 88, 4%) !important;
|
||||
|
||||
svg {
|
||||
path:first-child {
|
||||
stroke: rgba(56, 55, 67, 20%);
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
fill: rgba(56, 55, 67, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function -- 历史逻辑 陆续在拆 */
|
||||
/* eslint-disable max-lines -- 历史逻辑 陆续在拆 */
|
||||
|
||||
import { cloneDeep, flow, get as ObjectGet, set as ObjectSet } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { ArrayUtil } from '@coze-arch/bot-utils';
|
||||
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
|
||||
import {
|
||||
UIIconButton,
|
||||
UISelect,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Switch,
|
||||
Space,
|
||||
Checkbox,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconAddChildOutlined, IconDeleteOutline } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
type APIParameter,
|
||||
ParameterType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import {
|
||||
defaultNode,
|
||||
deleteAllChildNode,
|
||||
deleteNode,
|
||||
findPathById,
|
||||
handleIsShowDelete,
|
||||
updateNodeById,
|
||||
} from '../utils';
|
||||
import {
|
||||
type UpdateNodeWithDataFn,
|
||||
type APIParameterRecord,
|
||||
type AddChildNodeFn,
|
||||
} from '../types';
|
||||
import s from '../index.module.less';
|
||||
import {
|
||||
childrenRecordName,
|
||||
parameterLocationOptions,
|
||||
ROWKEY,
|
||||
} from '../config';
|
||||
import { FormTitle, InputItem } from './form-components';
|
||||
import { DefaultValueInput } from './default-value-input';
|
||||
import ParamTypeColRender from './columns/param-type-col';
|
||||
|
||||
const DEEP_INDENT_NUM = 20;
|
||||
const DISABLED_REQ_SLICE = -4;
|
||||
const DISABLED_RES_SLICE = -3;
|
||||
|
||||
export interface ColumnsProps {
|
||||
data: Array<APIParameter>;
|
||||
flag: boolean;
|
||||
checkFlag: number;
|
||||
isResponse?: boolean;
|
||||
disabled: boolean;
|
||||
setCheckFlag: (val: number) => void;
|
||||
setFlag: (val: boolean) => void;
|
||||
setData: (val: Array<APIParameter>, checkDefault?: boolean) => void;
|
||||
showSecurityCheckFailedMsg: boolean;
|
||||
setShowSecurityCheckFailedMsg: (flag: boolean) => void;
|
||||
/**
|
||||
* 是否支持扩展的文件类型
|
||||
*/
|
||||
enableFileType?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
export const getColumns = ({
|
||||
data,
|
||||
checkFlag,
|
||||
isResponse = false,
|
||||
disabled,
|
||||
setCheckFlag,
|
||||
setData,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
enableFileType = false,
|
||||
}: ColumnsProps) => {
|
||||
// 添加子节点
|
||||
const addChildNode: AddChildNodeFn = ({
|
||||
record,
|
||||
isArray = false,
|
||||
type,
|
||||
recordType,
|
||||
}) => {
|
||||
const newData = cloneDeep(data);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const deleteArrayGlobalDefaultByPath = (obj: APIParameter[], path) => {
|
||||
const index = path[0];
|
||||
const child = obj[index];
|
||||
if (child && child.type === ParameterType.Array) {
|
||||
child.global_default = '';
|
||||
child.global_disable = false;
|
||||
} else {
|
||||
if (child && child.sub_parameters) {
|
||||
deleteArrayGlobalDefaultByPath(child.sub_parameters, path.slice(1));
|
||||
}
|
||||
}
|
||||
};
|
||||
setCheckFlag(0);
|
||||
let result: APIParameter & {
|
||||
path?: Array<number>;
|
||||
} = {};
|
||||
|
||||
// 1.查找路径
|
||||
findPathById({
|
||||
data,
|
||||
callback: (item: APIParameter, path: Array<number>) => {
|
||||
if (item[ROWKEY] === record[ROWKEY]) {
|
||||
result = { ...item, path };
|
||||
// 修改复杂类型结构,需要重置数组的默认值
|
||||
deleteArrayGlobalDefaultByPath(newData, path);
|
||||
}
|
||||
},
|
||||
});
|
||||
// 2.拼接路径
|
||||
const path = (result?.path || [])
|
||||
.map((v: number) => [v, childrenRecordName])
|
||||
.flat();
|
||||
// 如果是添加子节点,则更新父节点中的类型
|
||||
if (recordType) {
|
||||
const typePath = cloneDeep(path);
|
||||
typePath.pop();
|
||||
typePath.push('type');
|
||||
|
||||
// type 为4/5,切换节点的时候需要先删除子节点
|
||||
// recordType 原节点的类型
|
||||
// newData 新节点数据
|
||||
if (ObjectGet(newData, typePath) !== recordType) {
|
||||
deleteAllChildNode(newData, record[ROWKEY] as string);
|
||||
}
|
||||
|
||||
ObjectSet(newData, typePath, type);
|
||||
}
|
||||
// 3.添加节点
|
||||
if (Array.isArray(ObjectGet(newData, path))) {
|
||||
ObjectSet(newData, path, [
|
||||
...ObjectGet(newData, path),
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
defaultNode({ isArray, iscChildren: true, deep: record.deep + 1 }),
|
||||
]);
|
||||
} else {
|
||||
ObjectSet(newData, path, [
|
||||
defaultNode({
|
||||
isArray,
|
||||
iscChildren: true,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
deep: record.deep + 1,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
setData(newData);
|
||||
};
|
||||
// 删除子节点
|
||||
const deleteChildNode = (record: APIParameter) => {
|
||||
const cloneData = cloneDeep(data);
|
||||
const delStatsu = deleteNode(cloneData, record[ROWKEY] as string);
|
||||
if (delStatsu) {
|
||||
setData(cloneData);
|
||||
}
|
||||
};
|
||||
|
||||
const updateNodeWithData: UpdateNodeWithDataFn = ({
|
||||
record,
|
||||
key,
|
||||
value,
|
||||
updateData = false,
|
||||
inherit = false,
|
||||
}) => {
|
||||
if (Array.isArray(key)) {
|
||||
key.forEach((item, idx) => {
|
||||
updateNodeById({
|
||||
data,
|
||||
targetKey: record[ROWKEY] as string,
|
||||
field: item,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
value: value[idx],
|
||||
});
|
||||
});
|
||||
} else {
|
||||
updateNodeById({
|
||||
data,
|
||||
targetKey: record[ROWKEY] as string,
|
||||
field: key,
|
||||
value,
|
||||
inherit,
|
||||
});
|
||||
}
|
||||
if (updateData) {
|
||||
const cloneData = cloneDeep(data);
|
||||
setData(cloneData);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: Array<ColumnProps<APIParameter>> = [
|
||||
{
|
||||
title: () => (
|
||||
<FormTitle
|
||||
name={I18n.t('Create_newtool_s3_table_name')}
|
||||
required
|
||||
toolTipText={
|
||||
isResponse
|
||||
? I18n.t('Create_newtool_s3_table_name_tooltip')
|
||||
: I18n.t('Create_newtool_s2_table_name_tooltip')
|
||||
}
|
||||
/>
|
||||
),
|
||||
key: 'name',
|
||||
className: s['no-wrap-min-width'],
|
||||
render: (record: APIParameter & { deep?: number }) =>
|
||||
disabled ? (
|
||||
<Typography.Text
|
||||
component="span"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: { style: { maxWidth: '100%' } },
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
maxWidth: `calc(100% - ${
|
||||
DEEP_INDENT_NUM * (record.deep || 1)
|
||||
}px)`,
|
||||
}}
|
||||
>
|
||||
{record.name}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<InputItem
|
||||
check={checkFlag}
|
||||
val={record?.name}
|
||||
data={data}
|
||||
placeholder={I18n.t('Create_newtool_s2_table_name_empty')}
|
||||
useBlockWrap={true}
|
||||
checkSame={true}
|
||||
targetKey={record[ROWKEY]}
|
||||
dynamicWidth={true}
|
||||
deep={record.deep}
|
||||
callback={(e: string) => {
|
||||
// record.name = e;
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'name',
|
||||
value: e,
|
||||
updateData: true,
|
||||
});
|
||||
if (showSecurityCheckFailedMsg) {
|
||||
setShowSecurityCheckFailedMsg?.(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<FormTitle
|
||||
name={I18n.t('Create_newtool_s2_table_des')}
|
||||
required={!isResponse}
|
||||
toolTipText={
|
||||
isResponse
|
||||
? I18n.t('Create_newtool_s3_table_des_tooltip')
|
||||
: I18n.t('Create_newtool_s2_table_des_tooltip')
|
||||
}
|
||||
/>
|
||||
),
|
||||
key: 'desc',
|
||||
render: (record: APIParameter) =>
|
||||
// ,帮助用户/大模型更好地理解。
|
||||
disabled ? (
|
||||
<Typography.Text
|
||||
component="div"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
style: { wordBreak: 'break-word' },
|
||||
},
|
||||
},
|
||||
}}
|
||||
style={{ maxWidth: '100%' }}
|
||||
>
|
||||
{record.desc}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<InputItem
|
||||
check={checkFlag}
|
||||
width="100%"
|
||||
placeholder={I18n.t('plugin_Parameter_des')}
|
||||
val={record?.desc}
|
||||
useCheck={false}
|
||||
checkAscii={true}
|
||||
filterSpace={false}
|
||||
max={300}
|
||||
isRequired={isResponse ? false : true}
|
||||
callback={(e: string) => {
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'desc',
|
||||
value: e,
|
||||
});
|
||||
if (showSecurityCheckFailedMsg) {
|
||||
setShowSecurityCheckFailedMsg?.(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<FormTitle name={I18n.t('Create_newtool_s3_table_type')} required />
|
||||
),
|
||||
key: 'type',
|
||||
width: 120,
|
||||
render: (record: APIParameterRecord) => (
|
||||
<ParamTypeColRender
|
||||
record={record}
|
||||
disabled={disabled}
|
||||
data={data}
|
||||
setData={setData}
|
||||
checkFlag={checkFlag}
|
||||
updateNodeWithData={updateNodeWithData}
|
||||
addChildNode={addChildNode}
|
||||
enableFileType={enableFileType}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<FormTitle name={I18n.t('Create_newtool_s2_table_method')} required />
|
||||
),
|
||||
key: 'location',
|
||||
width: 120,
|
||||
render: (record: APIParameter) => {
|
||||
if (record.location === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
const methodLabelMap = ArrayUtil.array2Map(
|
||||
parameterLocationOptions,
|
||||
'value',
|
||||
'label',
|
||||
);
|
||||
return disabled ? (
|
||||
methodLabelMap[record.location]
|
||||
) : (
|
||||
<UISelect
|
||||
theme="light"
|
||||
defaultValue={record.location}
|
||||
onChange={e => {
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'location',
|
||||
value: e,
|
||||
updateData: true,
|
||||
inherit: true,
|
||||
});
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{parameterLocationOptions.map(item => (
|
||||
<UISelect.Option key={record?.id + item.label} value={item.value}>
|
||||
{item.label}
|
||||
</UISelect.Option>
|
||||
))}
|
||||
</UISelect>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('Create_newtool_s2_table_required'),
|
||||
width: 80,
|
||||
key: 'default',
|
||||
render: (record: APIParameter) => (
|
||||
<Checkbox
|
||||
style={{ position: 'relative', left: 18 }}
|
||||
disabled={disabled}
|
||||
defaultChecked={record.is_required}
|
||||
onChange={e => {
|
||||
// 必填 + 没有默认值 = 可见
|
||||
if (e.target.checked && !record.global_default) {
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'global_disable',
|
||||
value: false,
|
||||
updateData: true,
|
||||
inherit: true,
|
||||
});
|
||||
}
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'is_required',
|
||||
value: e.target.checked,
|
||||
updateData: true,
|
||||
inherit: true,
|
||||
});
|
||||
}}
|
||||
></Checkbox>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('plugin_api_list_table_action'),
|
||||
key: 'addChild',
|
||||
width: 107,
|
||||
render: (record: APIParameter & { deep: number }) => (
|
||||
<Space>
|
||||
{record.type === ParameterType.Object && (
|
||||
<Tooltip content={I18n.t('plugin_form_add_child_tooltip')}>
|
||||
<UIIconButton
|
||||
disabled={disabled}
|
||||
style={{ marginLeft: '8px' }}
|
||||
onClick={() => addChildNode({ record })}
|
||||
icon={<IconAddChildOutlined />}
|
||||
type="secondary"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{handleIsShowDelete(data, record[ROWKEY]) && (
|
||||
<Tooltip content={I18n.t('Delete')}>
|
||||
<UIIconButton
|
||||
disabled={disabled}
|
||||
style={{ marginLeft: '8px' }}
|
||||
onClick={() => deleteChildNode(record)}
|
||||
icon={<IconDeleteOutline />}
|
||||
type="secondary"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (!isResponse) {
|
||||
columns.splice(
|
||||
-1,
|
||||
0,
|
||||
...[
|
||||
{
|
||||
title: () => (
|
||||
<FormTitle
|
||||
name={I18n.t(
|
||||
'plugin_edit_tool_default_value_config_item_default_value',
|
||||
)}
|
||||
/>
|
||||
),
|
||||
key: 'global_default',
|
||||
width: 120,
|
||||
render: (record: APIParameter) => (
|
||||
<DefaultValueInput record={record} data={data} setData={setData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<FormTitle
|
||||
name={I18n.t('plugin_edit_tool_default_value_config_item_enable')}
|
||||
toolTipText={I18n.t(
|
||||
'plugin_edit_tool_default_value_config_item_enable_tip',
|
||||
)}
|
||||
/>
|
||||
),
|
||||
key: 'global_disable',
|
||||
width: 78,
|
||||
render: (record: APIParameter) => {
|
||||
if (record.global_default === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
const switchNode = (
|
||||
<Switch
|
||||
style={{ position: 'relative', top: 3, left: 12 }}
|
||||
defaultChecked={!record.global_disable}
|
||||
disabled={record.is_required && !record.global_default}
|
||||
onChange={e => {
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'global_disable',
|
||||
value: !e,
|
||||
updateData: true,
|
||||
inherit: true,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return record.is_required && !record.global_default ? (
|
||||
<Tooltip
|
||||
content={I18n.t(
|
||||
'plugin_edit_tool_default_value_config_item_enable_disable_tip',
|
||||
)}
|
||||
>
|
||||
{switchNode}
|
||||
</Tooltip>
|
||||
) : (
|
||||
switchNode
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
//出参场景,移除 required,增加 enabled 开关
|
||||
|
||||
if (isResponse) {
|
||||
const targetIndex = columns.findIndex(c => c.key === 'default');
|
||||
|
||||
columns.splice(targetIndex, 1);
|
||||
columns.splice(-1, 0, {
|
||||
title: (
|
||||
<FormTitle
|
||||
name={I18n.t('plugin_edit_tool_default_value_config_item_enable')}
|
||||
toolTipText={I18n.t('plugin_edit_tool_output_param_enable_tip')}
|
||||
/>
|
||||
),
|
||||
key: 'global_disable',
|
||||
width: 78,
|
||||
render: (record: APIParameter) => {
|
||||
if (record.global_default === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
const switchNode = (
|
||||
<Switch
|
||||
style={{ position: 'relative', top: 3, left: 12 }}
|
||||
defaultChecked={!record.global_disable}
|
||||
onChange={e => {
|
||||
updateNodeWithData({
|
||||
record,
|
||||
key: 'global_disable',
|
||||
value: !e,
|
||||
updateData: true,
|
||||
inherit: true,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return switchNode;
|
||||
},
|
||||
});
|
||||
}
|
||||
return flow(
|
||||
// 将 columns 以函数参数形式传入,而非直接传给组合函数(`flow(...)(columns)`)是为了利于类型推导
|
||||
() => columns,
|
||||
// 只读状态不展示后四项操作列
|
||||
newColumns => {
|
||||
const len = isResponse ? DISABLED_RES_SLICE : DISABLED_REQ_SLICE;
|
||||
return disabled ? newColumns.slice(0, len) : newColumns;
|
||||
},
|
||||
// response不需要location字段
|
||||
newColumns =>
|
||||
isResponse
|
||||
? newColumns.filter(item => item.key !== 'location')
|
||||
: newColumns,
|
||||
)();
|
||||
};
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import {
|
||||
type Dispatch,
|
||||
type ReactNode,
|
||||
type SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIButton, Toast, Table } from '@coze-arch/bot-semi';
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
type APIParameter,
|
||||
type UpdateAPIRequest,
|
||||
type PluginAPIInfo,
|
||||
type UpdateAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
initParamsDefault,
|
||||
defaultNode,
|
||||
maxDeep,
|
||||
scrollToBottom,
|
||||
scrollToErrorElement,
|
||||
sleep,
|
||||
} from './utils';
|
||||
import { ERROR_CODE, type RenderEnhancedComponentProps } from './types';
|
||||
import { getColumns } from './params-components';
|
||||
import { ROWKEY, childrenRecordName } from './config';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const STARTNUM = 4;
|
||||
const CHANGENUM = 13;
|
||||
const SMALLGAP = 19;
|
||||
const MAXZGAP = 40;
|
||||
const TIMER = 100;
|
||||
|
||||
export interface UseRequestParamsProps {
|
||||
pluginId: string;
|
||||
apiId?: string;
|
||||
requestParams: Array<APIParameter> | undefined;
|
||||
step?: number;
|
||||
disabled: boolean;
|
||||
showSecurityCheckFailedMsg?: boolean;
|
||||
setShowSecurityCheckFailedMsg?: Dispatch<SetStateAction<boolean>>;
|
||||
editVersion?: number;
|
||||
functionName?: string;
|
||||
apiInfo?: PluginAPIInfo;
|
||||
spaceID: string;
|
||||
onSuccess?: (params: UpdateAPIResponse) => void;
|
||||
renderEnhancedComponent?: RenderEnhancedComponentProps['renderParamsComponent'];
|
||||
}
|
||||
|
||||
export interface UseRequestParamsReturnValue {
|
||||
submitRequestParams: () => Promise<boolean>;
|
||||
requestParamsNode: JSX.Element;
|
||||
nlTool?: ReactNode;
|
||||
}
|
||||
|
||||
export const useRequestParams = ({
|
||||
apiInfo,
|
||||
pluginId,
|
||||
apiId,
|
||||
requestParams,
|
||||
disabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
functionName,
|
||||
spaceID,
|
||||
onSuccess,
|
||||
renderEnhancedComponent,
|
||||
}: UseRequestParamsProps): UseRequestParamsReturnValue => {
|
||||
const [data, setFormData] = useState<Array<APIParameter>>(
|
||||
requestParams ? requestParams : [],
|
||||
);
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const setData = (formData, checkDefault = true) => {
|
||||
let fd = formData;
|
||||
if (checkDefault) {
|
||||
fd = initParamsDefault(formData, 'global_default');
|
||||
}
|
||||
setFormData(fd);
|
||||
};
|
||||
const [flag, setFlag] = useState<boolean>(false); // 为了更新视图
|
||||
const [checkFlag, setCheckFlag] = useState<number>(0); // 全局校验用
|
||||
const columns = getColumns({
|
||||
data,
|
||||
flag,
|
||||
checkFlag,
|
||||
setCheckFlag,
|
||||
setFlag,
|
||||
setData,
|
||||
disabled,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
showSecurityCheckFailedMsg,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
setShowSecurityCheckFailedMsg,
|
||||
enableFileType: true,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (
|
||||
Array.isArray(requestParams) &&
|
||||
requestParams.length === 0 &&
|
||||
Array.isArray(data) &&
|
||||
data.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setData(requestParams ? requestParams : []);
|
||||
}, [disabled, requestParams]);
|
||||
const submitRequestParams = async () => {
|
||||
setCheckFlag(checkFlag + 1);
|
||||
const sleepTime = 100;
|
||||
await sleep(sleepTime);
|
||||
if (!apiId || document.getElementsByClassName('errorClassTag').length > 0) {
|
||||
scrollToErrorElement('.errorClassTag');
|
||||
Toast.error({
|
||||
content: I18n.t('tool_new_S2_feedback_failed'),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const params: UpdateAPIRequest = {
|
||||
plugin_id: pluginId,
|
||||
api_id: apiId,
|
||||
request_params: data,
|
||||
edit_version: editVersion,
|
||||
function_name: functionName,
|
||||
};
|
||||
const resData = await PluginDevelopApi.UpdateAPI(params, {
|
||||
__disableErrorToast: true,
|
||||
});
|
||||
onSuccess?.(resData);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code, msg } = error;
|
||||
if (Number(code) === ERROR_CODE.SAFE_CHECK) {
|
||||
setShowSecurityCheckFailedMsg?.(true);
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const addParams = () => {
|
||||
setCheckFlag(0);
|
||||
const cloneData = cloneDeep(data);
|
||||
cloneData.push(defaultNode());
|
||||
setData(cloneData);
|
||||
setTimeout(() => {
|
||||
scrollToBottom(document.getElementsByClassName('semi-table-body')[0]);
|
||||
}, TIMER);
|
||||
};
|
||||
const maxNum = maxDeep(data);
|
||||
|
||||
return {
|
||||
submitRequestParams,
|
||||
requestParamsNode: (
|
||||
<div>
|
||||
<div
|
||||
className={s['table-wrapper']}
|
||||
style={{ minWidth: 1008, overflowY: 'auto' }}
|
||||
>
|
||||
<Table
|
||||
// 最小宽度,为了兼容多层级场景,最大层级可支持超过50层
|
||||
// 最小宽度 = 模块最小宽度 + (当前层级数 - 宽度变化起始层级) * (当前层级数 < 宽度变化起始层级 ? 小间隔数 : 大间隔数)
|
||||
style={{
|
||||
minWidth: `calc(1008px + ${
|
||||
(maxNum - STARTNUM) * (maxNum < CHANGENUM ? SMALLGAP : MAXZGAP)
|
||||
}px)`,
|
||||
}} // 从第4层开始,每多一层增加19px
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey={ROWKEY}
|
||||
childrenRecordName={childrenRecordName}
|
||||
expandAllRows={true}
|
||||
className={classNames(
|
||||
disabled ? s['request-params'] : s['request-params-edit'],
|
||||
s['table-style-list'],
|
||||
)}
|
||||
empty={<div></div>}
|
||||
/>
|
||||
{!disabled && (
|
||||
<div
|
||||
style={
|
||||
Array.isArray(data) && data.length === 0 ? { borderTop: 0 } : {}
|
||||
}
|
||||
className={s['add-params-btn-wrap']}
|
||||
>
|
||||
<UIButton
|
||||
disabled={disabled}
|
||||
icon={<IconAdd />}
|
||||
style={{ marginTop: 12 }}
|
||||
type="tertiary"
|
||||
onClick={addParams}
|
||||
>
|
||||
{I18n.t('Create_newtool_s3_table_new')}
|
||||
</UIButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
nlTool: renderEnhancedComponent?.({
|
||||
disabled: !data?.length || disabled,
|
||||
src: 'request',
|
||||
originParams: data,
|
||||
apiInfo,
|
||||
onSetParams: p => setFormData(p),
|
||||
spaceID,
|
||||
pluginId,
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import {
|
||||
type Dispatch,
|
||||
type ReactNode,
|
||||
type SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
import { UIButton, Toast, UIModal, Table } from '@coze-arch/bot-semi';
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
PluginType,
|
||||
type APIParameter,
|
||||
type UpdateAPIRequest,
|
||||
type PluginAPIInfo,
|
||||
type UpdateAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
addDepthAndValue,
|
||||
defaultNode,
|
||||
sleep,
|
||||
maxDeep,
|
||||
scrollToErrorElement,
|
||||
scrollToBottom,
|
||||
initParamsDefault,
|
||||
doRemoveDefaultFromResponseParams,
|
||||
} from './utils';
|
||||
import {
|
||||
type RenderEnhancedComponentProps,
|
||||
type CheckParamsProps,
|
||||
ERROR_CODE,
|
||||
STATUS,
|
||||
} from './types';
|
||||
import { getColumns } from './params-components';
|
||||
import { DebugParams } from './debug-components/debug-params';
|
||||
import { ROWKEY, childrenRecordName } from './config';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface UseRequestParamsProps {
|
||||
pluginId: string;
|
||||
apiId: string;
|
||||
requestParams: Array<APIParameter> | undefined;
|
||||
responseParams: Array<APIParameter> | undefined;
|
||||
step?: number;
|
||||
disabled: boolean;
|
||||
showSecurityCheckFailedMsg?: boolean;
|
||||
setShowSecurityCheckFailedMsg?: Dispatch<SetStateAction<boolean>>;
|
||||
editVersion?: number;
|
||||
pluginType?: PluginType;
|
||||
functionName?: string;
|
||||
apiInfo?: PluginAPIInfo;
|
||||
spaceID: string;
|
||||
onSuccess?: (params: UpdateAPIResponse) => void;
|
||||
renderEnhancedComponent?: RenderEnhancedComponentProps['renderParamsComponent'];
|
||||
}
|
||||
|
||||
export interface UseRequestParamsReturnValue {
|
||||
submitResponseParams: () => Promise<boolean>;
|
||||
responseParamsNode: JSX.Element;
|
||||
extra?: ReactNode;
|
||||
}
|
||||
|
||||
const SLEEP_TIME = 100;
|
||||
const TIMER = 100;
|
||||
|
||||
export const useResponseParams = ({
|
||||
apiInfo,
|
||||
pluginId,
|
||||
requestParams,
|
||||
responseParams,
|
||||
apiId,
|
||||
disabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
pluginType,
|
||||
functionName,
|
||||
spaceID,
|
||||
onSuccess,
|
||||
renderEnhancedComponent,
|
||||
}: UseRequestParamsProps): UseRequestParamsReturnValue => {
|
||||
const [data, setFormData] = useState<Array<APIParameter>>(
|
||||
responseParams || [],
|
||||
);
|
||||
const [flag, setFlag] = useState<boolean>(false); // 为了更新视图
|
||||
const [checkFlag, setCheckFlag] = useState<number>(0); // 全局校验用
|
||||
const [inputModal, setInputModal] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const setData = useMemoizedFn((formData, checkDefault = true) => {
|
||||
let fd = formData;
|
||||
if (checkDefault) {
|
||||
fd = initParamsDefault(formData, 'global_default');
|
||||
}
|
||||
setFormData(fd);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
Array.isArray(responseParams) &&
|
||||
responseParams.length === 0 &&
|
||||
Array.isArray(data) &&
|
||||
data.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setData(responseParams || []);
|
||||
}, [disabled, responseParams]);
|
||||
const columns = getColumns({
|
||||
data,
|
||||
flag,
|
||||
checkFlag,
|
||||
setCheckFlag,
|
||||
setFlag,
|
||||
setData,
|
||||
isResponse: true,
|
||||
disabled,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
showSecurityCheckFailedMsg,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
setShowSecurityCheckFailedMsg,
|
||||
enableFileType: true,
|
||||
});
|
||||
|
||||
const submitResponseParams = async () => {
|
||||
setCheckFlag(checkFlag + 1);
|
||||
await sleep(SLEEP_TIME);
|
||||
if (!apiId || document.getElementsByClassName('errorClassTag').length > 0) {
|
||||
scrollToErrorElement('.errorClassTag');
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('tool_new_S2_feedback_failed')),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!apiId) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const params: UpdateAPIRequest = {
|
||||
plugin_id: pluginId,
|
||||
api_id: apiId,
|
||||
response_params: doRemoveDefaultFromResponseParams(data, false),
|
||||
edit_version: editVersion,
|
||||
function_name: functionName,
|
||||
};
|
||||
|
||||
const resData = await PluginDevelopApi.UpdateAPI(params, {
|
||||
__disableErrorToast: true,
|
||||
});
|
||||
onSuccess?.(resData);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code, msg } = error;
|
||||
if (Number(code) === ERROR_CODE.SAFE_CHECK) {
|
||||
setShowSecurityCheckFailedMsg?.(true);
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const handleAction = ({
|
||||
response_params,
|
||||
status,
|
||||
failReason,
|
||||
}: CheckParamsProps) => {
|
||||
if (status === STATUS.PASS && response_params) {
|
||||
addDepthAndValue(response_params);
|
||||
setData(response_params);
|
||||
Toast.success({
|
||||
content: I18n.t('plugin_s3_success'),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(failReason ?? I18n.t('plugin_s3_failed')),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
setInputModal(false);
|
||||
};
|
||||
|
||||
const handleActionNoParams = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const resData = await PluginDevelopApi.DebugAPI({
|
||||
plugin_id: pluginId,
|
||||
api_id: apiId,
|
||||
parameters: JSON.stringify({}),
|
||||
operation: 2,
|
||||
});
|
||||
if (resData?.success && resData?.response_params) {
|
||||
setData(resData.response_params);
|
||||
Toast.success({
|
||||
content: I18n.t('plugin_s3_success'),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(
|
||||
resData?.reason ?? I18n.t('plugin_s3_failed'),
|
||||
),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('plugin_s3_failed')),
|
||||
duration: 3,
|
||||
theme: 'light',
|
||||
showClose: false,
|
||||
});
|
||||
logger.persist.error({
|
||||
message: 'Custom Error: debug api failed',
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error,
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
const addParams = () => {
|
||||
setCheckFlag(0);
|
||||
const cloneData = cloneDeep(data);
|
||||
cloneData.push(defaultNode());
|
||||
setData(cloneData);
|
||||
setFlag(!flag);
|
||||
setTimeout(() => {
|
||||
scrollToBottom(document.getElementsByClassName('semi-table-body')[0]);
|
||||
}, TIMER);
|
||||
};
|
||||
|
||||
const maxNum = maxDeep(data);
|
||||
|
||||
return {
|
||||
submitResponseParams,
|
||||
responseParamsNode: (
|
||||
<div>
|
||||
<div
|
||||
className={s['table-wrapper']}
|
||||
style={{ minWidth: 1008, overflowY: 'auto' }}
|
||||
>
|
||||
<Table
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- ui
|
||||
style={{ minWidth: `calc(1008px + ${(maxNum - 6) * 20}px)` }} // 从第6层开始,每多一层增加20px
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey={ROWKEY}
|
||||
childrenRecordName={childrenRecordName}
|
||||
expandAllRows={true}
|
||||
className={classNames(
|
||||
disabled ? s['request-params'] : s['request-params-edit'],
|
||||
s['table-style-list'],
|
||||
)}
|
||||
empty={<div></div>}
|
||||
/>
|
||||
{!disabled && (
|
||||
<div
|
||||
className={s['add-params-btn-wrap']}
|
||||
style={
|
||||
Array.isArray(data) && data.length === 0 ? { borderTop: 0 } : {}
|
||||
}
|
||||
>
|
||||
<UIButton
|
||||
disabled={disabled}
|
||||
icon={<IconAdd />}
|
||||
style={{ marginTop: 12 }}
|
||||
type="tertiary"
|
||||
onClick={addParams}
|
||||
>
|
||||
{I18n.t('Create_newtool_s3_table_new')}
|
||||
</UIButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<UIModal
|
||||
visible={inputModal}
|
||||
title={I18n.t('plugin_s3_Parse')}
|
||||
className={s['input-modal']}
|
||||
keepDOM={false}
|
||||
footer={<></>}
|
||||
width={800}
|
||||
maskClosable={false}
|
||||
onCancel={() => setInputModal(false)}
|
||||
>
|
||||
<DebugParams
|
||||
disabled={disabled}
|
||||
pluginId={pluginId}
|
||||
apiId={apiId}
|
||||
requestParams={requestParams}
|
||||
operation={2}
|
||||
btnText={I18n.t('Create_newtool_s3_button_auto')}
|
||||
callback={handleAction}
|
||||
/>
|
||||
</UIModal>
|
||||
</div>
|
||||
),
|
||||
extra: (
|
||||
<>
|
||||
{renderEnhancedComponent?.({
|
||||
disabled: !data?.length || disabled,
|
||||
src: 'response',
|
||||
originParams: data,
|
||||
apiInfo,
|
||||
onSetParams: p => setData(p),
|
||||
spaceID,
|
||||
pluginId,
|
||||
})}
|
||||
<Button
|
||||
disabled={disabled || pluginType === PluginType.LOCAL}
|
||||
className="!mr-2"
|
||||
color="primary"
|
||||
loading={loading}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (Array.isArray(requestParams) && requestParams.length > 0) {
|
||||
setInputModal(true);
|
||||
} else {
|
||||
handleActionNoParams();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading
|
||||
? I18n.t('plugin_s3_Parsing')
|
||||
: I18n.t('Create_newtool_s3_button_auto')}
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 @coze-arch/no-batch-import-or-export */
|
||||
export * from './modal';
|
||||
export * from './params';
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 APIParameter } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
export enum STATUS {
|
||||
PASS = 'PASS',
|
||||
FAIL = 'FAIL',
|
||||
WAIT = 'WAIT',
|
||||
}
|
||||
export interface CheckParamsProps {
|
||||
status?: STATUS;
|
||||
request?: string;
|
||||
response?: string;
|
||||
failReason?: string;
|
||||
response_params?: Array<APIParameter>;
|
||||
rawResp?: string;
|
||||
}
|
||||
|
||||
export interface StepUpdateApiRes {
|
||||
code: string | number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export const ERROR_CODE = {
|
||||
SAFE_CHECK: 720092020,
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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 ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
type APIParameter,
|
||||
type AssistParameterType,
|
||||
type ParameterType,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { type ParameterTypeExtend, type PluginParameterType } from '../config';
|
||||
|
||||
export interface APIParameterRecord extends APIParameter {
|
||||
deep?: number;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface UpdateNodeWithDataFn {
|
||||
(params: {
|
||||
record: APIParameter;
|
||||
key: string | Array<string>;
|
||||
value: unknown;
|
||||
updateData?: boolean;
|
||||
checkDefault?: boolean;
|
||||
inherit?: boolean;
|
||||
}): void;
|
||||
}
|
||||
|
||||
export interface AddChildNodeFn {
|
||||
(params: {
|
||||
record: APIParameterRecord;
|
||||
isArray?: boolean;
|
||||
isObj?: boolean;
|
||||
type?: ParameterType;
|
||||
recordType?: ParameterType;
|
||||
}): void;
|
||||
}
|
||||
|
||||
export interface InputItemProps {
|
||||
val?: string;
|
||||
max?: number;
|
||||
check?: number;
|
||||
width?: number | string;
|
||||
useCheck?: boolean;
|
||||
checkAscii?: boolean;
|
||||
isRequired?: boolean;
|
||||
placeholder?: string;
|
||||
filterSpace?: boolean;
|
||||
callback?: (val: string) => void;
|
||||
selectCallback?: (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
val: string | number | any[] | Record<string, any> | undefined,
|
||||
) => void;
|
||||
targetKey?: string;
|
||||
data?: Array<APIParameter>;
|
||||
checkSame?: boolean;
|
||||
useBlockWrap?: boolean;
|
||||
disabled?: boolean;
|
||||
record?: APIParameterRecord;
|
||||
dynamicWidth?: boolean;
|
||||
deep?: number;
|
||||
typeOptions?: Array<Record<string, string | number>>;
|
||||
}
|
||||
|
||||
export type CascaderValueType = [PluginParameterType, ParameterTypeExtend?];
|
||||
export type CascaderOnChangValueType = [
|
||||
PluginParameterType,
|
||||
AssistParameterType?,
|
||||
];
|
||||
export interface RenderEnhancedComponentProps {
|
||||
renderDescComponent: (props: {
|
||||
onSetDescription: (desc: string) => void;
|
||||
originDesc: string | undefined;
|
||||
className: string;
|
||||
disabled?: boolean;
|
||||
plugin_id: string;
|
||||
space_id: string;
|
||||
}) => ReactNode;
|
||||
renderParamsComponent: (props: {
|
||||
size?: 'small' | 'default';
|
||||
src: 'request' | 'response';
|
||||
apiInfo: PluginAPIInfo | undefined;
|
||||
originParams: APIParameter[];
|
||||
onSetParams: (params: APIParameter[]) => void;
|
||||
disabled?: boolean;
|
||||
spaceID: string;
|
||||
pluginId: string;
|
||||
}) => ReactNode;
|
||||
}
|
||||
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* 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 complexity */
|
||||
/* eslint-disable max-lines */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- 一些历史any 改不动 */
|
||||
import { nanoid } from 'nanoid';
|
||||
import { cloneDeep, has, isEmpty, isNumber, isObject } from 'lodash-es';
|
||||
import {
|
||||
type APIParameter,
|
||||
ParameterLocation,
|
||||
ParameterType,
|
||||
DefaultParamSource,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { ARRAYTAG, ROWKEY, childrenRecordName } from './config';
|
||||
|
||||
// 遍历树,返回目标id路径
|
||||
export const findPathById = ({
|
||||
data,
|
||||
callback,
|
||||
childrenName = childrenRecordName,
|
||||
path = [],
|
||||
}: {
|
||||
data: any;
|
||||
callback: (item: APIParameter, path: Array<number>) => void;
|
||||
childrenName?: string;
|
||||
path?: Array<number>;
|
||||
}) => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const clonePath = JSON.parse(JSON.stringify(path));
|
||||
clonePath.push(i);
|
||||
callback(data[i], clonePath);
|
||||
if (data[i][childrenName] && data[i][childrenName].length > 0) {
|
||||
findPathById({
|
||||
data: data[i][childrenName],
|
||||
callback,
|
||||
childrenName,
|
||||
path: clonePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 给每层对象增加层级深度标识
|
||||
export const addDepthAndValue = (
|
||||
tree: any,
|
||||
valKey: 'global_default' | 'local_default' = 'global_default',
|
||||
depth = 1,
|
||||
) => {
|
||||
if (!Array.isArray(tree)) {
|
||||
return;
|
||||
}
|
||||
// 遍历树中的每个节点
|
||||
for (const node of tree) {
|
||||
// 为当前节点添加深度标识符
|
||||
node.deep = depth;
|
||||
if (node[valKey]) {
|
||||
node.value = node[valKey];
|
||||
}
|
||||
// 如果当前节点有子节点,则递归地为子节点添加深度标识符
|
||||
if (node[childrenRecordName]) {
|
||||
addDepthAndValue(node[childrenRecordName], valKey, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 将深度信息push到一个数组里,最后取最大值
|
||||
export const handleDeepArr = (tree: any, deepArr: Array<number> = []) => {
|
||||
if (!Array.isArray(tree)) {
|
||||
return;
|
||||
}
|
||||
// 遍历树中的每个节点
|
||||
for (const node of tree) {
|
||||
// 为当前节点添加深度标识符
|
||||
if (isNumber(node.deep)) {
|
||||
deepArr.push(node.deep);
|
||||
}
|
||||
|
||||
if (node[childrenRecordName]) {
|
||||
handleDeepArr(node[childrenRecordName], deepArr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 返回最大深度
|
||||
export const maxDeep = (tree: any) => {
|
||||
if (!Array.isArray(tree) || tree.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const arr: Array<number> = [];
|
||||
handleDeepArr(tree, arr);
|
||||
return Math.max.apply(null, arr);
|
||||
};
|
||||
|
||||
interface DefaultNode {
|
||||
isArray?: boolean;
|
||||
iscChildren?: boolean;
|
||||
deep?: number;
|
||||
}
|
||||
|
||||
// 默认子节点
|
||||
export const defaultNode = ({
|
||||
isArray = false,
|
||||
iscChildren = false,
|
||||
deep = 1,
|
||||
}: DefaultNode = {}) => ({
|
||||
[ROWKEY]: nanoid(),
|
||||
name: isArray ? ARRAYTAG : '',
|
||||
desc: '',
|
||||
type: ParameterType.String,
|
||||
location: iscChildren ? undefined : ParameterLocation.Query,
|
||||
is_required: true,
|
||||
sub_parameters: [],
|
||||
deep,
|
||||
});
|
||||
|
||||
// 删除当前节点
|
||||
export const deleteNode = (data: any, targetKey: string) => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i][ROWKEY] === targetKey) {
|
||||
data.splice(i, 1);
|
||||
return true;
|
||||
} else if (
|
||||
data[i][childrenRecordName] &&
|
||||
data[i][childrenRecordName].length > 0
|
||||
) {
|
||||
if (deleteNode(data[i][childrenRecordName], targetKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 删除全部子节点
|
||||
export const deleteAllChildNode = (data: any, targetKey: string) => {
|
||||
for (const item of data) {
|
||||
if (item[ROWKEY] === targetKey) {
|
||||
item[childrenRecordName] = [];
|
||||
return true;
|
||||
} else if (
|
||||
Array.isArray(item[childrenRecordName]) &&
|
||||
item[childrenRecordName].length > 0
|
||||
) {
|
||||
if (deleteAllChildNode(item[childrenRecordName], targetKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
interface UpdateNodeById {
|
||||
data: APIParameter[];
|
||||
targetKey: string;
|
||||
field: string;
|
||||
value: any;
|
||||
/** 数组的子节点是否需要继承父节点的字段值,当前只有可见性开关需要继承 */
|
||||
inherit?: boolean;
|
||||
}
|
||||
|
||||
const updateNodeByVal = (data: any, field: any, val: any) => {
|
||||
for (const item of data) {
|
||||
item[field] = val;
|
||||
if (Array.isArray(item[childrenRecordName])) {
|
||||
updateNodeByVal(item[childrenRecordName], field, val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 更新节点信息
|
||||
export const updateNodeById = ({
|
||||
data,
|
||||
targetKey,
|
||||
field,
|
||||
value,
|
||||
inherit = false,
|
||||
}: UpdateNodeById) => {
|
||||
for (const item of data) {
|
||||
if (item[ROWKEY] === targetKey) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
item[field] = value;
|
||||
if (
|
||||
inherit &&
|
||||
Array.isArray(item[childrenRecordName]) &&
|
||||
item[childrenRecordName].length > 0
|
||||
) {
|
||||
updateNodeByVal(item[childrenRecordName], field, value);
|
||||
}
|
||||
return;
|
||||
} else if (
|
||||
Array.isArray(item[childrenRecordName]) &&
|
||||
item[childrenRecordName].length > 0
|
||||
) {
|
||||
updateNodeById({
|
||||
data: item[childrenRecordName],
|
||||
targetKey,
|
||||
field,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 根据路径找对应模版值
|
||||
export const findTemplateNodeByPath = (
|
||||
dsl: any,
|
||||
path: Array<string | number>,
|
||||
) => {
|
||||
let node = cloneDeep(dsl);
|
||||
const newPath = [...path]; //创建新的路径,避免修改原路径
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
// 如果存在节点,说明是源数据节点上增加子节点
|
||||
if (node[path[i]]) {
|
||||
node = node[path[i]];
|
||||
} else {
|
||||
// 如果不存在,说明是新增的节点增加子节点,这时需要将路径指向原始节点(第一个节点)
|
||||
node = node[0];
|
||||
newPath[i] = 0;
|
||||
}
|
||||
}
|
||||
return newPath;
|
||||
};
|
||||
|
||||
// 树转换成对象
|
||||
export const transformTreeToObj = (tree: any, checkType = true): any =>
|
||||
// 树的每一层级表示一个对象的属性集
|
||||
|
||||
tree.reduce((acc: any, item: any) => {
|
||||
let arrTemp = [];
|
||||
switch (item.type) {
|
||||
case ParameterType.String:
|
||||
if (item.value) {
|
||||
acc[item.name] = item.value;
|
||||
}
|
||||
if (!checkType) {
|
||||
acc[item.name] = item.value;
|
||||
}
|
||||
break;
|
||||
case ParameterType.Integer:
|
||||
case ParameterType.Number:
|
||||
if (item.value) {
|
||||
acc[item.name] = Number(item.value);
|
||||
}
|
||||
if (!checkType) {
|
||||
acc[item.name] = item.value;
|
||||
}
|
||||
break;
|
||||
case ParameterType.Bool:
|
||||
if (item.value) {
|
||||
acc[item.name] = item.value === 'true';
|
||||
}
|
||||
if (!checkType) {
|
||||
acc[item.name] = item.value;
|
||||
}
|
||||
break;
|
||||
case ParameterType.Object:
|
||||
if (item.sub_parameters) {
|
||||
const obj = transformTreeToObj(item.sub_parameters, checkType);
|
||||
if (!isEmpty(obj)) {
|
||||
acc[item.name] = obj;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ParameterType.Array:
|
||||
/**
|
||||
* 如果是数组,需要过滤掉空的项(且数组的子项非object和array)
|
||||
* 这里用temp接收过滤后的子项,避免直接修改原数组(因为原数组和页面数据绑定,不能直接删除空项)
|
||||
*/
|
||||
arrTemp = item.sub_parameters;
|
||||
if (
|
||||
[
|
||||
ParameterType.Bool,
|
||||
ParameterType.Integer,
|
||||
ParameterType.Number,
|
||||
ParameterType.String,
|
||||
].includes(item.sub_parameters[0].type)
|
||||
) {
|
||||
arrTemp = item.sub_parameters.filter((subItem: any) => subItem.value);
|
||||
}
|
||||
if (isEmpty(arrTemp)) {
|
||||
break;
|
||||
}
|
||||
acc[item.name] = arrTemp.map((subItem: any) => {
|
||||
// boolean类型匹配字符串true/false
|
||||
if ([ParameterType.Bool].includes(subItem.type)) {
|
||||
return checkType ? subItem.value === 'true' : subItem.value;
|
||||
}
|
||||
// 数字类型转为number
|
||||
if (
|
||||
[ParameterType.Integer, ParameterType.Number].includes(subItem.type)
|
||||
) {
|
||||
return checkType ? Number(subItem.value) : subItem.value;
|
||||
}
|
||||
// 字符串类型直接返回(进到这里的已经是过滤完空值的数组)
|
||||
if ([ParameterType.String].includes(subItem.type)) {
|
||||
return subItem.value;
|
||||
}
|
||||
// 如果是对象,递归遍历
|
||||
if (subItem.type === ParameterType.Object) {
|
||||
return transformTreeToObj(subItem.sub_parameters, checkType);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// 克隆节点,修改key及清空value
|
||||
export const cloneWithRandomKey = (obj: any) => {
|
||||
// 创建新对象储存值
|
||||
const clone: any = {};
|
||||
|
||||
// 遍历原对象的所有属性
|
||||
for (const prop in obj) {
|
||||
// 如果原对象的这个属性是一个对象,递归调用 cloneWithRandomKey 函数
|
||||
if (obj[prop]?.constructor === Object) {
|
||||
clone[prop] = cloneWithRandomKey(obj[prop]);
|
||||
} else {
|
||||
// 否则,直接复制这个属性
|
||||
clone[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// 如果这个对象有 sub_parameters 属性,需要遍历它
|
||||
if ('sub_parameters' in clone) {
|
||||
clone.sub_parameters = clone.sub_parameters?.map(cloneWithRandomKey);
|
||||
}
|
||||
|
||||
// 生成一个新的随机 key
|
||||
if (clone[ROWKEY]) {
|
||||
clone[ROWKEY] = nanoid();
|
||||
}
|
||||
if (clone.value) {
|
||||
clone.value = null;
|
||||
}
|
||||
|
||||
// 返回克隆的对象
|
||||
return clone;
|
||||
};
|
||||
// 判断参数是否显示删除按钮 先判断是否是根节点,根节点允许删除
|
||||
export const handleIsShowDelete = (
|
||||
data: any,
|
||||
targetKey: string | undefined,
|
||||
) => {
|
||||
const rootIds = data.map((d: any) => d[ROWKEY]);
|
||||
if (rootIds.includes(targetKey)) {
|
||||
return true;
|
||||
}
|
||||
return isShowDelete(data, targetKey);
|
||||
};
|
||||
|
||||
// 检查是否存在相同名称
|
||||
export const checkSameName = (
|
||||
data: Array<APIParameter>,
|
||||
targetKey: string,
|
||||
val: string,
|
||||
): boolean | undefined => {
|
||||
for (const item of data) {
|
||||
if (item[ROWKEY] === targetKey) {
|
||||
const items = data.filter(
|
||||
(dataItem: APIParameter) => dataItem.name === val,
|
||||
);
|
||||
return items.length > 1;
|
||||
} else if (
|
||||
Array.isArray(item[childrenRecordName]) &&
|
||||
item[childrenRecordName].length > 0
|
||||
) {
|
||||
if (checkSameName(item[childrenRecordName], targetKey, val)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 检查是否有array类型(用来判断response是否需要操作列)
|
||||
export const checkHasArray = (data: unknown) => {
|
||||
if (!Array.isArray(data)) {
|
||||
return false;
|
||||
}
|
||||
for (const item of data) {
|
||||
if (item.type === ParameterType.Array) {
|
||||
return true;
|
||||
} else if (
|
||||
Array.isArray(item[childrenRecordName]) &&
|
||||
item[childrenRecordName].length > 0
|
||||
) {
|
||||
// 调整 循环退出时机
|
||||
if (checkHasArray(item[childrenRecordName])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 判断参数是否显示删除按钮(object类型最后一个不允许删除)
|
||||
export const isShowDelete = (data: any, targetKey: string | undefined) => {
|
||||
for (const item of data) {
|
||||
if (item[ROWKEY] === targetKey) {
|
||||
return data.length > 1;
|
||||
} else if (
|
||||
Array.isArray(item[childrenRecordName]) &&
|
||||
item[childrenRecordName].length > 0
|
||||
) {
|
||||
if (isShowDelete(item[childrenRecordName], targetKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sleep = (time: number) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(0);
|
||||
}, time);
|
||||
});
|
||||
|
||||
// 该方法兼容chrome、Arch、Safari浏览器及iPad,增加兼容firefox
|
||||
export const scrollToErrorElement = (className: string) => {
|
||||
const errorElement = document.querySelector(className);
|
||||
if (errorElement) {
|
||||
if (typeof (errorElement as any).scrollIntoViewIfNeeded === 'function') {
|
||||
(errorElement as any).scrollIntoViewIfNeeded();
|
||||
} else {
|
||||
// 兼容性处理,如 Firefox
|
||||
errorElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const scrollToBottom = (ele: Element) => {
|
||||
const scrollEle = ele;
|
||||
scrollEle.scrollTo({
|
||||
left: 0,
|
||||
top: scrollEle.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
export const initParamsDefault = (
|
||||
data: Array<APIParameter>,
|
||||
keyDefault: 'global_default' | 'local_default',
|
||||
) => {
|
||||
const result = cloneDeep(data);
|
||||
const init = (obj: APIParameter) => {
|
||||
if (keyDefault === 'local_default' && !has(obj, 'local_default')) {
|
||||
obj[keyDefault] = obj.global_default;
|
||||
}
|
||||
if (!obj[keyDefault]) {
|
||||
obj[keyDefault] = '';
|
||||
}
|
||||
// bot非引用+必填+local默认值为空+不可见,是异常场景,需手动拨正
|
||||
const isUnusual =
|
||||
obj.default_param_source === DefaultParamSource.Input &&
|
||||
keyDefault === 'local_default' &&
|
||||
obj.is_required &&
|
||||
!obj.local_default &&
|
||||
obj.local_disable;
|
||||
|
||||
if (isUnusual) {
|
||||
obj.local_disable = false;
|
||||
}
|
||||
};
|
||||
|
||||
const addDefault = (res: Array<APIParameter>, isArray = false) => {
|
||||
for (let i = 0, len = res.length; i < len; i++) {
|
||||
const obj = res[i];
|
||||
if (isArray) {
|
||||
obj[keyDefault] = undefined;
|
||||
if (obj.sub_parameters && obj.sub_parameters.length > 0) {
|
||||
addDefault(obj.sub_parameters, true);
|
||||
}
|
||||
} else if (obj.type === ParameterType.Array) {
|
||||
init(obj);
|
||||
if (obj.sub_parameters && obj.sub_parameters.length > 0) {
|
||||
addDefault(obj.sub_parameters, true);
|
||||
}
|
||||
} else {
|
||||
if (obj.type === ParameterType.Object) {
|
||||
obj[keyDefault] = undefined;
|
||||
} else {
|
||||
init(obj);
|
||||
}
|
||||
if (obj.sub_parameters && obj.sub_parameters.length > 0) {
|
||||
addDefault(obj.sub_parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
addDefault(result);
|
||||
return result;
|
||||
};
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
export const transformArrayToTree = (array, template: Array<APIParameter>) => {
|
||||
const arrObj = array;
|
||||
const tree: Array<APIParameter> = [];
|
||||
|
||||
if (Array.isArray(arrObj)) {
|
||||
arrObj.forEach(item => {
|
||||
const subTree = createSubTree(item, template[0]);
|
||||
tree.push(subTree);
|
||||
});
|
||||
}
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
const createSubTree = (arrItem: any, tem: any) => {
|
||||
let subTree: APIParameter & { value?: unknown } = {};
|
||||
// 数组
|
||||
if (Array.isArray(arrItem)) {
|
||||
subTree = {
|
||||
...tem,
|
||||
id: nanoid(),
|
||||
sub_parameters: [],
|
||||
};
|
||||
arrItem.forEach(item => {
|
||||
const arrItemSubTree = createSubTree(item, tem.sub_parameters[0]);
|
||||
subTree.sub_parameters?.push(arrItemSubTree);
|
||||
});
|
||||
} else if (isObject(arrItem)) {
|
||||
subTree = {
|
||||
...tem,
|
||||
id: nanoid(),
|
||||
sub_parameters: [],
|
||||
};
|
||||
let childTree: APIParameter & { value?: unknown } = {};
|
||||
Object.keys(arrItem).map(key => {
|
||||
if (Object.prototype.hasOwnProperty.call(arrItem, key)) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const value = arrItem[key];
|
||||
|
||||
if (Array.isArray(value) || typeof value === 'object') {
|
||||
const nestedSubTree = createSubTree(
|
||||
value,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tem.sub_parameters.find(item => item.name === key),
|
||||
);
|
||||
subTree.sub_parameters?.push(nestedSubTree);
|
||||
} else {
|
||||
childTree = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
...tem.sub_parameters.find(item => item.name === key),
|
||||
id: nanoid(),
|
||||
sub_parameters: [],
|
||||
};
|
||||
childTree.value = String(value);
|
||||
subTree.sub_parameters?.push(childTree);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
subTree = {
|
||||
...tem,
|
||||
id: nanoid(),
|
||||
sub_parameters: [],
|
||||
};
|
||||
subTree.value = String(arrItem);
|
||||
}
|
||||
|
||||
return subTree;
|
||||
};
|
||||
|
||||
export const transformParamsToTree = (params: Array<APIParameter>) => {
|
||||
const result = cloneDeep(params);
|
||||
for (let i = 0, len = result.length; i < len; i++) {
|
||||
if (result[i].type === ParameterType.Array) {
|
||||
const arr = JSON.parse(result[i].global_default || '[]');
|
||||
if (arr.length > 0) {
|
||||
const tree = transformArrayToTree(arr, result[i].sub_parameters || []);
|
||||
result[i].sub_parameters = tree;
|
||||
}
|
||||
} else {
|
||||
// 对象嵌数组有问题 被覆盖了 需要重置
|
||||
result[i].sub_parameters = transformParamsToTree(
|
||||
result[i].sub_parameters || [],
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// data 额外加工 / 如果本身没有 global_default === undefined 就设置 global_disable 也是 undefined,最后将所有的 global_default 设置成 undefined
|
||||
export const doRemoveDefaultFromResponseParams = (
|
||||
data: APIParameter[],
|
||||
hasRequired = false,
|
||||
) => {
|
||||
if (!data.length) {
|
||||
return [];
|
||||
}
|
||||
const returnData = cloneDeep(data);
|
||||
|
||||
for (let i = 0, len = returnData.length; i < len; i++) {
|
||||
const current = returnData[i];
|
||||
|
||||
if (current.global_default === undefined) {
|
||||
current.global_disable = undefined;
|
||||
}
|
||||
|
||||
current.global_default = undefined;
|
||||
if (!hasRequired) {
|
||||
current.is_required = undefined;
|
||||
}
|
||||
current.sub_parameters = doRemoveDefaultFromResponseParams(
|
||||
current.sub_parameters ?? [],
|
||||
hasRequired,
|
||||
);
|
||||
}
|
||||
|
||||
return returnData;
|
||||
};
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
export const doValidParams = (
|
||||
params: APIParameter[],
|
||||
targetKey: keyof APIParameter,
|
||||
) => {
|
||||
if (!params?.length || !targetKey) {
|
||||
return !!0;
|
||||
}
|
||||
|
||||
for (let i = 0, j = params.length; i < j; i++) {
|
||||
const target = params[i];
|
||||
|
||||
if (!target[targetKey]) {
|
||||
return !!0;
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (target.sub_parameters.length > 0) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const sub = doValidParams(target.sub_parameters, targetKey);
|
||||
|
||||
if (sub === !!0) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !0;
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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 };
|
||||
19
frontend/packages/agent-ide/bot-plugin/tools/src/index.tsx
Normal file
19
frontend/packages/agent-ide/bot-plugin/tools/src/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// export { useViewExample } './hooks/example/use-view-example.tsx';
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
export { useEditExample } from './hooks/example/use-edit-example.tsx';
|
||||
17
frontend/packages/agent-ide/bot-plugin/tools/src/typings.d.ts
vendored
Normal file
17
frontend/packages/agent-ide/bot-plugin/tools/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
Reference in New Issue
Block a user