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/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`
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { MockDataStatus, MockDataValueType } from '../../../src/util/typings';
|
||||
import {
|
||||
BranchType,
|
||||
useGenTreeBranch,
|
||||
} from '../../../src/hook/use-gen-tree-branch';
|
||||
|
||||
describe('useGenTreeBranch', () => {
|
||||
it('should return an empty object when no mockData is provided', () => {
|
||||
const { result } = renderHook(() => useGenTreeBranch());
|
||||
|
||||
expect(result.current.branchInfo).toEqual({});
|
||||
expect(result.current.prunedData).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should prune the tree correctly and generate branchInfo', () => {
|
||||
const mockData = {
|
||||
label: 'label',
|
||||
type: MockDataValueType.OBJECT,
|
||||
status: MockDataStatus.DEFAULT,
|
||||
key: 'root',
|
||||
isRequired: false,
|
||||
children: [
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: MockDataValueType.ARRAY,
|
||||
status: MockDataStatus.ADDED,
|
||||
key: 'array1',
|
||||
children: [
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: MockDataValueType.STRING,
|
||||
status: MockDataStatus.DEFAULT,
|
||||
key: 'str1',
|
||||
},
|
||||
{
|
||||
label: 'label',
|
||||
type: MockDataValueType.OBJECT,
|
||||
status: MockDataStatus.ADDED,
|
||||
isRequired: false,
|
||||
key: 'obj1',
|
||||
children: [
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: MockDataValueType.NUMBER,
|
||||
status: MockDataStatus.DEFAULT,
|
||||
key: 'num1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: MockDataValueType.OBJECT,
|
||||
status: MockDataStatus.DEFAULT,
|
||||
key: 'obj2',
|
||||
children: [
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: MockDataValueType.BOOLEAN,
|
||||
status: MockDataStatus.DEFAULT,
|
||||
key: 'bool1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useGenTreeBranch(mockData));
|
||||
|
||||
expect(result.current.prunedData).toEqual({
|
||||
label: 'label',
|
||||
type: 'object',
|
||||
status: 'default',
|
||||
key: 'root',
|
||||
isRequired: false,
|
||||
children: [
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: 'array',
|
||||
status: 'added',
|
||||
key: 'array1',
|
||||
children: undefined,
|
||||
},
|
||||
{
|
||||
label: 'label',
|
||||
isRequired: false,
|
||||
type: 'object',
|
||||
status: 'default',
|
||||
key: 'obj2',
|
||||
children: [
|
||||
{
|
||||
isRequired: false,
|
||||
key: 'bool1',
|
||||
label: 'label',
|
||||
status: 'default',
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.current.branchInfo).toEqual({
|
||||
array1: {
|
||||
isLast: false,
|
||||
v: [],
|
||||
},
|
||||
obj2: {
|
||||
isLast: true,
|
||||
v: [],
|
||||
},
|
||||
bool1: {
|
||||
isLast: true,
|
||||
v: [BranchType.HALF],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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 { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { useMockSetInSettingModalController } from '../../../src/hook/use-mock-set-in-setting-modal';
|
||||
vi.stubGlobal('IS_PROD', false);
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
debuggerApi: {
|
||||
BindMockSet: vi.fn().mockResolvedValue(null),
|
||||
GetMockSetUsageInfo: vi.fn().mockResolvedValue({ usersUsageCount: 0 }),
|
||||
MGetMockSet: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ mockSets: [] })
|
||||
.mockResolvedValue({
|
||||
mockSets: [
|
||||
{
|
||||
createTimeInSec: '1717510112',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: '',
|
||||
id: '7376649762918891521',
|
||||
mockRuleQuantity: 1,
|
||||
name: 'getNews mockset84',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1718180157',
|
||||
},
|
||||
{
|
||||
createTimeInSec: '1718179289',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: 'getNews mockset76',
|
||||
id: '7379523858379833345',
|
||||
mockRuleQuantity: 1,
|
||||
name: 'getNews mockset76',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1718179317',
|
||||
},
|
||||
{
|
||||
createTimeInSec: '1717596210',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: '这是一个描述',
|
||||
id: '7377019552296599553',
|
||||
mockRuleQuantity: 1,
|
||||
name: 'getNews mockset51',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1717596219',
|
||||
},
|
||||
{
|
||||
createTimeInSec: '1717586096',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: '',
|
||||
id: '7376976111470641153',
|
||||
mockRuleQuantity: 1,
|
||||
name: 'getNews mockset44',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1717586102',
|
||||
},
|
||||
{
|
||||
createTimeInSec: '1717422094',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: '',
|
||||
id: '7376271731066929153',
|
||||
mockRuleQuantity: 8,
|
||||
name: 'getNews mockset74',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1717448382',
|
||||
},
|
||||
{
|
||||
createTimeInSec: '1717424229',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: '',
|
||||
id: '7376280897617657858',
|
||||
mockRuleQuantity: 3,
|
||||
name: 'getNews mockset80',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1717424242',
|
||||
},
|
||||
{
|
||||
createTimeInSec: '1717423923',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
avatarUrl:
|
||||
'https://p3-passport.byteacctimg.com/obj/user-avatar/assets/e7b19241fb224cea967dfaea35448102_1080_1080.png',
|
||||
name: 'minalwws8888',
|
||||
},
|
||||
description: '',
|
||||
id: '7376279584485933058',
|
||||
mockRuleQuantity: 1,
|
||||
name: 'getNews mockset93',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1717423928',
|
||||
},
|
||||
],
|
||||
}),
|
||||
MGetMockSetBinding: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ mockSetBindings: [], mockSetDetails: {} })
|
||||
.mockResolvedValue({
|
||||
mockSetBindings: [
|
||||
{
|
||||
bizCtx: {
|
||||
bizSpaceID: '7313780910216708140',
|
||||
connectorID: '10000010',
|
||||
connectorUID: '1735475817351321',
|
||||
ext: {
|
||||
mockSubjectInfo:
|
||||
'{"componentID":"7373521805258047532","componentType":10001,"parentComponentID":"7373521805258014764","parentComponentType":10000}',
|
||||
},
|
||||
trafficCallerID: '7340867364105633836',
|
||||
trafficScene: 10000,
|
||||
},
|
||||
mockSetID: '7376649762918891521',
|
||||
mockSubject: {
|
||||
componentID: '7373521805258047532',
|
||||
componentType: 10001,
|
||||
parentComponentID: '7373521805258014764',
|
||||
parentComponentType: 10000,
|
||||
},
|
||||
},
|
||||
],
|
||||
mockSetDetails: {
|
||||
'7376649762918891521': {
|
||||
createTimeInSec: '1717510112',
|
||||
creator: {
|
||||
ID: '1735475817351321',
|
||||
},
|
||||
description: '',
|
||||
id: '7376649762918891521',
|
||||
mockRuleQuantity: 1,
|
||||
mockSubject: {
|
||||
componentID: '7373521805258047532',
|
||||
componentType: 10001,
|
||||
parentComponentID: '7373521805258014764',
|
||||
parentComponentType: 10000,
|
||||
},
|
||||
name: 'getNews mockset84',
|
||||
schemaIncompatible: false,
|
||||
updateTimeInSec: '1718180157',
|
||||
},
|
||||
},
|
||||
}),
|
||||
DeleteMockSet: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-studio-store', () => ({
|
||||
useSpaceStore: vi.fn().mockReturnValue(SpaceType.Personal),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-studio/user-store', () => ({
|
||||
userStoreService: {
|
||||
useUserInfo: vi.fn().mockReturnValue({
|
||||
user_id_str: 'test-user-id',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/util', () => ({
|
||||
getEnvironment: vi.fn(),
|
||||
getMockSubjectInfo: vi.fn(),
|
||||
getPluginInfo: vi.fn().mockReturnValue({
|
||||
spaceID: 'spaceID',
|
||||
toolID: 'toolID',
|
||||
pluginID: 'pluginID',
|
||||
}),
|
||||
getUsedScene: vi.fn(),
|
||||
isCurrent: vi.fn().mockReturnValue(true),
|
||||
MockTrafficEnabled: {
|
||||
DISABLE: 0,
|
||||
ENABLE: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
createLoggerWith: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
reporter: {
|
||||
createReporterWithPreset: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockProps = {
|
||||
bindSubjectInfo: {
|
||||
componentID: 'test-subject-id',
|
||||
componentType: 10001,
|
||||
parentComponentID: 'parentComponentID',
|
||||
parentComponentType: 10000,
|
||||
},
|
||||
bizCtx: {
|
||||
connectorID: 'connectorID',
|
||||
connectorUID: 'connectorUID',
|
||||
trafficScene: 10000,
|
||||
trafficCallerID: 'trafficCallerID',
|
||||
bizSpaceID: 'bizSpaceID',
|
||||
},
|
||||
};
|
||||
|
||||
describe('useMockSetInSettingModalController', () => {
|
||||
it('should initialize correctly', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useMockSetInSettingModalController(mockProps),
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.isEnabled).toBeFalsy(); // 初始状态应该是不启用
|
||||
expect(result.current.mockSetData).toEqual([]); // 初始mockSetData应该是空数组
|
||||
});
|
||||
|
||||
it('should act export action', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useMockSetInSettingModalController(mockProps),
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.isEnabled).toBeTruthy(); // 有选中的mock数据的前提下,会自动启用
|
||||
expect(result.current.mockSetData.length).toEqual(7); // mockSetData 数据是 7
|
||||
|
||||
act(() => result.current.doSetCreateModal(true));
|
||||
expect(result.current.showCreateModal).toBeTruthy(); // 打开创建modal
|
||||
|
||||
act(() => result.current.doHandleView({ id: 'record-id' })); // 点击查看
|
||||
|
||||
act(() => result.current.doEnabled()); // 关闭
|
||||
expect(result.current.isEnabled).toBeFalsy(); // 禁用
|
||||
|
||||
act(() => result.current.doSetDeleteId('record-id')); // 删除
|
||||
expect(result.current.deleteRenderTitle).toBe(
|
||||
'Translated: delete_the_mockset {}',
|
||||
); // 删除后应该显示删除的title
|
||||
|
||||
act(() => result.current.doConfirmDelete()); // 确认删除
|
||||
|
||||
act(() => result.current.doChangeMock({ id: 'change-mock' })); // 修改mock
|
||||
expect(result.current.selectedMockSet).toStrictEqual({ id: 'change-mock' }); // 修改mock后应该显示新的mock
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 { useSaveMockData } from '../../../src/hook/use-save-mock-data';
|
||||
|
||||
vi.mock('@coze-arch/coze-design', () => import('@coze-arch/bot-semi'));
|
||||
|
||||
const mockBizCtx = {};
|
||||
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
debuggerApi: {
|
||||
ResponseExpectType: {
|
||||
Undefined: 0,
|
||||
JSON: 1,
|
||||
},
|
||||
MockRule: {
|
||||
id: 'id',
|
||||
},
|
||||
BizCtx: {
|
||||
connectorID: 'connectorID',
|
||||
},
|
||||
SaveMockRule: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
status: 'success',
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
status: 'success',
|
||||
})
|
||||
.mockRejectedValueOnce({
|
||||
error: { message: 'Failed to save mock data' },
|
||||
})
|
||||
.mockRejectedValueOnce({
|
||||
error: { message: 'Failed to save mock data' },
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('useSaveMockData', () => {
|
||||
it('should call onSuccess when all rules are saved successfully', async () => {
|
||||
const onSuccess = vi.fn();
|
||||
const onError = vi.fn();
|
||||
const mockSetId = 'mockSetId';
|
||||
const mockRules = ['rule1', 'rule2'];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useSaveMockData({
|
||||
mockSetId,
|
||||
basicParams: {
|
||||
environment: 'environment',
|
||||
workspace_id: 'workspace_id',
|
||||
workspace_type: 'personal_workspace',
|
||||
tool_id: 'tool_id',
|
||||
mock_set_id: 'mock_set_id',
|
||||
},
|
||||
bizCtx: mockBizCtx,
|
||||
onSuccess,
|
||||
onError,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
result.current.save(mockRules);
|
||||
});
|
||||
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1);
|
||||
expect(onError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onError and display a toast when all rules fail to save', async () => {
|
||||
const onSuccess = vi.fn();
|
||||
const onError = vi.fn();
|
||||
const mockRules = ['rule1', 'rule2'];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useSaveMockData({
|
||||
mockSetId: undefined, // 测试没有mockSetId的情况
|
||||
basicParams: {
|
||||
environment: 'environment',
|
||||
workspace_id: 'workspace_id',
|
||||
workspace_type: 'personal_workspace',
|
||||
tool_id: 'tool_id',
|
||||
mock_set_id: 'mock_set_id',
|
||||
},
|
||||
bizCtx: mockBizCtx,
|
||||
onSuccess,
|
||||
onError,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
result.current.save(mockRules);
|
||||
});
|
||||
|
||||
expect(onSuccess).not.toHaveBeenCalled();
|
||||
expect(onError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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 '../../src/component/interface';
|
||||
import { useInitialGetEnabledMockSet } from '../../src/component/hooks/use-get-mockset';
|
||||
|
||||
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(),
|
||||
},
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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 {
|
||||
getPluginInfo,
|
||||
getMockSubjectInfo,
|
||||
type BasicMockSetInfo,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
|
||||
import {
|
||||
isRealData,
|
||||
isCurrent,
|
||||
isSameWorkflowTool,
|
||||
isSameScene,
|
||||
getUsedScene,
|
||||
} from '../../src/util';
|
||||
|
||||
vi.mock('@coze-arch/bot-utils', () => ({
|
||||
safeJSONParse: JSON.parse,
|
||||
}));
|
||||
|
||||
vi.mock('../const', () => ({
|
||||
REAL_DATA_MOCKSET: {
|
||||
id: '0',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
createLoggerWith: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
reporter: {
|
||||
createReporterWithPreset: 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,283 @@
|
||||
/*
|
||||
* 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 { type JSONSchema7 } from 'json-schema';
|
||||
import {
|
||||
ROOT_KEY,
|
||||
getArrayItemKey,
|
||||
transDataWithStatus2Object,
|
||||
transSchema2DataWithStatus,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
|
||||
import {
|
||||
getMergedDataWithStatus,
|
||||
mergeDataWithStatus,
|
||||
safeJSONParse,
|
||||
transMockData2DataWithStatus,
|
||||
} from '../src/util/utils';
|
||||
import {
|
||||
MockDataStatus,
|
||||
type MockDataValueType,
|
||||
type MockDataWithStatus,
|
||||
} from '../src/util/typings';
|
||||
|
||||
const testSchema: JSONSchema7 = {
|
||||
$schema: 'https://json-schema.org/draft-07/schema',
|
||||
required: ['num', 'str', 'bool'],
|
||||
properties: {
|
||||
bool: {
|
||||
additionalProperties: false,
|
||||
type: ['boolean'],
|
||||
},
|
||||
int: {
|
||||
additionalProperties: false,
|
||||
type: ['integer'],
|
||||
},
|
||||
num: {
|
||||
additionalProperties: false,
|
||||
type: ['number'],
|
||||
},
|
||||
str: {
|
||||
additionalProperties: false,
|
||||
type: ['string'],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
type: ['object'],
|
||||
};
|
||||
|
||||
const testMockA: MockDataWithStatus = {
|
||||
children: [
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: 'false',
|
||||
isRequired: true,
|
||||
key: 'mock-bool',
|
||||
label: 'bool',
|
||||
status: 'added' as MockDataStatus,
|
||||
type: 'boolean' as MockDataValueType,
|
||||
realValue: false,
|
||||
},
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: '0',
|
||||
isRequired: false,
|
||||
key: 'mock-int',
|
||||
label: 'int',
|
||||
status: 'added' as MockDataStatus,
|
||||
type: 'integer' as MockDataValueType,
|
||||
realValue: 0,
|
||||
},
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: '0',
|
||||
isRequired: true,
|
||||
key: 'mock-num',
|
||||
label: 'num',
|
||||
status: 'added' as MockDataStatus,
|
||||
type: 'number' as MockDataValueType,
|
||||
realValue: 0,
|
||||
},
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: '""',
|
||||
isRequired: true,
|
||||
key: 'mock-str',
|
||||
label: 'str',
|
||||
status: 'added' as MockDataStatus,
|
||||
type: 'string' as MockDataValueType,
|
||||
realValue: '',
|
||||
},
|
||||
],
|
||||
description: undefined,
|
||||
displayValue: undefined,
|
||||
isRequired: false,
|
||||
key: 'mock',
|
||||
label: 'mock',
|
||||
status: 'added' as MockDataStatus,
|
||||
type: 'object' as MockDataValueType,
|
||||
realValue: undefined,
|
||||
};
|
||||
|
||||
const testMockAObj = { mock: { bool: false, int: 0, num: 0, str: '' } };
|
||||
|
||||
const testMockBStr = '{"bool":true,"num":2,"str":"hello","extra":"extra"}';
|
||||
|
||||
const testMockB: MockDataWithStatus = {
|
||||
key: 'mock',
|
||||
label: 'mock',
|
||||
realValue: undefined,
|
||||
displayValue: undefined,
|
||||
isRequired: false,
|
||||
type: 'object' as MockDataValueType,
|
||||
status: 'removed' as MockDataStatus,
|
||||
children: [
|
||||
{
|
||||
key: 'mock-bool',
|
||||
label: 'bool',
|
||||
realValue: true,
|
||||
displayValue: 'true',
|
||||
isRequired: false,
|
||||
type: 'boolean' as MockDataValueType,
|
||||
status: 'removed' as MockDataStatus,
|
||||
},
|
||||
{
|
||||
key: 'mock-num',
|
||||
label: 'num',
|
||||
realValue: 2,
|
||||
displayValue: '2',
|
||||
isRequired: false,
|
||||
type: 'number' as MockDataValueType,
|
||||
status: 'removed' as MockDataStatus,
|
||||
},
|
||||
{
|
||||
key: 'mock-str',
|
||||
label: 'str',
|
||||
realValue: 'hello',
|
||||
displayValue: '"hello"',
|
||||
isRequired: false,
|
||||
type: 'string' as MockDataValueType,
|
||||
status: 'removed' as MockDataStatus,
|
||||
},
|
||||
{
|
||||
key: 'mock-extra',
|
||||
label: 'extra',
|
||||
realValue: 'extra',
|
||||
displayValue: '"extra"',
|
||||
isRequired: false,
|
||||
type: 'string' as MockDataValueType,
|
||||
status: 'removed' as MockDataStatus,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const testMergedMockData: MockDataWithStatus = {
|
||||
key: 'mock',
|
||||
label: 'mock',
|
||||
description: undefined,
|
||||
realValue: undefined,
|
||||
displayValue: undefined,
|
||||
isRequired: false,
|
||||
type: 'object' as MockDataValueType,
|
||||
status: 'added' as MockDataStatus,
|
||||
children: [
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: 'true',
|
||||
isRequired: true,
|
||||
key: 'mock-bool',
|
||||
label: 'bool',
|
||||
status: 'default' as MockDataStatus,
|
||||
type: 'boolean' as MockDataValueType,
|
||||
realValue: true,
|
||||
},
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: '0',
|
||||
isRequired: false,
|
||||
key: 'mock-int',
|
||||
label: 'int',
|
||||
status: 'added' as MockDataStatus,
|
||||
type: 'integer' as MockDataValueType,
|
||||
realValue: 0,
|
||||
},
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: '2',
|
||||
isRequired: true,
|
||||
key: 'mock-num',
|
||||
label: 'num',
|
||||
status: 'default' as MockDataStatus,
|
||||
type: 'number' as MockDataValueType,
|
||||
realValue: 2,
|
||||
},
|
||||
{
|
||||
description: undefined,
|
||||
displayValue: '"hello"',
|
||||
isRequired: true,
|
||||
key: 'mock-str',
|
||||
label: 'str',
|
||||
status: 'default' as MockDataStatus,
|
||||
type: 'string' as MockDataValueType,
|
||||
realValue: 'hello',
|
||||
},
|
||||
{
|
||||
key: 'mock-extra',
|
||||
label: 'extra',
|
||||
realValue: 'extra',
|
||||
displayValue: '"extra"',
|
||||
isRequired: false,
|
||||
type: 'string' as MockDataValueType,
|
||||
status: 'removed' as MockDataStatus,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('plugin-mock-data-utils', () => {
|
||||
it('getArrayItemKey', () => {
|
||||
const key = getArrayItemKey(1);
|
||||
|
||||
expect(key).toEqual('item_1');
|
||||
});
|
||||
|
||||
it('transSchema2DataWithStatus', () => {
|
||||
const data = transSchema2DataWithStatus(ROOT_KEY, testSchema);
|
||||
const compareResult = isEqual(data, testMockA);
|
||||
|
||||
expect(compareResult).toEqual(true);
|
||||
});
|
||||
|
||||
it('transDataWithStatus2Object', () => {
|
||||
const data = transDataWithStatus2Object(testMockA);
|
||||
const compareResult = isEqual(data, testMockAObj);
|
||||
|
||||
expect(compareResult).toEqual(true);
|
||||
});
|
||||
|
||||
it('transMockData2DataWithStatus', () => {
|
||||
const obj = safeJSONParse(testMockBStr);
|
||||
const parsedMockData = transMockData2DataWithStatus(ROOT_KEY, obj, {
|
||||
defaultStatus: MockDataStatus.REMOVED,
|
||||
});
|
||||
const compareResult = isEqual(parsedMockData, testMockB);
|
||||
|
||||
expect(compareResult).toEqual(true);
|
||||
});
|
||||
|
||||
it('mergeDataWithStatus', () => {
|
||||
const { merged: mergedMockData, incompatible } = mergeDataWithStatus(
|
||||
testMockA.children,
|
||||
testMockB.children,
|
||||
);
|
||||
const compareResult = isEqual(mergedMockData, testMergedMockData.children);
|
||||
|
||||
expect(compareResult).toEqual(true);
|
||||
expect(incompatible).toEqual(true);
|
||||
});
|
||||
|
||||
it('getMergedDataWithStatus', () => {
|
||||
const { result: mergedMockData, incompatible } = getMergedDataWithStatus(
|
||||
testMockA,
|
||||
testMockBStr,
|
||||
);
|
||||
|
||||
const compareResult = isEqual(mergedMockData, testMergedMockData);
|
||||
|
||||
expect(compareResult).toEqual(true);
|
||||
expect(incompatible).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.stubGlobal('IS_DEV_MODE', false);
|
||||
|
||||
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,77 @@
|
||||
/*
|
||||
* 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 { useTransSchema } from '../src/hook/use-trans-schema';
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
logger: {
|
||||
createLoggerWith: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-utils', () => ({
|
||||
safeJSONParse: JSON.parse,
|
||||
}));
|
||||
|
||||
describe('plugin-mock-data-hooks', () => {
|
||||
it('useTransSchema - compatible case 1 ', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTransSchema(
|
||||
'{"$schema":"https://json-schema.org/draft-07/schema","type":["object"],"additionalProperties":false}',
|
||||
'{}',
|
||||
),
|
||||
);
|
||||
|
||||
const { incompatible } = result.current;
|
||||
|
||||
expect(incompatible).toEqual(false);
|
||||
});
|
||||
|
||||
it('useTransSchema - compatible case 2', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTransSchema(
|
||||
'{"$schema":"https://json-schema.org/draft-07/schema","required":["num","str","bool"],"properties":{"bool":{"additionalProperties":false,"type":["boolean"]},"int":{"additionalProperties":false,"type":["integer"]},"num":{"additionalProperties":false,"type":["number"]},"str":{"additionalProperties":false,"type":["string"]}},"additionalProperties":false,"type":["object"]}',
|
||||
'{"int": 1,"num": 1.11,"str": "test","bool": true\n}',
|
||||
),
|
||||
);
|
||||
|
||||
const { incompatible } = result.current;
|
||||
|
||||
expect(incompatible).toEqual(false);
|
||||
});
|
||||
|
||||
it('useTransSchema - testValueValid', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTransSchema(
|
||||
'{"$schema":"https://json-schema.org/draft-07/schema","properties":{"response_for_model":{"additionalProperties":false,"type":"string"},"str":{"additionalProperties":false,"type":["string"]}},"additionalProperties":false,"type":["object"]}',
|
||||
),
|
||||
);
|
||||
|
||||
const { testValueValid } = result.current;
|
||||
|
||||
const testPass = testValueValid('{"response_for_model": "xxx"}');
|
||||
const testFail1 = testValueValid(
|
||||
'{"response_for_model": "", "str": "hello"}',
|
||||
);
|
||||
const testFail2 = testValueValid('{}');
|
||||
|
||||
expect(testPass).toEqual(true);
|
||||
expect(testFail1).toEqual(false);
|
||||
expect(testFail2).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -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 { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getMockSetReqOptions } from '../../../src/util/get-mock-set-options';
|
||||
|
||||
vi.mock('@coze-arch/bot-flags', () => ({
|
||||
getFlags: vi
|
||||
.fn()
|
||||
.mockReturnValueOnce({
|
||||
'bot.devops.plugin_mockset': true,
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
'bot.devops.plugin_mockset': false,
|
||||
}),
|
||||
}));
|
||||
|
||||
const baseBotInfo = {
|
||||
mode: 0,
|
||||
botId: 'testBotId',
|
||||
botMarketStatus: 1,
|
||||
space_id: 'testSpaceId',
|
||||
};
|
||||
|
||||
describe('getMockSetReqOptions', () => {
|
||||
it('should return mock headers when plugin_mockset flag is true', () => {
|
||||
const result = getMockSetReqOptions(baseBotInfo);
|
||||
|
||||
expect(result).toEqual({
|
||||
headers: {
|
||||
'rpc-persist-mock-traffic-scene': 10000,
|
||||
'rpc-persist-mock-traffic-caller-id': 'testBotId',
|
||||
'rpc-persist-mock-space-id': 'testSpaceId',
|
||||
'rpc-persist-mock-traffic-enable': 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty object when plugin_mockset flag is false', () => {
|
||||
const result = getMockSetReqOptions(baseBotInfo);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -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: {},
|
||||
});
|
||||
138
frontend/packages/agent-ide/bot-plugin/mock-set/package.json
Normal file
138
frontend/packages/agent-ide/bot-plugin/mock-set/package.json
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"name": "@coze-agent-ide/bot-plugin-mock-set",
|
||||
"version": "0.0.1",
|
||||
"description": "plugin mock set",
|
||||
"license": "Apache-2.0",
|
||||
"author": "lihuiwen.123@bytedance.com",
|
||||
"maintainers": [],
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./hook/use-mock-set-in-setting-modal": "./src/hook/use-mock-set-in-setting-modal.ts",
|
||||
"./use-trans-schema": "./src/hoos/use-trans-schema.ts",
|
||||
"./mockset-edit-modal": "./src/component/mockset-edit-modal/index.tsx",
|
||||
"./mockset-delete-modal": "./src/component/mockset-delete-modal/index.tsx",
|
||||
"./interface": "./src/component/interface.ts",
|
||||
"./mock-set/utils": "./src/util/index.ts",
|
||||
"./mock-set-intro": "./src/component/mock-set-intro.tsx",
|
||||
"./mock-data-list": "./src/component/mock-data-list.tsx",
|
||||
"./mock-data-page-breadcrumb": "./src/component/mock-data-page-breadcrumb.tsx",
|
||||
"./mock-set/const": "./src/component/const.ts",
|
||||
"./long-text-with-tooltip": "./src/component/long-text-with-tooltip/index.tsx",
|
||||
"./typings": "./src/util/typings.ts",
|
||||
"./util": "./src/util/index.ts",
|
||||
"./auto-generate-select": "./src/component/auto-generate-select.tsx",
|
||||
"./mock-set-select-actions": "./src/component/mock-select/index.tsx"
|
||||
},
|
||||
"main": "src/index.tsx",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"hook/use-mock-set-in-setting-modal": [
|
||||
"./src/hook/use-mock-set-in-setting-modal.ts"
|
||||
],
|
||||
"use-trans-schema": [
|
||||
"./src/hoos/use-trans-schema.ts"
|
||||
],
|
||||
"mockset-edit-modal": [
|
||||
"./src/component/mockset-edit-modal/index.tsx"
|
||||
],
|
||||
"mockset-delete-modal": [
|
||||
"./src/component/mockset-delete-modal/index.tsx"
|
||||
],
|
||||
"interface": [
|
||||
"./src/component/interface.ts"
|
||||
],
|
||||
"mock-set/utils": [
|
||||
"./src/util/index.ts"
|
||||
],
|
||||
"mock-set-intro": [
|
||||
"./src/component/mock-set-intro.tsx"
|
||||
],
|
||||
"mock-data-list": [
|
||||
"./src/component/mock-data-list.tsx"
|
||||
],
|
||||
"mock-data-page-breadcrumb": [
|
||||
"./src/component/mock-data-page-breadcrumb.tsx"
|
||||
],
|
||||
"mock-set/const": [
|
||||
"./src/component/const.ts"
|
||||
],
|
||||
"long-text-with-tooltip": [
|
||||
"./src/component/long-text-with-tooltip/index.tsx"
|
||||
],
|
||||
"typings": [
|
||||
"./src/util/typings.ts"
|
||||
],
|
||||
"util": [
|
||||
"./src/util/index.ts"
|
||||
],
|
||||
"auto-generate-select": [
|
||||
"./src/component/auto-generate-select/index.tsx"
|
||||
],
|
||||
"mock-set-select-actions": [
|
||||
"./src/component/mock-select/index.tsx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-flags": "workspace:*",
|
||||
"@coze-arch/bot-hooks": "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/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/logger": "workspace:*",
|
||||
"@coze-studio/bot-detail-store": "workspace:*",
|
||||
"@coze-studio/bot-utils": "workspace:*",
|
||||
"@coze-studio/components": "workspace:*",
|
||||
"@coze-studio/mockset-edit-modal-adapter": "workspace:*",
|
||||
"@coze-studio/mockset-editor": "workspace:*",
|
||||
"@coze-studio/mockset-editor-adapter": "workspace:*",
|
||||
"@coze-studio/mockset-shared": "workspace:*",
|
||||
"@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",
|
||||
"json-schema": "~0.4.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/bot-utils": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
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';
|
||||
|
||||
@@ -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 { type BasicMockSetInfo } from '@coze-studio/mockset-shared';
|
||||
|
||||
import { isCurrent } from '../../util';
|
||||
|
||||
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,196 @@
|
||||
/*
|
||||
* 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, 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 { MockTrafficEnabled } from '../../util/get-mock-set-options';
|
||||
import { isSameScene } from '../../util';
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
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 {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, 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,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
.layout-header {
|
||||
padding: 16px 24px 24px;
|
||||
}
|
||||
|
||||
|
||||
.content-title {
|
||||
max-width: 1160px;
|
||||
margin: 0 auto 16px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: var(--semi-color-text-0);
|
||||
}
|
||||
|
||||
.list-container_scroll {
|
||||
width: 100%;
|
||||
max-width: 1160px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.list-container_flexible,
|
||||
.list-container-no-header_flexible {
|
||||
width: 100%;
|
||||
max-width: 1160px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.list-container_flexible {
|
||||
height: calc(100% - 72px);
|
||||
}
|
||||
|
||||
.list-container-no-header_flexible {
|
||||
height: calc(100%);
|
||||
}
|
||||
|
||||
/** mock-set-intro */
|
||||
|
||||
.mock-set-intro-title {
|
||||
width: 100%;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.mock-set-intro-title_full {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mock-set-intro-name {
|
||||
overflow: hidden;
|
||||
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: var(--semi-color-text-0);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mock-set-intro-name_full {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.mock-set-intro-edit {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--semi-color-text-2)
|
||||
}
|
||||
|
||||
.mock-set-intro-edit_full,
|
||||
.mock-set-intro-edit_full svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.mock-set-intro-desc_priority.mock-set-intro-desc {
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
color: var(--semi-color-text-1);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mock-set-intro-desc_priority.mock-set-intro-desc_full {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
/** 创建框 **/
|
||||
.mock-creation-modal {
|
||||
:global {
|
||||
.semi-modal-content .semi-modal-body {
|
||||
/** 保证内部 tooltip 不被遮盖 **/
|
||||
overflow: unset
|
||||
}
|
||||
|
||||
.semi-modal-footer .semi-button{
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mock-creation-card {
|
||||
height: calc(100% - 24px);
|
||||
}
|
||||
|
||||
div.mock-creation-modal-editor {
|
||||
/** 兼容 modal 在小窗口下 body 高度不生效的问题 */
|
||||
height: calc(100vh - 316px);
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.mock-creation-card-editor {
|
||||
height: calc(100% - 40px - 32px - 48px);
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.mock-creation-card-operation {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/** mock-data-list **/
|
||||
.skeleton {
|
||||
:global {
|
||||
.semi-skeleton-image {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
justify-content: center;
|
||||
height: calc(100% - 68px);
|
||||
padding-bottom: 10%;
|
||||
|
||||
:global {
|
||||
.semi-empty-image svg {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.semi-empty-description {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.long-text.long-text-tooltip {
|
||||
color: var(--semi-color-bg-0);
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { type Ellipsis, type TextProps } from '@coze-arch/bot-semi/Typography';
|
||||
import { Typography } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface LongTextWithTooltip extends TextProps {
|
||||
tooltipText?: string;
|
||||
}
|
||||
|
||||
export function LongTextWithTooltip(props: LongTextWithTooltip) {
|
||||
const { children, ellipsis, tooltipText, ...rest } = props;
|
||||
|
||||
const ellipsisConfig: boolean | Ellipsis | undefined =
|
||||
ellipsis === false
|
||||
? ellipsis
|
||||
: {
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: (
|
||||
<Typography.Text
|
||||
className={classNames(s['long-text-tooltip'], s['long-text'])}
|
||||
onClick={e => e.stopPropagation()}
|
||||
ellipsis={{ showTooltip: false, rows: 16 }}
|
||||
>
|
||||
{tooltipText || props.children}
|
||||
</Typography.Text>
|
||||
),
|
||||
},
|
||||
},
|
||||
...(typeof ellipsis !== 'object' ? {} : ellipsis),
|
||||
};
|
||||
|
||||
return (
|
||||
<Typography.Text ellipsis={ellipsisConfig} {...rest}>
|
||||
{props.children}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
.mock-data-content {
|
||||
overflow: auto;
|
||||
|
||||
min-height: 64px;
|
||||
max-height: 500px;
|
||||
|
||||
word-wrap: break-word;
|
||||
|
||||
border: 1px var(--semi-color-border) solid;
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
.semi-tree-option-list-block .semi-tree-option-selected {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.semi-tree-option-list-block .semi-tree-option:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mock-data-card-operations {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
|
||||
padding: 4px;
|
||||
|
||||
visibility: hidden;
|
||||
background: #F7F7FA;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.mock-data-card {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:hover .mock-data-card-operations {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mock-data-card-edit,
|
||||
.mock-data-card-delete {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.mock-data-content-code {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.mock-data-banner {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 20%;
|
||||
width: 60%;
|
||||
|
||||
:global {
|
||||
.semi-banner-icon {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card-item {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.card-item_deleted {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.card-item-text {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: #1D1C23;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.card-item-text_required,
|
||||
.card-item-text_highlighted {
|
||||
color: var(--semi-color-danger);
|
||||
}
|
||||
|
||||
.card-item-text_primary {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-item-text_invalid {
|
||||
color: var(--semi-color-text-3);
|
||||
}
|
||||
|
||||
.card-item-text_stretched {
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-item-text_wrap {
|
||||
white-space: normal;
|
||||
|
||||
}
|
||||
|
||||
.card-item-tag {
|
||||
padding: 2px 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: #6B6B75;
|
||||
word-wrap: break-word;
|
||||
|
||||
background: rgb(46 46 56 / 8%);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.card-branches {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 8px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-branch-v {
|
||||
display: inline-block;
|
||||
|
||||
width: 13px;
|
||||
height: 100%;
|
||||
margin-left: 6px;
|
||||
|
||||
vertical-align: top;
|
||||
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.card-branch-v_visible {
|
||||
border-color: #C6C6CD;
|
||||
}
|
||||
|
||||
.card-branch-v_half {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.card-branch-h {
|
||||
display: inline-block;
|
||||
|
||||
width: 6px;
|
||||
height: 15px;
|
||||
margin-left: -13px;
|
||||
|
||||
vertical-align: top;
|
||||
|
||||
border-color: #C6C6CD;
|
||||
border-style: solid;
|
||||
border-width: 0 0 1px 1px;
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
|
||||
.card-branch-h_long {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.card-non-tree-container {
|
||||
padding: 8px 30px;
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type TreeNodeData } from '@coze-arch/bot-semi/Tree';
|
||||
import { Space, Tree, UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { IconEditNew, IconDeleteOutline } from '@coze-arch/bot-icons';
|
||||
import { type infra, type MockRule } from '@coze-arch/bot-api/debugger_api';
|
||||
import { ROOT_KEY } from '@coze-studio/mockset-shared';
|
||||
|
||||
import { transUpperCase } from '../../util/utils';
|
||||
import {
|
||||
type MockDataInfo,
|
||||
MockDataStatus,
|
||||
MockDataValueType,
|
||||
type MockDataWithStatus,
|
||||
} from '../../util/typings';
|
||||
import { useTransSchema } from '../../hook/use-trans-schema';
|
||||
import { BranchType, useGenTreeBranch } from '../../hook/use-gen-tree-branch';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface MockDataCardProps {
|
||||
mock?: MockRule;
|
||||
schema?: string;
|
||||
readOnly?: boolean;
|
||||
className?: string;
|
||||
|
||||
onEdit?: (params: MockDataInfo) => void;
|
||||
onRemove?: (params: MockDataInfo) => void;
|
||||
bizCtx: infra.BizCtx;
|
||||
}
|
||||
|
||||
/** mock data 展示卡片 */
|
||||
export function MockDataCard({
|
||||
mock,
|
||||
readOnly,
|
||||
schema,
|
||||
onEdit,
|
||||
onRemove,
|
||||
}: MockDataCardProps) {
|
||||
const { formattedResultExample, incompatible, mergedResult } = useTransSchema(
|
||||
schema,
|
||||
mock?.responseExpect?.responseExpectRule,
|
||||
);
|
||||
|
||||
const { branchInfo, prunedData } = useGenTreeBranch(mergedResult);
|
||||
|
||||
const deleteHandler = () => {
|
||||
onRemove?.({
|
||||
schema,
|
||||
mock,
|
||||
});
|
||||
};
|
||||
|
||||
const editHandler = () => {
|
||||
onEdit?.({
|
||||
schema,
|
||||
mock,
|
||||
mergedResultExample: formattedResultExample,
|
||||
incompatible,
|
||||
});
|
||||
};
|
||||
|
||||
const renderBranches = (item: MockDataWithStatus, isLevel0Item: boolean) => {
|
||||
const branchThisRow = item?.key ? branchInfo[item.key] : undefined;
|
||||
|
||||
return (
|
||||
<span className={s['card-branches']}>
|
||||
{branchThisRow?.v.map((type, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={classNames(
|
||||
s['card-branch-v'],
|
||||
type !== BranchType.NONE ? s['card-branch-v_visible'] : '',
|
||||
type === BranchType.HALF ? s['card-branch-v_half'] : '',
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
{!isLevel0Item ? (
|
||||
<span
|
||||
className={classNames(
|
||||
s['card-branch-h'],
|
||||
item?.children ? '' : s['card-branch-h_long'],
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFieldContent = (item: MockDataWithStatus) => {
|
||||
const isRemoved = item?.status === MockDataStatus.REMOVED;
|
||||
|
||||
if (item?.status === MockDataStatus.ADDED) {
|
||||
return (
|
||||
<MockDataValueSpan
|
||||
val={
|
||||
item.isRequired
|
||||
? I18n.t('mockset_field_is_required', { field: item?.label })
|
||||
: undefined
|
||||
}
|
||||
className={classNames(
|
||||
'ms-[8px]',
|
||||
item.isRequired ? s['card-item-text_highlighted'] : '',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (item?.type === MockDataValueType.ARRAY ||
|
||||
item?.type === MockDataValueType.OBJECT) &&
|
||||
item?.children ? (
|
||||
''
|
||||
) : (
|
||||
<MockDataValueSpan
|
||||
val={item?.displayValue}
|
||||
className={classNames(
|
||||
'ms-[8px]',
|
||||
isRemoved ? s['card-item-text_highlighted'] : '',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const renderLabel = (_, node?: TreeNodeData) => {
|
||||
const item = node as MockDataWithStatus | undefined;
|
||||
const isLevel0Item = `${ROOT_KEY}-${item?.label}` === item?.key;
|
||||
const isRemoved = item?.status === MockDataStatus.REMOVED;
|
||||
const isAdded = item?.status === MockDataStatus.ADDED && item.isRequired;
|
||||
|
||||
return item ? (
|
||||
<>
|
||||
{renderBranches(item, isLevel0Item)}
|
||||
|
||||
<span
|
||||
className={classNames(
|
||||
s['card-item'],
|
||||
isRemoved || isAdded ? s['card-item-text_highlighted'] : '',
|
||||
isRemoved ? s['card-item_deleted'] : '',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
s['card-item-text'],
|
||||
isLevel0Item ? s['card-item-text_primary'] : '',
|
||||
isRemoved || isAdded ? s['card-item-text_highlighted'] : '',
|
||||
)}
|
||||
>
|
||||
{item?.label}
|
||||
</span>
|
||||
|
||||
{item?.isRequired ? (
|
||||
<span
|
||||
className={classNames(
|
||||
s['card-item-text'],
|
||||
s['card-item-text_required'],
|
||||
)}
|
||||
>
|
||||
*
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{!isRemoved && !isAdded ? (
|
||||
<span className={classNames(s['card-item-tag'], 'ms-[8px]')}>
|
||||
{transUpperCase(item?.type)}
|
||||
{item?.type === MockDataValueType.ARRAY
|
||||
? `<${transUpperCase(item?.childrenType)}>`
|
||||
: ''}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{renderFieldContent(item)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
const renderData = () => {
|
||||
if (
|
||||
prunedData?.type === MockDataValueType.ARRAY ||
|
||||
prunedData?.type === MockDataValueType.OBJECT
|
||||
) {
|
||||
if (prunedData.children?.length) {
|
||||
return (
|
||||
<Tree
|
||||
defaultExpandAll
|
||||
treeData={prunedData.children}
|
||||
renderLabel={renderLabel}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={s['card-non-tree-container']}>
|
||||
<span
|
||||
className={classNames(
|
||||
s['card-item-text'],
|
||||
s['card-item-text_invalid'],
|
||||
)}
|
||||
>
|
||||
Empty
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<div className={s['card-non-tree-container']}>
|
||||
<MockDataValueSpan val={prunedData?.displayValue} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return mock?.responseExpect?.responseExpectRule ? (
|
||||
<div className={s['mock-data-card']}>
|
||||
<div className={s['mock-data-content']}>{renderData()}</div>
|
||||
{!readOnly ? (
|
||||
<Space className={s['mock-data-card-operations']} spacing={12}>
|
||||
<UIIconButton
|
||||
icon={<IconEditNew className={s['mock-data-card-edit']} />}
|
||||
size="small"
|
||||
theme="borderless"
|
||||
onClick={editHandler}
|
||||
/>
|
||||
<UIIconButton
|
||||
icon={<IconDeleteOutline className={s['mock-data-card-edit']} />}
|
||||
size="small"
|
||||
theme="borderless"
|
||||
onClick={deleteHandler}
|
||||
/>
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
const MockDataValueSpan = (props: { val?: string; className?: string }) =>
|
||||
props.val ? (
|
||||
<span className={classNames(props.className, s['card-item-text'])}>
|
||||
{props.val}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={classNames(
|
||||
props.className,
|
||||
s['card-item-text'],
|
||||
s['card-item-text_invalid'],
|
||||
)}
|
||||
>
|
||||
Undefined
|
||||
</span>
|
||||
);
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import {
|
||||
type PluginMockSetCommonParams,
|
||||
type PluginMockDataGenerateMode,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import {
|
||||
Space,
|
||||
Toast,
|
||||
UIButton,
|
||||
UIModal,
|
||||
Typography,
|
||||
Divider,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { SpaceType } from '@coze-arch/bot-api/playground_api';
|
||||
import { type mockset, type infra } from '@coze-arch/bot-api/debugger_api';
|
||||
import {
|
||||
calcStringSize,
|
||||
type MockDataInfo,
|
||||
MAX_SUBMIT_LENGTH,
|
||||
getEnvironment,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
import {
|
||||
MocksetEditor,
|
||||
type EditorAreaActions,
|
||||
} from '@coze-studio/mockset-editor-adapter';
|
||||
|
||||
import {
|
||||
PRE_DEFINED_NO_EMPTY_KEY,
|
||||
useTransSchema,
|
||||
} from '../hook/use-trans-schema';
|
||||
import { useSaveMockData } from '../hook/use-save-mock-data';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export enum CreationMode {
|
||||
/** 弹窗形式 */
|
||||
MODAL = 'modal',
|
||||
/** 嵌入页面 */
|
||||
CARD = 'card',
|
||||
}
|
||||
|
||||
interface MockDataCreateCardProps {
|
||||
mode: CreationMode;
|
||||
mockInfo?: MockDataInfo;
|
||||
// mode 为 modal 时生效
|
||||
visible?: boolean;
|
||||
// mode 为 modal 时生效
|
||||
onCancel?: () => void;
|
||||
onSuccess: (data?: mockset.MockRule[]) => void;
|
||||
bizCtx: infra.BizCtx;
|
||||
forceGenerate?: {
|
||||
mode: PluginMockDataGenerateMode;
|
||||
count: number;
|
||||
};
|
||||
}
|
||||
|
||||
/** 创建or编辑 mock data - */
|
||||
export function MockDataCreateCard({
|
||||
mode,
|
||||
mockInfo,
|
||||
visible,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
bizCtx,
|
||||
forceGenerate,
|
||||
}: MockDataCreateCardProps) {
|
||||
const { schema } = mockInfo || {};
|
||||
const editorsRef = useRef<EditorAreaActions>(null);
|
||||
const [disableSubmit, setDisableSubmit] = useState(false);
|
||||
const [disableSubmitWhenGenerating, setDisableSubmitWhenGenerating] =
|
||||
useState(false);
|
||||
|
||||
const { testValueValid, formattedResultExample: initialExample } =
|
||||
useTransSchema(schema);
|
||||
const { mock_set_id, tool_id } = useParams<DynamicParams>();
|
||||
|
||||
// space信息
|
||||
const spaceType = useSpaceStore(store => store.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
const basicParams: PluginMockSetCommonParams = {
|
||||
environment: getEnvironment(),
|
||||
workspace_id: bizCtx.bizSpaceID || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
tool_id: tool_id || '',
|
||||
mock_set_id: mock_set_id || '',
|
||||
};
|
||||
|
||||
const { save, loading } = useSaveMockData({
|
||||
mockSetId: mock_set_id,
|
||||
basicParams,
|
||||
bizCtx,
|
||||
onSuccess,
|
||||
});
|
||||
|
||||
const confirmHandler = () => {
|
||||
const values = editorsRef.current?.getValue();
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const value of values) {
|
||||
if (!value) {
|
||||
Toast.error('no data');
|
||||
return;
|
||||
}
|
||||
|
||||
if (calcStringSize(value) > MAX_SUBMIT_LENGTH) {
|
||||
Toast.error({
|
||||
content: I18n.t('mockset_toast_data_size_limit'),
|
||||
showClose: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!testValueValid(value)) {
|
||||
Toast.error({
|
||||
content: I18n.t('mockdata_field_empty', {
|
||||
fieldName: PRE_DEFINED_NO_EMPTY_KEY,
|
||||
}),
|
||||
showClose: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const mockDataId = String(mockInfo?.mock?.id || 0);
|
||||
|
||||
save(values, mockDataId);
|
||||
};
|
||||
|
||||
const validateHandler = (isValid: boolean[]) => {
|
||||
setDisableSubmit(isValid.some(v => !v));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const unloadHandler = e => {
|
||||
const info = I18n.t('mockset_tip_data_will_lose');
|
||||
e.preventDefault();
|
||||
e.returnValue = info;
|
||||
return info;
|
||||
};
|
||||
|
||||
if (
|
||||
(mode === CreationMode.MODAL && visible) ||
|
||||
mode === CreationMode.CARD
|
||||
) {
|
||||
window.addEventListener('beforeunload', unloadHandler);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', unloadHandler);
|
||||
};
|
||||
}, [mode, visible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (forceGenerate) {
|
||||
editorsRef.current?.forceStartGenerate?.(
|
||||
forceGenerate.mode,
|
||||
forceGenerate.count,
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return mode === CreationMode.MODAL ? (
|
||||
<UIModal
|
||||
visible={visible}
|
||||
title={
|
||||
mockInfo?.mock ? I18n.t('edit_mock_data') : I18n.t('add_mock_data')
|
||||
}
|
||||
className={s['mock-creation-modal']}
|
||||
keepDOM={false}
|
||||
footer={
|
||||
<>
|
||||
<span className="mr-[8px]">{I18n.t('mockset_save_description')}</span>
|
||||
<Divider layout="vertical" margin="0px" />
|
||||
<UIButton type={'tertiary'} key="Cancel" onClick={onCancel}>
|
||||
{I18n.t('cancel')}
|
||||
</UIButton>
|
||||
<UIButton
|
||||
type={'primary'}
|
||||
theme={'solid'}
|
||||
key="Confirm"
|
||||
onClick={confirmHandler}
|
||||
loading={loading}
|
||||
disabled={disableSubmit || disableSubmitWhenGenerating}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</UIButton>
|
||||
</>
|
||||
}
|
||||
width={1000}
|
||||
maskClosable={false}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<MocksetEditor
|
||||
className={s['mock-creation-modal-editor']}
|
||||
mockInfo={{
|
||||
mergedResultExample: initialExample,
|
||||
...mockInfo,
|
||||
}}
|
||||
readOnly={false}
|
||||
ref={editorsRef}
|
||||
onValidate={validateHandler}
|
||||
environment={{
|
||||
spaceId: bizCtx.bizSpaceID,
|
||||
mockSetId: mock_set_id,
|
||||
basicParams,
|
||||
}}
|
||||
isCreateScene={!mockInfo?.mock}
|
||||
onGenerationStatusChange={isGenerating =>
|
||||
setDisableSubmitWhenGenerating(isGenerating)
|
||||
}
|
||||
/>
|
||||
</UIModal>
|
||||
) : (
|
||||
<div className={s['mock-creation-card']}>
|
||||
<div className={s['mock-creation-card-editor']}>
|
||||
<MocksetEditor
|
||||
mockInfo={{
|
||||
mergedResultExample: initialExample,
|
||||
...mockInfo,
|
||||
}}
|
||||
ref={editorsRef}
|
||||
onValidate={validateHandler}
|
||||
environment={{
|
||||
spaceId: bizCtx.bizSpaceID,
|
||||
mockSetId: mock_set_id,
|
||||
basicParams,
|
||||
}}
|
||||
isCreateScene={!mockInfo?.mock}
|
||||
onGenerationStatusChange={isGenerating =>
|
||||
setDisableSubmitWhenGenerating(isGenerating)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={s['mock-creation-card-operation']}>
|
||||
<Space>
|
||||
<Typography.Text>
|
||||
{I18n.t('mockset_save_description')}
|
||||
</Typography.Text>
|
||||
<Divider layout="vertical" margin="0px" />
|
||||
<UIButton
|
||||
type={'primary'}
|
||||
theme={'solid'}
|
||||
onClick={confirmHandler}
|
||||
loading={loading}
|
||||
disabled={disableSubmit || disableSubmitWhenGenerating}
|
||||
>
|
||||
{I18n.t('mockset_save')}
|
||||
</UIButton>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
forwardRef,
|
||||
type ForwardedRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
sendTeaEvent,
|
||||
type ParamsTypeDefine,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { Empty, Spin, UIModal } from '@coze-arch/bot-semi';
|
||||
import { PageType, usePageJumpResponse } from '@coze-arch/bot-hooks';
|
||||
import { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
import { infra, type MockRule } from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
IllustrationNoContent,
|
||||
IllustrationNoContentDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
import { getEnvironment } from '@coze-studio/mockset-shared';
|
||||
|
||||
import { type MockDataInfo } from '../util/typings';
|
||||
import { SpaceHolder } from './space-holder';
|
||||
import { CreationMode, MockDataCreateCard } from './mock-data-create-card';
|
||||
import { MockDataCard } from './mock-data-card';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
enum RuleActions {
|
||||
CREATE,
|
||||
EDIT,
|
||||
DELETE,
|
||||
}
|
||||
|
||||
interface MockDataListProps {
|
||||
mockSetID?: string;
|
||||
perm: {
|
||||
readOnly: boolean;
|
||||
uninitialized: boolean;
|
||||
};
|
||||
toolSchema: string;
|
||||
bizCtx: infra.BizCtx;
|
||||
onListUpdate?: (length: number, needScrollToTop?: boolean) => void;
|
||||
}
|
||||
|
||||
export interface MockDataListActions {
|
||||
update: () => void;
|
||||
create: () => void;
|
||||
}
|
||||
|
||||
export const MockDataList = forwardRef(
|
||||
(
|
||||
{ mockSetID, perm, toolSchema, bizCtx, onListUpdate }: MockDataListProps,
|
||||
ref: ForwardedRef<MockDataListActions>,
|
||||
) => {
|
||||
// loading
|
||||
const [loading, setLoading] = useState(false);
|
||||
// mock data list
|
||||
const [mockDataList, setMockDataList] = useState<MockRule[]>([]);
|
||||
// modal visible
|
||||
const [createModalVisible, setCreateModalVisible] =
|
||||
useState<boolean>(false);
|
||||
// delete modal visible
|
||||
const [deleteModalVisible, setDeleteModalVisible] =
|
||||
useState<boolean>(false);
|
||||
const [deleting, setDeleting] = useState<boolean>(false);
|
||||
// 当前选中状态
|
||||
const [currentSelect, setCurrentSelect] = useState<
|
||||
MockDataInfo | undefined
|
||||
>();
|
||||
|
||||
const routeResponse = usePageJumpResponse(PageType.PLUGIN_MOCK_DATA);
|
||||
|
||||
const { mock_set_id, space_id, tool_id } = useParams<DynamicParams>();
|
||||
// space信息
|
||||
const spaceType = useSpaceStore(store => store.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
const clickItemUpdateEntryHandler = (params: MockDataInfo) => {
|
||||
setCurrentSelect(params);
|
||||
setCreateModalVisible(true);
|
||||
};
|
||||
|
||||
const clickItemDeleteEntryHandler = (params: MockDataInfo) => {
|
||||
setCurrentSelect(params);
|
||||
setDeleteModalVisible(true);
|
||||
};
|
||||
|
||||
// 获取当前 mock set 下的 mock data
|
||||
const getMockData = async (needScrollToTop?: boolean) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await debuggerApi.MGetMockRule({
|
||||
bizCtx,
|
||||
mockSetID: mock_set_id,
|
||||
orderBy: infra.OrderBy.UpdateTime,
|
||||
desc: true,
|
||||
});
|
||||
setMockDataList(data.mockRules || []);
|
||||
|
||||
onListUpdate?.(data.mockRules?.length || 0, needScrollToTop);
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'get_mock_data_fail' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteConfirmHandler = async () => {
|
||||
const { mock } = currentSelect || {};
|
||||
if (!mock) {
|
||||
return;
|
||||
}
|
||||
|
||||
const basicParams: Omit<
|
||||
ParamsTypeDefine[EVENT_NAMES.del_mock_front],
|
||||
'status'
|
||||
> = {
|
||||
environment: getEnvironment(),
|
||||
workspace_id: space_id || '',
|
||||
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
|
||||
tool_id: tool_id || '',
|
||||
mock_set_id: mock_set_id || '',
|
||||
mock_counts: 1,
|
||||
};
|
||||
|
||||
try {
|
||||
setDeleting(true);
|
||||
await debuggerApi.DeleteMockRule({
|
||||
bizCtx,
|
||||
id: String(mock.id),
|
||||
});
|
||||
|
||||
updateList(mock, RuleActions.DELETE);
|
||||
setCurrentSelect(undefined);
|
||||
setDeleteModalVisible(false);
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.del_mock_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'delete_mock_fail' });
|
||||
sendTeaEvent(EVENT_NAMES.del_mock_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 前端更新
|
||||
const updateList = (data: MockRule, action: RuleActions) => {
|
||||
let len = 0;
|
||||
if (action === RuleActions.CREATE) {
|
||||
// 创建场景直接 force update
|
||||
getMockData(true);
|
||||
} else if (action === RuleActions.DELETE) {
|
||||
len = mockDataList.length - 1;
|
||||
setMockDataList(cur => {
|
||||
const index = cur.findIndex(item => item.id === data?.id);
|
||||
if (index !== -1) {
|
||||
cur.splice(index, 1);
|
||||
}
|
||||
len = cur.length;
|
||||
return [...cur];
|
||||
});
|
||||
|
||||
onListUpdate?.(len);
|
||||
} else {
|
||||
len = mockDataList.length;
|
||||
setMockDataList(cur => {
|
||||
const index = cur.findIndex(item => item.id === data?.id);
|
||||
if (index !== -1) {
|
||||
cur.splice(index, 1);
|
||||
cur.unshift(data);
|
||||
}
|
||||
len = cur.length;
|
||||
return [...cur];
|
||||
});
|
||||
onListUpdate?.(len, true);
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
update: getMockData,
|
||||
create: () => {
|
||||
setCurrentSelect(undefined);
|
||||
setCreateModalVisible(true);
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
getMockData();
|
||||
}, [mockSetID]);
|
||||
|
||||
useEffect(() => {
|
||||
if (routeResponse?.generationMode) {
|
||||
// 清除跳转参数
|
||||
const state = {
|
||||
...history.state,
|
||||
usr: { ...(history.state.usr || {}), generationMode: undefined },
|
||||
};
|
||||
history.replaceState(state, '');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderList = () => {
|
||||
if (loading || perm.uninitialized) {
|
||||
return (
|
||||
<div className={s['list-container-no-header_flexible']}>
|
||||
<Spin
|
||||
size="large"
|
||||
spinning
|
||||
style={{ height: '80%', width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (perm.readOnly && mockDataList.length === 0) {
|
||||
return (
|
||||
<>
|
||||
<h1 className={classNames(s['content-title'])}>
|
||||
{I18n.t('mockset_data')}
|
||||
</h1>
|
||||
<div className={s['list-container_flexible']}>
|
||||
<Empty
|
||||
className={s.empty}
|
||||
image={<IllustrationNoContent />}
|
||||
darkModeImage={<IllustrationNoContentDark />}
|
||||
description={I18n.t('no_mock_yet')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!perm.readOnly && mockDataList.length === 0) {
|
||||
return (
|
||||
<div className={s['list-container-no-header_flexible']}>
|
||||
<MockDataCreateCard
|
||||
mode={CreationMode.CARD}
|
||||
mockInfo={{
|
||||
schema: toolSchema,
|
||||
}}
|
||||
onSuccess={data => {
|
||||
data && updateList(data[0], RuleActions.CREATE);
|
||||
}}
|
||||
bizCtx={bizCtx}
|
||||
forceGenerate={
|
||||
routeResponse?.generationMode
|
||||
? {
|
||||
mode: routeResponse.generationMode,
|
||||
count: 1,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className={classNames(s['content-title'])}>
|
||||
{I18n.t('mockset_data')}
|
||||
</h1>
|
||||
<div className={s['list-container_scroll']}>
|
||||
{mockDataList.map(item => (
|
||||
<MockDataCard
|
||||
readOnly={perm.readOnly}
|
||||
key={item.id}
|
||||
mock={item}
|
||||
schema={toolSchema}
|
||||
onEdit={params => clickItemUpdateEntryHandler(params)}
|
||||
onRemove={params => clickItemDeleteEntryHandler(params)}
|
||||
bizCtx={bizCtx}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpaceHolder height={24} />
|
||||
{renderList()}
|
||||
<MockDataCreateCard
|
||||
mode={CreationMode.MODAL}
|
||||
mockInfo={
|
||||
currentSelect || {
|
||||
schema: toolSchema,
|
||||
}
|
||||
}
|
||||
visible={createModalVisible}
|
||||
onCancel={() => {
|
||||
setCurrentSelect(undefined);
|
||||
setCreateModalVisible(false);
|
||||
}}
|
||||
onSuccess={data => {
|
||||
setCurrentSelect(undefined);
|
||||
setCreateModalVisible(false);
|
||||
data?.[0] &&
|
||||
updateList(
|
||||
data[0],
|
||||
currentSelect ? RuleActions.EDIT : RuleActions.CREATE,
|
||||
);
|
||||
}}
|
||||
bizCtx={bizCtx}
|
||||
/>
|
||||
<UIModal
|
||||
type="info"
|
||||
icon={
|
||||
<IconAlertCircle
|
||||
size="extra-large"
|
||||
className="inline-flex text-[#FF2710]"
|
||||
/>
|
||||
}
|
||||
title={I18n.t('delete_mock_data')}
|
||||
visible={deleteModalVisible}
|
||||
onCancel={() => {
|
||||
setCurrentSelect(undefined);
|
||||
setDeleteModalVisible(false);
|
||||
}}
|
||||
okText={I18n.t('confirm')}
|
||||
cancelText={I18n.t('cancel')}
|
||||
confirmLoading={deleting}
|
||||
onOk={() => deleteConfirmHandler()}
|
||||
okType="danger"
|
||||
>
|
||||
{I18n.t('operation_cannot_be_reversed')}
|
||||
</UIModal>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 { UIBreadcrumb } from '@coze-studio/components';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { UILayout } from '@coze-arch/bot-semi';
|
||||
import { usePageJumpResponse, PageType } from '@coze-arch/bot-hooks';
|
||||
import {
|
||||
type PluginMetaInfo,
|
||||
type PluginAPIInfo,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { type MockSet } from '@coze-arch/bot-api/debugger_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface MockSetPageBreadcrumbProps {
|
||||
pluginId?: string;
|
||||
apiInfo?: PluginAPIInfo;
|
||||
mockSetInfo?: MockSet;
|
||||
}
|
||||
|
||||
export function MockSetPageBreadcrumb({
|
||||
pluginId,
|
||||
apiInfo,
|
||||
mockSetInfo,
|
||||
}: MockSetPageBreadcrumbProps) {
|
||||
const routeResponse = usePageJumpResponse(PageType.PLUGIN_MOCK_DATA);
|
||||
|
||||
// 插件详情
|
||||
const [pluginInfo, setPluginInfo] = useState<PluginMetaInfo>({
|
||||
name: routeResponse?.pluginName,
|
||||
});
|
||||
|
||||
// 获取当前 plugin 信息
|
||||
const getPluginInfo = async () => {
|
||||
try {
|
||||
const res = await DeveloperApi.GetPluginInfo(
|
||||
{
|
||||
plugin_id: pluginId || '',
|
||||
},
|
||||
{ __disableErrorToast: true },
|
||||
);
|
||||
if (res?.code === 0) {
|
||||
setPluginInfo(res.meta_info || {});
|
||||
}
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'get_plugin_info_fail' });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getPluginInfo();
|
||||
}, [pluginId]);
|
||||
|
||||
return (
|
||||
<UILayout.Header
|
||||
className={s['layout-header']}
|
||||
breadcrumb={
|
||||
<UIBreadcrumb
|
||||
showTooltip={{ width: '300px' }}
|
||||
pluginInfo={pluginInfo}
|
||||
pluginToolInfo={apiInfo}
|
||||
mockSetInfo={mockSetInfo}
|
||||
compact={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
.select-container {
|
||||
display: inline-block;
|
||||
|
||||
max-width: 100%;
|
||||
padding: 4px 6px;
|
||||
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
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 {
|
||||
min-width: 234px;
|
||||
max-width: 336px;
|
||||
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 {
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select-label {
|
||||
svg {
|
||||
>path {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
fill: currentcolor !important;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-option-render-focused {
|
||||
cursor: pointer;
|
||||
background-color: rgb(46 46 56 / 8%);
|
||||
|
||||
}
|
||||
|
||||
.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;
|
||||
margin: 4px 0;
|
||||
background: var(--semi-color-border);
|
||||
}
|
||||
|
||||
.create-container {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
|
||||
color: #4D53E8;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #4D53E8;
|
||||
|
||||
.spin-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* 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 {
|
||||
EVENT_NAMES,
|
||||
type ParamsTypeDefine,
|
||||
sendTeaEvent,
|
||||
} 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, 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 {
|
||||
type MockSelectOptionProps,
|
||||
type MockSelectRenderOptionProps,
|
||||
type MockSetSelectProps,
|
||||
MockSetStatus,
|
||||
getEnvironment,
|
||||
getMockSubjectInfo,
|
||||
getPluginInfo,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
import {
|
||||
builtinSuccessCallback,
|
||||
MockSetEditModal,
|
||||
} from '@coze-studio/mockset-edit-modal-adapter';
|
||||
|
||||
import { MockSetDeleteModal } from '../mockset-delete-modal';
|
||||
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 { getUsedScene, isCurrent, isRealData } from '../../util';
|
||||
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);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'change_mockset_fail' });
|
||||
isBinding &&
|
||||
sendTeaEvent(EVENT_NAMES.use_mockset_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error: e?.msg 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,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
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) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, 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?: { generateMode: number },
|
||||
) => {
|
||||
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
|
||||
zIndex={100}
|
||||
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, config) => {
|
||||
const { id } = info || {};
|
||||
setShowCreateModal(false);
|
||||
|
||||
builtinSuccessCallback(config);
|
||||
|
||||
handleView({ id }, config);
|
||||
}}
|
||||
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,49 @@
|
||||
.mock-select-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: '100%';
|
||||
}
|
||||
|
||||
.mock-main-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
|
||||
.status-icon {
|
||||
margin-right: 4px;
|
||||
color: #FF8500;
|
||||
}
|
||||
|
||||
.mock-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mock-extra-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
width: 64px;
|
||||
margin-left: 16px;
|
||||
|
||||
.creator-name {
|
||||
font-size: 12px;
|
||||
color: #1D1C2359;
|
||||
}
|
||||
|
||||
.operation-icon {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
width: 40px;
|
||||
|
||||
font-size: 14px;
|
||||
color: #1D1C2399;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 '@coze-studio/mockset-shared';
|
||||
|
||||
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,112 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { Space, UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { IconEditNew } from '@coze-arch/bot-icons';
|
||||
import { type infra, type MockSet } from '@coze-arch/bot-api/debugger_api';
|
||||
import { MockSetEditModal } from '@coze-studio/mockset-edit-modal-adapter';
|
||||
|
||||
import { LongTextWithTooltip } from './long-text-with-tooltip';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface MockSetIntroProps {
|
||||
isFullHeader: boolean;
|
||||
readOnly?: boolean;
|
||||
mockSetInfo: MockSet;
|
||||
onUpdateMockSetInfo?: (mockSetInfo?: MockSet) => void;
|
||||
bizCtx: infra.BizCtx;
|
||||
}
|
||||
|
||||
const GAP_2 = 2;
|
||||
const GAP_4 = 4;
|
||||
|
||||
export function MockSetIntro({
|
||||
isFullHeader = true,
|
||||
readOnly = true,
|
||||
mockSetInfo,
|
||||
onUpdateMockSetInfo,
|
||||
bizCtx,
|
||||
}: MockSetIntroProps) {
|
||||
const [showEditModal, setShowEditModal] = useState<boolean>(false);
|
||||
const editHandler = (info?: MockSet) => {
|
||||
onUpdateMockSetInfo?.(info);
|
||||
setShowEditModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space
|
||||
spacing={isFullHeader ? GAP_2 : GAP_4}
|
||||
className={classNames(
|
||||
s['mock-set-intro-title'],
|
||||
isFullHeader ? s['mock-set-intro-title_full'] : '',
|
||||
)}
|
||||
>
|
||||
<LongTextWithTooltip
|
||||
className={classNames(
|
||||
s['mock-set-intro-name'],
|
||||
isFullHeader ? s['mock-set-intro-name_full'] : '',
|
||||
)}
|
||||
>
|
||||
{mockSetInfo.name}
|
||||
</LongTextWithTooltip>
|
||||
{!readOnly && mockSetInfo.name ? (
|
||||
<UIIconButton
|
||||
icon={
|
||||
<IconEditNew
|
||||
className={classNames(
|
||||
s['mock-set-intro-edit'],
|
||||
isFullHeader ? s['mock-set-intro-edit_full'] : '',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
theme="borderless"
|
||||
onClick={() => setShowEditModal(true)}
|
||||
/>
|
||||
) : null}
|
||||
<MockSetEditModal
|
||||
visible={showEditModal}
|
||||
initialInfo={{
|
||||
bindSubjectInfo: mockSetInfo.mockSubject || {},
|
||||
bizCtx,
|
||||
id: String(mockSetInfo.id),
|
||||
name: mockSetInfo.name,
|
||||
desc: mockSetInfo.description,
|
||||
}}
|
||||
onSuccess={editHandler}
|
||||
onCancel={() => setShowEditModal(false)}
|
||||
></MockSetEditModal>
|
||||
</Space>
|
||||
|
||||
{mockSetInfo.description ? (
|
||||
<LongTextWithTooltip
|
||||
className={classNames(
|
||||
s['mock-set-intro-desc'],
|
||||
s['mock-set-intro-desc_priority'],
|
||||
isFullHeader ? s['mock-set-intro-desc_full'] : '',
|
||||
)}
|
||||
>
|
||||
{mockSetInfo.description}
|
||||
</LongTextWithTooltip>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
type ParamsTypeDefine,
|
||||
sendTeaEvent,
|
||||
} 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 '@coze-studio/mockset-shared';
|
||||
|
||||
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 () => {
|
||||
try {
|
||||
const { spaceID } = getPluginInfo(
|
||||
ctx?.bizCtx || {},
|
||||
ctx?.mockSubjectInfo || {},
|
||||
);
|
||||
const { usersUsageCount } = await debuggerApi.GetMockSetUsageInfo({
|
||||
mockSetID: id,
|
||||
spaceID,
|
||||
});
|
||||
setMockSetRefCount(Number(usersUsageCount ?? 0));
|
||||
} catch (e) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, 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,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error: e?.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,27 @@
|
||||
/*
|
||||
* 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 SpaceHolder({
|
||||
height,
|
||||
width,
|
||||
}: {
|
||||
height?: number;
|
||||
width?: number;
|
||||
}) {
|
||||
return (
|
||||
<div style={{ width, height, display: width ? 'inline-block' : 'block' }} />
|
||||
);
|
||||
}
|
||||
19
frontend/packages/agent-ide/bot-plugin/mock-set/src/edenx-app-env.d.ts
vendored
Normal file
19
frontend/packages/agent-ide/bot-plugin/mock-set/src/edenx-app-env.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
|
||||
declare module 'card-builder-sdk/*';
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 {
|
||||
MockDataStatus,
|
||||
MockDataValueType,
|
||||
type MockDataWithStatus,
|
||||
} from '../util/typings';
|
||||
|
||||
export enum BranchType {
|
||||
NONE,
|
||||
VISIBLE,
|
||||
HALF,
|
||||
}
|
||||
|
||||
type BranchInfo = Record<
|
||||
string,
|
||||
| {
|
||||
// 纵向连接线
|
||||
v: BranchType[];
|
||||
isLast: boolean;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
|
||||
export function useGenTreeBranch(mockData?: MockDataWithStatus) {
|
||||
const [branchInfo, setBranchInfo] = useState<BranchInfo>({});
|
||||
const [pruned, setPruned] = useState<MockDataWithStatus>();
|
||||
|
||||
// 裁剪树枝
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const pruning = (data?: MockDataWithStatus) => {
|
||||
if (!data?.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const children = data.children.map(cur => {
|
||||
if (
|
||||
cur.type === MockDataValueType.ARRAY ||
|
||||
cur.type === MockDataValueType.OBJECT
|
||||
) {
|
||||
if (cur.isRequired === false && cur.status === MockDataStatus.ADDED) {
|
||||
return {
|
||||
...cur,
|
||||
children: undefined,
|
||||
};
|
||||
} else {
|
||||
return pruning(cur);
|
||||
}
|
||||
} else {
|
||||
return { ...cur };
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...data,
|
||||
children,
|
||||
};
|
||||
};
|
||||
|
||||
const generate = (
|
||||
data?: MockDataWithStatus,
|
||||
branchPrefix: BranchType[] = [],
|
||||
) => {
|
||||
const branch: BranchInfo = {};
|
||||
if (data?.children) {
|
||||
const { length } = data.children;
|
||||
data?.children.forEach((item, index) => {
|
||||
const isLast = index === length - 1;
|
||||
branch[item.key] = {
|
||||
isLast,
|
||||
v:
|
||||
isLast && branchPrefix.length > 0
|
||||
? [...branchPrefix.slice(0, -1), BranchType.HALF]
|
||||
: branchPrefix,
|
||||
};
|
||||
const childBranchPrefix: BranchType[] =
|
||||
isLast && branchPrefix.length > 0
|
||||
? [
|
||||
...branchPrefix.slice(0, -1),
|
||||
BranchType.NONE,
|
||||
BranchType.VISIBLE,
|
||||
]
|
||||
: [...branchPrefix, BranchType.VISIBLE];
|
||||
Object.assign(branch, generate(item, childBranchPrefix));
|
||||
});
|
||||
}
|
||||
return branch;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const result = pruning(mockData);
|
||||
const branch = generate(result);
|
||||
|
||||
setPruned(result);
|
||||
setBranchInfo(branch);
|
||||
}, [mockData]);
|
||||
|
||||
return {
|
||||
branchInfo,
|
||||
prunedData: pruned,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* 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 { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useMemoizedFn, useRequest } from 'ahooks';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
type PluginMockDataGenerateMode,
|
||||
sendTeaEvent,
|
||||
type ParamsTypeDefine,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { SceneType, usePageJumpService } from '@coze-arch/bot-hooks';
|
||||
import { SpaceType } from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
type MockSet,
|
||||
type BizCtx,
|
||||
infra,
|
||||
TrafficScene,
|
||||
type MockSetBinding,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
getEnvironment,
|
||||
getMockSubjectInfo,
|
||||
getPluginInfo,
|
||||
type MockSetSelectProps,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
|
||||
import { getUsedScene, isCurrent } from '../util/index';
|
||||
import { MockTrafficEnabled } from '../util/get-mock-set-options';
|
||||
import { type EnabledMockSetInfo } from '../component/hooks/store';
|
||||
import { CONNECTOR_ID, REAL_DATA_MOCKSET } from '../component/const';
|
||||
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const useMockSetInSettingModalController = ({
|
||||
bindSubjectInfo: mockSubjectInfo,
|
||||
bizCtx: bizSceneCtx,
|
||||
readonly = false,
|
||||
}: MockSetSelectProps) => {
|
||||
const [isEnabled, setIsEnabled] = useState(!!0);
|
||||
const [isInit, setIsInit] = useState(!0);
|
||||
|
||||
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 { jump } = usePageJumpService();
|
||||
|
||||
const bizCtx: BizCtx = useMemo(
|
||||
() => ({
|
||||
...bizSceneCtx,
|
||||
connectorUID: uid,
|
||||
connectorID: CONNECTOR_ID, // 业务线为Coze
|
||||
}),
|
||||
[bizSceneCtx, uid, CONNECTOR_ID],
|
||||
);
|
||||
|
||||
const [selectedMockSet, setSelectedMockSet] =
|
||||
useState<MockSet>(REAL_DATA_MOCKSET);
|
||||
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
|
||||
const preSelectionRef = useRef<MockSet>(REAL_DATA_MOCKSET);
|
||||
|
||||
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);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'change_mockset_fail' });
|
||||
isBinding &&
|
||||
sendTeaEvent(EVENT_NAMES.use_mockset_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error: e?.msg as string,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
const doChangeMock = (obj?: MockSet) => {
|
||||
if (obj) {
|
||||
preSelectionRef.current = selectedMockSet;
|
||||
setSelectedMockSet(obj);
|
||||
changeMockSet(obj);
|
||||
}
|
||||
};
|
||||
|
||||
const doEnabled = useMemoizedFn(() => {
|
||||
if (isEnabled) {
|
||||
doChangeMock(REAL_DATA_MOCKSET);
|
||||
}
|
||||
|
||||
setIsEnabled(!isEnabled);
|
||||
});
|
||||
|
||||
const initialInfo = useMemo(
|
||||
() => ({
|
||||
bizCtx,
|
||||
bindSubjectInfo,
|
||||
name: subjectDetail?.name,
|
||||
}),
|
||||
[bizCtx, bindSubjectInfo, subjectDetail],
|
||||
);
|
||||
|
||||
const [deleteTargetId, setDeleteTargetId] = useState(undefined);
|
||||
|
||||
const { data: deleteUsingCountInfo } = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
const { usersUsageCount } = await debuggerApi.GetMockSetUsageInfo({
|
||||
mockSetID: deleteTargetId,
|
||||
spaceID,
|
||||
});
|
||||
return Number(usersUsageCount ?? 0);
|
||||
} catch (e) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'fetch_mockset_ref_fail' });
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [deleteTargetId],
|
||||
ready: deleteTargetId !== undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const deleteRenderTitle =
|
||||
(deleteUsingCountInfo ?? 0) > 0
|
||||
? I18n.t('people_using_mockset_delete', { num: deleteUsingCountInfo })
|
||||
: I18n.t('delete_the_mockset');
|
||||
|
||||
const {
|
||||
data: mockSetData,
|
||||
loading: isListLoading,
|
||||
refresh,
|
||||
} = useRequest(
|
||||
async d => {
|
||||
try {
|
||||
const res = await debuggerApi.MGetMockSet({
|
||||
bizCtx,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
mockSubject: getMockSubjectInfo(bizCtx, mockSubjectInfo),
|
||||
pageToken: d?.pageToken,
|
||||
orderBy: infra.OrderBy.UpdateTime,
|
||||
desc: true,
|
||||
});
|
||||
|
||||
setIsInit(!!0);
|
||||
|
||||
return res?.mockSets ?? [];
|
||||
} catch (e) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'mockset_list_fetch_fail' });
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{ ready: !readonly },
|
||||
);
|
||||
|
||||
const { data: enabledMockSetInfo, loading: isSettingLoading } = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
const { mockSetBindings = [], mockSetDetails = {} } =
|
||||
await debuggerApi.MGetMockSetBinding(
|
||||
{
|
||||
bizCtx,
|
||||
needMockSetDetail: true,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'rpc-persist-mock-traffic-enable': MockTrafficEnabled.ENABLE,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return combineBindMockSetInfo(mockSetBindings, mockSetDetails);
|
||||
} catch (e) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error: e, eventName: 'poll_scene_mockset_fail' });
|
||||
}
|
||||
},
|
||||
{ ready: !readonly, refreshDeps: [bizCtx] },
|
||||
);
|
||||
|
||||
const doConfirmDelete = useMemoizedFn(async () => {
|
||||
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(deleteTargetId) || '',
|
||||
status: 1,
|
||||
};
|
||||
try {
|
||||
deleteTargetId &&
|
||||
(await debuggerApi.DeleteMockSet({
|
||||
id: deleteTargetId,
|
||||
bizCtx,
|
||||
}));
|
||||
|
||||
refresh();
|
||||
|
||||
setDeleteTargetId(undefined);
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.del_mockset_front, {
|
||||
...basicParams,
|
||||
status: 0,
|
||||
});
|
||||
} catch (e) {
|
||||
sendTeaEvent(EVENT_NAMES.del_mockset_front, {
|
||||
...basicParams,
|
||||
status: 1,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
error: e?.message as string,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabledMockSetInfo?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
setIsEnabled(!0);
|
||||
} else {
|
||||
setSelectedMockSet(REAL_DATA_MOCKSET);
|
||||
}
|
||||
}, [enabledMockSetInfo]);
|
||||
|
||||
const doHandleView = useMemoizedFn(
|
||||
(
|
||||
record?: MockSet,
|
||||
autoGenerateConfig?: { generateMode: PluginMockDataGenerateMode },
|
||||
) => {
|
||||
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,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
// actions
|
||||
doSetCreateModal: setShowCreateModal,
|
||||
doHandleView,
|
||||
doEnabled,
|
||||
doSetDeleteId: setDeleteTargetId,
|
||||
deleteRenderTitle,
|
||||
doConfirmDelete,
|
||||
doChangeMock,
|
||||
// data-source
|
||||
selectedMockSet,
|
||||
mockSetData,
|
||||
initialInfo,
|
||||
// status
|
||||
isListLoading: isListLoading && isInit,
|
||||
isSettingLoading,
|
||||
isEnabled,
|
||||
showCreateModal,
|
||||
};
|
||||
};
|
||||
|
||||
export { useMockSetInSettingModalController };
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 { useRequest } from 'ahooks';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
type PluginMockSetCommonParams,
|
||||
sendTeaEvent,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { Toast } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type MockRule,
|
||||
ResponseExpectType,
|
||||
type BizCtx,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { debuggerApi } from '@coze-arch/bot-api';
|
||||
enum ResType {
|
||||
SUCCESS = 'success',
|
||||
FAIL = 'fail',
|
||||
}
|
||||
|
||||
interface SuccessResType extends MockRule {
|
||||
status: ResType;
|
||||
}
|
||||
|
||||
interface FailResType {
|
||||
status: ResType;
|
||||
error: Error;
|
||||
}
|
||||
|
||||
export function useSaveMockData({
|
||||
mockSetId,
|
||||
basicParams,
|
||||
bizCtx,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
mockSetId?: string;
|
||||
basicParams: PluginMockSetCommonParams;
|
||||
bizCtx: BizCtx;
|
||||
onSuccess?: (rules: MockRule[]) => void;
|
||||
onError?: () => void;
|
||||
}) {
|
||||
const { runAsync: save, loading } = useRequest(
|
||||
async (values: string[], mockDataId?: string) => {
|
||||
const promises: Promise<SuccessResType | FailResType>[] = values.map(
|
||||
async v => {
|
||||
const rule = {
|
||||
id: mockDataId,
|
||||
mocksetID: mockSetId,
|
||||
responseExpect: {
|
||||
responseExpectType: ResponseExpectType.JSON,
|
||||
responseExpectRule: v,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { id } = await debuggerApi.SaveMockRule(
|
||||
{
|
||||
bizCtx,
|
||||
...rule,
|
||||
},
|
||||
{ __disableErrorToast: true },
|
||||
);
|
||||
|
||||
return {
|
||||
status: ResType.SUCCESS,
|
||||
...rule,
|
||||
id: id || mockDataId,
|
||||
};
|
||||
} catch (error) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
logger.error({ error, eventName: 'save_mock_info_fail' });
|
||||
|
||||
return {
|
||||
status: ResType.FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const res = await Promise.all(promises);
|
||||
|
||||
const successRes = res.filter(
|
||||
item => item.status === 'success',
|
||||
) as SuccessResType[];
|
||||
const failRes = res.filter(
|
||||
item => item.status !== 'success',
|
||||
) as FailResType[];
|
||||
|
||||
if (successRes.length) {
|
||||
sendTeaEvent(EVENT_NAMES.create_mock_front, {
|
||||
...basicParams,
|
||||
mock_counts: successRes.length,
|
||||
status: 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (failRes.length) {
|
||||
sendTeaEvent(EVENT_NAMES.create_mock_front, {
|
||||
...basicParams,
|
||||
mock_counts: failRes.length,
|
||||
status: 1,
|
||||
error: failRes[0].error?.message,
|
||||
});
|
||||
}
|
||||
|
||||
if (successRes.length === 0) {
|
||||
// 仅全部失败时认为失败,此时需要 toast 提示
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(
|
||||
failRes[0]?.error?.message || I18n.t('error'),
|
||||
),
|
||||
showClose: false,
|
||||
});
|
||||
onError?.();
|
||||
} else {
|
||||
onSuccess?.(successRes);
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
return { save, loading };
|
||||
}
|
||||
@@ -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 { useCallback, useMemo } from 'react';
|
||||
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
|
||||
import {
|
||||
ROOT_KEY,
|
||||
parseToolSchema,
|
||||
stringifyEditorContent,
|
||||
transDataWithStatus2Object,
|
||||
transSchema2DataWithStatus,
|
||||
MockDataValueType,
|
||||
type MockDataWithStatus,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
|
||||
import { getMergedDataWithStatus } from '../util/utils';
|
||||
|
||||
/** 缓存最新一个解析结果 */
|
||||
const cache: {
|
||||
schema: string;
|
||||
result: MockDataWithStatus | undefined;
|
||||
} = {
|
||||
schema: '',
|
||||
result: undefined,
|
||||
};
|
||||
|
||||
export const PRE_DEFINED_NO_EMPTY_KEY = 'response_for_model';
|
||||
|
||||
function useGetCachedSchemaData(schema?: string) {
|
||||
const result = useMemo(() => {
|
||||
if (schema && cache.schema === schema) {
|
||||
return cache.result;
|
||||
}
|
||||
|
||||
if (schema) {
|
||||
cache.schema = schema;
|
||||
const parsedSchema = parseToolSchema(schema);
|
||||
const transData = transSchema2DataWithStatus(ROOT_KEY, parsedSchema);
|
||||
cache.result = transData;
|
||||
return transData;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [schema]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function useTransSchema(schema?: string, currentMock?: string) {
|
||||
const result = useGetCachedSchemaData(schema);
|
||||
|
||||
const {
|
||||
result: mergedResultExample,
|
||||
merged: mergedResult,
|
||||
incompatible,
|
||||
formatted: formattedResultExample,
|
||||
} = useMemo(() => {
|
||||
const { result: merged, incompatible: mergedIncompatible } =
|
||||
getMergedDataWithStatus(result, currentMock);
|
||||
|
||||
if (merged) {
|
||||
const resultObj = transDataWithStatus2Object(
|
||||
merged,
|
||||
currentMock !== undefined,
|
||||
)?.[ROOT_KEY];
|
||||
|
||||
return {
|
||||
merged,
|
||||
result: resultObj,
|
||||
formatted: stringifyEditorContent(resultObj),
|
||||
incompatible: mergedIncompatible,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
incompatible: mergedIncompatible,
|
||||
};
|
||||
}
|
||||
}, [result, currentMock]);
|
||||
|
||||
// 特殊字段需要单独处理:response_for_model 不能为空字符串
|
||||
const testValueValid = useCallback(
|
||||
(value: string) => {
|
||||
if (
|
||||
result?.children?.some(
|
||||
item =>
|
||||
item.label === PRE_DEFINED_NO_EMPTY_KEY &&
|
||||
item.type === MockDataValueType.STRING,
|
||||
)
|
||||
) {
|
||||
const parsedValue = safeJSONParse(value);
|
||||
if (
|
||||
typeof parsedValue === 'object' &&
|
||||
(typeof parsedValue[PRE_DEFINED_NO_EMPTY_KEY] !== 'string' ||
|
||||
parsedValue[PRE_DEFINED_NO_EMPTY_KEY].length === 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[result],
|
||||
);
|
||||
|
||||
return {
|
||||
// 由 schema 生成的结构话数据,值为初始值
|
||||
result,
|
||||
// 合并模拟数据的结构话数据,全集
|
||||
mergedResult,
|
||||
// mergedResult 转换的 object
|
||||
mergedResultExample,
|
||||
// 格式化的数据
|
||||
formattedResultExample,
|
||||
// 是否兼容
|
||||
incompatible,
|
||||
// 是否合并模拟数据
|
||||
isInit: currentMock === undefined,
|
||||
testValueValid,
|
||||
};
|
||||
}
|
||||
@@ -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 { DemoComponent } from './demo';
|
||||
17
frontend/packages/agent-ide/bot-plugin/mock-set/src/typings.d.ts
vendored
Normal file
17
frontend/packages/agent-ide/bot-plugin/mock-set/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,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.
|
||||
*/
|
||||
|
||||
import { type BotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { getFlags } from '@coze-arch/bot-flags';
|
||||
import { BotMode } from '@coze-arch/bot-api/developer_api';
|
||||
import { TrafficScene } from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
export enum MockTrafficEnabled {
|
||||
DISABLE = 0,
|
||||
ENABLE = 1,
|
||||
}
|
||||
|
||||
export function getMockSetReqOptions(baseBotInfo: BotInfoStore) {
|
||||
const FLAGS = getFlags();
|
||||
|
||||
return FLAGS['bot.devops.plugin_mockset']
|
||||
? {
|
||||
headers: {
|
||||
'rpc-persist-mock-traffic-scene':
|
||||
baseBotInfo.mode === BotMode.MultiMode
|
||||
? TrafficScene.CozeMultiAgentDebug
|
||||
: TrafficScene.CozeSingleAgentDebug,
|
||||
'rpc-persist-mock-traffic-caller-id': baseBotInfo.botId,
|
||||
'rpc-persist-mock-space-id': baseBotInfo?.space_id,
|
||||
'rpc-persist-mock-traffic-enable': MockTrafficEnabled.ENABLE,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { safeJSONParse } from '@coze-arch/bot-utils';
|
||||
import {
|
||||
type BizCtx,
|
||||
TrafficScene,
|
||||
type MockSet,
|
||||
} from '@coze-arch/bot-api/debugger_api';
|
||||
import { type BasicMockSetInfo } from '@coze-studio/mockset-shared';
|
||||
|
||||
import { REAL_DATA_MOCKSET } from '../component/const';
|
||||
export { safeJSONParse } from './utils';
|
||||
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 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';
|
||||
}
|
||||
}
|
||||
@@ -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 { type MockRule } from '@coze-arch/bot-api/debugger_api';
|
||||
|
||||
export enum MockDataValueType {
|
||||
STRING = 'string',
|
||||
INTEGER = 'integer',
|
||||
NUMBER = 'number',
|
||||
OBJECT = 'object',
|
||||
ARRAY = 'array',
|
||||
BOOLEAN = 'boolean',
|
||||
}
|
||||
|
||||
export enum MockDataStatus {
|
||||
DEFAULT = 'default',
|
||||
REMOVED = 'removed',
|
||||
ADDED = 'added',
|
||||
}
|
||||
|
||||
export interface MockDataWithStatus {
|
||||
/** key */
|
||||
key: string;
|
||||
/** 字段名称 */
|
||||
label: string;
|
||||
/** 字段值 */
|
||||
realValue?: string | number | boolean;
|
||||
/** 展示使用 */
|
||||
displayValue?: string;
|
||||
/** 描述 */
|
||||
description?: string;
|
||||
/** 是否必填 */
|
||||
isRequired: boolean;
|
||||
/** 字段数据类型 */
|
||||
type: MockDataValueType;
|
||||
/** for array */
|
||||
childrenType?: MockDataValueType;
|
||||
/** 字段状态 */
|
||||
status: MockDataStatus;
|
||||
/** 字段子节点 */
|
||||
children?: MockDataWithStatus[];
|
||||
}
|
||||
|
||||
export interface MockDataInfo {
|
||||
schema?: string;
|
||||
mock?: MockRule;
|
||||
mergedResultExample?: string;
|
||||
incompatible?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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 {
|
||||
getArrayItemKey,
|
||||
getMockValue,
|
||||
ROOT_KEY,
|
||||
} from '@coze-studio/mockset-shared';
|
||||
|
||||
import {
|
||||
MockDataStatus,
|
||||
MockDataValueType,
|
||||
type MockDataWithStatus,
|
||||
} from './typings';
|
||||
export function transUpperCase(str?: string) {
|
||||
return str ? `${str.slice(0, 1).toUpperCase()}${str.slice(1)}` : '';
|
||||
}
|
||||
|
||||
// 仅开发中使用
|
||||
export function sleep(t = 1000) {
|
||||
return new Promise(r => {
|
||||
setTimeout(() => {
|
||||
r(1);
|
||||
}, t);
|
||||
});
|
||||
}
|
||||
|
||||
export function safeJSONParse<T>(str: string, errCb?: () => T | undefined) {
|
||||
try {
|
||||
return JSON.parse(str) as T;
|
||||
} catch (error) {
|
||||
return errCb?.();
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 value 生成 displayValue
|
||||
function getTargetValue(
|
||||
type: MockDataValueType,
|
||||
value: string | number | boolean | undefined,
|
||||
): [string | number | boolean | undefined, string | undefined] {
|
||||
return getMockValue(type, {
|
||||
getBooleanValue: () => Boolean(value),
|
||||
getNumberValue: () => Number(value),
|
||||
getStringValue: () => String(value),
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- plugin resp 的类型由用户定义,包含任何可能
|
||||
type PluginRespType = any;
|
||||
|
||||
// 合并 schema 和 mock data
|
||||
export function getMergedDataWithStatus(
|
||||
schemaData?: MockDataWithStatus,
|
||||
currentMock?: string,
|
||||
): {
|
||||
result?: MockDataWithStatus;
|
||||
incompatible: boolean;
|
||||
} {
|
||||
const isInit = currentMock === undefined;
|
||||
|
||||
if (!schemaData || isInit) {
|
||||
return {
|
||||
result: schemaData,
|
||||
incompatible: false,
|
||||
};
|
||||
}
|
||||
|
||||
// parse mock string
|
||||
const mock =
|
||||
typeof currentMock === 'string'
|
||||
? safeJSONParse<PluginRespType>(currentMock) || currentMock
|
||||
: currentMock;
|
||||
|
||||
// 将 mock 转换为 MockDataWithStatus 格式
|
||||
const processedMock = transMockData2DataWithStatus(ROOT_KEY, mock, {
|
||||
defaultStatus: MockDataStatus.REMOVED,
|
||||
});
|
||||
|
||||
// 合并
|
||||
const { merged, incompatible } = mergeDataWithStatus(
|
||||
schemaData.children,
|
||||
processedMock?.children,
|
||||
schemaData.type === MockDataValueType.ARRAY,
|
||||
);
|
||||
|
||||
return {
|
||||
result: {
|
||||
...schemaData,
|
||||
children: merged,
|
||||
},
|
||||
incompatible,
|
||||
};
|
||||
}
|
||||
|
||||
// 解析当前 realValue 所属于的 MockDataValueType
|
||||
function getMockDataType(currentMock: PluginRespType) {
|
||||
let dataTypeName = typeof currentMock as MockDataValueType | undefined;
|
||||
if (currentMock instanceof Array) {
|
||||
dataTypeName = MockDataValueType.ARRAY;
|
||||
}
|
||||
return dataTypeName;
|
||||
}
|
||||
|
||||
function compareMockDataType(
|
||||
mockDataType?: MockDataValueType,
|
||||
initDataType?: MockDataValueType,
|
||||
) {
|
||||
// mock data 的类型是根据值识别出的,存在 Integer 类型被识别为 Number 的情况
|
||||
if (mockDataType === MockDataValueType.NUMBER) {
|
||||
return (
|
||||
initDataType === MockDataValueType.NUMBER ||
|
||||
initDataType === MockDataValueType.INTEGER
|
||||
);
|
||||
} else {
|
||||
return mockDataType === initDataType;
|
||||
}
|
||||
}
|
||||
|
||||
// 转换 Object 格式到 DataWithStatus 格式
|
||||
export function transMockData2DataWithStatus(
|
||||
label: string,
|
||||
currentMock: PluginRespType,
|
||||
params?: {
|
||||
defaultStatus: MockDataStatus;
|
||||
keyPrefix?: string;
|
||||
},
|
||||
): MockDataWithStatus | undefined {
|
||||
const { defaultStatus = MockDataStatus.DEFAULT } = params || {};
|
||||
const dataTypeName = getMockDataType(currentMock);
|
||||
|
||||
if (!dataTypeName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [realValue, displayValue] = getTargetValue(dataTypeName, currentMock);
|
||||
|
||||
const itemKey = params?.keyPrefix ? `${params?.keyPrefix}-${label}` : label;
|
||||
|
||||
const item: MockDataWithStatus = {
|
||||
label,
|
||||
realValue,
|
||||
displayValue,
|
||||
isRequired: false,
|
||||
type: dataTypeName,
|
||||
status: defaultStatus,
|
||||
key: itemKey,
|
||||
};
|
||||
|
||||
if (dataTypeName === MockDataValueType.OBJECT) {
|
||||
const children: MockDataWithStatus[] = [];
|
||||
for (const property of Object.keys(currentMock)) {
|
||||
if (property) {
|
||||
const child = transMockData2DataWithStatus(
|
||||
property,
|
||||
currentMock[property],
|
||||
{
|
||||
defaultStatus,
|
||||
keyPrefix: itemKey,
|
||||
},
|
||||
);
|
||||
child && children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
item.children = children;
|
||||
}
|
||||
|
||||
if (dataTypeName === MockDataValueType.ARRAY) {
|
||||
item.childrenType = getMockDataType(currentMock[0]);
|
||||
const children: MockDataWithStatus[] = [];
|
||||
|
||||
for (const index in currentMock) {
|
||||
if (currentMock[index] !== undefined) {
|
||||
const child = transMockData2DataWithStatus(
|
||||
getArrayItemKey(index),
|
||||
currentMock[index],
|
||||
{
|
||||
defaultStatus,
|
||||
keyPrefix: itemKey,
|
||||
},
|
||||
);
|
||||
child && children.push(child);
|
||||
}
|
||||
}
|
||||
item.children = children;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function mergeDataItems(item1: MockDataWithStatus, item2: MockDataWithStatus) {
|
||||
let incompatible = false;
|
||||
const newItem = {
|
||||
...item1,
|
||||
key: item2.key,
|
||||
label: item2.label,
|
||||
realValue: item2.realValue,
|
||||
displayValue: item2.displayValue,
|
||||
status: MockDataStatus.DEFAULT,
|
||||
};
|
||||
if (
|
||||
item2.type === MockDataValueType.ARRAY ||
|
||||
item2.type === MockDataValueType.OBJECT
|
||||
) {
|
||||
const { merged, incompatible: childIncompatible } = mergeDataWithStatus(
|
||||
item1.children,
|
||||
item2.children,
|
||||
item1.type === MockDataValueType.ARRAY,
|
||||
);
|
||||
newItem.children = merged;
|
||||
incompatible = incompatible || childIncompatible;
|
||||
}
|
||||
return {
|
||||
result: newItem,
|
||||
incompatible,
|
||||
};
|
||||
}
|
||||
|
||||
function merge2DataList(
|
||||
autoInitDataList: MockDataWithStatus[],
|
||||
mockDataListWithStatus: MockDataWithStatus[],
|
||||
isArrayType = false,
|
||||
): {
|
||||
merged: MockDataWithStatus[];
|
||||
incompatible: boolean;
|
||||
} {
|
||||
let incompatible = false;
|
||||
let appendData: MockDataWithStatus[] = [...mockDataListWithStatus];
|
||||
const originData: MockDataWithStatus[] = [...autoInitDataList];
|
||||
|
||||
for (const i in originData) {
|
||||
if (originData[i]) {
|
||||
const item = originData[i];
|
||||
const index = appendData.findIndex(
|
||||
data =>
|
||||
data.label === item.label &&
|
||||
compareMockDataType(data.type, item.type) &&
|
||||
compareMockDataType(data.childrenType, item.childrenType),
|
||||
);
|
||||
if (index !== -1) {
|
||||
const data = appendData.splice(index, 1);
|
||||
const { result: newItem, incompatible: childIncompatible } =
|
||||
mergeDataItems(item, data[0]);
|
||||
originData[i] = newItem;
|
||||
incompatible = incompatible || childIncompatible;
|
||||
} else if (item.isRequired) {
|
||||
incompatible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在合并 array 时,会出现 autoInitDataList(originData) 中只存在一个初始化数据,而 mockDataListWithStatus(appendData) 存在多个相同结构的数据
|
||||
// 需要将 mockDataListWithStatus 中剩余的数据进行合入
|
||||
if (appendData.length && isArrayType) {
|
||||
const target = autoInitDataList[0];
|
||||
appendData.forEach(item => {
|
||||
const { result: newItem, incompatible: childIncompatible } =
|
||||
mergeDataItems(target, item);
|
||||
originData.push(newItem);
|
||||
incompatible = incompatible || childIncompatible;
|
||||
});
|
||||
appendData = [];
|
||||
}
|
||||
|
||||
if (appendData.length) {
|
||||
incompatible = true;
|
||||
}
|
||||
|
||||
return {
|
||||
merged: [...originData, ...appendData],
|
||||
incompatible,
|
||||
};
|
||||
}
|
||||
|
||||
// 合并两个 MockDataWithStatus 数组,以 autoInitDataList 顺序优先
|
||||
export function mergeDataWithStatus(
|
||||
autoInitDataList?: MockDataWithStatus[],
|
||||
mockDataListWithStatus?: MockDataWithStatus[],
|
||||
isArrayType = false,
|
||||
): {
|
||||
merged: MockDataWithStatus[];
|
||||
incompatible: boolean;
|
||||
} {
|
||||
if (autoInitDataList === undefined || mockDataListWithStatus === undefined) {
|
||||
return {
|
||||
merged: [...(autoInitDataList || []), ...(mockDataListWithStatus || [])],
|
||||
incompatible: autoInitDataList !== mockDataListWithStatus,
|
||||
};
|
||||
}
|
||||
|
||||
// 在合并 array 时,存在 mockDataListWithStatus 为空的情况,此时直接判断
|
||||
if (mockDataListWithStatus.length === 0 && isArrayType) {
|
||||
return {
|
||||
merged: [],
|
||||
incompatible: false,
|
||||
};
|
||||
}
|
||||
|
||||
return merge2DataList(autoInitDataList, mockDataListWithStatus, isArrayType);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"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-flags/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-hooks/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/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/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/mockset-edit-modal-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/mockset-editor-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/mockset-editor/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/mockset-shared/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-detail/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/user-store/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
all: true,
|
||||
include: ['src'],
|
||||
exclude: [
|
||||
'src/index.tsx',
|
||||
'src/hook/table/**',
|
||||
'src/global.d.ts',
|
||||
'src/typings.d.ts',
|
||||
'src/component/**',
|
||||
'src/page/**',
|
||||
'src/demo/**',
|
||||
'src/hook/index.ts',
|
||||
'src/util/index.ts',
|
||||
'src/util/editor.ts',
|
||||
'src/hook/example/**',
|
||||
],
|
||||
},
|
||||
setupFiles: ['./__tests__/setup.ts'],
|
||||
},
|
||||
},
|
||||
{
|
||||
fixSemi: true,
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user