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,18 @@
/*
* 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 { TopBar } from './top-bar';
export { SpaceAppList } from './space-app-list';

View File

@@ -0,0 +1,49 @@
.item {
display: flex;
align-items: center;
justify-content: center;
height: 32px;
min-height: 32px;
padding: 6px 14px;
font-size: 14px;
font-weight: 500;
border-radius: 8px;
@apply coz-fg-secondary;
&:hover {
@apply coz-mg-primary coz-fg-primary;
}
}
.active {
@apply coz-mg-primary coz-fg-primary;
}
.item-link {
margin-bottom: 0;
text-decoration: none;
}
.tag-container {
@apply flex items-center gap-4px;
.label {
@apply px-4px text-foreground-2 font-semibold text-lg;
}
.tag {
@apply rounded-mini;
padding: 1px 6px;
}
&.active {
.label {
@apply coz-fg-primary;
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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 { NavLink } from 'react-router-dom';
import React, { type ReactNode } from 'react';
import { isString } from 'lodash-es';
import classNames from 'classnames';
import { SpaceAppEnum } from '@coze-arch/web-context';
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import { Space, Badge } from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { getFlags } from '@coze-arch/bot-flags';
import { KnowledgeE2e, BotE2e } from '@coze-data/e2e';
import { useSpaceApp } from '@coze-foundation/space-store';
import s from './index.module.less';
interface MenuItem {
/**
* 如果是string需传入starling key并且会由div包一层
* 如果是function则自定义label的实现active表示是否是选中态
*/
label: string | ((active: boolean) => React.ReactNode);
/** label 外的 badge未来再扩展配置项 */
badge?: string;
app: SpaceAppEnum;
/**
* Q为什么不叫 visibleFG 要取反filter() 也要取反,很麻烦
* A为了兼容旧配置缺省时认定为 visible。避免合码时无冲突 导致忽略掉新增配置的问题。
*/
invisible?: boolean;
/** 目前24.05.21)没发现用处,怀疑是以前的功能迭代掉了,@huangjian 说先留着 */
icon?: ReactNode;
/** 目前24.05.21)没发现用处,怀疑是以前的功能迭代掉了,@huangjian 说先留着 */
selectedIcon?: ReactNode;
/** 自动化打标 */
e2e?: string;
}
const GET_MENU_SPACE_APP = (): Array<MenuItem> => [
{
label: 'menu_bots',
app: SpaceAppEnum.BOT,
e2e: BotE2e.BotTab,
},
{
label: 'menu_plugins',
app: SpaceAppEnum.PLUGIN,
},
{
label: 'menu_workflows',
app: SpaceAppEnum.WORKFLOW,
},
{
label: 'imageflow_title',
app: SpaceAppEnum.IMAGEFLOW,
invisible: false,
},
{
label: 'menu_datasets',
app: SpaceAppEnum.KNOWLEDGE,
e2e: KnowledgeE2e.KnowledgeTab,
},
{
label: 'menu_widgets',
app: SpaceAppEnum.WIDGET,
invisible: !getFlags()['bot.builder.bot.builder.widget'],
},
{
label: 'scene_resource_name',
badge: 'scene_beta_sign',
app: SpaceAppEnum.SOCIAL_SCENE,
invisible: !getFlags()['bot.studio.social'],
},
];
export const SpaceAppList = () => {
const spaceApp = useSpaceApp();
const { id: spaceId } = useSpaceStore(store => store.space);
return (
<Space spacing={4}>
{GET_MENU_SPACE_APP()
.filter(item => !item.invisible)
.map(item => {
const active = item.app === spaceApp;
const tabContent = (
<NavLink
key={item.app}
data-testid={item.e2e}
to={`/space/${spaceId}/${item.app}`}
className={s['item-link']}
onClick={() => {
sendTeaEvent(EVENT_NAMES.workspace_tab_expose, {
tab_name: item.app,
});
}}
>
{isString(item.label) ? (
<div
className={classNames({
[s.item]: true,
[s.active]: active,
})}
>
{I18n.t(item.label as I18nKeysNoOptionsType)}
</div>
) : (
item.label(active)
)}
</NavLink>
);
return item.badge ? (
<Badge
type="alt"
key={item.app}
count={I18n.t(item.badge as I18nKeysNoOptionsType)}
countStyle={{
backgroundColor: 'var(--coz-mg-color-plus-emerald)',
}}
>
{tabContent}
</Badge>
) : (
tabContent
);
})}
</Space>
);
};

View File

@@ -0,0 +1,38 @@
.topBar {
display: flex;
width: 100%;
flex-direction: column;
.des {
font-size: 14px;
font-style: normal;
font-weight: 600;
display: flex;
align-items: center;
margin: 0;
}
.tabs {
width: 100%;
text-align: left;
}
.name {
max-width: calc(100% - 28px);
word-break: break-word;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 28px;
height: 28px;
@apply coz-fg-plus;
}
.split {
width: 1px;
height: 20px;
margin: 0 4px;
border-bottom: none;
background-color: var(--coz-stroke-primary);
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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 React, { type JSX } from 'react';
import classnames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { IconCozSetting } from '@coze-arch/coze-design/icons';
import {
Typography,
Space,
IconButton,
Divider,
Avatar,
} from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { useNavigate } from 'react-router-dom';
import { SpaceAppList } from '../space-app-list';
import s from './index.module.less';
interface TopBarProps {
showActions?: boolean;
showFilter?: boolean;
isPersonal?: boolean;
actions: JSX.Element;
children: React.ReactNode;
className?: string;
titleExtend?: JSX.Element;
}
export const TopBar = (props: TopBarProps) => {
const { Text } = Typography;
const navigate = useNavigate();
const {
space: { name: spaceName, id: spaceId, icon_url: spaceIconUrl },
} = useSpaceStore();
const {
showActions,
showFilter,
isPersonal,
actions,
children,
className,
titleExtend,
} = props;
const settingLabel = I18n.t('basic_setting');
return (
<div className={classnames(s.topBar, className)}>
<Space className="w-full flex justify-between mb-24px">
<Space>
<div className={s.des}>
<Avatar
src={spaceIconUrl}
size="small"
style={{ marginRight: 8 }}
/>
<Text
ellipsis={{
showTooltip: {
opts: { content: spaceName },
},
}}
className={classnames(s.name, '!max-w-[320px]')}
>
{spaceName}
</Text>
{titleExtend}
</div>
</Space>
<Space spacing={8} className="flex items-center align-right">
{showActions ? actions : null}
{!isPersonal && (
<>
<Divider layout="horizontal" className={s.split} />
<IconButton
color="primary"
type="primary"
size="large"
onClick={() => {
sendTeaEvent(EVENT_NAMES.workspace_tab_expose, {
tab_name: 'team_manage',
});
navigate(`/space/${spaceId}/team`);
}}
icon={<IconCozSetting />}
aria-label={settingLabel}
/>
</>
)}
</Space>
</Space>
<div className={s.tabs}>
<Space className="w-full flex justify-between">
<Space spacing={8} className="flex items-center align-left shrink-0">
<SpaceAppList />
</Space>
<Space
className="!flex items-center !overflow-hidden shrink-1"
spacing={8}
>
{showFilter ? children : null}
</Space>
</Space>
</div>
</div>
);
};