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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -0,0 +1,45 @@
.main {
display: flex;
flex-wrap: nowrap;
width: 100%;
height: 100%;
.sider {
overflow-y: auto;
display: flex;
flex-direction: column;
width: 218px;
padding: 12px;
background-color: #ebedf0;
}
.content {
overflow-y: hidden;
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
background: #f7f7fa;
.filter {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 8px 24px;
}
.content-inner {
overflow-y: hidden;
display: flex;
flex: 1;
}
}
}
.data-sets-content {
padding: 16px 24px;
}

View File

@@ -0,0 +1,243 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import classNames from 'classnames';
import { type ModalProps } from '@douyinfe/semi-foundation/lib/es/modal/modalFoundation';
import { useWorkflowModalParts } from '@coze-workflow/components';
import { type WorkFlowItemType } from '@coze-studio/bot-detail-store';
import { KnowledgeListModalContent } from '@coze-data/knowledge-modal-adapter';
import { I18n } from '@coze-arch/i18n';
import { UITabsModal } from '@coze-arch/bot-semi';
import { PageType, SceneType, usePageJumpResponse } from '@coze-arch/bot-hooks';
import { type Dataset } from '@coze-arch/bot-api/knowledge';
import { type PluginApi, WorkflowMode } from '@coze-arch/bot-api/developer_api';
import { type PluginModalModeProps } from '@coze-agent-ide/plugin-shared';
import { usePluginModalParts } from '@coze-agent-ide/bot-plugin-export/agentSkillPluginModal/hooks';
import { isNonNull } from './utils';
import s from './index.module.less';
export interface SkillsModalProps
extends Partial<ModalProps>,
PluginModalModeProps {
tabs: ('plugin' | 'workflow' | 'datasets' | 'imageFlow')[];
tabsConfig?: {
plugin?: {
list: PluginApi[];
onChange: (list: PluginApi[]) => void;
};
workflow?: {
list: WorkFlowItemType[];
onChange: (list: WorkFlowItemType[]) => void;
};
datasets?: {
list: Dataset[];
onChange: (list: Dataset[]) => void;
};
imageFlow?: {
list: WorkFlowItemType[];
onChange: (list: WorkFlowItemType[]) => void;
};
};
}
const SCENE_TAB_MAP: Partial<
Record<SceneType, 'tools' | 'workflow' | 'datasets' | 'imageFlow'>
> = {
[SceneType.WORKFLOW__BACK__BOT]: 'workflow',
};
export function SkillsModal({
tabsConfig,
openMode,
openModeCallback,
tabs,
...restModalProps
}: SkillsModalProps) {
const {
plugin: {
list: pluginApiList = [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: onPluginApiListChange = () => {},
} = {},
workflow: {
list: workFlowList = [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: onWorkFlowListChange = () => {},
} = {},
datasets: {
list: datasetList = [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: onDatasetListChange = () => {},
} = {},
imageFlow: {
list: imageFlowList = [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: onImageFlowListChange = () => {},
} = {},
} = tabsConfig ?? {};
const jumpResponse = usePageJumpResponse(PageType.BOT);
const pluginModalParts = usePluginModalParts({
pluginApiList,
onPluginApiListChange,
openModeCallback,
openMode,
});
const workflowModalParts = useWorkflowModalParts({
workFlowList,
onWorkFlowListChange,
onAdd: openModeCallback,
});
const imageFlowModalParts = useWorkflowModalParts({
// 如果是仅添加一次/或者添加多次,清空默认选中
workFlowList: imageFlowList,
onWorkFlowListChange: onImageFlowListChange,
flowMode: WorkflowMode.Imageflow,
onAdd: openModeCallback,
});
const toolTabPane = {
tabPaneProps: {
tab: I18n.t('Tools'),
itemKey: 'tools',
},
content: (
<div className={s.main}>
<div className={s.sider}>{pluginModalParts.sider}</div>
<div className={s.content}>
<div className={s.filter}>{pluginModalParts.filter}</div>
<div className={s['content-inner']}>{pluginModalParts.content}</div>
</div>
</div>
),
};
const workflowTabPane = {
tabPaneProps: {
tab: I18n.t('Workflow'),
itemKey: 'workflow',
},
content: (
<div className={s.main}>
<div className={s.sider}>{workflowModalParts.sider}</div>
<div className={s.content}>
{!!workflowModalParts.filter && (
<div className={s.filter}>{workflowModalParts.filter}</div>
)}
<div className={s['content-inner']}>{workflowModalParts.content}</div>
</div>
</div>
),
};
const datasetTabPane = {
tabPaneProps: {
tab: I18n.t('Datasets'),
itemKey: 'datasets',
},
content: (
<div className={classNames(s.main, s['data-sets-content'])}>
<KnowledgeListModalContent
datasetList={datasetList}
onDatasetListChange={onDatasetListChange}
/>
</div>
),
};
const imageFlowTabPane = {
tabPaneProps: {
tab: I18n.t('imageflow_title'),
itemKey: 'imageFlow',
},
content: (
<div className={s.main}>
<div className={s.sider}>{imageFlowModalParts.sider}</div>
<div className={s.content}>
{!!imageFlowModalParts.filter && (
<div className={s.filter}>{imageFlowModalParts.filter}</div>
)}
<div className={s['content-inner']}>
{imageFlowModalParts.content}
</div>
</div>
</div>
),
};
return (
<UITabsModal
visible
tabs={{
tabsProps: {
lazyRender: true,
defaultActiveKey: jumpResponse?.scene
? SCENE_TAB_MAP[jumpResponse.scene] || 'tools'
: 'tools',
},
tabPanes: tabs
.map(tab => {
if (tab === 'plugin') {
return toolTabPane;
}
if (tab === 'workflow') {
return workflowTabPane;
}
if (tab === 'datasets') {
return datasetTabPane;
}
if (tab === 'imageFlow') {
return imageFlowTabPane;
}
return null;
})
.filter(isNonNull),
}}
{...restModalProps}
/>
);
}
// TODO: 后续添加返会新增skill需要用到
// export const useSkillsModal = (props: SkillsModalProps) => {
// const pageJumpResp = usePageJumpResponse(PageType.BOT);
// // const [visible, setVisible] = useState(
// // pageJumpResp?.scene === SceneType.WORKFLOW__BACK__BOT &&
// // pageJumpResp.agentID === agentId &&
// // !!pageJumpResp.workflowModalState,
// // );
// const [visible, setVisible] = useState(false);
// const close = () => {
// setVisible(false);
// };
// const open = () => {
// setVisible(true);
// };
// useEffect(() => {
// if (visible) {
// pageJumpResp?.clearScene();
// }
// }, []);
// return {
// node: visible ? <SkillsModal {...props} onCancel={close} /> : null,
// close,
// open,
// };
// };

View File

@@ -0,0 +1,19 @@
/*
* 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 function isNonNull<T>(value: T | null): value is T {
return value !== null;
}

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 ReactNode } from 'react';
import classNames from 'classnames';
import { SheetView } from '@coze-agent-ide/space-bot/component';
import { I18n } from '@coze-arch/i18n';
import { IconBotMultiLeftBtnIcon } from '@coze-arch/bot-icons';
import { type BotMode } from '@coze-arch/bot-api/developer_api';
import s from '../../pages/index.module.less';
interface ToolSheetProps {
mode: BotMode;
titleNode: ReactNode;
children: ReactNode;
}
export const ToolSheet = ({ mode, titleNode, children }: ToolSheetProps) => (
<SheetView
headerClassName={classNames([
'coz-bg-plus',
'coz-fg-secondary',
s['sheet-view-left-header'],
s['sheet-view-new-header'],
])}
mode={mode}
title={I18n.t('bot_build_title')}
titleNode={titleNode}
slideProps={{
placement: 'left',
closeBtnTooltip: I18n.t('chatflow_develop_tooltip_hide'),
openBtnTooltip: I18n.t('chatflow_develop_tooltip_show'),
width: 400,
visible: true,
btnNode: <IconBotMultiLeftBtnIcon />,
}}
>
{children}
</SheetView>
);

View File

@@ -0,0 +1,68 @@
/*
* 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 queryString from 'query-string';
import { I18n } from '@coze-arch/i18n';
import { Toast, Space, Typography, Button } from '@coze-arch/coze-design';
import { appendUrlParam } from '@coze-arch/bot-utils';
import { ProductEntityType } from '@coze-arch/bot-api/product_api';
import { useNavigate } from 'react-router-dom';
const { Text } = Typography;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const useInitToast = (spaceId: any) => {
const navigate = useNavigate();
useEffect(() => {
if (!spaceId) {
return;
}
const queryParams = queryString.parse(location.search);
const { show_toast, entity_id, toast_entity_type } = queryParams || {};
if (show_toast === 'workflow_copy_success') {
Toast.success({
content: (
<Space spacing={6}>
<Text>
{Number(toast_entity_type as string) ===
ProductEntityType.ImageflowTemplate
? I18n.t('imageflow_detail_toast_createcopy_succeed')
: I18n.t('workflowstore_workflow_copy_successful')}
</Text>
<Button
color="primary"
onClick={() => {
window.open(
`/work_flow?space_id=${spaceId}&workflow_id=${entity_id}`,
);
}}
>
{I18n.t('workflowstore_continue_editing')}
</Button>
</Space>
),
});
const url = appendUrlParam(location.href, 'show_toast', undefined);
navigate(url.slice(`${location.protocol}//${location.host}`.length), {
replace: true,
});
}
}, [spaceId]);
};

View File

@@ -0,0 +1,252 @@
/* stylelint-disable declaration-no-important */
@import '@coze-common/assets/style/common.less';
.wrapper {
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
@apply bg-background-1;
}
.layout-hotzone {
z-index: calc(var(--chat-z-index-header, 50) + 1);
}
.text {
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
}
.container {
display: flex;
flex: 1;
flex-direction: row;
width: 100%;
// 动态撑满父容器且可以滚动
// @reference: https://stackoverflow.com/questions/14962468/how-can-i-combine-flexbox-and-vertical-scroll-in-a-full-height-app
min-height: 0;
&.store {
height: 100%;
}
}
.spin {
position: absolute;
z-index: 100;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgb(255 255 255 / 50%);
}
.develop-area {
overflow: hidden;
display: flex;
flex-direction: column;
@apply coz-bg-plus;
}
.develop-area-scroll {
overflow: auto;
flex: 1;
}
.setting-area {
overflow: hidden;
display: flex;
flex: 1 1;
flex-direction: column;
border-left: 1px solid rgb(28 29 35 / 12%);
.setting-title-block {
display: flex;
justify-content: flex-end;
width: 100%;
margin: 12px 0;
}
:global {
.semi-collapse-item {
border-bottom: none;
}
.semi-collapse-header {
margin-right: 0;
margin-left: 0;
}
.semi-collapse-content {
padding-right: 0;
padding-left: 0;
}
.semi-select {
width: 100%;
min-width: 80px;
}
.semi-collapsible-wrapper {
padding-left: 16px;
}
}
}
.message-area {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
min-width: 404px;
height: 100%;
transition: min-width 0.2s ease;
@apply bg-background-3;
}
.playground-neat {
.message-area {
min-width: 258px;
}
}
.title {
margin: 8px 0 0 4px !important;
}
.sheet-title-node-cover {
overflow: hidden;
display: flex;
flex: 1;
align-items: center;
}
.bj-cover {
@apply bg-background-3;
}
.bj-img-cover {
z-index: 20;
text-shadow: 0 0.5px 1px rgba(0, 0, 0, 25%);
// 有背景图时需要覆盖组件样式
background-color: transparent !important;
}
.bj-single-cover {
flex: 0 0 auto;
height: 64px !important;
}
.border-cover {
@apply bg-background-3;
}
.spin-wrapper.top-level {
width: 100%;
height: 100% !important;
:global {
.semi-spin-children {
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
}
}
}
.sheet-view-new-header {
height: 64px !important;
}
.sheet-view-left-header {
padding: 16px 16px 16px 28px !important;
}
.icon-button-16 {
cursor: pointer;
&:hover {
border-radius: 4px;
}
:global {
.semi-button {
&.semi-button-size-small {
height: 16px;
padding: 1px !important;
}
}
}
}
// 能力模块默认说明文案样式
.tip-text {
font-size: 14px;
font-weight: 400;
font-style: normal;
line-height: 22px;
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
}
.sheet-view-hidden {
display: none;
}
.left-sheet-config {
display: flex;
flex-direction: row;
flex-shrink: 0;
align-items: center;
}
.wrapper-single-with-tool-area-hidden {
grid-template-columns: 26fr 14fr !important;
}
.tool-card {
overflow: hidden;
display: grid;
grid-template-columns: 13fr 13fr;
height: 100%;
}
.tool-card-with-tool-area-hidden {
grid-template-columns: 26fr !important;
}
.tool-hidden {
display: none;
}
.config-left {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: flex-start;
}
.display-none {
display: none !important;
}
.debug-chat-header-padding {
padding-right: 24px;
padding-left: 24px;
}

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.
*/
export { useInitToast } from './hooks/use-init-toast';
export {
SingleMode,
WorkflowMode,
type SingleModeProps,
type WorkflowModeProps,
} from './modes';
export { SkillsModal } from './components/shortcut-skills-modal';

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 { SingleMode, type SingleModeProps } from './single-mode';
export { WorkflowMode, type WorkflowModeProps } from './workflow-mode';

View File

@@ -0,0 +1,52 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import Coachmark, { StepCard } from '@coze-common/biz-components/coachmark';
import promptDiffGuideI18NPNG from '../../../assets/coachmark/prompt-diff-guide.i18n.png';
import promptDiffGuideCNPNG from '../../../assets/coachmark/prompt-diff-guide.cn.png';
import modelSelectGuideI18NPNG from '../../../assets/coachmark/model-diff-guide.i18n.png';
import modelSelectGuideCNPNG from '../../../assets/coachmark/model-diff-guide.cn.png';
export const CoachMark = () => (
<Coachmark
steps={[
{
content: (
<StepCard
imgSrc={IS_OVERSEA ? promptDiffGuideI18NPNG : promptDiffGuideCNPNG}
content={I18n.t('compare_guide_description_prompt')}
title={I18n.t('compare_guide_title_prompt')}
/>
),
target: '#prompt_diff_coachmark_target',
},
{
content: (
<StepCard
imgSrc={
IS_OVERSEA ? modelSelectGuideI18NPNG : modelSelectGuideCNPNG
}
content={I18n.t('compare_guide_description_model')}
title={I18n.t('compare_guide_title_model')}
/>
),
target: '#model_select_coachmark_target',
},
]}
caseId="singleAgentDiffGuide"
/>
);

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 { useShallow } from 'zustand/react/shallow';
import { useDiffTaskStore } from '@coze-studio/bot-detail-store/diff-task';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { I18n } from '@coze-arch/i18n';
import { IconCozCompare } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
export const ModelDiffButton = (props: { readonly?: boolean }) => {
const { readonly } = props;
const { enterDiffMode } = useDiffTaskStore(
useShallow(state => ({
enterDiffMode: state.enterDiffMode,
})),
);
const { botId } = useBotInfoStore(
useShallow(state => ({
botId: state.botId,
})),
);
return (
<Button
icon={<IconCozCompare />}
color="highlight"
disabled={readonly}
onClick={() => {
sendTeaEvent(EVENT_NAMES.compare_mode_front, {
bot_id: botId,
compare_type: 'models',
from: 'compare_button',
source: 'bot_detail_page',
action: 'start',
});
enterDiffMode({ diffTask: 'model' });
}}
>
{I18n.t('compare_model_compare_model')}
</Button>
);
};

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ReactNode, useState } from 'react';
import { useShallow } from 'zustand/react/shallow';
import classNames from 'classnames';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
import { BotMode, TabStatus } from '@coze-arch/bot-api/developer_api';
import { AbilityAreaContainer } from '@coze-agent-ide/tool';
import { useBotPageStore } from '@coze-agent-ide/space-bot/store';
import {
ContentView,
BotDebugPanel,
} from '@coze-agent-ide/space-bot/component';
import s from '../../index.module.less';
import {
AgentConfigArea,
type AgentConfigAreaProps,
} from './section-area/agent-config-area/index';
import {
AgentChatArea,
type AgentChatAreaProps,
} from './section-area/agent-chat-area';
import { CoachMark } from './components/coach-mark';
export interface SingleModeProps
extends Omit<AgentConfigAreaProps, 'isAllToolHidden'>,
AgentChatAreaProps {
rightSheetSlot?: ReactNode;
chatAreaReadOnly?: boolean;
}
export const SingleMode: React.FC<SingleModeProps> = ({
rightSheetSlot,
renderChatTitleNode,
chatSlot,
chatHeaderClassName,
chatAreaReadOnly,
...agentConfigAreaProps
}) => {
const { isInit, historyVisible, pageFrom, defaultAllHidden } =
usePageRuntimeStore(
useShallow(state => ({
isInit: state.init,
historyVisible: state.historyVisible,
pageFrom: state.pageFrom,
defaultAllHidden: !Object.values(
state.botSkillBlockCollapsibleState,
).some(val => val !== TabStatus.Hide),
})),
);
const modeSwitching = useBotPageStore(state => state.bot.modeSwitching);
const isReadonly = useBotDetailIsReadonly();
const [isAllToolHidden, setIsAllToolHidden] = useState(defaultAllHidden);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onAllToolHiddenStatusChange = (_isAllToolHidden: any) => {
setIsAllToolHidden(_isAllToolHidden);
};
return (
<div
className={classNames(
s.container,
historyVisible && s['playground-neat'],
pageFrom === BotPageFromEnum.Store && s.store,
)}
>
<AbilityAreaContainer
enableToolHiddenMode
eventCallbacks={{
onAllToolHiddenStatusChange,
}}
isReadonly={isReadonly}
mode={BotMode.SingleMode}
modeSwitching={modeSwitching}
isInit={isInit}
>
<ContentView
mode={BotMode.SingleMode}
className={classNames({
[s['wrapper-single-with-tool-area-hidden']]: isAllToolHidden,
})}
>
<AgentConfigArea
isAllToolHidden={isAllToolHidden}
{...agentConfigAreaProps}
/>
<AgentChatArea
renderChatTitleNode={renderChatTitleNode}
chatSlot={chatSlot}
chatHeaderClassName={chatHeaderClassName}
chatAreaReadOnly={chatAreaReadOnly}
/>
</ContentView>
{/* Debug调试台 */}
<BotDebugPanel />
{rightSheetSlot}
<CoachMark />
</AbilityAreaContainer>
</div>
);
};

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.
*/
import { type ReactNode } from 'react';
import { useShallow } from 'zustand/react/shallow';
import classNames from 'classnames';
import { SingleSheet } from '@coze-agent-ide/space-bot/component';
import { I18n } from '@coze-arch/i18n';
import { type BotPageFromEnum } from '@coze-arch/bot-typings/common';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
import { BotDebugChatArea } from '@coze-agent-ide/chat-debug-area';
import s from '../../../index.module.less';
export interface AgentChatAreaProps {
renderChatTitleNode?: (params: {
pageFrom: BotPageFromEnum | undefined;
showBackground: boolean;
}) => ReactNode;
chatSlot?: ReactNode;
chatHeaderClassName?: string;
chatAreaReadOnly?: boolean;
}
export const AgentChatArea: React.FC<AgentChatAreaProps> = ({
renderChatTitleNode,
chatSlot,
chatHeaderClassName,
chatAreaReadOnly,
}) => {
const { showBackground } = useBotSkillStore(
useShallow(({ backgroundImageInfoList }) => ({
showBackground: Boolean(
backgroundImageInfoList?.[0]?.mobile_background_image?.origin_image_url,
),
})),
);
const { pageFrom } = usePageRuntimeStore(
useShallow(state => ({
pageFrom: state.pageFrom,
})),
);
return (
<SingleSheet
title={I18n.t('bot_preview_debug_title')}
titleClassName={classNames(
showBackground ? '!coz-fg-images-white' : '',
'!text-[16px]',
)}
containerClassName={classNames(
s['bj-cover'],
showBackground && `${s['bj-img-cover']}`,
'flex-none !h-12',
)}
headerSlotClassName="!h-12"
headerClassName={classNames(
s['debug-chat-header-padding'],
'!h-12 !text-[16px] !border-0',
`${s['border-cover']}`,
{
'!bg-transparent': showBackground,
},
chatHeaderClassName,
)}
titleNode={renderChatTitleNode?.({
pageFrom,
showBackground,
})}
renderContent={headerNode => (
<div className={classNames(s['message-area'])}>
<BotDebugChatArea
headerNode={headerNode}
readOnly={chatAreaReadOnly}
/>
{chatSlot}
</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 { useShallow } from 'zustand/react/shallow';
import classNames from 'classnames';
import { SingleSheet } from '@coze-agent-ide/space-bot/component';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { I18n } from '@coze-arch/i18n';
import { LayoutContext, PlacementEnum } from '@coze-arch/bot-hooks';
import { PromptView } from '@coze-agent-ide/prompt-adapter';
import { BotConfigArea } from '@coze-agent-ide/bot-config-area-adapter';
// eslint-disable-next-line @coze-arch/no-deep-relative-import
import s from '../../../../index.module.less';
import { ToolArea, type ToolAreaProps } from './tool-area';
export type AgentConfigAreaProps = ToolAreaProps & {
modelListExtraHeaderSlot?: React.ReactNode;
};
export const AgentConfigArea: React.FC<AgentConfigAreaProps> = props => {
const { editable, pageFrom } = usePageRuntimeStore(
useShallow(state => ({
editable: state.editable,
pageFrom: state.pageFrom,
})),
);
return (
<SingleSheet
headerClassName={classNames([
'coz-bg-plus',
'coz-fg-secondary',
'!h-12',
'!px-4',
'!py-0',
])}
title={I18n.t('bot_build_title')}
titleClassName="!text-[16px]"
titleNode={
<div className={s['sheet-title-node-cover']}>
<BotConfigArea
pageFrom={pageFrom}
editable={editable}
modelListExtraHeaderSlot={props.modelListExtraHeaderSlot}
/>
</div>
}
>
<div className={s['tool-card']}>
<LayoutContext value={{ placement: PlacementEnum.LEFT }}>
<PromptView />
</LayoutContext>
<ToolArea {...props} />
</div>
</SingleSheet>
);
};

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/no-deep-relative-import */
import classNames from 'classnames';
import { useModelStore } from '@coze-studio/bot-detail-store/model';
import { ShortcutToolConfig } from '@coze-common/chat-area-plugins-chat-shortcuts/shortcut-tool';
import { I18n } from '@coze-arch/i18n';
import { LayoutContext, PlacementEnum } from '@coze-arch/bot-hooks';
import { FormatType } from '@coze-arch/bot-api/knowledge';
import { BotMode, WorkflowMode } from '@coze-arch/bot-api/developer_api';
import {
WorkflowCard,
WorkflowModalFrom,
} from '@coze-agent-ide/workflow-card-adapter';
import { ToolGroupKey } from '@coze-agent-ide/tool-config';
import { GroupingContainer, ToolKey, ToolView } from '@coze-agent-ide/tool';
import { useDataSetArea } from '@coze-agent-ide/space-bot/hook';
import {
ChatBackground,
DataMemory,
Setting as DataSetSetting,
settingAreaScrollId,
SuggestionBlock,
} from '@coze-agent-ide/space-bot/component';
import { PluginApisArea } from '@coze-agent-ide/plugin-area-adapter';
import { OnboardingMessage } from '@coze-agent-ide/onboarding-message-adapter';
import s from '../../../../index.module.less';
import { SkillsModal } from '../../../../components/shortcut-skills-modal';
export interface ToolAreaProps {
isAllToolHidden: boolean;
skillToolSlot?: React.ReactNode;
knowledgeToolSlot?: React.ReactNode;
memoryToolSlot?: React.ReactNode;
dialogToolSlot?: React.ReactNode;
debugToolSlot?: React.ReactNode;
extraToolSlot?: React.ReactNode;
}
export const ToolArea: React.FC<ToolAreaProps> = props => {
const {
isAllToolHidden,
skillToolSlot,
knowledgeToolSlot,
memoryToolSlot,
dialogToolSlot,
extraToolSlot,
} = props;
const { node: DataSetArea, initRef: DataSetAreaRef } = useDataSetArea();
const modelId = useModelStore(state => state.config.model);
return (
<LayoutContext value={{ placement: PlacementEnum.CENTER }}>
<div
className={classNames(s['setting-area'], 'coz-bg-plus', {
[s['tool-hidden']]: isAllToolHidden,
})}
>
<div className="p-[12px] overflow-auto flex-1" id={settingAreaScrollId}>
<ToolView>
<GroupingContainer
title={I18n.t('bot_edit_type_skills')}
toolGroupKey={ToolGroupKey.SKILL}
>
{/* 工具 */}
<PluginApisArea
toolKey={ToolKey.PLUGIN}
title={I18n.t('Plugins')}
/>
{/* Workflow */}
<WorkflowCard
flowMode={WorkflowMode.Workflow}
toolKey={ToolKey.WORKFLOW}
title={I18n.t('Workflows')}
from={WorkflowModalFrom.BotSkills}
/>
{skillToolSlot}
</GroupingContainer>
<GroupingContainer
toolGroupKey={ToolGroupKey.KNOWLEDGE}
title={I18n.t('bot_edit_type_knowledge')}
actionNodes={<DataSetSetting modelId={modelId ?? ''} />}
>
{/* 知识库 */}
<DataSetArea
initRef={DataSetAreaRef}
toolKey={ToolKey.DOCUMENT}
title={I18n.t('dataset_detail_type_text')}
desc={I18n.t('bot_ide_knowledge_text_desc')}
formatType={FormatType.Text}
/>
<DataSetArea
initRef={DataSetAreaRef}
toolKey={ToolKey.TABLE}
title={I18n.t('dataset_detail_type_table')}
formatType={FormatType.Table}
desc={I18n.t('bot_ide_knowledge_table_desc')}
/>
<DataSetArea
initRef={DataSetAreaRef}
toolKey={ToolKey.PHOTO}
title={I18n.t('knowledge_photo_025')}
formatType={FormatType.Image}
desc={I18n.t('knowledge_photo_027')}
/>
{knowledgeToolSlot}
</GroupingContainer>
<GroupingContainer
toolGroupKey={ToolGroupKey.MEMORY}
title={I18n.t('bot_edit_type_memory')}
>
{/* 变量存储 */}
<DataMemory
toolKey={ToolKey.VARIABLE}
title={I18n.t('user_profile')}
/>
{memoryToolSlot}
</GroupingContainer>
<GroupingContainer
toolGroupKey={ToolGroupKey.DIALOG}
title={I18n.t('bot_edit_type_dialog')}
>
{/* 开场白 */}
<OnboardingMessage
toolKey={ToolKey.ONBOARDING}
title={I18n.t('bot_preview_opening_remarks')}
/>
{/* suggestion 开关 */}
<SuggestionBlock
toolKey={ToolKey.SUGGEST}
title={I18n.t('bot_edit_suggestion')}
/>
{/* 快捷键shortcut */}
<ShortcutToolConfig
skillModal={SkillsModal}
toolKey={ToolKey.SHORTCUT}
botMode={BotMode.SingleMode}
title={I18n.t('bot_ide_shortcut')}
/>
{/* 聊天背景图片 */}
<ChatBackground
toolKey={ToolKey.BACKGROUND}
title={I18n.t('bgi_title')}
/>
{dialogToolSlot}
</GroupingContainer>
{extraToolSlot}
</ToolView>
</div>
</div>
</LayoutContext>
);
};

View File

@@ -0,0 +1,249 @@
/*
* 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';
import { useShallow } from 'zustand/react/shallow';
import classNames from 'classnames';
import { ResizableLayout } from '@coze-studio/components';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import { I18n } from '@coze-arch/i18n';
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
import {
IconBotMultiLeftBtnIcon,
IconBotMultiRightBtnIcon,
} from '@coze-arch/bot-icons';
import { LayoutContext, PlacementEnum } from '@coze-arch/bot-hooks';
import { BotMode, TabStatus } from '@coze-arch/bot-api/developer_api';
import { WorkflowConfigArea } from '@coze-agent-ide/workflow';
import { ToolGroupKey } from '@coze-agent-ide/tool-config';
import {
GroupingContainer,
AbilityAreaContainer,
ToolKey,
ToolView,
} from '@coze-agent-ide/tool';
import { useBotPageStore } from '@coze-agent-ide/space-bot/store';
import {
DataMemory,
settingAreaScrollId,
ChatBackground,
SheetView,
ContentView,
BotDebugPanel,
} from '@coze-agent-ide/space-bot/component';
import { OnboardingMessage } from '@coze-agent-ide/onboarding-message-adapter';
import { BotDebugChatArea } from '@coze-agent-ide/chat-debug-area';
import { BotConfigArea } from '@coze-agent-ide/bot-config-area-adapter';
import s from '../../index.module.less';
export interface WorkflowModeProps {
rightSheetSlot?: ReactNode;
memoryToolSlot?: ReactNode;
dialogToolSlot?: ReactNode;
renderChatTitleNode?: (params: {
pageFrom: BotPageFromEnum | undefined;
showBackground: boolean;
}) => ReactNode;
chatSlot?: ReactNode;
chatHeaderClassName?: string;
chatAreaReadOnly?: boolean;
}
// eslint-disable-next-line @coze-arch/max-line-per-function
export const WorkflowMode: React.FC<WorkflowModeProps> = ({
rightSheetSlot,
memoryToolSlot,
dialogToolSlot,
renderChatTitleNode,
chatSlot,
chatHeaderClassName,
chatAreaReadOnly,
}) => {
const { isInit, editable, historyVisible, pageFrom, defaultAllHidden } =
usePageRuntimeStore(
useShallow(state => ({
editable: state.editable,
isInit: state.init,
historyVisible: state.historyVisible,
pageFrom: state.pageFrom,
defaultAllHidden: !Object.values(
state.botSkillBlockCollapsibleState,
).some(val => val !== TabStatus.Hide),
})),
);
const { showBackground } = useBotSkillStore(
useShallow(({ backgroundImageInfoList }) => ({
showBackground: Boolean(
backgroundImageInfoList?.[0]?.mobile_background_image?.origin_image_url,
),
})),
);
const modeSwitching = useBotPageStore(state => state.bot.modeSwitching);
const isReadonly = useBotDetailIsReadonly();
const toolArea = (
<div className="overflow-hidden h-full flex">
<LayoutContext value={{ placement: PlacementEnum.CENTER }}>
<div
className={classNames(s['setting-area'], 'coz-bg-plus', {
[s['tool-hidden']]: defaultAllHidden,
})}
>
<div
className="px-[28px] py-[16px] overflow-auto flex-1"
id={settingAreaScrollId}
>
<ToolView>
<WorkflowConfigArea />
<div className="mx-[-28px] my-[16px] border-0 border-b border-solid coz-stroke-primary" />
<GroupingContainer
toolGroupKey={ToolGroupKey.MEMORY}
title={I18n.t('bot_edit_type_memory')}
>
{/* 变量存储 */}
<DataMemory
toolKey={ToolKey.VARIABLE}
title={I18n.t('user_profile')}
/>
{memoryToolSlot}
</GroupingContainer>
<GroupingContainer
toolGroupKey={ToolGroupKey.DIALOG}
title={I18n.t('bot_edit_type_dialog')}
>
{/* 开场白 */}
<OnboardingMessage
toolKey={ToolKey.ONBOARDING}
title={I18n.t('bot_preview_opening_remarks')}
/>
{/* 聊天背景图片 */}
<ChatBackground
toolKey={ToolKey.BACKGROUND}
title={I18n.t('bgi_title')}
/>
{dialogToolSlot}
</GroupingContainer>
</ToolView>
</div>
</div>
</LayoutContext>
</div>
);
const leftSheet = (
<SheetView
headerClassName={classNames([
'coz-bg-plus',
'coz-fg-secondary',
s['sheet-view-left-header'],
s['sheet-view-new-header'],
])}
mode={BotMode.WorkflowMode}
title={I18n.t('bot_build_title')}
titleNode={
<div className={s['sheet-title-node-cover']}>
<BotConfigArea pageFrom={pageFrom} editable={editable} />
</div>
}
slideProps={{
placement: 'left',
closeBtnTooltip: I18n.t('chatflow_develop_tooltip_hide'),
openBtnTooltip: I18n.t('chatflow_develop_tooltip_show'),
width: 400,
visible: true,
btnNode: <IconBotMultiLeftBtnIcon />,
}}
>
{toolArea}
</SheetView>
);
const rightSheet = (
<SheetView
mode={BotMode.WorkflowMode}
title={I18n.t('bot_preview_debug_title')}
titleClassName={showBackground ? '!coz-fg-images-white' : ''}
containerClassName={classNames(
s['bj-cover'],
showBackground && `${s['bj-img-cover']} `,
s['bj-single-cover'],
)}
headerClassName={classNames(
s['debug-chat-header-padding'],
`${s['border-cover']}`,
{
'!bg-transparent': showBackground,
},
chatHeaderClassName,
)}
slideProps={{
placement: 'right',
closeBtnTooltip: I18n.t('chatflow_preview_tooltip_hide'),
openBtnTooltip: I18n.t('chatflow_preview_tooltip_show'),
width: 400,
visible: true,
btnNode: <IconBotMultiRightBtnIcon />,
}}
titleNode={renderChatTitleNode?.({ pageFrom, showBackground })}
renderContent={headerNode => (
<div className={classNames(s['message-area'])}>
<BotDebugChatArea
headerNode={headerNode}
readOnly={chatAreaReadOnly}
/>
{chatSlot}
</div>
)}
/>
);
return (
<div
className={classNames(
s.container,
historyVisible && s['playground-neat'],
pageFrom === BotPageFromEnum.Store && s.store,
)}
>
<AbilityAreaContainer
enableToolHiddenMode
isReadonly={isReadonly}
mode={BotMode.WorkflowMode}
modeSwitching={modeSwitching}
isInit={isInit}
>
<ContentView mode={BotMode.WorkflowMode}>
<ResizableLayout
className="h-full"
hotZoneClassName={s['layout-hotzone']}
>
<div className="w-1/2 min-w-[610px]">{leftSheet}</div>
<div className="w-1/2 min-w-[480px]">{rightSheet}</div>
</ResizableLayout>
</ContentView>
{/* Debug调试台 */}
<BotDebugPanel />
{rightSheetSlot}
</AbilityAreaContainer>
</div>
);
};

View File

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