feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -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 { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import { type BotMode } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { InvisibleToolController } from '../invisible-tool-controller';
|
||||
import { type IEventCallbacks } from '../../typings/event-callbacks';
|
||||
import {
|
||||
type IPreferenceContext,
|
||||
PreferenceContextProvider,
|
||||
} from '../../context/preference-context';
|
||||
import { AbilityAreaContextProvider } from '../../context/ability-area-context';
|
||||
|
||||
type IProps = {
|
||||
eventCallbacks?: Partial<IEventCallbacks>;
|
||||
mode: BotMode;
|
||||
modeSwitching: boolean;
|
||||
isInit: boolean;
|
||||
} & Partial<IPreferenceContext>;
|
||||
|
||||
export const AbilityAreaContainer: FC<PropsWithChildren<IProps>> = props => {
|
||||
const {
|
||||
children,
|
||||
eventCallbacks,
|
||||
enableToolHiddenMode,
|
||||
isReadonly,
|
||||
mode,
|
||||
modeSwitching,
|
||||
isInit,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<PreferenceContextProvider
|
||||
enableToolHiddenMode={enableToolHiddenMode}
|
||||
isReadonly={isReadonly}
|
||||
>
|
||||
<AbilityAreaContextProvider
|
||||
eventCallbacks={eventCallbacks}
|
||||
mode={mode}
|
||||
modeSwitching={modeSwitching}
|
||||
isInit={isInit}
|
||||
>
|
||||
<InvisibleToolController />
|
||||
{children}
|
||||
</AbilityAreaContextProvider>
|
||||
</PreferenceContextProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { IconCozEdit, IconCozPlus } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import { type ToolButtonCommonProps } from '../../typings/button';
|
||||
|
||||
interface AddButtonProps extends ToolButtonCommonProps {
|
||||
iconName?: 'add' | 'edit';
|
||||
enableAutoHidden?: boolean;
|
||||
}
|
||||
|
||||
export const AddButton: FC<AddButtonProps> = ({
|
||||
onClick,
|
||||
tooltips,
|
||||
disabled,
|
||||
loading,
|
||||
iconName = 'add',
|
||||
enableAutoHidden,
|
||||
...restProps
|
||||
}) => {
|
||||
const readonly = useBotDetailIsReadonly();
|
||||
|
||||
if (readonly && enableAutoHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolTooltip content={tooltips}>
|
||||
<div>
|
||||
<IconButton
|
||||
icon={
|
||||
iconName === 'add' ? (
|
||||
<IconCozPlus className="text-base coz-fg-secondary" />
|
||||
) : (
|
||||
<IconCozEdit className="text-base coz-fg-secondary" />
|
||||
)
|
||||
}
|
||||
loading={loading}
|
||||
onClick={onClick}
|
||||
size="small"
|
||||
color="secondary"
|
||||
disabled={!!disabled}
|
||||
data-testid={restProps['data-testid']}
|
||||
/>
|
||||
</div>
|
||||
</ToolTooltip>
|
||||
);
|
||||
};
|
||||
@@ -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 { ErrorBoundary } from 'react-error-boundary';
|
||||
import { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import { AbilityScope, type AgentSkillKey } from '@coze-agent-ide/tool-config';
|
||||
|
||||
import { AbilityConfigContextProvider } from '../../context/ability-config-context';
|
||||
|
||||
interface IProps {
|
||||
agentSkillKey?: AgentSkillKey;
|
||||
}
|
||||
|
||||
export const AgentSkillContainer: FC<PropsWithChildren<IProps>> = ({
|
||||
children,
|
||||
agentSkillKey,
|
||||
}) => (
|
||||
<ErrorBoundary fallback={<div>error</div>}>
|
||||
<AbilityConfigContextProvider
|
||||
abilityKey={agentSkillKey}
|
||||
scope={AbilityScope.AGENT_SKILL}
|
||||
>
|
||||
{children}
|
||||
</AbilityConfigContextProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
@@ -0,0 +1,100 @@
|
||||
.content-block {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--light-usage-fill-color-fill-0, rgb(46 46 56 / 4%));
|
||||
border-radius: 8px;
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.header-icon-arrow {
|
||||
cursor: pointer;
|
||||
|
||||
transform: rotate3d(0, 0, 0, 0);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-right: 3px;
|
||||
|
||||
transition: transform .2s linear 0s;
|
||||
|
||||
&.open {
|
||||
transform: rotate3d(0, 0, 1, 90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: flex;
|
||||
margin-right: 8px;
|
||||
|
||||
>img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
|
||||
.label {
|
||||
margin-left: 4px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
}
|
||||
|
||||
.popover {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
&>svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
color: #A7A9B0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-collapsible-wrapper {
|
||||
padding-left: 0 !important;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.overflow-content {
|
||||
padding-left: 0;
|
||||
|
||||
&.open {
|
||||
:global {
|
||||
.semi-collapsible-wrapper,
|
||||
.semi-collapsible-wrapper [x-semi-prop] {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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,
|
||||
forwardRef,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Tooltip, Collapsible } from '@coze-arch/bot-semi';
|
||||
import { IconInfo, IconArrowRight } from '@coze-arch/bot-icons';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export type AgentContentBlockProps = PropsWithChildren<{
|
||||
allowToggleCollapsible?: boolean;
|
||||
title?: ReactNode;
|
||||
actionButton?: ReactNode;
|
||||
tooltip?: string | ReactNode;
|
||||
maxContentHeight?: number;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
style?: CSSProperties;
|
||||
defaultExpand?: boolean;
|
||||
autoExpandWhenDomChange?: boolean;
|
||||
}>;
|
||||
|
||||
export interface ContentRef {
|
||||
setOpen?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const AgentSkillContentBlock = forwardRef<
|
||||
ContentRef,
|
||||
AgentContentBlockProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
allowToggleCollapsible = true,
|
||||
children,
|
||||
title,
|
||||
actionButton,
|
||||
maxContentHeight,
|
||||
tooltip,
|
||||
className,
|
||||
contentClassName,
|
||||
style,
|
||||
defaultExpand = true,
|
||||
autoExpandWhenDomChange,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [isOpen, setIsOpen] = useState(defaultExpand);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const childNodeRef = useRef<HTMLDivElement>(null);
|
||||
const actionDivRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const target = childNodeRef.current;
|
||||
if (autoExpandWhenDomChange && target && allowToggleCollapsible) {
|
||||
const config = { attributes: true, childList: true, subtree: true };
|
||||
// 只有开启了dom改变自动展开功能才启动
|
||||
const callback: MutationCallback = mutationList => {
|
||||
if (mutationList.length > 0 && !isOpen) {
|
||||
// 当dom改变并且没有开启时,会自动开启
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
const observer = new MutationObserver(callback);
|
||||
observer.observe(target, config);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}
|
||||
}, [isOpen, allowToggleCollapsible]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setOpen,
|
||||
}));
|
||||
|
||||
const setOpen = (innerIsOpen: boolean) => {
|
||||
setIsOpen(innerIsOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(className, s['content-block'])}
|
||||
style={style}
|
||||
ref={containerRef}
|
||||
>
|
||||
<header
|
||||
className={classNames(s['header-content'])}
|
||||
onClick={e => {
|
||||
if (!allowToggleCollapsible) {
|
||||
return;
|
||||
}
|
||||
// @danger 不可以阻止内部节点的点击冒泡,不然无法设置节点的选中态
|
||||
const el = e.target as HTMLElement;
|
||||
// 这里需要多重判断,
|
||||
// 第一次判断:如果包含在container内,才需要去切换open
|
||||
// 第二次判断:如果包含在action内,则不能切换open,其他都可以
|
||||
// @TIP contains方法会判断自身节点,即A.contains(A)也是true。但是这里就算是自身也没有影响
|
||||
if (containerRef.current && containerRef.current.contains(el)) {
|
||||
if (actionDivRef.current && actionDivRef.current.contains(el)) {
|
||||
// 此时不切换open
|
||||
return;
|
||||
}
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={s.header}>
|
||||
{allowToggleCollapsible ? (
|
||||
<div
|
||||
className={classNames({
|
||||
[s['header-icon-arrow'] || '']: true,
|
||||
[s.open || '']: isOpen,
|
||||
})}
|
||||
>
|
||||
<IconArrowRight />
|
||||
</div>
|
||||
) : null}
|
||||
<div className={s.label}>{title}</div>
|
||||
{tooltip ? (
|
||||
<Tooltip
|
||||
showArrow
|
||||
position="top"
|
||||
className={s.popover}
|
||||
content={tooltip}
|
||||
>
|
||||
<IconInfo className={s.icon} />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<div ref={actionDivRef}>{actionButton}</div>
|
||||
</header>
|
||||
<div
|
||||
className={classNames({
|
||||
[s['overflow-content'] || '']: true,
|
||||
[contentClassName || '']: Boolean(contentClassName),
|
||||
[s.open || '']: isOpen,
|
||||
})}
|
||||
>
|
||||
<Collapsible keepDOM fade isOpen={isOpen}>
|
||||
<div
|
||||
className={s.content}
|
||||
ref={childNodeRef}
|
||||
style={{
|
||||
maxHeight: maxContentHeight ? maxContentHeight : undefined,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
.item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.skills {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 20px; // 142.857%
|
||||
color: var(--light-usage-bg-color-bg-0, #FFF);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { type PopoverProps } from '@coze-arch/bot-semi/Popover';
|
||||
import { Popover } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const POPOVER_PROPS: Partial<PopoverProps> = {
|
||||
style: {
|
||||
backgroundColor: 'var(--light-color-grey-grey-7, #41464C)',
|
||||
borderColor: 'var(--light-color-grey-grey-7, #41464C)',
|
||||
padding: '8px 12px',
|
||||
},
|
||||
showArrow: true,
|
||||
position: 'top',
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
tooltip: React.ReactNode;
|
||||
icon: React.ReactElement;
|
||||
}
|
||||
|
||||
export const AgentSkillContent = React.memo((props: IProps) => {
|
||||
const { children, tooltip, icon } = props;
|
||||
|
||||
const iconNode = React.cloneElement(icon, {
|
||||
className: classnames(icon?.props?.className, styles.icon),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
<Popover
|
||||
{...POPOVER_PROPS}
|
||||
content={<span className={styles['popover-content']}>{tooltip}</span>}
|
||||
>
|
||||
{iconNode}
|
||||
</Popover>
|
||||
<div className={styles.skills}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
// eslint-disable-next-line @coze-arch/no-pkg-dir-import
|
||||
import { type AgentModalTabKey } from '@coze-agent-ide/tool-config/src/types';
|
||||
import { AbilityScope } from '@coze-agent-ide/tool-config';
|
||||
import { UITabsModal } from '@coze-arch/bot-semi';
|
||||
import { type ModalProps } from '@douyinfe/semi-foundation/lib/es/modal/modalFoundation';
|
||||
|
||||
import { ToolContainer } from '../tool-container';
|
||||
import { useAgentModalTriggerEvent } from '../../hooks/agent-skill-modal/use-agent-modal-trigger-event';
|
||||
|
||||
export interface IAgentSkillModalPane {
|
||||
key: AgentModalTabKey;
|
||||
tab: React.ReactNode;
|
||||
pane: React.ReactNode;
|
||||
}
|
||||
|
||||
interface AgentSkillModalProps extends Partial<ModalProps> {
|
||||
tabPanes: IAgentSkillModalPane[];
|
||||
}
|
||||
|
||||
export const AgentSkillModal: FC<AgentSkillModalProps> = ({
|
||||
tabPanes,
|
||||
...restModalProps
|
||||
}) => {
|
||||
const { emitTabChangeEvent } = useAgentModalTriggerEvent();
|
||||
return (
|
||||
<UITabsModal
|
||||
visible
|
||||
tabs={{
|
||||
tabsProps: {
|
||||
lazyRender: true,
|
||||
onChange: activityKey =>
|
||||
emitTabChangeEvent(activityKey as AgentModalTabKey),
|
||||
},
|
||||
tabPanes: tabPanes.map(tab => ({
|
||||
tabPaneProps: {
|
||||
tab: tab.tab,
|
||||
itemKey: tab.key,
|
||||
},
|
||||
content: (
|
||||
<ToolContainer scope={AbilityScope.AGENT_SKILL}>
|
||||
<>{tab.pane}</>
|
||||
</ToolContainer>
|
||||
),
|
||||
})),
|
||||
}}
|
||||
{...restModalProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
.container {
|
||||
margin-top: 8px;
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
overflow: hidden auto;
|
||||
width: 100%;
|
||||
max-height: 280px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 20px; // 142.857%
|
||||
color: var(--light-usage-text-color-text-3, rgb(29 28 35 / 35%));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 PropsWithChildren, Children, useEffect } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { AbilityScope } from '@coze-agent-ide/tool-config';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
|
||||
import { AgentSkillContentBlock } from '../agent-skill-content-block';
|
||||
import { AgentSkillContainer } from '../agent-skill-container';
|
||||
import { hasValidAgentSkillKey } from '../../utils/has-valid-key';
|
||||
import { useSubscribeToolStore } from '../../hooks/public/store/use-tool-store';
|
||||
import { useHasAgentSkill } from '../../hooks/public/agent/use-has-agent-skill';
|
||||
import { useRegisterAgentSkillKey } from '../../hooks/builtin/use-register-agent-skill-key';
|
||||
import { useNoneAgentSkill } from '../../hooks/agent-skill/use-agent-skill';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
agentId: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
actionButton?: React.ReactNode;
|
||||
emptyText: string;
|
||||
}
|
||||
|
||||
export const AgentSkillView: FC<PropsWithChildren<IProps>> = ({
|
||||
children,
|
||||
agentId,
|
||||
title,
|
||||
className,
|
||||
style,
|
||||
actionButton,
|
||||
emptyText,
|
||||
}) => {
|
||||
const readonly = useBotDetailIsReadonly();
|
||||
|
||||
const registerAgentSkillKey = useRegisterAgentSkillKey();
|
||||
const noneAgentSkill = useNoneAgentSkill();
|
||||
const { getHasAgentSkill } = useHasAgentSkill();
|
||||
|
||||
useSubscribeToolStore(AbilityScope.AGENT_SKILL, agentId);
|
||||
|
||||
// 前置注册
|
||||
useEffect(() => {
|
||||
Children.map(children, child => {
|
||||
if (!hasValidAgentSkillKey(child)) {
|
||||
return child;
|
||||
}
|
||||
|
||||
const agentSkillKey = child.key;
|
||||
|
||||
registerAgentSkillKey(agentSkillKey);
|
||||
});
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<AgentSkillContentBlock
|
||||
title={title}
|
||||
className={classNames(styles.container, className)}
|
||||
style={style}
|
||||
actionButton={!readonly && actionButton}
|
||||
>
|
||||
{noneAgentSkill ? (
|
||||
<span className={styles.empty}>{emptyText}</span>
|
||||
) : (
|
||||
<div className={styles.content}>
|
||||
{Children.map(children, child => {
|
||||
if (
|
||||
typeof child === 'string' ||
|
||||
typeof child === 'number' ||
|
||||
typeof child === 'boolean'
|
||||
) {
|
||||
return child;
|
||||
}
|
||||
if (!hasValidAgentSkillKey(child)) {
|
||||
return child;
|
||||
}
|
||||
const agentSkillKey = child.key;
|
||||
const hasAgentSkill = getHasAgentSkill(agentSkillKey);
|
||||
return (
|
||||
hasAgentSkill && (
|
||||
<AgentSkillContainer agentSkillKey={agentSkillKey}>
|
||||
<>{child}</>
|
||||
</AgentSkillContainer>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</AgentSkillContentBlock>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
import { IconAuto } from '@coze-arch/bot-icons';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import { type ToolButtonCommonProps } from '../../typings/button';
|
||||
|
||||
interface AutoGenerateButtonProps extends ToolButtonCommonProps {
|
||||
enableAutoHidden?: boolean;
|
||||
}
|
||||
|
||||
export const AutoGenerateButton: FC<AutoGenerateButtonProps> = ({
|
||||
onClick,
|
||||
tooltips,
|
||||
loading,
|
||||
disabled,
|
||||
enableAutoHidden,
|
||||
...restProps
|
||||
}) => {
|
||||
const readonly = useBotDetailIsReadonly();
|
||||
|
||||
if (readonly && enableAutoHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolTooltip content={tooltips}>
|
||||
<IconButton
|
||||
icon={<IconAuto />}
|
||||
loading={loading}
|
||||
disabled={!!disabled}
|
||||
onClick={onClick}
|
||||
size="small"
|
||||
color="secondary"
|
||||
data-testid={restProps['data-testid']}
|
||||
/>
|
||||
</ToolTooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.tool-container-fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin-bottom: 4px;
|
||||
padding: 0 3px;
|
||||
|
||||
color: #F93920;
|
||||
|
||||
border-bottom: 1px solid;
|
||||
|
||||
@apply coz-stroke-primary;
|
||||
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left: 7px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
@@ -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 { type FC, useState } from 'react';
|
||||
|
||||
import { getSlardarInstance } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconInfoCircle } from '@coze-arch/bot-icons';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
toolTitle?: string;
|
||||
}
|
||||
export const ToolContainerFallback: FC<IProps> = ({ toolTitle }) => {
|
||||
const [sessionId] = useState(() => getSlardarInstance()?.config()?.sessionId);
|
||||
|
||||
return (
|
||||
<div className={styles['tool-container-fallback']}>
|
||||
<IconInfoCircle />
|
||||
<span className={styles.text}>
|
||||
{toolTitle}
|
||||
{I18n.t('tool_load_error')}
|
||||
</span>
|
||||
{!!sessionId && (
|
||||
<div className="leading-[12px] ml-[6px] text-[12px] text-gray-400">
|
||||
{sessionId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
.wrapper {
|
||||
padding-bottom: 24px;
|
||||
|
||||
&.left {
|
||||
.header {
|
||||
padding: 0 28px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.center {
|
||||
.header {
|
||||
height: 22px;
|
||||
padding: 0 8px 6px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.action-nodes {}
|
||||
}
|
||||
|
||||
:global {
|
||||
.collapse-panel-hide-underline > div {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// // 第一个容器不加顶边
|
||||
:nth-child(1 of.wrapper) {
|
||||
.header {
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -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 } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { isArray } from 'lodash-es';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
TOOL_KEY_TO_API_STATUS_KEY_MAP,
|
||||
type ToolGroupKey,
|
||||
} from '@coze-agent-ide/tool-config';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useLayoutContext } from '@coze-arch/bot-hooks';
|
||||
import { TabStatus } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { useRegisteredToolKeyConfigList } from '../../hooks/builtin/use-register-tool-key';
|
||||
import { usePreference } from '../../context/preference-context';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
/**
|
||||
* 分组容器
|
||||
* @see
|
||||
*/
|
||||
|
||||
interface IProps {
|
||||
children?: ReactNode;
|
||||
title: ReactNode;
|
||||
toolGroupKey?: ToolGroupKey;
|
||||
actionNodes?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const GroupingContainer: FC<IProps> = props => {
|
||||
const { children, title, toolGroupKey, actionNodes, className } = props;
|
||||
// 容器在页面中的展示位置,不同位置样式有区别
|
||||
const { placement } = useLayoutContext();
|
||||
|
||||
const { isReadonly } = usePreference();
|
||||
|
||||
const registeredToolKeyConfigList = useRegisteredToolKeyConfigList();
|
||||
|
||||
const registeredToolKeyListInGroup = registeredToolKeyConfigList.filter(
|
||||
toolConfig => toolConfig.toolGroupKey === toolGroupKey,
|
||||
);
|
||||
|
||||
const statusKeys = registeredToolKeyListInGroup.map(
|
||||
toolConfig => TOOL_KEY_TO_API_STATUS_KEY_MAP[toolConfig.toolKey],
|
||||
);
|
||||
|
||||
const { enableToolHiddenMode } = usePreference();
|
||||
|
||||
const tabInvisible = usePageRuntimeStore(
|
||||
useShallow(state =>
|
||||
statusKeys
|
||||
.map(_key => state.botSkillBlockCollapsibleState[_key])
|
||||
.every(status => status === TabStatus.Hide),
|
||||
),
|
||||
);
|
||||
|
||||
const getInvisible = () => {
|
||||
if (!enableToolHiddenMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isReadonly) {
|
||||
return !registeredToolKeyListInGroup.some(
|
||||
toolConfig => toolConfig.hasValidData,
|
||||
);
|
||||
}
|
||||
|
||||
return tabInvisible;
|
||||
};
|
||||
|
||||
const invisible = getInvisible();
|
||||
|
||||
if (!children || (isArray(children) && !children.length)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(styles[placement], 'coz-bg-plus', className, {
|
||||
hidden: invisible,
|
||||
[styles.wrapper || '']: !invisible,
|
||||
})}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={classnames(styles.title, 'coz-fg-secondary')}>
|
||||
{title}
|
||||
</div>
|
||||
<div className={styles['action-nodes']}>{actionNodes}</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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, type FC } from 'react';
|
||||
|
||||
import { useIsAllToolHidden } from '../../hooks/public/container/use-tool-all-hidden';
|
||||
import { useAbilityAreaContext } from '../../context/ability-area-context';
|
||||
|
||||
type IProps = Record<string, unknown>;
|
||||
|
||||
export const InvisibleToolController: FC<IProps> = () => {
|
||||
const isAllToolHidden = useIsAllToolHidden();
|
||||
|
||||
const { eventCallbacks, store } = useAbilityAreaContext();
|
||||
const { isInitialed } = store.useToolAreaStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitialed) {
|
||||
return;
|
||||
}
|
||||
eventCallbacks?.onAllToolHiddenStatusChange?.(isAllToolHidden);
|
||||
}, [isAllToolHidden, isInitialed]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { ToolGroupKey } from '@coze-agent-ide/tool-config';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
ModelFuncConfigStatus,
|
||||
ModelFuncConfigType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import {
|
||||
mergeModelFuncConfigStatus,
|
||||
useModelCapabilityConfig,
|
||||
} from '@coze-agent-ide/bot-editor-context-store';
|
||||
import { IconCozWarningCircleFillPalette } from '@coze-arch/coze-design/icons';
|
||||
import { Tag, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { abilityKey2ModelFunctionConfigType } from '../../utils/model-function-config-type-mapping';
|
||||
import { useGetToolConfig } from '../../hooks/builtin/use-get-tool-config';
|
||||
import { useAbilityConfig } from '../../hooks/builtin/use-ability-config';
|
||||
|
||||
export const TipsDisplay: FC<{
|
||||
status?: ModelFuncConfigStatus;
|
||||
modelName: string;
|
||||
showTooltip?: boolean;
|
||||
toolName?: string;
|
||||
className?: string;
|
||||
}> = ({
|
||||
status = ModelFuncConfigStatus.FullSupport,
|
||||
modelName,
|
||||
toolName,
|
||||
showTooltip = true,
|
||||
className,
|
||||
}) => {
|
||||
if (status === ModelFuncConfigStatus.NotSupport) {
|
||||
const content = (
|
||||
<Tag
|
||||
size="mini"
|
||||
color="primary"
|
||||
className={classNames('mx-2', className)}
|
||||
prefixIcon={
|
||||
<IconCozWarningCircleFillPalette className="coz-fg-hglt-red" />
|
||||
}
|
||||
>
|
||||
{I18n.t('not_supported')}
|
||||
</Tag>
|
||||
);
|
||||
if (!showTooltip) {
|
||||
return content;
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
toolName
|
||||
? I18n.t('not_supported_explain_toolName', {
|
||||
modelName,
|
||||
toolName,
|
||||
})
|
||||
: I18n.t('not_supported_explain', { modelName })
|
||||
}
|
||||
>
|
||||
{content}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
if (status === ModelFuncConfigStatus.PoorSupport) {
|
||||
const content = (
|
||||
<Tag
|
||||
size="mini"
|
||||
color="primary"
|
||||
className={classNames('mx-2', className)}
|
||||
prefixIcon={
|
||||
<IconCozWarningCircleFillPalette className="coz-fg-hglt-yellow" />
|
||||
}
|
||||
>
|
||||
{I18n.t('support_poor')}
|
||||
</Tag>
|
||||
);
|
||||
if (!showTooltip) {
|
||||
return content;
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
toolName
|
||||
? I18n.t('poorly_supported_explain_toolName', {
|
||||
modelName,
|
||||
toolName,
|
||||
})
|
||||
: I18n.t('support_poor_explain', { modelName })
|
||||
}
|
||||
>
|
||||
{content}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const TipsImpl: FC<{ configType: ModelFuncConfigType }> = ({ configType }) => {
|
||||
const modelCapabilityConfig = useModelCapabilityConfig();
|
||||
const [configStatus, modelName] = modelCapabilityConfig[configType];
|
||||
return <TipsDisplay status={configStatus} modelName={modelName} />;
|
||||
};
|
||||
|
||||
const TipsImplForKnowledge: FC<{
|
||||
configType: ModelFuncConfigType;
|
||||
toolName: string;
|
||||
}> = ({ configType, toolName }) => {
|
||||
const modelCapabilityConfig = useModelCapabilityConfig();
|
||||
const auto = useBotSkillStore(state => state.knowledge.dataSetInfo.auto);
|
||||
const [autoConfigStatus, autoModelName] =
|
||||
modelCapabilityConfig[
|
||||
auto
|
||||
? ModelFuncConfigType.KnowledgeAutoCall
|
||||
: ModelFuncConfigType.KnowledgeOnDemandCall
|
||||
];
|
||||
// 根据自动调用还是按需调用,获取另一个 status,取合并
|
||||
const [configStatus, modelName] = modelCapabilityConfig[configType];
|
||||
const mergedStatus = mergeModelFuncConfigStatus(
|
||||
autoConfigStatus,
|
||||
configStatus,
|
||||
);
|
||||
const mergedToolTittle: string[] = [];
|
||||
if (mergedStatus === configStatus) {
|
||||
mergedToolTittle.push(toolName);
|
||||
}
|
||||
if (mergedStatus === autoConfigStatus) {
|
||||
mergedToolTittle.push(
|
||||
auto
|
||||
? I18n.t('dataset_automatic_call')
|
||||
: I18n.t('dataset_on_demand_call'),
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TipsDisplay
|
||||
status={mergedStatus}
|
||||
modelName={mergedStatus === autoConfigStatus ? autoModelName : modelName}
|
||||
// 因为按需调用的提示合并到了这里,知识库显示 tips 时,需要显示具体不支持的能力(按需调用 / 知识库)
|
||||
toolName={mergedToolTittle.join(', ')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ModelCapabilityTipsImpl = () => {
|
||||
const { abilityKey } = useAbilityConfig();
|
||||
const getToolConfig = useGetToolConfig();
|
||||
const toolConfig = getToolConfig(abilityKey);
|
||||
|
||||
const configType = abilityKey
|
||||
? abilityKey2ModelFunctionConfigType(abilityKey)
|
||||
: undefined;
|
||||
|
||||
// 降低 useModelCapabilityConfig 调用频率
|
||||
if (toolConfig && configType) {
|
||||
// 知识库需要引入一个额外的判断是否是按需调用
|
||||
if (toolConfig.toolGroupKey === ToolGroupKey.KNOWLEDGE) {
|
||||
return (
|
||||
<TipsImplForKnowledge
|
||||
configType={configType}
|
||||
toolName={toolConfig.toolTitle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <TipsImpl configType={configType} />;
|
||||
}
|
||||
// 不需要渲染任何内容
|
||||
return null;
|
||||
};
|
||||
|
||||
export const ModelCapabilityTips = ModelCapabilityTipsImpl;
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
AbilityScope,
|
||||
TOOL_KEY_TO_API_STATUS_KEY_MAP,
|
||||
type ToolKey,
|
||||
} from '@coze-agent-ide/tool-config';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { ErrorBoundary } from '@coze-arch/logger';
|
||||
import { TabStatus } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { ToolContainerFallback } from '../fallbacks';
|
||||
import { useGetToolConfig } from '../../hooks/builtin/use-get-tool-config';
|
||||
import { usePreference } from '../../context/preference-context';
|
||||
import { AbilityConfigContextProvider } from '../../context/ability-config-context';
|
||||
|
||||
interface IProps {
|
||||
scope: AbilityScope;
|
||||
toolKey?: ToolKey;
|
||||
onMouseOver?: (toolKey: string | undefined) => void;
|
||||
onMouseLeave?: (toolKey: string | undefined) => void;
|
||||
}
|
||||
|
||||
export const ToolContainer: FC<PropsWithChildren<IProps>> = ({
|
||||
children,
|
||||
toolKey,
|
||||
onMouseOver,
|
||||
onMouseLeave,
|
||||
}) => {
|
||||
const { enableToolHiddenMode, isReadonly } = usePreference();
|
||||
|
||||
const toolStatus = usePageRuntimeStore(state =>
|
||||
toolKey
|
||||
? state.botSkillBlockCollapsibleState[
|
||||
TOOL_KEY_TO_API_STATUS_KEY_MAP[toolKey]
|
||||
]
|
||||
: null,
|
||||
);
|
||||
|
||||
const getToolConfig = useGetToolConfig();
|
||||
|
||||
const toolConfig = getToolConfig(toolKey);
|
||||
|
||||
const getInvisible = () => {
|
||||
if (!enableToolHiddenMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isReadonly) {
|
||||
return !toolConfig?.hasValidData;
|
||||
}
|
||||
|
||||
return toolStatus === TabStatus.Hide;
|
||||
};
|
||||
|
||||
const invisible = getInvisible();
|
||||
|
||||
const handleOnMouseEnter = (key: string) => {
|
||||
const siblingClassList = document.querySelector(`.collapse-panel-${key}`)
|
||||
?.previousElementSibling?.classList;
|
||||
// 如果找到兄弟节点则隐藏下划线
|
||||
if (siblingClassList?.contains('collapse-panel')) {
|
||||
siblingClassList.add('collapse-panel-hide-underline');
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnMouseLeave = () => {
|
||||
const className = 'collapse-panel-hide-underline';
|
||||
document
|
||||
.querySelectorAll(`.${className}`)
|
||||
.forEach(element => element.classList.remove(className));
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
hidden: invisible,
|
||||
'collapse-panel': true,
|
||||
[`collapse-panel-${toolKey}`]: true,
|
||||
})}
|
||||
onMouseEnter={() => {
|
||||
if (toolKey) {
|
||||
handleOnMouseEnter(toolKey);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
>
|
||||
<ErrorBoundary
|
||||
errorBoundaryName={`botEditorTool${toolConfig?.toolKey}`}
|
||||
FallbackComponent={() => (
|
||||
<ToolContainerFallback toolTitle={toolConfig?.toolTitle} />
|
||||
)}
|
||||
>
|
||||
<AbilityConfigContextProvider
|
||||
abilityKey={toolKey}
|
||||
scope={AbilityScope.TOOL}
|
||||
>
|
||||
{children}
|
||||
</AbilityConfigContextProvider>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
|
||||
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
|
||||
|
||||
.common-svg-icon(@size: 14px) {
|
||||
>svg {
|
||||
width: @size;
|
||||
height: @size;
|
||||
|
||||
@apply coz-fg-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.content-block {
|
||||
width: 100%;
|
||||
// border-radius: 8px;
|
||||
// background-color: #fff;
|
||||
// box-shadow: (@common-box-shadow);
|
||||
// margin-bottom: 4px;
|
||||
border-bottom: 1px solid theme('colors.stroke.5');
|
||||
|
||||
&.isOpen {
|
||||
border-bottom: 1px solid theme('colors.stroke.5') !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
&.left {
|
||||
.header-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.content-old {
|
||||
padding: 4px 28px;
|
||||
}
|
||||
}
|
||||
|
||||
&.center {
|
||||
.header-content {
|
||||
padding: 0 7px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 4px 0 16px;
|
||||
}
|
||||
|
||||
.content-old {
|
||||
padding: 8px 8px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
&.collapsible {
|
||||
&:hover {
|
||||
@apply coz-mg-secondary-hovered;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@apply coz-mg-secondary-pressed;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply coz-fg-primary;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.header-icon-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-right: 8px;
|
||||
padding: 1px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
:global {
|
||||
.semi-icon {
|
||||
svg {
|
||||
font-size: 14px;
|
||||
|
||||
@apply coz-fg-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: flex;
|
||||
margin-right: 8px;
|
||||
|
||||
>img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex: 1 1;
|
||||
align-items: center;
|
||||
|
||||
height: 100%;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
|
||||
|
||||
.icon {
|
||||
margin-left: 8px;
|
||||
.common-svg-icon(16px)
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.setting {
|
||||
// margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
// border-top: 1px solid #efefef;
|
||||
overflow: auto;
|
||||
height: calc(100% - 48px);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-collapsible-wrapper {
|
||||
padding-left: 0 !important;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.semi-button {
|
||||
svg {
|
||||
@apply coz-fg-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useMemo,
|
||||
useCallback,
|
||||
type ForwardedRef,
|
||||
} from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { isBoolean } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { Collapsible } from '@coze-arch/coze-design';
|
||||
import {
|
||||
type OpenBlockEvent,
|
||||
handleEvent,
|
||||
removeEvent,
|
||||
skillKeyToApiStatusKeyTransformer,
|
||||
} from '@coze-arch/bot-utils';
|
||||
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
|
||||
import { Image } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
IconInfo,
|
||||
IconChevronRight,
|
||||
IconChevronDown,
|
||||
} from '@coze-arch/bot-icons';
|
||||
import { useLayoutContext } from '@coze-arch/bot-hooks';
|
||||
import { TabStatus } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import { ToolPopover } from '../tool-popover';
|
||||
import { ModelCapabilityTips } from '../model-capability-tips';
|
||||
import { toolKeyToApiStatusKeyTransformer } from '../../utils/tool-content-block';
|
||||
import { EventCenterEventName } from '../../typings/scoped-events';
|
||||
import { type IToggleContentBlockEventParams } from '../../typings/event';
|
||||
import { useRegisterCollapse } from '../../hooks/tool/use-tool-toggle-collapse';
|
||||
import { useEvent } from '../../hooks/event/use-event';
|
||||
import { useAbilityConfig } from '../../hooks/builtin/use-ability-config';
|
||||
import { openBlockEventToToolKey } from '../../constants/tool-content-block';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ToolContentBlockProps {
|
||||
contentClassName?: string;
|
||||
header?: ReactNode;
|
||||
icon?: string;
|
||||
actionButton?: ReactNode;
|
||||
tooltip?: ReactNode;
|
||||
setting?: ReactNode;
|
||||
maxContentHeight?: number;
|
||||
showBottomBorder?: boolean;
|
||||
showBorderTopRadius?: boolean;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
collapsible?: boolean;
|
||||
defaultExpand?: boolean;
|
||||
onRef?: ForwardedRef<ToolContentRef>;
|
||||
/**
|
||||
* @deprecated tool 插件化改造后无需传入 (如果保留老式event则需要传入)
|
||||
*/
|
||||
blockEventName?: OpenBlockEvent;
|
||||
tooltipType?: 'tooltip' | 'popOver';
|
||||
childNodeWrapClassName?: string;
|
||||
headerClassName?: string;
|
||||
}
|
||||
|
||||
interface ToolContentRef {
|
||||
setOpen?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
/* eslint @coze-arch/max-line-per-function: ["error", {"max": 250}] */
|
||||
export const ToolContentBlock: React.FC<
|
||||
PropsWithChildren<ToolContentBlockProps>
|
||||
> = ({
|
||||
children,
|
||||
icon,
|
||||
header,
|
||||
actionButton,
|
||||
maxContentHeight,
|
||||
tooltip,
|
||||
tooltipType = 'popOver',
|
||||
setting,
|
||||
className,
|
||||
style,
|
||||
collapsible = true,
|
||||
defaultExpand,
|
||||
onRef,
|
||||
blockEventName,
|
||||
childNodeWrapClassName,
|
||||
headerClassName,
|
||||
}) => {
|
||||
/** 后续长期使用的ToolKey */
|
||||
const { abilityKey } = useAbilityConfig();
|
||||
|
||||
const { registerCollapse } = useRegisterCollapse();
|
||||
|
||||
useEffect(() => {
|
||||
if (!abilityKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
return registerCollapse(isExpand => setIsOpen(isExpand), abilityKey);
|
||||
}, [abilityKey]);
|
||||
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
const { botId } = useBotInfoStore(
|
||||
useShallow(store => ({
|
||||
botId: store.botId,
|
||||
})),
|
||||
);
|
||||
const { editable, setBotSkillBlockCollapsibleState } = usePageRuntimeStore(
|
||||
useShallow(store => ({
|
||||
editable: store.editable,
|
||||
setBotSkillBlockCollapsibleState: store.setBotSkillBlockCollapsibleState,
|
||||
})),
|
||||
);
|
||||
|
||||
// 容器在页面中的展示位置,不同位置样式有区别
|
||||
const { placement } = useLayoutContext();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const initialized = useRef<boolean>(false);
|
||||
const childNode = (
|
||||
<div
|
||||
className={classNames(s.content, childNodeWrapClassName)}
|
||||
style={{ maxHeight: maxContentHeight ? maxContentHeight : 'unset' }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
const setOpen = ($isOpen: boolean) => {
|
||||
setIsOpen($isOpen);
|
||||
// 记录用户使用状态
|
||||
if (editable && !isReadonly && (abilityKey || blockEventName)) {
|
||||
if (blockEventName) {
|
||||
const blockKey = openBlockEventToToolKey[blockEventName];
|
||||
|
||||
blockKey &&
|
||||
setBotSkillBlockCollapsibleState({
|
||||
[skillKeyToApiStatusKeyTransformer(blockKey)]: $isOpen
|
||||
? TabStatus.Open
|
||||
: TabStatus.Close,
|
||||
});
|
||||
} else if (abilityKey) {
|
||||
setBotSkillBlockCollapsibleState({
|
||||
[toolKeyToApiStatusKeyTransformer(abilityKey)]: $isOpen
|
||||
? TabStatus.Open
|
||||
: TabStatus.Close,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 尚未完成初始化时如果用户手动展开/收起,则马上完成初始化
|
||||
if (!initialized.current) {
|
||||
initialized.current = true;
|
||||
}
|
||||
};
|
||||
useImperativeHandle(onRef, () => ({
|
||||
setOpen,
|
||||
}));
|
||||
|
||||
const onEvent = useCallback(() => {
|
||||
setOpen(true);
|
||||
}, [
|
||||
blockEventName,
|
||||
botId,
|
||||
editable,
|
||||
isReadonly,
|
||||
openBlockEventToToolKey,
|
||||
abilityKey,
|
||||
]);
|
||||
|
||||
const onEventNew = useCallback(
|
||||
({ abilityKey: _abilityKey, isExpand }: IToggleContentBlockEventParams) => {
|
||||
if (_abilityKey === abilityKey) {
|
||||
setOpen(isExpand);
|
||||
}
|
||||
},
|
||||
[abilityKey, setOpen],
|
||||
);
|
||||
|
||||
const { on } = useEvent();
|
||||
|
||||
useEffect(() => {
|
||||
blockEventName && handleEvent(blockEventName, onEvent);
|
||||
const offEvent =
|
||||
abilityKey &&
|
||||
on<IToggleContentBlockEventParams>(
|
||||
EventCenterEventName.ToggleContentBlock,
|
||||
onEventNew,
|
||||
);
|
||||
return () => {
|
||||
blockEventName && removeEvent(blockEventName, onEvent);
|
||||
offEvent?.();
|
||||
};
|
||||
}, [onEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
// 传入默认值之后才能初始化
|
||||
if (isBoolean(defaultExpand)) {
|
||||
// 初始化完成后忽略 defaultExpand 变化
|
||||
if (!initialized.current) {
|
||||
setIsOpen(defaultExpand);
|
||||
initialized.current = true;
|
||||
}
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
initialized.current = false;
|
||||
}
|
||||
}, [defaultExpand]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
// 初始化成功之后才能开始渲染 Collapsible 组件
|
||||
if (!initialized.current) {
|
||||
return null;
|
||||
}
|
||||
if (collapsible) {
|
||||
return (
|
||||
<Collapsible keepDOM isOpen={isOpen || !collapsible}>
|
||||
{childNode}
|
||||
</Collapsible>
|
||||
);
|
||||
} else {
|
||||
return childNode;
|
||||
}
|
||||
}, [collapsible, isOpen, childNode]);
|
||||
|
||||
const onToggle = () => {
|
||||
if (collapsible) {
|
||||
setOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
const isFromStore = usePageRuntimeStore(
|
||||
state => state.pageFrom === BotPageFromEnum.Store,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
s['content-block'],
|
||||
s[placement],
|
||||
{
|
||||
[s.isOpen || '']: isOpen,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
<header
|
||||
className={classNames(s['header-content'], headerClassName, {
|
||||
[s.collapsible || '']: collapsible,
|
||||
})}
|
||||
>
|
||||
<div className={s.header} onClick={onToggle}>
|
||||
{collapsible ? (
|
||||
<div className={s['header-icon-arrow']}>
|
||||
{isOpen ? <IconChevronDown /> : <IconChevronRight />}
|
||||
</div>
|
||||
) : null}
|
||||
{icon ? (
|
||||
<Image preview={false} className={s['header-icon']} src={icon} />
|
||||
) : null}
|
||||
<div className="shrink-0">{header}</div>
|
||||
{tooltip && tooltipType === 'popOver' ? (
|
||||
<ToolPopover
|
||||
content={<div onClick={e => e.stopPropagation()}>{tooltip}</div>}
|
||||
>
|
||||
<IconInfo className={s.icon} />
|
||||
</ToolPopover>
|
||||
) : null}
|
||||
{tooltip && tooltipType === 'tooltip' ? (
|
||||
<ToolTooltip content={<div>{tooltip}</div>}>
|
||||
<IconInfo className={s.icon} />
|
||||
</ToolTooltip>
|
||||
) : null}
|
||||
{!isFromStore ? <ModelCapabilityTips /> : null}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
s['action-button'],
|
||||
'grid grid-flow-row gap-x-[2px]',
|
||||
)}
|
||||
>
|
||||
{!!setting && <div className={s.setting}>{setting}</div>}
|
||||
{actionButton}
|
||||
</div>
|
||||
</header>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozCardPencil } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionCardProps = ComponentProps<typeof ToolItemAction>;
|
||||
|
||||
export const ToolItemActionCard: FC<ToolItemActionCardProps> = props => {
|
||||
const { disabled } = props;
|
||||
|
||||
return (
|
||||
<ToolItemAction {...props}>
|
||||
<IconCozCardPencil
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozCopy } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionCopyProps = ComponentProps<typeof ToolItemAction>;
|
||||
|
||||
export const ToolItemActionCopy: FC<ToolItemActionCopyProps> = props => {
|
||||
const { disabled } = props;
|
||||
return (
|
||||
<ToolItemAction {...props}>
|
||||
<IconCozCopy
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionDeleteProps = ComponentProps<typeof ToolItemAction>;
|
||||
|
||||
export const ToolItemActionDelete: FC<ToolItemActionDeleteProps> = props => {
|
||||
const { disabled } = props;
|
||||
return (
|
||||
<ToolItemAction {...props}>
|
||||
<IconCozTrashCan
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -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 { type ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozHamburger } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionEditProps = ComponentProps<typeof ToolItemAction> & {
|
||||
isDragging: boolean;
|
||||
};
|
||||
|
||||
export const ToolItemActionDrag: FC<ToolItemActionEditProps> = props => {
|
||||
const { disabled, isDragging } = props;
|
||||
return (
|
||||
<ToolItemAction hoverStyle={false} {...props}>
|
||||
<IconCozHamburger
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
'cursor-grab': !isDragging,
|
||||
'cursor-grabbing': isDragging,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozEdit } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionEditProps = ComponentProps<typeof ToolItemAction>;
|
||||
|
||||
export const ToolItemActionEdit: FC<ToolItemActionEditProps> = props => {
|
||||
const { disabled } = props;
|
||||
return (
|
||||
<ToolItemAction {...props}>
|
||||
<IconCozEdit
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionInfoProps = ComponentProps<typeof ToolItemAction>;
|
||||
|
||||
export const ToolItemActionInfo: FC<ToolItemActionInfoProps> = props => {
|
||||
const { disabled } = props;
|
||||
|
||||
return (
|
||||
<ToolItemAction {...props}>
|
||||
<IconCozInfoCircle
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 ComponentProps, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozSetting } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemAction } from '..';
|
||||
|
||||
type ToolItemActionSettingProps = ComponentProps<typeof ToolItemAction>;
|
||||
|
||||
export const ToolItemActionSetting: FC<ToolItemActionSettingProps> = props => {
|
||||
const { disabled } = props;
|
||||
return (
|
||||
<ToolItemAction {...props}>
|
||||
<IconCozSetting
|
||||
className={classNames('text-sm', {
|
||||
'coz-fg-secondary': !disabled,
|
||||
'coz-fg-dim': disabled,
|
||||
})}
|
||||
/>
|
||||
</ToolItemAction>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 PropsWithChildren, type MouseEventHandler } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import { type ToolButtonCommonProps } from '../../typings/button';
|
||||
|
||||
type ToolItemActionProps = ToolButtonCommonProps & {
|
||||
/** 是否展示hover样式 **/
|
||||
hoverStyle?: boolean;
|
||||
};
|
||||
|
||||
export const ToolItemAction: FC<PropsWithChildren<ToolItemActionProps>> = ({
|
||||
children,
|
||||
disabled,
|
||||
tooltips,
|
||||
onClick,
|
||||
hoverStyle = true,
|
||||
...restProps
|
||||
}) => {
|
||||
const handleClick: MouseEventHandler<HTMLDivElement> = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolTooltip content={tooltips} disableFocusListener={disabled}>
|
||||
<div
|
||||
className={classNames(
|
||||
'w-[24px] h-[24px] flex justify-center items-center rounded-mini',
|
||||
{
|
||||
'hover:coz-mg-secondary-hovered active:coz-mg-secondary-pressed cursor-pointer':
|
||||
!disabled && hoverStyle,
|
||||
},
|
||||
{
|
||||
'coz-fg-dim hover:coz-fg-dim active:coz-fg-dim cursor-not-allowed':
|
||||
disabled,
|
||||
},
|
||||
)}
|
||||
onClick={disabled ? undefined : handleClick}
|
||||
data-testid={restProps['data-testid']}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ToolTooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozCard } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemIcon } from '..';
|
||||
|
||||
interface ToolItemIconCardProps {
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
export const ToolItemIconCard: FC<ToolItemIconCardProps> = ({ isError }) => (
|
||||
<ToolItemIcon>
|
||||
<IconCozCard
|
||||
className={classNames('text-base', {
|
||||
'coz-fg-secondary': !isError,
|
||||
'coz-fg-hglt-yellow': isError,
|
||||
})}
|
||||
/>
|
||||
</ToolItemIcon>
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemIcon } from '..';
|
||||
|
||||
export const ToolItemIconInfo: FC = () => (
|
||||
<ToolItemIcon>
|
||||
<IconCozInfoCircle className="text-sm coz-fg-secondary" />
|
||||
</ToolItemIcon>
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { IconCozPeople } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ToolItemIcon } from '..';
|
||||
|
||||
export const ToolItemIconPeople = () => (
|
||||
<ToolItemIcon size="small">
|
||||
<IconCozPeople className="text-base coz-fg-secondary" />
|
||||
</ToolItemIcon>
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const ToolItemIcon: FC<PropsWithChildren & { size?: 'small' }> = ({
|
||||
children,
|
||||
size,
|
||||
}) => (
|
||||
<div
|
||||
className={classNames('flex justify-center items-center cursor-pointer', {
|
||||
'w-[24px] h-[24px]': size !== 'small',
|
||||
'w-[16px] h-[16px]': size === 'small',
|
||||
})}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
export const ToolItemList: FC<PropsWithChildren> = ({ children }) => (
|
||||
<div className="grid grid-flow-row gap-y-[4px]">{children}</div>
|
||||
);
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 ReactNode, type FC, type ChangeEvent } from 'react';
|
||||
|
||||
import { Switch } from '@coze-arch/coze-design';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import { ToolItemIconInfo } from '../tool-item-icon/icons/tool-item-icon-info';
|
||||
|
||||
interface ToolItemSwitchProps {
|
||||
title: string;
|
||||
tooltips?: ReactNode;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
onChange?:
|
||||
| ((checked: boolean, e: ChangeEvent<HTMLInputElement>) => void)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export const ToolItemSwitch: FC<ToolItemSwitchProps> = ({
|
||||
title,
|
||||
tooltips,
|
||||
checked,
|
||||
disabled,
|
||||
onChange,
|
||||
}) => (
|
||||
<div className="w-full px-[12px] py-[10px] coz-bg-max flex flex-row items-center rounded-[8px]">
|
||||
<div className="flex flex-row items-center flex-1 min-w-0">
|
||||
<p className="coz-fg-primary text-[14px] leading-[20px] mr-[4px]">
|
||||
{title}
|
||||
</p>
|
||||
<ToolTooltip content={tooltips}>
|
||||
<div>
|
||||
<ToolItemIconInfo />
|
||||
</div>
|
||||
</ToolTooltip>
|
||||
</div>
|
||||
<Switch
|
||||
size="mini"
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
.actions-large * svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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 react-hooks/rules-of-hooks */
|
||||
import { type ReactNode, useRef, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useHover } from 'ahooks';
|
||||
import { Divider } from '@coze-arch/coze-design';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import {
|
||||
ToolItemContextProvider,
|
||||
useToolItemContext,
|
||||
} from '../../context/tool-item-context';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ToolItemProps {
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* tags
|
||||
*/
|
||||
tags?: ReactNode;
|
||||
/**
|
||||
* avatar
|
||||
*/
|
||||
avatar: string;
|
||||
/**
|
||||
* Actions区域
|
||||
*/
|
||||
actions?: ReactNode;
|
||||
/**
|
||||
* Icon展示区域
|
||||
*/
|
||||
icons?: ReactNode;
|
||||
/**
|
||||
* 禁用状态
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* tooltips
|
||||
*/
|
||||
tooltips?: ReactNode;
|
||||
/**
|
||||
* 点击卡片的回调
|
||||
*/
|
||||
onClick?: () => void;
|
||||
|
||||
// 尺寸 - 适配 workflow-as-agent 模式下的大号卡片
|
||||
size?: 'default' | 'large';
|
||||
|
||||
avatarStyle?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const _ToolItem: FC<ToolItemProps> = ({
|
||||
title,
|
||||
description,
|
||||
avatar,
|
||||
actions,
|
||||
icons,
|
||||
onClick,
|
||||
tooltips,
|
||||
tags,
|
||||
disabled,
|
||||
size = 'default',
|
||||
avatarStyle,
|
||||
}) => {
|
||||
const containerRef = useRef(null);
|
||||
const isHovering = useHover(containerRef);
|
||||
|
||||
const { isForceShowAction } = useToolItemContext();
|
||||
|
||||
const isShowAction = isHovering || isForceShowAction;
|
||||
|
||||
return (
|
||||
<ToolTooltip content={tooltips} position="top">
|
||||
<div
|
||||
data-testid={'bot.editor.tool.added-tool'}
|
||||
ref={containerRef}
|
||||
className={classNames(
|
||||
'w-full flex flex-row items-center coz-bg-max rounded-[8px]',
|
||||
{
|
||||
default: 'min-h-[56px] px-[8px] py-[10px]',
|
||||
large: 'min-h-[102px] px-[24px] py-[16px]',
|
||||
}[size],
|
||||
{
|
||||
'!coz-mg-secondary-hovered': isHovering,
|
||||
'cursor-pointer': Boolean(onClick),
|
||||
'cursor-default': !onClick,
|
||||
'cursor-not-allowed': disabled,
|
||||
},
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-row flex-1 min-w-[0px] justify-center items-center',
|
||||
{
|
||||
'opacity-30': disabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{avatar ? (
|
||||
<img
|
||||
src={avatar}
|
||||
style={avatarStyle}
|
||||
className={classNames(
|
||||
{
|
||||
default: 'w-[36px] h-[36px] rounded-[5px]',
|
||||
large: 'w-[48px] h-[48px] rounded-[6px]',
|
||||
}[size],
|
||||
'overflow-hidden',
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className={classNames(
|
||||
{
|
||||
default: 'ml-[8px]',
|
||||
large: 'ml-[12px]',
|
||||
}[size],
|
||||
'flex flex-col flex-1 min-w-[0px] w-0',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row items-center overflow-hidden">
|
||||
<p
|
||||
className={classNames(
|
||||
{
|
||||
default: 'text-[14px] leading-[20px]',
|
||||
large: 'text-[20px] leading-[28px]',
|
||||
}[size],
|
||||
'coz-fg-primary truncate flex-1 font-medium',
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</p>
|
||||
{!isShowAction || disabled ? (
|
||||
<div className="justify-self-end grid grid-flow-col gap-x-[2px]">
|
||||
{icons}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<p
|
||||
className={classNames(
|
||||
{
|
||||
default: 'text-[12px] leading-[16px] truncate',
|
||||
large:
|
||||
'text-[14px] leading-[20px] mt-[2px] text-clip line-clamp-2',
|
||||
}[size],
|
||||
'coz-fg-secondary',
|
||||
)}
|
||||
>
|
||||
{tags ? (
|
||||
<>
|
||||
{tags}
|
||||
<Divider layout="vertical" margin="4px" className="h-[9px]" />
|
||||
</>
|
||||
) : null}
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
{
|
||||
default: 'grid grid-flow-col gap-x-[2px]',
|
||||
large: 'flex gap-[4px] ml-[12px]',
|
||||
}[size],
|
||||
size === 'large' && s['actions-large'],
|
||||
{
|
||||
hidden: !isShowAction,
|
||||
'opacity-30': disabled,
|
||||
},
|
||||
)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{actions}
|
||||
</div>
|
||||
</div>
|
||||
</ToolTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToolItem: FC<ToolItemProps> = props => (
|
||||
<ToolItemContextProvider>
|
||||
<_ToolItem {...props} />
|
||||
</ToolItemContextProvider>
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
.tool-menu-dropdown-menu {
|
||||
width: 210px;
|
||||
max-height: 590px;
|
||||
}
|
||||
|
||||
@media screen and (max-height: 750px) {
|
||||
.tool-menu-dropdown-menu {
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.dropdown-item-text {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import {
|
||||
TOOL_GROUP_CONFIG,
|
||||
TOOL_KEY_TO_API_STATUS_KEY_MAP,
|
||||
type ToolKey,
|
||||
} from '@coze-agent-ide/tool-config';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Menu, Checkbox } from '@coze-arch/coze-design';
|
||||
import { TabStatus } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { ToolTooltip } from '../tool-tooltip';
|
||||
import { useRegisteredToolKeyConfigList } from '../../hooks/builtin/use-register-tool-key';
|
||||
import { useRegisteredToolGroupList } from '../../hooks/builtin/use-register-tool-group';
|
||||
import { usePreference } from '../../context/preference-context';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
type IProps = Record<string, unknown>;
|
||||
|
||||
export const ToolMenuDropdownMenu: FC<IProps> = () => {
|
||||
const registeredToolKeyConfigList = useRegisteredToolKeyConfigList();
|
||||
const registeredToolGroupList = useRegisteredToolGroupList();
|
||||
const { botSkillBlockCollapsible, setBotSkillBlockCollapsibleState } =
|
||||
usePageRuntimeStore(
|
||||
useShallow(state => ({
|
||||
botSkillBlockCollapsible: state.botSkillBlockCollapsibleState,
|
||||
setBotSkillBlockCollapsibleState:
|
||||
state.setBotSkillBlockCollapsibleState,
|
||||
})),
|
||||
);
|
||||
|
||||
const { isReadonly } = usePreference();
|
||||
|
||||
if (!registeredToolKeyConfigList.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toolGroupKeyList = Object.keys(TOOL_GROUP_CONFIG);
|
||||
|
||||
const menuConfig = toolGroupKeyList
|
||||
.map(toolGroupKey => ({
|
||||
toolGroupKey,
|
||||
toolGroupTitle: registeredToolGroupList.find(
|
||||
toolGroupConfig => toolGroupConfig.toolGroupKey === toolGroupKey,
|
||||
)?.groupTitle,
|
||||
toolList: registeredToolKeyConfigList
|
||||
.filter(toolConfig => toolConfig.toolGroupKey === toolGroupKey)
|
||||
.map(toolConfig => toolConfig),
|
||||
}))
|
||||
.filter(toolGroup => toolGroup.toolList.length);
|
||||
|
||||
const getToolStatus = (toolKey: ToolKey) =>
|
||||
botSkillBlockCollapsible[TOOL_KEY_TO_API_STATUS_KEY_MAP[toolKey]];
|
||||
|
||||
const handleClick = (toolKey: ToolKey, currentStatus?: TabStatus) => {
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBotSkillBlockCollapsibleState({
|
||||
[TOOL_KEY_TO_API_STATUS_KEY_MAP[toolKey]]:
|
||||
currentStatus === TabStatus.Hide ? TabStatus.Default : TabStatus.Hide,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['tool-menu-dropdown-menu']}>
|
||||
<Menu.SubMenu mode="menu">
|
||||
{menuConfig.map((toolGroup, groupIdx) => (
|
||||
<div key={toolGroup.toolGroupKey}>
|
||||
<Menu.Title style={{ paddingLeft: '32px' }}>
|
||||
{toolGroup.toolGroupTitle}
|
||||
</Menu.Title>
|
||||
{toolGroup.toolList.map(tool => {
|
||||
const toolStatus = getToolStatus(tool.toolKey);
|
||||
return (
|
||||
<ToolTooltip
|
||||
content={
|
||||
tool.hasValidData
|
||||
? I18n.t('modules_menu_guide_warning')
|
||||
: undefined
|
||||
}
|
||||
position="right"
|
||||
key={`tooltips-${tool.toolKey}`}
|
||||
>
|
||||
<Menu.Item
|
||||
style={{ display: 'block' }}
|
||||
key={tool.toolKey}
|
||||
disabled={tool.hasValidData}
|
||||
onClick={() => handleClick(tool.toolKey, toolStatus)}
|
||||
>
|
||||
<div className={styles['dropdown-item-container']}>
|
||||
<Checkbox
|
||||
checked={toolStatus !== TabStatus.Hide}
|
||||
disabled={tool.hasValidData}
|
||||
/>
|
||||
<span className={styles['dropdown-item-text']}>
|
||||
{tool.toolTitle}
|
||||
</span>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</ToolTooltip>
|
||||
);
|
||||
})}
|
||||
{groupIdx < menuConfig.length - 1 ? <Menu.Divider /> : null}
|
||||
</div>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 { useState, type FC } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
import { useCommonConfigStore } from '@coze-foundation/global-store';
|
||||
|
||||
import guideFallbackImage from './images/guide-fallback.png';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const GuidePopover: FC<IProps> = ({ onClose }) => {
|
||||
const [fallbackUrl, setFallbackUrl] = useState('');
|
||||
|
||||
const botIdeGuideVideoUrl = useCommonConfigStore(
|
||||
state => state.commonConfigs.botIdeGuideVideoUrl,
|
||||
);
|
||||
return (
|
||||
<div className={styles.guide}>
|
||||
<p className={classnames(styles['guide-text'], 'coz-fg-primary')}>
|
||||
{I18n.t('modules_menu_guide')}
|
||||
</p>
|
||||
{fallbackUrl ? (
|
||||
<img src={fallbackUrl} className={styles['guide-image']} />
|
||||
) : (
|
||||
<video
|
||||
width={380}
|
||||
height={238}
|
||||
src={botIdeGuideVideoUrl}
|
||||
poster={guideFallbackImage}
|
||||
data-object-fit
|
||||
muted
|
||||
data-autoplay
|
||||
loop={true}
|
||||
autoPlay={true}
|
||||
onError={() => setFallbackUrl(guideFallbackImage)}
|
||||
className={styles['guide-video']}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className={styles['guide-button']}
|
||||
type="primary"
|
||||
theme="solid"
|
||||
onClick={onClose}
|
||||
>
|
||||
{I18n.t('modules_menu_guide_gotit')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,29 @@
|
||||
.guide-popover {}
|
||||
|
||||
.guide {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.guide-text {
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.guide-image {
|
||||
width: 380px;
|
||||
margin: 0 0 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.guide-video {
|
||||
overflow: hidden;
|
||||
width: 380px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.guide-button {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -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 { type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Menu, Popover, IconButton } from '@coze-arch/coze-design';
|
||||
import { IconMenu } from '@coze-arch/bot-icons';
|
||||
|
||||
import { ToolMenuDropdownMenu } from '../tool-menu-dropdown-menu';
|
||||
import { GuidePopover } from './guide-popover';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
visible?: boolean;
|
||||
newbieGuideVisible?: boolean;
|
||||
onNewbieGuidePopoverClose?: () => void;
|
||||
rePosKey: number;
|
||||
}
|
||||
|
||||
export const ToolMenu: FC<IProps> = ({
|
||||
visible = true,
|
||||
onNewbieGuidePopoverClose,
|
||||
newbieGuideVisible,
|
||||
rePosKey,
|
||||
}) => {
|
||||
const onButtonClick = () => {
|
||||
if (!newbieGuideVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
onNewbieGuidePopoverClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
hidden: !visible,
|
||||
[s['guide-popover'] || '']: true,
|
||||
})}
|
||||
>
|
||||
<Popover
|
||||
content={<GuidePopover onClose={onNewbieGuidePopoverClose} />}
|
||||
trigger="custom"
|
||||
visible={newbieGuideVisible && visible}
|
||||
showArrow
|
||||
onClickOutSide={onButtonClick}
|
||||
>
|
||||
<Menu
|
||||
trigger="click"
|
||||
position="bottomRight"
|
||||
render={<ToolMenuDropdownMenu />}
|
||||
rePosKey={rePosKey}
|
||||
>
|
||||
<IconButton
|
||||
size="default"
|
||||
color="secondary"
|
||||
icon={<IconMenu className="text-[16px]" />}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
.tool-popover {
|
||||
color: rgba(255, 255, 255, 79%)!important;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { Popover, type PopoverProps } from '@coze-arch/coze-design';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
type ToolPopoverProps = {
|
||||
children: JSX.Element;
|
||||
hideToolTip?: boolean;
|
||||
} & PopoverProps;
|
||||
|
||||
export const ToolPopover: FC<ToolPopoverProps> = props => {
|
||||
const { content, children, hideToolTip, ...restProps } = props;
|
||||
return (
|
||||
<Popover
|
||||
showArrow
|
||||
position="top"
|
||||
className={s['tool-popover']}
|
||||
trigger={hideToolTip ? 'custom' : 'hover'}
|
||||
visible={hideToolTip ? false : undefined}
|
||||
content={content}
|
||||
style={{ backgroundColor: '#363D4D', padding: 8 }}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
.tool-tooltips {
|
||||
color: rgba(255, 255, 255, 79%)!important;
|
||||
background-color: #363D4D !important;
|
||||
|
||||
:global {
|
||||
.semi-tooltip-icon-arrow {
|
||||
color: #363D4D !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { Tooltip, type TooltipProps } from '@coze-arch/coze-design';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
type ToolTooltipsProps = {
|
||||
children: JSX.Element;
|
||||
hideToolTip?: boolean;
|
||||
} & TooltipProps;
|
||||
|
||||
export const ToolTooltip: FC<ToolTooltipsProps> = props => {
|
||||
const { content, children, hideToolTip, ...restProps } = props;
|
||||
return content ? (
|
||||
<Tooltip
|
||||
trigger={hideToolTip ? 'custom' : 'hover'}
|
||||
visible={hideToolTip ? false : undefined}
|
||||
content={content}
|
||||
className={s['tool-tooltips']}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 { Children, useMemo, type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { AbilityScope } from '@coze-agent-ide/tool-config';
|
||||
import { Spin } from '@coze-arch/coze-design';
|
||||
import { PlacementEnum, useLayoutContext } from '@coze-arch/bot-hooks';
|
||||
|
||||
import { ToolContainer } from '../tool-container';
|
||||
import { useSubscribeToolStore } from '../../hooks/public/store/use-tool-store';
|
||||
import { useRegisterToolKey } from '../../hooks/builtin/use-register-tool-key';
|
||||
import { useRegisterToolGroup } from '../../hooks/builtin/use-register-tool-group';
|
||||
import { useAbilityAreaContext } from '../../context/ability-area-context';
|
||||
|
||||
type IProps = Record<string, unknown>;
|
||||
|
||||
export const ToolView: FC<PropsWithChildren<IProps>> = ({ children }) => {
|
||||
const {
|
||||
store: { useToolAreaStore },
|
||||
} = useAbilityAreaContext();
|
||||
const registerToolKey = useRegisterToolKey();
|
||||
const registerToolGroup = useRegisterToolGroup();
|
||||
useSubscribeToolStore(AbilityScope.TOOL);
|
||||
|
||||
const { isInitialed, isModeSwitching } = useToolAreaStore(state => ({
|
||||
isInitialed: state.isInitialed,
|
||||
isModeSwitching: state.isModeSwitching,
|
||||
}));
|
||||
|
||||
const { placement } = useLayoutContext();
|
||||
|
||||
const newChildren = useMemo(() => {
|
||||
const allChildren = Array.isArray(children) ? children : [children];
|
||||
|
||||
if (!isInitialed) {
|
||||
return isModeSwitching ? null : (
|
||||
<div
|
||||
className={classNames('w-full flex items-center justify-center', {
|
||||
'h-auto': placement === PlacementEnum.LEFT,
|
||||
'h-full': placement === PlacementEnum.CENTER,
|
||||
})}
|
||||
>
|
||||
<Spin spinning />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 遍历 GroupingContainer 的所有子元素
|
||||
return Children.map(allChildren, childLevel1 => {
|
||||
if (Children.count(childLevel1?.props?.children)) {
|
||||
return {
|
||||
...childLevel1,
|
||||
props: {
|
||||
...childLevel1.props,
|
||||
// 子元素都套一层 ToolContainer
|
||||
children: Children.map(childLevel1.props.children, childLevel2 => {
|
||||
const { toolKey, title: toolTitle } = childLevel2?.props ?? {};
|
||||
const { toolGroupKey, title: groupTitle } =
|
||||
childLevel1?.props ?? {};
|
||||
|
||||
if (!toolKey || !toolTitle || !toolGroupKey || !groupTitle) {
|
||||
return childLevel2;
|
||||
}
|
||||
|
||||
registerToolGroup({
|
||||
toolGroupKey,
|
||||
groupTitle,
|
||||
});
|
||||
|
||||
registerToolKey({
|
||||
toolKey,
|
||||
toolGroupKey,
|
||||
toolTitle,
|
||||
hasValidData: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<ToolContainer scope={AbilityScope.TOOL} toolKey={toolKey}>
|
||||
{childLevel2}
|
||||
</ToolContainer>
|
||||
);
|
||||
}),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return childLevel1;
|
||||
}
|
||||
});
|
||||
}, [children, isInitialed]);
|
||||
|
||||
return newChildren;
|
||||
};
|
||||
Reference in New Issue
Block a user