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