feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 TagColor } from '@coze-arch/bot-semi/Tag';
|
||||
import { PluginType } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
export const PLUGIN_TYPE_MAP = new Map<
|
||||
PluginType,
|
||||
{ label: string; color: TagColor }
|
||||
>([
|
||||
[PluginType.APP, { label: I18n.t('plugin_type_app'), color: 'yellow' }],
|
||||
[PluginType.PLUGIN, { label: I18n.t('plugin_type_plugin'), color: 'blue' }],
|
||||
[PluginType.FUNC, { label: I18n.t('plugin_type_func'), color: 'blue' }],
|
||||
[
|
||||
PluginType.WORKFLOW,
|
||||
{ label: I18n.t('plugin_type_workflow'), color: 'blue' },
|
||||
],
|
||||
]);
|
||||
|
||||
export const PLUGIN_PUBLISH_MAP = new Map<
|
||||
boolean,
|
||||
{ label: string; color: string }
|
||||
>([
|
||||
[
|
||||
false,
|
||||
{
|
||||
label: I18n.t('Unpublished_1'),
|
||||
color: 'var(--coz-fg-secondary)',
|
||||
},
|
||||
],
|
||||
[true, { label: I18n.t('Published_1'), color: 'var(--coz-fg-hglt-green)' }],
|
||||
]);
|
||||
@@ -0,0 +1,115 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.tool-wrap {
|
||||
min-height: 100%;
|
||||
padding-bottom: 20px;
|
||||
background-color: #f7f7fa;
|
||||
|
||||
:global {
|
||||
.semi-table-row-head {
|
||||
font-size: 12px;
|
||||
color: var(
|
||||
--light-usage-text-color-text-1,
|
||||
rgb(28 29 35 / 80%)
|
||||
) !important;
|
||||
}
|
||||
|
||||
.semi-steps-item-title-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.semi-table-row.semi-table-row-expanded:last-child {
|
||||
.semi-table-row-cell {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 24px 12px;
|
||||
border-bottom: 1px solid rgb(29 28 35 / 8%);
|
||||
|
||||
.simple-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 56px;
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
|
||||
margin-left: 12px;
|
||||
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 56px;
|
||||
color: #1d1c23;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.simple-title .title {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 36px 0 30px;
|
||||
}
|
||||
|
||||
.modal-steps {
|
||||
width: 1008px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-wrap {
|
||||
margin: 42px auto 0;
|
||||
|
||||
:global {
|
||||
.semi-form-vertical .semi-form-field:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-footer {
|
||||
width: calc(100% - 200px);
|
||||
min-width: 1008px;
|
||||
margin: 0 auto;
|
||||
text-align: right;
|
||||
|
||||
&.step-one {
|
||||
max-width: 1008px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
padding: 8px 24px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
color: #f93920;
|
||||
text-align: left;
|
||||
|
||||
.link {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: #4d53e8;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
padding-top: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 { STARTNODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/config';
|
||||
import { PluginDocs } from '@coze-agent-ide/bot-plugin-export/pluginDocs';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
export const SecurityCheckFailed = ({ step }) => (
|
||||
<div className={s['error-msg']}>
|
||||
{step !== STARTNODE
|
||||
? I18n.t('plugin_parameter_create_modal_safe_error')
|
||||
: I18n.t('plugin_tool_create_modal_safe_error')}
|
||||
<PluginDocs />
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
.editor-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.editor {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
background-color: white;
|
||||
border: 1px solid #1D1C231F;
|
||||
border-radius: 8px;
|
||||
|
||||
|
||||
:global {
|
||||
.monaco-editor .scroll-decoration {
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
|
||||
import copy from 'copy-to-clipboard';
|
||||
import classNames from 'classnames';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import {
|
||||
UIButton,
|
||||
UIModal,
|
||||
Space,
|
||||
UIToast,
|
||||
RadioGroup,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { Editor as MonacoEditor } from '@coze-arch/bot-monaco-editor';
|
||||
import { ProgramLang } from '@coze-arch/bot-api/plugin_develop';
|
||||
import {
|
||||
SpaceType,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { getEnv } from '../../util';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const LANG_OPTIONS = [
|
||||
{ label: 'cURL', value: ProgramLang.Curl },
|
||||
{ label: 'Wget', value: ProgramLang.Wget },
|
||||
{ label: 'Node.js', value: ProgramLang.NodeJS },
|
||||
{ label: 'Python', value: ProgramLang.Python },
|
||||
{ label: 'Golang', value: ProgramLang.Golang },
|
||||
];
|
||||
|
||||
function getReportLang(lang: ProgramLang) {
|
||||
switch (lang) {
|
||||
case ProgramLang.Curl:
|
||||
return 'curl';
|
||||
case ProgramLang.Wget:
|
||||
return 'wget';
|
||||
case ProgramLang.NodeJS:
|
||||
return 'javascript';
|
||||
case ProgramLang.Python:
|
||||
return 'python';
|
||||
case ProgramLang.Golang:
|
||||
return 'golang';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
function getEditorMode(lang: ProgramLang) {
|
||||
switch (lang) {
|
||||
case ProgramLang.Curl:
|
||||
return 'javascript';
|
||||
case ProgramLang.Wget:
|
||||
return 'javascript';
|
||||
case ProgramLang.NodeJS:
|
||||
return 'javascript';
|
||||
case ProgramLang.Python:
|
||||
return 'python';
|
||||
case ProgramLang.Golang:
|
||||
return 'go';
|
||||
default:
|
||||
return 'javascript';
|
||||
}
|
||||
}
|
||||
|
||||
interface CodeSnippetModalProps {
|
||||
visible: boolean;
|
||||
onCancel?: () => void;
|
||||
pluginAPIInfo?: PluginAPIInfo;
|
||||
}
|
||||
|
||||
export const CodeSnippetModal: React.FC<CodeSnippetModalProps> = props => {
|
||||
const { onCancel, visible, pluginAPIInfo } = props;
|
||||
const [lang, setLang] = useState<ProgramLang>(ProgramLang.Curl);
|
||||
const [content, setContent] = useState<string>('');
|
||||
|
||||
const { id: spaceId, space_type } = useSpaceStore(s => s.space);
|
||||
|
||||
const isPersonal = space_type === SpaceType.Personal;
|
||||
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
|
||||
useEffect(() => {
|
||||
setLang(ProgramLang.Curl);
|
||||
setContent('');
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pluginAPIInfo) {
|
||||
const fetchCode = async () => {
|
||||
setContent('');
|
||||
const res = await PluginDevelopApi.PluginAPI2Code({
|
||||
plugin_id: pluginAPIInfo.plugin_id || '',
|
||||
api_id: pluginAPIInfo.api_id || '',
|
||||
space_id: spaceId || '',
|
||||
dev_id: userInfo?.user_id_str || '',
|
||||
program_lang: lang,
|
||||
});
|
||||
setContent(res?.program_code || '');
|
||||
};
|
||||
fetchCode();
|
||||
}
|
||||
}, [lang, pluginAPIInfo]);
|
||||
|
||||
const handleCopy = () => {
|
||||
const resp = copy(content);
|
||||
const basicParams = {
|
||||
environment: getEnv(),
|
||||
workspace_id: spaceId || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
tool_id: pluginAPIInfo?.api_id || '',
|
||||
code_type: getReportLang(lang) || '',
|
||||
status: 1,
|
||||
};
|
||||
if (resp) {
|
||||
UIToast.success({ content: I18n.t('copy_success') });
|
||||
sendTeaEvent(EVENT_NAMES.code_snippet_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
});
|
||||
} else {
|
||||
UIToast.warning({ content: I18n.t('copy_failed') });
|
||||
sendTeaEvent(EVENT_NAMES.code_snippet_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
error_message: 'copy_failed',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<UIModal
|
||||
type="base-composition"
|
||||
title={I18n.t('code_snippet')}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
footer={
|
||||
<Space>
|
||||
<UIButton theme="solid" type="primary" onClick={handleCopy}>
|
||||
{I18n.t('copy')}
|
||||
</UIButton>
|
||||
</Space>
|
||||
}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className="h-[100%] flex flex-col min-h-0">
|
||||
<div>
|
||||
<RadioGroup
|
||||
type="card"
|
||||
options={LANG_OPTIONS}
|
||||
defaultValue={lang}
|
||||
className={'mb-[16px]'}
|
||||
value={lang}
|
||||
onChange={e => setLang(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles['editor-container'], 'flex-1 min-h-0')}
|
||||
>
|
||||
<MonacoEditor
|
||||
className={styles.editor}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
}}
|
||||
language={getEditorMode(lang)}
|
||||
theme={'tomorrow'}
|
||||
width="100%"
|
||||
value={content}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</UIModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 {
|
||||
CreateCodePluginModal,
|
||||
CreateFormPluginModal,
|
||||
} from '@coze-agent-ide/bot-plugin-export/botEdit';
|
||||
export { ImportPluginModal } from '@coze-agent-ide/bot-plugin-export/fileImport';
|
||||
export { CodeSnippetModal } from './code-snippet';
|
||||
export { PluginDocs } from '@coze-agent-ide/bot-plugin-export/pluginDocs';
|
||||
export { Editor } from '@coze-agent-ide/bot-plugin-export/editor';
|
||||
export { usePluginApisModal } from './plugin-apis/use-plugin-apis-modal';
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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-len */
|
||||
import { type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozArrowRight,
|
||||
IconCozCheckMarkFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Space, Typography } from '@coze-arch/coze-design';
|
||||
import { Modal } from '@coze-arch/bot-semi';
|
||||
|
||||
import { useAuthForApiTool } from '@/hooks/auth/use-auth-for-api-tool';
|
||||
|
||||
interface IOauthProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const doConfirmOAuth = (doOauth: () => void) => {
|
||||
Modal.info({
|
||||
title: I18n.t('plugin_tool_config_auth_modal_auth_required'),
|
||||
content: I18n.t('plugin_tool_config_auth_modal_auth_required_desc'),
|
||||
onOk: doOauth,
|
||||
okText: I18n.t('Confirm'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
});
|
||||
};
|
||||
|
||||
const doConfirmCancelOauth = (doCancelOauth: () => void) => {
|
||||
Modal.warning({
|
||||
title: I18n.t('plugin_tool_config_auth_modal_cancel_confirmation'),
|
||||
content: I18n.t('plugin_tool_config_auth_modal_cancel_confirmation_desc'),
|
||||
onOk: doCancelOauth,
|
||||
okText: I18n.t('Confirm'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
});
|
||||
};
|
||||
|
||||
const OauthHeaderAction = () => {
|
||||
const {
|
||||
needAuth,
|
||||
isHasAuth,
|
||||
doCancelOauth,
|
||||
isUpdateLoading,
|
||||
doOauth,
|
||||
canEdit,
|
||||
} = useAuthForApiTool();
|
||||
|
||||
if (!canEdit) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const isEnableCancelAuthorization = needAuth && isHasAuth;
|
||||
const isEnableAuthorization = needAuth && !isHasAuth;
|
||||
|
||||
return (
|
||||
<Space spacing={8}>
|
||||
{needAuth ? (
|
||||
<span className="rounded-[4px] bg-[#EDD5FC] px-[8px] py-[2px] text-[#6C2CC6] text-[12px] font-medium leading-[16px]">
|
||||
{I18n.t('plugin_mark_created_by_existing_services')}
|
||||
</span>
|
||||
) : null}
|
||||
{needAuth ? (
|
||||
<Typography.Text
|
||||
disabled={isUpdateLoading}
|
||||
onClick={() => {
|
||||
if (isEnableAuthorization) {
|
||||
doConfirmOAuth(doOauth);
|
||||
return;
|
||||
}
|
||||
if (isEnableCancelAuthorization) {
|
||||
doConfirmCancelOauth(doCancelOauth);
|
||||
}
|
||||
}}
|
||||
icon={isHasAuth ? <IconCozCheckMarkFill /> : undefined}
|
||||
className={classNames(
|
||||
'overflow-hidden text-[#4C54F0] overflow-ellipsis text-[14px] font-normal leading-[20px]',
|
||||
(isEnableAuthorization || isEnableCancelAuthorization) &&
|
||||
'cursor-pointer',
|
||||
)}
|
||||
>
|
||||
{isHasAuth
|
||||
? I18n.t('plugin_tool_config_status_authorized')
|
||||
: I18n.t('plugin_tool_config_status_unauthorized')}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{!isHasAuth && needAuth ? (
|
||||
<IconCozArrowRight className="w-[12px] h-[12px] ml-[-6px]" />
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
const OauthButtonAction: FC<IOauthProps> = ({ className }) => {
|
||||
const {
|
||||
needAuth,
|
||||
isHasAuth,
|
||||
doCancelOauth,
|
||||
isUpdateLoading,
|
||||
doOauth,
|
||||
canEdit,
|
||||
} = useAuthForApiTool();
|
||||
|
||||
if (!canEdit) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return needAuth ? (
|
||||
<Typography.Text
|
||||
disabled={isUpdateLoading}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('click');
|
||||
if (isHasAuth) {
|
||||
doConfirmCancelOauth(doCancelOauth);
|
||||
} else {
|
||||
doConfirmOAuth(doOauth);
|
||||
}
|
||||
}}
|
||||
icon={isHasAuth ? <IconCozCheckMarkFill /> : undefined}
|
||||
className={`overflow-hidden text-[#4C54F0] overflow-ellipsis text-[14px] font-normal leading-[20px] cursor-pointer px-[12px] py-[0] items-center ${className}`}
|
||||
>
|
||||
{isHasAuth
|
||||
? I18n.t('plugin_tool_config_status_authorized')
|
||||
: I18n.t('plugin_tool_config_status_unauthorized')}
|
||||
</Typography.Text>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export { OauthHeaderAction, OauthButtonAction };
|
||||
@@ -0,0 +1,346 @@
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
@import '@coze-common/assets/style/common.less';
|
||||
@import '@coze-common/assets/style/mixins.less';
|
||||
|
||||
.tools-content {
|
||||
.tools-table-thead {
|
||||
padding-bottom: 6px;
|
||||
|
||||
th {
|
||||
padding: var(--spacing-spacing-button-default-padding-top, 6px) 0 var(--spacing-tight, 8px) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-table {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px; // 133.333%
|
||||
color: var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%));
|
||||
word-wrap: break-word;
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px; // 133.333%
|
||||
color: var(--light-color-grey-grey-4,
|
||||
var(--light-color-grey-grey-4, #888d92));
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.auto-add-api {
|
||||
animation: flash 6s linear;
|
||||
animation-delay: 300ms;
|
||||
|
||||
td:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
10% {
|
||||
background: rgb(180 186 246 / 24%);
|
||||
}
|
||||
|
||||
20% {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
30% {
|
||||
background: rgb(180 186 246 / 24%);
|
||||
}
|
||||
|
||||
40% {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: rgb(180 186 246 / 24%);
|
||||
}
|
||||
|
||||
95% {
|
||||
background: rgb(180 186 246 / 24%);
|
||||
}
|
||||
}
|
||||
|
||||
.api-row {
|
||||
border-bottom: 1px solid transparent;
|
||||
|
||||
&.border-top {
|
||||
position: relative;
|
||||
|
||||
td {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// &:last-child {
|
||||
// border-bottom: 1px solid #f9f9f9;
|
||||
// }
|
||||
td {
|
||||
overflow: hidden;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
height: 20px;
|
||||
background: var(--light-color-white-white, #fff);
|
||||
border-radius: var(--spacing-spacing-button-default-padding-top, 6px);
|
||||
}
|
||||
|
||||
.semi-tag-content {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&.api-disable {
|
||||
.api-plugin-name,
|
||||
.api-name-text,
|
||||
.icon-disabled,
|
||||
{
|
||||
cursor: default;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-plugin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding-right: 10px;
|
||||
|
||||
&-image {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
|
||||
>img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-image-status {
|
||||
background-color: rgba(#fff, 0) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-method {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
color: var(--light-color-grey-grey-5, rgb(107 107 117 / 100%));
|
||||
}
|
||||
|
||||
.icon-config {
|
||||
cursor: pointer;
|
||||
color: rgb(107 109 117 / 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.api-method-read {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.api-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0;
|
||||
padding-right: 10px;
|
||||
|
||||
&-text {
|
||||
margin-right: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: rgb(29 28 35 / 80%);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
background: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.api-divider {
|
||||
height: 12px;
|
||||
border-color: var(--light-color-brand-brand-2, #b3c4ff);
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
.icon-tips {
|
||||
cursor: pointer;
|
||||
.common-svg-icon(14px, rgba(107, 109, 117, 1));
|
||||
|
||||
padding: 1px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&-publish {
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-modal {
|
||||
height: 100%;
|
||||
|
||||
.plugin-filter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 218px;
|
||||
|
||||
background: #ebedf0;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-modal-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-spin-children {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-collapse {
|
||||
:global {
|
||||
.semi-collapse-header {
|
||||
height: 120px !important;
|
||||
margin: 0 !important;
|
||||
padding: 14px 16px;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-collapse {
|
||||
padding: 16px 0 12px;
|
||||
}
|
||||
|
||||
.semi-collapse-content {
|
||||
// background-color: #fff;
|
||||
padding: 0;
|
||||
border-radius: 0 0 8px 8px;
|
||||
// border-color: var(
|
||||
// --light-usage-border-color-border,
|
||||
// rgba(28, 31, 35, 0.08)
|
||||
// );
|
||||
// border-width: 0 1px 1px 1px;
|
||||
// border-style: solid;
|
||||
}
|
||||
|
||||
.semi-collapse-item {
|
||||
// border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.default-text {
|
||||
.tip-text;
|
||||
}
|
||||
|
||||
.hide-button-model-wrap {
|
||||
.ml20 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.h56 {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
top: 20px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-func-collapse {
|
||||
.plugin-api-desc {
|
||||
cursor: pointer;
|
||||
width: 200px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.icon-button-16 {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-button {
|
||||
&.semi-button-size-small {
|
||||
height: 16px;
|
||||
padding: 1px !important;
|
||||
|
||||
svg {
|
||||
@apply text-foreground-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 能力模块默认说明文案样式
|
||||
.tip-text {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 ComponentProps } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import classNames from 'classnames';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UICompositionModal, type Modal } from '@coze-arch/bot-semi';
|
||||
import { OpenModeType } from '@coze-arch/bot-hooks';
|
||||
import { type PluginModalModeProps } from '@coze-agent-ide/plugin-shared';
|
||||
import { PluginFeatButton } from '@coze-agent-ide/bot-plugin-export/pluginFeatModal/featButton';
|
||||
import { usePluginModalParts } from '@coze-agent-ide/bot-plugin-export/agentSkillPluginModal/hooks';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export type PluginModalProps = ComponentProps<typeof Modal> &
|
||||
PluginModalModeProps & {
|
||||
type: number;
|
||||
};
|
||||
|
||||
export const PluginModal: React.FC<PluginModalProps> = ({
|
||||
type,
|
||||
openMode,
|
||||
from,
|
||||
openModeCallback,
|
||||
showButton,
|
||||
showCopyPlugin,
|
||||
onCopyPluginCallback,
|
||||
pluginApiList,
|
||||
projectId,
|
||||
clickProjectPluginCallback,
|
||||
hideCreateBtn,
|
||||
initQuery,
|
||||
...props
|
||||
}) => {
|
||||
const { pluginApis, updateSkillPluginApis } = useBotSkillStore(
|
||||
useShallow(store => ({
|
||||
pluginApis: store.pluginApis,
|
||||
updateSkillPluginApis: store.updateSkillPluginApis,
|
||||
})),
|
||||
);
|
||||
const getPluginApiList = () => {
|
||||
if (pluginApiList) {
|
||||
return pluginApiList;
|
||||
}
|
||||
return openMode === OpenModeType.OnlyOnceAdd ? [] : pluginApis;
|
||||
};
|
||||
const { sider, filter, content } = usePluginModalParts({
|
||||
// 如果是仅添加一次,清空默认选中
|
||||
pluginApiList: getPluginApiList(),
|
||||
onPluginApiListChange: updateSkillPluginApis,
|
||||
openMode,
|
||||
from,
|
||||
openModeCallback,
|
||||
showButton,
|
||||
showCopyPlugin,
|
||||
onCopyPluginCallback,
|
||||
projectId,
|
||||
clickProjectPluginCallback,
|
||||
onCreateSuccess: props?.onCreateSuccess,
|
||||
isShowStorePlugin: props?.isShowStorePlugin,
|
||||
hideCreateBtn,
|
||||
initQuery,
|
||||
});
|
||||
|
||||
return (
|
||||
<UICompositionModal
|
||||
data-testid="plugin-modal"
|
||||
{...props}
|
||||
header={I18n.t('bot_edit_plugin_select_title')}
|
||||
className={classNames(s['plugin-modal'], props.className)}
|
||||
sider={sider}
|
||||
extra={!IS_OPEN_SOURCE ? <PluginFeatButton /> : null}
|
||||
filter={filter}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { isNumber } from 'lodash-es';
|
||||
import {
|
||||
type PluginModalModeProps,
|
||||
type PluginQuery,
|
||||
} from '@coze-agent-ide/plugin-shared';
|
||||
|
||||
import { PluginModal } from './plugin-modal';
|
||||
|
||||
export const usePluginApisModal = (props?: PluginModalModeProps) => {
|
||||
const { closeCallback, ...restProps } = props || {};
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [type, setType] = useState(1);
|
||||
const [initQuery, setInitQuery] = useState<Partial<PluginQuery>>();
|
||||
const open = (
|
||||
params?: number | { openType?: number; initQuery?: Partial<PluginQuery> },
|
||||
) => {
|
||||
const openType = isNumber(params) ? params : params?.openType;
|
||||
const _initQuery = isNumber(params) ? undefined : params?.initQuery;
|
||||
setVisible(true);
|
||||
setInitQuery(_initQuery);
|
||||
// 0 也有效
|
||||
if (isNumber(openType)) {
|
||||
setType(openType);
|
||||
}
|
||||
};
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
setInitQuery(undefined);
|
||||
closeCallback?.();
|
||||
};
|
||||
const node = visible ? (
|
||||
<PluginModal
|
||||
type={type}
|
||||
visible={visible}
|
||||
onCancel={() => {
|
||||
setVisible(false);
|
||||
closeCallback?.();
|
||||
}}
|
||||
initQuery={initQuery}
|
||||
footer={null}
|
||||
{...restProps}
|
||||
/>
|
||||
) : null;
|
||||
return {
|
||||
node,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
.plugin-detail-info {
|
||||
position: relative;
|
||||
padding-bottom: 12px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -24px;
|
||||
|
||||
width: calc(100% + 48px);
|
||||
height: 1px;
|
||||
|
||||
background-color: rgb(29 28 35 / 8%);
|
||||
}
|
||||
|
||||
.plugin-detail-title {
|
||||
max-width: 300px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.plugin-detail-published {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 60%);
|
||||
|
||||
}
|
||||
|
||||
.plugin-detail-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.plugin-detail-desc {
|
||||
max-width: 300px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 80%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconButton,
|
||||
Tag,
|
||||
Typography,
|
||||
Space,
|
||||
Avatar,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { IconCardSearchOutlined, IconEdit } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
CreationMethod,
|
||||
type GetPluginInfoResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { IconTickCircle, IconClock } from '@douyinfe/semi-icons';
|
||||
|
||||
import { OauthHeaderAction } from '../../components/oauth-action';
|
||||
import { PLUGIN_PUBLISH_MAP } from '../../common';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const PluginHeader = ({
|
||||
pluginInfo,
|
||||
loading,
|
||||
canEdit,
|
||||
extraRight,
|
||||
onClickEdit,
|
||||
}: {
|
||||
pluginInfo: GetPluginInfoResponse;
|
||||
loading: boolean;
|
||||
canEdit: boolean;
|
||||
extraRight?: ReactNode;
|
||||
onClickEdit?: () => void;
|
||||
}) => (
|
||||
<Space
|
||||
className={classNames(
|
||||
s['plugin-detail-info'],
|
||||
'w-full',
|
||||
'px-[16px]',
|
||||
'py-[16px]',
|
||||
'shrink-0',
|
||||
'grow-0',
|
||||
)}
|
||||
spacing={20}
|
||||
>
|
||||
<Space style={{ flex: 1 }} spacing={12}>
|
||||
<Avatar
|
||||
className={classNames(s['plugin-detail-avatar'])}
|
||||
size="medium"
|
||||
shape="square"
|
||||
src={pluginInfo?.meta_info?.icon?.url}
|
||||
/>
|
||||
<div>
|
||||
<div>
|
||||
<Space spacing={4} className="mb-1">
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { style: { wordBreak: 'break-word' } },
|
||||
},
|
||||
}}
|
||||
className={classNames(s['plugin-detail-title'])}
|
||||
>
|
||||
{pluginInfo?.meta_info?.name}
|
||||
</Text>
|
||||
{!loading ? (
|
||||
<IconButton
|
||||
icon={canEdit ? <IconEdit /> : <IconCardSearchOutlined />}
|
||||
size="small"
|
||||
color="secondary"
|
||||
className={classNames(s['edit-plugin-btn'], {
|
||||
[s.edit]: canEdit,
|
||||
})}
|
||||
onClick={onClickEdit}
|
||||
/>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
<Space spacing={4}>
|
||||
<Tag size="mini" color="primary">
|
||||
<Space spacing={2}>
|
||||
{pluginInfo?.published ? (
|
||||
<IconTickCircle
|
||||
size="small"
|
||||
style={{
|
||||
color: PLUGIN_PUBLISH_MAP.get(Boolean(pluginInfo.published))
|
||||
?.color,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IconClock
|
||||
size="small"
|
||||
style={{
|
||||
color: PLUGIN_PUBLISH_MAP.get(
|
||||
Boolean(pluginInfo?.published),
|
||||
)?.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{PLUGIN_PUBLISH_MAP.get(Boolean(pluginInfo?.published))?.label}
|
||||
</Space>
|
||||
</Tag>
|
||||
{pluginInfo?.creation_method === CreationMethod.IDE && (
|
||||
<Tag size="mini" color="purple">
|
||||
{I18n.t('plugin_mark_created_by_ide')}
|
||||
</Tag>
|
||||
)}
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
style: {
|
||||
wordBreak: 'break-word',
|
||||
maxWidth: '560px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
className={classNames(s['plugin-detail-desc'])}
|
||||
>
|
||||
{pluginInfo?.meta_info?.desc}
|
||||
</Text>
|
||||
<OauthHeaderAction />
|
||||
</Space>
|
||||
</div>
|
||||
</Space>
|
||||
{/* filter */}
|
||||
{extraRight ? <Space spacing={12}>{extraRight}</Space> : null}
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default PluginHeader;
|
||||
@@ -0,0 +1,114 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.tool-wrap {
|
||||
min-height: 100%;
|
||||
padding-bottom: 20px;
|
||||
background-color: #f7f7fa;
|
||||
|
||||
:global {
|
||||
.semi-table-row-head {
|
||||
font-size: 12px;
|
||||
color: var(
|
||||
--light-usage-text-color-text-1,
|
||||
rgb(28 29 35 / 80%)
|
||||
) !important;
|
||||
}
|
||||
|
||||
.semi-steps-item-title-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.semi-table-row.semi-table-row-expanded:last-child {
|
||||
.semi-table-row-cell {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgb(29 28 35 / 8%);
|
||||
|
||||
.simple-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
|
||||
margin-left: 12px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: #1d1c23;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.simple-title .title {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 36px 0 30px;
|
||||
}
|
||||
|
||||
.modal-steps {
|
||||
width: 1008px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-wrap {
|
||||
margin: 42px auto 0;
|
||||
|
||||
:global {
|
||||
.semi-form-vertical .semi-form-field:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-footer {
|
||||
width: calc(100% - 200px);
|
||||
min-width: 1008px;
|
||||
margin: 0 auto;
|
||||
text-align: right;
|
||||
|
||||
&.step-one {
|
||||
max-width: 1008px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
padding: 8px 24px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
color: #f93920;
|
||||
text-align: left;
|
||||
|
||||
.link {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: #4d53e8;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
padding-top: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* 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 FC, useEffect, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useUpdateEffect } from 'ahooks';
|
||||
import { usePluginStore } from '@coze-studio/bot-plugin-store';
|
||||
import {
|
||||
REPORT_EVENTS,
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
} from '@coze-arch/report-events';
|
||||
import { useErrorHandler } from '@coze-arch/logger';
|
||||
import { Spin, Collapse } from '@coze-arch/coze-design';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
type APIParameter,
|
||||
type PluginAPIInfo,
|
||||
DebugExampleStatus,
|
||||
type UpdateAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { addDepthAndValue } from '@coze-agent-ide/bot-plugin-tools/pluginModal/utils';
|
||||
import { type RenderEnhancedComponentProps } from '@coze-agent-ide/bot-plugin-tools/pluginModal/types';
|
||||
import { setEditToolExampleValue } from '@coze-agent-ide/bot-plugin-tools/example/utils';
|
||||
|
||||
import { useContentResponse } from './use-content-response';
|
||||
import { useContentRequest } from './use-content-request';
|
||||
import { useContentBaseInfo } from './use-content-baseinfo';
|
||||
import { useContentBaseMore } from './use-content-base-more';
|
||||
import { ToolHeader } from './tool-header';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ToolDetailPageProps
|
||||
extends Partial<RenderEnhancedComponentProps> {
|
||||
toolID: string;
|
||||
onDebugSuccessCallback?: () => void;
|
||||
}
|
||||
|
||||
// 页面-编辑插件API
|
||||
export const ToolDetailPage: FC<ToolDetailPageProps> = ({
|
||||
toolID,
|
||||
onDebugSuccessCallback,
|
||||
renderDescComponent,
|
||||
renderParamsComponent,
|
||||
}) => {
|
||||
//捕获错误信息,跳转统一落地页
|
||||
const capture = useErrorHandler();
|
||||
const [editVersion, setEditVersion] = useState<number>();
|
||||
//插件-API详情
|
||||
const [apiInfo, setApiInfo] = useState<PluginAPIInfo>();
|
||||
const [debugApiInfo, setDebugApiInfo] = useState<PluginAPIInfo>();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
const {
|
||||
canEdit,
|
||||
init,
|
||||
pluginInfo,
|
||||
updatedInfo,
|
||||
wrapWithCheckLock,
|
||||
unlockPlugin,
|
||||
spaceID,
|
||||
pluginID,
|
||||
updatePluginInfoByImmer,
|
||||
version,
|
||||
} = usePluginStore(
|
||||
useShallow(store => ({
|
||||
canEdit: store.canEdit,
|
||||
init: store.init,
|
||||
pluginInfo: store.pluginInfo,
|
||||
updatedInfo: store.updatedInfo,
|
||||
wrapWithCheckLock: store.wrapWithCheckLock,
|
||||
unlockPlugin: store.unlockPlugin,
|
||||
spaceID: store.spaceID,
|
||||
pluginID: store.pluginId,
|
||||
updatePluginInfoByImmer: store.updatePluginInfoByImmer,
|
||||
version: store.version,
|
||||
})),
|
||||
);
|
||||
|
||||
const handleSuccess = (baseResData: UpdateAPIResponse) => {
|
||||
updatePluginInfoByImmer(draft => {
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
draft.edit_version = baseResData?.edit_version;
|
||||
});
|
||||
};
|
||||
|
||||
// 重置 request 参数
|
||||
const resetRequestParams = (data: PluginAPIInfo) => {
|
||||
const requestParams = cloneDeep(data.request_params as APIParameter[]);
|
||||
if (
|
||||
data?.debug_example_status === DebugExampleStatus.Enable &&
|
||||
data?.debug_example?.req_example
|
||||
) {
|
||||
setEditToolExampleValue(
|
||||
requestParams,
|
||||
JSON.parse((data as PluginAPIInfo)?.debug_example?.req_example ?? '{}'),
|
||||
);
|
||||
}
|
||||
addDepthAndValue(requestParams);
|
||||
return requestParams;
|
||||
};
|
||||
|
||||
// 设置接口信息(回显和置空)
|
||||
const handleInit = async (useloading = false) => {
|
||||
setApiInfo({
|
||||
...apiInfo,
|
||||
debug_example_status: DebugExampleStatus.Disable,
|
||||
});
|
||||
useloading && setLoading(true);
|
||||
try {
|
||||
const {
|
||||
api_info = [],
|
||||
msg,
|
||||
edit_version,
|
||||
} = await PluginDevelopApi.GetPluginAPIs({
|
||||
plugin_id: pluginID,
|
||||
api_ids: [toolID],
|
||||
preview_version_ts: version,
|
||||
});
|
||||
|
||||
if (api_info.length > 0) {
|
||||
const apiInfoTemp = api_info.length > 0 ? api_info[0] : {};
|
||||
// debug 的数据 如果有 example 需要回显 入参数据额外处理
|
||||
setDebugApiInfo({
|
||||
...apiInfoTemp,
|
||||
request_params: resetRequestParams(apiInfoTemp),
|
||||
});
|
||||
// 给对象增加层级标识
|
||||
addDepthAndValue(apiInfoTemp.request_params);
|
||||
addDepthAndValue(apiInfoTemp.response_params);
|
||||
setApiInfo(apiInfoTemp);
|
||||
setEditVersion(edit_version);
|
||||
} else {
|
||||
capture(
|
||||
new CustomError(
|
||||
ReportEventNames.responseValidation,
|
||||
msg || 'GetPluginAPIs error',
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
capture(
|
||||
new CustomError(
|
||||
REPORT_EVENTS.PluginInitError,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
`plugin init error: ${error.message}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
useloading && setLoading(false);
|
||||
};
|
||||
|
||||
// 1.基本信息
|
||||
const {
|
||||
isBaseInfoDisabled,
|
||||
header: baseInfoHeader,
|
||||
itemKey: baseInfoItemKey,
|
||||
extra: baseInfoExtra,
|
||||
content: baseInfoContent,
|
||||
classNameWrap: baseInfoClassNameWrap,
|
||||
} = useContentBaseInfo({
|
||||
space_id: spaceID,
|
||||
plugin_id: pluginID,
|
||||
tool_id: toolID,
|
||||
apiInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
editVersion,
|
||||
renderDescComponent,
|
||||
});
|
||||
|
||||
// 2 更多设置
|
||||
const {
|
||||
isBaseMoreDisabled,
|
||||
header: baseMoreHeader,
|
||||
itemKey: baseMoreItemKey,
|
||||
extra: baseMoreExtra,
|
||||
content: baseMoreContent,
|
||||
classNameWrap: baseMoreClassNameWrap,
|
||||
} = useContentBaseMore({
|
||||
plugin_id: pluginID,
|
||||
pluginInfo,
|
||||
tool_id: toolID,
|
||||
apiInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
editVersion,
|
||||
space_id: spaceID,
|
||||
onSuccess: handleSuccess,
|
||||
});
|
||||
|
||||
// 3.设置 request
|
||||
const {
|
||||
isRequestParamsDisabled,
|
||||
itemKey: requestItemKey,
|
||||
header: requestHeader,
|
||||
extra: requestExtra,
|
||||
content: requestContent,
|
||||
classNameWrap: requestClassNameWrap,
|
||||
} = useContentRequest({
|
||||
apiInfo,
|
||||
plugin_id: pluginID,
|
||||
tool_id: toolID,
|
||||
pluginInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
editVersion,
|
||||
spaceID,
|
||||
onSuccess: handleSuccess,
|
||||
renderParamsComponent,
|
||||
});
|
||||
|
||||
// 4.设置 response
|
||||
const {
|
||||
isResponseParamsDisabled,
|
||||
itemKey: responseItemKey,
|
||||
header: responseHeader,
|
||||
extra: responseExtra,
|
||||
content: responseContent,
|
||||
classNameWrap: responseClassNameWrap,
|
||||
} = useContentResponse({
|
||||
apiInfo,
|
||||
plugin_id: pluginID,
|
||||
tool_id: toolID,
|
||||
editVersion,
|
||||
pluginInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
debugApiInfo,
|
||||
setDebugApiInfo,
|
||||
spaceID,
|
||||
onSuccess: handleSuccess,
|
||||
renderParamsComponent,
|
||||
});
|
||||
|
||||
const collapseItems = [
|
||||
{
|
||||
header: baseInfoHeader,
|
||||
itemKey: baseInfoItemKey,
|
||||
extra: baseInfoExtra,
|
||||
content: baseInfoContent,
|
||||
classNameWrap: baseInfoClassNameWrap,
|
||||
},
|
||||
{
|
||||
header: baseMoreHeader,
|
||||
itemKey: baseMoreItemKey,
|
||||
extra: baseMoreExtra,
|
||||
content: baseMoreContent,
|
||||
classNameWrap: baseMoreClassNameWrap,
|
||||
},
|
||||
{
|
||||
header: requestHeader,
|
||||
itemKey: requestItemKey,
|
||||
extra: requestExtra,
|
||||
content: requestContent,
|
||||
classNameWrap: requestClassNameWrap,
|
||||
},
|
||||
{
|
||||
header: responseHeader,
|
||||
itemKey: responseItemKey,
|
||||
extra: responseExtra,
|
||||
content: responseContent,
|
||||
classNameWrap: responseClassNameWrap,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await init();
|
||||
handleInit(true);
|
||||
})();
|
||||
return () => {
|
||||
unlockPlugin();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 预览状态解锁,如果有一步为编辑态,则不解锁
|
||||
useUpdateEffect(() => {
|
||||
if (
|
||||
!isBaseInfoDisabled ||
|
||||
!isRequestParamsDisabled ||
|
||||
!isResponseParamsDisabled ||
|
||||
!isBaseMoreDisabled
|
||||
) {
|
||||
return;
|
||||
}
|
||||
unlockPlugin();
|
||||
}, [
|
||||
isBaseInfoDisabled,
|
||||
isRequestParamsDisabled,
|
||||
isResponseParamsDisabled,
|
||||
isBaseMoreDisabled,
|
||||
]);
|
||||
|
||||
return !loading ? (
|
||||
<div className={s.toolWrap}>
|
||||
<ToolHeader
|
||||
space_id={spaceID}
|
||||
plugin_id={pluginID}
|
||||
unlockPlugin={unlockPlugin}
|
||||
tool_id={toolID}
|
||||
pluginInfo={pluginInfo}
|
||||
updatedInfo={updatedInfo}
|
||||
apiInfo={apiInfo}
|
||||
editVersion={editVersion || 0}
|
||||
canEdit={canEdit}
|
||||
debugApiInfo={debugApiInfo}
|
||||
onDebugSuccessCallback={onDebugSuccessCallback}
|
||||
/>
|
||||
<Collapse
|
||||
keepDOM={true}
|
||||
defaultActiveKey={collapseItems.map(item => item.itemKey)}
|
||||
>
|
||||
{collapseItems.map((item, index) => (
|
||||
<Collapse.Panel
|
||||
className={item.classNameWrap}
|
||||
header={item.header}
|
||||
itemKey={item.itemKey}
|
||||
extra={item.extra}
|
||||
key={`${index}collapse`}
|
||||
>
|
||||
{item.content}
|
||||
</Collapse.Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
</div>
|
||||
) : (
|
||||
<Spin size="large" spinning style={{ height: '100%', width: '100%' }} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 { useMemo, type FC } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import {
|
||||
type GetPluginInfoResponse,
|
||||
type GetUpdatedAPIsResponse,
|
||||
type PluginAPIInfo,
|
||||
PluginType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { IconChevronLeft } from '@douyinfe/semi-icons';
|
||||
import { usePluginNavigate } from '@coze-studio/bot-plugin-store';
|
||||
import { Button, IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { OauthButtonAction } from '@/components/oauth-action';
|
||||
|
||||
import { useContentDebug } from './use-content-debug';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ToolHeaderProps {
|
||||
space_id: string;
|
||||
plugin_id: string;
|
||||
unlockPlugin: () => void;
|
||||
tool_id: string;
|
||||
pluginInfo?: GetPluginInfoResponse & { plugin_id?: string };
|
||||
updatedInfo?: GetUpdatedAPIsResponse;
|
||||
apiInfo?: PluginAPIInfo;
|
||||
editVersion: number;
|
||||
canEdit: boolean;
|
||||
debugApiInfo?: PluginAPIInfo;
|
||||
onDebugSuccessCallback?: () => void;
|
||||
}
|
||||
|
||||
const ToolHeader: FC<ToolHeaderProps> = ({
|
||||
space_id,
|
||||
plugin_id,
|
||||
unlockPlugin,
|
||||
tool_id,
|
||||
pluginInfo,
|
||||
updatedInfo,
|
||||
apiInfo,
|
||||
editVersion,
|
||||
canEdit,
|
||||
debugApiInfo,
|
||||
onDebugSuccessCallback,
|
||||
}) => {
|
||||
const resourceNavigate = usePluginNavigate();
|
||||
|
||||
const [FLAGS] = useFlags();
|
||||
const goBack = () => {
|
||||
resourceNavigate.toResource?.('plugin', plugin_id);
|
||||
unlockPlugin();
|
||||
};
|
||||
|
||||
// 管理模拟集
|
||||
const handleManageMockset = () => {
|
||||
resourceNavigate.mocksetList?.(tool_id);
|
||||
};
|
||||
|
||||
const mocksetDisabled = useMemo(
|
||||
() =>
|
||||
pluginInfo?.plugin_type === PluginType.LOCAL ||
|
||||
!pluginInfo?.published ||
|
||||
(pluginInfo?.status &&
|
||||
updatedInfo?.created_api_names &&
|
||||
Boolean(updatedInfo.created_api_names.includes(apiInfo?.name || ''))),
|
||||
[pluginInfo, updatedInfo, apiInfo],
|
||||
);
|
||||
|
||||
const { modalContent: debugModalContent } = useContentDebug({
|
||||
debugApiInfo,
|
||||
canEdit,
|
||||
space_id: space_id || '',
|
||||
plugin_id: plugin_id || '',
|
||||
tool_id: tool_id || '',
|
||||
unlockPlugin,
|
||||
editVersion,
|
||||
pluginInfo,
|
||||
onDebugSuccessCallback,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={s.header}>
|
||||
<div className={s['simple-title']}>
|
||||
{/* <UIBreadcrumb
|
||||
showTooltip={{
|
||||
width: '160px',
|
||||
opts: {
|
||||
style: { wordBreak: 'break-word' },
|
||||
},
|
||||
}}
|
||||
pluginInfo={pluginInfo?.meta_info}
|
||||
pluginToolInfo={apiInfo}
|
||||
compact={false}
|
||||
className={s.breadcrumb}
|
||||
/> */}
|
||||
<IconButton
|
||||
icon={<IconChevronLeft style={{ color: 'rgba(29, 28, 35, 0.6)' }} />}
|
||||
onClick={goBack}
|
||||
size="small"
|
||||
color="secondary"
|
||||
/>
|
||||
<span className={s.title}>{I18n.t('plugin_edit_tool_title')}</span>
|
||||
<OauthButtonAction />
|
||||
{/* 社区版暂不支持该功能 */}
|
||||
{FLAGS['bot.devops.plugin_mockset'] ? (
|
||||
<Tooltip
|
||||
style={{ display: mocksetDisabled ? 'block' : 'none' }}
|
||||
content={I18n.t('unreleased_plugins_tool_cannot_create_mockset')}
|
||||
position="left"
|
||||
trigger="hover"
|
||||
>
|
||||
<Button
|
||||
onClick={handleManageMockset}
|
||||
disabled={mocksetDisabled}
|
||||
color="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
{I18n.t('manage_mockset')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{canEdit ? debugModalContent : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ToolHeader };
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconEdit } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
type UpdateAPIResponse,
|
||||
type GetPluginInfoResponse,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { useBaseMore } from '@coze-agent-ide/bot-plugin-tools/useBaseMore';
|
||||
import { REQUESTNODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/config';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { SecurityCheckFailed } from '@/components/check_failed';
|
||||
|
||||
interface UseContentBaseInfoProps {
|
||||
plugin_id: string;
|
||||
pluginInfo?: GetPluginInfoResponse & { plugin_id?: string };
|
||||
tool_id: string;
|
||||
apiInfo?: PluginAPIInfo;
|
||||
space_id: string;
|
||||
canEdit: boolean;
|
||||
handleInit: (loading?: boolean) => Promise<void>;
|
||||
wrapWithCheckLock: (fn: () => void) => () => Promise<void>;
|
||||
editVersion?: number;
|
||||
onSuccess?: (params: UpdateAPIResponse) => void;
|
||||
}
|
||||
|
||||
export const useContentBaseMore = ({
|
||||
plugin_id,
|
||||
pluginInfo,
|
||||
tool_id,
|
||||
apiInfo,
|
||||
space_id,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
editVersion,
|
||||
onSuccess,
|
||||
}: UseContentBaseInfoProps) => {
|
||||
// 是否显示安全检查失败信息
|
||||
const [showSecurityCheckFailedMsg, setShowSecurityCheckFailedMsg] =
|
||||
useState(false);
|
||||
const [isBaseMoreDisabled, setIsBaseMoreDisabled] = useState(true);
|
||||
|
||||
// 基本信息
|
||||
const { baseInfoNode, submitBaseInfo } = useBaseMore({
|
||||
pluginId: plugin_id || '',
|
||||
pluginMeta: pluginInfo?.meta_info || {},
|
||||
apiId: tool_id,
|
||||
baseInfo: apiInfo,
|
||||
showModal: false,
|
||||
disabled: isBaseMoreDisabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
pluginType: pluginInfo?.plugin_type,
|
||||
spaceId: space_id,
|
||||
onSuccess,
|
||||
});
|
||||
|
||||
return {
|
||||
isBaseMoreDisabled,
|
||||
header: I18n.t('project_plugin_setup_metadata_more_info'),
|
||||
itemKey: 'baseMore',
|
||||
extra: (
|
||||
<>
|
||||
{showSecurityCheckFailedMsg ? (
|
||||
<SecurityCheckFailed step={REQUESTNODE} />
|
||||
) : null}
|
||||
{!isBaseMoreDisabled && (
|
||||
<Button
|
||||
color="primary"
|
||||
className="mr-2"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setIsBaseMoreDisabled(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{canEdit && !isBaseMoreDisabled ? (
|
||||
<Button
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
const status = await submitBaseInfo();
|
||||
// 更新成功后进入下一步
|
||||
if (status) {
|
||||
handleInit();
|
||||
}
|
||||
setIsBaseMoreDisabled(true);
|
||||
}}
|
||||
className="mr-2"
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_save')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canEdit && isBaseMoreDisabled ? (
|
||||
<Button
|
||||
icon={<IconEdit className="!pr-0" />}
|
||||
color="primary"
|
||||
className="!bg-transparent !coz-fg-secondary"
|
||||
onClick={e => {
|
||||
const el = document.querySelector(
|
||||
'.plugin-tool-detail-baseMore .semi-collapsible-wrapper',
|
||||
) as HTMLElement;
|
||||
if (parseInt(el?.style?.height) !== 0) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
wrapWithCheckLock(() => {
|
||||
setIsBaseMoreDisabled(false);
|
||||
})();
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_edit')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
content: baseInfoNode,
|
||||
classNameWrap: 'plugin-tool-detail-baseMore',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type PluginAPIInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { IconEdit } from '@coze-arch/bot-icons';
|
||||
import { useBaseInfo } from '@coze-agent-ide/bot-plugin-tools/useBaseInfo';
|
||||
import { type RenderEnhancedComponentProps } from '@coze-agent-ide/bot-plugin-tools/pluginModal/types';
|
||||
import { STARTNODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/config';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { SecurityCheckFailed } from '@/components/check_failed';
|
||||
|
||||
interface UseContentBaseInfoProps
|
||||
extends Pick<Partial<RenderEnhancedComponentProps>, 'renderDescComponent'> {
|
||||
space_id: string;
|
||||
plugin_id: string;
|
||||
tool_id: string;
|
||||
apiInfo?: PluginAPIInfo;
|
||||
canEdit: boolean;
|
||||
handleInit: (loading?: boolean) => Promise<void>;
|
||||
wrapWithCheckLock: (fn: () => void) => () => Promise<void>;
|
||||
editVersion?: number;
|
||||
}
|
||||
export const useContentBaseInfo = ({
|
||||
space_id,
|
||||
plugin_id,
|
||||
tool_id,
|
||||
apiInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
editVersion,
|
||||
renderDescComponent,
|
||||
}: UseContentBaseInfoProps) => {
|
||||
// 是否显示安全检查失败信息
|
||||
const [showSecurityCheckFailedMsg, setShowSecurityCheckFailedMsg] =
|
||||
useState(false);
|
||||
const [isBaseInfoDisabled, setIsBaseInfoDisabled] = useState(true);
|
||||
|
||||
// 基本信息
|
||||
const { baseInfoNode, submitBaseInfo } = useBaseInfo({
|
||||
pluginId: plugin_id || '',
|
||||
apiId: tool_id,
|
||||
baseInfo: apiInfo,
|
||||
showModal: false,
|
||||
disabled: isBaseInfoDisabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
space_id,
|
||||
renderEnhancedComponent: renderDescComponent,
|
||||
});
|
||||
|
||||
return {
|
||||
isBaseInfoDisabled,
|
||||
header: I18n.t('Create_newtool_s1_title'),
|
||||
itemKey: 'baseInfo',
|
||||
extra: (
|
||||
<>
|
||||
{showSecurityCheckFailedMsg ? (
|
||||
<SecurityCheckFailed step={STARTNODE} />
|
||||
) : null}
|
||||
{!isBaseInfoDisabled && (
|
||||
<Button
|
||||
color="primary"
|
||||
className="mr-2"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setIsBaseInfoDisabled(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{canEdit && !isBaseInfoDisabled ? (
|
||||
<Button
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
const status = await submitBaseInfo();
|
||||
// 更新成功后进入下一步
|
||||
if (status) {
|
||||
handleInit();
|
||||
}
|
||||
setIsBaseInfoDisabled(true);
|
||||
}}
|
||||
className="mr-2"
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_save')}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{canEdit && isBaseInfoDisabled ? (
|
||||
<Button
|
||||
icon={<IconEdit className="!pr-0" />}
|
||||
color="primary"
|
||||
className="!bg-transparent !coz-fg-secondary"
|
||||
onClick={e => {
|
||||
const el = document.querySelector(
|
||||
'.plugin-tool-detail-baseInfo .semi-collapsible-wrapper',
|
||||
) as HTMLElement;
|
||||
if (parseInt(el?.style?.height) !== 0) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
wrapWithCheckLock(() => {
|
||||
setIsBaseInfoDisabled(false);
|
||||
})();
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_edit')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
content: baseInfoNode,
|
||||
classNameWrap: 'plugin-tool-detail-baseInfo',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type GetPluginInfoResponse,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { usePluginNavigate } from '@coze-studio/bot-plugin-store';
|
||||
import { type STATUS } from '@coze-agent-ide/bot-plugin-tools/pluginModal/types';
|
||||
import { Debug } from '@coze-agent-ide/bot-plugin-tools/pluginModal/debug';
|
||||
import { useDebugFooter } from '@coze-agent-ide/bot-plugin-tools/example/useDebugFooter';
|
||||
import { IconCozPlayFill } from '@coze-arch/coze-design/icons';
|
||||
import { Button, Modal } from '@coze-arch/coze-design';
|
||||
|
||||
interface UseContentDebugProps {
|
||||
debugApiInfo?: PluginAPIInfo;
|
||||
pluginInfo?: GetPluginInfoResponse & { plugin_id?: string };
|
||||
canEdit: boolean;
|
||||
space_id: string;
|
||||
plugin_id: string;
|
||||
tool_id: string;
|
||||
unlockPlugin: () => void;
|
||||
editVersion?: number;
|
||||
onDebugSuccessCallback?: () => void;
|
||||
}
|
||||
|
||||
export const useContentDebug = ({
|
||||
debugApiInfo,
|
||||
canEdit,
|
||||
plugin_id,
|
||||
tool_id,
|
||||
unlockPlugin,
|
||||
editVersion,
|
||||
pluginInfo,
|
||||
onDebugSuccessCallback,
|
||||
}: UseContentDebugProps) => {
|
||||
const resourceNavigate = usePluginNavigate();
|
||||
|
||||
const [dugStatus, setDebugStatus] = useState<STATUS | undefined>();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading] = useState<boolean>(false);
|
||||
|
||||
const { debugFooterNode, setDebugExample, debugExample } = useDebugFooter({
|
||||
apiInfo: debugApiInfo,
|
||||
loading,
|
||||
dugStatus,
|
||||
btnLoading: false,
|
||||
nextStep: () => {
|
||||
resourceNavigate.toResource?.('plugin', plugin_id);
|
||||
|
||||
unlockPlugin();
|
||||
},
|
||||
editVersion,
|
||||
});
|
||||
|
||||
return {
|
||||
itemKey: 'tool_debug',
|
||||
header: I18n.t('Create_newtool_s4_debug'),
|
||||
extra: <>{canEdit ? debugFooterNode : null}</>,
|
||||
content:
|
||||
debugApiInfo && tool_id ? (
|
||||
<Debug
|
||||
pluginType={pluginInfo?.plugin_type}
|
||||
disabled={false} // 是否可调试
|
||||
setDebugStatus={setDebugStatus}
|
||||
pluginId={String(plugin_id)}
|
||||
apiId={String(tool_id)}
|
||||
apiInfo={debugApiInfo as PluginAPIInfo}
|
||||
pluginName={String(pluginInfo?.meta_info?.name)}
|
||||
setDebugExample={setDebugExample}
|
||||
debugExample={debugExample}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
modalContent: (
|
||||
<>
|
||||
{debugApiInfo && tool_id ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
icon={<IconCozPlayFill />}
|
||||
color="highlight"
|
||||
>
|
||||
{I18n.t('project_plugin_testrun')}
|
||||
</Button>
|
||||
) : null}
|
||||
<Modal
|
||||
title={I18n.t('project_plugin_testrun')}
|
||||
width={1000}
|
||||
visible={visible}
|
||||
onOk={() => setVisible(false)}
|
||||
onCancel={() => setVisible(false)}
|
||||
closeOnEsc={true}
|
||||
footer={debugFooterNode}
|
||||
>
|
||||
<Debug
|
||||
pluginType={pluginInfo?.plugin_type}
|
||||
disabled={false} // 是否可调试
|
||||
setDebugStatus={setDebugStatus}
|
||||
pluginId={String(plugin_id)}
|
||||
apiId={String(tool_id)}
|
||||
apiInfo={debugApiInfo as PluginAPIInfo}
|
||||
pluginName={String(pluginInfo?.meta_info?.name)}
|
||||
setDebugExample={setDebugExample}
|
||||
debugExample={debugExample}
|
||||
onSuccessCallback={onDebugSuccessCallback}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconEdit } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
PluginType,
|
||||
type UpdateAPIResponse,
|
||||
type GetPluginInfoResponse,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { useRequestParams } from '@coze-agent-ide/bot-plugin-tools/useRequestParams';
|
||||
import { type RenderEnhancedComponentProps } from '@coze-agent-ide/bot-plugin-tools/pluginModal/types';
|
||||
import { RESPONSENODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/config';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { SecurityCheckFailed } from '@/components/check_failed';
|
||||
|
||||
interface UseContentRequestProps
|
||||
extends Pick<Partial<RenderEnhancedComponentProps>, 'renderParamsComponent'> {
|
||||
apiInfo?: PluginAPIInfo;
|
||||
plugin_id: string;
|
||||
tool_id: string;
|
||||
pluginInfo?: GetPluginInfoResponse & { plugin_id?: string };
|
||||
canEdit: boolean;
|
||||
handleInit: () => Promise<void>;
|
||||
wrapWithCheckLock: (fn: () => void) => () => Promise<void>;
|
||||
editVersion?: number;
|
||||
spaceID: string;
|
||||
onSuccess?: (params: UpdateAPIResponse) => void;
|
||||
}
|
||||
|
||||
export const useContentRequest = ({
|
||||
apiInfo,
|
||||
plugin_id,
|
||||
tool_id,
|
||||
pluginInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
editVersion,
|
||||
spaceID,
|
||||
onSuccess,
|
||||
renderParamsComponent,
|
||||
}: UseContentRequestProps) => {
|
||||
// 是否显示安全检查失败信息
|
||||
const [showSecurityCheckFailedMsg, setShowSecurityCheckFailedMsg] =
|
||||
useState(false);
|
||||
const [isRequestParamsDisabled, setIsRequestParamsDisabled] = useState(true);
|
||||
// 设置请求参数
|
||||
const { requestParamsNode, submitRequestParams, nlTool } = useRequestParams({
|
||||
apiInfo,
|
||||
pluginId: plugin_id || '',
|
||||
requestParams: apiInfo?.request_params,
|
||||
apiId: tool_id,
|
||||
disabled: isRequestParamsDisabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
functionName:
|
||||
pluginInfo?.plugin_type === PluginType.LOCAL
|
||||
? apiInfo?.function_name
|
||||
: undefined,
|
||||
spaceID,
|
||||
onSuccess,
|
||||
renderEnhancedComponent: renderParamsComponent,
|
||||
});
|
||||
return {
|
||||
isRequestParamsDisabled,
|
||||
itemKey: 'request',
|
||||
header: I18n.t('Create_newtool_s2'),
|
||||
extra: (
|
||||
<>
|
||||
{showSecurityCheckFailedMsg ? (
|
||||
<SecurityCheckFailed step={RESPONSENODE} />
|
||||
) : null}
|
||||
{!isRequestParamsDisabled ? nlTool : null}
|
||||
{!isRequestParamsDisabled ? (
|
||||
<Button
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setIsRequestParamsDisabled(true);
|
||||
}}
|
||||
color="primary"
|
||||
className="mr-2"
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_cancel')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canEdit && !isRequestParamsDisabled ? (
|
||||
<Button
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
const status = await submitRequestParams();
|
||||
// 更新成功后进入下一步
|
||||
if (status) {
|
||||
handleInit();
|
||||
setIsRequestParamsDisabled(true);
|
||||
}
|
||||
}}
|
||||
className="mr-2"
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_save')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canEdit && isRequestParamsDisabled ? (
|
||||
<Button
|
||||
icon={<IconEdit className="!pr-0" />}
|
||||
color="primary"
|
||||
className="!bg-transparent !coz-fg-secondary"
|
||||
onClick={e => {
|
||||
const el = document.querySelector(
|
||||
'.plugin-tool-detail-request .semi-collapsible-wrapper',
|
||||
) as HTMLElement;
|
||||
if (parseInt(el?.style?.height) !== 0) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
wrapWithCheckLock(() => {
|
||||
setIsRequestParamsDisabled(false);
|
||||
})();
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_edit')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
content: requestParamsNode,
|
||||
classNameWrap: 'plugin-tool-detail-request',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 */
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconEdit } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
PluginType,
|
||||
type GetPluginInfoResponse,
|
||||
type PluginAPIInfo,
|
||||
DebugExampleStatus,
|
||||
type UpdateAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { useResponseParams } from '@coze-agent-ide/bot-plugin-tools/useResponseParams';
|
||||
import { type RenderEnhancedComponentProps } from '@coze-agent-ide/bot-plugin-tools/pluginModal/types';
|
||||
import { RESPONSENODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/config';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { OauthButtonAction } from '@/components/oauth-action';
|
||||
import { SecurityCheckFailed } from '@/components/check_failed';
|
||||
|
||||
interface UseContentResponseProps
|
||||
extends Pick<Partial<RenderEnhancedComponentProps>, 'renderParamsComponent'> {
|
||||
apiInfo?: PluginAPIInfo;
|
||||
plugin_id: string;
|
||||
tool_id: string;
|
||||
editVersion?: number;
|
||||
pluginInfo?: GetPluginInfoResponse & { plugin_id?: string };
|
||||
canEdit: boolean;
|
||||
handleInit: (loading?: boolean) => Promise<void>;
|
||||
wrapWithCheckLock: (fn: () => void) => () => Promise<void>;
|
||||
debugApiInfo?: PluginAPIInfo;
|
||||
setDebugApiInfo: (apiInfo: PluginAPIInfo) => void;
|
||||
spaceID: string;
|
||||
onSuccess?: (params: UpdateAPIResponse) => void;
|
||||
}
|
||||
|
||||
export const useContentResponse = ({
|
||||
apiInfo,
|
||||
plugin_id,
|
||||
tool_id,
|
||||
editVersion,
|
||||
pluginInfo,
|
||||
canEdit,
|
||||
handleInit,
|
||||
wrapWithCheckLock,
|
||||
debugApiInfo,
|
||||
setDebugApiInfo,
|
||||
spaceID,
|
||||
onSuccess,
|
||||
renderParamsComponent,
|
||||
}: UseContentResponseProps) => {
|
||||
// 是否显示安全检查失败信息
|
||||
const [showSecurityCheckFailedMsg, setShowSecurityCheckFailedMsg] =
|
||||
useState(false);
|
||||
const [isResponseParamsDisabled, setIsResponseParamsDisabled] =
|
||||
useState(true);
|
||||
// 第三步,设置相应参数的hooks组件
|
||||
const { responseParamsNode, submitResponseParams, extra } = useResponseParams(
|
||||
{
|
||||
apiInfo,
|
||||
pluginId: plugin_id || '',
|
||||
responseParams: apiInfo?.response_params,
|
||||
requestParams: apiInfo?.request_params,
|
||||
apiId: tool_id || '',
|
||||
disabled: isResponseParamsDisabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion,
|
||||
pluginType: pluginInfo?.plugin_type,
|
||||
functionName:
|
||||
pluginInfo?.plugin_type === PluginType.LOCAL
|
||||
? apiInfo?.function_name
|
||||
: undefined,
|
||||
spaceID,
|
||||
onSuccess,
|
||||
renderEnhancedComponent: renderParamsComponent,
|
||||
},
|
||||
);
|
||||
|
||||
// 处理 debug 时 example数据先显示后隐藏的问题
|
||||
useEffect(() => {
|
||||
if (!isResponseParamsDisabled) {
|
||||
setDebugApiInfo({
|
||||
...debugApiInfo,
|
||||
debug_example: {},
|
||||
debug_example_status: DebugExampleStatus.Disable,
|
||||
});
|
||||
}
|
||||
}, [isResponseParamsDisabled]);
|
||||
|
||||
return {
|
||||
isResponseParamsDisabled,
|
||||
header: I18n.t('Create_newtool_s3_Outputparameters'),
|
||||
itemKey: 'response',
|
||||
extra: (
|
||||
<>
|
||||
{showSecurityCheckFailedMsg ? (
|
||||
<SecurityCheckFailed step={RESPONSENODE} />
|
||||
) : null}
|
||||
{!isResponseParamsDisabled && canEdit ? <OauthButtonAction /> : null}
|
||||
{!isResponseParamsDisabled ? extra : null}
|
||||
{!isResponseParamsDisabled ? (
|
||||
<Button
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setIsResponseParamsDisabled(true);
|
||||
}}
|
||||
color="primary"
|
||||
className="mr-2"
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_cancel')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canEdit && !isResponseParamsDisabled ? (
|
||||
<Button
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
const status = await submitResponseParams();
|
||||
// 更新成功后进入下一步
|
||||
if (status) {
|
||||
handleInit();
|
||||
setIsResponseParamsDisabled(true);
|
||||
}
|
||||
}}
|
||||
className="mr-2"
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_save')}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{canEdit && isResponseParamsDisabled ? (
|
||||
<Button
|
||||
icon={<IconEdit className="!pr-0" />}
|
||||
color="primary"
|
||||
className="!bg-transparent !coz-fg-secondary"
|
||||
onClick={e => {
|
||||
const el = document.querySelector(
|
||||
'.plugin-tool-detail-response .semi-collapsible-wrapper',
|
||||
) as HTMLElement;
|
||||
if (parseInt(el?.style?.height) !== 0) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
wrapWithCheckLock(() => {
|
||||
setIsResponseParamsDisabled(false);
|
||||
})();
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_plugin_setup_metadata_edit')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
content: responseParamsNode,
|
||||
classNameWrap: 'plugin-tool-detail-response',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useMemoizedFn, useRequest } from 'ahooks';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { OAuthStatus } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { usePluginStore } from '@coze-studio/bot-plugin-store';
|
||||
|
||||
const useAuthForApiTool = () => {
|
||||
const { pluginInfo, canEdit } = usePluginStore(
|
||||
useShallow(store => ({
|
||||
pluginInfo: store.pluginInfo,
|
||||
canEdit: store.canEdit,
|
||||
})),
|
||||
);
|
||||
|
||||
const { data, refresh } = useRequest(
|
||||
async () =>
|
||||
await PluginDevelopApi.GetOAuthStatus({
|
||||
plugin_id: pluginInfo?.plugin_id || '',
|
||||
}),
|
||||
{
|
||||
refreshDeps: [pluginInfo],
|
||||
ready: pluginInfo?.plugin_id !== undefined && canEdit,
|
||||
refreshOnWindowFocus: !0,
|
||||
},
|
||||
);
|
||||
|
||||
const { run, loading: isUpdateLoading } = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
await PluginDevelopApi.RevokeAuthToken({
|
||||
plugin_id: (pluginInfo?.plugin_id as string) || '',
|
||||
});
|
||||
|
||||
refresh();
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error(error);
|
||||
}
|
||||
},
|
||||
{ manual: !0, ready: canEdit },
|
||||
);
|
||||
|
||||
const needAuth = useMemo(() => data?.is_oauth, [data]);
|
||||
|
||||
const isHasAuth = useMemo(
|
||||
() => data?.status === OAuthStatus.Authorized,
|
||||
[data],
|
||||
);
|
||||
|
||||
const content = useMemo(() => data?.content, [data]);
|
||||
|
||||
const doCancelOauth = useMemoizedFn(async () => {
|
||||
await run();
|
||||
});
|
||||
|
||||
const doOauth = useMemoizedFn(() => {
|
||||
window.open(content, '_blank');
|
||||
});
|
||||
|
||||
return {
|
||||
canEdit,
|
||||
needAuth, // 需要 auth 授权
|
||||
isHasAuth, // 是否完成了授权
|
||||
doCancelOauth,
|
||||
isUpdateLoading,
|
||||
doOauth,
|
||||
};
|
||||
};
|
||||
|
||||
export { useAuthForApiTool };
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 {
|
||||
useBotCodeEditInPlugin,
|
||||
useBotFormEditInPlugin,
|
||||
useImportToolInPlugin,
|
||||
useBotCodeEditOutPlugin,
|
||||
} from '@coze-agent-ide/bot-plugin-export/botEdit';
|
||||
20
frontend/packages/agent-ide/bot-plugin/entry/src/index.tsx
Normal file
20
frontend/packages/agent-ide/bot-plugin/entry/src/index.tsx
Normal file
@@ -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.
|
||||
*/
|
||||
|
||||
export { PLUGIN_TYPE_MAP, PLUGIN_PUBLISH_MAP } from './common';
|
||||
|
||||
import PluginHeader from './components/plugin-header';
|
||||
export { PluginHeader };
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export { PluginDetailPage } from './plugin-id';
|
||||
export { MockSetDetail } from './mock-set-detail';
|
||||
export { MockSetList } from './mock-set';
|
||||
export { ToolDetailPage } from './plugin-tool-detail';
|
||||
@@ -0,0 +1,54 @@
|
||||
.layout-content {
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
padding: 0 24px 14px;
|
||||
|
||||
border-bottom: 1px solid #1D1C2314;
|
||||
|
||||
.page-header-intro {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
height: 56px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.page-header-intro_center {
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.page-header-intro_top {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.page-header-operations {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-header-back {
|
||||
margin-right: 12px;
|
||||
|
||||
& svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
/* stylelint-disable-next-line declaration-no-important -- 覆盖icon颜色 */
|
||||
color: var(--semi-color-text-2) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-header_full {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* 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 { useNavigate } from 'react-router-dom';
|
||||
import { useState, useEffect, useMemo, useRef, type FC } from 'react';
|
||||
|
||||
import queryString from 'query-string';
|
||||
import classNames from 'classnames';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpace } from '@coze-arch/foundation-sdk';
|
||||
import { renderHtmlTitle } from '@coze-arch/bot-utils';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
sendTeaEvent,
|
||||
type ParamsTypeDefine,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { Space, UIButton, UILayout, Toast } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type PluginAPIInfo,
|
||||
SpaceType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
type ComponentSubject,
|
||||
ComponentType,
|
||||
type infra,
|
||||
type MockSet,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { PluginDevelopApi, debuggerApi } from '@coze-arch/bot-api';
|
||||
import { getEnvironment } from '@coze-studio/mockset-shared';
|
||||
import { IconCloseNoCycle } from '@coze-arch/bot-icons';
|
||||
import { PageType, usePageJumpResponse } from '@coze-arch/bot-hooks';
|
||||
import {
|
||||
safeJSONParse,
|
||||
getUsedScene,
|
||||
} from '@coze-agent-ide/bot-plugin-mock-set/util';
|
||||
import { MockSetIntro } from '@coze-agent-ide/bot-plugin-mock-set/mock-set-intro';
|
||||
import { CONNECTOR_ID } from '@coze-agent-ide/bot-plugin-mock-set/mock-set/const';
|
||||
import { MockSetPageBreadcrumb } from '@coze-agent-ide/bot-plugin-mock-set/mock-data-page-breadcrumb';
|
||||
import {
|
||||
MockDataList,
|
||||
type MockDataListActions,
|
||||
} from '@coze-agent-ide/bot-plugin-mock-set/mock-data-list';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
enum PageSource {
|
||||
FROM_BOT = 'bot',
|
||||
FROM_WORKFLOW = 'workflow',
|
||||
FROM_MOCK_SET = 'mock_set',
|
||||
}
|
||||
|
||||
enum PageMode {
|
||||
/** 整页 UI类似全覆盖浮层 */
|
||||
FULL_PAGE = 'full_page',
|
||||
/** 嵌入(左侧有菜单栏) */
|
||||
EMBED = 'embed',
|
||||
}
|
||||
|
||||
const MockSetDetail: FC<{
|
||||
toolID: string;
|
||||
mocksetID: string;
|
||||
pluginID: string;
|
||||
spaceID: string;
|
||||
version?: string;
|
||||
}> = ({ toolID, mocksetID, pluginID, spaceID, version }) => {
|
||||
const params = useMemo(
|
||||
() =>
|
||||
queryString.parse(location.search) as {
|
||||
hideMenu: string;
|
||||
},
|
||||
[],
|
||||
);
|
||||
const routeResponse = usePageJumpResponse(PageType.PLUGIN_MOCK_DATA);
|
||||
// API 详情
|
||||
const [apiInfo, setApiInfo] = useState<PluginAPIInfo>({
|
||||
name: routeResponse?.toolName,
|
||||
});
|
||||
// mock set 详情
|
||||
const [mockSetInfo, setMockSetInfo] = useState<MockSet>({
|
||||
id: mocksetID,
|
||||
name: routeResponse?.mockSetName,
|
||||
});
|
||||
// API 对应 schema
|
||||
const [toolSchema, setToolSchema] = useState<string>('');
|
||||
const [perm, setPerm] = useState<{
|
||||
readOnly: boolean;
|
||||
uninitialized: boolean;
|
||||
}>({
|
||||
readOnly: true,
|
||||
uninitialized: true,
|
||||
});
|
||||
|
||||
const listRef = useRef<MockDataListActions>(null);
|
||||
const contentEleRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 页面展示模式
|
||||
const pageMode = params.hideMenu ? PageMode.FULL_PAGE : PageMode.EMBED;
|
||||
// 页面来源
|
||||
const fromSource = routeResponse?.fromSource
|
||||
? (routeResponse.fromSource as PageSource)
|
||||
: PageSource.FROM_MOCK_SET;
|
||||
const [listLength, setListLength] = useState(0);
|
||||
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
|
||||
const space = useSpace(spaceID);
|
||||
const isPersonal = space?.space_type === SpaceType.Personal;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const bizCtx = useMemo(
|
||||
() => ({
|
||||
connectorID: CONNECTOR_ID,
|
||||
connectorUID: userInfo?.user_id_str,
|
||||
bizSpaceID: spaceID,
|
||||
}),
|
||||
[CONNECTOR_ID, userInfo, spaceID],
|
||||
);
|
||||
|
||||
const mockSubject = useMemo(
|
||||
() => ({
|
||||
componentType: ComponentType.CozeTool,
|
||||
componentID: toolID,
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
parentComponentID: pluginID,
|
||||
}),
|
||||
[toolID, pluginID],
|
||||
);
|
||||
|
||||
// 获取当前 tool 信息
|
||||
const getPluginToolInfo = async () => {
|
||||
try {
|
||||
const { api_info = [] } = await PluginDevelopApi.GetPluginAPIs(
|
||||
{
|
||||
plugin_id: pluginID,
|
||||
api_ids: [toolID],
|
||||
preview_version_ts: version,
|
||||
},
|
||||
{ __disableErrorToast: true },
|
||||
);
|
||||
|
||||
if (api_info.length > 0) {
|
||||
const apiInfoTemp = api_info.length > 0 ? api_info[0] : {};
|
||||
setApiInfo(apiInfoTemp);
|
||||
}
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'get_tool_info_fail' });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前 mock set 信息
|
||||
const getMockSetInfo = async () => {
|
||||
if (!mocksetID) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await debuggerApi.MGetMockSet({
|
||||
bizCtx,
|
||||
mockSubject,
|
||||
ids: [mocksetID],
|
||||
pageLimit: 1,
|
||||
});
|
||||
|
||||
if (data.mockSets?.[0]) {
|
||||
setMockSetInfo(data.mockSets[0]);
|
||||
}
|
||||
if (data.schema) {
|
||||
setToolSchema(data.schema);
|
||||
}
|
||||
setPerm({
|
||||
readOnly: userInfo?.user_id_str !== data.mockSets?.[0]?.creator?.ID,
|
||||
uninitialized: false,
|
||||
});
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'get_mockset_info_fail' });
|
||||
}
|
||||
};
|
||||
|
||||
const updateMockSetInfo = (info?: MockSet) => {
|
||||
if (info) {
|
||||
setMockSetInfo(cur => ({ ...cur, ...info }));
|
||||
}
|
||||
};
|
||||
|
||||
const clickAddHandler = () => {
|
||||
listRef.current?.create();
|
||||
};
|
||||
|
||||
const clickUseHandler = async () => {
|
||||
const sourceBizCtx = safeJSONParse<infra.BizCtx>(
|
||||
routeResponse?.bizCtx || '',
|
||||
);
|
||||
const basicParams: ParamsTypeDefine[EVENT_NAMES.use_mockset_front] = {
|
||||
environment: getEnvironment(),
|
||||
workspace_id: spaceID,
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
tool_id: toolID,
|
||||
status: 1,
|
||||
mock_set_id: mocksetID,
|
||||
where: getUsedScene(sourceBizCtx?.trafficScene),
|
||||
};
|
||||
|
||||
try {
|
||||
await debuggerApi.BindMockSet({
|
||||
mockSetID: mocksetID,
|
||||
bizCtx: sourceBizCtx,
|
||||
mockSubject: safeJSONParse<ComponentSubject>(
|
||||
routeResponse?.bindSubjectInfo || '',
|
||||
),
|
||||
});
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.use_mockset_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
});
|
||||
|
||||
navigate(-1);
|
||||
const text = I18n.t('toolname_used_mockset_mocksetname', {
|
||||
toolName: routeResponse?.toolName || '',
|
||||
mockSetName: mockSetInfo.name || '',
|
||||
});
|
||||
text && Toast.success({ content: text, showClose: false });
|
||||
} catch (e) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'change_mockset_fail' });
|
||||
sendTeaEvent(EVENT_NAMES.use_mockset_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error: e?.msg as string,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const closeHandler = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const listUpdateHandler = (num: number, needScrollToTop?: boolean) => {
|
||||
setListLength(num);
|
||||
|
||||
if (needScrollToTop) {
|
||||
contentEleRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
const renderOperations = () => {
|
||||
const operationConfig: {
|
||||
label: string;
|
||||
handler?: () => void;
|
||||
disabled?: boolean;
|
||||
}[] = [];
|
||||
|
||||
if (!perm.readOnly && listLength !== 0) {
|
||||
operationConfig.push({
|
||||
label: I18n.t('add_mock_data'),
|
||||
handler: clickAddHandler,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
fromSource === PageSource.FROM_BOT ||
|
||||
fromSource === PageSource.FROM_WORKFLOW
|
||||
) {
|
||||
operationConfig.push({
|
||||
label: I18n.t(
|
||||
fromSource === PageSource.FROM_BOT ? 'use_in_bot' : 'use_in_workflow',
|
||||
),
|
||||
handler: clickUseHandler,
|
||||
disabled: listLength === 0,
|
||||
});
|
||||
}
|
||||
|
||||
return operationConfig.map((item, index) => (
|
||||
<UIButton
|
||||
type={index === operationConfig.length - 1 ? 'primary' : 'tertiary'}
|
||||
theme={index === operationConfig.length - 1 ? 'solid' : undefined}
|
||||
key={item.label}
|
||||
onClick={item.handler}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.label}
|
||||
</UIButton>
|
||||
));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getMockSetInfo();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getPluginToolInfo();
|
||||
}, [pluginID, toolID]);
|
||||
|
||||
return (
|
||||
<UILayout title={renderHtmlTitle(mockSetInfo.name || I18n.t('mockset'))}>
|
||||
{pageMode === PageMode.EMBED ? (
|
||||
<MockSetPageBreadcrumb
|
||||
pluginId={pluginID}
|
||||
apiInfo={apiInfo}
|
||||
mockSetInfo={mockSetInfo}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className={classNames(
|
||||
s['page-header'],
|
||||
pageMode === PageMode.FULL_PAGE ? s['page-header_full'] : '',
|
||||
)}
|
||||
>
|
||||
{pageMode === PageMode.FULL_PAGE ? (
|
||||
<UIButton
|
||||
className={classNames(s['page-header-back'])}
|
||||
icon={<IconCloseNoCycle />}
|
||||
onClick={closeHandler}
|
||||
theme="borderless"
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className={classNames(
|
||||
s['page-header-intro'],
|
||||
pageMode === PageMode.FULL_PAGE
|
||||
? s['page-header-intro_center']
|
||||
: s['page-header-intro_top'],
|
||||
)}
|
||||
>
|
||||
<MockSetIntro
|
||||
isFullHeader={pageMode === PageMode.FULL_PAGE}
|
||||
mockSetInfo={{
|
||||
mockSubject,
|
||||
...mockSetInfo,
|
||||
}}
|
||||
bizCtx={bizCtx}
|
||||
readOnly={perm.readOnly}
|
||||
onUpdateMockSetInfo={updateMockSetInfo}
|
||||
/>
|
||||
</div>
|
||||
<Space className={classNames(s['page-header-operations'])} spacing={12}>
|
||||
{renderOperations()}
|
||||
</Space>
|
||||
</div>
|
||||
<UILayout.Content
|
||||
className={classNames(s['layout-content'])}
|
||||
ref={contentEleRef}
|
||||
>
|
||||
<MockDataList
|
||||
mockSetID={mocksetID}
|
||||
toolSchema={toolSchema}
|
||||
perm={perm}
|
||||
ref={listRef}
|
||||
bizCtx={bizCtx}
|
||||
onListUpdate={listUpdateHandler}
|
||||
/>
|
||||
</UILayout.Content>
|
||||
</UILayout>
|
||||
);
|
||||
};
|
||||
|
||||
export { MockSetDetail };
|
||||
@@ -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 { I18n } from '@coze-arch/i18n';
|
||||
import { formatDate, formatNumber } from '@coze-arch/bot-utils';
|
||||
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
|
||||
import { Avatar, Typography, UITag } from '@coze-arch/bot-semi';
|
||||
import { type MockSet } from '@coze-arch/bot-api/debugger_api';
|
||||
import { LongTextWithTooltip } from '@coze-agent-ide/bot-plugin-mock-set/long-text-with-tooltip';
|
||||
|
||||
export function getDisplayCols(isPersonal?: boolean): ColumnProps<MockSet>[] {
|
||||
const basicCols: ColumnProps<MockSet>[] = [
|
||||
{
|
||||
title: I18n.t('mockset_name'),
|
||||
dataIndex: 'name',
|
||||
className: 'min-w-[200px]',
|
||||
render: (_v, record: MockSet) => (
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<Typography.Text
|
||||
strong
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { style: { wordBreak: 'break-word' } },
|
||||
},
|
||||
}}
|
||||
className="min-w-[0px]"
|
||||
>
|
||||
{record.name || '-'}
|
||||
</Typography.Text>
|
||||
{record?.schemaIncompatible ? (
|
||||
<UITag className="ml-[10px]" shape="circle" color="orange">
|
||||
{I18n.t('update_required')}
|
||||
</UITag>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{record.description ? (
|
||||
<LongTextWithTooltip>{record.description}</LongTextWithTooltip>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('mock_data_counts'),
|
||||
dataIndex: 'mockRuleQuantity',
|
||||
width: 116,
|
||||
render: (_v, record) => {
|
||||
if (record.mockRuleQuantity === undefined) {
|
||||
return '-';
|
||||
}
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
className="text-[#1C1D2359]"
|
||||
>
|
||||
{formatNumber(record.mockRuleQuantity)}
|
||||
</Typography.Text>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('edit_time'),
|
||||
dataIndex: 'updateTimeInSec',
|
||||
width: 150,
|
||||
sorter: true,
|
||||
render: (_v, record) => {
|
||||
if (!record.updateTimeInSec) {
|
||||
return '-';
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{formatDate(Number(record.updateTimeInSec), 'YYYY-MM-DD HH:mm')}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
const creatorInfoCol: ColumnProps<MockSet>[] = [
|
||||
{
|
||||
title: I18n.t('creators'),
|
||||
dataIndex: 'creatorID',
|
||||
width: 132,
|
||||
render: (_v, record) => {
|
||||
if (!record.creator?.ID) {
|
||||
return '-';
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Avatar
|
||||
src={record.creator?.avatarUrl}
|
||||
size="extra-extra-small"
|
||||
className="mr-[8px]"
|
||||
alt="User"
|
||||
></Avatar>
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
className="flex-1 text-[#1C1D2359]"
|
||||
>
|
||||
{record.creator?.name}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return isPersonal ? basicCols : [...basicCols, ...creatorInfoCol];
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.page {
|
||||
height: 100%;
|
||||
color: #1c1d23;
|
||||
|
||||
:global {
|
||||
.semi-table-row-head {
|
||||
font-size: 12px;
|
||||
color: var(--light-usage-text-color-text-1,
|
||||
rgb(28 29 35 / 80%)) !important;
|
||||
}
|
||||
|
||||
.semi-steps-item-title-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.semi-table-row.semi-table-row-expanded:last-child {
|
||||
.semi-table-row-cell {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
.header-info {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 24px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -24px;
|
||||
|
||||
width: calc(100% + 48px);
|
||||
height: 1px;
|
||||
|
||||
background-color: rgb(29 28 35 / 8%);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.icon-disabled {
|
||||
svg {
|
||||
color: rgb(29 28 35 / 20%);
|
||||
|
||||
path {
|
||||
fill: currentcolor
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.icon-default {
|
||||
span {
|
||||
color: rgb(29 28 35 / 60%);
|
||||
}
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: currentcolor;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.icon-delete {
|
||||
:global {
|
||||
.semi-icon {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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-per-function */
|
||||
import React, { type FC, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import classNames from 'classnames';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { UIBreadcrumb } from '@coze-studio/components';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpace } from '@coze-arch/foundation-sdk';
|
||||
import { renderHtmlTitle } from '@coze-arch/bot-utils';
|
||||
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
|
||||
import {
|
||||
UIIconButton,
|
||||
type UITableMethods,
|
||||
UILayout,
|
||||
UIButton,
|
||||
UITable,
|
||||
UIEmpty,
|
||||
Space,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type PluginAPIInfo,
|
||||
SpaceType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
ComponentType,
|
||||
type MockSet,
|
||||
TrafficScene,
|
||||
OrderBy,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi, PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { MockSetEditModal } from '@coze-studio/mockset-edit-modal-adapter';
|
||||
import {
|
||||
usePluginNavigate,
|
||||
usePluginStore,
|
||||
} from '@coze-studio/bot-plugin-store';
|
||||
import { IconDeleteOutline, IconEditOutline } from '@coze-arch/bot-icons';
|
||||
import { MockSetDeleteModal } from '@coze-agent-ide/bot-plugin-mock-set/mockset-delete-modal';
|
||||
import { CONNECTOR_ID } from '@coze-agent-ide/bot-plugin-mock-set/mock-set/const';
|
||||
|
||||
import { getDisplayCols } from './get-col';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface ListParams {
|
||||
pageNo?: number; // 用于前端计算数量
|
||||
pageSize?: number;
|
||||
pageToken?: string;
|
||||
order?: {
|
||||
desc?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const PLUGIN_NOT_FOUND_CODE = '600303107';
|
||||
const TOOL_NOT_FOUND_CODE = '600303108';
|
||||
|
||||
const MockSetList: FC<{ toolID: string }> = ({ toolID }) => {
|
||||
const resourceNavigate = usePluginNavigate();
|
||||
|
||||
// user信息
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
|
||||
// 路由信息
|
||||
|
||||
const [params, setParams] = useState<ListParams>({
|
||||
//请求参数
|
||||
pageSize: PAGE_SIZE,
|
||||
pageNo: 1,
|
||||
});
|
||||
|
||||
const { pluginInfo, initPlugin, pluginID, spaceID, version } = usePluginStore(
|
||||
useShallow(store => ({
|
||||
pluginInfo: store.pluginInfo,
|
||||
initPlugin: store.initPlugin,
|
||||
pluginID: store.pluginId,
|
||||
spaceID: store.spaceID,
|
||||
version: store.version,
|
||||
})),
|
||||
);
|
||||
|
||||
// space信息
|
||||
const space = useSpace(spaceID);
|
||||
const isPersonal = space?.space_type === SpaceType.Personal;
|
||||
|
||||
// API 详情
|
||||
const [apiInfo, setApiInfo] = useState<PluginAPIInfo>();
|
||||
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
|
||||
const [deleteMockSet, setDeleteMockset] = useState<MockSet | undefined>();
|
||||
|
||||
const pageTokenRef = useRef<string>();
|
||||
|
||||
const tableRef = useRef<UITableMethods>(null);
|
||||
|
||||
const [editDisabled, setEditDisabled] = useState(false);
|
||||
|
||||
// 后端需要的mock上下文信息
|
||||
const ctxInfo = {
|
||||
bizCtx: {
|
||||
trafficScene: TrafficScene.Undefined,
|
||||
connectorID: CONNECTOR_ID,
|
||||
bizSpaceID: spaceID,
|
||||
connectorUID: userInfo?.user_id_str,
|
||||
},
|
||||
mockSubject: {
|
||||
componentType: ComponentType.CozeTool,
|
||||
componentID: toolID,
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
parentComponentID: pluginID,
|
||||
},
|
||||
};
|
||||
|
||||
const columns: ColumnProps<MockSet>[] = [
|
||||
...getDisplayCols(isPersonal),
|
||||
{
|
||||
title: I18n.t('actions'),
|
||||
dataIndex: 'action',
|
||||
width: 108,
|
||||
render: (_v, record) => {
|
||||
const isCreator = userInfo?.user_id_str === record?.creator?.ID;
|
||||
return (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Space spacing={16}>
|
||||
<Tooltip content={I18n.t('Edit')}>
|
||||
<UIIconButton
|
||||
disabled={!isCreator || editDisabled}
|
||||
icon={<IconEditOutline />}
|
||||
onClick={() => {
|
||||
handleEdit(record);
|
||||
}}
|
||||
className={
|
||||
!isCreator || editDisabled
|
||||
? styles['icon-disabled']
|
||||
: styles['icon-default']
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={I18n.t('Delete')}>
|
||||
<UIIconButton
|
||||
icon={<IconDeleteOutline />}
|
||||
className={classNames(
|
||||
styles['icon-delete'],
|
||||
!isCreator || editDisabled
|
||||
? styles['icon-disabled']
|
||||
: styles['icon-default'],
|
||||
)}
|
||||
disabled={!isCreator || editDisabled}
|
||||
onClick={() => {
|
||||
setDeleteMockset(record);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleCreate = () => {
|
||||
setShowCreateModal(true);
|
||||
};
|
||||
|
||||
const handleEdit = (
|
||||
record?: MockSet,
|
||||
autoGenerateConfig?: { generateMode: number },
|
||||
) => {
|
||||
const { id } = record || {};
|
||||
if (id) {
|
||||
resourceNavigate.mocksetDetail?.(
|
||||
toolID,
|
||||
String(id),
|
||||
{},
|
||||
{
|
||||
state: {
|
||||
spaceId: spaceID,
|
||||
pluginId: pluginID,
|
||||
pluginName: pluginInfo?.meta_info?.name,
|
||||
toolId: toolID,
|
||||
toolName: apiInfo?.name,
|
||||
mockSetId: String(id),
|
||||
mockSetName: record?.name,
|
||||
generationMode: autoGenerateConfig?.generateMode,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前tool信息
|
||||
const getPluginToolInfo = async () => {
|
||||
try {
|
||||
const { api_info = [] } = await PluginDevelopApi.GetPluginAPIs({
|
||||
plugin_id: pluginID,
|
||||
api_ids: [toolID],
|
||||
preview_version_ts: version,
|
||||
});
|
||||
|
||||
if (api_info.length > 0) {
|
||||
const apiInfoTemp = api_info.length > 0 ? api_info[0] : {};
|
||||
setApiInfo(apiInfoTemp);
|
||||
}
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'fetch_tool_info_fail' });
|
||||
setApiInfo({});
|
||||
}
|
||||
};
|
||||
|
||||
// mock list
|
||||
const { data, loading } = useRequest(
|
||||
async () => {
|
||||
if (
|
||||
!ctxInfo.mockSubject.componentID ||
|
||||
!ctxInfo.mockSubject.parentComponentID
|
||||
) {
|
||||
return {
|
||||
total: 0,
|
||||
list: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { mockSets, pageToken, count } = await debuggerApi.MGetMockSet({
|
||||
bizCtx: ctxInfo.bizCtx,
|
||||
mockSubject: ctxInfo.mockSubject,
|
||||
pageLimit: params.pageSize,
|
||||
pageToken: params.pageToken,
|
||||
desc: params.order?.desc ?? true,
|
||||
orderBy: OrderBy.UpdateTime,
|
||||
});
|
||||
pageTokenRef.current = pageToken;
|
||||
|
||||
return {
|
||||
total: count,
|
||||
list: mockSets || [],
|
||||
};
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code } = error || {};
|
||||
if (code === PLUGIN_NOT_FOUND_CODE || code === TOOL_NOT_FOUND_CODE) {
|
||||
setEditDisabled(true);
|
||||
}
|
||||
return {
|
||||
total: 0,
|
||||
list: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [params],
|
||||
onError: error => {
|
||||
logger.error({ error, eventName: 'fetch_mockset_list_fail' });
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const refreshPage = () => {
|
||||
tableRef.current?.reset();
|
||||
setParams(p => ({
|
||||
...p,
|
||||
pageSize: PAGE_SIZE,
|
||||
pageToken: undefined,
|
||||
pageNo: 1,
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initPlugin();
|
||||
getPluginToolInfo();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.page}>
|
||||
<UILayout title={renderHtmlTitle(I18n.t('manage_mockset'))}>
|
||||
<UILayout.Header
|
||||
className={styles['layout-header']}
|
||||
breadcrumb={
|
||||
<UIBreadcrumb
|
||||
showTooltip={{ width: '300px' }}
|
||||
pluginInfo={pluginInfo?.meta_info}
|
||||
pluginToolInfo={apiInfo}
|
||||
compact={false}
|
||||
mockSetInfo={{}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<UILayout.Content className={styles['layout-content']}>
|
||||
<div className={styles['header-info']}>
|
||||
<Typography.Text className={styles['layout-header-title']}>
|
||||
{pluginInfo?.meta_info?.name
|
||||
? I18n.t('mockset_of_toolname', {
|
||||
toolName: pluginInfo?.meta_info?.name,
|
||||
})
|
||||
: I18n.t('mockset')}
|
||||
</Typography.Text>
|
||||
<Tooltip
|
||||
style={{ display: editDisabled ? 'block' : 'none' }}
|
||||
content={I18n.t(
|
||||
'unreleased_plugins_tool_cannot_create_mockset',
|
||||
)}
|
||||
>
|
||||
<UIButton
|
||||
onClick={handleCreate}
|
||||
theme="solid"
|
||||
disabled={editDisabled}
|
||||
>
|
||||
{I18n.t('create_mockset')}
|
||||
</UIButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<UITable
|
||||
ref={tableRef}
|
||||
offsetY={207}
|
||||
tableProps={{
|
||||
loading,
|
||||
dataSource: data?.list || [],
|
||||
columns,
|
||||
onRow: (record?: PluginAPIInfo) => ({
|
||||
onClick: () => {
|
||||
if (!editDisabled) {
|
||||
handleEdit(record);
|
||||
}
|
||||
}, // 点击行
|
||||
}),
|
||||
onChange: e => {
|
||||
if (e.sorter?.sortOrder) {
|
||||
tableRef.current?.reset();
|
||||
|
||||
//时间排序
|
||||
setParams(p => ({
|
||||
...p,
|
||||
pageSize: PAGE_SIZE,
|
||||
pageNo: 1,
|
||||
pageToken: undefined,
|
||||
order: {
|
||||
desc: e.sorter?.sortOrder === 'descend',
|
||||
},
|
||||
}));
|
||||
}
|
||||
},
|
||||
}}
|
||||
empty={
|
||||
<UIEmpty
|
||||
empty={{
|
||||
title: I18n.t('no_mockset_yet'),
|
||||
description: editDisabled
|
||||
? undefined
|
||||
: I18n.t('click_button_to_create_mockset'),
|
||||
btnText: editDisabled
|
||||
? undefined
|
||||
: I18n.t('create_mockset'),
|
||||
btnOnClick: editDisabled ? undefined : handleCreate,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
enableLoad
|
||||
total={Number(data?.total || 0)}
|
||||
onLoad={() => {
|
||||
setParams(p => ({
|
||||
...p,
|
||||
pageToken: pageTokenRef.current,
|
||||
pageNo: (p.pageNo ?? 0) + 1,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</UILayout.Content>
|
||||
</UILayout>
|
||||
</div>
|
||||
{showCreateModal ? (
|
||||
<MockSetEditModal
|
||||
visible={showCreateModal}
|
||||
initialInfo={{
|
||||
bizCtx: ctxInfo.bizCtx,
|
||||
bindSubjectInfo: ctxInfo.mockSubject,
|
||||
name: apiInfo?.name,
|
||||
}}
|
||||
onSuccess={handleEdit}
|
||||
onCancel={() => setShowCreateModal(false)}
|
||||
></MockSetEditModal>
|
||||
) : null}
|
||||
{
|
||||
// 删除弹窗
|
||||
deleteMockSet ? (
|
||||
<MockSetDeleteModal
|
||||
visible={!!deleteMockSet}
|
||||
mockSetInfo={{
|
||||
detail: deleteMockSet,
|
||||
ctx: {
|
||||
bizCtx: ctxInfo.bizCtx,
|
||||
mockSubjectInfo: ctxInfo.mockSubject,
|
||||
},
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setDeleteMockset(undefined);
|
||||
refreshPage();
|
||||
}}
|
||||
onCancel={() => setDeleteMockset(undefined)}
|
||||
></MockSetDeleteModal>
|
||||
) : null
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { MockSetList };
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 { useShallow } from 'zustand/react/shallow';
|
||||
import { isBoolean } from 'lodash-es';
|
||||
import {
|
||||
usePluginNavigate,
|
||||
usePluginStore,
|
||||
} from '@coze-studio/bot-plugin-store';
|
||||
import { Modal } from '@coze-arch/bot-semi';
|
||||
import { useBaseInfo } from '@coze-agent-ide/bot-plugin-tools/useBaseInfo';
|
||||
import { STARTNODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/config';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { SecurityCheckFailed } from '@/components/check_failed';
|
||||
|
||||
interface UseCreateToolProps {
|
||||
text: string;
|
||||
space_id?: string;
|
||||
plugin_id: string;
|
||||
onClickWrapper?: (fn: () => void) => () => Promise<void>;
|
||||
/**
|
||||
* 点击创建工具按钮前的回调函数
|
||||
* @returns {boolean | void} 返回false时将阻止后续动作
|
||||
*/
|
||||
onBeforeClick?: () => void;
|
||||
disabled: boolean;
|
||||
isShowBtn?: boolean;
|
||||
}
|
||||
|
||||
interface CreateToolProps extends UseCreateToolProps {
|
||||
todo?: string;
|
||||
}
|
||||
|
||||
export const useCreateTool = ({
|
||||
text,
|
||||
plugin_id,
|
||||
onClickWrapper,
|
||||
onBeforeClick,
|
||||
disabled,
|
||||
isShowBtn = true,
|
||||
space_id,
|
||||
}: UseCreateToolProps) => {
|
||||
const resourceNavigate = usePluginNavigate();
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [showSecurityCheckFailedMsg, setShowSecurityCheckFailedMsg] =
|
||||
useState(false);
|
||||
const { pluginInfo, unlockPlugin, setPluginInfo } = usePluginStore(
|
||||
useShallow(store => ({
|
||||
pluginInfo: store.pluginInfo,
|
||||
unlockPlugin: store.unlockPlugin,
|
||||
setPluginInfo: store.setPluginInfo,
|
||||
})),
|
||||
);
|
||||
const { baseInfoNode, submitBaseInfo } = useBaseInfo({
|
||||
pluginId: plugin_id || '',
|
||||
setApiId: (apiId: string) => {
|
||||
setIsSubmit(false);
|
||||
resourceNavigate.tool?.(apiId, { toStep: '1' }, { replace: true });
|
||||
},
|
||||
showModal: false,
|
||||
disabled,
|
||||
showSecurityCheckFailedMsg,
|
||||
setShowSecurityCheckFailedMsg,
|
||||
editVersion: pluginInfo?.edit_version,
|
||||
space_id: space_id || '',
|
||||
pluginType: pluginInfo?.plugin_type,
|
||||
showFunctionName: true,
|
||||
onSuccess: baseResData => {
|
||||
setPluginInfo({
|
||||
...pluginInfo,
|
||||
edit_version: baseResData?.edit_version,
|
||||
});
|
||||
},
|
||||
});
|
||||
const handleShow = () => {
|
||||
const res = onBeforeClick?.();
|
||||
if (isBoolean(res) && !res) {
|
||||
return;
|
||||
}
|
||||
setVisible(true);
|
||||
setBtnLoading(false);
|
||||
};
|
||||
const handleLoading = (fn: () => void) => () => {
|
||||
setBtnLoading(true);
|
||||
fn();
|
||||
};
|
||||
return {
|
||||
content: (
|
||||
<>
|
||||
{isShowBtn ? (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
loading={btnLoading}
|
||||
color="primary"
|
||||
onClick={handleLoading(
|
||||
onClickWrapper ? onClickWrapper(handleShow) : handleShow,
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
) : null}
|
||||
<Modal
|
||||
title={text}
|
||||
loading={isSubmit}
|
||||
visible={visible}
|
||||
onOk={async () => {
|
||||
setIsSubmit(true);
|
||||
await submitBaseInfo();
|
||||
unlockPlugin();
|
||||
setIsSubmit(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
unlockPlugin();
|
||||
setVisible(false);
|
||||
}}
|
||||
closeOnEsc={true}
|
||||
>
|
||||
{baseInfoNode}
|
||||
{showSecurityCheckFailedMsg ? (
|
||||
<SecurityCheckFailed step={STARTNODE} />
|
||||
) : null}
|
||||
</Modal>
|
||||
</>
|
||||
),
|
||||
openModal: () => {
|
||||
onClickWrapper ? onClickWrapper(handleShow)() : handleShow();
|
||||
},
|
||||
closeModal: () => {
|
||||
unlockPlugin();
|
||||
setVisible(false);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 创建工具
|
||||
*/
|
||||
export const CreateTool: FC<CreateToolProps> = props => {
|
||||
const { content } = useCreateTool({
|
||||
...props,
|
||||
});
|
||||
|
||||
return <>{content}</>;
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.tool-wrapper {
|
||||
height: 100%;
|
||||
color: #1c1d23;
|
||||
|
||||
.layout-header {
|
||||
padding: 24px 24px 12px;
|
||||
}
|
||||
|
||||
.plugin-detail-info {
|
||||
position: relative;
|
||||
margin-bottom: 36px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -24px;
|
||||
|
||||
width: calc(100% + 48px);
|
||||
height: 1px;
|
||||
|
||||
background-color: rgb(29 28 35 / 8%);
|
||||
}
|
||||
|
||||
.plugin-detail-title {
|
||||
max-width: 300px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.plugin-detail-published {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 60%);
|
||||
|
||||
}
|
||||
|
||||
.plugin-detail-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.plugin-detail-desc {
|
||||
max-width: 300px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 80%);
|
||||
}
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 14px 0 rgb(0 0 0 / 10%),
|
||||
0 0 1px 0 rgb(0 0 0 / 30%);
|
||||
}
|
||||
|
||||
.notips {
|
||||
cursor: pointer;
|
||||
margin-left: 12px;
|
||||
font-weight: 600;
|
||||
color: #4062ff;
|
||||
}
|
||||
|
||||
.min-width-200 {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.tool-table-desc {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.icon-delete-disabled {
|
||||
path {
|
||||
fill: currentcolor
|
||||
}
|
||||
}
|
||||
|
||||
.icon-btn-disable {
|
||||
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 20%));
|
||||
|
||||
}
|
||||
|
||||
.debug-btn-disable {
|
||||
color: #1D1C23;
|
||||
|
||||
path {
|
||||
fill: currentcolor;
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-more {
|
||||
color: #1D1C2399;
|
||||
|
||||
|
||||
path {
|
||||
fill: currentcolor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.grey-light {
|
||||
height: 16px;
|
||||
background-color: #f0f0f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.edit-plugin-btn {
|
||||
padding: 0 8px;
|
||||
|
||||
&.edit {
|
||||
:global {
|
||||
.semi-button-content-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.circle-point{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.plugin-method-tag{
|
||||
height: 16px;
|
||||
color: var(--Light-color-violet---violet-6, #6430BF);
|
||||
background: var(--Light-color-violet---violet-1, #E9D6F9);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.icon-example-disabled {
|
||||
path {
|
||||
fill: currentcolor!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
* 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 max-lines-per-function */
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useRequest, useUpdateEffect } from 'ahooks';
|
||||
import { useGetToolColumnsAdapter } from '@coze-studio/plugin-tool-columns-adapter';
|
||||
import { InitialAction } from '@coze-studio/plugin-shared';
|
||||
import { BizPluginPublishPopover } from '@coze-studio/plugin-publish-ui-adapter';
|
||||
import { UIBreadcrumb } from '@coze-studio/components';
|
||||
import {
|
||||
usePluginCallbacks,
|
||||
usePluginHistoryController,
|
||||
usePluginNavigate,
|
||||
usePluginStore,
|
||||
useUnmountUnlock,
|
||||
} from '@coze-studio/bot-plugin-store';
|
||||
import { useReportTti } from '@coze-arch/report-tti';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { useErrorHandler } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconButton,
|
||||
Button,
|
||||
Table,
|
||||
type TableMethods,
|
||||
Layout,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Banner,
|
||||
Popconfirm,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { renderHtmlTitle } from '@coze-arch/bot-utils';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UIEmpty } from '@coze-arch/bot-semi';
|
||||
import { IconCodeOutlined } from '@coze-arch/bot-icons';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
type PluginAPIInfo,
|
||||
type GetUpdatedAPIsResponse,
|
||||
type GetPluginAPIsRequest,
|
||||
CreationMethod,
|
||||
PluginType,
|
||||
type GetPluginInfoResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { useEditExample } from '@coze-agent-ide/bot-plugin-tools';
|
||||
|
||||
import PluginHeader from '@/components/plugin-header';
|
||||
|
||||
import {
|
||||
useBotCodeEditInPlugin,
|
||||
useBotFormEditInPlugin,
|
||||
useImportToolInPlugin,
|
||||
} from '../../hooks';
|
||||
import { CodeSnippetModal } from '../../components';
|
||||
import { CreateTool, useCreateTool } from './create-tool';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const creationMethodText = {
|
||||
[CreationMethod.COZE]: I18n.t('create_tool'),
|
||||
[CreationMethod.IDE]: I18n.t('plugin_creation_create_tool_in_ide'),
|
||||
};
|
||||
|
||||
type PreloadIDEHook = (params: { onBack?: () => void; pluginID: string }) => {
|
||||
handleInitIde: (readonly: boolean) => void;
|
||||
handleShowIde: (params: {
|
||||
initialAction: InitialAction;
|
||||
toolId?: string;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
interface PluginDetailPageProps {
|
||||
projectId?: string;
|
||||
keepDocTitle?: boolean;
|
||||
renderHeaderSlot?: (props: {
|
||||
pluginInfo: GetPluginInfoResponse;
|
||||
}) => ReactNode;
|
||||
usePreloadIDE?: PreloadIDEHook;
|
||||
}
|
||||
|
||||
const PluginDetailPage = ({
|
||||
projectId,
|
||||
keepDocTitle,
|
||||
renderHeaderSlot,
|
||||
usePreloadIDE,
|
||||
}: PluginDetailPageProps) => {
|
||||
const spaceID = useSpaceStore(store => store.space.id);
|
||||
|
||||
const {
|
||||
wrapWithCheckLock,
|
||||
checkPluginIsLockedByOthers,
|
||||
updatedInfo,
|
||||
pluginInfo,
|
||||
canEdit,
|
||||
initPlugin,
|
||||
unlockPlugin,
|
||||
initSuccessed,
|
||||
pluginID,
|
||||
version,
|
||||
updatePluginInfoByImmer,
|
||||
} = usePluginStore(
|
||||
useShallow(store => ({
|
||||
wrapWithCheckLock: store.wrapWithCheckLock,
|
||||
checkPluginIsLockedByOthers: store.checkPluginIsLockedByOthers,
|
||||
updatedInfo: store.updatedInfo,
|
||||
pluginInfo: store.pluginInfo,
|
||||
canEdit: store.canEdit,
|
||||
initPlugin: store.initPlugin,
|
||||
unlockPlugin: store.unlockPlugin,
|
||||
initSuccessed: store.initSuccessed,
|
||||
pluginID: store.pluginId,
|
||||
version: store.version,
|
||||
updatePluginInfoByImmer: store.updatePluginInfoByImmer,
|
||||
})),
|
||||
);
|
||||
const isCloudIDEPlugin = pluginInfo?.creation_method === CreationMethod.IDE;
|
||||
const isCozePlugin = pluginInfo?.creation_method === CreationMethod.COZE;
|
||||
const isInLibraryScope = typeof projectId === 'undefined';
|
||||
const pluginHistoryController = usePluginHistoryController();
|
||||
|
||||
const { onStatusChange, onUpdateDisplayName } = usePluginCallbacks();
|
||||
const resourceNavigate = usePluginNavigate();
|
||||
const navigate = useNavigate();
|
||||
const capture = useErrorHandler();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [curAPIInfo, setCurAPIInfo] = useState<PluginAPIInfo>();
|
||||
|
||||
const [isPublishPopShow, setPublishPopShow] = useState(false);
|
||||
const [showPublishCheckPop, setShowPublishCheckPop] = useState(false);
|
||||
const [publishPopData, setPublishPopData] = useState<GetUpdatedAPIsResponse>(
|
||||
{},
|
||||
);
|
||||
|
||||
const [params, setParams] = useState<GetPluginAPIsRequest>({
|
||||
//请求参数
|
||||
page: 1,
|
||||
size: 10,
|
||||
plugin_id: pluginID,
|
||||
preview_version_ts: version,
|
||||
});
|
||||
const [targetSwitchId, setTargetSwitchId] = useState<string>('');
|
||||
|
||||
const [showDropdownItem, setShowDropDownItem] = useState<
|
||||
PluginAPIInfo | undefined
|
||||
>();
|
||||
|
||||
const { modal: codeModal, setShowCodePluginModel } = useBotCodeEditInPlugin({
|
||||
modalProps: {
|
||||
onSuccess: () => refreshPage(),
|
||||
},
|
||||
});
|
||||
|
||||
const { modal: pluginEditModal, setShowFormPluginModel } =
|
||||
useBotFormEditInPlugin({
|
||||
modalProps: {
|
||||
onSuccess: () => refreshPage(),
|
||||
},
|
||||
});
|
||||
|
||||
const { modal: toolInputModal, setShowImportToolModal } =
|
||||
useImportToolInPlugin({
|
||||
modalProps: {
|
||||
onSuccess: () => refreshPage(),
|
||||
},
|
||||
});
|
||||
|
||||
useUnmountUnlock(pluginID);
|
||||
|
||||
const { data, loading } = useRequest(
|
||||
() => PluginDevelopApi.GetPluginAPIs(params),
|
||||
{
|
||||
refreshDeps: [params],
|
||||
onError: error => {
|
||||
capture(
|
||||
new CustomError(
|
||||
REPORT_EVENTS.PluginGetApis,
|
||||
`get Plugin Detail Error: ${error.message}`,
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const editExampleId = searchParams.get('edit_example_id');
|
||||
const editPlugin = searchParams.get('edit_plugin');
|
||||
const findTool = data?.api_info?.find(
|
||||
item => item.api_id === editExampleId,
|
||||
);
|
||||
if (findTool && editExampleId) {
|
||||
openExample(findTool);
|
||||
searchParams.delete('edit_example_id');
|
||||
navigate({ search: searchParams.toString() }, { replace: true });
|
||||
}
|
||||
|
||||
if (data && editPlugin) {
|
||||
handleEditPlugin();
|
||||
searchParams.delete('edit_plugin');
|
||||
navigate({ search: searchParams.toString() }, { replace: true });
|
||||
}
|
||||
}, [data, searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
onUpdateDisplayName?.(pluginInfo?.meta_info?.name ?? '');
|
||||
}, [pluginInfo?.meta_info?.name]);
|
||||
|
||||
useReportTti({
|
||||
isLive: !!data && !loading,
|
||||
extra: {
|
||||
renderSize: `${data?.api_info?.length}`,
|
||||
},
|
||||
});
|
||||
|
||||
const dataSource = data?.api_info;
|
||||
|
||||
/** 不再提示 */
|
||||
const noTips = async () => {
|
||||
const res = await PluginDevelopApi.NoUpdatedPrompt({
|
||||
plugin_id: pluginID,
|
||||
});
|
||||
if (res) {
|
||||
refreshPage();
|
||||
}
|
||||
};
|
||||
|
||||
const checkPublish = async () => {
|
||||
if (!pluginInfo?.published) {
|
||||
//未发布过点击直接发布
|
||||
setPublishPopShow(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await PluginDevelopApi.GetUpdatedAPIs({
|
||||
plugin_id: pluginID,
|
||||
});
|
||||
if (
|
||||
(res.created_api_names && res.created_api_names.length > 0) ||
|
||||
(res.deleted_api_names && res.deleted_api_names.length > 0) ||
|
||||
(res.updated_api_names && res.updated_api_names.length > 0)
|
||||
) {
|
||||
setPublishPopData(res);
|
||||
setShowPublishCheckPop(true);
|
||||
} else {
|
||||
//没有修改api直接发布
|
||||
setPublishPopShow(true);
|
||||
}
|
||||
};
|
||||
|
||||
const getPublishText = () => {
|
||||
const arr = [
|
||||
...(publishPopData.created_api_names || []),
|
||||
...(publishPopData.deleted_api_names || []),
|
||||
...(publishPopData.updated_api_names || []),
|
||||
];
|
||||
const text = I18n.t('Plugin_update_info_text', {
|
||||
number: arr.length,
|
||||
array: arr.join('、'),
|
||||
});
|
||||
return text;
|
||||
};
|
||||
|
||||
const refreshPage = () => {
|
||||
tableRef.current?.reset();
|
||||
|
||||
initPlugin();
|
||||
|
||||
setParams(p => ({
|
||||
...p,
|
||||
page: 1,
|
||||
size: 10,
|
||||
}));
|
||||
};
|
||||
|
||||
const preloadIDE = usePreloadIDE?.({
|
||||
onBack: refreshPage,
|
||||
pluginID,
|
||||
});
|
||||
useUpdateEffect(() => {
|
||||
if (initSuccessed) {
|
||||
onStatusChange?.('normal');
|
||||
if (isCloudIDEPlugin) {
|
||||
preloadIDE?.handleInitIde(!canEdit);
|
||||
}
|
||||
} else {
|
||||
onStatusChange?.('error');
|
||||
}
|
||||
}, [initSuccessed]);
|
||||
// 区分ide的跳转
|
||||
const handleIdeJump = (
|
||||
initialAction = InitialAction.DEFAULT,
|
||||
toolId = '',
|
||||
) => {
|
||||
// ide 逻辑
|
||||
if (isCloudIDEPlugin) {
|
||||
// 改变路由地址 返回的时候会清掉
|
||||
preloadIDE?.handleShowIde({ initialAction, toolId });
|
||||
} else if (toolId) {
|
||||
resourceNavigate.tool?.(toolId);
|
||||
}
|
||||
};
|
||||
|
||||
const onRow = (record?: PluginAPIInfo) => ({
|
||||
onClick: () => {
|
||||
if (record?.api_id) {
|
||||
setShowDropDownItem(undefined);
|
||||
|
||||
if (isCloudIDEPlugin) {
|
||||
handleIdeJump(InitialAction.SELECT_TOOL, record?.api_id);
|
||||
return;
|
||||
}
|
||||
|
||||
resourceNavigate.tool?.(
|
||||
record.api_id,
|
||||
canEdit ? { mode: 'preview' } : {},
|
||||
);
|
||||
}
|
||||
}, // 点击行
|
||||
});
|
||||
|
||||
const { exampleNode, openExample } = useEditExample({
|
||||
onUpdate: refreshPage,
|
||||
});
|
||||
|
||||
const { getColumns, reactNode: customToolNode } = useGetToolColumnsAdapter({
|
||||
targetSwitchId,
|
||||
setTargetSwitchId,
|
||||
loading,
|
||||
canEdit,
|
||||
refreshPage,
|
||||
plugin_id: pluginID,
|
||||
pluginInfo,
|
||||
updatedInfo,
|
||||
showDropdownItem,
|
||||
setShowDropDownItem,
|
||||
handleIdeJump,
|
||||
setCurAPIInfo,
|
||||
openExample,
|
||||
projectId,
|
||||
unlockPlugin,
|
||||
});
|
||||
const columns = getColumns();
|
||||
|
||||
const tableRef = useRef<TableMethods>(null);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const createToolText = creationMethodText[pluginInfo?.creation_method] || '';
|
||||
const { openModal: openCreateToolModal, content: createToolContent } =
|
||||
useCreateTool({
|
||||
text: createToolText,
|
||||
isShowBtn: false,
|
||||
disabled: !canEdit,
|
||||
onClickWrapper: wrapWithCheckLock,
|
||||
onBeforeClick: () => {
|
||||
setShowDropDownItem(undefined);
|
||||
},
|
||||
plugin_id: pluginID,
|
||||
space_id: spaceID,
|
||||
});
|
||||
|
||||
const handleEditPlugin = async () => {
|
||||
setShowDropDownItem(undefined);
|
||||
if (canEdit) {
|
||||
const isLocked = await checkPluginIsLockedByOthers();
|
||||
|
||||
if (isLocked) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setShowFormPluginModel(true);
|
||||
};
|
||||
|
||||
const handlePublishSuccess = () => {
|
||||
pluginHistoryController.current?.reload();
|
||||
setPublishPopShow(false);
|
||||
updatePluginInfoByImmer(draft => {
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
draft.published = true;
|
||||
});
|
||||
};
|
||||
|
||||
const isRenderCodePluginButton = !isCloudIDEPlugin;
|
||||
|
||||
const isRenderCreateToolButton = canEdit && Boolean(data?.total);
|
||||
|
||||
const isRenderImportButton = canEdit && !isCloudIDEPlugin;
|
||||
|
||||
const isRenderPublishButton = isRenderCreateToolButton && isInLibraryScope;
|
||||
|
||||
const isRenderIDEPublishButton = isRenderPublishButton && isCloudIDEPlugin;
|
||||
|
||||
const isRenderCozePluginPublishButton = isRenderPublishButton && isCozePlugin;
|
||||
|
||||
return (
|
||||
<div className={s['tool-wrapper']}>
|
||||
{codeModal}
|
||||
{pluginEditModal}
|
||||
{toolInputModal}
|
||||
{customToolNode}
|
||||
{exampleNode}
|
||||
<Layout
|
||||
className="flex"
|
||||
title={renderHtmlTitle(
|
||||
I18n.t('tab_plugin_detail', {
|
||||
plugin_name: pluginInfo?.meta_info?.name ?? '',
|
||||
}),
|
||||
)}
|
||||
keepDocTitle={keepDocTitle}
|
||||
>
|
||||
{isInLibraryScope ? (
|
||||
<Layout.Header
|
||||
className={s['layout-header']}
|
||||
breadcrumb={
|
||||
<UIBreadcrumb
|
||||
showTooltip={{ width: '300px' }}
|
||||
pluginInfo={pluginInfo?.meta_info}
|
||||
compact={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Layout.Content className={s['layout-content']}>
|
||||
{/* 已发布且有更新展示 */}
|
||||
{pluginInfo?.status &&
|
||||
pluginInfo?.published &&
|
||||
canEdit &&
|
||||
isInLibraryScope ? (
|
||||
<Banner
|
||||
className={s.banner}
|
||||
type="info"
|
||||
bordered
|
||||
fullMode={false}
|
||||
description={
|
||||
<div>
|
||||
{I18n.t('plugin_update_tip')}
|
||||
<Typography.Text
|
||||
className={s.notips}
|
||||
onClick={() => {
|
||||
noTips();
|
||||
}}
|
||||
>
|
||||
{I18n.t('not_show_again')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{/* plugin简介 */}
|
||||
{pluginInfo ? (
|
||||
<PluginHeader
|
||||
pluginInfo={pluginInfo}
|
||||
loading={loading}
|
||||
canEdit={canEdit}
|
||||
onClickEdit={handleEditPlugin}
|
||||
extraRight={
|
||||
<>
|
||||
{renderHeaderSlot?.({ pluginInfo })}
|
||||
{isRenderCodePluginButton ? (
|
||||
<Tooltip
|
||||
position="left"
|
||||
content={I18n.t('Plugin_button_code_tooltip')}
|
||||
>
|
||||
<IconButton
|
||||
icon={<IconCodeOutlined />}
|
||||
onClick={() => {
|
||||
setShowDropDownItem(undefined);
|
||||
setShowCodePluginModel(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{isRenderCreateToolButton ? (
|
||||
<CreateTool
|
||||
text={createToolText}
|
||||
disabled={!canEdit}
|
||||
onClickWrapper={wrapWithCheckLock}
|
||||
onBeforeClick={() => {
|
||||
setShowDropDownItem(undefined);
|
||||
if (isCloudIDEPlugin) {
|
||||
// 改变路由地址 返回的时候会清掉
|
||||
preloadIDE?.handleShowIde({
|
||||
initialAction: InitialAction.CREATE_TOOL,
|
||||
toolId: '',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
plugin_id={pluginID}
|
||||
space_id={spaceID}
|
||||
/>
|
||||
) : null}
|
||||
{isRenderImportButton ? (
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={
|
||||
!canEdit || pluginInfo?.plugin_type === PluginType.LOCAL
|
||||
}
|
||||
onClick={wrapWithCheckLock(() => {
|
||||
setShowDropDownItem(undefined);
|
||||
setShowImportToolModal(true);
|
||||
})}
|
||||
>
|
||||
{I18n.t('import')}
|
||||
</Button>
|
||||
) : null}
|
||||
{/* ! 发布按钮 */}
|
||||
{isRenderIDEPublishButton ? (
|
||||
<Tooltip
|
||||
position="left"
|
||||
content={I18n.t('Plugin_button_publish_tooltip')}
|
||||
>
|
||||
<Button
|
||||
disabled={!data?.total}
|
||||
theme="solid"
|
||||
onClick={() => {
|
||||
setShowDropDownItem(undefined);
|
||||
handleIdeJump();
|
||||
}}
|
||||
>
|
||||
{I18n.t('Publish')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{isRenderCozePluginPublishButton ? (
|
||||
<Popconfirm
|
||||
visible={showPublishCheckPop}
|
||||
onCancel={() => setShowPublishCheckPop(false)}
|
||||
onClickOutSide={() => {
|
||||
setShowPublishCheckPop(false);
|
||||
}}
|
||||
style={{ width: 400 }}
|
||||
trigger="custom"
|
||||
onConfirm={() => {
|
||||
setShowPublishCheckPop(false);
|
||||
setPublishPopShow(true);
|
||||
}}
|
||||
title={I18n.t('Plugin_update_info_title')}
|
||||
content={<>{getPublishText()}</>}
|
||||
okText={I18n.t('Confirm')}
|
||||
cancelText={I18n.t('Cancel')}
|
||||
>
|
||||
<span>
|
||||
<BizPluginPublishPopover
|
||||
spaceId={spaceID}
|
||||
pluginInfo={pluginInfo}
|
||||
pluginId={pluginID}
|
||||
isInLibraryScope={isInLibraryScope}
|
||||
isPluginHasPublished={Boolean(pluginInfo.published)}
|
||||
visible={isPublishPopShow}
|
||||
onClickOutside={() => setPublishPopShow(false)}
|
||||
onPublishSuccess={handlePublishSuccess}
|
||||
>
|
||||
<span>
|
||||
<Tooltip
|
||||
position="left"
|
||||
content={I18n.t('Plugin_button_publish_tooltip')}
|
||||
>
|
||||
<Button
|
||||
disabled={!data?.total}
|
||||
theme="solid"
|
||||
onClick={checkPublish}
|
||||
>
|
||||
{I18n.t('Publish')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</BizPluginPublishPopover>
|
||||
</span>
|
||||
</Popconfirm>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{/* 工具列表表格 */}
|
||||
{!!dataSource?.length && (
|
||||
<div className="mb-[24px] mt-[36px] text-[18px] weight-[600]">
|
||||
{I18n.t('plugin_api_list_table_name')}
|
||||
</div>
|
||||
)}
|
||||
<Table
|
||||
ref={tableRef}
|
||||
offsetY={390}
|
||||
tableProps={{
|
||||
rowKey: 'api_id',
|
||||
loading,
|
||||
dataSource,
|
||||
columns,
|
||||
onRow,
|
||||
onChange: e => {
|
||||
if (e.sorter?.sortOrder) {
|
||||
//时间排序
|
||||
setParams(p => ({
|
||||
...p,
|
||||
page: 1,
|
||||
size: 10,
|
||||
order: {
|
||||
desc: e.sorter?.sortOrder === 'descend',
|
||||
},
|
||||
}));
|
||||
}
|
||||
},
|
||||
}}
|
||||
empty={
|
||||
<UIEmpty
|
||||
empty={{
|
||||
title: I18n.t('plugin_empty_desc'),
|
||||
btnText: canEdit ? createToolText : undefined,
|
||||
btnOnClick: () => {
|
||||
if (isCloudIDEPlugin) {
|
||||
// 改变路由地址 返回的时候会清掉
|
||||
preloadIDE?.handleShowIde({
|
||||
initialAction: InitialAction.CREATE_TOOL,
|
||||
toolId: '',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
openCreateToolModal();
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
enableLoad
|
||||
total={Number(data?.total || 0)}
|
||||
onLoad={() => {
|
||||
setParams(p => ({
|
||||
...p,
|
||||
page: (params.page ?? 0) + 1,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
{createToolContent}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
<CodeSnippetModal
|
||||
visible={!!curAPIInfo}
|
||||
onCancel={() => {
|
||||
setCurAPIInfo(undefined);
|
||||
}}
|
||||
pluginAPIInfo={curAPIInfo}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { PluginDetailPage };
|
||||
@@ -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 { ToolDetailPage } from '@/components/plugin-tool-detail';
|
||||
17
frontend/packages/agent-ide/bot-plugin/entry/src/typings.d.ts
vendored
Normal file
17
frontend/packages/agent-ide/bot-plugin/entry/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' />
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 function getEnv(): string {
|
||||
if (!IS_PROD) {
|
||||
return 'cn-boe';
|
||||
}
|
||||
|
||||
const regionPart = IS_OVERSEA ? 'oversea' : 'cn';
|
||||
const inhousePart = IS_RELEASE_VERSION ? 'release' : 'inhouse';
|
||||
return [regionPart, inhousePart].join('-');
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 PluginApi } from '@coze-arch/bot-api/developer_api';
|
||||
import { type PluginInfoProps } from '@coze-studio/plugin-shared';
|
||||
|
||||
export const getPluginApiKey = (api: Pick<PluginApi, 'plugin_id' | 'name'>) =>
|
||||
(api.plugin_id ?? '0') + (api.name ?? '');
|
||||
|
||||
export { getEnv } from './get-env';
|
||||
|
||||
export const doFormatTypeAndCreation = (info?: PluginInfoProps) =>
|
||||
info ? `${info?.plugin_type}-${info?.creation_method}` : '';
|
||||
Reference in New Issue
Block a user