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,70 @@
/*
* 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 ReactNode, type PropsWithChildren } from 'react';
import { BotMode } from '@coze-arch/bot-api/playground_api';
import { SingleSheet, type SingleSheetProps } from './single-sheet';
import { MultipleSheet, type MultipleSheetProps } from './multiple-sheet';
export { SingleSheet, MultipleSheet };
type SheetViewProps = MultipleSheetProps &
SingleSheetProps & {
mode: number;
renderContent?: (headerNode: ReactNode) => ReactNode;
};
export const SheetView: React.FC<PropsWithChildren<SheetViewProps>> = ({
mode = 1,
title,
titleNode,
children,
slideProps,
containerClassName,
headerClassName,
titleClassName,
renderContent,
}) => {
if (mode === BotMode.SingleMode || mode === BotMode.WorkflowMode) {
return (
<SingleSheet
containerClassName={containerClassName}
titleClassName={titleClassName}
headerClassName={headerClassName}
title={title}
titleNode={titleNode}
renderContent={renderContent}
>
{children}
</SingleSheet>
);
}
return (
<MultipleSheet
title={title}
titleNode={titleNode}
containerClassName={containerClassName}
titleClassName={titleClassName}
headerClassName={headerClassName}
slideProps={slideProps}
renderContent={renderContent}
>
{children}
</MultipleSheet>
);
};
export default SheetView;

View File

@@ -0,0 +1,101 @@
.sheet-container {
.sheet-wrapper();
position: relative;
transition: width .1s linear 0s;
}
// multi style
.button {
position: absolute;
top: 18px;
cursor: pointer;
z-index: 100;
}
.btn-left {
left: 0;
}
.btn-right {
right: 0;
}
.sheet-wrapper {
:global {
.semi-sidesheet-body {
padding: 0;
}
}
}
.sheet-content {
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--light-color-grey-grey-0, #f7f7fa);
box-shadow: 0 6px 8px 0 rgb(29 28 35 / 6%),
0 0 2px 0 rgb(29 28 35 / 18%);
}
.sheet-header {
height: 56px;
padding: 16px 24px;
display: flex;
align-items: center;
border-bottom: 1px solid var(--light-usage-border-color-border, rgb(29 28 35 / 8%));
z-index: 10;
}
.sheet-header-arrow {
margin-right: 8px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
width: 20px;
height: 20px;
flex-shrink: 0;
border-radius: 4px;
border: 0.5px solid var(--Light-usage-border---color-border, rgba(29, 28, 35, 8%));
transform: rotate(180deg);
background: var(--Light-usage-bg---color-bg-0, #FFF);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 4%), 0 0 1px 0 rgba(0, 0, 0, 8%);
}
.sheet-header-arrow-left {
margin-right: 0;
margin-left: 8px;
transform: rotate(0);
}
.sheet-header-arrow-right {
box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 4%), 0 0 1px 0 rgba(0, 0, 0, 8%);
}
.sheet-header-content {
display: flex;
align-items: center;
flex: 1;
}
.sheet-header-title {
color: var(--light-usage-text-color-text-0, #1c1d23);
font-size: 18px;
font-weight: 600;
line-height: 24px;
flex-shrink: 0;
margin-right: 24px;
}
.sheet-header-scope {
width: 0;
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
height: 64px;
}

View File

@@ -0,0 +1,211 @@
/*
* 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 React, { type PropsWithChildren, type ReactNode, useMemo } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { nanoid } from 'nanoid';
import classNames from 'classnames';
import { useMultiAgentStore } from '@coze-studio/bot-detail-store/multi-agent';
import { SideSheet, Tooltip } from '@coze-arch/bot-semi';
import { IconCollapse } from '@coze-arch/bot-icons';
import styles from './index.module.less';
export interface MultipleSheetProps extends PropsWithChildren {
containerClassName?: string;
headerClassName?: string;
titleClassName?: string;
title?: string;
titleNode?: ReactNode;
renderContent?: (headerNode: ReactNode) => ReactNode;
slideProps?: {
placement?: 'left' | 'right';
width?: number;
btnClassName?: string;
visible?: boolean;
btnNode?: ReactNode;
openBtnTooltip?: string;
closeBtnTooltip?: string;
};
}
export function MultipleSheet({
titleClassName,
containerClassName,
headerClassName,
title,
titleNode,
slideProps,
children,
renderContent,
}: MultipleSheetProps) {
const {
placement,
width,
btnClassName,
btnNode,
openBtnTooltip,
closeBtnTooltip,
} = slideProps || {};
const isLeft = useMemo(() => placement === 'left', [placement]);
const { setMultiSheetViewOpen, multiSheetViewOpen } = useMultiAgentStore(
useShallow(store => ({
setMultiSheetViewOpen: store.setMultiSheetViewOpen,
multiSheetViewOpen: store.multiSheetViewOpen,
})),
);
const containerId = useMemo(() => `container-${nanoid()}`, []);
const open = useMemo(() => {
if (isLeft) {
return multiSheetViewOpen.left;
}
return multiSheetViewOpen.right;
}, [isLeft, multiSheetViewOpen]);
const setOpen = (_open: boolean) => {
if (isLeft) {
setMultiSheetViewOpen({ left: _open });
} else {
setMultiSheetViewOpen({ right: _open });
}
};
const computedStyle = (): React.CSSProperties => {
if (open) {
return {
width,
};
}
return {
width: 0,
};
};
const header = (
<div
className={classNames(styles['sheet-header'], headerClassName)}
style={{
flexDirection: isLeft ? 'row-reverse' : 'row',
}}
>
{/* btn */}
<div
className={classNames(
styles['sheet-header-arrow'],
isLeft
? styles['sheet-header-arrow-left']
: styles['sheet-header-arrow-right'],
)}
onClick={() => {
setOpen(false);
}}
data-testid={
isLeft
? 'bot-edit-muli-agent-action-arrow-right-button'
: 'bot-edit-muli-agent-action-arrow-left-button'
}
>
{closeBtnTooltip && open ? (
<Tooltip
spacing={20}
position={isLeft ? 'right' : 'left'}
content={closeBtnTooltip}
>
<IconCollapse />
</Tooltip>
) : (
<IconCollapse />
)}
</div>
<div className={styles['sheet-header-content']}>
{/* title */}
<div
className={classNames(styles['sheet-header-title'], titleClassName)}
>
{title}
</div>
{/* 头部插槽 */}
<div className={styles['sheet-header-scope']}> {titleNode}</div>
</div>
</div>
);
return (
<div
id={containerId}
className={styles['sheet-container']}
style={computedStyle()}
>
{!!btnNode && (
<div
className={classNames(
styles.button,
isLeft ? styles['btn-left'] : styles['btn-right'],
btnClassName,
)}
onClick={() => {
setOpen(true);
}}
>
{!open && openBtnTooltip ? (
<Tooltip
position={isLeft ? 'right' : 'left'}
content={openBtnTooltip}
>
{btnNode}
</Tooltip>
) : (
btnNode
)}
</div>
)}
<SideSheet
keepDOM
width={width}
mask={false}
placement={placement}
visible={open}
getPopupContainer={() =>
// @tip 不确定这个PopupContainer的实现逻辑采用ref.current拿不到最新的值
// @tip Semi需更新至2.54.0以上当SideSheet的visible为true才挂载可以确保`getPopupContainer`使用querySelector可以获取到父组件dom但兜底值要去掉https://github.com/DouyinFE/semi-design/pull/2094
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
document.querySelector(`#${containerId}`)!
}
className={styles['sheet-wrapper']}
headerStyle={{
display: 'none',
}}
>
<div
className={classNames(styles['sheet-content'], containerClassName)}
>
{/* 浮层头部 */}
{renderContent ? (
renderContent(header)
) : (
<>
{header}
{children}
</>
)}
</div>
</SideSheet>
</div>
);
}

View File

@@ -0,0 +1,33 @@
.card {
background-color: var(--light-color-grey-grey-0, #f7f7fa);
display: flex;
overflow: hidden;
flex-direction: column;
height: 100%;
position: relative;
}
.sheet-header {
height: 64px;
padding: 16px;
display: flex;
align-items: center;
border-bottom: 1px solid theme('colors.stroke.5');
}
.sheet-header-title {
font-size: 20px;
font-weight: 500;
line-height: 24px;
flex-shrink: 0;
margin-right: 24px;
}
.sheet-header-scope {
width: 0;
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
height: 64px;
}

View File

@@ -0,0 +1,70 @@
/*
* 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 PropsWithChildren, type ReactNode } from 'react';
import classNames from 'classnames';
import styles from './index.module.less';
export interface SingleSheetProps extends PropsWithChildren {
containerClassName?: string;
headerClassName?: string;
title?: string;
titleNode?: ReactNode;
titleClassName?: string;
headerSlotClassName?: string;
renderContent?: (headerNode: ReactNode) => ReactNode;
}
export function SingleSheet({
containerClassName,
headerClassName,
titleClassName,
title,
titleNode,
children,
headerSlotClassName,
renderContent,
}: SingleSheetProps) {
const headerNode = (
<div className={classNames(styles.card, containerClassName)}>
{/* 浮层头部 */}
<div className={classNames(styles['sheet-header'], headerClassName)}>
<div
className={classNames(
styles['sheet-header-title'],
'coz-fg-plus',
titleClassName,
)}
>
{title}
</div>
{/* 头部插槽 */}
<div
className={classNames(
styles['sheet-header-scope'],
headerSlotClassName,
)}
>
{titleNode}
</div>
</div>
{children}
</div>
);
return renderContent ? <>{renderContent(headerNode)}</> : headerNode;
}