feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/agent-ide/bot-plugin/entry/README.md
Normal file
16
frontend/packages/agent-ide/bot-plugin/entry/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-agent-ide/bot-plugin
|
||||
|
||||
> Project template for react component with storybook.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] eslint & ts
|
||||
- [x] esm bundle
|
||||
- [x] umd bundle
|
||||
- [x] storybook
|
||||
|
||||
## Commands
|
||||
|
||||
- init: `rush update`
|
||||
- dev: `npm run dev`
|
||||
- build: `npm run build`
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 { describe, expect, it, vi } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { OAuthStatus } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { useAuthForApiTool } from '@/hooks/auth/use-auth-for-api-tool';
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
createLoggerWith: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-studio/bot-plugin-store', () => ({
|
||||
usePluginStore: vi.fn().mockReturnValue({
|
||||
pluginInfo: {
|
||||
plugin_id: 'plugin_id',
|
||||
canEdit: !0,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
PluginDevelopApi: {
|
||||
RevokeAuthToken: vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('mockRejectedValue'))
|
||||
.mockResolvedValue(0),
|
||||
GetOAuthStatus: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
is_oauth: !0,
|
||||
status: OAuthStatus.Authorized,
|
||||
content: 'content',
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
is_oauth: !0,
|
||||
status: OAuthStatus.Unauthorized,
|
||||
content: 'content',
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
is_oauth: !!0,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
is_oauth: !0,
|
||||
status: OAuthStatus.Unauthorized,
|
||||
content: 'content',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.stubGlobal('open', vi.fn());
|
||||
|
||||
describe('useAuthForApiTool', () => {
|
||||
it('useAuthForApiTool (三方服务 & 已授权 => 取消授权失败 => 取消授权成功) is pass', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useAuthForApiTool());
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.needAuth).toBe(!0);
|
||||
|
||||
expect(result.current.isHasAuth).toBe(!0);
|
||||
|
||||
try {
|
||||
await act(async () => await result.current.doCancelOauth());
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
|
||||
expect(result.current.isHasAuth).toBe(!0);
|
||||
|
||||
await act(async () => await result.current.doCancelOauth());
|
||||
|
||||
expect(result.current.isHasAuth).toBe(!!0);
|
||||
});
|
||||
|
||||
it('useAuthForApiTool (非三方服务 & 无需授权) is pass', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useAuthForApiTool());
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.needAuth).toBe(!!0);
|
||||
|
||||
expect(result.current.isHasAuth).toBe(!!0);
|
||||
});
|
||||
|
||||
it('useAuthForApiTool (三方服务 & 未授权 => 去授权) is pass', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useAuthForApiTool());
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.needAuth).toBe(!0);
|
||||
|
||||
expect(result.current.isHasAuth).toBe(!!0);
|
||||
|
||||
act(() => result.current.doOauth());
|
||||
});
|
||||
});
|
||||
111
frontend/packages/agent-ide/bot-plugin/entry/__tests__/setup.ts
Normal file
111
frontend/packages/agent-ide/bot-plugin/entry/__tests__/setup.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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 { vi } from 'vitest';
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
useErrorHandler: vi.fn().mockReturnValue(vi.fn()),
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
},
|
||||
reporter: {
|
||||
tracer: vi.fn().mockReturnValue(vi.fn()),
|
||||
event: vi.fn(),
|
||||
errorEvent: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-studio/bot-plugin-store', () => ({
|
||||
usePluginStore: vi.fn().mockReturnValue({
|
||||
pluginInfo: {
|
||||
plugin_id: 'plugin_id',
|
||||
canEdit: !0,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: {
|
||||
t: (key, params = {}) => `Translated: ${key} ${JSON.stringify(params)}`,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-tea', () => ({
|
||||
sendTeaEvent: vi.fn(),
|
||||
EVENT_NAMES: {
|
||||
use_mockset_front: 'use_mockset_front',
|
||||
del_mockset_front: 'del_mockset_front',
|
||||
},
|
||||
ParamsTypeDefine: {},
|
||||
PluginMockDataGenerateMode: {
|
||||
MANUAL: 0, // 手动创建
|
||||
RANDOM: 1, // 随机生成
|
||||
LLM: 2,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-hooks', () => ({
|
||||
SceneType: {
|
||||
BOT__VIEW__WORKFLOW: 'botViewWorkflow',
|
||||
/** bot 详情页查看 workflow,或新建 workflow 但未发布,点击返回 */
|
||||
WORKFLOW__BACK__BOT: 'workflowBackBot',
|
||||
/** bot 详情页创建 workflow,在 workflow 发布后返回 */
|
||||
WORKFLOW_PUBLISHED__BACK__BOT: 'workflowPublishedBackBot',
|
||||
/** bot 详情页进入 mock data 页面 */
|
||||
BOT__TO__PLUGIN_MOCK_DATA: 'botToPluginMockData',
|
||||
/** workflow 详情页进入 mock data 页面 */
|
||||
WORKFLOW__TO__PLUGIN_MOCK_DATA: 'workflowToPluginMockData',
|
||||
/** mock set 页进入 mock data 页面 */
|
||||
PLUGIN_MOCK_SET__TO__PLUGIN_MOCK_DATA: 'pluginMockSetToPluginMockData',
|
||||
/** bot 详情页进入 knowledge 页面 */
|
||||
BOT__VIEW__KNOWLEDGE: 'botViewKnowledge',
|
||||
/** knowledge 页面点击退出返回 bot 详情页(未点击添加) */
|
||||
KNOWLEDGE__BACK__BOT: 'knowledgeBackBot',
|
||||
/** knowledge 页面点击返回 bot 详情页,并添加到 bot */
|
||||
KNOWLEDGE__ADD_TO__BOT: 'knowledgeAddToBot',
|
||||
},
|
||||
usePageJumpService: vi.fn().mockReturnValue({
|
||||
jump: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-studio-store', () => ({
|
||||
useSpaceStore: {
|
||||
getState: vi.fn().mockReturnValue({
|
||||
getSpaceId: vi.fn().mockReturnValue('spaceId'),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/report-events', () => ({
|
||||
REPORT_EVENTS: {
|
||||
pluginIdeInitTrace: 'pluginIdeInitTrace',
|
||||
pluginIdeInit: 'pluginIdeInit',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-error', () => ({
|
||||
CustomError: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: vi.fn().mockReturnValue(vi.fn()),
|
||||
useParams: vi.fn().mockReturnValue({
|
||||
space_id: 'space_id',
|
||||
plugin_id: 'plugin_id',
|
||||
}),
|
||||
}));
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getEnv } from '@/util/get-env';
|
||||
|
||||
describe('getEnv function', () => {
|
||||
it('should return "cn-boe" when not in production', () => {
|
||||
vi.stubGlobal('IS_PROD', undefined);
|
||||
// 不设置IS_PROD,默认为非生产环境
|
||||
const env = getEnv();
|
||||
expect(env).toBe('cn-boe');
|
||||
});
|
||||
|
||||
it('should return "cn-release" when in production, not overseas, and is release version', () => {
|
||||
vi.stubGlobal('IS_PROD', true); // 设置为生产环境
|
||||
vi.stubGlobal('IS_OVERSEA', false); // 不是海外
|
||||
vi.stubGlobal('IS_RELEASE_VERSION', true); // 是发布版本
|
||||
const env = getEnv();
|
||||
expect(env).toBe('cn-release');
|
||||
});
|
||||
|
||||
it('should return "cn-inhouse" when in production, not overseas, and is not release version', () => {
|
||||
vi.stubGlobal('IS_PROD', true); // 设置为生产环境
|
||||
vi.stubGlobal('IS_OVERSEA', false); // 不是海外
|
||||
vi.stubGlobal('IS_RELEASE_VERSION', false); // 不是发布版本
|
||||
const env = getEnv();
|
||||
expect(env).toBe('cn-inhouse');
|
||||
});
|
||||
|
||||
it('should return "oversea-release" when in production, overseas, and is release version', () => {
|
||||
vi.stubGlobal('IS_PROD', true); // 设置为生产环境
|
||||
vi.stubGlobal('IS_OVERSEA', true); // 是海外
|
||||
vi.stubGlobal('IS_RELEASE_VERSION', true); // 是发布版本
|
||||
const env = getEnv();
|
||||
expect(env).toBe('oversea-release');
|
||||
});
|
||||
|
||||
it('should return "oversea-inhouse" when in production, overseas, and is not release version', () => {
|
||||
vi.stubGlobal('IS_PROD', true); // 设置为生产环境
|
||||
vi.stubGlobal('IS_OVERSEA', true); // 是海外
|
||||
vi.stubGlobal('IS_RELEASE_VERSION', false); // 不是发布版本
|
||||
const env = getEnv();
|
||||
expect(env).toBe('oversea-inhouse');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
144
frontend/packages/agent-ide/bot-plugin/entry/package.json
Normal file
144
frontend/packages/agent-ide/bot-plugin/entry/package.json
Normal file
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"name": "@coze-agent-ide/bot-plugin",
|
||||
"version": "0.0.1",
|
||||
"description": "空间下 plugin 能力",
|
||||
"license": "Apache-2.0",
|
||||
"author": "lihuiwen.123@bytedance.com",
|
||||
"maintainers": [],
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./page": "./src/pages/index.ts",
|
||||
"./hook": "./src/hooks/index.ts",
|
||||
"./util": "./src/util/index.ts",
|
||||
"./store": "./src/store/index.ts",
|
||||
"./components/plugin-apis/use-plugin-apis-modal": "./src/components/plugin-apis/use-plugin-apis-modal.tsx",
|
||||
"./component": "./src/components/index.ts",
|
||||
"./components/*": "./src/components/*"
|
||||
},
|
||||
"main": "src/index.tsx",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"page": [
|
||||
"./src/pages/index.ts"
|
||||
],
|
||||
"hook": [
|
||||
"./src/hooks/index.ts"
|
||||
],
|
||||
"util": [
|
||||
"./src/util/index.ts"
|
||||
],
|
||||
"store": [
|
||||
"./src/store/index.ts"
|
||||
],
|
||||
"component": [
|
||||
"./src/components/index.ts"
|
||||
],
|
||||
"components/plugin-apis/use-plugin-apis-modal": [
|
||||
"./src/components/plugin-apis/use-plugin-apis-modal.tsx"
|
||||
],
|
||||
"components/*": [
|
||||
"./src/components/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^5.1.5",
|
||||
"@coze-agent-ide/bot-plugin-export": "workspace:*",
|
||||
"@coze-agent-ide/bot-plugin-mock-set": "workspace:*",
|
||||
"@coze-agent-ide/bot-plugin-tools": "workspace:*",
|
||||
"@coze-agent-ide/plugin-content-adapter": "workspace:*",
|
||||
"@coze-agent-ide/plugin-modal-adapter": "workspace:*",
|
||||
"@coze-agent-ide/plugin-risk-warning": "workspace:*",
|
||||
"@coze-agent-ide/plugin-shared": "workspace:*",
|
||||
"@coze-agent-ide/tool": "workspace:*",
|
||||
"@coze-agent-ide/tool-config": "workspace:*",
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-flags": "workspace:*",
|
||||
"@coze-arch/bot-hooks": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-md-box-adapter": "workspace:*",
|
||||
"@coze-arch/bot-monaco-editor": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/bot-space-api": "workspace:*",
|
||||
"@coze-arch/bot-studio-store": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/bot-utils": "workspace:*",
|
||||
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/foundation-sdk": "workspace:*",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/idl": "workspace:*",
|
||||
"@coze-arch/logger": "workspace:*",
|
||||
"@coze-arch/report-events": "workspace:*",
|
||||
"@coze-arch/report-tti": "workspace:*",
|
||||
"@coze-common/assets": "workspace:*",
|
||||
"@coze-common/biz-components": "workspace:*",
|
||||
"@coze-studio/bot-detail-store": "workspace:*",
|
||||
"@coze-studio/bot-plugin-store": "workspace:*",
|
||||
"@coze-studio/bot-utils": "workspace:*",
|
||||
"@coze-studio/components": "workspace:*",
|
||||
"@coze-studio/file-kit": "workspace:*",
|
||||
"@coze-studio/mockset-edit-modal-adapter": "workspace:*",
|
||||
"@coze-studio/mockset-shared": "workspace:*",
|
||||
"@coze-studio/plugin-publish-ui-adapter": "workspace:*",
|
||||
"@coze-studio/plugin-shared": "workspace:*",
|
||||
"@coze-studio/plugin-tool-columns-adapter": "workspace:*",
|
||||
"@coze-studio/user-store": "workspace:*",
|
||||
"@coze-workflow/base": "workspace:*",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"@douyinfe/semi-illustrations": "^2.36.0",
|
||||
"@flowgram-adapter/free-layout-editor": "workspace:*",
|
||||
"ahooks": "^3.7.8",
|
||||
"classnames": "^2.3.2",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"dayjs": "^1.11.7",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"qs": "^6.11.2",
|
||||
"query-string": "^8.1.0",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@rsbuild/core": "1.1.13",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/json-schema": "~7.0.15",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"debug": "^4.3.4",
|
||||
"less": "^3.13.1",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"react-is": ">= 16.8.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"scheduler": ">=0.19.0",
|
||||
"styled-components": ">= 2",
|
||||
"stylelint": "^15.11.0",
|
||||
"typescript": "~5.8.2",
|
||||
"utility-types": "^3.10.0",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5",
|
||||
"webpack": "~5.91.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
},
|
||||
"// deps": "debug@^4.3.4 为脚本自动补齐,请勿改动"
|
||||
}
|
||||
|
||||
@@ -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}` : '';
|
||||
159
frontend/packages/agent-ide/bot-plugin/entry/tsconfig.build.json
Normal file
159
frontend/packages/agent-ide/bot-plugin/entry/tsconfig.build.json
Normal file
@@ -0,0 +1,159 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-flags/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-hooks/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-md-box-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-monaco-editor/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-space-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/foundation-sdk/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/idl/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/logger/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/report-events/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/report-tti/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/assets/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/biz-components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/flowgram-adapter/free-layout-editor/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-icons/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-semi/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/stylelint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/ts-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/vitest-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../export/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../mock-set/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../plugin-content-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../plugin-modal-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-risk-warning/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../plugin-shared/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/common/file-kit/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/mockset-edit-modal-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/mockset-shared/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/plugin-publish-ui-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/plugin-shared/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/plugin-tool-columns-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-detail/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-plugin/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/user-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tool-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tool/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../tools/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../workflow/base/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/agent-ide/bot-plugin/entry/tsconfig.json
Normal file
15
frontend/packages/agent-ide/bot-plugin/entry/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
],
|
||||
"exclude": ["**/*"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
test: {
|
||||
setupFiles: ['./__tests__/setup.ts'],
|
||||
},
|
||||
},
|
||||
{
|
||||
fixSemi: true,
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/agent-ide/bot-plugin/export/README.md
Normal file
16
frontend/packages/agent-ide/bot-plugin/export/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-agent-ide/bot-plugin-export
|
||||
|
||||
> Project template for react component with storybook.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] eslint & ts
|
||||
- [x] esm bundle
|
||||
- [x] umd bundle
|
||||
- [x] storybook
|
||||
|
||||
## Commands
|
||||
|
||||
- init: `rush update`
|
||||
- dev: `npm run dev`
|
||||
- build: `npm run build`
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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 {
|
||||
getFileExtension,
|
||||
getInitialPluginMetaInfo,
|
||||
isValidURL,
|
||||
} from '../src/component/file-import/utils';
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
persist: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-error', () => ({
|
||||
CustomError: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-utils', () => ({
|
||||
safeJSONParse: JSON.parse,
|
||||
}));
|
||||
|
||||
describe('getFileExtension', () => {
|
||||
it('yaml file extension', () => {
|
||||
const res = getFileExtension('test.yaml');
|
||||
expect(res).toEqual('yaml');
|
||||
});
|
||||
|
||||
it('json file extension', () => {
|
||||
const res = getFileExtension('test.json');
|
||||
expect(res).toEqual('json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidURL', () => {
|
||||
it('is not valid url', () => {
|
||||
const res = isValidURL('app//ddd');
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
|
||||
it('is valid url', () => {
|
||||
const res = isValidURL('https://www.coze.com/hello');
|
||||
expect(res).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInitialPluginMetaInfo', () => {
|
||||
it('get initial info', () => {
|
||||
const data: any = {
|
||||
aiPlugin: {
|
||||
name_for_human: '1',
|
||||
description_for_human: '1',
|
||||
auth: { type: 'none' },
|
||||
},
|
||||
openAPI: { servers: [{ url: 'url' }] },
|
||||
};
|
||||
const res = getInitialPluginMetaInfo(data);
|
||||
expect(res.name).toEqual('1');
|
||||
expect(res.desc).toEqual('1');
|
||||
expect(res.auth_type?.[0]).toEqual(0);
|
||||
expect(res.url).toEqual('url');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
133
frontend/packages/agent-ide/bot-plugin/export/package.json
Normal file
133
frontend/packages/agent-ide/bot-plugin/export/package.json
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"name": "@coze-agent-ide/bot-plugin-export",
|
||||
"version": "0.0.1",
|
||||
"description": "plugin 导出模块",
|
||||
"license": "Apache-2.0",
|
||||
"author": "lihuiwen.123@bytedance.com",
|
||||
"maintainers": [],
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./agentSkillPluginModal/hooks": "./src/component/agent-skill-plugin-modal/hooks.tsx",
|
||||
"./asyncSetting": "./src/component/async-setting/index.tsx",
|
||||
"./pluginFeatModal": "./src/component/plugin-feat-modal/index.tsx",
|
||||
"./pluginFeatModal/featButton": "./src/component/plugin-feat-modal/feat-button/index.tsx",
|
||||
"./botEdit": "./src/component/bot_edit/index.ts",
|
||||
"./fileImport": "./src/component/file-import/index.tsx",
|
||||
"./editor": "./src/component/editor/index.ts",
|
||||
"./pluginDocs": "./src/component/plugin-docs/index.tsx"
|
||||
},
|
||||
"main": "src/index.tsx",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"agentSkillPluginModal/hooks": [
|
||||
"./src/component/agent-skill-plugin-modal/hooks.tsx"
|
||||
],
|
||||
"asyncSetting": [
|
||||
"./src/component/async-setting/index.tsx"
|
||||
],
|
||||
"pluginFeatModal": [
|
||||
"./src/component/plugin-feat-modal/index.tsx"
|
||||
],
|
||||
"pluginFeatModal/featButton": [
|
||||
"./src/component/plugin-feat-modal/feat-button/index.tsx"
|
||||
],
|
||||
"botEdit": [
|
||||
"./src/component/bot_edit/index.ts"
|
||||
],
|
||||
"fileImport": [
|
||||
"./src/component/file-import/index.tsx"
|
||||
],
|
||||
"editor": [
|
||||
"./src/component/editor/index.ts"
|
||||
],
|
||||
"pluginDocs": [
|
||||
"./src/component/plugin-docs/index.tsx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^5.1.5",
|
||||
"@coze-agent-ide/bot-plugin-mock-set": "workspace:*",
|
||||
"@coze-agent-ide/bot-plugin-tools": "workspace:*",
|
||||
"@coze-agent-ide/plugin-modal-adapter": "workspace:*",
|
||||
"@coze-agent-ide/plugin-shared": "workspace:*",
|
||||
"@coze-agent-ide/tool": "workspace:*",
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-flags": "workspace:*",
|
||||
"@coze-arch/bot-hooks": "workspace:*",
|
||||
"@coze-arch/bot-http": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-monaco-editor": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/bot-studio-store": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/bot-utils": "workspace:*",
|
||||
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/foundation-sdk": "workspace:*",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/logger": "workspace:*",
|
||||
"@coze-common/assets": "workspace:*",
|
||||
"@coze-common/biz-components": "workspace:*",
|
||||
"@coze-community/components": "workspace:*",
|
||||
"@coze-foundation/enterprise-store-adapter": "workspace:*",
|
||||
"@coze-studio/bot-detail-store": "workspace:*",
|
||||
"@coze-studio/bot-plugin-store": "workspace:*",
|
||||
"@coze-studio/bot-utils": "workspace:*",
|
||||
"@coze-studio/components": "workspace:*",
|
||||
"@coze-studio/plugin-form-adapter": "workspace:*",
|
||||
"@coze-studio/plugin-shared": "workspace:*",
|
||||
"@coze-studio/premium-store-adapter": "workspace:*",
|
||||
"@coze-studio/user-store": "workspace:*",
|
||||
"@coze-workflow/base": "workspace:*",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"@flowgram-adapter/free-layout-editor": "workspace:*",
|
||||
"ahooks": "^3.7.8",
|
||||
"axios": "^1.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"yaml": "^2.2.2",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@rsbuild/core": "1.1.13",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/node": "18.18.9",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"less": "^3.13.1",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"react-is": ">= 16.8.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"scheduler": ">=0.19.0",
|
||||
"styled-components": ">= 2",
|
||||
"stylelint": "^15.11.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5",
|
||||
"webpack": "~5.91.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 581 B |
Binary file not shown.
|
After Width: | Height: | Size: 553 B |
@@ -0,0 +1,463 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
@import '@coze-common/assets/style/common.less';
|
||||
@import '@coze-common/assets/style/mixins.less';
|
||||
|
||||
.plugin-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - 68px);
|
||||
margin: 14px 0 0 68px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 14px;
|
||||
|
||||
&:not(:last-child) {
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--light-usage-border-color-border, rgba(28, 31, 35, 8%));
|
||||
}
|
||||
|
||||
.plugin-api-main {
|
||||
flex: 1;
|
||||
|
||||
.plugin-api-name {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-api-desc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-2,
|
||||
rgba(28, 31, 35, 60%)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.api-params {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
|
||||
.params-tags {
|
||||
max-width: 500px;
|
||||
|
||||
.tag-item {
|
||||
width: fit-content;
|
||||
min-width: fit-content;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-2, rgba(28, 29, 35, 60%));
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
/* 133.333% */
|
||||
}
|
||||
|
||||
&> :not(:first-child) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.params-desc {
|
||||
cursor: pointer;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #4d53e8;
|
||||
letter-spacing: 0.12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-api-method {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-panel-header {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
height: 80px;
|
||||
|
||||
font-weight: 400;
|
||||
|
||||
.creator-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.creator-time {
|
||||
/* Paragraph/small/EN-Regular */
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 29, 35, 35%));
|
||||
text-align: right;
|
||||
|
||||
/* 133.333% */
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.header-main {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
margin: 0 16px;
|
||||
|
||||
// margin-top: -12px;
|
||||
.header-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23) !important;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
|
||||
.semi-highlight-tag {
|
||||
color: #fda633;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.market-link-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--light-usage-text-color-text-1,
|
||||
rgba(28, 29, 35, 80%)) !important;
|
||||
letter-spacing: 0.12px;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-info {
|
||||
/* Paragraph/small/EN-Regular */
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 29, 35, 35%));
|
||||
text-align: right;
|
||||
|
||||
/* 133.333% */
|
||||
:global {
|
||||
.semi-divider-vertical {
|
||||
height: 10px;
|
||||
color: var(--light-usage-border-color-border-1, rgba(28, 29, 35, 12%));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-content {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.loading-more {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plugin-content-filter {
|
||||
display: flex;
|
||||
padding: 0 36px;
|
||||
padding-left: 22px;
|
||||
|
||||
.plugin-content-sort {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.bot-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tabs-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-tab-button.semi-tabs-tab-active {
|
||||
color: var(--light-usage-text-color-text-1, rgba(28, 29, 35, 80%));
|
||||
background-color: transparent;
|
||||
|
||||
.semi-icon {
|
||||
.common-svg-icon(20px, rgba(28, 29, 35, 0.8));
|
||||
}
|
||||
}
|
||||
|
||||
.semi-tabs-tab-single.semi-tabs-tab-active .semi-icon:not(.semi-icon-checkbox_tick,
|
||||
.semi-icon-radio,
|
||||
.semi-icon-checkbox_indeterminate) {
|
||||
top: 0;
|
||||
color: var(--light-usage-text-color-text-1, rgba(28, 29, 35, 80%));
|
||||
}
|
||||
|
||||
.semi-tabs-tab-single.semi-tabs-tab .semi-icon:not(.semi-icon-checkbox_tick,
|
||||
.semi-icon-radio,
|
||||
.semi-icon-checkbox_indeterminate) {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-tab:last-child::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: -4px;
|
||||
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
|
||||
background-color: var(--light-usage-border-color-border,
|
||||
rgba(28, 29, 35, 12%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-collapse {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-collapse-item {
|
||||
position: relative;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.semi-collapse-header:hover::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
|
||||
background-color: #f7f7fa;
|
||||
}
|
||||
|
||||
.semi-collapse-header {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
border-radius: 0;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-usage-fill-color-fill-0,
|
||||
rgba(46, 47, 56, 5%));
|
||||
border-bottom: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--light-usage-fill-color-fill-0,
|
||||
rgba(46, 47, 56, 5%));
|
||||
}
|
||||
}
|
||||
|
||||
.semi-collapse-header-icon {
|
||||
width: auto;
|
||||
height: 24px;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-usage-fill-color-fill-1,
|
||||
rgba(46, 47, 56, 9%));
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.semi-collapse-header) {
|
||||
&:hover {
|
||||
.market-link-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
.common-svg-icon(16px, rgba(28, 29, 35, 0.35));
|
||||
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.activePanel {
|
||||
margin-bottom: 8px;
|
||||
background: var(--light-usage-fill-color-fill-0, rgba(46, 47, 56, 5%));
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
.semi-collapse-header {
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
}
|
||||
|
||||
.semi-collapse-header:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-content,
|
||||
.plugin-collapse {
|
||||
:global {
|
||||
.semi-collapse-header {
|
||||
min-width: 870px;
|
||||
height: 140px !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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.operator-btn {
|
||||
width: 98px;
|
||||
|
||||
&.added {
|
||||
color: var(--light-usage-primary-color-primary-disabled, #b4baf6);
|
||||
background: var(--light-usage-bg-color-bg-0, #fff);
|
||||
border: 1px solid var(--light-usage-disabled-color-disabled-border, #f0f0f5);
|
||||
}
|
||||
|
||||
&.addedMouseIn {
|
||||
color: var(--light-color-red-red-5, #ff441e);
|
||||
background: #fff;
|
||||
border: 1px solid var(--light-usage-border-color-border-1, rgba(29, 28, 35, 12%));
|
||||
}
|
||||
}
|
||||
|
||||
.workflow_count_span {
|
||||
display: inline-block;
|
||||
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 6px;
|
||||
|
||||
font-size: 10px;
|
||||
line-height: 17px;
|
||||
color: #fff;
|
||||
vertical-align: 1px;
|
||||
|
||||
background-color: rgba(77, 83, 232, 100%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.store-plugin-tools {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 29, 35, 35%));
|
||||
}
|
||||
|
||||
.plugin-total {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 29, 35, 35%));
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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 { type FC, useRef, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { sortBy } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useUpdateEffect } from 'ahooks';
|
||||
import { IconChevronDown, IconChevronRight } from '@douyinfe/semi-icons';
|
||||
import { useWorkflowStore } from '@coze-workflow/base/store';
|
||||
import { InfiniteList, type InfiniteListRef } from '@coze-community/components';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UICompositionModalMain, Collapse } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type PluginApi,
|
||||
type PluginInfoForPlayground,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import {
|
||||
fetchPlugin,
|
||||
formatCacheKey,
|
||||
type PluginContentListItem,
|
||||
type PluginQuery,
|
||||
type PluginModalModeProps,
|
||||
} from '@coze-agent-ide/plugin-shared';
|
||||
import { PluginPanel } from '@coze-agent-ide/plugin-modal-adapter';
|
||||
|
||||
import { useInfiniteScrollCacheLoad } from '../use-request-cache';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface PluginModalContentProps extends PluginModalModeProps {
|
||||
query: PluginQuery;
|
||||
pluginApiList: PluginApi[];
|
||||
onPluginApiListChange: (list: PluginApi[]) => void;
|
||||
setQuery: (value: Partial<PluginQuery>, refreshPage?: boolean) => void;
|
||||
}
|
||||
export type PluginModalContentListItem = PluginInfoForPlayground & {
|
||||
// 当前数据属于列表的第几页
|
||||
belong_page?: number;
|
||||
};
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
// eslint-disable-next-line max-params
|
||||
const getEmptyConf = (spaceId, isMine, isTeam, isProject) => {
|
||||
if ((isMine || isTeam) && !isProject) {
|
||||
return {
|
||||
text: {
|
||||
emptyTitle: I18n.t('plugin_empty_desc'),
|
||||
emptyDesc: I18n.t('plugin_empty_description'),
|
||||
},
|
||||
btn: {
|
||||
emptyClick: () => {
|
||||
window.open(`/space/${spaceId}/library?type=1`);
|
||||
},
|
||||
emptyText: I18n.t('plugin_create'),
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
text: {
|
||||
emptyTitle: I18n.t('plugin_empty_desc'),
|
||||
emptyDesc: '',
|
||||
},
|
||||
btn: {
|
||||
emptyClick: () => {
|
||||
window.open('/store/plugin');
|
||||
},
|
||||
emptyText: I18n.t('mkl_plugin_to_plugin_gallery'),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
export const PluginModalContent: FC<PluginModalContentProps> = ({
|
||||
query,
|
||||
pluginApiList,
|
||||
onPluginApiListChange,
|
||||
openMode,
|
||||
from,
|
||||
openModeCallback,
|
||||
showButton,
|
||||
showCopyPlugin,
|
||||
onCopyPluginCallback,
|
||||
clickProjectPluginCallback,
|
||||
}) => {
|
||||
// 状态hook
|
||||
const {
|
||||
type,
|
||||
mineActive,
|
||||
search,
|
||||
isOfficial,
|
||||
orderBy,
|
||||
orderByPublic,
|
||||
orderByFavorite,
|
||||
agentId,
|
||||
pluginType,
|
||||
} = query;
|
||||
const id = useSpaceStore(store => store.space.id);
|
||||
// scroll的container
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
// 当前active的key
|
||||
const [activeKey, setActivekey] = useState<string | string[] | undefined>([]);
|
||||
const refInfiniteScroll = useRef<InfiniteListRef>(null);
|
||||
const {
|
||||
scroll2Top,
|
||||
loadData,
|
||||
isSearching,
|
||||
isFavorite,
|
||||
isTemplate,
|
||||
isProject,
|
||||
isMine,
|
||||
isTeam,
|
||||
} = useInfiniteScrollCacheLoad<PluginContentListItem, PluginQuery>({
|
||||
query,
|
||||
formatCacheKey,
|
||||
scrollContainer: scrollContainerRef,
|
||||
triggerService: fetchPlugin,
|
||||
onSetScrollData: scrollData => {
|
||||
refInfiniteScroll.current?.mutate(scrollData);
|
||||
},
|
||||
});
|
||||
const { nodes: workflowNodes } = useWorkflowStore(
|
||||
useShallow(state => ({
|
||||
nodes: state.nodes,
|
||||
})),
|
||||
);
|
||||
// 首次effect不执行,这个是切换状态的effect
|
||||
useUpdateEffect(() => {
|
||||
scroll2Top(); // 当筛选项改变时,回到顶部
|
||||
// 只要是query中非page改变,就执行此effect
|
||||
}, []);
|
||||
return (
|
||||
<UICompositionModalMain>
|
||||
<div className={s['plugin-content']} ref={scrollContainerRef}>
|
||||
<UICompositionModalMain.Content
|
||||
style={{ minHeight: '100%', display: 'flex' }}
|
||||
>
|
||||
<Collapse
|
||||
className={s['plugin-collapse']}
|
||||
activeKey={activeKey}
|
||||
onChange={value => {
|
||||
setActivekey(value);
|
||||
}}
|
||||
expandIcon={
|
||||
<IconChevronRight
|
||||
className={s['collapse-icon']}
|
||||
data-testid="plugin-collapse-panel-expand"
|
||||
/>
|
||||
}
|
||||
collapseIcon={
|
||||
<IconChevronDown
|
||||
className={s['collapse-icon']}
|
||||
data-testid="plugin-collapse-panel-collapse"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<InfiniteList<PluginContentListItem>
|
||||
ref={refInfiniteScroll}
|
||||
itemClassName={s['item-container']}
|
||||
renderItem={(item, index) => {
|
||||
const pluginId = item?.pluginInfo?.id;
|
||||
return (
|
||||
<PluginPanel
|
||||
agentId={agentId}
|
||||
index={index}
|
||||
pluginApiList={pluginApiList}
|
||||
onPluginApiListChange={onPluginApiListChange}
|
||||
onCopyPluginCallback={onCopyPluginCallback}
|
||||
showButton={showButton}
|
||||
showCopyPlugin={showCopyPlugin}
|
||||
openMode={openMode}
|
||||
from={from}
|
||||
workflowNodes={workflowNodes}
|
||||
openModeCallback={openModeCallback}
|
||||
highlightWords={[search]}
|
||||
showCreator={true}
|
||||
showMarketLink={isFavorite || isTemplate}
|
||||
showCreateTime={orderBy === 0 || typeof type === 'number'}
|
||||
showPublishTime={!isMine && !isTeam && !isProject}
|
||||
activeKey={activeKey}
|
||||
scrollContainerRef={scrollContainerRef}
|
||||
isFromMarket={item?.isFromMarket}
|
||||
info={{
|
||||
...item?.pluginInfo,
|
||||
id: pluginId,
|
||||
listed_at: item?.productInfo?.listed_at,
|
||||
plugin_apis: sortBy(
|
||||
item?.pluginInfo?.plugin_apis,
|
||||
p => p.name,
|
||||
),
|
||||
}}
|
||||
productInfo={item?.productInfo}
|
||||
commercialSetting={item?.commercial_setting}
|
||||
key={pluginId}
|
||||
type={String(type || '')}
|
||||
className={classNames(s['plugin-collapse'], {
|
||||
[s.activePanel]: activeKey?.includes(pluginId ?? ''),
|
||||
})}
|
||||
showProjectPluginLink={isProject}
|
||||
clickProjectPluginCallback={clickProjectPluginCallback}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
emptyConf={getEmptyConf(id, isMine, isTeam, isProject)}
|
||||
scrollConf={{
|
||||
reloadDeps: [
|
||||
type,
|
||||
mineActive,
|
||||
search,
|
||||
isOfficial,
|
||||
orderBy,
|
||||
orderByPublic,
|
||||
orderByFavorite,
|
||||
pluginType,
|
||||
],
|
||||
targetRef: scrollContainerRef,
|
||||
loadData,
|
||||
}}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
</Collapse>
|
||||
</UICompositionModalMain.Content>
|
||||
</div>
|
||||
</UICompositionModalMain>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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 { useShallow } from 'zustand/react/shallow';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { useCollaborationStore } from '@coze-studio/bot-detail-store/collaboration';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import {
|
||||
PluginType,
|
||||
ProductEntityType,
|
||||
SortType,
|
||||
} from '@coze-arch/bot-api/product_api';
|
||||
import {
|
||||
OrderBy,
|
||||
SpaceType,
|
||||
type PluginApi,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
DEFAULT_PAGE,
|
||||
MineActiveEnum,
|
||||
PluginFilterType,
|
||||
type PluginQuery,
|
||||
type PluginModalModeProps,
|
||||
From,
|
||||
} from '@coze-agent-ide/plugin-shared';
|
||||
import { PluginModalFilter } from '@coze-agent-ide/plugin-modal-adapter';
|
||||
|
||||
import { PluginModalSider } from './sider';
|
||||
import { PluginModalContent } from './content';
|
||||
|
||||
export interface UsePluginModalPartsProp extends PluginModalModeProps {
|
||||
pluginApiList: PluginApi[];
|
||||
onPluginApiListChange: (list: PluginApi[]) => void;
|
||||
agentId?: string;
|
||||
projectId?: string;
|
||||
isShowStorePlugin?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取初始化类型
|
||||
* @param from 来源
|
||||
* @param spaceType 空间类型
|
||||
* @returns 初始化类型
|
||||
*/
|
||||
const getInitType = (from?: From, spaceType?: SpaceType) => {
|
||||
// 项目workflow引用插件,默认选中项目插件
|
||||
if (from === From.ProjectWorkflow) {
|
||||
return '';
|
||||
}
|
||||
if (from !== From.ProjectIde || !spaceType || !from) {
|
||||
return '';
|
||||
}
|
||||
// projectIDE下,并且是个人空间,选中Mine
|
||||
if (spaceType === SpaceType.Personal) {
|
||||
return PluginFilterType.Mine;
|
||||
}
|
||||
// projectIDE下,并且是团队空间,选中Team
|
||||
if (spaceType === SpaceType.Team && from === From.ProjectIde) {
|
||||
return PluginFilterType.Team;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const usePluginModalParts = ({
|
||||
pluginApiList,
|
||||
onPluginApiListChange,
|
||||
agentId,
|
||||
openMode,
|
||||
from,
|
||||
openModeCallback,
|
||||
showButton,
|
||||
showCopyPlugin,
|
||||
onCopyPluginCallback,
|
||||
projectId,
|
||||
clickProjectPluginCallback,
|
||||
onCreateSuccess,
|
||||
isShowStorePlugin,
|
||||
hideCreateBtn,
|
||||
initQuery,
|
||||
}: UsePluginModalPartsProp) => {
|
||||
// 获取devId
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
const spaceType = useSpaceStore(store => store.space.space_type);
|
||||
const [query, setQuery] = useState<PluginQuery>({
|
||||
agentId,
|
||||
projectId,
|
||||
devId: userInfo?.user_id_str || '',
|
||||
search: '',
|
||||
page: DEFAULT_PAGE,
|
||||
// 项目IDE插件仅展示我的插件
|
||||
type: initQuery?.type ?? getInitType(from, spaceType),
|
||||
orderBy: OrderBy.CreateTime,
|
||||
orderByPublic: SortType.Heat,
|
||||
orderByFavorite: SortType.Newest,
|
||||
mineActive: MineActiveEnum.All,
|
||||
isOfficial: initQuery?.isOfficial ?? undefined,
|
||||
// project workflow添加插件,只展示云插件
|
||||
pluginType:
|
||||
from === From.ProjectWorkflow ? PluginType.CLoudPlugin : undefined,
|
||||
});
|
||||
const { botId } = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
botId: state.botId,
|
||||
})),
|
||||
);
|
||||
const { version } = useCollaborationStore(
|
||||
useShallow(state => ({
|
||||
version: state.baseVersion,
|
||||
})),
|
||||
);
|
||||
const updateQuery = (value: Partial<PluginQuery>, refreshPage = true) => {
|
||||
const botInfo = {
|
||||
current_entity_type: ProductEntityType.Bot,
|
||||
current_entity_id: botId,
|
||||
current_entity_version: version,
|
||||
};
|
||||
setQuery(prev => {
|
||||
if (refreshPage) {
|
||||
return {
|
||||
...prev,
|
||||
...value,
|
||||
page: DEFAULT_PAGE,
|
||||
botInfo,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
...value,
|
||||
botInfo,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const sider = (
|
||||
<PluginModalSider
|
||||
hideCreateBtn={hideCreateBtn}
|
||||
query={query}
|
||||
setQuery={updateQuery}
|
||||
from={from}
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
isShowStorePlugin={isShowStorePlugin}
|
||||
/>
|
||||
);
|
||||
const filter = (
|
||||
<PluginModalFilter from={from} query={query} setQuery={updateQuery} />
|
||||
);
|
||||
const content = (
|
||||
<PluginModalContent
|
||||
query={query}
|
||||
setQuery={updateQuery}
|
||||
pluginApiList={pluginApiList}
|
||||
onPluginApiListChange={onPluginApiListChange}
|
||||
openMode={openMode}
|
||||
from={from}
|
||||
openModeCallback={openModeCallback}
|
||||
showButton={showButton}
|
||||
showCopyPlugin={showCopyPlugin}
|
||||
onCopyPluginCallback={onCopyPluginCallback}
|
||||
clickProjectPluginCallback={clickProjectPluginCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
return {
|
||||
sider,
|
||||
content,
|
||||
filter,
|
||||
} as const;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
button.addbtn {
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 { useDebounceFn } from 'ahooks';
|
||||
import { UISearch } from '@coze-studio/components';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UIButton, UICompositionModalSider } from '@coze-arch/bot-semi';
|
||||
import { From, type PluginQuery } from '@coze-agent-ide/plugin-shared';
|
||||
import { PluginFilter } from '@coze-agent-ide/plugin-modal-adapter';
|
||||
|
||||
import { CreateFormPluginModal } from '../../bot_edit';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface PluginModalSiderProp {
|
||||
query: PluginQuery;
|
||||
setQuery: (value: Partial<PluginQuery>, refreshPage?: boolean) => void;
|
||||
from?: From;
|
||||
onCreateSuccess?: (val?: { spaceId?: string; pluginId?: string }) => void;
|
||||
isShowStorePlugin?: boolean;
|
||||
hideCreateBtn?: boolean;
|
||||
}
|
||||
const MAX_SEARCH_LENGTH = 100;
|
||||
|
||||
export const PluginModalSider: FC<PluginModalSiderProp> = ({
|
||||
query,
|
||||
setQuery,
|
||||
from,
|
||||
onCreateSuccess,
|
||||
isShowStorePlugin,
|
||||
hideCreateBtn,
|
||||
}) => {
|
||||
const [showFormPluginModel, setShowFormPluginModel] = useState(false);
|
||||
const id = useSpaceStore(item => item.space.id);
|
||||
const updateSearchQuery = (search?: string) => {
|
||||
setQuery({
|
||||
search: search ?? '',
|
||||
});
|
||||
};
|
||||
const { run: debounceChangeSearch, cancel } = useDebounceFn(
|
||||
(search: string) => {
|
||||
updateSearchQuery(search);
|
||||
},
|
||||
{ wait: 300 },
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{hideCreateBtn ? null : (
|
||||
<CreateFormPluginModal
|
||||
projectId={query.projectId}
|
||||
isCreate={true}
|
||||
visible={showFormPluginModel}
|
||||
onSuccess={pluginID => {
|
||||
onCreateSuccess?.({
|
||||
spaceId: id,
|
||||
pluginId: pluginID,
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
setShowFormPluginModel(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<UICompositionModalSider style={{ paddingTop: 16 }}>
|
||||
<UICompositionModalSider.Header>
|
||||
<UISearch
|
||||
tabIndex={-1}
|
||||
value={query.search}
|
||||
maxLength={MAX_SEARCH_LENGTH}
|
||||
onSearch={search => {
|
||||
if (!search) {
|
||||
// 如果search清空了,那么立即更新query
|
||||
cancel();
|
||||
updateSearchQuery(search);
|
||||
} else {
|
||||
// 如果search有值,那么防抖更新
|
||||
debounceChangeSearch(search);
|
||||
}
|
||||
}}
|
||||
placeholder={I18n.t('Search')}
|
||||
data-testid="plugin.modal.search"
|
||||
/>
|
||||
{hideCreateBtn ? null : (
|
||||
<UIButton
|
||||
data-testid="plugin.modal.create.plugin"
|
||||
className={s.addbtn}
|
||||
theme="solid"
|
||||
onClick={() => {
|
||||
// TODO: 其他场景应该也统一创建方式,如果创建成功回调存在,则打开插件modal,否则打开新tab
|
||||
if (
|
||||
onCreateSuccess &&
|
||||
(from === From.ProjectIde || from === From.ProjectWorkflow)
|
||||
) {
|
||||
setShowFormPluginModel(true);
|
||||
return;
|
||||
}
|
||||
window.open(`/space/${id}/library?type=1`);
|
||||
}}
|
||||
>
|
||||
{I18n.t('plugin_create')}
|
||||
</UIButton>
|
||||
)}
|
||||
</UICompositionModalSider.Header>
|
||||
<UICompositionModalSider.Content>
|
||||
<PluginFilter
|
||||
isSearching={query.search !== ''}
|
||||
type={query.type}
|
||||
onChange={type => {
|
||||
setQuery({ type });
|
||||
}}
|
||||
from={from}
|
||||
projectId={query.projectId}
|
||||
isShowStorePlugin={isShowStorePlugin}
|
||||
/>
|
||||
</UICompositionModalSider.Content>
|
||||
</UICompositionModalSider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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 MutableRefObject, useEffect } from 'react';
|
||||
|
||||
import { setCache, getCache, clearCache } from '@coze-arch/bot-utils';
|
||||
import { type InfiniteListDataProps } from '@coze-community/components';
|
||||
import {
|
||||
DEFAULT_PAGE_SIZE,
|
||||
MineActiveEnum,
|
||||
PluginFilterType,
|
||||
type CommonQuery,
|
||||
type ListItemCommon,
|
||||
type RequestServiceResp,
|
||||
} from '@coze-agent-ide/plugin-shared';
|
||||
|
||||
interface Data {
|
||||
list: object[];
|
||||
total: number;
|
||||
hasMore?: boolean;
|
||||
}
|
||||
export interface InfiniteScrollViewportOptions<
|
||||
ListItem extends ListItemCommon,
|
||||
Query extends CommonQuery,
|
||||
> {
|
||||
scrollContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
query: Query;
|
||||
triggerService: (
|
||||
query: Query,
|
||||
commonParam: {
|
||||
nextPage: number;
|
||||
isMine: boolean;
|
||||
isTeam: boolean;
|
||||
isCreatorMine: boolean;
|
||||
isTemplate: boolean;
|
||||
isFavorite: boolean;
|
||||
isProject: boolean;
|
||||
},
|
||||
) => Promise<RequestServiceResp<ListItem> | undefined>;
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
onSetScrollData: (scrollData) => void;
|
||||
formatCacheKey: (query: {
|
||||
query: Query;
|
||||
isSearching: boolean;
|
||||
isTemplate: boolean;
|
||||
page: number;
|
||||
}) => string | undefined;
|
||||
}
|
||||
|
||||
const DEFAULT_CACHE_TIME = 300000;
|
||||
|
||||
export function useInfiniteScrollCacheLoad<
|
||||
ListItem extends ListItemCommon,
|
||||
Query extends CommonQuery,
|
||||
>({
|
||||
scrollContainer,
|
||||
query,
|
||||
triggerService,
|
||||
formatCacheKey,
|
||||
onSetScrollData,
|
||||
}: InfiniteScrollViewportOptions<ListItem, Query>) {
|
||||
const { search, type, mineActive } = query;
|
||||
const isSearching = search !== '';
|
||||
// my tools
|
||||
const isMine = type === PluginFilterType.Mine;
|
||||
// team tools
|
||||
const isTeam = type === PluginFilterType.Team;
|
||||
const isFavorite = type === PluginFilterType.Favorite;
|
||||
const isProject = type === PluginFilterType.Project;
|
||||
|
||||
// team tools -> my creator
|
||||
const isCreatorMine = mineActive === MineActiveEnum.Mine;
|
||||
const isTemplate = Number(type) >= 0 || type === 'recommend';
|
||||
|
||||
const scroll2Top = () => {
|
||||
if (scrollContainer.current) {
|
||||
scrollContainer.current.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onBeforeLoadData = (
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
current,
|
||||
cachedKey: string,
|
||||
isImmediateUpdate: boolean,
|
||||
) => {
|
||||
const res = getCache(cachedKey);
|
||||
if (!cachedKey || !res) {
|
||||
return false;
|
||||
}
|
||||
const { data } = res;
|
||||
if (!isImmediateUpdate) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
const currentPage = current?.nextPage || 1;
|
||||
const { list, total } = (data as Data) || { list: [], total: 0 };
|
||||
const hasMore = total > 0 && currentPage * DEFAULT_PAGE_SIZE < total;
|
||||
onSetScrollData({
|
||||
...current,
|
||||
hasMore,
|
||||
list: [...(currentPage?.list || []), ...list],
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
const setCacheData = <TData,>(
|
||||
cachedKey: string,
|
||||
cacheTime: number,
|
||||
res: TData,
|
||||
) => {
|
||||
if (!cachedKey) {
|
||||
return;
|
||||
}
|
||||
setCache(cachedKey, cacheTime, {
|
||||
time: Date.now(),
|
||||
data: res,
|
||||
});
|
||||
};
|
||||
|
||||
const loadData = async (current: InfiniteListDataProps<ListItem>) => {
|
||||
const currentPage = current?.nextPage || 1;
|
||||
let cachedKey =
|
||||
formatCacheKey({ query, isSearching, isTemplate, page: currentPage }) ||
|
||||
'';
|
||||
if (!isMine && !isTeam) {
|
||||
cachedKey = '';
|
||||
}
|
||||
let res = onBeforeLoadData(current, cachedKey, !isTemplate);
|
||||
|
||||
if (!res) {
|
||||
res = await triggerService(query, {
|
||||
nextPage: currentPage,
|
||||
isMine,
|
||||
isTeam,
|
||||
isCreatorMine,
|
||||
isTemplate,
|
||||
isFavorite,
|
||||
isProject,
|
||||
});
|
||||
setCacheData(cachedKey, DEFAULT_CACHE_TIME, res);
|
||||
}
|
||||
|
||||
const { list, hasMore } = (res as InfiniteListDataProps<ListItem>) || {
|
||||
list: [],
|
||||
total: 0,
|
||||
};
|
||||
const nextPage = currentPage + 1;
|
||||
const refIdList = {};
|
||||
(current?.list || []).map(item => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
refIdList[
|
||||
(item as unknown as { pluginInfo: { id: string } })?.pluginInfo?.id
|
||||
] = true;
|
||||
});
|
||||
|
||||
//数据去重
|
||||
const uniqList = (list || []).filter(item => {
|
||||
const pluginId = (item as unknown as { pluginInfo: { id: string } })
|
||||
?.pluginInfo?.id;
|
||||
if (pluginId) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (refIdList[pluginId]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
list: uniqList || [],
|
||||
hasMore,
|
||||
nextPage,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
clearCache();
|
||||
}, []);
|
||||
return {
|
||||
scroll2Top,
|
||||
isSearching,
|
||||
loadData,
|
||||
isFavorite,
|
||||
isTemplate,
|
||||
isMine,
|
||||
isTeam,
|
||||
isProject,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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, type ReactNode } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { type PluginInfoProps } from '@coze-studio/plugin-shared';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UIButton, UIModal, Toast, Space } from '@coze-arch/bot-semi';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { Editor } from '../editor';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface CreatePluginProps {
|
||||
visible: boolean;
|
||||
isCreate?: boolean;
|
||||
editInfo?: PluginInfoProps;
|
||||
disabled?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (pluginId?: string) => void;
|
||||
actions?: ReactNode;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
const INDENTATION_SPACES = 2;
|
||||
const EDITOR_HEIGHT_MAX = 560;
|
||||
|
||||
export const CreateCodePluginModal: React.FC<CreatePluginProps> = props => {
|
||||
const {
|
||||
isCreate = true,
|
||||
onCancel,
|
||||
editInfo,
|
||||
visible,
|
||||
onSuccess,
|
||||
disabled = false,
|
||||
actions,
|
||||
projectId,
|
||||
} = props;
|
||||
|
||||
const [aiPlugin, setAiPlugin] = useState<string | undefined>();
|
||||
const [clientId, setClientId] = useState<string | undefined>();
|
||||
const [clientSecret, setClientSecret] = useState<string | undefined>();
|
||||
const [serviceToken, setServiceToken] = useState<string | undefined>();
|
||||
const [openApi, setOpenApi] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
/** 每次打开重置弹窗数据 */
|
||||
if (visible) {
|
||||
//格式化json
|
||||
const desc = JSON.stringify(
|
||||
safeJSONParse(editInfo?.code_info?.plugin_desc),
|
||||
null,
|
||||
INDENTATION_SPACES,
|
||||
);
|
||||
setAiPlugin(desc || '');
|
||||
setOpenApi(editInfo?.code_info?.openapi_desc || '');
|
||||
setClientId(editInfo?.code_info?.client_id);
|
||||
setClientSecret(editInfo?.code_info?.client_secret);
|
||||
setServiceToken(editInfo?.code_info?.service_token);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const registerPlugin = async () => {
|
||||
const params = {
|
||||
ai_plugin: aiPlugin,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
service_token: serviceToken,
|
||||
openapi: openApi,
|
||||
};
|
||||
let res;
|
||||
if (isCreate) {
|
||||
res = await PluginDevelopApi.RegisterPlugin({
|
||||
...params,
|
||||
project_id: projectId,
|
||||
space_id: useSpaceStore.getState().getSpaceId(),
|
||||
});
|
||||
} else {
|
||||
await PluginDevelopApi.UpdatePlugin({
|
||||
...params,
|
||||
plugin_id: editInfo?.plugin_id,
|
||||
edit_version: editInfo?.edit_version,
|
||||
});
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content: isCreate
|
||||
? I18n.t('register_success')
|
||||
: I18n.t('Plugin_update_success'),
|
||||
showClose: false,
|
||||
});
|
||||
onSuccess?.(res?.data?.plugin_id);
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<UIModal
|
||||
fullScreen
|
||||
className="full-screen-modal"
|
||||
title={
|
||||
<div className={s['bot-code-edit-title-action']}>
|
||||
<span>
|
||||
{isCreate ? I18n.t('plugin_create') : I18n.t('plugin_Update')}
|
||||
</span>
|
||||
<div>{actions}</div>
|
||||
</div>
|
||||
}
|
||||
visible={visible}
|
||||
onCancel={() => onCancel?.()}
|
||||
footer={
|
||||
!disabled ? (
|
||||
<Space>
|
||||
<UIButton type="tertiary" onClick={() => onCancel?.()}>
|
||||
{I18n.t('Cancel')}
|
||||
</UIButton>
|
||||
<UIButton type="primary" onClick={registerPlugin}>
|
||||
{I18n.t('Confirm')}
|
||||
</UIButton>
|
||||
</Space>
|
||||
) : null
|
||||
}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className={classNames(s.flex)}>
|
||||
<div className={classNames(s['plugin-height'], s.flex5)}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ flex: 1, borderRight: '1px solid rgb(215,218,221)' }}>
|
||||
<div className={s.title}>
|
||||
{I18n.t('ai_plugin_(fill_in_json)_*')}
|
||||
</div>
|
||||
<Editor
|
||||
dataTestID="create-plugin-code-editor-json"
|
||||
disabled={disabled}
|
||||
theme="tomorrow"
|
||||
mode="json"
|
||||
height={EDITOR_HEIGHT_MAX}
|
||||
value={aiPlugin}
|
||||
useValidate={false}
|
||||
onChange={e => setAiPlugin(e)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className={s.title}>
|
||||
{I18n.t('openapi_(fill_in_yaml)_*')}
|
||||
</div>
|
||||
<Editor
|
||||
dataTestID="create-plugin-code-editor-yaml"
|
||||
disabled={disabled}
|
||||
theme="tomorrow"
|
||||
mode="yaml"
|
||||
height={EDITOR_HEIGHT_MAX}
|
||||
value={openApi}
|
||||
useValidate={false}
|
||||
onChange={e => setOpenApi(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UIModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
import { IconCodeOutlined } from '@coze-arch/bot-icons';
|
||||
|
||||
import { CreateCodePluginModal } from '../bot-code-edit';
|
||||
|
||||
export const CodeModal: FC<{
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (pluginId?: string) => void;
|
||||
projectId?: string;
|
||||
}> = ({ onCancel, onSuccess, projectId }) => {
|
||||
const [showCodePluginModel, setShowCodePluginModel] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<CreateCodePluginModal
|
||||
isCreate={true}
|
||||
visible={showCodePluginModel}
|
||||
onSuccess={pluginId => {
|
||||
onSuccess?.(pluginId);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setShowCodePluginModel(false);
|
||||
}}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<Button
|
||||
data-testid="create-plugin-code-modal-button"
|
||||
color="primary"
|
||||
icon={<IconCodeOutlined />}
|
||||
onClick={() => {
|
||||
setShowCodePluginModel(true);
|
||||
onCancel?.();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { ImportPluginModal } from '../../file-import';
|
||||
|
||||
export const ImportModal: FC<{
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (pluginID?: string) => void;
|
||||
projectId?: string;
|
||||
}> = ({ onCancel, onSuccess, projectId }) => {
|
||||
const [showFileImportPluginModel, setShowFileImportPluginModel] =
|
||||
useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImportPluginModal
|
||||
projectId={projectId}
|
||||
visible={showFileImportPluginModel}
|
||||
onSuccess={d => {
|
||||
const pluginId = d?.plugin_id;
|
||||
if (pluginId) {
|
||||
onSuccess?.(pluginId);
|
||||
} else {
|
||||
onSuccess?.();
|
||||
}
|
||||
}}
|
||||
onCancel={() => setShowFileImportPluginModel(false)}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setShowFileImportPluginModel(true);
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
{I18n.t('import')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* 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, useMemo, useState, useEffect } from 'react';
|
||||
|
||||
import { type PluginInfoProps } from '@coze-studio/plugin-shared';
|
||||
import {
|
||||
PluginForm,
|
||||
usePluginFormState,
|
||||
convertPluginMetaParams,
|
||||
registerPluginMeta,
|
||||
updatePluginMeta,
|
||||
} from '@coze-studio/plugin-form-adapter';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import {
|
||||
type CreationMethod,
|
||||
type PluginType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { ERROR_CODE } from '@coze-agent-ide/bot-plugin-tools/pluginModal/types';
|
||||
import { IconCozInfoCircleFill } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Modal,
|
||||
Space,
|
||||
Toast,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import s from '../index.module.less';
|
||||
import { PluginDocs } from '../../plugin-docs';
|
||||
import { ImportModal } from './import-modal';
|
||||
import { CodeModal } from './code-modal';
|
||||
|
||||
export interface CreatePluginFormProps {
|
||||
visible: boolean;
|
||||
isCreate?: boolean;
|
||||
editInfo?: PluginInfoProps;
|
||||
disabled?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (pluginID?: string) => Promise<void> | void;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
export const CreateFormPluginModal: FC<CreatePluginFormProps> = props => {
|
||||
const {
|
||||
onCancel,
|
||||
editInfo,
|
||||
isCreate = true,
|
||||
visible,
|
||||
onSuccess,
|
||||
disabled = false,
|
||||
projectId,
|
||||
} = props;
|
||||
|
||||
const { id } = useSpaceStore(store => store.space);
|
||||
const modalTitle = useMemo(() => {
|
||||
if (isCreate) {
|
||||
return (
|
||||
<div className="w-full flex justify-between items-center pr-[8px]">
|
||||
<div>{I18n.t('create_plugin_modal_title1')}</div>
|
||||
<Space>
|
||||
<CodeModal
|
||||
onCancel={onCancel}
|
||||
onSuccess={onSuccess}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<ImportModal
|
||||
onCancel={onCancel}
|
||||
onSuccess={onSuccess}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<Divider layout="vertical" className="h-5" />
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (disabled) {
|
||||
return I18n.t('plugin_detail_view_modal_title');
|
||||
}
|
||||
return I18n.t('plugin_detail_edit_modal_title');
|
||||
}, [isCreate, disabled]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const pluginState = usePluginFormState();
|
||||
|
||||
const {
|
||||
formApi,
|
||||
extItems,
|
||||
headerList,
|
||||
isValidCheckResult,
|
||||
setIsValidCheckResult,
|
||||
pluginTypeCreationMethod,
|
||||
defaultRuntime,
|
||||
} = pluginState;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCreate) {
|
||||
return;
|
||||
}
|
||||
if (visible) {
|
||||
// 显示后滚动条滑动到最上边
|
||||
const modalContent = document.querySelector(
|
||||
'.create-plugin-modal-content .semi-modal-body',
|
||||
);
|
||||
if (modalContent) {
|
||||
modalContent.scrollTop = 0;
|
||||
}
|
||||
} else {
|
||||
// 隐藏后重置表单
|
||||
formApi?.current?.reset();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const confirmBtn = async () => {
|
||||
await formApi.current?.validate();
|
||||
const type = isCreate ? 'create' : 'edit';
|
||||
const val = formApi.current?.getValues();
|
||||
if (!val || !pluginTypeCreationMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
const json: Record<string, string> = {};
|
||||
extItems?.forEach(item => {
|
||||
if (item.key in val) {
|
||||
json[item.key] = val[item.key];
|
||||
}
|
||||
});
|
||||
|
||||
const [pluginType, creationMethod] = pluginTypeCreationMethod.split('-');
|
||||
|
||||
const params = convertPluginMetaParams({
|
||||
val,
|
||||
spaceId: String(id),
|
||||
headerList,
|
||||
projectId,
|
||||
creationMethod: Number(creationMethod) as unknown as CreationMethod,
|
||||
defaultRuntime,
|
||||
pluginType: Number(pluginType) as unknown as PluginType,
|
||||
extItemsJSON: json,
|
||||
});
|
||||
const action = {
|
||||
create: () => registerPluginMeta({ params }),
|
||||
edit: () => updatePluginMeta({ params, editInfo }),
|
||||
};
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const pluginID = await action[type]();
|
||||
Toast.success({
|
||||
content: isCreate
|
||||
? I18n.t('Plugin_new_toast_success')
|
||||
: I18n.t('Plugin_update_toast_success'),
|
||||
showClose: false,
|
||||
});
|
||||
onCancel?.();
|
||||
onSuccess?.(pluginID);
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code, msg } = error;
|
||||
if (Number(code) === ERROR_CODE.SAFE_CHECK) {
|
||||
setIsValidCheckResult(false);
|
||||
} else {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title={modalTitle}
|
||||
className="[&_.semi-modal-header]:items-center"
|
||||
visible={visible}
|
||||
keepDOM={isCreate}
|
||||
onCancel={() => onCancel?.()}
|
||||
modalContentClass="create-plugin-modal-content"
|
||||
footer={
|
||||
!disabled && (
|
||||
<div>
|
||||
{!isValidCheckResult && (
|
||||
<div className={s['error-msg-box']}>
|
||||
<span className={s['error-msg']}>
|
||||
{I18n.t('plugin_create_modal_safe_error')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Typography.Paragraph
|
||||
type="secondary"
|
||||
fontSize="12px"
|
||||
className="text-start mb-[16px]"
|
||||
>
|
||||
<IconCozInfoCircleFill className="coz-fg-hglt text-[14px] align-sub" />
|
||||
<span className="mx-[4px]">
|
||||
{I18n.t('plugin_create_draft_desc')}
|
||||
</span>
|
||||
<PluginDocs />
|
||||
</Typography.Paragraph>
|
||||
<div>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
{I18n.t('create_plugin_modal_button_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
confirmBtn();
|
||||
}}
|
||||
>
|
||||
{I18n.t('create_plugin_modal_button_confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<PluginForm
|
||||
pluginState={pluginState}
|
||||
visible={visible}
|
||||
isCreate={isCreate}
|
||||
disabled={disabled}
|
||||
editInfo={editInfo}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,371 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
.card {
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
min-width: 248px;
|
||||
height: 172px;
|
||||
|
||||
background-color: white !important;
|
||||
border-radius: 8px;
|
||||
|
||||
transition: box-shadow 0.4s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 20px 0 rgb(31 35 41 / 4%),
|
||||
0 4px 10px 0 rgb(31 35 41 / 4%),
|
||||
0 2px 5px 0 rgb(31 35 41 / 4%);
|
||||
}
|
||||
}
|
||||
|
||||
.card-favorite-not-publish {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--light-usage-fill-color-fill-0,
|
||||
rgb(46 50 56 / 5%)) !important;
|
||||
}
|
||||
|
||||
.add-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.add-card-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.name-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 16px 16px 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%px;
|
||||
}
|
||||
|
||||
.name {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 48px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
max-width: calc(100% - 156px);
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #000;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extra {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 40px);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.description {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #494c4f;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.recent-modify {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 31 35 / 60%);
|
||||
}
|
||||
|
||||
.creator {
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #346ef8;
|
||||
|
||||
background: rgb(51 112 255 / 10%);
|
||||
border: none !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.upload-field {
|
||||
padding-top: 0;
|
||||
|
||||
:global {
|
||||
.semi-form-field-help-text {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-single-line {
|
||||
:global {
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-multi-line {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:global {
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -20px;
|
||||
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-draft {
|
||||
align-items: flex-start;
|
||||
|
||||
padding-top: 16px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--coz-fg-secondary);
|
||||
|
||||
.link {
|
||||
font-weight: 400;
|
||||
color: var(--coz-fg-hglt);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-icon {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-form-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
display: none !important;
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-form-item {
|
||||
:global {
|
||||
.semi-form-field-label-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collect-num {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 4px;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.extinfo {
|
||||
max-width: 338px;
|
||||
font-size: 12px;
|
||||
|
||||
.extinfo-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.extinfo-text {
|
||||
color: rgb(28 31 35 / 60%);
|
||||
}
|
||||
|
||||
.extinfo-ex {
|
||||
margin-top: 4px;
|
||||
padding: 6px 10px;
|
||||
color: rgb(28 31 35 / 60%);
|
||||
border: 1px solid rgb(28 31 35 / 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.upload-avatar {
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
|
||||
background: #fff !important;
|
||||
border-radius: var(--spacing-tight, 8px) !important;
|
||||
}
|
||||
|
||||
.header-list {
|
||||
:global {
|
||||
.semi-form-field-label-with-extra {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.semi-form-field-label-extra {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.header-list-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-list-box {
|
||||
overflow: auto;
|
||||
max-height: 348px;
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-radius: 8px;
|
||||
|
||||
.header-row {
|
||||
border-bottom: 1px solid var(--coz-stroke-primary);
|
||||
}
|
||||
|
||||
.header-col-content {
|
||||
padding: 6px 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--coz-fg-secondary);
|
||||
}
|
||||
|
||||
.col-content {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-msg-box {
|
||||
position: relative;
|
||||
top: -24px;
|
||||
|
||||
.error-msg {
|
||||
display: block;
|
||||
|
||||
padding: 8px 16px;
|
||||
|
||||
line-height: 16px;
|
||||
color: #F93920;
|
||||
text-align: left;
|
||||
|
||||
.link {
|
||||
font-weight: 400;
|
||||
color: #4D53E8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.creation-method {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 0 !important;
|
||||
|
||||
:global {
|
||||
.semi-radio {
|
||||
padding: 8px 12px;
|
||||
background-color: var(--coz-mg-card);
|
||||
border: solid 1px var(--coz-stroke-plus);
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--coz-mg-secondary-hovered);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--coz-mg-secondary-pressed);
|
||||
}
|
||||
}
|
||||
|
||||
.semi-radio-inner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-radio-addon {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.semi-radio-checked {
|
||||
background: var(--coz-mg-hglt);
|
||||
border: 1px solid var(--coz-stroke-hglt);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--coz-mg-hglt-hovered);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--coz-mg-hglt-pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-runtime-list {
|
||||
:global {
|
||||
.semi-select-option-selected .semi-select-option-icon {
|
||||
color: #4d53e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-code-edit-title-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -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 { CreateFormPluginModal } from './bot-form-edit';
|
||||
export { CreateCodePluginModal } from './bot-code-edit';
|
||||
|
||||
export {
|
||||
useBotCodeEditInPlugin,
|
||||
useBotFormEditInPlugin,
|
||||
useImportToolInPlugin,
|
||||
useBotCodeEditOutPlugin,
|
||||
} from './plugin-edit';
|
||||
@@ -0,0 +1,3 @@
|
||||
.actions {
|
||||
margin-right: 20px;
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIButton } from '@coze-arch/bot-semi';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { type PluginInfoProps } from '@coze-studio/plugin-shared';
|
||||
import {
|
||||
checkOutPluginContext,
|
||||
unlockOutPluginContext,
|
||||
usePluginStore,
|
||||
} from '@coze-studio/bot-plugin-store';
|
||||
|
||||
import {
|
||||
CreateFormPluginModal,
|
||||
type CreatePluginFormProps,
|
||||
} from '../bot-form-edit';
|
||||
import {
|
||||
CreateCodePluginModal,
|
||||
type CreatePluginProps,
|
||||
} from '../bot-code-edit';
|
||||
import { ImportToolModal, type ImportToolModalProps } from '../../file-import';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const useBotCodeEditInPlugin = ({
|
||||
modalProps,
|
||||
}: {
|
||||
modalProps: Pick<CreatePluginProps, 'onSuccess'>;
|
||||
}) => {
|
||||
const { pluginInfo, canEdit, unlockPlugin, wrapWithCheckLock } =
|
||||
usePluginStore(
|
||||
useShallow(store => ({
|
||||
pluginInfo: store.pluginInfo,
|
||||
canEdit: store.canEdit,
|
||||
unlockPlugin: store.unlockPlugin,
|
||||
wrapWithCheckLock: store.wrapWithCheckLock,
|
||||
})),
|
||||
);
|
||||
const [showCodePluginModel, setShowCodePluginModel] = useState(false);
|
||||
const [editable, setEditable] = useState(false);
|
||||
|
||||
const action = useMemo(() => {
|
||||
if (!canEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
{editable ? (
|
||||
<UIButton
|
||||
onClick={() => {
|
||||
setEditable(false);
|
||||
unlockPlugin();
|
||||
}}
|
||||
>
|
||||
{I18n.t('Cancel')}
|
||||
</UIButton>
|
||||
) : (
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={wrapWithCheckLock(() => setEditable(true))}
|
||||
>
|
||||
{I18n.t('Edit')}
|
||||
</UIButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [editable, canEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showCodePluginModel) {
|
||||
setEditable(false);
|
||||
}
|
||||
}, [showCodePluginModel]);
|
||||
|
||||
const modal = (
|
||||
<CreateCodePluginModal
|
||||
{...modalProps}
|
||||
isCreate={false}
|
||||
visible={showCodePluginModel}
|
||||
onCancel={() => {
|
||||
setShowCodePluginModel(false);
|
||||
unlockPlugin();
|
||||
}}
|
||||
disabled={!editable || !canEdit}
|
||||
editInfo={pluginInfo}
|
||||
actions={action}
|
||||
/>
|
||||
);
|
||||
|
||||
return { modal, setShowCodePluginModel };
|
||||
};
|
||||
|
||||
export const useBotCodeEditOutPlugin = ({
|
||||
modalProps,
|
||||
}: {
|
||||
modalProps: Pick<CreatePluginProps, 'onSuccess'>;
|
||||
}) => {
|
||||
const [pluginInfo, setPluginInfo] = useState<PluginInfoProps>({});
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [editable, setEditable] = useState(false);
|
||||
const [disableEdit, setDisableEdit] = useState(false);
|
||||
const pluginId = pluginInfo?.plugin_id || '';
|
||||
|
||||
const action = useMemo(() => {
|
||||
if (disableEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
{editable ? (
|
||||
<UIButton
|
||||
onClick={() => {
|
||||
setEditable(false);
|
||||
unlockOutPluginContext(pluginId);
|
||||
}}
|
||||
>
|
||||
{I18n.t('Cancel')}
|
||||
</UIButton>
|
||||
) : (
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={async () => {
|
||||
const isLocked = await checkOutPluginContext(pluginId);
|
||||
|
||||
if (isLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEditable(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('Edit')}
|
||||
</UIButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [editable, pluginId, disableEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalVisible) {
|
||||
setEditable(false);
|
||||
}
|
||||
}, [modalVisible]);
|
||||
|
||||
const modal = (
|
||||
<CreateCodePluginModal
|
||||
{...modalProps}
|
||||
editInfo={pluginInfo}
|
||||
isCreate={false}
|
||||
visible={modalVisible}
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
if (!disableEdit) {
|
||||
unlockOutPluginContext(pluginId);
|
||||
}
|
||||
}}
|
||||
disabled={!editable}
|
||||
actions={action}
|
||||
/>
|
||||
);
|
||||
|
||||
const open = useCallback(async (id: string, disable: boolean) => {
|
||||
const res = await PluginDevelopApi.GetPluginInfo({
|
||||
plugin_id: id || '',
|
||||
});
|
||||
setPluginInfo({
|
||||
plugin_id: id,
|
||||
code_info: {
|
||||
plugin_desc: res.code_info?.plugin_desc,
|
||||
/** yaml */
|
||||
openapi_desc: res.code_info?.openapi_desc,
|
||||
client_id: res.code_info?.client_id,
|
||||
client_secret: res.code_info?.client_secret,
|
||||
service_token: res.code_info?.service_token,
|
||||
},
|
||||
});
|
||||
setDisableEdit(disable);
|
||||
setModalVisible(true);
|
||||
}, []);
|
||||
|
||||
return { modal, open };
|
||||
};
|
||||
|
||||
export const useBotFormEditInPlugin = ({
|
||||
modalProps,
|
||||
}: {
|
||||
modalProps: Pick<CreatePluginFormProps, 'onSuccess'>;
|
||||
}) => {
|
||||
const { pluginInfo, canEdit, unlockPlugin } = usePluginStore(store => ({
|
||||
pluginInfo: store.pluginInfo,
|
||||
canEdit: store.canEdit,
|
||||
unlockPlugin: store.unlockPlugin,
|
||||
}));
|
||||
const [showFormPluginModel, setShowFormPluginModel] = useState(false);
|
||||
|
||||
const modal = (
|
||||
<CreateFormPluginModal
|
||||
{...modalProps}
|
||||
isCreate={false}
|
||||
visible={showFormPluginModel}
|
||||
editInfo={pluginInfo}
|
||||
onCancel={() => {
|
||||
unlockPlugin();
|
||||
setShowFormPluginModel(false);
|
||||
}}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
);
|
||||
|
||||
return {
|
||||
modal,
|
||||
setShowFormPluginModel,
|
||||
};
|
||||
};
|
||||
|
||||
export const useImportToolInPlugin = ({
|
||||
modalProps,
|
||||
}: {
|
||||
modalProps: Pick<ImportToolModalProps, 'onSuccess'>;
|
||||
}) => {
|
||||
const { pluginInfo, unlockPlugin } = usePluginStore(store => ({
|
||||
pluginInfo: store.pluginInfo,
|
||||
unlockPlugin: store.unlockPlugin,
|
||||
}));
|
||||
const [showImportToolModal, setShowImportToolModal] = useState(false);
|
||||
|
||||
const modal = (
|
||||
<ImportToolModal
|
||||
{...modalProps}
|
||||
pluginInfo={{
|
||||
pluginID: pluginInfo?.plugin_id,
|
||||
pluginName: pluginInfo?.meta_info?.name,
|
||||
pluginUrl: pluginInfo?.meta_info?.url,
|
||||
pluginDesc: pluginInfo?.meta_info?.desc,
|
||||
editVersion: pluginInfo?.edit_version,
|
||||
}}
|
||||
visible={showImportToolModal}
|
||||
onCancel={() => {
|
||||
unlockPlugin();
|
||||
setShowImportToolModal(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return {
|
||||
modal,
|
||||
setShowImportToolModal,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Editor as MonacoEditor } from '@coze-arch/bot-monaco-editor';
|
||||
|
||||
interface EditorPros {
|
||||
mode: 'yaml' | 'json' | 'javascript';
|
||||
value?: string;
|
||||
onChange?: (v: string | undefined) => void;
|
||||
height?: number | string;
|
||||
useValidate?: boolean;
|
||||
theme?: string;
|
||||
disabled?: boolean;
|
||||
dataTestID?: string;
|
||||
}
|
||||
|
||||
export const Editor: React.FC<EditorPros> = ({
|
||||
mode,
|
||||
value,
|
||||
onChange,
|
||||
height = 500,
|
||||
theme = 'monokai',
|
||||
disabled = false,
|
||||
dataTestID,
|
||||
}) => {
|
||||
const [heightVal, setHeightVal] = useState(height);
|
||||
useEffect(() => {
|
||||
setHeightVal(height);
|
||||
}, [height]);
|
||||
return (
|
||||
<div style={{ position: 'relative' }} data-testid={dataTestID}>
|
||||
<MonacoEditor
|
||||
options={{ readOnly: disabled }}
|
||||
language={mode}
|
||||
theme={theme}
|
||||
width="100%"
|
||||
onChange={onChange}
|
||||
height={heightVal}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 { Editor } from './editor';
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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('-');
|
||||
}
|
||||
|
||||
// error code
|
||||
export const ERROR_CODE = {
|
||||
SAFE_CHECK: 720092020,
|
||||
DUP_NAME_URL: 702093022,
|
||||
DUP_NAME: 702092010,
|
||||
DUP_PATH: 702093021,
|
||||
};
|
||||
|
||||
export const ACCEPT_FORMAT = ['json', 'yaml'];
|
||||
|
||||
export const ACCEPT_EXT = ACCEPT_FORMAT.map(item => `.${item}`);
|
||||
|
||||
export const INITIAL_PLUGIN_REPORT_PARAMS = {
|
||||
environment: getEnv(),
|
||||
workspace_id: '',
|
||||
workspace_type: '',
|
||||
status: 1,
|
||||
create_type: 'import',
|
||||
};
|
||||
|
||||
export const INITIAL_TOOL_REPORT_PARAMS = {
|
||||
environment: getEnv(),
|
||||
workspace_id: '',
|
||||
workspace_type: '',
|
||||
status: 1,
|
||||
create_type: 'import',
|
||||
plugin_id: '',
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
/* stylelint-disable order/order */
|
||||
.upload-file-area {
|
||||
:global {
|
||||
.semi-upload-file-list {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
.semi-upload-file-list-main {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-upload-drag-area {
|
||||
height: 380px;
|
||||
background-color: white;
|
||||
border: 1px dashed var(--semi-color-border);
|
||||
|
||||
}
|
||||
|
||||
.semi-upload-drag-area-tips {
|
||||
font-weight: 400;
|
||||
|
||||
}
|
||||
|
||||
.semi-upload-drag-area-legal {
|
||||
border: 1px dashed var(--semi-color-primary);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4D53E8;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-area-disabled {
|
||||
:global {
|
||||
.semi-upload-drag-area {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
padding: 16px 20px;
|
||||
|
||||
background: #FFF;
|
||||
border: 1px solid #1D1C2314;
|
||||
border-radius: 8px;
|
||||
|
||||
.file-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 16px;
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
color: #1D1C2359;
|
||||
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 160px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-area {
|
||||
background-color: #FFF;
|
||||
|
||||
&:hover {
|
||||
background-color: #FFF;
|
||||
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-input-textarea {
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 4px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgb(29 28 35 / 30%);
|
||||
border-radius: 6px;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: rgb(29 28 35 / 60%);
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type RenderFileItemProps,
|
||||
type FileItem,
|
||||
type UploadProps,
|
||||
} from '@coze-arch/bot-semi/Upload';
|
||||
import { type TextAreaProps } from '@coze-arch/bot-semi/Input';
|
||||
import {
|
||||
Progress,
|
||||
TextArea,
|
||||
Typography,
|
||||
UIButton,
|
||||
UIIconButton,
|
||||
UIToast,
|
||||
Upload,
|
||||
Image,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconDeleteOutline, IconError } from '@coze-arch/bot-icons';
|
||||
|
||||
import YAMLImg from '@/assets/yaml.png';
|
||||
import JsonImg from '@/assets/json-file.png';
|
||||
|
||||
import { getContent, getFileExtension } from './utils';
|
||||
import { ACCEPT_EXT, ACCEPT_FORMAT } from './const';
|
||||
|
||||
import styles from './import-content.module.less';
|
||||
export interface FileUploadProps {
|
||||
onUpload: (content?: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
type SemiTextAreaProps = Omit<TextAreaProps, 'forwardRef'>;
|
||||
interface RawTextProps extends SemiTextAreaProps {
|
||||
onChange: (val?: string) => void;
|
||||
}
|
||||
|
||||
export const FileUpload = ({ onUpload, disabled }: FileUploadProps) => {
|
||||
const [fileList, setFileList] = useState<FileItem[]>([]);
|
||||
|
||||
const customRequest: UploadProps['customRequest'] = async options => {
|
||||
const { onSuccess, file, onError, onProgress } = options;
|
||||
|
||||
if (typeof file === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { name, fileInstance } = file;
|
||||
|
||||
if (fileInstance) {
|
||||
const extension = getFileExtension(name);
|
||||
if (!ACCEPT_FORMAT.includes(extension)) {
|
||||
return;
|
||||
}
|
||||
const result = await getContent(fileInstance, onProgress);
|
||||
onSuccess(result);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
eventName: 'fail_to_read_file',
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error,
|
||||
});
|
||||
onError({ status: 0 });
|
||||
}
|
||||
};
|
||||
|
||||
const renderFileItem = (renderFileItemProps: RenderFileItemProps) => {
|
||||
const { name, onRemove, onRetry, percent, status } = renderFileItemProps;
|
||||
const renderProgress = () => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return (
|
||||
<Typography.Text className={styles['upload-text']} ellipsis>
|
||||
{I18n.t('file_upload_success')}
|
||||
</Typography.Text>
|
||||
);
|
||||
case 'uploadFail':
|
||||
case 'validateFail':
|
||||
return (
|
||||
<>
|
||||
<IconError />
|
||||
<UIButton
|
||||
theme="borderless"
|
||||
className="ml-[8px]"
|
||||
onClick={onRetry}
|
||||
>
|
||||
{I18n.t('retry')}
|
||||
</UIButton>
|
||||
</>
|
||||
);
|
||||
case 'uploading':
|
||||
case 'wait':
|
||||
case 'validating':
|
||||
default:
|
||||
return (
|
||||
<div className={classNames('w-[90px]')}>
|
||||
<Progress percent={percent} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
styles['upload-file-item'],
|
||||
disabled && styles.disabled,
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
preview={false}
|
||||
className={styles['file-icon']}
|
||||
src={getFileExtension(name) === 'yaml' ? YAMLImg : JsonImg}
|
||||
/>
|
||||
<Typography.Text
|
||||
className={styles.text}
|
||||
ellipsis={{ showTooltip: { opts: { content: name } } }}
|
||||
>
|
||||
{name}
|
||||
</Typography.Text>
|
||||
{<div className={styles.progress}>{renderProgress()}</div>}
|
||||
<UIIconButton
|
||||
icon={
|
||||
<IconDeleteOutline
|
||||
className={styles['delete-icon']}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Upload
|
||||
accept={ACCEPT_EXT.join(',')}
|
||||
action=""
|
||||
onAcceptInvalid={() => {
|
||||
UIToast.warning(I18n.t('file_format_not_supported'));
|
||||
}}
|
||||
onSuccess={res => {
|
||||
onUpload(res);
|
||||
}}
|
||||
disabled={disabled}
|
||||
fileList={fileList}
|
||||
onChange={({ fileList: list }) => {
|
||||
setFileList(list);
|
||||
if (!list.length) {
|
||||
// 清空content
|
||||
onUpload();
|
||||
}
|
||||
}}
|
||||
className={classNames(
|
||||
styles['upload-file-area'],
|
||||
fileList.length && styles['drag-area-disabled'],
|
||||
)}
|
||||
dragMainText={I18n.t('click_upload_or_drag_files')}
|
||||
draggable={true}
|
||||
dragSubText={
|
||||
<>
|
||||
<span>{I18n.t('supports_uploading_json_or_yaml_files')}</span>
|
||||
<a
|
||||
href={
|
||||
IS_OVERSEA
|
||||
? '/open/docs/guides/plugin_import'
|
||||
: '/open/docs/guides/import'
|
||||
}
|
||||
target="_blank"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{I18n.t('view_detailed_information')}
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
renderFileItem={renderFileItem}
|
||||
limit={1}
|
||||
customRequest={customRequest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RawText = forwardRef<HTMLTextAreaElement | null, RawTextProps>(
|
||||
(props, ref) => {
|
||||
const { onChange, ...extraProps } = props;
|
||||
return (
|
||||
<TextArea
|
||||
placeholder={I18n.t('enter_raw_content_or_url')}
|
||||
rows={17}
|
||||
{...extraProps}
|
||||
ref={ref}
|
||||
onChange={value => {
|
||||
onChange(value.trim());
|
||||
}}
|
||||
className={styles['text-area']}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
.radio-group {
|
||||
:global {
|
||||
.semi-radio {
|
||||
width: 136px;
|
||||
}
|
||||
|
||||
.semi-radio-content {
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.semi-radio-addon-buttonRadio {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
margin-top: 8px;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
color: #FF5656;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-modal {
|
||||
:global {
|
||||
.semi-modal-content {
|
||||
min-height: 646px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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, useRef } from 'react';
|
||||
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
UIButton,
|
||||
UIModal,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Typography,
|
||||
} from '@coze-arch/bot-semi';
|
||||
|
||||
import { isValidURL, customService } from './utils';
|
||||
import { FileUpload, RawText } from './import-content';
|
||||
|
||||
import styles from './import-modal.module.less';
|
||||
|
||||
export enum ImportType {
|
||||
File = 'File',
|
||||
Text = 'Text',
|
||||
}
|
||||
|
||||
enum ImportDetailType {
|
||||
File = 'file',
|
||||
FileUrl = 'file_url',
|
||||
Text = 'raw_txt',
|
||||
}
|
||||
export interface ImportData {
|
||||
type?: ImportDetailType;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export interface ImportModalProps {
|
||||
visible: boolean;
|
||||
title?: React.ReactNode;
|
||||
onCancel?: () => void;
|
||||
onOk?: (
|
||||
data: ImportData,
|
||||
) => Promise<{ success?: boolean; result?: unknown; errMsg?: string }>;
|
||||
}
|
||||
|
||||
export const ImportModal: React.FC<ImportModalProps> = props => {
|
||||
const { onCancel, visible, onOk, title } = props;
|
||||
const [importType, setImportType] = useState(ImportType.File);
|
||||
const [content, setContent] = useState<string>();
|
||||
const [errMsg, setErrMsg] = useState<string>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const handleContent = (text?: string) => {
|
||||
setContent(text);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setImportType(ImportType.File);
|
||||
setContent(undefined);
|
||||
};
|
||||
|
||||
const handleParse = async () => {
|
||||
setLoading(true);
|
||||
setErrMsg(undefined);
|
||||
let originContent = content;
|
||||
let type: ImportDetailType =
|
||||
importType === ImportType.Text
|
||||
? ImportDetailType.Text
|
||||
: ImportDetailType.File;
|
||||
if (importType === ImportType.Text && isValidURL(content)) {
|
||||
try {
|
||||
const res = await customService(content || '');
|
||||
originContent = res as unknown as string;
|
||||
type = ImportDetailType.FileUrl;
|
||||
} catch (e) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'fetch_url_resource_fail' });
|
||||
setErrMsg(I18n.t('unable_to_access_input_url'));
|
||||
setLoading(false);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const res = await onOk?.({ type, content: originContent });
|
||||
if (!res?.success) {
|
||||
setErrMsg(res?.errMsg);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => (
|
||||
<UIButton
|
||||
theme="solid"
|
||||
type="primary"
|
||||
disabled={!content}
|
||||
onClick={handleParse}
|
||||
loading={loading}
|
||||
>
|
||||
{I18n.t('next')}
|
||||
</UIButton>
|
||||
);
|
||||
|
||||
const renderErrMsg = () =>
|
||||
errMsg ? (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: errMsg },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{errMsg}
|
||||
</Typography.Text>
|
||||
) : null;
|
||||
|
||||
useEffect(() => {
|
||||
errMsg && setErrMsg(undefined);
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
reset();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (importType === ImportType.Text && textAreaRef.current) {
|
||||
textAreaRef?.current?.focus();
|
||||
}
|
||||
}, [importType]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UIModal
|
||||
afterClose={reset}
|
||||
keepDOM={false}
|
||||
type="action-small"
|
||||
title={title}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={handleParse}
|
||||
footer={renderFooter()}
|
||||
className={styles['import-modal']}
|
||||
>
|
||||
<div className="min-h-[472px]">
|
||||
<div className="flex justify-center mb-[24px]">
|
||||
<RadioGroup
|
||||
onChange={e => {
|
||||
setImportType(e.target.value);
|
||||
setContent(undefined);
|
||||
}}
|
||||
type="button"
|
||||
buttonSize="middle"
|
||||
defaultValue={importType}
|
||||
disabled={loading}
|
||||
className={styles['radio-group']}
|
||||
>
|
||||
<Radio value={ImportType.File}>{I18n.t('local_file')}</Radio>
|
||||
<Radio value={ImportType.Text}>{I18n.t('url_raw_data')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div>
|
||||
{importType === ImportType.File ? (
|
||||
<FileUpload onUpload={handleContent} disabled={loading} />
|
||||
) : (
|
||||
<RawText
|
||||
onChange={handleContent}
|
||||
disabled={loading}
|
||||
ref={textAreaRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles['error-msg']}> {renderErrMsg()}</div>
|
||||
</div>
|
||||
</UIModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { type AxiosResponse } from 'axios';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
sendTeaEvent,
|
||||
type ParamsTypeDefine,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UIToast, UIModal } from '@coze-arch/bot-semi';
|
||||
import { IconWarningInfo } from '@coze-arch/bot-icons';
|
||||
import { type ApiError } from '@coze-arch/bot-http';
|
||||
import {
|
||||
type Convert2OpenAPIRequest,
|
||||
type BatchCreateAPIRequest,
|
||||
type BatchCreateAPIResponse,
|
||||
type Convert2OpenAPIResponse,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import {
|
||||
SpaceType,
|
||||
type PluginMetaInfo,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
getImportFormatType,
|
||||
getInitialPluginMetaInfo,
|
||||
isDuplicatePathErrorResponseData,
|
||||
parsePluginInfo,
|
||||
} from './utils';
|
||||
import { showMergeTool } from './show-merge-tool';
|
||||
import { PluginInfoConfirm } from './plugin-info-confirm';
|
||||
import { type ImportData, ImportModal } from './import-modal';
|
||||
import {
|
||||
ERROR_CODE,
|
||||
INITIAL_PLUGIN_REPORT_PARAMS,
|
||||
INITIAL_TOOL_REPORT_PARAMS,
|
||||
getEnv,
|
||||
} from './const';
|
||||
interface ImportModalProps {
|
||||
visible: boolean;
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (pluginInfo?: { plugin_id?: string }) => void;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
export type ImportPluginModalProps = ImportModalProps;
|
||||
export interface ImportToolModalProps extends ImportModalProps {
|
||||
pluginInfo?: {
|
||||
pluginName?: string;
|
||||
pluginUrl?: string;
|
||||
pluginID?: string;
|
||||
pluginDesc?: string;
|
||||
editVersion?: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface ImportPluginInfo {
|
||||
aiPlugin?: string;
|
||||
openAPI?: string;
|
||||
metaInfo?: PluginMetaInfo;
|
||||
}
|
||||
|
||||
export const ImportPluginModal: React.FC<ImportPluginModalProps> = props => {
|
||||
const { visible } = props;
|
||||
return visible ? <ImportPluginModalContent {...props} /> : null;
|
||||
};
|
||||
|
||||
export const ImportPluginModalContent: React.FC<
|
||||
ImportPluginModalProps
|
||||
> = props => {
|
||||
const { onCancel, visible, onSuccess, projectId } = props;
|
||||
const [importPluginInfo, setImportPluginInfo] = useState<ImportPluginInfo>();
|
||||
|
||||
const { id: spaceId, space_type } = useSpaceStore(store => store.space);
|
||||
|
||||
const reportParams = useRef<
|
||||
ParamsTypeDefine[EVENT_NAMES.create_plugin_front]
|
||||
>(INITIAL_PLUGIN_REPORT_PARAMS);
|
||||
|
||||
const isPersonal = space_type === SpaceType.Personal;
|
||||
|
||||
useEffect(() => {
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
environment: getEnv(),
|
||||
workspace_id: spaceId || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
status: 1,
|
||||
create_type: 'import',
|
||||
};
|
||||
}, [spaceId, isPersonal]);
|
||||
|
||||
const convert2OpenAPI = async (
|
||||
req: Convert2OpenAPIRequest,
|
||||
): Promise<{ success: boolean; errMsg?: string }> => {
|
||||
try {
|
||||
const { openapi, ai_plugin, plugin_data_format } =
|
||||
await PluginDevelopApi.Convert2OpenAPI(req, {
|
||||
__disableErrorToast: true,
|
||||
});
|
||||
|
||||
// 解析string
|
||||
const result = parsePluginInfo({
|
||||
aiPlugin: ai_plugin,
|
||||
openAPI: openapi,
|
||||
});
|
||||
const metaImportPluginInfo = getInitialPluginMetaInfo(result);
|
||||
|
||||
setImportPluginInfo({
|
||||
aiPlugin: ai_plugin,
|
||||
openAPI: openapi,
|
||||
metaInfo: metaImportPluginInfo,
|
||||
});
|
||||
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
import_format_type: getImportFormatType(plugin_data_format),
|
||||
import_tools_count: Object.entries(result?.openAPI?.paths || {}).length,
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
const { msg, code, response } = e as ApiError;
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'plugin_convert_openapi_fail' });
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
import_format_type: getImportFormatType(
|
||||
(response as unknown as AxiosResponse<Convert2OpenAPIResponse>)?.data
|
||||
?.plugin_data_format,
|
||||
),
|
||||
import_tools_count: 0,
|
||||
};
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_front, {
|
||||
...reportParams.current,
|
||||
status: 1,
|
||||
error_message: msg,
|
||||
});
|
||||
if (
|
||||
Number(code) === ERROR_CODE.DUP_PATH ||
|
||||
isDuplicatePathErrorResponseData(response?.data)
|
||||
) {
|
||||
const handleMerge = async () => {
|
||||
const { errMsg, success } = await convert2OpenAPI({
|
||||
...req,
|
||||
merge_same_paths: true,
|
||||
});
|
||||
if (!success) {
|
||||
UIToast.error({
|
||||
content: withSlardarIdButton(errMsg || I18n.t('error')),
|
||||
});
|
||||
return Promise.reject(errMsg);
|
||||
}
|
||||
};
|
||||
showMergeTool({
|
||||
onOk: handleMerge,
|
||||
duplicateInfos: (
|
||||
response as unknown as AxiosResponse<Convert2OpenAPIResponse>
|
||||
)?.data?.duplicate_api_infos,
|
||||
});
|
||||
return { success: false, errMsg: '' };
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
errMsg: msg || I18n.t('error'),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleConvertOpenAPI = async ({ content, type }: ImportData) => {
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
import_way_type: type,
|
||||
};
|
||||
return await convert2OpenAPI({
|
||||
data: content || '',
|
||||
space_id: spaceId,
|
||||
merge_same_paths: false,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImportModal
|
||||
title={I18n.t('import_plugin')}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={handleConvertOpenAPI}
|
||||
/>
|
||||
{importPluginInfo ? (
|
||||
<PluginInfoConfirm
|
||||
visible={!!importPluginInfo}
|
||||
projectId={projectId}
|
||||
onCancel={() => setImportPluginInfo(undefined)}
|
||||
importInfo={{
|
||||
metaInfo: importPluginInfo.metaInfo,
|
||||
openAPI: importPluginInfo.openAPI,
|
||||
aiPlugin: importPluginInfo.aiPlugin,
|
||||
extra: { reportParams: reportParams.current },
|
||||
}}
|
||||
onSuccess={data => {
|
||||
onCancel?.();
|
||||
onSuccess?.(data);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ImportToolModal: React.FC<ImportToolModalProps> = props => {
|
||||
const { visible } = props;
|
||||
return visible ? <ImportToolModalContent {...props} /> : null;
|
||||
};
|
||||
|
||||
export const ImportToolModalContent: React.FC<ImportToolModalProps> = props => {
|
||||
const { onCancel, visible, onSuccess, pluginInfo } = props;
|
||||
|
||||
const reportParams = useRef<
|
||||
ParamsTypeDefine[EVENT_NAMES.create_plugin_tool_front]
|
||||
>(INITIAL_TOOL_REPORT_PARAMS);
|
||||
|
||||
const { id: spaceId, space_type } = useSpaceStore(store => store.space);
|
||||
const isPersonal = space_type === SpaceType.Personal;
|
||||
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
|
||||
useEffect(() => {
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
environment: getEnv(),
|
||||
workspace_id: spaceId || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
status: 1,
|
||||
create_type: 'import',
|
||||
plugin_id: pluginInfo?.pluginID || '',
|
||||
};
|
||||
}, [pluginInfo?.pluginID, spaceId, isPersonal]);
|
||||
|
||||
const handleBatchImportTool = async (req?: BatchCreateAPIRequest) => {
|
||||
try {
|
||||
const resp = await PluginDevelopApi.BatchCreateAPI(req, {
|
||||
__disableErrorToast: true,
|
||||
});
|
||||
const toolsCount = req?.replace_same_paths
|
||||
? req?.paths_to_replace?.length
|
||||
: resp?.paths_created?.length;
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_tool_front, {
|
||||
...reportParams.current,
|
||||
status: 0,
|
||||
import_tools_count: toolsCount || 0,
|
||||
});
|
||||
if (resp && !resp?.paths_duplicated?.length) {
|
||||
UIToast.success(
|
||||
req?.replace_same_paths
|
||||
? I18n.t('plugin_tool_replace_success')
|
||||
: I18n.t('plugin_tool_import_succes'),
|
||||
);
|
||||
onCancel?.();
|
||||
onSuccess?.();
|
||||
}
|
||||
} catch (e) {
|
||||
const { code, response } = e as ApiError;
|
||||
|
||||
if (
|
||||
Number(code) !== ERROR_CODE.DUP_PATH &&
|
||||
!isDuplicatePathErrorResponseData(response?.data)
|
||||
) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_tool_front, {
|
||||
...reportParams.current,
|
||||
status: 0,
|
||||
import_tools_count:
|
||||
(response as unknown as AxiosResponse<BatchCreateAPIResponse>)?.data
|
||||
?.paths_created?.length || 0,
|
||||
});
|
||||
handleDupPath(
|
||||
req,
|
||||
(response as unknown as AxiosResponse<BatchCreateAPIResponse>)?.data,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDupPath = (
|
||||
req?: BatchCreateAPIRequest,
|
||||
resp?: BatchCreateAPIResponse,
|
||||
) => {
|
||||
const { paths_created = [], paths_duplicated = [] } = resp || {};
|
||||
const importedLength = paths_created.length;
|
||||
const duplicatedLength = paths_duplicated.length;
|
||||
const failedContent = I18n.t('failed_to_import_tool', {
|
||||
num: duplicatedLength,
|
||||
});
|
||||
const successContent = I18n.t('tools_imported_successfully', {
|
||||
num: importedLength,
|
||||
});
|
||||
UIModal.warning({
|
||||
title: importedLength
|
||||
? `${successContent}, ${failedContent}`
|
||||
: failedContent,
|
||||
content: duplicatedLength
|
||||
? I18n.t('plugin_tool_exists_tips', { num: duplicatedLength })
|
||||
: null,
|
||||
okText: I18n.t('replace'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
centered: true,
|
||||
icon: <IconWarningInfo />,
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
onOk: async () => {
|
||||
const batchCreateReq: BatchCreateAPIRequest = {
|
||||
...req,
|
||||
replace_same_paths: true,
|
||||
paths_to_replace: paths_duplicated,
|
||||
};
|
||||
try {
|
||||
await handleBatchImportTool(batchCreateReq);
|
||||
} catch (err) {
|
||||
const { msg: errMsg } = err as ApiError;
|
||||
UIToast.error({
|
||||
content: withSlardarIdButton(errMsg || I18n.t('error')),
|
||||
});
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_tool_front, {
|
||||
...reportParams.current,
|
||||
import_tools_count: 0,
|
||||
status: 1,
|
||||
error_message: errMsg || '',
|
||||
});
|
||||
}
|
||||
},
|
||||
onCancel: importedLength
|
||||
? async () => {
|
||||
onCancel?.();
|
||||
await onSuccess?.();
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const convertOpenAPI = async (req: Convert2OpenAPIRequest) =>
|
||||
await PluginDevelopApi.Convert2OpenAPI(
|
||||
{ ...req, plugin_description: pluginInfo?.pluginDesc },
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
const batchImport = async (req: Convert2OpenAPIRequest) => {
|
||||
try {
|
||||
const resp = await convertOpenAPI(req);
|
||||
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
import_format_type: getImportFormatType(resp?.plugin_data_format),
|
||||
};
|
||||
const batchCreateReq: BatchCreateAPIRequest = {
|
||||
plugin_id: pluginInfo?.pluginID,
|
||||
ai_plugin: resp?.ai_plugin,
|
||||
openapi: resp?.openapi,
|
||||
replace_same_paths: false,
|
||||
space_id: spaceId,
|
||||
dev_id: userInfo?.user_id_str,
|
||||
edit_version: pluginInfo?.editVersion,
|
||||
};
|
||||
|
||||
await handleBatchImportTool(batchCreateReq);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
const { msg, code, response } = e as ApiError;
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'batch_create_fail' });
|
||||
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
import_format_type: getImportFormatType(
|
||||
(response as unknown as AxiosResponse<Convert2OpenAPIResponse>)?.data
|
||||
?.plugin_data_format,
|
||||
),
|
||||
};
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_tool_front, {
|
||||
...reportParams.current,
|
||||
import_tools_count: 0,
|
||||
status: 1,
|
||||
error_message: msg || '',
|
||||
});
|
||||
|
||||
if (
|
||||
Number(code) === ERROR_CODE.DUP_PATH ||
|
||||
isDuplicatePathErrorResponseData(response?.data)
|
||||
) {
|
||||
const handleMerge = async () => {
|
||||
const { success, errMsg } = await batchImport({
|
||||
...req,
|
||||
merge_same_paths: true,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
UIToast.error({
|
||||
content: withSlardarIdButton(errMsg || I18n.t('error')),
|
||||
});
|
||||
return Promise.reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
showMergeTool({
|
||||
onOk: handleMerge,
|
||||
duplicateInfos: (
|
||||
response as unknown as AxiosResponse<Convert2OpenAPIResponse>
|
||||
)?.data?.duplicate_api_infos,
|
||||
});
|
||||
return { success: false };
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
errMsg: msg || I18n.t('error'),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleImport = async (importData?: ImportData) => {
|
||||
const { content, type } = importData || {};
|
||||
reportParams.current = {
|
||||
...reportParams.current,
|
||||
import_way_type: type,
|
||||
};
|
||||
const res = await batchImport({
|
||||
data: content || '',
|
||||
plugin_name: pluginInfo?.pluginName,
|
||||
plugin_url: pluginInfo?.pluginUrl,
|
||||
merge_same_paths: false,
|
||||
space_id: spaceId,
|
||||
});
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
return (
|
||||
<ImportModal
|
||||
title={I18n.t('import_plugin_tool')}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={handleImport}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { InputWithCountField } from '@coze-studio/components';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
import {
|
||||
Col,
|
||||
Form,
|
||||
Row,
|
||||
UICascader,
|
||||
UIFormInput,
|
||||
UIFormTextArea,
|
||||
UIIconButton,
|
||||
UIInput,
|
||||
useFormApi,
|
||||
withField,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { type commonParamSchema } from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
type OauthTccOpt,
|
||||
authOptionsPlaceholder,
|
||||
extInfoText,
|
||||
locationOption,
|
||||
} from '@coze-studio/plugin-shared';
|
||||
import { IconAdd, IconDeleteOutline } from '@coze-arch/bot-icons';
|
||||
import { InfoPopover } from '@coze-agent-ide/bot-plugin-tools/infoPopover';
|
||||
|
||||
import { type AuthOption, findAuthTypeItem, formRuleList } from './utils';
|
||||
import { type ConfirmFormProps } from './interface';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface PluginInfoFormFieldProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const HEADER_LIST_LENGTH_MAX = 20;
|
||||
|
||||
export const PluginNameField = ({ disabled }: PluginInfoFormFieldProps) => {
|
||||
const formApi = useFormApi<ConfirmFormProps>();
|
||||
const formValues = formApi.getValues();
|
||||
return disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_name1'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>{formValues?.name}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<UIFormTextArea
|
||||
field="name"
|
||||
className={s['textarea-single-line']}
|
||||
label={I18n.t('create_plugin_modal_name1')}
|
||||
placeholder={I18n.t('create_plugin_modal_name2')}
|
||||
trigger={['blur', 'change']}
|
||||
maxCount={30}
|
||||
maxLength={30}
|
||||
rows={1}
|
||||
onBlur={() => {
|
||||
formApi.setValue('name', formApi.getValue('name')?.trim());
|
||||
}}
|
||||
rules={formRuleList.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const PluginDescField = ({ disabled }: PluginInfoFormFieldProps) => {
|
||||
const formApi = useFormApi<ConfirmFormProps>();
|
||||
const formValues = formApi.getValues();
|
||||
|
||||
return disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_descrip1'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>{formValues?.desc}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<UIFormTextArea
|
||||
field="desc"
|
||||
label={I18n.t('create_plugin_modal_descrip1')}
|
||||
trigger={['blur', 'change']}
|
||||
placeholder={I18n.t('create_plugin_modal_descrip2')}
|
||||
rows={2}
|
||||
maxCount={600}
|
||||
maxLength={600}
|
||||
onBlur={() => {
|
||||
formApi.setValue('desc', formValues?.desc?.trim());
|
||||
}}
|
||||
rules={formRuleList.desc}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const PluginUrlField = ({ disabled }: PluginInfoFormFieldProps) => {
|
||||
const formApi = useFormApi<ConfirmFormProps>();
|
||||
const formValues = formApi.getValues();
|
||||
return disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_url1'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>{formValues?.url}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<UIFormInput
|
||||
className={s['textarea-single-line']}
|
||||
trigger={['blur', 'change']}
|
||||
field="url"
|
||||
label={I18n.t('create_plugin_modal_url1')}
|
||||
placeholder={I18n.t('create_plugin_modal_url2')}
|
||||
onBlur={() => {
|
||||
formApi.setValue('url', formValues?.url?.trim());
|
||||
}}
|
||||
rules={formRuleList.url}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const HeaderList = ({
|
||||
disabled,
|
||||
value: headerList = [],
|
||||
onChange: setHeaderList,
|
||||
}: PluginInfoFormFieldProps & {
|
||||
value?: commonParamSchema[];
|
||||
onChange?: (val?: commonParamSchema[]) => void;
|
||||
}) => {
|
||||
/** 添加header */
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const addHeader = data => {
|
||||
const h = [...headerList];
|
||||
h.push(data.name ? data : { name: '', value: '' });
|
||||
setHeaderList?.(h);
|
||||
};
|
||||
/** 删除header */
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const deleteHeader = index => {
|
||||
// 若为最后一个header,则只清空内容,不删除
|
||||
const filterList = cloneDeep(headerList);
|
||||
filterList.splice(index, 1);
|
||||
setHeaderList?.(filterList);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Slot
|
||||
className={s['header-list']}
|
||||
label={{
|
||||
text: I18n.t('plugin_create_header_list_title'),
|
||||
align: 'right',
|
||||
extra: (
|
||||
<div className={s['header-list-extra']}>
|
||||
<InfoPopover data={extInfoText.header_list} />
|
||||
{headerList.length < HEADER_LIST_LENGTH_MAX && !disabled && (
|
||||
<UIIconButton
|
||||
size="large"
|
||||
icon={<IconAdd />}
|
||||
onClick={addHeader}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<div className={s['herder-list-box']}>
|
||||
<Row className={s['header-row']} gutter={8}>
|
||||
<Col span={9}>
|
||||
<div className={s['header-col-content']}>Key</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={s['header-col-content']}>Value</div>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<div
|
||||
className={s['header-col-content']}
|
||||
style={{ textAlign: 'right' }}
|
||||
>
|
||||
{I18n.t('plugin_create_action_btn')}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div className={s['herder-list-cotent']}>
|
||||
{headerList?.map((item, index) => (
|
||||
<Row
|
||||
gutter={8}
|
||||
type="flex"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
key={index}
|
||||
>
|
||||
<Col span={9}>
|
||||
<div className={s['col-content']}>
|
||||
<UIInput
|
||||
placeholder={'Name'}
|
||||
value={item.name}
|
||||
onChange={val => {
|
||||
const list = cloneDeep(headerList);
|
||||
list[index].name = val;
|
||||
setHeaderList?.(list);
|
||||
}}
|
||||
maxLength={100}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={s['col-content']}>
|
||||
<UIInput
|
||||
placeholder={'Value'}
|
||||
value={item.value}
|
||||
onChange={val => {
|
||||
const list = cloneDeep(headerList);
|
||||
list[index].value = val;
|
||||
setHeaderList?.(list);
|
||||
}}
|
||||
maxLength={200}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<div className={s['col-content']}>
|
||||
<UIIconButton
|
||||
icon={<IconDeleteOutline />}
|
||||
type="secondary"
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
deleteHeader(index);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Form.Slot>
|
||||
);
|
||||
};
|
||||
|
||||
const HeaderListInnerField = withField(HeaderList, {
|
||||
valueKey: 'value',
|
||||
onKeyChangeFnName: 'onChange',
|
||||
});
|
||||
|
||||
export const HeaderListField = (props: PluginInfoFormFieldProps) => (
|
||||
<HeaderListInnerField
|
||||
{...props}
|
||||
field="headerList"
|
||||
label={{ text: '' }}
|
||||
></HeaderListInnerField>
|
||||
);
|
||||
|
||||
export const AuthTypeField = ({
|
||||
disabled,
|
||||
authOption,
|
||||
onChange,
|
||||
}: PluginInfoFormFieldProps & {
|
||||
authOption: Array<AuthOption>;
|
||||
onChange: (val?: Array<number>) => void;
|
||||
}) => {
|
||||
const formApi = useFormApi<ConfirmFormProps>();
|
||||
const formValues = formApi.getValues();
|
||||
return disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_auth1'),
|
||||
extra: <InfoPopover data={extInfoText.auth} />,
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{findAuthTypeItem(authOption, formValues?.auth_type?.at(-1))?.label}
|
||||
</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<UICascader.FormItem
|
||||
rules={[{ required: true }]}
|
||||
style={{ width: '100%' }}
|
||||
initValue={formValues?.auth_type || [0]}
|
||||
field="auth_type"
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_auth1'),
|
||||
extra: <InfoPopover data={extInfoText.auth} />,
|
||||
}}
|
||||
placeholder={I18n.t('please_select_an_authorization_method')}
|
||||
treeData={authOption}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
displayRender={(list: any) => `${(list as string[])?.at(-1)}`}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onChange={(val: any) => {
|
||||
onChange(val as Array<number>);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServiceField = ({ disabled }: PluginInfoFormFieldProps) => {
|
||||
const formApi = useFormApi<ConfirmFormProps>();
|
||||
const formValues = formApi.getValues();
|
||||
return (
|
||||
<>
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_location'),
|
||||
extra: <InfoPopover data={extInfoText.location} />,
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{
|
||||
findAuthTypeItem(locationOption, formApi.getValues()?.location)
|
||||
?.label
|
||||
}
|
||||
</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<Form.RadioGroup
|
||||
rules={[{ required: true }]}
|
||||
field="location"
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_location'),
|
||||
extra: <InfoPopover data={extInfoText.location} />,
|
||||
}}
|
||||
options={locationOption}
|
||||
/>
|
||||
)}
|
||||
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_Parameter'),
|
||||
extra: <InfoPopover data={extInfoText.key} />,
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>{formApi.getValues()?.key}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<InputWithCountField
|
||||
initValue={formValues?.key}
|
||||
trigger={['blur', 'change']}
|
||||
field="key"
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_Parameter'),
|
||||
extra: <InfoPopover data={extInfoText.key} />,
|
||||
}}
|
||||
placeholder={I18n.t('create_plugin_modal_Parameter_empty')}
|
||||
maxLength={100}
|
||||
rules={formRuleList.key}
|
||||
/>
|
||||
)}
|
||||
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_Servicetoken'),
|
||||
extra: <InfoPopover data={extInfoText.service_token} />,
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>{formValues?.service_token}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<InputWithCountField
|
||||
initValue={formValues?.service_token}
|
||||
trigger={['blur', 'change']}
|
||||
field="service_token"
|
||||
label={{
|
||||
text: I18n.t('create_plugin_modal_Servicetoken'),
|
||||
extra: <InfoPopover data={extInfoText.service_token} />,
|
||||
}}
|
||||
placeholder={I18n.t('create_plugin_modal_Servicetoken_empty')}
|
||||
maxLength={400}
|
||||
rules={formRuleList.service_token}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// extItems 动态下发
|
||||
export const ExtItems = ({
|
||||
disabled,
|
||||
extItems,
|
||||
}: PluginInfoFormFieldProps & { extItems: OauthTccOpt[] }) => {
|
||||
const formApi = useFormApi<ConfirmFormProps>();
|
||||
const formValues = formApi.getValues();
|
||||
return (
|
||||
<>
|
||||
{/* 服务端动态返回授权项 */}
|
||||
{extItems?.map(item => (
|
||||
<>
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
key={item.key}
|
||||
label={{
|
||||
text: item.key,
|
||||
extra: extInfoText[item.key] && (
|
||||
<InfoPopover data={extInfoText[item.key]} />
|
||||
),
|
||||
required: item.required,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{formValues?.oauth_info
|
||||
? safeJSONParse(formValues.oauth_info)[item.key]
|
||||
: null}
|
||||
</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<InputWithCountField
|
||||
key={item.key}
|
||||
trigger={['blur', 'change']}
|
||||
field={item.key}
|
||||
label={{
|
||||
text: item.key,
|
||||
extra: extInfoText[item.key] && (
|
||||
<InfoPopover data={extInfoText[item.key]} />
|
||||
),
|
||||
}}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
placeholder={authOptionsPlaceholder[item.key]}
|
||||
initValue={
|
||||
(formValues?.oauth_info &&
|
||||
safeJSONParse(formValues.oauth_info)[item.key]) ||
|
||||
item.default
|
||||
}
|
||||
maxLength={item.max_len}
|
||||
rules={[
|
||||
{
|
||||
required: item.required,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
message: authOptionsPlaceholder[item.key],
|
||||
},
|
||||
item.type === 'url'
|
||||
? {
|
||||
pattern: /^(http|https):\/\/.+$/,
|
||||
message: I18n.t('create_plugin_modal_URLerror'),
|
||||
}
|
||||
: {
|
||||
// eslint-disable-next-line no-control-regex -- regex
|
||||
pattern: /^[\x00-\x7F]+$/,
|
||||
message: I18n.t('create_plugin_modal_descrip_error'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
|
||||
|
||||
.upload-form {
|
||||
.upload-field {
|
||||
padding-top: 0;
|
||||
|
||||
:global {
|
||||
.semi-form-field-help-text {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-single-line {
|
||||
:global {
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-multi-line {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:global {
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -20px;
|
||||
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-draft {
|
||||
align-items: flex-start;
|
||||
|
||||
padding-top: 16px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #000;
|
||||
|
||||
.link {
|
||||
font-weight: 400;
|
||||
color: #4D53E8;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-icon {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
display: none !important;
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.upload-avatar {
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
|
||||
background: #fff !important;
|
||||
border-radius: var(--spacing-tight, 8px) !important;
|
||||
}
|
||||
|
||||
.header-list {
|
||||
:global {
|
||||
.semi-form-field-label-with-extra {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.semi-form-field-label-extra {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.header-list-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.herder-list-box {
|
||||
overflow: auto;
|
||||
|
||||
max-height: 348px;
|
||||
padding: 0 16px;
|
||||
|
||||
border: 1px solid rgb(29 28 35 / 12%);
|
||||
border-radius: 8px;
|
||||
|
||||
.herder-list-cotent {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
border-bottom: 1px solid rgb(29 28 35 / 12%);
|
||||
}
|
||||
|
||||
.header-col-content {
|
||||
padding: 12px 0;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.col-content {
|
||||
padding: 12px 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-msg-box {
|
||||
position: relative;
|
||||
top: -24px;
|
||||
|
||||
.error-msg {
|
||||
display: block;
|
||||
|
||||
padding: 8px 16px;
|
||||
|
||||
line-height: 16px;
|
||||
color: #F93920;
|
||||
text-align: left;
|
||||
|
||||
.link {
|
||||
font-weight: 400;
|
||||
color: #4D53E8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
sendTeaEvent,
|
||||
type ParamsTypeDefine,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { type FormApi } from '@coze-arch/bot-semi/Form';
|
||||
import { UIModal, UIButton, Space, Form, UIToast } from '@coze-arch/bot-semi';
|
||||
import { IconInfoCircle } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
ParameterLocation,
|
||||
PluginType,
|
||||
type PluginMetaInfo,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
import { FileBizType, IconType } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi, PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { PictureUpload } from '@coze-common/biz-components/picture-upload';
|
||||
import { type OauthTccOpt } from '@coze-studio/plugin-shared';
|
||||
|
||||
import { getRegisterInfo } from '../utils';
|
||||
import { ERROR_CODE, INITIAL_PLUGIN_REPORT_PARAMS } from '../const';
|
||||
import { PluginDocs } from '../../plugin-docs';
|
||||
import { type AuthOption, findAuthTypeItem, getAuthOptions } from './utils';
|
||||
import { type ConfirmFormProps } from './interface';
|
||||
import {
|
||||
AuthTypeField,
|
||||
ExtItems,
|
||||
HeaderListField,
|
||||
PluginDescField,
|
||||
PluginNameField,
|
||||
PluginUrlField,
|
||||
ServiceField,
|
||||
} from './fields';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface PluginInfoConfirmProps {
|
||||
visible: boolean;
|
||||
importInfo?: {
|
||||
metaInfo?: PluginMetaInfo;
|
||||
aiPlugin?: string;
|
||||
openAPI?: string;
|
||||
extra?: {
|
||||
reportParams?: ParamsTypeDefine[EVENT_NAMES.create_plugin_front];
|
||||
};
|
||||
};
|
||||
disabled?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (pluginInfo?: { plugin_id?: string }) => void;
|
||||
onError?: () => void;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
const INITIAL_FORM_VALUES = {
|
||||
headerList: [{ name: 'User-Agent', value: 'Coze/1.0' }],
|
||||
};
|
||||
|
||||
/**
|
||||
文件导入plugin确认信息弹窗,目前和普通创建导入很像,调用接口不一样,
|
||||
目前感觉这个确认形式不太友好,后续不太确定优化形态,所以新建单独文件处理,以防污染bot-form-edit
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export const PluginInfoConfirm: React.FC<PluginInfoConfirmProps> = props => {
|
||||
const {
|
||||
onCancel,
|
||||
importInfo,
|
||||
visible,
|
||||
onSuccess,
|
||||
disabled = false,
|
||||
projectId,
|
||||
} = props;
|
||||
|
||||
const [authOption, setAuthOption] = useState<AuthOption[]>([]);
|
||||
// 合规审核结果
|
||||
const [isValidCheckResult, setIsValidCheckResult] = useState(true);
|
||||
|
||||
const [extItems, setExtItems] = useState<OauthTccOpt[]>([]);
|
||||
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const header = importInfo?.metaInfo?.common_params?.[4] || [];
|
||||
const initialFormValues = importInfo
|
||||
? { ...importInfo?.metaInfo, headerList: header || [] }
|
||||
: INITIAL_FORM_VALUES;
|
||||
|
||||
const formApi = useRef<FormApi<ConfirmFormProps>>();
|
||||
|
||||
const formStateValues = formApi.current?.getFormState()?.values;
|
||||
|
||||
const spaceId = useSpaceStore(store => store.space.id);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await DeveloperApi.GetOAuthSchema();
|
||||
const authOptions = getAuthOptions(res?.oauth_schema);
|
||||
setAuthOption(authOptions);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (importInfo) {
|
||||
//更新插件
|
||||
setExtItems(
|
||||
findAuthTypeItem(
|
||||
authOption,
|
||||
importInfo.metaInfo?.auth_type?.at(-1) || 0,
|
||||
)?.items || [],
|
||||
);
|
||||
} else {
|
||||
setExtItems([]);
|
||||
}
|
||||
}, [authOption, importInfo]);
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const confirmBtn = async () => {
|
||||
await formApi.current?.validate();
|
||||
const formValues = formApi.current?.getValues();
|
||||
|
||||
if (!formValues) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { openAPI, aiPlugin } = getEditRegisterInfo(formValues);
|
||||
|
||||
try {
|
||||
setSubmitting(true);
|
||||
const { data } = await PluginDevelopApi.RegisterPlugin(
|
||||
{
|
||||
ai_plugin: aiPlugin,
|
||||
openapi: openAPI,
|
||||
plugin_type: PluginType.PLUGIN,
|
||||
client_id: formValues?.client_id,
|
||||
client_secret: formValues?.client_secret,
|
||||
service_token: formValues?.service_token,
|
||||
import_from_file: true,
|
||||
space_id: spaceId,
|
||||
project_id: projectId,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
UIToast.success(I18n.t('plugin_imported_successfully'));
|
||||
|
||||
onCancel?.();
|
||||
await onSuccess?.({ plugin_id: data?.plugin_id });
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_front, {
|
||||
...(importInfo?.extra?.reportParams || INITIAL_PLUGIN_REPORT_PARAMS),
|
||||
status: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { code, msg } = error;
|
||||
sendTeaEvent(EVENT_NAMES.create_plugin_front, {
|
||||
...(importInfo?.extra?.reportParams || INITIAL_PLUGIN_REPORT_PARAMS),
|
||||
status: 1,
|
||||
error_message: msg,
|
||||
});
|
||||
if (Number(code) === ERROR_CODE.SAFE_CHECK) {
|
||||
setIsValidCheckResult(false);
|
||||
} else {
|
||||
UIToast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getEditRegisterInfo = (formValues: ConfirmFormProps) => {
|
||||
const { headerList, plugin_uri = [], ...extraValues } = formValues;
|
||||
const json: Record<string, string> = {};
|
||||
extItems?.forEach(item => {
|
||||
if (item.key in formValues) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
json[item.key] = formValues[item.key];
|
||||
}
|
||||
});
|
||||
|
||||
const metaParams: PluginMetaInfo = {
|
||||
...extraValues,
|
||||
oauth_info: JSON.stringify(json),
|
||||
icon: { uri: plugin_uri[0]?.uid },
|
||||
common_params: {
|
||||
[ParameterLocation.Header]: formValues?.headerList || [],
|
||||
[ParameterLocation.Body]: [],
|
||||
[ParameterLocation.Path]: [],
|
||||
[ParameterLocation.Query]: [],
|
||||
},
|
||||
};
|
||||
|
||||
const params = getRegisterInfo(metaParams, {
|
||||
openAPI: importInfo?.openAPI,
|
||||
aiPlugin: importInfo?.aiPlugin,
|
||||
});
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValidCheckResult) {
|
||||
setIsValidCheckResult(true);
|
||||
}
|
||||
}, [formStateValues?.name || formStateValues?.desc]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible ? (
|
||||
<UIModal
|
||||
type="action-small"
|
||||
title={I18n.t('confirm_plugin_information')}
|
||||
visible={visible}
|
||||
onCancel={() => onCancel?.()}
|
||||
footer={
|
||||
!disabled && (
|
||||
<div>
|
||||
{!isValidCheckResult && (
|
||||
<div className={s['error-msg-box']}>
|
||||
<span className={s['error-msg']}>
|
||||
{I18n.t('plugin_create_modal_safe_error')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<UIButton
|
||||
type="tertiary"
|
||||
onClick={() => {
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
{I18n.t('create_plugin_modal_button_cancel')}
|
||||
</UIButton>
|
||||
<UIButton
|
||||
type="primary"
|
||||
theme="solid"
|
||||
loading={submitting}
|
||||
onClick={() => {
|
||||
confirmBtn();
|
||||
}}
|
||||
>
|
||||
{I18n.t('create_plugin_modal_button_confirm')}
|
||||
</UIButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Form<typeof initialFormValues>
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
getFormApi={api => (formApi.current = api)}
|
||||
showValidateIcon={false}
|
||||
initValues={{ ...(initialFormValues || {}) }}
|
||||
className={s['upload-form']}
|
||||
>
|
||||
{({ values }) => (
|
||||
<>
|
||||
{/* 插件头像 */}
|
||||
<PictureUpload
|
||||
noLabel
|
||||
disabled={disabled}
|
||||
fieldClassName={s['upload-field']}
|
||||
field="plugin_uri"
|
||||
iconType={IconType.Plugin}
|
||||
fileBizType={FileBizType.BIZ_PLUGIN_ICON}
|
||||
/>
|
||||
{/* 插件名称/插件描述/插件URL */}
|
||||
<PluginNameField disabled={disabled} />
|
||||
<PluginDescField disabled={disabled} />
|
||||
<PluginUrlField disabled={true} />
|
||||
{/* 插件Header */}
|
||||
<HeaderListField disabled={disabled} />
|
||||
{/* 授权方式 */}
|
||||
<AuthTypeField
|
||||
disabled={disabled}
|
||||
authOption={authOption}
|
||||
onChange={val => {
|
||||
setExtItems(
|
||||
findAuthTypeItem(authOption, val?.at(-1))?.items || [],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/* 授权方式-Service */}
|
||||
{values.auth_type.at(-1) === 1 && (
|
||||
<ServiceField disabled={disabled} />
|
||||
)}
|
||||
<ExtItems disabled={disabled} extItems={extItems} />
|
||||
{/* 协议 */}
|
||||
{!disabled && (
|
||||
<Space spacing={8} className={s['footer-draft']}>
|
||||
<IconInfoCircle
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
color: '#4D53E8',
|
||||
}}
|
||||
/>
|
||||
<span>
|
||||
{I18n.t('plugin_create_draft_desc')}
|
||||
<PluginDocs />
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</UIModal>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 {
|
||||
type RegisterPluginMetaRequest,
|
||||
type AuthorizationType,
|
||||
type commonParamSchema,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { type UploadValue } from '@coze-common/biz-components';
|
||||
|
||||
export interface ConfirmFormProps
|
||||
extends Omit<RegisterPluginMetaRequest, 'auth_type'> {
|
||||
plugin_uri: UploadValue;
|
||||
auth_type: Array<AuthorizationType>;
|
||||
headerList?: Array<commonParamSchema>;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
}
|
||||
@@ -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 { I18n } from '@coze-arch/i18n';
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
import { type PluginMetaInfo } from '@coze-arch/bot-api/developer_api';
|
||||
import { type UploadValue } from '@coze-common/biz-components';
|
||||
|
||||
export const formRuleList = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('create_plugin_modal_name1_error'),
|
||||
},
|
||||
IS_OVERSEA
|
||||
? {
|
||||
pattern: /^[\w\s]+$/,
|
||||
message: I18n.t('create_plugin_modal_nameerror'),
|
||||
}
|
||||
: {
|
||||
pattern: /^[\w\s\u4e00-\u9fa5]+$/u, // 国内增加支持中文
|
||||
message: I18n.t('create_plugin_modal_nameerror_cn'),
|
||||
},
|
||||
],
|
||||
desc: [
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('create_plugin_modal_descrip1_error'),
|
||||
},
|
||||
IS_OVERSEA && {
|
||||
// eslint-disable-next-line no-control-regex -- regex
|
||||
pattern: /^[\x00-\x7F]+$/,
|
||||
message: I18n.t('create_plugin_modal_descrip_error'),
|
||||
},
|
||||
],
|
||||
url: [
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('create_plugin_modal_url1_error'),
|
||||
},
|
||||
{
|
||||
pattern: /^(https):\/\/.+$/,
|
||||
message: I18n.t('create_plugin_modal_url_error_https'),
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('create_plugin_modal_Parameter_error'),
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line no-control-regex -- regex
|
||||
pattern: /^[\x00-\x7F]+$/,
|
||||
message: I18n.t('plugin_Parametename_error'),
|
||||
},
|
||||
],
|
||||
service_token: [
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('create_plugin_modal_Servicetoken_error'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const getPictureUploadInitValue = (
|
||||
info?: PluginMetaInfo,
|
||||
): UploadValue | undefined => {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
return [
|
||||
{
|
||||
url: info.icon?.url || '',
|
||||
uid: info?.icon?.uri || '',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export interface AuthOption {
|
||||
label: string;
|
||||
value: number;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- any
|
||||
[key: string]: any;
|
||||
}
|
||||
/** 递归寻找auth选项下的输入项 */
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
export const findAuthTypeItem = (data: AuthOption[], targetKey = 0) => {
|
||||
for (const item of data) {
|
||||
if (item.value === targetKey) {
|
||||
return item;
|
||||
} else if (item.children?.length > 0) {
|
||||
return findAuthTypeItem(item.children, targetKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function getAuthOptions(authSchema?: string): Array<AuthOption> {
|
||||
const authOptions: AuthOption[] = [
|
||||
{
|
||||
label: I18n.t('create_plugin_modal_Authorization_no'),
|
||||
value: 0,
|
||||
key: 'None',
|
||||
},
|
||||
{
|
||||
label: I18n.t('create_plugin_modal_Authorization_service'),
|
||||
value: 1,
|
||||
key: 'Service',
|
||||
},
|
||||
{
|
||||
label: I18n.t('create_plugin_modal_Authorization_oauth'),
|
||||
value: 3,
|
||||
key: 'OAuth',
|
||||
children: safeJSONParse(authSchema),
|
||||
},
|
||||
];
|
||||
return authOptions;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 { UIModal } from '@coze-arch/bot-semi';
|
||||
import { IconWarningInfo } from '@coze-arch/bot-icons';
|
||||
import { type DuplicateAPIInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
interface MergeToolInfoProps {
|
||||
onOk?: () => void;
|
||||
onCancel?: () => void;
|
||||
duplicateInfos?: DuplicateAPIInfo[];
|
||||
}
|
||||
|
||||
export function showMergeTool({
|
||||
duplicateInfos = [],
|
||||
onCancel,
|
||||
onOk,
|
||||
}: MergeToolInfoProps) {
|
||||
UIModal.warning({
|
||||
title: I18n.t('duplicate_tools_within_plugin'),
|
||||
content: duplicateInfos?.map(item => (
|
||||
<div>{`${item.method} ${I18n.t('path_has_duplicates', {
|
||||
path: item.path,
|
||||
num: item.count,
|
||||
})}`}</div>
|
||||
)),
|
||||
okText: I18n.t('merge_duplicate_tools'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
centered: true,
|
||||
icon: <IconWarningInfo />,
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
onOk,
|
||||
onCancel,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 { parse as yamlParse, stringify as yamlStringify } from 'yaml';
|
||||
import { isObject } from 'lodash-es';
|
||||
import axios from 'axios';
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { PluginDataFormat } from '@coze-arch/bot-api/plugin_develop';
|
||||
import {
|
||||
AuthorizationServiceLocation,
|
||||
AuthorizationType,
|
||||
ParameterLocation,
|
||||
type commonParamSchema,
|
||||
type PluginMetaInfo,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
interface PluginInfo {
|
||||
aiPlugin?: string;
|
||||
openAPI?: string;
|
||||
}
|
||||
|
||||
interface AIPluginMetaInfo {
|
||||
name_for_human?: string;
|
||||
name_for_model?: string;
|
||||
description_for_human?: string;
|
||||
description_for_model?: string;
|
||||
auth: {
|
||||
type?: AIPluginAuthType;
|
||||
client_url?: string;
|
||||
authorization_url?: string;
|
||||
authorization_content_type?: string;
|
||||
platform?: string;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
location?: string;
|
||||
key?: string;
|
||||
service_token?: string;
|
||||
scope?: string;
|
||||
};
|
||||
logo_url?: string;
|
||||
common_params?: {
|
||||
header?: Array<commonParamSchema>;
|
||||
body?: Array<commonParamSchema>;
|
||||
query?: Array<commonParamSchema>;
|
||||
path?: Array<commonParamSchema>;
|
||||
};
|
||||
}
|
||||
|
||||
interface PluginInfoObject {
|
||||
aiPlugin?: AIPluginMetaInfo;
|
||||
openAPI?: {
|
||||
info?: { description?: string; title?: string; version?: string };
|
||||
servers?: Array<{ url?: string }>;
|
||||
paths?: Array<unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export enum AIPluginAuthType {
|
||||
None = 'none',
|
||||
Service = 'service_http',
|
||||
OAuth = 'oauth',
|
||||
}
|
||||
|
||||
export enum AIPluginAuthServiceLocation {
|
||||
Header = 'Header',
|
||||
Query = 'Query',
|
||||
}
|
||||
|
||||
enum ImportFormatType {
|
||||
Curl = 'curl',
|
||||
OpenApi = 'openapi',
|
||||
Postman = 'postman',
|
||||
Unknown = '',
|
||||
Swagger = 'swagger',
|
||||
}
|
||||
|
||||
export function getFileExtension(name: string) {
|
||||
const index = name.lastIndexOf('.');
|
||||
return name.slice(index + 1);
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
export async function getContent(file: Blob, onProgress): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = event => {
|
||||
const result = event.target?.result;
|
||||
|
||||
if (!result || typeof result !== 'string') {
|
||||
reject(new CustomError('normal_error', 'file read fail'));
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
};
|
||||
fileReader.onprogress = event => {
|
||||
if (event.total) {
|
||||
onProgress({
|
||||
total: event.total,
|
||||
loaded: event.loaded,
|
||||
});
|
||||
}
|
||||
};
|
||||
fileReader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
export function isValidURL(str?: string): boolean {
|
||||
// 缩略版
|
||||
try {
|
||||
const objExp = new RegExp(
|
||||
'^(https?:\\/\\/)?' + // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name and extension
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
|
||||
'(\\:\\d+)?' + // port
|
||||
'(\\/[-a-z\\d%_.~+]*)*',
|
||||
'i',
|
||||
);
|
||||
return Boolean(objExp.test(str || ''));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function customService(url: string) {
|
||||
// 这里需要自定义请求,需要引入axios
|
||||
const axiosInstance = axios.create({ responseType: 'text' });
|
||||
|
||||
const response = await axiosInstance.get(url);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
const AUTH_TYPE_MAP: Record<AIPluginAuthType, AuthorizationType> = {
|
||||
[AIPluginAuthType.None]: AuthorizationType.None,
|
||||
[AIPluginAuthType.Service]: AuthorizationType.Service,
|
||||
[AIPluginAuthType.OAuth]: AuthorizationType.OAuth,
|
||||
};
|
||||
|
||||
const AUTH_LOCATION_MAP: Record<
|
||||
AIPluginAuthServiceLocation,
|
||||
AuthorizationServiceLocation
|
||||
> = {
|
||||
[AIPluginAuthServiceLocation.Header]: AuthorizationServiceLocation.Header,
|
||||
[AIPluginAuthServiceLocation.Query]: AuthorizationServiceLocation.Query,
|
||||
};
|
||||
|
||||
export function parsePluginInfo(data: PluginInfo): PluginInfoObject {
|
||||
const { aiPlugin, openAPI } = data;
|
||||
const aiPluginObj = safeJSONParse(aiPlugin || '{}');
|
||||
const openAPIObj = yamlParse(openAPI || '');
|
||||
|
||||
return {
|
||||
aiPlugin: aiPluginObj,
|
||||
openAPI: openAPIObj,
|
||||
};
|
||||
}
|
||||
|
||||
export function getInitialPluginMetaInfo(
|
||||
data: PluginInfoObject,
|
||||
): PluginMetaInfo {
|
||||
const { aiPlugin, openAPI } = data;
|
||||
const { type, location, key, service_token, ...oauthInfo } =
|
||||
aiPlugin?.auth || {};
|
||||
return {
|
||||
name: aiPlugin?.name_for_human,
|
||||
desc: aiPlugin?.description_for_human,
|
||||
url: openAPI?.servers?.[0]?.url,
|
||||
icon: { uri: aiPlugin?.logo_url },
|
||||
auth_type: [AUTH_TYPE_MAP[type || AIPluginAuthType.None]],
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
location: AUTH_LOCATION_MAP[location || ''],
|
||||
key,
|
||||
service_token,
|
||||
oauth_info: JSON.stringify(oauthInfo),
|
||||
common_params: {
|
||||
[ParameterLocation.Header]: aiPlugin?.common_params?.header || [],
|
||||
[ParameterLocation.Body]: aiPlugin?.common_params?.body || [],
|
||||
[ParameterLocation.Path]: aiPlugin?.common_params?.path || [],
|
||||
[ParameterLocation.Query]: aiPlugin?.common_params?.query || [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getKeyByValue<V>(
|
||||
map: Record<string, V>,
|
||||
value?: V,
|
||||
): string | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
for (const [key, val] of Object.entries(map)) {
|
||||
if (val === value) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRegisterInfo(
|
||||
pluginMetaInfo: PluginMetaInfo,
|
||||
data: PluginInfo,
|
||||
): PluginInfo {
|
||||
const { aiPlugin: oriAIPluginInfo, openAPI: oriOpenAPIInfo } =
|
||||
parsePluginInfo(data);
|
||||
const {
|
||||
name,
|
||||
desc,
|
||||
auth_type,
|
||||
common_params,
|
||||
location,
|
||||
key,
|
||||
service_token,
|
||||
oauth_info,
|
||||
icon,
|
||||
} = pluginMetaInfo;
|
||||
const newAIPlugin: AIPluginMetaInfo = {
|
||||
name_for_human: name,
|
||||
name_for_model: name,
|
||||
description_for_human: desc,
|
||||
description_for_model: desc,
|
||||
logo_url: icon?.uri,
|
||||
common_params: {
|
||||
header: common_params?.[ParameterLocation.Header],
|
||||
body: common_params?.[ParameterLocation.Body],
|
||||
path: common_params?.[ParameterLocation.Path],
|
||||
query: common_params?.[ParameterLocation.Query],
|
||||
},
|
||||
auth: {
|
||||
type: getKeyByValue<AuthorizationType>(AUTH_TYPE_MAP, auth_type?.at(0)),
|
||||
location: getKeyByValue<AuthorizationServiceLocation>(
|
||||
AUTH_LOCATION_MAP,
|
||||
location,
|
||||
),
|
||||
key,
|
||||
service_token,
|
||||
...JSON.parse(oauth_info || '{}'),
|
||||
},
|
||||
};
|
||||
const mergedAIPluginInfo = { ...oriAIPluginInfo, ...newAIPlugin };
|
||||
const mergedOpenAPIInfo = {
|
||||
...(oriOpenAPIInfo || {}),
|
||||
info: { ...(oriOpenAPIInfo?.info || {}), title: name, description: desc },
|
||||
servers: [{ url: pluginMetaInfo.url }],
|
||||
};
|
||||
return {
|
||||
aiPlugin: JSON.stringify(mergedAIPluginInfo),
|
||||
openAPI: yamlStringify(mergedOpenAPIInfo),
|
||||
};
|
||||
}
|
||||
|
||||
export function getImportFormatType(
|
||||
format?: PluginDataFormat,
|
||||
): ImportFormatType {
|
||||
switch (format) {
|
||||
case PluginDataFormat.Curl:
|
||||
return ImportFormatType.Curl;
|
||||
case PluginDataFormat.OpenAPI:
|
||||
return ImportFormatType.OpenApi;
|
||||
case PluginDataFormat.Postman:
|
||||
return ImportFormatType.Postman;
|
||||
case PluginDataFormat.Swagger:
|
||||
return ImportFormatType.Swagger;
|
||||
default:
|
||||
return ImportFormatType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export const isDuplicatePathErrorResponseData = (value: unknown): boolean =>
|
||||
isObject(value) && 'paths_duplicated' in value;
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { Typography } from '@coze-arch/coze-design';
|
||||
|
||||
export const PluginDocs = () => {
|
||||
const docsHref = useMemo(() => {
|
||||
const DRAFT_CN = {
|
||||
'zh-CN': '/docs/guides/plugin',
|
||||
en: '/docs/en_guides/en_plugin',
|
||||
};
|
||||
const DRAFT_OVERSEA = {
|
||||
'zh-CN': '',
|
||||
en: '',
|
||||
};
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
return IS_OVERSEA ? DRAFT_OVERSEA[I18n.language] : DRAFT_CN[I18n.language];
|
||||
}, []);
|
||||
|
||||
return !IS_OVERSEA ? (
|
||||
<Typography.Text
|
||||
link={{
|
||||
href: docsHref,
|
||||
target: '_blank',
|
||||
}}
|
||||
fontSize="12px"
|
||||
>
|
||||
{I18n.t('plugin_create_guide_link')}
|
||||
</Typography.Text>
|
||||
) : null;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
color: rgba(29, 28, 35, 35%);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import cs from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIButton } from '@coze-arch/bot-semi';
|
||||
|
||||
import { usePluginFeatModal } from '..';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const PluginFeatButton: FC<{
|
||||
className?: string;
|
||||
}> = ({ className }) => {
|
||||
const { modal, open } = usePluginFeatModal();
|
||||
|
||||
return (
|
||||
<div className={cs(styles.wrapper, className)}>
|
||||
{modal}
|
||||
<span className={styles.tip}>{I18n.t('plugin_feedback_entry_tip')}</span>
|
||||
<UIButton type="tertiary" onClick={open}>
|
||||
{I18n.t('plugin_feedback_entry_button')}
|
||||
</UIButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { SortType, ProductEntityType } from '@coze-arch/bot-api/product_api';
|
||||
import { FeedbackType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { ProductApi, PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
Modal,
|
||||
Form,
|
||||
type FormApi,
|
||||
Button as CozeButton,
|
||||
type SelectProps,
|
||||
type CommonFieldProps,
|
||||
type ButtonProps,
|
||||
Toast,
|
||||
Tooltip,
|
||||
Avatar,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
const { Select, TextArea } = Form;
|
||||
|
||||
interface FormType {
|
||||
feedback_type: FeedbackType;
|
||||
plugin_id?: string;
|
||||
feedback: string;
|
||||
}
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: FeedbackType.NotFoundPlugin,
|
||||
label: I18n.t(
|
||||
'plugin_feedback_modal_request_type_official_plugins_not_found',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: FeedbackType.OfficialPlugin,
|
||||
label: I18n.t(
|
||||
'plugin_feedback_modal_request_type_feedback_to_existing_plugin',
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const PluginSelect: FC<SelectProps & CommonFieldProps> = props => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const { data, loading } = useRequest(
|
||||
async () => {
|
||||
const res = await ProductApi.PublicGetProductList({
|
||||
keyword: inputValue,
|
||||
page_num: 1,
|
||||
page_size: 50,
|
||||
sort_type: SortType.Heat,
|
||||
entity_type: ProductEntityType.Plugin,
|
||||
is_official: true,
|
||||
need_extra: false,
|
||||
});
|
||||
const products = res?.data?.products;
|
||||
|
||||
return products?.length ? products : [];
|
||||
},
|
||||
{
|
||||
refreshDeps: [inputValue],
|
||||
},
|
||||
);
|
||||
|
||||
const pluginOptions = data
|
||||
?.filter(p => p.meta_info)
|
||||
.map(plugin => {
|
||||
const meta = plugin.meta_info;
|
||||
return {
|
||||
value: meta.entity_id,
|
||||
label: (
|
||||
<>
|
||||
{meta.icon_url ? (
|
||||
<Avatar
|
||||
size="extra-extra-small"
|
||||
src={meta.icon_url}
|
||||
shape="square"
|
||||
className="mr-[5px]"
|
||||
/>
|
||||
) : null}
|
||||
{meta.name}
|
||||
</>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Select
|
||||
onSearch={debounce((val, event) => {
|
||||
if (event.type === 'change') {
|
||||
setInputValue(val);
|
||||
}
|
||||
}, 800)}
|
||||
loading={loading}
|
||||
optionList={pluginOptions}
|
||||
filter
|
||||
remote
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePluginFeatModal = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const formApi = useRef<FormApi<FormType>>();
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
setSubmitting(true);
|
||||
const vals = await formApi.current?.validate();
|
||||
|
||||
const res = await PluginDevelopApi.CreatePluginFeedback(vals);
|
||||
|
||||
if (res?.code !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.success(I18n.t('plugin_feedback_modal_tip_submission_success'));
|
||||
setVisible(false);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
formApi.current?.setValues({
|
||||
feedback_type: FeedbackType.NotFoundPlugin,
|
||||
plugin_id: undefined,
|
||||
feedback: '',
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
reset();
|
||||
}, [visible]);
|
||||
|
||||
const modal = (
|
||||
<Modal
|
||||
title={I18n.t('plugin_feedback_modal_title')}
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
width={562}
|
||||
footer={
|
||||
<>
|
||||
<CozeButton
|
||||
color="secondary"
|
||||
onClick={() => setVisible(false)}
|
||||
className="mr-[12px]"
|
||||
>
|
||||
{I18n.t('coze_home_delete_modal_btn_cancel')}
|
||||
</CozeButton>
|
||||
<CozeButton onClick={onSubmit} loading={submitting}>
|
||||
{I18n.t('feedback_submit')}
|
||||
</CozeButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form<FormType>
|
||||
getFormApi={api => (formApi.current = api)}
|
||||
render={({ formState, values }) => (
|
||||
<>
|
||||
<Select
|
||||
field="feedback_type"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
label={I18n.t('plugin_feedback_modal_request_type')}
|
||||
optionList={options}
|
||||
className="w-full"
|
||||
/>
|
||||
{values?.feedback_type === FeedbackType.OfficialPlugin && (
|
||||
<PluginSelect
|
||||
field="plugin_id"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t(
|
||||
'plugin_feedback_error_tip_no_official_plugin_choosen',
|
||||
),
|
||||
},
|
||||
]}
|
||||
label={I18n.t('plugin_feedback_modal_choose_official_plugin')}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
<TextArea
|
||||
field="feedback"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('plugin_feedback_error_tip_empty_content'),
|
||||
},
|
||||
]}
|
||||
label={I18n.t('plugin_feedback_modal_feedback_content')}
|
||||
placeholder={I18n.t(
|
||||
'plugin_feedback_modal_feedback_content_placeholder',
|
||||
)}
|
||||
maxCount={2000}
|
||||
maxLength={2000}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
const open = () => setVisible(true);
|
||||
|
||||
const EntryButton = useCallback<FC<ButtonProps>>(
|
||||
props => (
|
||||
<Tooltip content={I18n.t('plugin_feedback_entry_tip')}>
|
||||
<CozeButton onClick={open} size="large" color="primary" {...props}>
|
||||
{I18n.t('plugin_feedback_entry_button')}
|
||||
</CozeButton>
|
||||
</Tooltip>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
open,
|
||||
EntryButton,
|
||||
modal,
|
||||
};
|
||||
};
|
||||
17
frontend/packages/agent-ide/bot-plugin/export/src/typings.d.ts
vendored
Normal file
17
frontend/packages/agent-ide/bot-plugin/export/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,129 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-flags/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-hooks/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-http/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-monaco-editor/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/foundation-sdk/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/logger/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/assets/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/biz-components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/flowgram-adapter/free-layout-editor/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../community/component/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-icons/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-semi/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/stylelint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/ts-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/vitest-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../foundation/enterprise-store-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../mock-set/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../plugin-modal-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../plugin-shared/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/plugin-form-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/plugin-shared/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/premium/premium-store-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-detail/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-plugin/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/user-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tool/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../tools/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../workflow/base/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/agent-ide/bot-plugin/export/tsconfig.json
Normal file
15
frontend/packages/agent-ide/bot-plugin/export/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
],
|
||||
"exclude": ["**/*"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/agent-ide/bot-plugin/mock-set/README.md
Normal file
16
frontend/packages/agent-ide/bot-plugin/mock-set/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-agent-ide/bot-plugin-mock-set
|
||||
|
||||
> Project template for react component with storybook.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] eslint & ts
|
||||
- [x] esm bundle
|
||||
- [x] umd bundle
|
||||
- [x] storybook
|
||||
|
||||
## Commands
|
||||
|
||||
- init: `rush update`
|
||||
- dev: `npm run dev`
|
||||
- build: `npm run build`
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user