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,45 @@
/*
* 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 { PatBody } from '@coze-studio/open-auth';
import {
useAccountSettings as useBaseAccountSettings,
UserInfoPanel,
} from '@coze-foundation/account-ui-base';
import { I18n } from '@coze-arch/i18n';
export const useAccountSettings = () => {
const tabs = [
{
id: 'account',
tabName: I18n.t('menu_profile_account'),
content: () => <UserInfoPanel />,
},
{
id: 'api-auth',
tabName: I18n.t('settings_api_authorization'),
content: () => <PatBody size="small" type="primary" />,
},
];
const { node, open } = useBaseAccountSettings({
tabs,
});
return {
node,
open,
};
};

View File

@@ -0,0 +1,79 @@
/*
* 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 { GlobalLayoutAccountDropdown } from '@coze-foundation/layout';
import { useLogout } from '@coze-foundation/account-ui-adapter';
import { I18n } from '@coze-arch/i18n';
import { useUserInfo } from '@coze-arch/foundation-sdk';
import { IconCozExit, IconCozSetting } from '@coze-arch/coze-design/icons';
import { Dropdown } from '@coze-arch/coze-design';
import { UserInfoMenu } from './user-info-menu';
import { useAccountSettings } from './account-settings';
export const AccountDropdown = () => {
const [visible, setVisible] = useState(false);
const userInfo = useUserInfo();
const { node: logoutModal, open: openLogoutModal } = useLogout();
const { node: accountSettingsNode, open: openAccountSettings } =
useAccountSettings();
if (!userInfo) {
return null;
}
return (
<GlobalLayoutAccountDropdown
menus={[
<UserInfoMenu />,
<Dropdown.Divider />,
{
prefixIcon: <IconCozExit />,
title: I18n.t('settings_api_authorization'),
onClick: () => {
openAccountSettings('api-auth');
},
dataTestId: 'layout_avatar_api-auth',
},
{
prefixIcon: <IconCozSetting />,
title: I18n.t('navi_bar_account_settings'),
onClick: () => {
openAccountSettings('account');
},
dataTestId: 'layout_avatar_profile-settings',
},
<Dropdown.Divider />,
{
prefixIcon: <IconCozExit />,
title: I18n.t('basic_log_out'),
onClick: () => {
openLogoutModal();
},
dataTestId: 'layout_avatar_logout-button',
},
]}
visible={visible}
onVisibleChange={setVisible}
>
{logoutModal}
{accountSettingsNode}
</GlobalLayoutAccountDropdown>
);
};

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useUserInfo, useUserLabel } from '@coze-arch/foundation-sdk';
import {
CozAvatar,
Badge,
Dropdown,
Space,
Typography,
Tooltip,
} from '@coze-arch/coze-design';
const ellipsis = {
showTooltip: true,
};
export const UserInfoMenu = () => {
const userInfo = useUserInfo();
const userLabel = useUserLabel();
if (!userInfo) {
return null;
}
const userUniqueName = userInfo?.app_user_info?.user_unique_name;
return (
<Dropdown.Item className="!h-fit">
<div className="flex justify-between items-center w-full">
<Space spacing={8} className="shrink grow overflow-hidden">
{
<Badge
position="rightBottom"
countStyle={{
right: 6,
bottom: 6,
}}
count={
userLabel?.icon_url ? (
<Tooltip
showArrow
position="right"
content={userLabel?.label_name}
trigger={userLabel?.label_name ? 'hover' : 'custom'}
>
<div className="bg-white rounded-full w-[16px] h-[16px] flex items-center justify-center">
<CozAvatar
src={userLabel?.icon_url}
className="w-[12px] h-[12px] rounded-full"
type="person"
onClick={event => {
if (userLabel?.jump_link) {
event?.preventDefault();
event?.stopPropagation();
window.open(userLabel?.jump_link, '_blank');
}
}}
/>
</div>
</Tooltip>
) : null
}
className="shrink-0"
>
<CozAvatar
src={userInfo.avatar_url}
className="w-[32px] h-[32px] rounded-full"
type="person"
/>
</Badge>
}
{
<div className="flex-1 text-[14px] leading-[20px] overflow-hidden sp">
<Typography.Text
className="coz-fg-primary font-[500]"
ellipsis={ellipsis}
>
{userInfo.name}
</Typography.Text>
<Typography.Text className="coz-fg-secondary" ellipsis={ellipsis}>
{userUniqueName ? '@' : ''}
{userUniqueName}
</Typography.Text>
</div>
}
</Space>
</div>
</Dropdown.Item>
);
};

View File

@@ -0,0 +1,42 @@
/*
* 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 { useLocation } from 'react-router-dom';
import { useIsLogined } from '@coze-arch/foundation-sdk';
import { useRouteConfig } from '@coze-arch/bot-hooks';
export const useHasSider = () => {
const config = useRouteConfig();
const location = useLocation();
const isLogined = useIsLogined();
const queryParams = new URLSearchParams(location.search);
const pageMode = queryParams.get('page_mode');
// 优先使用 page_mode 参数判断是否为全屏模式
if (config.pageModeByQuery && pageMode === 'modal') {
return false;
}
const notCheckLoginPage =
(config.requireAuth && config.requireAuthOptional) || !config.requireAuth;
// 未登录时也可访问的页面
if (config.hasSider && notCheckLoginPage && !isLogined) {
return false;
}
return !!config.hasSider;
};

View File

@@ -0,0 +1,96 @@
/*
* 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 { useParams } from 'react-router-dom';
import { type FC, type PropsWithChildren } from 'react';
import { GlobalLayout } from '@coze-foundation/layout';
import { useCreateBotAction } from '@coze-foundation/global';
import { RequireAuthContainer } from '@coze-foundation/account-ui-adapter';
import { I18n } from '@coze-arch/i18n';
import { useRouteConfig } from '@coze-arch/bot-hooks';
import {
IconCozPlusCircle,
IconCozWorkspace,
IconCozWorkspaceFill,
IconCozCompass,
IconCozCompassFill,
IconCozDocument,
} from '@coze-arch/coze-design/icons';
import { AccountDropdown } from '../account-dropdown';
import { useHasSider } from './hooks/use-has-sider';
export const GlobalLayoutComposed: FC<PropsWithChildren> = ({ children }) => {
const config = useRouteConfig();
const hasSider = useHasSider();
const { space_id } = useParams();
const { createBot, createBotModal } = useCreateBotAction({
currentSpaceId: space_id,
});
return (
<RequireAuthContainer
needLogin={!!config.requireAuth}
loginOptional={!!config.requireAuthOptional}
>
<GlobalLayout
hasSider={hasSider}
banner={null}
actions={[
{
tooltip: I18n.t('creat_tooltip_create'),
icon: <IconCozPlusCircle />,
onClick: createBot,
dataTestId: 'layout_create-agent-button',
},
]}
menus={[
{
title: I18n.t('navigation_workspace'),
icon: <IconCozWorkspace />,
activeIcon: <IconCozWorkspaceFill />,
path: '/space',
dataTestId: 'layout_workspace-button',
},
{
title: I18n.t('menu_title_store'),
icon: <IconCozCompass />,
activeIcon: <IconCozCompassFill />,
path: '/explore',
dataTestId: 'layout_explore-button',
},
]}
extras={[
{
icon: <IconCozDocument />,
tooltip: I18n.t('menu_documents'),
onClick: () => {
// cp-disable-next-line
window.open('https://www.coze.cn/open/docs/guides');
},
dataTestId: 'layout_document-button',
},
]}
footer={<AccountDropdown />}
>
{children}
{createBotModal}
</GlobalLayout>
</RequireAuthContainer>
);
};

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Outlet } from 'react-router-dom';
import { type FC, useEffect } from 'react';
import { useUpdate } from 'ahooks';
import { BrowserUpgradeWrap } from '@coze-foundation/browser-upgrade-banner';
import { I18nProvider } from '@coze-arch/i18n/i18n-provider';
import { I18n } from '@coze-arch/i18n';
import { useUserInfo } from '@coze-arch/foundation-sdk';
import { zh_CN, en_US } from '@coze-arch/coze-design/locales';
import {
CDLocaleProvider,
ThemeProvider,
enUS,
zhCN,
} from '@coze-arch/coze-design';
import { LocaleProvider } from '@coze-arch/bot-semi';
import { GlobalLayoutComposed } from '@/components/global-layout-composed';
export const GlobalLayout: FC = () => {
const userInfo = useUserInfo();
const update = useUpdate();
const currentLocale = userInfo?.locale ?? navigator.language ?? 'en-US';
// 历史原因en-US 需要被转换为 en
const transformedCurrentLocale =
currentLocale === 'en-US' ? 'en' : currentLocale;
useEffect(() => {
if (userInfo && I18n.language !== transformedCurrentLocale) {
localStorage.setItem('i18next', transformedCurrentLocale);
I18n.setLang(transformedCurrentLocale);
// 强制更新,否则切换语言不生效
update();
}
}, [userInfo, transformedCurrentLocale, update]);
return (
<I18nProvider i18n={I18n}>
<CDLocaleProvider locale={currentLocale === 'en-US' ? en_US : zh_CN}>
<LocaleProvider locale={currentLocale === 'en-US' ? enUS : zhCN}>
<ThemeProvider
defaultTheme="light"
changeSemiTheme={true}
changeBySystem={IS_BOE}
>
<BrowserUpgradeWrap>
<GlobalLayoutComposed>
<Outlet />
</GlobalLayoutComposed>
</BrowserUpgradeWrap>
</ThemeProvider>
</LocaleProvider>
</CDLocaleProvider>
</I18nProvider>
);
};

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import { reporter, logger } from '@coze-arch/logger';
import { useRouteConfig } from '@coze-arch/bot-hooks';
import { useErrorCatch } from '@coze-arch/bot-error';
import slardar from '@coze-studio/default-slardar';
import { useAlertOnLogout } from '@coze-foundation/global/use-app-init';
import {
useSyncLocalStorageUid,
useCheckLogin,
} from '@coze-foundation/account-adapter';
import { useSetResponsiveBodyStyle } from './use-responsive-body-style';
import { useResetStoreOnLogout } from './use-reset-store-on-logout';
import { useInitCommonConfig } from './use-init-common-config';
/**
* 所有初始化的逻辑收敛到这里
* 注意登录态需要自行处理
*/
export const useAppInit = () => {
const { requireAuth, requireAuthOptional, loginFallbackPath } =
useRouteConfig();
useCheckLogin({
needLogin: !!(requireAuth && !requireAuthOptional),
loginFallbackPath,
});
useSyncLocalStorageUid();
useEffect(() => {
reporter.info({ message: 'Ok fine' });
reporter.init(slardar);
logger.init(slardar);
}, []);
useErrorCatch(slardar);
useInitCommonConfig();
useResetStoreOnLogout();
useSetResponsiveBodyStyle();
useAlertOnLogout();
};

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file 社区版暂不支持后台配置,用于未来拓展
*/
import { useEffect } from 'react';
import { useCommonConfigStore } from '@coze-foundation/global-store';
export const useInitCommonConfig = () => {
const setInitialized = useCommonConfigStore(state => state.setInitialized);
useEffect(() => {
setInitialized();
}, []);
};

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import { userStoreService } from '@coze-studio/user-store';
import { useBotListFilterStore } from '@coze-agent-ide/space-bot/store';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
export const useResetStoreOnLogout = () => {
const isSettled = userStoreService.useIsSettled();
const isLogined = userStoreService.useIsLogined();
useEffect(() => {
if (isSettled && !isLogined) {
useSpaceStore.getState().reset();
useBotListFilterStore.getState().reset();
}
}, [isLogined, isSettled]);
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import { setMobileBody, setPCBody } from '@coze-arch/bot-utils';
import { useIsResponsiveByRouteConfig } from '@coze-arch/bot-hooks';
export const useSetResponsiveBodyStyle = () => {
const isResponsive = useIsResponsiveByRouteConfig();
useEffect(() => {
if (isResponsive) {
setMobileBody();
} else {
setPCBody();
}
}, [isResponsive]);
};

View File

@@ -0,0 +1,40 @@
/*
* 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 { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
import { logger } from '@coze-arch/logger';
export enum CustomPerfMarkNames {
RouteChange = 'route_change',
}
export const useTrackRouteChange = () => {
const location = useLocation();
useEffect(() => {
performance.mark(CustomPerfMarkNames.RouteChange, {
detail: {
location,
},
});
logger.info({
message: 'location change',
meta: { location },
});
}, [location.pathname]);
};

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 { GlobalLayout } from './components/global-layout';
export { useAppInit } from './hooks/use-app-init';
export { useHasSider } from './components/global-layout-composed/hooks/use-has-sider';
export { AccountDropdown } from './components/account-dropdown';
export { useAccountSettings } from './components/account-dropdown/account-settings';

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types='@coze-arch/bot-typings' />
declare const IS_OVERSEA: boolean;
declare const IS_BOE: boolean;