feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,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>
);
};

View File

@@ -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>
);
};

View File

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

View File

@@ -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;
}
}
}
}
}

View File

@@ -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>
);
},
);

View File

@@ -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);
}

View File

@@ -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>
);
});

View File

@@ -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}
/>
);
};

View File

@@ -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%));
}
}

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { 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>
);
};

View File

@@ -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;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, type ReactNode } 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>
);
};

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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>
);
};

View File

@@ -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;
}
}
}
}

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { 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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, type PropsWithChildren } from 'react';
export const ToolItemList: FC<PropsWithChildren> = ({ children }) => (
<div className="grid grid-flow-row gap-y-[4px]">{children}</div>
);

View File

@@ -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>
);

View File

@@ -0,0 +1,3 @@
.actions-large * svg {
font-size: 16px;
}

View File

@@ -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>
);

View File

@@ -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;
}

View File

@@ -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>
);
};

View File

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

View File

@@ -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%;
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { 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>
);
};

View File

@@ -0,0 +1,3 @@
.tool-popover {
color: rgba(255, 255, 255, 79%)!important;
}

View File

@@ -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>
);
};

View File

@@ -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;
}
}
}

View File

@@ -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}</>
);
};

View File

@@ -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;
};

View File

@@ -0,0 +1,36 @@
/*
* 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 { SkillKeyEnum } from '@coze-agent-ide/tool-config';
import { OpenBlockEvent } from '@coze-arch/bot-utils';
type IOpenBlockEventToToolKey = Record<string, SkillKeyEnum>;
// `模块折叠 有关事件` 和 `模块主键` 之间的映射关系
export const openBlockEventToToolKey: IOpenBlockEventToToolKey = {
[OpenBlockEvent.PLUGIN_API_BLOCK_OPEN]: SkillKeyEnum.PLUGIN_API_BLOCK,
[OpenBlockEvent.WORKFLOW_BLOCK_OPEN]: SkillKeyEnum.WORKFLOW_BLOCK,
[OpenBlockEvent.IMAGEFLOW_BLOCK_OPEN]: SkillKeyEnum.IMAGE_BLOCK,
[OpenBlockEvent.DATA_SET_BLOCK_OPEN]: SkillKeyEnum.DATA_SET_BLOCK,
[OpenBlockEvent.DATA_MEMORY_BLOCK_OPEN]: SkillKeyEnum.DATA_MEMORY_BLOCK,
[OpenBlockEvent.TABLE_MEMORY_BLOCK_OPEN]: SkillKeyEnum.TABLE_MEMORY_BLOCK,
[OpenBlockEvent.TIME_CAPSULE_BLOCK_OPEN]: SkillKeyEnum.TIME_CAPSULE_BLOCK,
[OpenBlockEvent.ONBORDING_MESSAGE_BLOCK_OPEN]:
SkillKeyEnum.ONBORDING_MESSAGE_BLOCK,
[OpenBlockEvent.TASK_MANAGE_OPEN]: SkillKeyEnum.TASK_MANAGE_BLOCK,
[OpenBlockEvent.SUGGESTION_BLOCK_OPEN]: SkillKeyEnum.AUTO_SUGGESTION,
[OpenBlockEvent.TTS_BLOCK_OPEN]: SkillKeyEnum.TEXT_TO_SPEECH,
[OpenBlockEvent.FILEBOX_OPEN]: SkillKeyEnum.FILEBOX_BLOCK,
};

View File

@@ -0,0 +1,131 @@
/*
* 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,
createContext,
useContext,
useEffect,
useState,
useMemo,
} from 'react';
import EventEmitter from 'eventemitter3';
import { type BotMode } from '@coze-arch/bot-api/developer_api';
import { isValidContext } from '../utils/is-valid-context';
import { type IAbilityStoreState } from '../typings/store';
import { type IEventCenterEventName } from '../typings/scoped-events';
import { type Nullable } from '../typings/index';
import { type IEventCallbacks } from '../typings/event-callbacks';
import { type ToolAreaStore } from '../store/tool-area';
import { type AgentAreaStore } from '../store/agent-area';
import { AbilityStoreProvider } from '../hooks/store/use-ability-store-context';
import { useCreateStore } from '../hooks/builtin/use-create-store';
type IAbilityAreaContext = Nullable<{
store: {
useToolAreaStore: ToolAreaStore;
useAgentAreaStore: AgentAreaStore;
};
scopedEventBus: EventEmitter<IEventCenterEventName>;
eventCallbacks: Partial<IEventCallbacks>;
}>;
const DEFAULT_ABILITY_AREA: IAbilityAreaContext = {
store: null,
scopedEventBus: null,
eventCallbacks: null,
};
const AbilityAreaContext =
createContext<IAbilityAreaContext>(DEFAULT_ABILITY_AREA);
export const AbilityAreaContextProvider: FC<
PropsWithChildren<{
eventCallbacks?: Partial<IEventCallbacks>;
mode: BotMode;
modeSwitching: boolean;
isInit: boolean;
}>
> = ({ children, eventCallbacks = {}, mode, modeSwitching, isInit }) => {
const store = useCreateStore();
const scopedEventBus = useMemo(() => new EventEmitter<string>(), []);
const { useToolAreaStore, useAgentAreaStore } = store;
const clearAgentAreaStore = useAgentAreaStore(state => state.clearStore);
const {
updateIsInitialed,
updateIsModeSwitching,
clearStore: clearToolAreaStore,
} = useToolAreaStore.getState();
/**
* 清除
*/
useEffect(() => {
updateIsModeSwitching(modeSwitching);
if (modeSwitching || !isInit) {
return;
}
updateIsInitialed(true);
eventCallbacks?.onInitialed?.();
const cleanUp = () => {
updateIsInitialed(false);
eventCallbacks?.onDestroy?.();
clearToolAreaStore();
clearAgentAreaStore();
};
return cleanUp;
}, [mode, modeSwitching, isInit]);
return (
<AbilityAreaContext.Provider
value={{
store,
scopedEventBus,
eventCallbacks,
}}
>
<AbilityStore>{children}</AbilityStore>
</AbilityAreaContext.Provider>
);
};
const AbilityStore: FC<PropsWithChildren> = ({ children }) => {
const [state, setState] = useState<IAbilityStoreState>({});
return (
<AbilityStoreProvider state={state} setState={setState}>
{children}
</AbilityStoreProvider>
);
};
export const useAbilityAreaContext = () => {
const toolAreaContext = useContext(AbilityAreaContext);
if (!isValidContext(toolAreaContext)) {
throw new Error('toolAreaContext is not valid');
}
return toolAreaContext;
};

View File

@@ -0,0 +1,55 @@
/*
* 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,
createContext,
useContext,
} from 'react';
import {
type AbilityKey,
type AbilityScope,
} from '@coze-agent-ide/tool-config';
interface IAbilityConfigContext {
abilityKey?: AbilityKey;
scope?: AbilityScope;
}
const DEFAULT_ABILITY_CONFIG = {
abilityKey: undefined,
scope: undefined,
};
const AbilityConfigContext = createContext<IAbilityConfigContext>(
DEFAULT_ABILITY_CONFIG,
);
export const AbilityConfigContextProvider: FC<
PropsWithChildren<IAbilityConfigContext>
> = props => {
const { children, ...rest } = props;
return (
<AbilityConfigContext.Provider value={rest}>
{children}
</AbilityConfigContext.Provider>
);
};
export const useAbilityConfigContext = () => useContext(AbilityConfigContext);

View File

@@ -0,0 +1,51 @@
/*
* 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,
createContext,
useContext,
} from 'react';
import { type AgentSkillKey } from '@coze-agent-ide/tool-config';
interface IAgentSkillConfigContext {
agentSkillKey?: AgentSkillKey;
}
const DEFAULT_AGENT_SKILL_CONFIG = {
agentSkillKey: undefined,
};
const AgentSkillConfigContext = createContext<IAgentSkillConfigContext>(
DEFAULT_AGENT_SKILL_CONFIG,
);
export const AgentSkillConfigContextProvider: FC<
PropsWithChildren<IAgentSkillConfigContext>
> = props => {
const { children, ...rest } = props;
return (
<AgentSkillConfigContext.Provider value={rest}>
{children}
</AgentSkillConfigContext.Provider>
);
};
export const useAgentSkillConfigContext = () =>
useContext(AgentSkillConfigContext);

View File

@@ -0,0 +1,56 @@
/*
* 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,
createContext,
useContext,
} from 'react';
import { merge } from 'lodash-es';
export interface IPreferenceContext {
/**
* 是否开启Tool隐藏模式
*/
enableToolHiddenMode: boolean;
/**
* 是否只读状态
*/
isReadonly: boolean;
}
const DEFAULT_PREFERENCE: IPreferenceContext = {
enableToolHiddenMode: false,
isReadonly: false,
};
const PreferenceContext = createContext<IPreferenceContext>(DEFAULT_PREFERENCE);
export const PreferenceContextProvider: FC<
PropsWithChildren<Partial<IPreferenceContext>>
> = props => {
const { children, ...rest } = props;
return (
<PreferenceContext.Provider value={merge({}, DEFAULT_PREFERENCE, rest)}>
{children}
</PreferenceContext.Provider>
);
};
export const usePreference = () => useContext(PreferenceContext);

View File

@@ -0,0 +1,58 @@
/*
* 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,
createContext,
useContext,
useState,
} from 'react';
import { merge } from 'lodash-es';
export interface IToolItemContext {
isForceShowAction: boolean;
setIsForceShowAction: (visible: boolean) => void;
}
const DEFAULT_TOOL_ITEM_CONTEXT: IToolItemContext = {
isForceShowAction: false,
setIsForceShowAction: (visible: boolean) => false,
};
const ToolItemContext = createContext<IToolItemContext>(
DEFAULT_TOOL_ITEM_CONTEXT,
);
export const ToolItemContextProvider: FC<PropsWithChildren> = props => {
const { children } = props;
const [_isForceShowAction, _setIsForceShowAction] = useState(false);
return (
<ToolItemContext.Provider
value={merge({}, DEFAULT_TOOL_ITEM_CONTEXT, {
isForceShowAction: _isForceShowAction,
setIsForceShowAction: _setIsForceShowAction,
})}
>
{children}
</ToolItemContext.Provider>
);
};
export const useToolItemContext = () => useContext(ToolItemContext);

View File

@@ -0,0 +1,49 @@
/*
* 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 AgentModalTabKey } from '@coze-agent-ide/tool-config';
import { useEvent } from '../event/use-event';
import { EventCenterEventName } from '../../typings/scoped-events';
import {
type IAgentModalTabChangeEventParams,
type IAgentModalVisibleChangeEventParams,
} from '../../typings/event';
/**
* 内部使用的方法,不对外使用,用于抛出事件
*/
export const useAgentModalTriggerEvent = () => {
const { emit } = useEvent();
const emitTabChangeEvent = (tabKey: AgentModalTabKey) => {
emit<IAgentModalTabChangeEventParams>(
EventCenterEventName.AgentModalTabChange,
{ tabKey },
);
};
const emitModalVisibleChangeEvent = (isVisible: boolean) => {
emit<IAgentModalVisibleChangeEventParams>(
EventCenterEventName.AgentModalVisibleChange,
{
isVisible,
},
);
};
return { emitTabChangeEvent, emitModalVisibleChangeEvent };
};

View File

@@ -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 { useState } from 'react';
import {
AgentSkillModal,
type IAgentSkillModalPane,
} from '../../components/agent-skill-modal';
import { useAgentModalTriggerEvent } from './use-agent-modal-trigger-event';
export const useAgentSkillModal = (tabPanes: IAgentSkillModalPane[]) => {
const [visible, setVisible] = useState(false);
const { emitModalVisibleChangeEvent } = useAgentModalTriggerEvent();
const close = () => {
setVisible(false);
emitModalVisibleChangeEvent(false);
};
const open = () => {
setVisible(true);
emitModalVisibleChangeEvent(true);
};
return {
node: visible ? (
<AgentSkillModal tabPanes={tabPanes} onCancel={close} />
) : null,
close,
open,
};
};

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useShallow } from 'zustand/react/shallow';
import { type AgentSkillKey } from '@coze-agent-ide/tool-config';
import { useAbilityAreaContext } from '../../context/ability-area-context';
/**
* @deprecated 内部使用过渡期方案针对非注册组件使用外部的skill设置
*/
export const useHasAgentSkillWithPK = () => {
const {
store: { useAgentAreaStore },
} = useAbilityAreaContext();
const { existManualAgentSkillKey, realSetHasAgentSkill } = useAgentAreaStore(
state => ({
existManualAgentSkillKey: state.existManualAgentSkillKey,
realSetHasAgentSkill: state.setHasAgentSkillKey,
}),
);
/**
* @deprecated 内部使用,过渡期
*/
const setHasAgentSkill = (
agentSkillKey: AgentSkillKey,
hasSkill: boolean,
) => {
const isManual = existManualAgentSkillKey(agentSkillKey);
if (!isManual) {
realSetHasAgentSkill(agentSkillKey, hasSkill);
}
};
return {
setHasAgentSkill,
};
};
export const useNoneAgentSkill = () => {
const {
store: { useAgentAreaStore },
} = useAbilityAreaContext();
const noneAgentSkill = useAgentAreaStore(
useShallow(state =>
state.registeredAgentSkillKeyList.every(
agentSkillKey => !state.hasAgentSkillKeyList.includes(agentSkillKey),
),
),
);
return noneAgentSkill;
};

View File

@@ -0,0 +1,26 @@
/*
* 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 { useAbilityConfigContext } from '../../context/ability-config-context';
/**
* 用户内部获取ToolKey使用
*/
export const useAbilityConfig = () => {
const { abilityKey, scope } = useAbilityConfigContext();
return { abilityKey, scope };
};

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo } from 'react';
import { createToolAreaStore } from '../../store/tool-area';
import { createAgentAreaStore } from '../../store/agent-area';
export const useCreateStore = () => {
const memoedUseToolAreaStore = useMemo(() => createToolAreaStore(), []);
const memoedUseAgentAreaStore = useMemo(() => createAgentAreaStore(), []);
return {
useToolAreaStore: memoedUseToolAreaStore,
useAgentAreaStore: memoedUseAgentAreaStore,
};
};

View File

@@ -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 AbilityKey } from '@coze-agent-ide/tool-config';
import { useAbilityAreaContext } from '../../context/ability-area-context';
export const useGetToolConfig = () => {
const {
store: { useToolAreaStore },
} = useAbilityAreaContext();
const registeredToolKeyConfigList = useToolAreaStore(
state => state.registeredToolKeyConfigList,
);
return (abilityKey?: AbilityKey) =>
registeredToolKeyConfigList.find(
toolConfig => toolConfig.toolKey === abilityKey,
);
};

View File

@@ -0,0 +1,36 @@
/*
* 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 AgentSkillKey } from '@coze-agent-ide/tool-config';
import { useAbilityAreaContext } from '../../context/ability-area-context';
/**
* 用于内部注册AgentSkill使用
*/
export const useRegisterAgentSkillKey = () => {
const {
store: { useAgentAreaStore },
} = useAbilityAreaContext();
const appendRegisteredAgentSkillKeyList = useAgentAreaStore(
state => state.appendRegisteredAgentSkillKeyList,
);
return (agentSkillKey: AgentSkillKey) => {
appendRegisteredAgentSkillKeyList(agentSkillKey);
};
};

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useShallow } from 'zustand/react/shallow';
import { type IRegisteredToolGroupConfig } from '../../store/tool-area';
import { useAbilityAreaContext } from '../../context/ability-area-context';
export const useRegisterToolGroup = () => {
const {
store: { useToolAreaStore },
} = useAbilityAreaContext();
const appendIntoRegisteredToolGroupList = useToolAreaStore(
useShallow(state => state.appendIntoRegisteredToolGroupList),
);
return (params: IRegisteredToolGroupConfig) => {
appendIntoRegisteredToolGroupList(params);
};
};
export const useRegisteredToolGroupList = () => {
const {
store: { useToolAreaStore },
} = useAbilityAreaContext();
const registeredToolGroupList = useToolAreaStore(
useShallow(state => state.registeredToolGroupList),
);
return registeredToolGroupList;
};

View File

@@ -0,0 +1,48 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import { type IRegisteredToolKeyConfig } from '../../store/tool-area';
import { useAbilityAreaContext } from '../../context/ability-area-context';
/**
* 用于内部注册Tool使用
*/
export const useRegisterToolKey = () => {
const {
store: { useToolAreaStore },
} = useAbilityAreaContext();
const appendIntoRegisteredToolKeyConfigList = useToolAreaStore(
useShallow(state => state.appendIntoRegisteredToolKeyConfigList),
);
return (params: IRegisteredToolKeyConfig) => {
appendIntoRegisteredToolKeyConfigList(params);
};
};
export const useRegisteredToolKeyConfigList = () => {
const {
store: { useToolAreaStore },
} = useAbilityAreaContext();
const registeredToolKeyConfigList = useToolAreaStore(
useShallow(state => state.registeredToolKeyConfigList),
);
return registeredToolKeyConfigList;
};

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useAbilityAreaContext } from '../../context/ability-area-context';
export const useEvent = () => {
const { scopedEventBus } = useAbilityAreaContext();
function on<T extends Record<string, any>>(
eventName: string,
listener: (params: T) => void,
) {
scopedEventBus.on(eventName, listener);
return () => {
scopedEventBus.off(eventName, listener);
};
}
function once<T extends Record<string, any>>(
eventName: string,
listener: (params: T) => void,
) {
scopedEventBus.once(eventName, listener);
}
function emit<T extends Record<string, any>>(eventName: string, params: T) {
scopedEventBus.emit(eventName, params);
}
return {
on,
once,
emit,
};
};

View File

@@ -0,0 +1,53 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import { type AgentSkillKey } from '@coze-agent-ide/tool-config';
import { useAbilityAreaContext } from '../../../context/ability-area-context';
export const useHasAgentSkill = () => {
const {
store: { useAgentAreaStore },
} = useAbilityAreaContext();
const {
setHasAgentSkillKey,
existHasAgentSkillKey,
appendManualAgentSkillKeyList,
} = useAgentAreaStore(
useShallow(state => ({
setHasAgentSkillKey: state.setHasAgentSkillKey,
existHasAgentSkillKey: state.existHasAgentSkillKey,
appendManualAgentSkillKeyList: state.appendManualAgentSkillKeyList,
})),
);
const setHasAgentSkill = (
agentSkillKey: AgentSkillKey,
hasSkill: boolean,
) => {
setHasAgentSkillKey(agentSkillKey, hasSkill);
appendManualAgentSkillKeyList(agentSkillKey);
};
const getHasAgentSkill = (agentSkillKey: AgentSkillKey) =>
existHasAgentSkillKey(agentSkillKey);
return {
setHasAgentSkill,
getHasAgentSkill,
};
};

View File

@@ -0,0 +1,101 @@
/*
* 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 { useMemo } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { size } from 'lodash-es';
import { type SkillKeyEnum } from '@coze-agent-ide/tool-config';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import {
TabStatus,
type TabDisplayItems,
} from '@coze-arch/bot-api/developer_api';
import { useAbilityConfig } from '../../builtin/use-ability-config';
import { toolKeyToApiStatusKeyTransformer } from '../../../utils/tool-content-block';
/**
* 用于校验当前模块默认展开收起状态
*
* @param blockKey 主键 - tool 插件化改造后无需传入
* @param configured 是否有配置内容
* @param when 是否校验
*
* @see
*/
export const useToolContentBlockDefaultExpand = (
$params: {
blockKey?: SkillKeyEnum;
configured: boolean;
},
$when = true,
) => {
const { abilityKey } = useAbilityConfig();
const { blockKey, configured = false } = $params;
const isReadonly = useBotDetailIsReadonly();
const { init, editable, botSkillBlockCollapsibleState } = usePageRuntimeStore(
useShallow(store => ({
init: store.init,
editable: store.editable,
botSkillBlockCollapsibleState: store.botSkillBlockCollapsibleState,
})),
);
return useMemo(() => {
// 不做校验
if (!$when) {
return undefined;
// 状态机未就绪
} else if (!init || size(botSkillBlockCollapsibleState) === 0) {
return undefined;
/**
* @description 仅在满足以下条件时用户行为记录才能生效
*
* 1. 拥有编辑权限
* 2. 不能是历史预览环境
* 3. 必须已配置
*/
} else if (editable && !isReadonly && configured) {
const key = abilityKey ?? blockKey;
if (!key) {
return;
}
const transformerBlockKey = toolKeyToApiStatusKeyTransformer(key);
const collapsibleState =
botSkillBlockCollapsibleState[
transformerBlockKey as keyof TabDisplayItems
];
if (collapsibleState === TabStatus.Open) {
return true;
} else if (collapsibleState === TabStatus.Close) {
return false;
}
}
return configured;
}, [
$when,
blockKey,
configured,
init,
isReadonly,
editable,
botSkillBlockCollapsibleState,
]);
};

View File

@@ -0,0 +1,46 @@
/*
* 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 AbilityKey } from '@coze-agent-ide/tool-config';
import { useEvent } from '../../event/use-event';
import { EventCenterEventName } from '../../../typings/scoped-events';
import { type IToggleContentBlockEventParams } from '../../../typings/event';
interface IUseToolToggleCollapseParams {
abilityKeyList: AbilityKey[];
isExpand: boolean;
}
export const useToolToggleCollapse = () => {
const { emit } = useEvent();
return ({ abilityKeyList, isExpand }: IUseToolToggleCollapseParams) => {
if (!abilityKeyList.length) {
return;
}
abilityKeyList.forEach(abilityKey => {
emit<IToggleContentBlockEventParams>(
EventCenterEventName.ToggleContentBlock,
{
abilityKey,
isExpand,
},
);
});
};
};

View File

@@ -0,0 +1,46 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import { TOOL_KEY_TO_API_STATUS_KEY_MAP } from '@coze-agent-ide/tool-config';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { TabStatus } from '@coze-arch/bot-api/developer_api';
import { useRegisteredToolKeyConfigList } from '../../builtin/use-register-tool-key';
import { usePreference } from '../../../context/preference-context';
export const useIsAllToolHidden = () => {
const { isReadonly } = usePreference();
const botSkillBlockCollapsibleState = usePageRuntimeStore(
useShallow(state => state.botSkillBlockCollapsibleState),
);
const registeredToolKeyConfigList = useRegisteredToolKeyConfigList();
if (isReadonly) {
return registeredToolKeyConfigList.every(
toolConfig => !toolConfig.hasValidData,
);
}
const statusKeyMap = registeredToolKeyConfigList.map(
toolConfig => TOOL_KEY_TO_API_STATUS_KEY_MAP[toolConfig.toolKey],
);
return statusKeyMap.every(
statusKey => botSkillBlockCollapsibleState[statusKey] === TabStatus.Hide,
);
};

View File

@@ -0,0 +1,74 @@
/*
* 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 { TOOL_KEY_TO_API_STATUS_KEY_MAP } from '@coze-agent-ide/tool-config';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { TabStatus } from '@coze-arch/bot-api/developer_api';
import { useAbilityConfig } from '../../builtin/use-ability-config';
import { isToolKey } from '../../../utils/is-tool-key';
import { usePreference } from '../../../context/preference-context';
import { useAbilityAreaContext } from '../../../context/ability-area-context';
export const useToolValidData = () => {
const {
store: { useToolAreaStore },
} = useAbilityAreaContext();
const setToolHasValidData = useToolAreaStore(
state => state.setToolHasValidData,
);
const setBotSkillBlockCollapsibleState = usePageRuntimeStore(
state => state.setBotSkillBlockCollapsibleState,
);
const { abilityKey, scope } = useAbilityConfig();
const toolStatus = usePageRuntimeStore(state =>
abilityKey
? state.botSkillBlockCollapsibleState[
TOOL_KEY_TO_API_STATUS_KEY_MAP[abilityKey]
]
: null,
);
const { isReadonly } = usePreference();
return (hasValidData: boolean) => {
if (!isToolKey(abilityKey, scope)) {
return;
}
setToolHasValidData({
toolKey: abilityKey,
hasValidData,
});
/**
* 异常场景兜底,视图和服务端数据无法匹配,需要触发更新服务端数据
* 有数据但是隐藏状态
*/
if (toolStatus === TabStatus.Hide && hasValidData) {
setBotSkillBlockCollapsibleState(
{
[TOOL_KEY_TO_API_STATUS_KEY_MAP[abilityKey]]: TabStatus.Default,
},
isReadonly,
);
}
};
};

View File

@@ -0,0 +1,129 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import {
AbilityScope,
type ToolKey,
type AgentSkillKey,
type AbilityKey,
} from '@coze-agent-ide/tool-config';
import { useEvent } from '../../event/use-event';
import { useAbilityConfig } from '../../builtin/use-ability-config';
import { generateError } from '../../../utils/error';
import { EventCenterEventName } from '../../../typings/scoped-events';
import { type IAbilityInitialedEventParams } from '../../../typings/event';
import { useAbilityAreaContext } from '../../../context/ability-area-context';
export const useInit = () => {
const { on, emit } = useEvent();
const { abilityKey, scope } = useAbilityConfig();
if (!abilityKey || !scope) {
throw generateError('AbilityKey or Scope is undefined');
}
const {
store: { useToolAreaStore, useAgentAreaStore },
} = useAbilityAreaContext();
const { registeredToolKeyConfigList, appendIntoInitialedToolKeyList } =
useToolAreaStore(
useShallow(state => ({
registeredToolKeyConfigList: state.registeredToolKeyConfigList,
appendIntoInitialedToolKeyList: state.appendIntoInitialedToolKeyList,
})),
);
const { registeredAgentSkillKeyList, appendIntoInitialedAgentSkillKeyList } =
useAgentAreaStore(
useShallow(state => ({
registeredAgentSkillKeyList: state.registeredAgentSkillKeyList,
appendIntoInitialedAgentSkillKeyList:
state.appendIntoInitialedAgentSkillKeyList,
})),
);
function markToolInitialed() {
let updatedInitialedAbilityKeyList: ToolKey[] | AgentSkillKey[] = [];
if (scope === AbilityScope.TOOL) {
appendIntoInitialedToolKeyList(abilityKey as unknown as ToolKey);
updatedInitialedAbilityKeyList =
useToolAreaStore.getState().initialedToolKeyList;
} else if (scope === AbilityScope.AGENT_SKILL) {
appendIntoInitialedAgentSkillKeyList(
abilityKey as unknown as AgentSkillKey,
);
updatedInitialedAbilityKeyList =
useAgentAreaStore.getState().initialedAgentSkillKeyList;
}
emit<IAbilityInitialedEventParams>(EventCenterEventName.AbilityInitialed, {
initialedAbilityKeyList: updatedInitialedAbilityKeyList,
});
}
function onToolInitialed(
listener: () => void,
listenedAbilityKey: AbilityKey,
) {
const offToolInitialed = on<IAbilityInitialedEventParams>(
EventCenterEventName.AbilityInitialed,
params => {
const { initialedAbilityKeyList } = params;
if (initialedAbilityKeyList.includes(listenedAbilityKey)) {
listener();
offToolInitialed();
}
},
);
}
function onAllToolInitialed(listener: (isAllInitialed: boolean) => void) {
const offToolInitialed = on<IAbilityInitialedEventParams>(
EventCenterEventName.AbilityInitialed,
params => {
const { initialedAbilityKeyList } = params;
let isAllRegisteredAbilityKeyInitialed = false;
if (scope === AbilityScope.TOOL) {
isAllRegisteredAbilityKeyInitialed =
registeredToolKeyConfigList.every(toolKeyConfig =>
initialedAbilityKeyList.includes(toolKeyConfig.toolKey),
);
} else if (scope === AbilityScope.AGENT_SKILL) {
isAllRegisteredAbilityKeyInitialed =
registeredAgentSkillKeyList.every(agentSkillKey =>
initialedAbilityKeyList.includes(agentSkillKey),
);
}
listener(isAllRegisteredAbilityKeyInitialed);
},
);
return offToolInitialed;
}
return {
markToolInitialed,
onToolInitialed,
onAllToolInitialed,
};
};

View File

@@ -0,0 +1,53 @@
/*
* 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-next-line @coze-arch/no-pkg-dir-import
import { type AgentModalTabKey } from '@coze-agent-ide/tool-config/src/types';
import { useEvent } from '../../event/use-event';
import { EventCenterEventName } from '../../../typings/scoped-events';
import {
type IAgentModalTabChangeEventParams,
type IAgentModalVisibleChangeEventParams,
} from '../../../typings/event';
export const useAgentSkillModalCallbacks = () => {
const { on } = useEvent();
const onTabChange = (listener: (tabKey: AgentModalTabKey) => void) => {
on<IAgentModalTabChangeEventParams>(
EventCenterEventName.AgentModalTabChange,
params => {
const { tabKey } = params;
listener(tabKey);
},
);
};
const onModalVisibleChange = (listener: (isVisible: boolean) => void) => {
on<IAgentModalVisibleChangeEventParams>(
EventCenterEventName.AgentModalVisibleChange,
params => {
const { isVisible } = params;
listener(isVisible);
},
);
};
return {
onTabChange,
onModalVisibleChange,
};
};

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { size } from 'lodash-es';
import {
AbilityScope,
TOOL_KEY_STORE_MAP,
AGENT_SKILL_KEY_MAP,
} from '@coze-agent-ide/tool-config';
import { useMultiAgentStore } from '@coze-studio/bot-detail-store/multi-agent';
import {
type BotSkillAction,
type BotSkillStore,
useBotSkillStore,
} from '@coze-studio/bot-detail-store/bot-skill';
import { findTargetAgent } from '@coze-studio/bot-detail-store';
import { useAbilityStoreContext } from '../../store/use-ability-store-context';
import { useAbilityConfig } from '../../builtin/use-ability-config';
import { generateError } from '../../../utils/error';
const KEY_MAP = {
[AbilityScope.TOOL]: TOOL_KEY_STORE_MAP,
[AbilityScope.AGENT_SKILL]: AGENT_SKILL_KEY_MAP,
};
// 访问全局状态机中的状态
export function useToolStore<U>(selector: (state: BotSkillStore) => U): U {
const { abilityKey } = useAbilityConfig();
if (!abilityKey) {
throw generateError('not find abilityKey');
}
return useBotSkillStore(selector) as U;
}
// 访问全局状态机中的方法
export function useToolStoreAction<U>(
selector: (state: BotSkillAction) => U,
): U {
const { abilityKey } = useAbilityConfig();
if (!abilityKey) {
throw generateError('not find abilityKey');
}
return useBotSkillStore(selector) as U;
}
// 提交数据
export function useToolDispatch<T>() {
const { abilityKey, scope } = useAbilityConfig();
const { state, setState } = useAbilityStoreContext();
if (!abilityKey || !scope) {
throw generateError('not find abilityKey or scope');
}
return (newState: T) => {
setState({
[scope]: {
...state[scope],
// @ts-expect-error -- 以后想着解决一下这里的类型问题
[KEY_MAP[scope][abilityKey]]: newState,
},
});
};
}
// 监听 tool 状态机数据变化,并同步到 bot detail store
export function useSubscribeToolStore(scope: AbilityScope, agentId?: string) {
// bot detail store 更新方法
const { setBotSkill } = useBotSkillStore(
useShallow(state => ({
setBotSkill: state.setBotSkill,
})),
);
const { setMultiAgentByImmer } = useMultiAgentStore(
useShallow(state => ({
setMultiAgentByImmer: state.setMultiAgentByImmer,
})),
);
// tools store 数据
const { state } = useAbilityStoreContext();
const newState = state[scope];
// 同步数据
useEffect(() => {
if (size(newState)) {
if (!newState) {
return;
}
if (scope === AbilityScope.TOOL) {
setBotSkill(newState);
} else if (scope === AbilityScope.AGENT_SKILL) {
setMultiAgentByImmer(agentState => {
const agent = findTargetAgent(agentState.agents, agentId);
if (agent) {
agent.skills = newState;
}
});
}
}
}, [newState]);
}

View File

@@ -0,0 +1,51 @@
/*
* 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,
createContext,
useContext,
} from 'react';
import { noop } from 'lodash-es';
import { type IAbilityStoreState } from '../../typings/store';
interface IAbilityStoreContext {
state: IAbilityStoreState;
setState: (state: IAbilityStoreState) => void;
}
const AbilityStoreContext = createContext<IAbilityStoreContext>({
state: {},
setState: noop,
});
export const AbilityStoreProvider: FC<
PropsWithChildren<IAbilityStoreContext>
> = ({ children, state, setState }) => (
<AbilityStoreContext.Provider
value={{
state,
setState,
}}
>
{children}
</AbilityStoreContext.Provider>
);
export const useAbilityStoreContext = () => useContext(AbilityStoreContext);

View File

@@ -0,0 +1,49 @@
/*
* 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 AbilityKey } from '@coze-agent-ide/tool-config';
import { useEvent } from '../event/use-event';
import { EventCenterEventName } from '../../typings/scoped-events';
import { type IToggleContentBlockEventParams } from '../../typings/event';
/**
* 私有的hooks不对外暴露使用
* @returns
*/
export const useRegisterCollapse = () => {
const { on } = useEvent();
const registerCollapse = (
listener: (isExpand: boolean) => void,
abilityKey: AbilityKey,
) =>
on<IToggleContentBlockEventParams>(
EventCenterEventName.ToggleContentBlock,
params => {
const { abilityKey: currentAbilityKey, isExpand } = params;
if (abilityKey === currentAbilityKey) {
listener(isExpand);
}
},
);
return {
registerCollapse,
};
};

View File

@@ -0,0 +1,97 @@
/*
* 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.
*/
/**
* START 对外长期暴露的类型、常量、函数、Hooks、组件等
*/
export {
AbilityScope,
ToolKey,
AgentSkillKey,
} from '@coze-agent-ide/tool-config';
export {
useToolStore,
useToolStoreAction,
useToolDispatch,
useSubscribeToolStore,
} from './hooks/public/store/use-tool-store';
export { openBlockEventToToolKey } from './constants/tool-content-block';
export { useToolContentBlockDefaultExpand } from './hooks/public/collapse/use-tool-content-block-default-expand';
export { useAgentSkillModal } from './hooks/agent-skill-modal/use-agent-skill-modal';
export { useToolToggleCollapse } from './hooks/public/collapse/use-tool-toggle-collapse';
export { useInit } from './hooks/public/init/use-init';
export { useRegisteredToolKeyConfigList } from './hooks/builtin/use-register-tool-key';
export { useIsAllToolHidden } from './hooks/public/container/use-tool-all-hidden';
export { useToolValidData } from './hooks/public/container/use-tool-valid-data';
export { ToolContentBlock } from './components/tool-content-block';
export { AbilityAreaContextProvider } from './context/ability-area-context';
export { ToolView } from './components/tool-view';
export { ToolContainer } from './components/tool-container';
export { ToolMenu } from './components/tool-menu';
export { GroupingContainer } from './components/grouping-container';
export { AbilityAreaContainer } from './components/ability-area-container';
export { ToolEntryCommonProps } from './typings/index';
export { ToolItemList } from './components/tool-item-list';
export { ToolItem } from './components/tool-item';
export { ToolItemSwitch } from './components/tool-item-switch';
export { ToolItemAction } from './components/tool-item-action';
export { ToolItemActionCard } from './components/tool-item-action/actions/tool-item-action-card';
export { ToolItemActionCopy } from './components/tool-item-action/actions/tool-item-action-copy';
export { ToolItemActionDelete } from './components/tool-item-action/actions/tool-item-action-delete';
export { ToolItemActionInfo } from './components/tool-item-action/actions/tool-item-action-info';
export { ToolItemActionSetting } from './components/tool-item-action/actions/tool-item-action-setting';
export { ToolItemActionEdit } from './components/tool-item-action/actions/tool-item-action-edit';
export { ToolItemActionDrag } from './components/tool-item-action/actions/tool-item-action-drag';
export { ToolItemIconInfo } from './components/tool-item-icon/icons/tool-item-icon-info';
export { ToolItemIconPeople } from './components/tool-item-icon/icons/tool-item-icon-people';
export { ToolItemIconCard } from './components/tool-item-icon/icons/tool-item-icon-card';
export { useToolItemContext } from './context/tool-item-context';
export { AutoGenerateButton } from './components/auto-generate-button';
export { AddButton } from './components/add-button';
export { TipsDisplay as ModelCapabilityTipsDisplay } from './components/model-capability-tips';
export { abilityKey2ModelFunctionConfigType } from './utils/model-function-config-type-mapping';
/**
* END 对外长期暴露的类型、常量、函数、Hooks、组件等
*/
/**
* START 过渡期对外暴露的类型、常量、函数、Hooks、组件等
*/
export {
/**
* 模块主键
* @deprecated 该使用方式已废弃, 请使用: `import { ToolKey } from '@coze-agent-ide/tool-config'`;
*/
SkillKeyEnum,
} from '@coze-agent-ide/tool-config';
export { useHasAgentSkillWithPK } from './hooks/agent-skill/use-agent-skill';
export { useEvent } from './hooks/event/use-event';
export { EventCenterEventName } from './typings/scoped-events';
export { IToggleContentBlockEventParams } from './typings/event';
/**
* END 过渡期对外暴露的类型、常量、函数、Hooks、组件等
*/

View File

@@ -0,0 +1,131 @@
/*
* 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 { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import { type AgentSkillKey } from '@coze-agent-ide/tool-config';
export interface IAgentAreaState {
/**
* @deprecated 过渡期使用用户手动搞的key list
*/
manualAgentSkillKeyList: AgentSkillKey[];
hasAgentSkillKeyList: AgentSkillKey[];
initialedAgentSkillKeyList: AgentSkillKey[];
registeredAgentSkillKeyList: AgentSkillKey[];
}
export interface IAgentAreaAction {
/**
* @deprecated 过渡期使用,后续删除
*/
appendManualAgentSkillKeyList: (skillKey: AgentSkillKey) => void;
setHasAgentSkillKey: (skillKey: AgentSkillKey, hasSkill: boolean) => void;
existHasAgentSkillKey: (skillKey: AgentSkillKey) => boolean;
appendRegisteredAgentSkillKeyList: (skillKey: AgentSkillKey) => void;
hasAgentSkillKeyInRegisteredAgentSkillKeyList: (
skillKey: AgentSkillKey,
) => boolean;
existManualAgentSkillKey: (skillKey: AgentSkillKey) => boolean;
appendIntoInitialedAgentSkillKeyList: (skillKey: AgentSkillKey) => void;
clearStore: () => void;
}
export const createAgentAreaStore = () =>
create<IAgentAreaState & IAgentAreaAction>()(
devtools(
(set, get) => ({
manualAgentSkillKeyList: [],
hasAgentSkillKeyList: [],
registeredAgentSkillKeyList: [],
initialedAgentSkillKeyList: [],
setHasAgentSkillKey: (skillKey, hasSkill) => {
set(
produce<IAgentAreaState>(state => {
const { hasAgentSkillKeyList } = state;
if (hasSkill) {
if (!hasAgentSkillKeyList.includes(skillKey)) {
hasAgentSkillKeyList.push(skillKey);
}
} else {
const index = hasAgentSkillKeyList.findIndex(
key => key === skillKey,
);
if (index >= 0) {
hasAgentSkillKeyList.splice(index, 1);
}
}
}),
);
},
existHasAgentSkillKey: skillKey => {
const { hasAgentSkillKeyList } = get();
return hasAgentSkillKeyList.includes(skillKey);
},
appendRegisteredAgentSkillKeyList: (skillKey: AgentSkillKey) => {
const { registeredAgentSkillKeyList } = get();
if (!registeredAgentSkillKeyList.includes(skillKey)) {
set({
registeredAgentSkillKeyList: [
...registeredAgentSkillKeyList,
skillKey,
],
});
}
},
hasAgentSkillKeyInRegisteredAgentSkillKeyList: (
skillKey: AgentSkillKey,
) => {
const { registeredAgentSkillKeyList } = get();
return registeredAgentSkillKeyList.includes(skillKey);
},
appendManualAgentSkillKeyList: skillKey => {
const { manualAgentSkillKeyList } = get();
if (!manualAgentSkillKeyList.includes(skillKey)) {
set({
manualAgentSkillKeyList: [...manualAgentSkillKeyList, skillKey],
});
}
},
existManualAgentSkillKey: skillKey =>
get().manualAgentSkillKeyList.includes(skillKey),
appendIntoInitialedAgentSkillKeyList: skillKey => {
const { initialedAgentSkillKeyList } = get();
if (!initialedAgentSkillKeyList.includes(skillKey)) {
set({
initialedAgentSkillKeyList: [
...initialedAgentSkillKeyList,
skillKey,
],
});
}
},
clearStore: () => {
set({
hasAgentSkillKeyList: [],
});
},
}),
{
name: 'botStudio.tool.AgentAreaStore',
enabled: IS_DEV_MODE,
},
),
);
export type AgentAreaStore = ReturnType<typeof createAgentAreaStore>;

View File

@@ -0,0 +1,151 @@
/*
* 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 { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import { type ToolKey, type ToolGroupKey } from '@coze-agent-ide/tool-config';
export interface IRegisteredToolKeyConfig {
toolGroupKey: ToolGroupKey;
toolKey: ToolKey;
toolTitle: string;
hasValidData: boolean;
}
export interface IRegisteredToolGroupConfig {
toolGroupKey: ToolGroupKey;
groupTitle: string;
}
export interface IToolAreaState {
isInitialed: boolean;
isModeSwitching: boolean;
initialedToolKeyList: ToolKey[];
registeredToolKeyConfigList: IRegisteredToolKeyConfig[];
registeredToolGroupList: IRegisteredToolGroupConfig[];
}
export interface IToolAreaAction {
updateIsInitialed: (isInitialed: boolean) => void;
updateIsModeSwitching: (isModeSwitching: boolean) => void;
appendIntoInitialedToolKeyList: (toolKey: ToolKey) => void;
hasToolKeyInInitialedToolKeyList: (toolKey: ToolKey) => boolean;
setToolHasValidData: (data: {
toolKey: ToolKey;
hasValidData: boolean;
}) => void;
appendIntoRegisteredToolKeyConfigList: (
params: IRegisteredToolKeyConfig,
) => void;
appendIntoRegisteredToolGroupList: (
params: IRegisteredToolGroupConfig,
) => void;
hasToolKeyInRegisteredToolKeyList: (toolKey: ToolKey) => boolean;
clearStore: () => void;
}
export const createToolAreaStore = () =>
create<IToolAreaState & IToolAreaAction>()(
devtools(
(set, get) => ({
initialedToolKeyList: [],
registeredToolKeyConfigList: [],
registeredToolGroupList: [],
isInitialed: false,
isModeSwitching: false,
appendIntoRegisteredToolKeyConfigList: params => {
const { toolKey } = params;
const { registeredToolKeyConfigList } = get();
if (
!registeredToolKeyConfigList.find(
toolKeyConfig => toolKeyConfig.toolKey === toolKey,
)
) {
set({
registeredToolKeyConfigList: [
...registeredToolKeyConfigList,
params,
],
});
}
},
hasToolKeyInRegisteredToolKeyList: (toolKey: ToolKey) => {
const { registeredToolKeyConfigList } = get();
return Boolean(
registeredToolKeyConfigList.find(
toolKeyConfig => toolKeyConfig.toolKey === toolKey,
),
);
},
setToolHasValidData: ({ toolKey, hasValidData }) => {
set(
produce<IToolAreaState>(state => {
const tool = state.registeredToolKeyConfigList.find(
toolConfig => toolConfig.toolKey === toolKey,
);
if (tool) {
tool.hasValidData = hasValidData;
}
}),
);
},
appendIntoRegisteredToolGroupList: params => {
const { registeredToolGroupList } = get();
if (
!registeredToolGroupList.find(
groupConfig => groupConfig.toolGroupKey === params.toolGroupKey,
)
) {
set({
registeredToolGroupList: [...registeredToolGroupList, params],
});
}
},
appendIntoInitialedToolKeyList: (toolKey: ToolKey) => {
const { initialedToolKeyList } = get();
if (!initialedToolKeyList.includes(toolKey)) {
set({
initialedToolKeyList: [...initialedToolKeyList, toolKey],
});
}
},
hasToolKeyInInitialedToolKeyList: (toolKey: ToolKey) => {
const { initialedToolKeyList } = get();
return initialedToolKeyList.includes(toolKey);
},
updateIsInitialed: (isInitialed: boolean) => set({ isInitialed }),
updateIsModeSwitching: (isModeSwitching: boolean) =>
set({ isModeSwitching }),
clearStore: () => {
set({
initialedToolKeyList: [],
registeredToolKeyConfigList: [],
registeredToolGroupList: [],
isInitialed: false,
});
},
}),
{
name: 'botStudio.tool.ToolAreaStore',
enabled: IS_DEV_MODE,
},
),
);
export type ToolAreaStore = ReturnType<typeof createToolAreaStore>;

View File

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

View File

@@ -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 { type ReactNode } from 'react';
export interface ToolButtonCommonProps {
onClick?: () => void;
tooltips?: ReactNode;
loading?: boolean;
disabled?: boolean;
[key: `data-${string}`]: string;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface IEventCallbacks {
onAllToolHiddenStatusChange: (isAllHidden: boolean) => void;
onInitialed: () => void;
onDestroy: () => void;
}

View File

@@ -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 AgentModalTabKey,
type AbilityKey,
} from '@coze-agent-ide/tool-config';
export interface IAbilityInitialedEventParams {
initialedAbilityKeyList: Array<AbilityKey>;
}
export interface IToggleContentBlockEventParams {
abilityKey: AbilityKey;
isExpand: boolean;
}
export interface IAgentModalTabChangeEventParams {
tabKey: AgentModalTabKey;
}
export interface IAgentModalVisibleChangeEventParams {
isVisible: boolean;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ToolKey } from '@coze-agent-ide/tool-config';
export type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
export type NonNullableType<T> = {
[P in keyof T]: Exclude<T[P], null>;
};
export type EmptyFunc = () => void | Promise<void>;
export interface ToolEntryCommonProps {
title: string;
toolKey?: ToolKey;
}

View File

@@ -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.
*/
export type IEventCenterEventName = EventCenterEventName | string;
/**
* 事件中心内置的事件
*/
export const enum EventCenterEventName {
/**
* 插件初始化后的事件名
*/
AbilityInitialed = 'abilityInitialed',
/**
* 折叠展开ContentBlock的事件
*/
ToggleContentBlock = 'toggleContentBlock',
/**
* Agent Modal中tab切换的事件
*/
AgentModalTabChange = 'agentModalTabChange',
/**
* Agent Modal中显隐发生变化
*/
AgentModalVisibleChange = 'agentModalVisibleChange',
}

View File

@@ -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 { type AbilityScope } from '@coze-agent-ide/tool-config';
import { type BotDetailSkill } from '@coze-studio/bot-detail-store';
type BotSkillType = BotDetailSkill;
export interface IAbilityStoreState {
[AbilityScope.TOOL]?: BotSkillType;
[AbilityScope.AGENT_SKILL]?: BotDetailSkill;
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const generateError = (message: string) =>
new Error(`[Bot Platform Tool Hooks]: ${message}`);

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { type ReactElement, type ReactNode } from 'react';
import { type AgentSkillKey } from '@coze-agent-ide/tool-config';
export function hasValidAgentSkillKey(
child: ReactNode,
): child is ReactElement<unknown> & { key: AgentSkillKey } {
return (
React.isValidElement(child) &&
child.key !== null &&
typeof child.key === 'string'
);
}

View File

@@ -0,0 +1,24 @@
/*
* 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 AbilityKey,
type AbilityScope,
type ToolKey,
} from '@coze-agent-ide/tool-config';
export const isToolKey = (_?: AbilityKey, scope?: AbilityScope): _ is ToolKey =>
scope === 'tool';

View File

@@ -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 NonNullableType } from '../typings/index';
export const isValidContext = <T extends object>(
context: T,
): context is NonNullableType<T> =>
Object.keys(context)
.map(keyName => context[keyName as keyof T])
.reduce(
(prevResult, currentProperty) => prevResult && currentProperty !== null,
true,
);

View File

@@ -0,0 +1,47 @@
/*
* 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 AbilityKey } from '@coze-agent-ide/tool-config';
import { ModelFuncConfigType } from '@coze-arch/bot-api/developer_api';
// AbilityKey 到 ModelFuncConfigType 的映射
const abilityKeyFuncConfigTypeMap: {
// 确保每个 key 这里都有配置
[key in AbilityKey]: ModelFuncConfigType | null;
} = {
plugin: ModelFuncConfigType.Plugin,
workflow: ModelFuncConfigType.Workflow,
knowledge: null,
imageflow: ModelFuncConfigType.ImageFlow,
variable: ModelFuncConfigType.Variable,
database: ModelFuncConfigType.Database,
longTermMemory: ModelFuncConfigType.LongTermMemory,
fileBox: ModelFuncConfigType.FileBox,
trigger: ModelFuncConfigType.Trigger,
onboarding: ModelFuncConfigType.Onboarding,
suggest: ModelFuncConfigType.Suggestion,
voice: ModelFuncConfigType.TTS,
background: ModelFuncConfigType.BackGroundImage,
document: ModelFuncConfigType.KnowledgeText,
table: ModelFuncConfigType.KnowledgeTable,
photo: ModelFuncConfigType.KnowledgePhoto,
shortcut: ModelFuncConfigType.ShortcutCommand,
devHooks: ModelFuncConfigType.HookInfo,
userInput: ModelFuncConfigType.TTS,
};
export const abilityKey2ModelFunctionConfigType = (abilityKey: AbilityKey) =>
abilityKeyFuncConfigTypeMap[abilityKey];

View File

@@ -0,0 +1,33 @@
/*
* 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 ToolKey,
TOOL_KEY_TO_API_STATUS_KEY_MAP,
type AbilityKey,
type SkillKeyEnum,
} from '@coze-agent-ide/tool-config';
/**
* `能力模块主键` 转 `接口定义的属性名` 函数
* ⚠️ 命名需参看 @/services/auto-generate/developer_api/namespaces/developer_api > TabDisplayItems
*/
export const toolKeyToApiStatusKeyTransformer = (
$key: AbilityKey | SkillKeyEnum,
) => {
const apiStatusKey = TOOL_KEY_TO_API_STATUS_KEY_MAP[$key as ToolKey];
return apiStatusKey ?? `${$key}_tab_status`;
};