feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -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';
|
||||
Reference in New Issue
Block a user