feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/*
* 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 enum ReportEventNames {
EmptySpaceList = 'empty_space_List', // 空间列表为空
PollingSpaceList = 'polling_space_list', // 轮训空间列表
}

View File

@@ -0,0 +1,228 @@
/*
* 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 { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { CustomError } from '@coze-arch/bot-error';
import {
type SaveSpaceRet,
type SaveSpaceV2Request,
type TransferSpaceV2Request,
type ExitSpaceV2Request,
type SpaceInfo,
} from '@coze-arch/bot-api/playground_api';
import { type BotSpace, SpaceType } from '@coze-arch/bot-api/developer_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { polling, reportSpaceListPollingRes } from './utils';
interface SpaceStoreState {
/** @deprecated try useSpace instead */
space: BotSpace;
spaceList: BotSpace[];
recentlyUsedSpaceList: BotSpace[];
loading: false | Promise<SpaceInfo | undefined>;
inited?: boolean;
createdTeamSpaceNum: number; // 个人创建的团队空间计数
maxTeamSpaceNum: number;
/** @deprecated 使用 spaceList & maxTeamSpaceNum */
spaces: {
bot_space_list: BotSpace[];
has_personal_space: boolean;
team_space_num: number;
max_team_space_num: number;
};
}
interface SpaceStoreAction {
reset: () => void;
/** @deprecated get id from url */
getSpaceId: () => string;
getPersonalSpaceID: () => string | undefined;
checkSpaceID: (spaceID: string) => boolean;
/** @deprecated 通过 id 索引 */
setSpace: (spaceId?: string, isBotDetailIframe?: boolean) => void | never;
createSpace: (
request: SaveSpaceV2Request,
) => Promise<SaveSpaceRet | undefined>;
exitSpace: (request: ExitSpaceV2Request) => Promise<string | undefined>;
deleteSpace: (id: string) => Promise<string | undefined>;
updateSpace: (request: SaveSpaceV2Request) => Promise<{
id?: string;
check_not_pass?: boolean;
}>;
transferSpace: (
request: TransferSpaceV2Request,
) => Promise<string | undefined>;
fetchSpaces: (force?: boolean) => Promise<SpaceInfo | undefined>;
}
const DEFAULT_MAXIMUM_SPACE = 3;
export const defaultState: SpaceStoreState = {
space: {},
spaceList: [],
recentlyUsedSpaceList: [],
loading: false,
maxTeamSpaceNum: DEFAULT_MAXIMUM_SPACE,
createdTeamSpaceNum: 0,
inited: false,
spaces: {
bot_space_list: [],
has_personal_space: true,
team_space_num: 0,
max_team_space_num: DEFAULT_MAXIMUM_SPACE,
},
};
export const useSpaceStore = create<SpaceStoreState & SpaceStoreAction>()(
devtools(
(set, get) => ({
...defaultState,
reset: () => {
set(defaultState, false, 'reset');
},
getSpaceId: () => {
const { id } = get().space;
if (!id) {
throw new CustomError(
REPORT_EVENTS.parmasValidation,
'lack space_id',
);
}
return id;
},
getPersonalSpaceID: () =>
get().spaces.bot_space_list?.find(
space => space.space_type === SpaceType.Personal,
)?.id,
checkSpaceID: spaceID =>
!!get().spaces.bot_space_list?.find(space => space.id === spaceID),
setSpace: id => {
const { space, spaces } = get();
if (id) {
const targetSapce = spaces.bot_space_list.find(s => s.id === id);
if (targetSapce) {
set({ space: targetSapce }, false, 'setSpace');
} else {
throw Error(`can not find space: ${id}`);
}
} else {
set(
{
space: {
...space,
id: '',
},
},
false,
'setSpace',
);
}
},
createSpace: async payload => {
const res = await PlaygroundApi.SaveSpaceV2(payload);
if (res.code === 0) {
return res.data;
} else {
throw Error(`create error: ${res.msg}`);
}
},
exitSpace: _ => Promise.resolve(undefined),
deleteSpace: _ => Promise.resolve(undefined),
updateSpace: _ => Promise.resolve({}),
transferSpace: () => Promise.resolve(undefined),
// eslint-disable-next-line complexity
fetchSpaces: async (force?: boolean) => {
const request = async () => {
const { data } = await PlaygroundApi.GetSpaceListV2({});
return data;
};
const prePromise = get().loading;
const currentPromise = force ? request() : prePromise || request();
if (currentPromise !== prePromise) {
set(
{
loading: currentPromise,
},
false,
'fetchSpaces',
);
} else {
return prePromise;
}
let res = await currentPromise;
if (!res?.has_personal_space) {
await get().createSpace({
name: 'Personal',
description: 'Personal Space',
icon_uri: '',
space_type: SpaceType.Personal,
});
const pollingRes = await polling({
request,
isValid: data => (data?.bot_space_list?.length ?? 0) > 0,
});
reportSpaceListPollingRes(pollingRes);
res = pollingRes.data;
}
const spaceInfo: SpaceStoreState['spaces'] = {
bot_space_list: res?.bot_space_list ?? [],
has_personal_space: res?.has_personal_space ?? true,
team_space_num: res?.team_space_num ?? 0,
max_team_space_num: res?.max_team_space_num ?? DEFAULT_MAXIMUM_SPACE,
};
set(
{
spaceList: spaceInfo.bot_space_list,
recentlyUsedSpaceList: res?.recently_used_space_list ?? [],
createdTeamSpaceNum: spaceInfo.team_space_num,
maxTeamSpaceNum: spaceInfo.max_team_space_num,
loading: false,
inited: true,
spaces: spaceInfo,
},
false,
'fetchSpaces',
);
return res;
},
}),
{
enabled: IS_DEV_MODE,
name: 'botStudio.spaceStore',
},
),
);

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reporter } from '@coze-arch/logger';
import { CustomError } from '@coze-arch/bot-error';
import { ReportEventNames } from './const';
const MAX_RETRY = 4;
const INTERVAL = 800;
interface PollingResponse<T = unknown> {
data: T;
isSuccess: boolean;
tryCount: number;
}
export const polling = <T>({
request,
isValid,
maxRetry = MAX_RETRY,
interval = INTERVAL,
}: {
request: () => Promise<T>;
isValid: (data: T) => boolean;
maxRetry?: number;
interval?: number;
}): Promise<PollingResponse<T>> => {
let tryCount = 0;
return new Promise(resolve => {
const go = async () => {
const data = await request();
if (!isValid(data)) {
if (++tryCount < maxRetry) {
setTimeout(go, interval);
} else {
resolve({
data,
isSuccess: false,
tryCount,
});
}
} else {
resolve({
data,
isSuccess: true,
tryCount,
});
}
};
go();
});
};
export const reportSpaceListPollingRes = ({
isSuccess,
tryCount,
}: PollingResponse) => {
reporter.errorEvent(
isSuccess
? {
eventName: ReportEventNames.PollingSpaceList,
error: new CustomError(
ReportEventNames.PollingSpaceList,
tryCount.toString(),
),
}
: {
eventName: ReportEventNames.EmptySpaceList,
error: new CustomError(
ReportEventNames.EmptySpaceList,
'space list is empty',
),
},
);
};