feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
5
frontend/packages/devops/mockset-manage/.stylelintrc.js
Normal file
5
frontend/packages/devops/mockset-manage/.stylelintrc.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/devops/mockset-manage/README.md
Normal file
16
frontend/packages/devops/mockset-manage/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-devops/mockset-manage
|
||||
|
||||
> 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`
|
||||
130
frontend/packages/devops/mockset-manage/__tests__/hooks.test.ts
Normal file
130
frontend/packages/devops/mockset-manage/__tests__/hooks.test.ts
Normal file
@@ -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 { renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
type BizCtx,
|
||||
ComponentType,
|
||||
TrafficScene,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
import { type BasicMockSetInfo } from '@/interface';
|
||||
import { useInitialGetEnabledMockSet } from '@/hooks/use-get-mockset';
|
||||
|
||||
vi.mock('@/const', () => ({
|
||||
IS_OVERSEA: true,
|
||||
MockTrafficEnabled: {
|
||||
DISABLE: 0,
|
||||
ENABLE: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-utils', () => ({
|
||||
safeJSONParse: JSON.parse,
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
createLoggerWith: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
},
|
||||
reporter: {
|
||||
createReporterWithPreset: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
debuggerApi: {
|
||||
MGetMockSetBinding: vi.fn().mockResolvedValue({
|
||||
code: 0,
|
||||
msg: '',
|
||||
mockSetBindings: [
|
||||
{
|
||||
mockSetID: '1',
|
||||
mockSubject: {
|
||||
componentID: 'tool1',
|
||||
componentType: ComponentType.CozeTool,
|
||||
parentComponentID: 'plugin1',
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
},
|
||||
bizCtx: {
|
||||
bizSpaceID: '1',
|
||||
trafficCallerID: '1',
|
||||
trafficScene: TrafficScene.CozeSingleAgentDebug,
|
||||
connectorID: '1',
|
||||
connectorUID: '2',
|
||||
},
|
||||
},
|
||||
],
|
||||
mockSetDetails: {
|
||||
'1': {
|
||||
id: '1',
|
||||
name: 'test-detail',
|
||||
mockSubject: {
|
||||
componentID: 'tool1',
|
||||
componentType: ComponentType.CozeTool,
|
||||
parentComponentID: 'plugin1',
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('mock-set-hooks', () => {
|
||||
it('fetch-mock-list', async () => {
|
||||
const TEST_COMMON_BIZ = {
|
||||
connectorID: '1',
|
||||
connectorUID: '2',
|
||||
};
|
||||
|
||||
const TEST_BIZ_CTX1: BizCtx = {
|
||||
bizSpaceID: '1',
|
||||
trafficCallerID: '1',
|
||||
trafficScene: TrafficScene.CozeSingleAgentDebug,
|
||||
};
|
||||
|
||||
const TEST_MOCK_SUBJECT = {
|
||||
componentID: 'tool1',
|
||||
componentType: ComponentType.CozeTool,
|
||||
parentComponentID: 'plugin1',
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
};
|
||||
|
||||
const singleAgentToolItem: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_SUBJECT,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX1,
|
||||
},
|
||||
};
|
||||
const { result } = renderHook(() =>
|
||||
useInitialGetEnabledMockSet({
|
||||
bizCtx: singleAgentToolItem.bizCtx,
|
||||
pollingInterval: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
const { start } = result.current;
|
||||
|
||||
await start();
|
||||
const { data } = result.current;
|
||||
expect(data[0]?.mockSetBinding.mockSetID).toEqual('1');
|
||||
expect(data[0]?.mockSetDetail?.id).toEqual('1');
|
||||
});
|
||||
});
|
||||
330
frontend/packages/devops/mockset-manage/__tests__/utils.test.ts
Normal file
330
frontend/packages/devops/mockset-manage/__tests__/utils.test.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* 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 BizCtx,
|
||||
ComponentType,
|
||||
TrafficScene,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
import {
|
||||
isRealData,
|
||||
isCurrent,
|
||||
isSameWorkflowTool,
|
||||
isSameScene,
|
||||
getPluginInfo,
|
||||
getMockSubjectInfo,
|
||||
getUsedScene,
|
||||
} from '@/utils';
|
||||
import { type BasicMockSetInfo } from '@/interface';
|
||||
|
||||
vi.mock('@coze-arch/bot-utils', () => ({
|
||||
safeJSONParse: JSON.parse,
|
||||
}));
|
||||
|
||||
vi.mock('@/const', () => ({
|
||||
IS_OVERSEA: true,
|
||||
REAL_DATA_MOCKSET: {
|
||||
id: '0',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
createLoggerWith: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const TEST_COMMON_BIZ = {
|
||||
connectorID: '1',
|
||||
connectorUID: '2',
|
||||
};
|
||||
|
||||
const TEST_BIZ_CTX1: BizCtx = {
|
||||
bizSpaceID: '1',
|
||||
trafficCallerID: '1',
|
||||
trafficScene: TrafficScene.CozeSingleAgentDebug,
|
||||
};
|
||||
|
||||
const TEST_BIZ_CTX2: BizCtx = {
|
||||
bizSpaceID: '1',
|
||||
trafficCallerID: '2',
|
||||
trafficScene: TrafficScene.CozeSingleAgentDebug,
|
||||
};
|
||||
|
||||
const TEST_BIZ_CTX3: BizCtx = {
|
||||
bizSpaceID: '1',
|
||||
trafficCallerID: '1',
|
||||
trafficScene: TrafficScene.CozeWorkflowDebug,
|
||||
};
|
||||
|
||||
const TEST_BIZ_CTX4: BizCtx = {
|
||||
bizSpaceID: '2',
|
||||
trafficCallerID: '1',
|
||||
trafficScene: TrafficScene.CozeWorkflowDebug,
|
||||
};
|
||||
|
||||
const TEST_MOCK_SUBJECT1 = {
|
||||
componentID: 'tool1',
|
||||
componentType: ComponentType.CozeTool,
|
||||
parentComponentID: 'plugin1',
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
};
|
||||
|
||||
const TEST_MOCK_SUBJECT2 = {
|
||||
componentID: 'tool2',
|
||||
componentType: ComponentType.CozeTool,
|
||||
parentComponentID: 'plugin1',
|
||||
parentComponentType: ComponentType.CozePlugin,
|
||||
};
|
||||
|
||||
const TEST_MOCK_WORKFLOW_NODE1 = {
|
||||
componentID: 'node1',
|
||||
componentType: ComponentType.CozeToolNode,
|
||||
parentComponentID: 'workflow1',
|
||||
parentComponentType: ComponentType.CozeWorkflow,
|
||||
};
|
||||
|
||||
const TEST_MOCK_WORKFLOW_NODE2 = {
|
||||
componentID: 'node2',
|
||||
componentType: ComponentType.CozeToolNode,
|
||||
parentComponentID: 'workflow1',
|
||||
parentComponentType: ComponentType.CozeWorkflow,
|
||||
};
|
||||
|
||||
const singleAgentToolItem1: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_SUBJECT1,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX1,
|
||||
},
|
||||
};
|
||||
const singleAgentToolItem2: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_SUBJECT2,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX2,
|
||||
},
|
||||
};
|
||||
|
||||
const multiAgentToolItem: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_SUBJECT1,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX1,
|
||||
trafficScene: TrafficScene.CozeMultiAgentDebug,
|
||||
},
|
||||
};
|
||||
|
||||
const workflowToolItem1: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_WORKFLOW_NODE1,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX3,
|
||||
ext: { mockSubjectInfo: JSON.stringify(TEST_MOCK_SUBJECT1) },
|
||||
},
|
||||
};
|
||||
const workflowToolItem2: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_WORKFLOW_NODE1,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX3,
|
||||
ext: { mockSubjectInfo: JSON.stringify(TEST_MOCK_SUBJECT2) },
|
||||
},
|
||||
};
|
||||
const workflowToolItem3: BasicMockSetInfo = {
|
||||
bindSubjectInfo: TEST_MOCK_WORKFLOW_NODE2,
|
||||
bizCtx: {
|
||||
...TEST_COMMON_BIZ,
|
||||
...TEST_BIZ_CTX3,
|
||||
ext: { mockSubjectInfo: JSON.stringify(TEST_MOCK_SUBJECT1) },
|
||||
},
|
||||
};
|
||||
|
||||
describe('mock-set-utils-isRealData', () => {
|
||||
it('real data', () => {
|
||||
const res = isRealData({ id: '0' });
|
||||
|
||||
expect(res).toEqual(true);
|
||||
});
|
||||
it('not real data', () => {
|
||||
const res = isRealData({ id: '123' });
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mock-set-utils-isCurrent', () => {
|
||||
it('same single agent mock tool', () => {
|
||||
const res = isCurrent(singleAgentToolItem1, singleAgentToolItem1);
|
||||
|
||||
expect(res).toEqual(true);
|
||||
});
|
||||
|
||||
it('same agent different mock tool', () => {
|
||||
const res = isCurrent(singleAgentToolItem1, singleAgentToolItem2);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
|
||||
it('different single multi agent mock tool', () => {
|
||||
const res = isCurrent(singleAgentToolItem1, multiAgentToolItem);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
|
||||
it('different single agent workflow mock tool', () => {
|
||||
const res = isCurrent(singleAgentToolItem1, workflowToolItem1);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
|
||||
it('same workflow mock tool', () => {
|
||||
const res = isCurrent(workflowToolItem1, workflowToolItem1);
|
||||
|
||||
expect(res).toEqual(true);
|
||||
});
|
||||
|
||||
it('different workflow mock tool', () => {
|
||||
const res = isCurrent(workflowToolItem1, workflowToolItem2);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
it('different workflow mock tool', () => {
|
||||
const res = isCurrent(workflowToolItem1, workflowToolItem2);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
it('different workflow mock node', () => {
|
||||
const res = isCurrent(workflowToolItem1, workflowToolItem3);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mock-set-utils-isSameWorkflowTool', () => {
|
||||
it('workflow same mock subject', () => {
|
||||
const res = isSameWorkflowTool(
|
||||
workflowToolItem1.bizCtx.ext?.mockSubjectInfo || '',
|
||||
workflowToolItem1.bizCtx.ext?.mockSubjectInfo || '',
|
||||
);
|
||||
|
||||
expect(res).toEqual(true);
|
||||
});
|
||||
|
||||
it('different mock subject', () => {
|
||||
const res = isSameWorkflowTool(
|
||||
workflowToolItem1.bizCtx.ext?.mockSubjectInfo || '',
|
||||
workflowToolItem2.bizCtx.ext?.mockSubjectInfo || '',
|
||||
);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mock-set-utils-isSameScene', () => {
|
||||
it('isSameScene', () => {
|
||||
const res = isSameScene(TEST_BIZ_CTX1, TEST_BIZ_CTX1);
|
||||
|
||||
expect(res).toEqual(true);
|
||||
});
|
||||
it('different caller id', () => {
|
||||
const res = isSameScene(TEST_BIZ_CTX1, TEST_BIZ_CTX2);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
it('different used scene', () => {
|
||||
const res = isSameScene(TEST_BIZ_CTX1, TEST_BIZ_CTX3);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
it('different space id', () => {
|
||||
const res = isSameScene(TEST_BIZ_CTX1, TEST_BIZ_CTX4);
|
||||
|
||||
expect(res).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mock-set-utils-getPluginInfo', () => {
|
||||
it('get agent tool info', () => {
|
||||
const res = getPluginInfo(TEST_BIZ_CTX1, TEST_MOCK_SUBJECT1);
|
||||
|
||||
expect(res.pluginID).toEqual(TEST_MOCK_SUBJECT1.parentComponentID);
|
||||
expect(res.toolID).toEqual(TEST_MOCK_SUBJECT1.componentID);
|
||||
expect(res.spaceID).toEqual(TEST_BIZ_CTX1.bizSpaceID);
|
||||
});
|
||||
|
||||
it('get workflow tool info', () => {
|
||||
const res = getPluginInfo(
|
||||
workflowToolItem1.bizCtx,
|
||||
workflowToolItem1.bindSubjectInfo,
|
||||
);
|
||||
|
||||
expect(res.pluginID).toEqual(TEST_MOCK_SUBJECT1.parentComponentID);
|
||||
expect(res.toolID).toEqual(TEST_MOCK_SUBJECT1.componentID);
|
||||
expect(res.spaceID).toEqual(TEST_BIZ_CTX3.bizSpaceID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mock-set-utils-getMockSubjectInfo', () => {
|
||||
it('get workflow mock subject info', () => {
|
||||
const res = getMockSubjectInfo(
|
||||
workflowToolItem1.bizCtx,
|
||||
workflowToolItem1.bindSubjectInfo,
|
||||
);
|
||||
|
||||
expect(res.componentID).toEqual(TEST_MOCK_SUBJECT1.componentID);
|
||||
expect(res.componentType).toEqual(TEST_MOCK_SUBJECT1.componentType);
|
||||
expect(res.parentComponentID).toEqual(TEST_MOCK_SUBJECT1.parentComponentID);
|
||||
expect(res.parentComponentType).toEqual(
|
||||
TEST_MOCK_SUBJECT1.parentComponentType,
|
||||
);
|
||||
});
|
||||
|
||||
it('get agent subject info', () => {
|
||||
const res = getMockSubjectInfo(
|
||||
singleAgentToolItem1.bizCtx,
|
||||
singleAgentToolItem1.bindSubjectInfo,
|
||||
);
|
||||
|
||||
expect(res.componentID).toEqual(TEST_MOCK_SUBJECT1.componentID);
|
||||
expect(res.componentType).toEqual(TEST_MOCK_SUBJECT1.componentType);
|
||||
expect(res.parentComponentID).toEqual(TEST_MOCK_SUBJECT1.parentComponentID);
|
||||
expect(res.parentComponentType).toEqual(
|
||||
TEST_MOCK_SUBJECT1.parentComponentType,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mock-set-utils-getMockSubjectInfo', () => {
|
||||
it('get single agent', () => {
|
||||
const res = getUsedScene(TrafficScene.CozeSingleAgentDebug);
|
||||
|
||||
expect(res).toEqual('bot');
|
||||
});
|
||||
|
||||
it('get multi agent', () => {
|
||||
const res = getUsedScene(TrafficScene.CozeMultiAgentDebug);
|
||||
|
||||
expect(res).toEqual('agent');
|
||||
});
|
||||
|
||||
it('get workflow', () => {
|
||||
const res = getUsedScene(TrafficScene.CozeWorkflowDebug);
|
||||
|
||||
expect(res).toEqual('flow');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
7
frontend/packages/devops/mockset-manage/eslint.config.js
Normal file
7
frontend/packages/devops/mockset-manage/eslint.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
73
frontend/packages/devops/mockset-manage/package.json
Normal file
73
frontend/packages/devops/mockset-manage/package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "@coze-devops/mockset-manage",
|
||||
"version": "0.0.1",
|
||||
"description": "plugin mockset management",
|
||||
"license": "Apache-2.0",
|
||||
"author": "linyueqiang@bytedance.com",
|
||||
"maintainers": [],
|
||||
"main": "src/index.tsx",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-env": "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-semi": "workspace:*",
|
||||
"@coze-arch/bot-studio-store": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/bot-utils": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/logger": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@coze-studio/user-store": "workspace:*",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"@douyinfe/semi-illustrations": "^2.36.0",
|
||||
"@douyinfe/semi-ui": "~2.72.3",
|
||||
"@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/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"ahooks": "^3.7.8",
|
||||
"axios": "^1.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
"debug": "^4.3.4",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@coze-studio/user-store": "workspace:*",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"@douyinfe/semi-illustrations": "^2.36.0",
|
||||
"ahooks": "^3.7.8",
|
||||
"axios": "^1.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"// deps": "debug@^4.3.4 为脚本自动补齐,请勿改动"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.auto-generate-select {
|
||||
:global {
|
||||
.semi-radioGroup-horizontal {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.semi-radio-cardRadioGroup {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
border: 1px solid var(--semi-color-border);
|
||||
}
|
||||
|
||||
.semi-radio-checked.semi-radio-cardRadioGroup {
|
||||
border: 1px solid var(--semi-color-primary);
|
||||
}
|
||||
|
||||
.semi-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auto-generate-radio {
|
||||
:global {
|
||||
.semi-radio-content {
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auto-generate-label {
|
||||
margin: 16px 0 8px;
|
||||
color: var(--semi-color-text-1);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { PluginMockDataGenerateMode } from '@coze-arch/bot-tea';
|
||||
import { RadioGroup, Radio, InputNumber } from '@coze-arch/bot-semi';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { type RadioChangeEvent } from '@douyinfe/semi-ui/lib/es/radio';
|
||||
|
||||
import {
|
||||
getLatestAutoGenerationChoice,
|
||||
setLatestAutoGenerationChoice,
|
||||
} from '../../utils/auto-generate-storage';
|
||||
|
||||
import style from './index.module.less';
|
||||
|
||||
interface AutoGenerateSelectProps {
|
||||
showLabel?: boolean;
|
||||
enableMulti?: boolean;
|
||||
defaultCount?: number;
|
||||
onChange?: (mode: AutoGenerateConfig) => void;
|
||||
onInit?: (mode: AutoGenerateConfig) => void;
|
||||
}
|
||||
|
||||
export interface AutoGenerateConfig {
|
||||
generateMode: PluginMockDataGenerateMode;
|
||||
generateCount?: number;
|
||||
}
|
||||
|
||||
export function AutoGenerateSelect({
|
||||
showLabel,
|
||||
enableMulti,
|
||||
defaultCount,
|
||||
onChange,
|
||||
onInit,
|
||||
}: AutoGenerateSelectProps) {
|
||||
const [config, setConfig] = useState<AutoGenerateConfig>({
|
||||
generateMode: PluginMockDataGenerateMode.MANUAL,
|
||||
generateCount: defaultCount || 1,
|
||||
});
|
||||
|
||||
const onSelectChange = (e: RadioChangeEvent) => {
|
||||
const updatedConfig = { ...config, generateMode: e.target.value };
|
||||
setLatestAutoGenerationChoice(e.target.value);
|
||||
setConfig(updatedConfig);
|
||||
onChange?.(updatedConfig);
|
||||
};
|
||||
|
||||
const initMode = async () => {
|
||||
const m = await getLatestAutoGenerationChoice();
|
||||
const initConfig = { ...config, generateMode: m };
|
||||
setConfig(initConfig);
|
||||
onInit?.(initConfig);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initMode();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={style['auto-generate-select']}>
|
||||
{showLabel ? (
|
||||
<h4 className={style['auto-generate-label']}>
|
||||
{I18n.t('plugin_creation_method')}
|
||||
</h4>
|
||||
) : null}
|
||||
|
||||
<RadioGroup
|
||||
type="card"
|
||||
value={config.generateMode}
|
||||
onChange={onSelectChange}
|
||||
>
|
||||
<Radio
|
||||
className={style['auto-generate-radio']}
|
||||
value={PluginMockDataGenerateMode.RANDOM}
|
||||
extra={I18n.t('generate_randomly_based_on_data_type_name')}
|
||||
>
|
||||
{I18n.t('randomlymode')}
|
||||
</Radio>
|
||||
<Radio
|
||||
className={style['auto-generate-radio']}
|
||||
value={PluginMockDataGenerateMode.LLM}
|
||||
extra={I18n.t('intelligently_generated_by_large_language_model')}
|
||||
>
|
||||
{I18n.t('llm_mode')}
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
{enableMulti && showLabel ? (
|
||||
<h4 className={style['auto-generate-label']}>
|
||||
{I18n.t('mock_data_quantity')}
|
||||
</h4>
|
||||
) : null}
|
||||
|
||||
{enableMulti ? (
|
||||
<InputNumber
|
||||
value={config.generateCount}
|
||||
max={5}
|
||||
min={1}
|
||||
onChange={e => {
|
||||
if (!Number.isNaN(Number(e))) {
|
||||
const updatedConfig = { ...config, generateCount: Number(e) };
|
||||
setConfig(updatedConfig);
|
||||
onChange?.(updatedConfig);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
.select-container {
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
|
||||
&.switch-disabled {
|
||||
* {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
color: #1D1C23CC !important;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
span {
|
||||
&[aria-label="small_triangle_down"] {
|
||||
color: #1D1C23CC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-list {
|
||||
max-width: 336px;
|
||||
min-width: 234px;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
color: #1D1C23;
|
||||
|
||||
}
|
||||
|
||||
.semi-select-option-list {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.semi-select-loading-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-selected {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.select-label {
|
||||
svg {
|
||||
>path {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
fill: currentcolor !important;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-option-render-focused {
|
||||
background-color: rgb(46 46 56 / 8%);
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.custom-option-render-selected {
|
||||
font-weight: 600;
|
||||
|
||||
}
|
||||
|
||||
.custom-option-render-disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
color: #1D1C2359;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.select-option-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
padding: 8px 8px 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--semi-color-border);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.create-container {
|
||||
padding: 0 16px;
|
||||
color: #4D53E8;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
color: #4D53E8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.spin-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* 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 @typescript-eslint/no-explicit-any -- 迁移代码 */
|
||||
/* eslint-disable max-lines -- 迁移代码 */
|
||||
/* eslint-disable max-lines-per-function -- 迁移代码 */
|
||||
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
forwardRef,
|
||||
type ForwardedRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useInViewport,
|
||||
useInfiniteScroll,
|
||||
useRequest,
|
||||
useUnmount,
|
||||
} from 'ahooks';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
PluginMockDataGenerateMode,
|
||||
sendTeaEvent,
|
||||
type ParamsTypeDefine,
|
||||
EVENT_NAMES,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
// eslint-disable-next-line @coze-arch/no-pkg-dir-import
|
||||
import { type SemiSelectActions } from '@coze-arch/bot-semi/src/components/ui-select';
|
||||
import {
|
||||
Spin,
|
||||
UIToast,
|
||||
Tooltip,
|
||||
UIButton,
|
||||
UISelect,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
import { SceneType, usePageJumpService } from '@coze-arch/bot-hooks';
|
||||
import { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
TrafficScene,
|
||||
infra,
|
||||
type BizCtx,
|
||||
type MockSet,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
import { IconTick, IconUploadError } from '@douyinfe/semi-icons';
|
||||
|
||||
import { MockSetEditModal } from '../mockset-edit-modal';
|
||||
import { MockSetDeleteModal } from '../mockset-delete-modal';
|
||||
import { type AutoGenerateConfig } from '../auto-generate-select';
|
||||
import {
|
||||
getEnvironment,
|
||||
getMockSubjectInfo,
|
||||
getPluginInfo,
|
||||
getUsedScene,
|
||||
isCurrent,
|
||||
isRealData,
|
||||
} from '../../utils';
|
||||
import {
|
||||
type MockSelectOptionProps,
|
||||
type MockSelectRenderOptionProps,
|
||||
type MockSetSelectProps,
|
||||
MockSetStatus,
|
||||
} from '../../interface';
|
||||
import { useInitialGetEnabledMockSet } from '../../hooks/use-get-mockset';
|
||||
import {
|
||||
CONNECTOR_ID,
|
||||
DELAY_TIME,
|
||||
MOCK_OPTION_LIST,
|
||||
POLLING_INTERVAL,
|
||||
REAL_DATA_ID,
|
||||
REAL_DATA_MOCKSET,
|
||||
} from '../../const';
|
||||
import { MockSetItem } from './option-item';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export function getMockSetOption(mockSet: MockSet): MockSelectOptionProps {
|
||||
const isInValid =
|
||||
!isRealData(mockSet) &&
|
||||
(mockSet?.schemaIncompatible || !mockSet?.mockRuleQuantity);
|
||||
return {
|
||||
value: mockSet?.id || '',
|
||||
label: (
|
||||
<Tooltip
|
||||
key={mockSet?.id}
|
||||
content={I18n.t('mockset_invaild_tip', { MockSetName: mockSet.name })}
|
||||
style={{ display: isInValid ? 'block' : 'none' }}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
'flex items-center w-[100%] min-w-0',
|
||||
styles['select-label'],
|
||||
)}
|
||||
>
|
||||
{isInValid ? (
|
||||
<IconUploadError
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
marginRight: 2,
|
||||
color: '#FF8500',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<span
|
||||
className={classNames(
|
||||
'flex-1 min-w-0 overflow-hidden text-ellipsis',
|
||||
isInValid ? 'text-[#1D1C2359]' : 'text-[#1D1C23CC]',
|
||||
)}
|
||||
>
|
||||
{mockSet?.name || ''}
|
||||
</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
disabled: isInValid,
|
||||
detail: mockSet,
|
||||
};
|
||||
}
|
||||
|
||||
export function getMockSetOptionList(
|
||||
mockSets: MockSet[],
|
||||
): Array<MockSelectOptionProps> {
|
||||
return mockSets.map(mockSet => getMockSetOption(mockSet));
|
||||
}
|
||||
export interface MockSetSelectActions {
|
||||
handleParentNodeDelete: () => void;
|
||||
}
|
||||
|
||||
const MockSetSelectComp = (
|
||||
{
|
||||
bindSubjectInfo: mockSubjectInfo,
|
||||
bizCtx: bizSceneCtx,
|
||||
className,
|
||||
style: baseStyle,
|
||||
readonly,
|
||||
}: MockSetSelectProps,
|
||||
ref: ForwardedRef<MockSetSelectActions>,
|
||||
) => {
|
||||
const { detail: subjectDetail, ...bindSubjectInfo } = mockSubjectInfo;
|
||||
|
||||
const { spaceID, toolID, pluginID } = getPluginInfo(
|
||||
bizSceneCtx,
|
||||
bindSubjectInfo,
|
||||
);
|
||||
const uid = userStoreService.useUserInfo()?.user_id_str;
|
||||
const spaceType = useSpaceStore(s => s.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
const bizCtx: BizCtx = {
|
||||
...bizSceneCtx,
|
||||
connectorUID: uid,
|
||||
connectorID: CONNECTOR_ID, // 业务线为Coze
|
||||
};
|
||||
|
||||
const { jump } = usePageJumpService();
|
||||
|
||||
const [selectedMockSet, setSelectedMockSet] =
|
||||
useState<MockSet>(REAL_DATA_MOCKSET);
|
||||
const selectedValue = getMockSetOption(selectedMockSet);
|
||||
|
||||
const [optionList, setOptionList] = useState<Array<MockSelectOptionProps>>(
|
||||
getMockSetOptionList(MOCK_OPTION_LIST),
|
||||
);
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [deleteMockSet, setDeleteMockSet] = useState<MockSet | undefined>();
|
||||
|
||||
const preSelectionRef = useRef<MockSet>(REAL_DATA_MOCKSET);
|
||||
const selectionDomRef = useRef<HTMLDivElement>(null);
|
||||
const selectionRef = useRef<SemiSelectActions>(null);
|
||||
|
||||
const [inViewPort] = useInViewport(selectionDomRef);
|
||||
const {
|
||||
data: enabledMockSetInfo,
|
||||
addMockComp,
|
||||
removeMockComp,
|
||||
start,
|
||||
cancel,
|
||||
setRestartTimer,
|
||||
} = useInitialGetEnabledMockSet({
|
||||
bizCtx,
|
||||
pollingInterval: POLLING_INTERVAL,
|
||||
});
|
||||
|
||||
const { runAsync: changeMockSet, loading: changeMockSetLoading } = useRequest(
|
||||
async (mockSet: MockSet, isBinding = true) => {
|
||||
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: (mockSet.id as string) || '',
|
||||
where: getUsedScene(bizCtx.trafficScene),
|
||||
};
|
||||
try {
|
||||
await debuggerApi.BindMockSet({
|
||||
mockSetID: isBinding ? mockSet.id : '0',
|
||||
bizCtx,
|
||||
mockSubject: bindSubjectInfo,
|
||||
});
|
||||
isBinding &&
|
||||
sendTeaEvent(EVENT_NAMES.use_mockset_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
});
|
||||
} catch (e) {
|
||||
setSelectedMockSet(preSelectionRef.current);
|
||||
logger.error({ error: e as Error, eventName: 'change_mockset_fail' });
|
||||
isBinding &&
|
||||
sendTeaEvent(EVENT_NAMES.use_mockset_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
error: (e as Error | undefined)?.message as string,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
const handleChange = async (obj?: MockSelectOptionProps) => {
|
||||
cancel();
|
||||
preSelectionRef.current = selectedMockSet;
|
||||
setSelectedMockSet((obj as MockSelectOptionProps)?.detail || {});
|
||||
await changeMockSet((obj as MockSelectOptionProps)?.detail || {});
|
||||
const restartTimerId = setTimeout(() => {
|
||||
start();
|
||||
}, DELAY_TIME);
|
||||
setRestartTimer(restartTimerId);
|
||||
};
|
||||
|
||||
const {
|
||||
reload: fetchOptionList,
|
||||
loadMore,
|
||||
loading,
|
||||
loadingMore,
|
||||
data: optionListData,
|
||||
} = useInfiniteScroll(
|
||||
async d => {
|
||||
try {
|
||||
const res = await debuggerApi.MGetMockSet({
|
||||
bizCtx,
|
||||
mockSubject: getMockSubjectInfo(bizCtx, mockSubjectInfo),
|
||||
pageToken: d?.pageToken,
|
||||
orderBy: infra.OrderBy.UpdateTime,
|
||||
desc: true,
|
||||
});
|
||||
const mockSetList = getMockSetOptionList(res?.mockSets || []);
|
||||
|
||||
return {
|
||||
list: mockSetList || [],
|
||||
pageToken: res?.pageToken,
|
||||
hasMore: res?.hasMore ?? true,
|
||||
schema: res?.schema,
|
||||
count: res?.count,
|
||||
};
|
||||
} catch (e) {
|
||||
logger.error({
|
||||
error: e as Error,
|
||||
eventName: 'mockset_list_fetch_fail',
|
||||
});
|
||||
return {
|
||||
list: [],
|
||||
pageToken: d?.pageToken,
|
||||
hasMore: d?.hasMore,
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleParentNodeDelete: () => {
|
||||
changeMockSet(selectedMockSet, false);
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
const newOptionList = [
|
||||
getMockSetOption(REAL_DATA_MOCKSET),
|
||||
...(optionListData?.list || []),
|
||||
];
|
||||
setOptionList(newOptionList);
|
||||
}, [optionListData]);
|
||||
|
||||
useEffect(() => {
|
||||
const mockSetInfo = enabledMockSetInfo.find(mockInfo =>
|
||||
isCurrent(
|
||||
{
|
||||
bizCtx: mockInfo?.mockSetBinding?.bizCtx || {},
|
||||
bindSubjectInfo: mockInfo?.mockSetBinding?.mockSubject || {},
|
||||
},
|
||||
{
|
||||
bizCtx,
|
||||
bindSubjectInfo,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (changeMockSetLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mockSetInfo?.mockSetDetail) {
|
||||
setSelectedMockSet(mockSetInfo?.mockSetDetail);
|
||||
} else {
|
||||
setSelectedMockSet(REAL_DATA_MOCKSET);
|
||||
}
|
||||
}, [enabledMockSetInfo]);
|
||||
|
||||
useUnmount(() => {
|
||||
const length = removeMockComp({ bizCtx, bindSubjectInfo });
|
||||
if (!length) {
|
||||
cancel();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inViewPort) {
|
||||
const length = addMockComp({ bizCtx, bindSubjectInfo });
|
||||
if (length === 1) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
const length = removeMockComp({ bizCtx, bindSubjectInfo });
|
||||
if (!length) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}, [inViewPort]);
|
||||
|
||||
const closePanel = () => {
|
||||
selectionRef?.current?.close();
|
||||
};
|
||||
|
||||
const handleView = (
|
||||
record?: MockSet,
|
||||
autoGenerateConfig?: AutoGenerateConfig,
|
||||
) => {
|
||||
const { trafficScene } = bizCtx || {};
|
||||
const { id } = record || {};
|
||||
if (spaceID && pluginID && toolID && id) {
|
||||
jump(
|
||||
trafficScene === TrafficScene.CozeWorkflowDebug
|
||||
? SceneType.WORKFLOW__TO__PLUGIN_MOCK_DATA
|
||||
: SceneType.BOT__TO__PLUGIN_MOCK_DATA,
|
||||
{
|
||||
spaceId: spaceID,
|
||||
pluginId: pluginID,
|
||||
toolId: toolID,
|
||||
toolName: subjectDetail?.name,
|
||||
mockSetId: String(id),
|
||||
mockSetName: record?.name,
|
||||
bizCtx: JSON.stringify(bizCtx),
|
||||
bindSubjectInfo: JSON.stringify(bindSubjectInfo),
|
||||
generationMode: autoGenerateConfig?.generateMode,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderCreateMockSet = () => (
|
||||
<>
|
||||
<div className={styles.divider}></div>
|
||||
<div
|
||||
onClick={() => {
|
||||
setShowCreateModal(true);
|
||||
closePanel();
|
||||
}}
|
||||
className={styles['create-container']}
|
||||
>
|
||||
<IconAdd
|
||||
className="mr-[10px]"
|
||||
style={{ fontSize: 14, color: '#4D53E8' }}
|
||||
/>
|
||||
<span>{I18n.t('create_mockset')}</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderLoadMore = () =>
|
||||
loading ||
|
||||
(optionListData?.list?.length || 0) >=
|
||||
(optionListData?.count || 0) ? null : (
|
||||
<div
|
||||
className={classNames(
|
||||
styles['select-option-container'],
|
||||
styles['load-more'],
|
||||
)}
|
||||
>
|
||||
{loadingMore ? (
|
||||
<>
|
||||
<Spin wrapperClassName={styles['spin-icon']} />
|
||||
<span>{I18n.t('Loading')}</span>
|
||||
</>
|
||||
) : (
|
||||
<UIButton
|
||||
onClick={loadMore}
|
||||
theme="borderless"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{I18n.t('Load More' as any)}
|
||||
</UIButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderOptionItem = (renderProps: MockSelectRenderOptionProps) => {
|
||||
const {
|
||||
disabled,
|
||||
selected,
|
||||
value,
|
||||
focused,
|
||||
style,
|
||||
onMouseEnter,
|
||||
onClick,
|
||||
detail,
|
||||
} = renderProps;
|
||||
|
||||
const getTooltipInfo = () => {
|
||||
if (detail?.schemaIncompatible) {
|
||||
return I18n.t('tool_updated_check_mockset_compatibility');
|
||||
} else if ((detail?.mockRuleQuantity || 0) <= 0) {
|
||||
return I18n.t('mockset_is_empty_add_data_before_use');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
zIndex={110}
|
||||
content={getTooltipInfo()}
|
||||
visible={disabled && focused}
|
||||
position="left"
|
||||
style={{ display: disabled ? 'block' : 'none' }} // visible disabled不生效
|
||||
>
|
||||
<div
|
||||
style={style}
|
||||
className={classNames(
|
||||
styles['select-option-container'],
|
||||
focused && styles['custom-option-render-focused'],
|
||||
disabled && styles['custom-option-render-disabled'],
|
||||
selected && styles['custom-option-render-selected'],
|
||||
)}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<div className="w-[16px] h-[16px] mr-[8px]">
|
||||
{selected ? (
|
||||
<IconTick style={{ fontSize: 14 }} className="text-[#4D53E8]" />
|
||||
) : (
|
||||
<div className="w-[16px]"></div>
|
||||
)}
|
||||
</div>
|
||||
{value === REAL_DATA_ID ? (
|
||||
<span>{I18n.t('real_data')}</span>
|
||||
) : (
|
||||
<MockSetItem
|
||||
status={
|
||||
detail?.schemaIncompatible
|
||||
? MockSetStatus.Incompatible
|
||||
: MockSetStatus.Normal
|
||||
}
|
||||
name={detail?.name || ''}
|
||||
onDelete={() => {
|
||||
closePanel();
|
||||
setDeleteMockSet(detail);
|
||||
}}
|
||||
onView={() => {
|
||||
handleView(detail);
|
||||
}}
|
||||
disableCreator={isPersonal}
|
||||
viewOnly={uid !== detail?.creator?.ID}
|
||||
creatorName={detail?.creator?.name}
|
||||
className="flex-1 min-w-0"
|
||||
></MockSetItem>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={selectionDomRef} style={baseStyle} className={className}>
|
||||
<UISelect
|
||||
stopPropagation
|
||||
disabled={readonly || changeMockSetLoading}
|
||||
className={classNames(
|
||||
styles['select-container'],
|
||||
changeMockSetLoading && styles['switch-disabled'],
|
||||
)}
|
||||
ref={selectionRef}
|
||||
selectedClassname={styles['item-selected']}
|
||||
optionList={optionList}
|
||||
dropdownClassName={styles['option-list']}
|
||||
outerBottomSlot={renderCreateMockSet()}
|
||||
innerBottomSlot={renderLoadMore()}
|
||||
onDropdownVisibleChange={(visible: boolean) => {
|
||||
if (visible) {
|
||||
fetchOptionList();
|
||||
} else {
|
||||
setOptionList([getMockSetOption(REAL_DATA_MOCKSET)]);
|
||||
}
|
||||
}}
|
||||
loading={loading}
|
||||
renderOptionItem={renderOptionItem}
|
||||
value={selectedValue}
|
||||
onChangeWithObject
|
||||
onChange={async obj => {
|
||||
await handleChange(obj as unknown as MockSelectOptionProps);
|
||||
}}
|
||||
/>
|
||||
{showCreateModal ? (
|
||||
<MockSetEditModal
|
||||
zIndex={9999}
|
||||
visible={showCreateModal}
|
||||
onCancel={() => setShowCreateModal(false)}
|
||||
onSuccess={(info, autoGenerateConfig) => {
|
||||
const { id } = info || {};
|
||||
setShowCreateModal(false);
|
||||
|
||||
const msgMap = {
|
||||
[PluginMockDataGenerateMode.LLM]: I18n.t(
|
||||
'created_mockset_please_add_mock_data_llm_generation',
|
||||
),
|
||||
[PluginMockDataGenerateMode.RANDOM]: I18n.t(
|
||||
'created_mockset_please_add_mock_data_random_generation',
|
||||
),
|
||||
[PluginMockDataGenerateMode.MANUAL]: I18n.t(
|
||||
'created_mockset_please_add_mock_data',
|
||||
),
|
||||
};
|
||||
UIToast.success(
|
||||
msgMap[
|
||||
autoGenerateConfig?.generateMode ||
|
||||
PluginMockDataGenerateMode.MANUAL
|
||||
],
|
||||
);
|
||||
|
||||
handleView({ id }, autoGenerateConfig);
|
||||
}}
|
||||
initialInfo={{
|
||||
bizCtx,
|
||||
bindSubjectInfo,
|
||||
name: subjectDetail?.name,
|
||||
}}
|
||||
needResetPopoverContainer={
|
||||
bizCtx.trafficScene === TrafficScene.CozeWorkflowDebug
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{deleteMockSet ? (
|
||||
<MockSetDeleteModal
|
||||
zIndex={9999}
|
||||
visible={!!deleteMockSet}
|
||||
mockSetInfo={{
|
||||
detail: deleteMockSet,
|
||||
ctx: { bizCtx, mockSubjectInfo: bindSubjectInfo },
|
||||
}}
|
||||
onSuccess={() => {
|
||||
deleteMockSet.id === selectedMockSet.id &&
|
||||
setSelectedMockSet(REAL_DATA_MOCKSET);
|
||||
setDeleteMockSet(undefined);
|
||||
cancel();
|
||||
start();
|
||||
}}
|
||||
onCancel={() => setDeleteMockSet(undefined)}
|
||||
needResetPopoverContainer={
|
||||
bizCtx.trafficScene === TrafficScene.CozeWorkflowDebug
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MockSetSelect = forwardRef(MockSetSelectComp);
|
||||
@@ -0,0 +1,45 @@
|
||||
.mock-select-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: '100%';
|
||||
}
|
||||
|
||||
.mock-main-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.status-icon {
|
||||
margin-right: 4px;
|
||||
color: #FF8500;
|
||||
}
|
||||
|
||||
.mock-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mock-extra-info {
|
||||
width: 64px;
|
||||
margin-left: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
.creator-name {
|
||||
color: #1D1C2359;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.operation-icon {
|
||||
font-size: 14px;
|
||||
color: #1D1C2399;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 CSSProperties } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Typography, UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { IconDeleteOutline, IconEdit } from '@coze-arch/bot-icons';
|
||||
import { IconEyeOpened, IconUploadError } from '@douyinfe/semi-icons';
|
||||
|
||||
import { MockSetStatus } from '../../interface';
|
||||
|
||||
import styles from './option-item.module.less';
|
||||
|
||||
export interface MockSetItemProps {
|
||||
name: string;
|
||||
onDelete?: () => void;
|
||||
onView?: () => void;
|
||||
status?: MockSetStatus;
|
||||
creatorName?: string;
|
||||
viewOnly?: boolean;
|
||||
disableCreator?: boolean;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const MockSetItem = ({
|
||||
name,
|
||||
onDelete,
|
||||
onView,
|
||||
status = MockSetStatus.Normal,
|
||||
creatorName,
|
||||
viewOnly,
|
||||
disableCreator,
|
||||
className,
|
||||
style,
|
||||
}: MockSetItemProps) => {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const renderExtraInfo = () => {
|
||||
if (isHover) {
|
||||
return (
|
||||
<div
|
||||
className={styles['operation-icon']}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{viewOnly ? (
|
||||
<UIIconButton onClick={onView} icon={<IconEyeOpened />} />
|
||||
) : (
|
||||
<>
|
||||
<UIIconButton
|
||||
onClick={onView}
|
||||
icon={<IconEdit />}
|
||||
wrapperClass="mr-[4px]"
|
||||
/>
|
||||
<UIIconButton onClick={onDelete} icon={<IconDeleteOutline />} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return disableCreator ? null : (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: creatorName },
|
||||
},
|
||||
}}
|
||||
className={styles['creator-name']}
|
||||
>
|
||||
{creatorName}
|
||||
</Typography.Text>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles['mock-select-item'], className)}
|
||||
style={style}
|
||||
onMouseEnter={() => {
|
||||
setIsHover(true);
|
||||
}}
|
||||
onMouseLeave={() => setIsHover(false)}
|
||||
>
|
||||
<span className={styles['mock-main-info']}>
|
||||
{status !== MockSetStatus.Normal && (
|
||||
<IconUploadError className={styles['status-icon']} />
|
||||
)}
|
||||
<Typography.Text ellipsis={{}} className={styles['mock-name']}>
|
||||
{name}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
<div className={styles['mock-extra-info']}>{renderExtraInfo()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 { useEffect, useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
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 { UIModal } from '@coze-arch/bot-semi';
|
||||
import { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
type BizCtx,
|
||||
type MockSet,
|
||||
type ComponentSubject,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
|
||||
import { getEnvironment, getPluginInfo } from '../../utils';
|
||||
|
||||
export interface MockSetInfo {
|
||||
detail: MockSet;
|
||||
ctx?: {
|
||||
mockSubjectInfo?: ComponentSubject;
|
||||
bizCtx?: BizCtx;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MockSetEditModalProps {
|
||||
visible: boolean;
|
||||
zIndex?: number;
|
||||
mockSetInfo: MockSetInfo;
|
||||
onSuccess?: () => void;
|
||||
onCancel?: () => void;
|
||||
needResetPopoverContainer?: boolean;
|
||||
}
|
||||
|
||||
function isValidRefCount(refCount: number) {
|
||||
return refCount >= 0;
|
||||
}
|
||||
|
||||
export const MockSetDeleteModal = ({
|
||||
visible,
|
||||
mockSetInfo,
|
||||
onSuccess,
|
||||
onCancel,
|
||||
zIndex,
|
||||
needResetPopoverContainer,
|
||||
}: MockSetEditModalProps) => {
|
||||
const {
|
||||
detail: { id },
|
||||
ctx,
|
||||
} = mockSetInfo || {};
|
||||
const [mockSetRefCount, setMockSetRefCount] = useState(-1);
|
||||
|
||||
// space信息
|
||||
const spaceType = useSpaceStore(s => s.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
const { run: fetchRefInfo } = useRequest(
|
||||
async () => {
|
||||
const { spaceID } = getPluginInfo(
|
||||
ctx?.bizCtx || {},
|
||||
ctx?.mockSubjectInfo || {},
|
||||
);
|
||||
try {
|
||||
const { usersUsageCount } = await debuggerApi.GetMockSetUsageInfo({
|
||||
mockSetID: id,
|
||||
spaceID,
|
||||
});
|
||||
setMockSetRefCount(Number(usersUsageCount ?? 0));
|
||||
} catch (e) {
|
||||
logger.error({
|
||||
error: e as Error,
|
||||
eventName: 'fetch_mockset_ref_fail',
|
||||
});
|
||||
setMockSetRefCount(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
fetchRefInfo();
|
||||
}, [mockSetInfo]);
|
||||
|
||||
const renderTitle =
|
||||
mockSetRefCount > 0
|
||||
? I18n.t('people_using_mockset_delete', { num: mockSetRefCount })
|
||||
: I18n.t('delete_the_mockset');
|
||||
|
||||
const handleOk = async () => {
|
||||
const { toolID, spaceID } = getPluginInfo(
|
||||
ctx?.bizCtx || {},
|
||||
ctx?.mockSubjectInfo || {},
|
||||
);
|
||||
const basicParams: ParamsTypeDefine[EVENT_NAMES.del_mockset_front] = {
|
||||
environment: getEnvironment(),
|
||||
workspace_id: spaceID || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
tool_id: toolID || '',
|
||||
mock_set_id: String(id) || '',
|
||||
status: 1,
|
||||
};
|
||||
try {
|
||||
id && (await debuggerApi.DeleteMockSet({ id, bizCtx: ctx?.bizCtx }));
|
||||
onSuccess?.();
|
||||
sendTeaEvent(EVENT_NAMES.del_mockset_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
});
|
||||
} catch (e) {
|
||||
sendTeaEvent(EVENT_NAMES.del_mockset_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
error: (e as Error | undefined)?.message as string,
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<UIModal
|
||||
type="info"
|
||||
zIndex={zIndex}
|
||||
icon={
|
||||
<IconAlertCircle
|
||||
size="extra-large"
|
||||
className="inline-flex text-[#FF2710]"
|
||||
/>
|
||||
}
|
||||
title={renderTitle}
|
||||
visible={isValidRefCount(mockSetRefCount) && visible}
|
||||
onCancel={onCancel}
|
||||
onOk={handleOk}
|
||||
getPopupContainer={
|
||||
needResetPopoverContainer ? () => document.body : undefined
|
||||
}
|
||||
okType="danger"
|
||||
>
|
||||
{I18n.t('operation_cannot_be_reversed')}
|
||||
</UIModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
.mockset-create-form {
|
||||
font-size: 14px;
|
||||
|
||||
:global {
|
||||
.semi-checkbox-addon {
|
||||
font-weight: 600;
|
||||
color: var(--semi-color-text-0);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.semi-form-field {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 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 { useRef } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { PluginMockDataGenerateMode } from '@coze-arch/bot-tea';
|
||||
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 { Form, UIFormTextArea, UIModal, UIToast } from '@coze-arch/bot-semi';
|
||||
import { type ApiError, isApiError } from '@coze-arch/bot-http';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
import { type MockSet } from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
type AutoGenerateConfig,
|
||||
AutoGenerateSelect,
|
||||
} from '../auto-generate-select';
|
||||
import { getEnvironment, getMockSubjectInfo, getPluginInfo } from '../../utils';
|
||||
import { type BasicMockSetInfo } from '../../interface';
|
||||
import { MOCK_SET_ERR_CODE, mockSetInfoRules } from '../../const';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface EditMockSetInfo
|
||||
extends BasicMockSetInfo,
|
||||
Partial<AutoGenerateConfig> {
|
||||
id?: string;
|
||||
name?: string;
|
||||
desc?: string;
|
||||
autoGenerate?: boolean;
|
||||
}
|
||||
|
||||
export interface MockSetEditModalProps {
|
||||
visible?: boolean;
|
||||
zIndex?: number;
|
||||
disabled?: boolean;
|
||||
initialInfo: EditMockSetInfo;
|
||||
onSuccess?: (
|
||||
mockSetInfo?: MockSet,
|
||||
autoGenerateConfig?: AutoGenerateConfig,
|
||||
) => void;
|
||||
onCancel?: () => void;
|
||||
needResetPopoverContainer?: boolean;
|
||||
}
|
||||
|
||||
function getRandomName(toolName?: string): string | undefined {
|
||||
if (!toolName) {
|
||||
return undefined;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
const num = Math.floor(Math.random() * 90 + 10);
|
||||
return `${toolName} mockset${num}`;
|
||||
}
|
||||
|
||||
export const MockSetEditModal = ({
|
||||
visible,
|
||||
zIndex,
|
||||
disabled,
|
||||
initialInfo,
|
||||
onSuccess,
|
||||
onCancel,
|
||||
needResetPopoverContainer,
|
||||
}: MockSetEditModalProps) => {
|
||||
const formApiRef = useRef<FormApi<EditMockSetInfo>>();
|
||||
|
||||
const [FLAGS] = useFlags();
|
||||
|
||||
// 根据是否传入 id 判断是否为创建场景
|
||||
const isCreate = !initialInfo.id;
|
||||
|
||||
// space信息
|
||||
const spaceType = useSpaceStore(s => s.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
const handleAutoGenerateSelect = (config: AutoGenerateConfig) => {
|
||||
formApiRef.current?.setValue?.('generateMode', String(config.generateMode));
|
||||
formApiRef.current?.setValue?.(
|
||||
'generateCount',
|
||||
String(config.generateCount),
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = async (formValues: EditMockSetInfo) => {
|
||||
const {
|
||||
id: existingId,
|
||||
name,
|
||||
desc,
|
||||
bindSubjectInfo,
|
||||
bizCtx,
|
||||
autoGenerate,
|
||||
generateMode,
|
||||
generateCount,
|
||||
} = formValues;
|
||||
const { toolID, spaceID } = getPluginInfo(bizCtx, bindSubjectInfo);
|
||||
const basicParams: ParamsTypeDefine[EVENT_NAMES.create_mockset_front] = {
|
||||
environment: getEnvironment(),
|
||||
workspace_id: spaceID || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
tool_id: toolID || '',
|
||||
status: 1,
|
||||
mock_set_id: '',
|
||||
auto_gen_mode: !autoGenerate
|
||||
? PluginMockDataGenerateMode.MANUAL
|
||||
: Number(generateMode) || PluginMockDataGenerateMode.RANDOM,
|
||||
mock_counts: 1,
|
||||
};
|
||||
try {
|
||||
const { id } = await debuggerApi.SaveMockSet(
|
||||
{
|
||||
name,
|
||||
description: desc,
|
||||
mockSubject: getMockSubjectInfo(bizCtx, bindSubjectInfo),
|
||||
bizCtx,
|
||||
id: existingId || '0',
|
||||
},
|
||||
{ __disableErrorToast: true },
|
||||
);
|
||||
onSuccess?.(
|
||||
{ id, name, description: desc },
|
||||
autoGenerate
|
||||
? {
|
||||
generateMode: Number(generateMode),
|
||||
generateCount: Number(generateCount),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
sendTeaEvent(EVENT_NAMES.create_mockset_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
mock_set_id: String(id) || '',
|
||||
});
|
||||
} catch (e) {
|
||||
const { message } = (e as Error | undefined) || {};
|
||||
const reportParams: ParamsTypeDefine[EVENT_NAMES.create_mockset_front] = {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
error: message,
|
||||
error_type: 'unknown',
|
||||
};
|
||||
|
||||
if (isApiError(e)) {
|
||||
const { code } = (e as ApiError) || {};
|
||||
|
||||
if (Number(code) === MOCK_SET_ERR_CODE.REPEAT_NAME) {
|
||||
formApiRef.current?.setError('name', I18n.t('name_already_taken'));
|
||||
sendTeaEvent(EVENT_NAMES.create_mockset_front, {
|
||||
...reportParams,
|
||||
error_type: 'repeat_name',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (message) {
|
||||
UIToast.error(message);
|
||||
}
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.create_mockset_front, {
|
||||
...reportParams,
|
||||
error_type: 'unknown',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOk = async () => {
|
||||
await formApiRef.current?.submitForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<UIModal
|
||||
type="action-small"
|
||||
zIndex={zIndex}
|
||||
title={`${isCreate ? I18n.t('create_mockset') : I18n.t('edit_mockset')}`}
|
||||
visible={visible}
|
||||
getPopupContainer={
|
||||
needResetPopoverContainer ? () => document.body : undefined
|
||||
}
|
||||
onCancel={onCancel}
|
||||
okButtonProps={{
|
||||
onClick: handleOk,
|
||||
disabled,
|
||||
}}
|
||||
>
|
||||
<Form<EditMockSetInfo>
|
||||
getFormApi={api => (formApiRef.current = api)}
|
||||
showValidateIcon={false}
|
||||
initValues={
|
||||
isCreate
|
||||
? { ...initialInfo, name: getRandomName(initialInfo.name) }
|
||||
: initialInfo
|
||||
}
|
||||
onSubmit={values => handleSubmit(values)}
|
||||
className={styles['mockset-create-form']}
|
||||
>
|
||||
{({ formState }) => (
|
||||
<>
|
||||
{/* mockSet名称 */}
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('mockset_name'),
|
||||
required: true,
|
||||
}}
|
||||
>
|
||||
<div>{initialInfo?.name}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<UIFormTextArea
|
||||
field="name"
|
||||
label={I18n.t('mockset_name')}
|
||||
placeholder={I18n.t('good_mockset_name_descriptive_concise')}
|
||||
trigger={['blur', 'change']}
|
||||
maxCount={50}
|
||||
maxLength={50}
|
||||
rows={1}
|
||||
onBlur={() => {
|
||||
formApiRef.current?.setValue(
|
||||
'name',
|
||||
formApiRef.current?.getValue('name')?.trim(),
|
||||
);
|
||||
}}
|
||||
rules={mockSetInfoRules.name}
|
||||
/>
|
||||
)}
|
||||
{/* mockSet描述 */}
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
text: I18n.t('mockset_description'),
|
||||
}}
|
||||
>
|
||||
<div>{initialInfo?.desc}</div>
|
||||
</Form.Slot>
|
||||
) : (
|
||||
<UIFormTextArea
|
||||
field="desc"
|
||||
label={{
|
||||
text: I18n.t('mockset_description'),
|
||||
}}
|
||||
trigger={['blur', 'change']}
|
||||
placeholder={I18n.t('describe_use_scenarios_of_mockset')}
|
||||
rows={2}
|
||||
maxCount={2000}
|
||||
maxLength={2000}
|
||||
rules={mockSetInfoRules.desc}
|
||||
onBlur={() => {
|
||||
formApiRef.current?.setValue(
|
||||
'desc',
|
||||
formApiRef.current?.getValue('desc')?.trim(),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* 二期支持autoGenerate*/}
|
||||
{/* 社区版暂不支持该功能 */}
|
||||
{isCreate && FLAGS['bot.devops.mockset_auto_generate'] ? (
|
||||
<>
|
||||
<Form.Checkbox
|
||||
field="autoGenerate"
|
||||
noLabel
|
||||
disabled={disabled}
|
||||
className={styles['auto-generate-checkbox']}
|
||||
>
|
||||
{I18n.t('auto_generate')}
|
||||
</Form.Checkbox>
|
||||
{formState.values.autoGenerate ? (
|
||||
<AutoGenerateSelect
|
||||
onInit={handleAutoGenerateSelect}
|
||||
onChange={handleAutoGenerateSelect}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</UIModal>
|
||||
);
|
||||
};
|
||||
73
frontend/packages/devops/mockset-manage/src/const.ts
Normal file
73
frontend/packages/devops/mockset-manage/src/const.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type RuleItem } from '@coze-arch/bot-semi/Form';
|
||||
|
||||
export const REAL_DATA_ID = '0';
|
||||
|
||||
export const REAL_DATA_MOCKSET = {
|
||||
id: REAL_DATA_ID,
|
||||
name: I18n.t('real_data'),
|
||||
};
|
||||
|
||||
// 初始化仅有real_data
|
||||
export const MOCK_OPTION_LIST = [REAL_DATA_MOCKSET];
|
||||
|
||||
export const POLLING_INTERVAL = 10000;
|
||||
|
||||
export const DELAY_TIME = 2000;
|
||||
|
||||
export const CONNECTOR_ID = '10000010';
|
||||
|
||||
export const mockSetInfoRules: {
|
||||
name: Array<RuleItem>;
|
||||
desc: Array<RuleItem>;
|
||||
} = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: I18n.t('please_enter_mockset_name'),
|
||||
},
|
||||
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: IS_OVERSEA
|
||||
? [
|
||||
{
|
||||
// eslint-disable-next-line no-control-regex -- regex
|
||||
pattern: /^[\x00-\x7F]+$/,
|
||||
message: I18n.t('create_plugin_modal_descrip_error'),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
|
||||
export const MOCK_SET_ERR_CODE = {
|
||||
REPEAT_NAME: 600303100,
|
||||
};
|
||||
|
||||
export enum MockTrafficEnabled {
|
||||
DISABLE = 0,
|
||||
ENABLE = 1,
|
||||
}
|
||||
104
frontend/packages/devops/mockset-manage/src/hooks/store.ts
Normal file
104
frontend/packages/devops/mockset-manage/src/hooks/store.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { produce } from 'immer';
|
||||
import {
|
||||
type BizCtx,
|
||||
type MockSet,
|
||||
type MockSetBinding,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
import { isCurrent } from '../utils';
|
||||
import { type BasicMockSetInfo } from '../interface';
|
||||
|
||||
export interface EnabledMockSetInfo {
|
||||
mockSetBinding: MockSetBinding;
|
||||
mockSetDetail?: MockSet;
|
||||
}
|
||||
|
||||
interface MockInfoStoreState {
|
||||
bizCtx: BizCtx;
|
||||
enabledMockSetInfo: Array<EnabledMockSetInfo>;
|
||||
isPolling: boolean;
|
||||
isLoading: boolean;
|
||||
currentMockComp: Array<BasicMockSetInfo>;
|
||||
timer?: NodeJS.Timeout;
|
||||
restartTimer?: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
interface MockInfoStoreAction {
|
||||
setPolling: (polling: boolean) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setCurrentBizCtx: (bizCtx: BizCtx) => void;
|
||||
setEnabledMockSetInfo: (mockSetList?: Array<EnabledMockSetInfo>) => void;
|
||||
removeMockComp: (mockComp: BasicMockSetInfo) => number;
|
||||
addMockComp: (mockComp: BasicMockSetInfo) => number;
|
||||
setTimer: (timer?: NodeJS.Timeout) => void;
|
||||
setRestartTimer: (timer?: NodeJS.Timeout) => void;
|
||||
}
|
||||
|
||||
export const useMockInfoStore = create<
|
||||
MockInfoStoreState & MockInfoStoreAction
|
||||
>((set, get) => ({
|
||||
bizCtx: {},
|
||||
enabledMockSetInfo: [],
|
||||
isPolling: false,
|
||||
isLoading: false,
|
||||
currentMockComp: [],
|
||||
setPolling: polling => {
|
||||
set({ isPolling: polling });
|
||||
},
|
||||
setLoading: loading => {
|
||||
set({ isLoading: loading });
|
||||
},
|
||||
setCurrentBizCtx: bizCtx => {
|
||||
set({ bizCtx });
|
||||
},
|
||||
setEnabledMockSetInfo: enabledMockSetInfo => {
|
||||
set({ enabledMockSetInfo });
|
||||
},
|
||||
addMockComp: mockSetInfo => {
|
||||
set(
|
||||
produce<MockInfoStoreState>(s => {
|
||||
const index = s.currentMockComp.findIndex(item =>
|
||||
isCurrent(item, mockSetInfo),
|
||||
);
|
||||
index <= -1 && s.currentMockComp.push(mockSetInfo);
|
||||
}),
|
||||
);
|
||||
return get().currentMockComp.length;
|
||||
},
|
||||
removeMockComp: mockSetInfo => {
|
||||
set(
|
||||
produce<MockInfoStoreState>(s => {
|
||||
const index = s.currentMockComp.findIndex(item =>
|
||||
isCurrent(item, mockSetInfo),
|
||||
);
|
||||
if (index > -1) {
|
||||
s.currentMockComp.splice(index, 1);
|
||||
}
|
||||
}),
|
||||
);
|
||||
return get().currentMockComp.length;
|
||||
},
|
||||
setTimer: timer => {
|
||||
set({ timer });
|
||||
},
|
||||
setRestartTimer: timer => {
|
||||
set({ timer });
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-lines-per-function -- 代码迁移 */
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- 代码迁移 */
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import axios, { type Canceler } from 'axios';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import {
|
||||
type BizCtx,
|
||||
type MockSet,
|
||||
type MockSetBinding,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { isSameScene } from '../utils';
|
||||
import { MockTrafficEnabled } from '../const';
|
||||
import { type EnabledMockSetInfo, useMockInfoStore } from './store';
|
||||
|
||||
function combineBindMockSetInfo(
|
||||
mockSetBindingList: Array<MockSetBinding>,
|
||||
mockSetDetailSet: Record<string, MockSet>,
|
||||
): Array<EnabledMockSetInfo> {
|
||||
return mockSetBindingList.map(mockSetInfo => {
|
||||
const { mockSetID } = mockSetInfo;
|
||||
const detail = mockSetID ? mockSetDetailSet[mockSetID] : {};
|
||||
return {
|
||||
mockSetBinding: mockSetInfo,
|
||||
mockSetDetail: detail,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const useInitialGetEnabledMockSet = ({
|
||||
bizCtx,
|
||||
pollingInterval,
|
||||
}: {
|
||||
bizCtx: BizCtx;
|
||||
pollingInterval?: number;
|
||||
}) => {
|
||||
const {
|
||||
enabledMockSetInfo,
|
||||
setPolling,
|
||||
setEnabledMockSetInfo,
|
||||
bizCtx: currentBizCtx,
|
||||
setCurrentBizCtx,
|
||||
addMockComp,
|
||||
removeMockComp,
|
||||
isPolling,
|
||||
setTimer,
|
||||
timer,
|
||||
currentMockComp,
|
||||
setLoading,
|
||||
isLoading,
|
||||
restartTimer,
|
||||
setRestartTimer,
|
||||
} = useMockInfoStore();
|
||||
const status = useRef<boolean>(false);
|
||||
const lastRequestId = useRef(0);
|
||||
const pollingTurnRef = useRef<string>();
|
||||
|
||||
const cancelReq = useRef<Canceler>();
|
||||
|
||||
const requestFn = async (curBizCtx: BizCtx) => {
|
||||
const currentRequestId = ++lastRequestId.current;
|
||||
const currentPollingTurn = pollingTurnRef.current;
|
||||
try {
|
||||
const { mockSetBindings = [], mockSetDetails = {} } =
|
||||
await debuggerApi.MGetMockSetBinding(
|
||||
{
|
||||
bizCtx: curBizCtx,
|
||||
needMockSetDetail: true,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'rpc-persist-mock-traffic-enable': MockTrafficEnabled.ENABLE,
|
||||
},
|
||||
cancelToken: new axios.CancelToken(function executor(c) {
|
||||
cancelReq.current = c;
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
(currentRequestId > 1 && currentRequestId !== lastRequestId.current) ||
|
||||
!pollingTurnRef.current ||
|
||||
pollingTurnRef.current !== currentPollingTurn
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setEnabledMockSetInfo?.(
|
||||
combineBindMockSetInfo(mockSetBindings, mockSetDetails),
|
||||
);
|
||||
return { mockSetBindings, mockSetDetails };
|
||||
} catch (e) {
|
||||
if (axios.isCancel(e)) {
|
||||
logger.info('poll_scene_mockset_canceled');
|
||||
} else {
|
||||
logger.error({
|
||||
error: e as Error,
|
||||
eventName: 'poll_scene_mockset_fail',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const request = async () => {
|
||||
try {
|
||||
const {
|
||||
trafficCallerID,
|
||||
connectorID,
|
||||
connectorUID,
|
||||
bizSpaceID,
|
||||
trafficScene,
|
||||
} = bizCtx;
|
||||
!status.current && (status.current = true);
|
||||
setLoading(true);
|
||||
if (status.current && pollingInterval) {
|
||||
setPolling(true);
|
||||
const id = setTimeout(() => {
|
||||
status.current && request();
|
||||
}, pollingInterval);
|
||||
setTimer(id);
|
||||
}
|
||||
await requestFn({
|
||||
trafficCallerID,
|
||||
connectorID,
|
||||
connectorUID,
|
||||
bizSpaceID,
|
||||
trafficScene,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消
|
||||
const cancel = () => {
|
||||
pollingTurnRef.current = undefined;
|
||||
cancelReq.current?.();
|
||||
lastRequestId.current = 0;
|
||||
cancelRestartTask();
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
setPolling(false);
|
||||
setTimer(undefined);
|
||||
status.current && (status.current = false);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelRestartTask = () => {
|
||||
if (restartTimer) {
|
||||
clearTimeout(restartTimer);
|
||||
setRestartTimer(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const start = async () => {
|
||||
cancel();
|
||||
pollingTurnRef.current = nanoid();
|
||||
await request();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentBizCtx && isSameScene(bizCtx, currentBizCtx)) {
|
||||
return;
|
||||
}
|
||||
setCurrentBizCtx(bizCtx);
|
||||
}, [bizCtx]);
|
||||
|
||||
return {
|
||||
start,
|
||||
cancel,
|
||||
isLoading,
|
||||
data: enabledMockSetInfo,
|
||||
addMockComp,
|
||||
removeMockComp,
|
||||
currentMockComp,
|
||||
isPolling,
|
||||
setRestartTimer,
|
||||
restartTimer,
|
||||
};
|
||||
};
|
||||
32
frontend/packages/devops/mockset-manage/src/index.tsx
Normal file
32
frontend/packages/devops/mockset-manage/src/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 { MockTrafficEnabled, CONNECTOR_ID } from './const';
|
||||
|
||||
export {
|
||||
MockSetSelect,
|
||||
type MockSetSelectActions,
|
||||
} from './components/mock-select';
|
||||
export { MockSetDeleteModal } from './components/mockset-delete-modal';
|
||||
export { MockSetEditModal } from './components/mockset-edit-modal';
|
||||
export {
|
||||
AutoGenerateSelect,
|
||||
type AutoGenerateConfig,
|
||||
} from './components/auto-generate-select';
|
||||
|
||||
export { getEnvironment, getUsedScene } from './utils';
|
||||
|
||||
export { type BindSubjectInfo, type BizCtxInfo } from './interface';
|
||||
63
frontend/packages/devops/mockset-manage/src/interface.ts
Normal file
63
frontend/packages/devops/mockset-manage/src/interface.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 CSSProperties } from 'react';
|
||||
|
||||
import {
|
||||
type OptionProps,
|
||||
type optionRenderProps,
|
||||
} from '@coze-arch/bot-semi/Select';
|
||||
import {
|
||||
type BizCtx,
|
||||
type MockSet,
|
||||
type ComponentSubject,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
export interface BizCtxInfo
|
||||
extends Omit<BizCtx, 'connectorID' | 'connectorUID' | 'ext'> {
|
||||
ext?: { mockSubjectInfo?: string } & Record<string, string>;
|
||||
}
|
||||
|
||||
export type BindSubjectInfo = ComponentSubject & { detail?: BindSubjectDetail };
|
||||
|
||||
export interface BasicMockSetInfo {
|
||||
bindSubjectInfo: ComponentSubject;
|
||||
bizCtx: BizCtx;
|
||||
}
|
||||
|
||||
export interface BindSubjectDetail {
|
||||
name?: string;
|
||||
}
|
||||
export interface MockSetSelectProps {
|
||||
bindSubjectInfo: BindSubjectInfo;
|
||||
bizCtx: BizCtxInfo;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export enum MockSetStatus {
|
||||
Incompatible = 'Incompatible',
|
||||
Normal = 'Normal',
|
||||
}
|
||||
|
||||
export interface MockSelectOptionProps extends OptionProps {
|
||||
detail?: MockSet;
|
||||
}
|
||||
|
||||
export interface MockSelectRenderOptionProps extends optionRenderProps {
|
||||
detail?: MockSet;
|
||||
}
|
||||
43
frontend/packages/devops/mockset-manage/src/typings.d.ts
vendored
Normal file
43
frontend/packages/devops/mockset-manage/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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' />
|
||||
|
||||
declare module '*.svg' {
|
||||
export const ReactComponent: React.FunctionComponent<
|
||||
React.SVGProps<SVGSVGElement>
|
||||
>;
|
||||
|
||||
/**
|
||||
* The default export type depends on the svgDefaultExport config,
|
||||
* it can be a string or a ReactComponent
|
||||
* */
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.less' {
|
||||
const content: { [className: string]: string };
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare const IS_OVERSEA: boolean;
|
||||
|
||||
declare const IS_PROD: boolean;
|
||||
|
||||
declare const IS_RELEASE_VERSION: boolean;
|
||||
|
||||
declare const IS_BOE: boolean;
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 { PluginMockDataGenerateMode } from '@coze-arch/bot-tea';
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'mockset_auto_generate_latest_choice';
|
||||
|
||||
let latestAutoGenerationChoice = PluginMockDataGenerateMode.MANUAL;
|
||||
|
||||
async function getFromLocalStorage() {
|
||||
const info = await localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||
if (Number(info) === PluginMockDataGenerateMode.LLM) {
|
||||
latestAutoGenerationChoice = PluginMockDataGenerateMode.LLM;
|
||||
} else {
|
||||
latestAutoGenerationChoice = PluginMockDataGenerateMode.RANDOM;
|
||||
}
|
||||
|
||||
if (!info || Number.isNaN(Number(info))) {
|
||||
localStorage.setItem(
|
||||
LOCAL_STORAGE_KEY,
|
||||
String(PluginMockDataGenerateMode.RANDOM),
|
||||
);
|
||||
}
|
||||
return latestAutoGenerationChoice;
|
||||
}
|
||||
|
||||
export function getLatestAutoGenerationChoice() {
|
||||
if (latestAutoGenerationChoice === PluginMockDataGenerateMode.MANUAL) {
|
||||
return getFromLocalStorage();
|
||||
} else {
|
||||
return latestAutoGenerationChoice;
|
||||
}
|
||||
}
|
||||
|
||||
export function setLatestAutoGenerationChoice(
|
||||
choice: PluginMockDataGenerateMode.RANDOM | PluginMockDataGenerateMode.LLM,
|
||||
) {
|
||||
latestAutoGenerationChoice = choice;
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, String(choice));
|
||||
}
|
||||
131
frontend/packages/devops/mockset-manage/src/utils/index.ts
Normal file
131
frontend/packages/devops/mockset-manage/src/utils/index.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 { isEqual } from 'lodash-es';
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
import {
|
||||
type BizCtx,
|
||||
type ComponentSubject,
|
||||
TrafficScene,
|
||||
type MockSet,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
import { type BasicMockSetInfo, type BizCtxInfo } from '../interface';
|
||||
import { REAL_DATA_MOCKSET } from '../const';
|
||||
|
||||
export function isRealData(mockSet: MockSet) {
|
||||
return mockSet.id === REAL_DATA_MOCKSET.id;
|
||||
}
|
||||
|
||||
export function isCurrent(sItem: BasicMockSetInfo, tItem: BasicMockSetInfo) {
|
||||
const { bindSubjectInfo: mockSubject, bizCtx } = sItem;
|
||||
const { bindSubjectInfo: compSubject, bizCtx: compBizCtx } = tItem;
|
||||
const isCurrentComponent = isEqual(mockSubject, compSubject);
|
||||
const { ext, ...baseBizCtx } = bizCtx || {};
|
||||
const { ext: compExt, ...baseCompBizCxt } = compBizCtx || {};
|
||||
const isCurrentScene = isSameScene(baseBizCtx, baseCompBizCxt);
|
||||
const isWorkflowExt =
|
||||
bizCtx?.trafficScene !== TrafficScene.CozeWorkflowDebug ||
|
||||
isSameWorkflowTool(
|
||||
ext?.mockSubjectInfo || '',
|
||||
compExt?.mockSubjectInfo || '',
|
||||
);
|
||||
return isCurrentComponent && isCurrentScene && isWorkflowExt;
|
||||
}
|
||||
|
||||
export function isSameWorkflowTool(
|
||||
sMockSubjectInfo: string,
|
||||
tMockSubjectInfo: string,
|
||||
) {
|
||||
const sMockInfo = safeJSONParse(sMockSubjectInfo || '{}');
|
||||
const tMockInfo = safeJSONParse(tMockSubjectInfo || '{}');
|
||||
return isEqual(sMockInfo, tMockInfo);
|
||||
}
|
||||
export function isSameScene(sBizCtx: BizCtx, tBizCtx: BizCtx) {
|
||||
return (
|
||||
sBizCtx.bizSpaceID === tBizCtx.bizSpaceID &&
|
||||
sBizCtx.trafficScene === tBizCtx.trafficScene &&
|
||||
sBizCtx.trafficCallerID === tBizCtx.trafficCallerID
|
||||
);
|
||||
}
|
||||
|
||||
export function getPluginInfo(
|
||||
bizCtx: BizCtxInfo,
|
||||
mockSubjectInfo: ComponentSubject,
|
||||
): { spaceID?: string; pluginID?: string; toolID?: string } {
|
||||
const { bizSpaceID, ext, trafficScene } = bizCtx || {};
|
||||
const extMockSubjectInfo = safeJSONParse(ext?.mockSubjectInfo || '{}');
|
||||
const { componentID, parentComponentID } = mockSubjectInfo;
|
||||
switch (trafficScene) {
|
||||
case TrafficScene.CozeWorkflowDebug:
|
||||
return {
|
||||
spaceID: bizSpaceID,
|
||||
toolID: extMockSubjectInfo?.componentID,
|
||||
pluginID: extMockSubjectInfo?.parentComponentID,
|
||||
};
|
||||
case TrafficScene.CozeSingleAgentDebug:
|
||||
case TrafficScene.CozeMultiAgentDebug:
|
||||
case TrafficScene.CozeToolDebug:
|
||||
default:
|
||||
return {
|
||||
spaceID: bizSpaceID,
|
||||
toolID: componentID,
|
||||
pluginID: parentComponentID,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getMockSubjectInfo(
|
||||
bizCtx: BizCtxInfo,
|
||||
mockSubjectInfo: ComponentSubject,
|
||||
) {
|
||||
const { ext, trafficScene } = bizCtx || {};
|
||||
const extMockSubjectInfo = safeJSONParse(ext?.mockSubjectInfo || '{}');
|
||||
switch (trafficScene) {
|
||||
case TrafficScene.CozeWorkflowDebug:
|
||||
return extMockSubjectInfo;
|
||||
case TrafficScene.CozeSingleAgentDebug:
|
||||
case TrafficScene.CozeMultiAgentDebug:
|
||||
case TrafficScene.CozeToolDebug:
|
||||
default:
|
||||
return mockSubjectInfo;
|
||||
}
|
||||
}
|
||||
|
||||
export function getEnvironment() {
|
||||
if (!IS_PROD) {
|
||||
return 'cn-boe';
|
||||
}
|
||||
const regionPart = IS_OVERSEA ? 'oversea' : 'cn';
|
||||
const inhousePart = IS_RELEASE_VERSION ? 'release' : 'inhouse';
|
||||
|
||||
return [regionPart, inhousePart].join('-');
|
||||
}
|
||||
|
||||
export function getUsedScene(scene?: TrafficScene): 'bot' | 'agent' | 'flow' {
|
||||
switch (scene) {
|
||||
case TrafficScene.CozeSingleAgentDebug:
|
||||
return 'bot';
|
||||
case TrafficScene.CozeMultiAgentDebug:
|
||||
return 'agent';
|
||||
case TrafficScene.CozeWorkflowDebug:
|
||||
return 'flow';
|
||||
case TrafficScene.CozeToolDebug:
|
||||
return 'bot';
|
||||
default:
|
||||
return 'bot';
|
||||
}
|
||||
}
|
||||
73
frontend/packages/devops/mockset-manage/tsconfig.build.json
Normal file
73
frontend/packages/devops/mockset-manage/tsconfig.build.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-env/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-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/logger/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": "../../studio/user-store/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/devops/mockset-manage/tsconfig.json
Normal file
15
frontend/packages/devops/mockset-manage/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": ["**/*"]
|
||||
}
|
||||
22
frontend/packages/devops/mockset-manage/tsconfig.misc.json
Normal file
22
frontend/packages/devops/mockset-manage/tsconfig.misc.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["__tests__", "stories", "vitest.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"jsx": "react-jsx",
|
||||
"types": ["react", "react-dom", "vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
22
frontend/packages/devops/mockset-manage/vitest.config.ts
Normal file
22
frontend/packages/devops/mockset-manage/vitest.config.ts
Normal file
@@ -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',
|
||||
});
|
||||
Reference in New Issue
Block a user