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,75 @@
/*
* 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 { type CSSProperties } from 'react';
interface Entry {
withTitle: boolean;
withDropdown: boolean;
left: number;
right: number;
}
const buttonPaddingTable: Entry[] = [
{
withDropdown: true,
withTitle: true,
left: 15,
right: 9,
},
{
withDropdown: true,
withTitle: false,
left: 8,
right: 6,
},
{
withDropdown: false,
withTitle: true,
left: 15,
right: 15,
},
{
withDropdown: false,
withTitle: false,
left: 8,
right: 8,
},
];
type IndexProperty = Pick<Entry, 'withTitle' | 'withDropdown'>;
const getIndexByEntry = (entry: IndexProperty) =>
`${entry.withDropdown}-${entry.withTitle}`;
const getStyleByEntry = (entry: Entry): CSSProperties => ({
paddingLeft: entry.left,
paddingRight: entry.right,
});
const initial: Record<string, CSSProperties> = {};
const indexTable = buttonPaddingTable.reduce((all, entry) => {
all[getIndexByEntry(entry)] = getStyleByEntry(entry);
return all;
}, initial);
export const getButtonPaddingStyle = (param: {
withDropdown: boolean;
withTitle: boolean;
}) => {
const index = getIndexByEntry(param);
return indexTable[index];
};

View File

@@ -0,0 +1,111 @@
/*
* 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 { type FC, type ReactNode, useState } from 'react';
import { omit } from 'lodash-es';
import classNames from 'classnames';
import { IconCozArrowDownFill } from '@coze-arch/coze-design/icons';
import {
Menu,
Button,
Tooltip,
type ButtonProps,
type MenuProps,
} from '@coze-arch/coze-design';
import { getButtonPaddingStyle } from './button-padding-table';
export interface DebugDropdownButtonProps {
withBackground: boolean;
hideTitle: boolean;
tooltipContent?: ReactNode;
menuContent?: ReactNode;
menuProps?: Omit<MenuProps, 'render' | 'clickToHide'>;
buttonProps?: Omit<ButtonProps, 'className' | 'icon'>;
icon?: ReactNode;
clickToHide?: boolean;
children?: ReactNode;
className?: string;
active?: boolean;
}
export const DebugDropdownButton: FC<DebugDropdownButtonProps> = props => {
const {
withBackground,
hideTitle,
menuContent,
children,
buttonProps,
menuProps,
className,
icon,
tooltipContent,
active,
clickToHide = true,
} = props;
const [isHovering, setIsHovering] = useState(false);
const withDropdown = !!menuContent;
const paddingStyle = getButtonPaddingStyle({
withDropdown,
withTitle: !hideTitle,
});
const { style: buttonStyle, ...restButtonProps } = buttonProps || {};
const Trigger = (
<Button
className={classNames(
className,
withBackground && '!coz-fg-images-white',
'mr-[4px]',
)}
color={active ? 'highlight' : 'secondary'}
icon={null}
style={{ ...paddingStyle, ...buttonStyle }}
onMouseEnter={evt => {
restButtonProps?.onMouseEnter?.(evt);
setIsHovering(true);
}}
onMouseLeave={evt => {
restButtonProps?.onMouseLeave?.(evt);
setIsHovering(false);
}}
{...omit(restButtonProps, 'onMouseEnter', 'onMouseLeave')}
>
<div className="flex items-center text-[14px] gap-[3px]">
<span className="inline-flex text-[16px] items-center">{icon}</span>
{hideTitle ? null : children}
{withDropdown ? <IconCozArrowDownFill className="!ml-0" /> : null}
</div>
</Button>
);
return (
<Tooltip
content={tooltipContent || children}
trigger="custom"
visible={isHovering}
>
{withDropdown ? (
<Menu clickToHide={clickToHide} render={menuContent} {...menuProps}>
{Trigger}
</Menu>
) : (
Trigger
)}
</Tooltip>
);
};

View File

@@ -0,0 +1,7 @@
/* stylelint-disable declaration-no-important */
.tool-pane {
.tool-pane-icon {
display: flex;
align-items: center;
}
}

View File

@@ -0,0 +1,203 @@
/*
* 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 CSSProperties,
type FC,
type PropsWithChildren,
type ReactNode,
useContext,
useState,
} from 'react';
import { omit } from 'lodash-es';
import { useUpdateEffect } from 'ahooks';
import { type DropdownProps } from '@coze-arch/bot-semi/Dropdown';
import { UIDragModal, UIModal, type UIModalProps } from '@coze-arch/bot-semi';
import {
DebugDropdownButton,
type DebugDropdownButtonProps,
} from '../debug-dropdown-button';
import { ToolPaneContext } from '../../debug-tool-list-context';
// 按钮点击交互枚举
export enum OperateTypeEnum {
MODAL = 'modal', // 弹窗
DROPDOWN = 'dropdown', // 下拉选择
CUSTOM = 'custom', // 自定义交互
}
// 弹窗类型枚举
export enum ModalTypeEnum {
Drag = 'drag', // 悬浮可拖拽弹窗
CENTER = 'center', // 居中弹窗
}
interface ToolPaneProps {
className?: string;
style?: CSSProperties;
visible?: boolean; // 是否展示入口
itemKey: string; // 唯一标识
icon: ReactNode;
title: string;
operateType: OperateTypeEnum;
customShowOperateArea?: boolean; // 仅operateType=custom时生效自定义当前展示操作区域状态
beforeVisible?: () => Promise<void>; // 交互窗口打开前回调
// 仅operateType=modal时生效modalContent通过children传递
modalType?: ModalTypeEnum;
modalProps?: UIModalProps;
// 仅operateType=dropdown时生效children无效通过dropdownProps.menu
dropdownProps?: Pick<
DropdownProps,
'clickToHide' | 'showTick' | 'render' | 'zIndex'
>;
buttonProps?: DebugDropdownButtonProps['buttonProps'];
onEntryButtonClick?: () => void;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const DEFAULT_MODAl_ZINDEX = 999;
// eslint-disable-next-line @typescript-eslint/naming-convention
const FOCUS_MODAl_ZINDEX = 1000;
export const ToolPane: FC<PropsWithChildren<ToolPaneProps>> = ({
className,
style,
visible = true,
itemKey,
icon,
title,
operateType,
customShowOperateArea,
beforeVisible,
children,
modalType = ModalTypeEnum.CENTER,
modalProps,
dropdownProps,
onEntryButtonClick,
buttonProps = {},
}) => {
const toolPaneContext = useContext(ToolPaneContext);
const {
hideTitle,
focusItemKey,
focusDragModal,
reComputeOverflow,
showBackground,
} = toolPaneContext;
const focus = focusItemKey === itemKey;
// 是否显示操作区域
const [showOperateArea, setShowOperateArea] = useState(false);
useUpdateEffect(() => {
!visible && reComputeOverflow?.();
}, [visible]);
const onClickButton = async () => {
onEntryButtonClick?.();
if (operateType === OperateTypeEnum.DROPDOWN) {
return;
}
if (operateType === OperateTypeEnum.CUSTOM) {
beforeVisible?.();
return;
}
if (!showOperateArea) {
await beforeVisible?.();
focusDragModal?.(itemKey);
}
setShowOperateArea(!showOperateArea);
};
const customAreaActivated =
showOperateArea ||
(operateType === OperateTypeEnum.CUSTOM && customShowOperateArea);
if (!visible) {
return null;
}
const setToolButton = () => (
<DebugDropdownButton
tooltipContent={title}
icon={icon}
hideTitle={Boolean(hideTitle)}
withBackground={Boolean(showBackground)}
menuContent={dropdownProps?.render}
menuProps={omit(dropdownProps || {}, 'render')}
active={customAreaActivated}
buttonProps={{
onClick: onClickButton,
style,
...buttonProps,
}}
className={className}
>
{title}
</DebugDropdownButton>
);
return (
<>
{/* 弹窗交互区域 */}
{operateType === OperateTypeEnum.MODAL && (
<>
{setToolButton()}
{/* 居中弹窗 */}
{modalType === ModalTypeEnum.CENTER && (
<UIModal
{...modalProps}
zIndex={FOCUS_MODAl_ZINDEX}
keepDOM={false}
centered
visible={showOperateArea}
onCancel={() => setShowOperateArea(false)}
>
{children}
</UIModal>
)}
{/* 悬浮、可拖拽 */}
{modalType === ModalTypeEnum.Drag && (
<UIDragModal
{...modalProps}
focusKey={itemKey}
zIndex={focus ? FOCUS_MODAl_ZINDEX : DEFAULT_MODAl_ZINDEX}
title={modalProps?.title || title}
visible={showOperateArea}
onCancel={() => setShowOperateArea(false)}
onWindowFocus={focusDragModal}
>
{children}
</UIDragModal>
)}
</>
)}
{/* 下拉交互区域 */}
{operateType === OperateTypeEnum.DROPDOWN && setToolButton()}
{/* 自定义交互区域 */}
{operateType === OperateTypeEnum.CUSTOM && setToolButton()}
</>
);
};

View File

@@ -0,0 +1,28 @@
/*
* 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 @typescript-eslint/naming-convention */
import React from 'react';
export const ToolPaneContext = React.createContext<{
hideTitle?: boolean;
focusItemKey?: string;
focusDragModal?: (v: string) => void;
reComputeOverflow?: () => void;
showBackground?: boolean;
}>({});
export const ToolPaneContextProvider = ToolPaneContext.Provider;

View File

@@ -0,0 +1,78 @@
/*
* 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 CSSProperties,
type FC,
type PropsWithChildren,
useState,
} from 'react';
import classNames from 'classnames';
import { OverflowList } from '@blueprintjs/core';
import { ToolPaneContextProvider } from './debug-tool-list-context';
import s from './index.module.less';
interface DebugToolListProps {
className?: string;
style?: CSSProperties;
showBackground: boolean;
}
export const DebugToolList: FC<PropsWithChildren<DebugToolListProps>> = ({
className,
style,
children,
showBackground,
}): JSX.Element => {
const [dragModalFocusItemKey, setDragModalFocusItemKey] =
useState<string>('');
const panes = React.Children.map(
children,
(child: React.ReactNode) => child,
)?.filter(Boolean);
return (
<div
className={classNames(s['debug-tool-list'], className)}
style={style}
data-testid="bot-detail.debug-tool-list"
>
<OverflowList
className={s['tool-overflow-list']}
items={panes}
overflowRenderer={() => null}
visibleItemRenderer={(child: React.ReactNode, index) => (
<ToolPaneContextProvider
key={index}
value={{
hideTitle: true,
focusItemKey: dragModalFocusItemKey,
focusDragModal: itemKey => setDragModalFocusItemKey(itemKey),
showBackground,
}}
>
{child}
</ToolPaneContextProvider>
)}
collapseFrom="end"
/>
</div>
);
};

View File

@@ -0,0 +1,6 @@
.debug-tool-list {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
}

View File

@@ -0,0 +1,23 @@
/*
* 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 { DebugToolList } from './debug-tool-list';
export {
ToolPane,
OperateTypeEnum,
ModalTypeEnum,
} from './components/tool-pane';
export { ToolPaneContext } from './debug-tool-list-context';

View File

@@ -0,0 +1,22 @@
/*
* 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 {
ModalTypeEnum,
OperateTypeEnum,
ToolPane,
DebugToolList,
} from './components/debug-tool-list';

View File

@@ -0,0 +1,17 @@
/*
* 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' />