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,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-agent-ide/layout
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,93 @@
{
"name": "@coze-agent-ide/layout",
"version": "0.0.1",
"description": "bot 编辑页 layout",
"license": "Apache-2.0",
"author": "meixuliang.3@bytedance.com",
"maintainers": [],
"main": "src/index.tsx",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-agent-ide/agent-ide-commons": "workspace:*",
"@coze-agent-ide/bot-creator-context": "workspace:*",
"@coze-agent-ide/bot-input-length-limit": "workspace:*",
"@coze-agent-ide/bot-plugin": "workspace:*",
"@coze-agent-ide/chat-area-provider-adapter": "workspace:*",
"@coze-agent-ide/chat-debug-area": "workspace:*",
"@coze-agent-ide/onboarding": "workspace:*",
"@coze-agent-ide/space-bot": "workspace:*",
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-error": "workspace:*",
"@coze-arch/bot-flags": "workspace:*",
"@coze-arch/bot-hooks": "workspace:*",
"@coze-arch/bot-icons": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/bot-space-api": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/bot-tea": "workspace:*",
"@coze-arch/bot-utils": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/logger": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"@coze-common/assets": "workspace:*",
"@coze-common/chat-area": "workspace:*",
"@coze-common/chat-core": "workspace:*",
"@coze-common/md-editor-adapter": "workspace:*",
"@coze-foundation/global-store": "workspace:*",
"@coze-foundation/layout": "workspace:*",
"@coze-foundation/space-store": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@coze-studio/components": "workspace:*",
"@coze-studio/entity-adapter": "workspace:*",
"@coze-studio/publish-manage-hooks": "workspace:*",
"@coze-studio/user-store": "workspace:*",
"@douyinfe/semi-icons": "^2.36.0",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3",
"immer": "^10.0.3",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
"react-helmet": "^6.1.0",
"zustand": "^4.4.7"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@rsbuild/core": "1.1.13",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/node": "18.18.9",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@types/react-helmet": "^6.1.11",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"react-is": ">= 16.8.0",
"react-router-dom": "^6.22.0",
"styled-components": ">= 2",
"stylelint": "^15.11.0",
"typescript": "~5.8.2",
"vite": "^4.3.9",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5",
"webpack": "~5.91.0"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

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 { type ReactNode } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { I18n } from '@coze-arch/i18n';
import {
IconCozPeopleFill,
IconCozTeamFill,
IconCozCheckMarkCircleFillPalette,
} from '@coze-arch/coze-design/icons';
import {
Avatar,
Typography,
Tag,
Popover,
IconButton,
} from '@coze-arch/coze-design';
import { formatDate } from '@coze-arch/bot-utils';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { IconEditNew } from '@coze-arch/bot-icons';
import { SpaceType } from '@coze-arch/bot-api/developer_api';
import { BotPublishStatus } from '../bot-publish-status';
const BotInfoCardContent = ({ deployButton }: { deployButton: ReactNode }) => {
const { botInfo } = useBotInfoStore(
useShallow(state => ({
botInfo: state,
})),
);
const {
space: { name: spaceName, space_type: spaceType },
} = useSpaceStore();
const isPersonal = spaceType === SpaceType.Personal;
return (
<div className="w-[260px] p-4 coz-bg-max">
<div className="flex items-center justify-center mb-7">
<Avatar size="medium" src={botInfo.icon_url} />
</div>
<div className="flex items-center justify-center gap-2 flex-col">
<Typography.Text strong className="!text-xxl !font-medium">
{botInfo.name}
</Typography.Text>
<div className="flex items-cente">
<Tag
color="primary"
className="max-w-[160px] !bg-transparent !coz-fg-secondary !p-0"
prefixIcon={
isPersonal ? <IconCozPeopleFill /> : <IconCozTeamFill />
}
>
{spaceName}
</Tag>
<BotPublishStatus deployButton={deployButton} />
</div>
{botInfo.description ? (
<Typography.Paragraph
className="text-sm coz-fg-primary"
ellipsis={{ rows: 2 }}
>
{botInfo.description}
</Typography.Paragraph>
) : null}
<div className="text-xs coz-fg-secondary">
{I18n.t('Create_time')}:{' '}
{botInfo.create_time ? formatDate(Number(botInfo.create_time)) : null}
</div>
</div>
</div>
);
};
interface BotInfoCardProps {
isReadonly: boolean;
editBotInfoFn: () => void;
deployButton: ReactNode;
}
export const BotInfoCard = ({
isReadonly,
editBotInfoFn,
deployButton,
}: BotInfoCardProps) => {
const { botInfo, noPublish } = useBotInfoStore(
useShallow(state => ({
botInfo: state,
noPublish: !state.has_publish,
})),
);
const triggerContent = (
<div className="flex items-center gap-2">
<div className="relative">
<Avatar
size="small"
shape="square"
src={botInfo?.icon_url}
className="rounded"
></Avatar>
{!noPublish ? (
<div className="absolute flex justify-center items-center -right-[1px] -bottom-[1px] w-3 h-3 text-[12px] coz-bg-max box-content border-[1.5px] border-solid rounded-small border-[#fff]">
<IconCozCheckMarkCircleFillPalette className="relative coz-fg-hglt-green" />
</div>
) : null}
</div>
<Typography.Title className="!text-[16px] !coz-fg-plus !font-medium !leading-[22px]">
{botInfo?.name}
</Typography.Title>
{!isReadonly && (
<IconButton
className="edit-btn"
color="secondary"
icon={<IconEditNew />}
theme="borderless"
onClick={() => {
editBotInfoFn();
}}
data-testid="bot.ide.bot_creator.bot-info-edit-create-edit-info-button"
/>
)}
</div>
);
return (
<Popover
content={<BotInfoCardContent deployButton={deployButton} />}
trigger="hover"
position="bottomLeft"
>
{triggerContent}
</Popover>
);
};

View File

@@ -0,0 +1,94 @@
/*
* 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 ReactNode } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { I18n } from '@coze-arch/i18n';
import {
IconCozCheckMarkCircleFill,
IconCozInfoCircleFill,
} from '@coze-arch/coze-design/icons';
import { Tag, Popover, Divider } from '@coze-arch/coze-design';
import { ConnectorDynamicStatus } from '@coze-arch/bot-api/developer_api';
import s from '../bot-status/style.module.less';
import { renderWarningContent } from '../bot-status/origin-status';
export const BotPublishStatus = ({
deployButton,
}: {
deployButton: ReactNode;
}) => {
const { connectors, noPublish } = useBotInfoStore(
useShallow(store => ({
noPublish: !store.has_publish,
connectors: store.connectors,
})),
);
const [visible, setVisible] = useState(false);
const renderPublishStatus = () => {
const warningList = connectors?.filter(
item => item.connector_status !== ConnectorDynamicStatus.Normal,
);
return warningList?.length ? (
<Popover
position="bottomLeft"
visible={visible}
content={renderWarningContent({
warningList,
onCancel: () => setVisible(false),
deployButton,
})}
trigger="custom"
>
<Divider layout="vertical" className="!h-3 mx-2" />
<Tag
color="yellow"
className="!p-0"
prefixIcon={<IconCozInfoCircleFill />}
onClick={() => {
setVisible(true);
}}
>
<div>{I18n.t('bot_status_published')}</div>
</Tag>
</Popover>
) : (
<>
<Divider layout="vertical" className="!h-3 mx-2" />
<Tag
color="primary"
className="!bg-transparent !p-0 !coz-fg-secondary"
prefixIcon={
<IconCozCheckMarkCircleFill className="coz-fg-hglt-green" />
}
>
{I18n.t('bot_status_published')}
</Tag>
</>
);
};
return (
<div className={s['status-tag']}>
{noPublish ? null : renderPublishStatus()}
</div>
);
};

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" fill="none" >
<circle cx="5" cy="5" r="5" fill="#C6C6CD" />
<path d="M4.49997 2.49923L4.49996 5.39945C4.49996 5.45469 4.54474 5.49946 4.59997 5.49945L7.5 5.49902" stroke="white" stroke-linecap="round" />
</svg>

After

Width:  |  Height:  |  Size: 298 B

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.
*/
export { OriginStatus, renderWarningContent } from './origin-status';

View File

@@ -0,0 +1,148 @@
/*
* 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 { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { I18n } from '@coze-arch/i18n';
import { IconCozLoading } from '@coze-arch/coze-design/icons';
import { Tag } from '@coze-arch/coze-design';
import { UIButton } from '@coze-arch/bot-semi';
import {
ConnectorDynamicStatus,
type ConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
import { IconAlertCircle } from '@douyinfe/semi-icons';
import s from './style.module.less';
export function OriginStatus() {
const { savingInfoSaving, savingInfoTime } = usePageRuntimeStore(
useShallow(store => ({
savingInfoSaving: store.savingInfo.saving,
savingInfoTime: store.savingInfo.time,
})),
);
return (
<>
<Tag
color="primary"
className="!bg-transparent !p-0 !text-xs"
loading={savingInfoSaving}
prefixIcon={savingInfoSaving ? <IconCozLoading /> : null}
>
{savingInfoSaving ? (
<div className={s['status-tag-spin']}>
<span>{I18n.t('bot_autosave_saving')}</span>
</div>
) : (
I18n.t('devops_publish_multibranch_auto_saved', {
time: savingInfoTime,
})
)}
</Tag>
</>
);
}
export const renderWarningContent = ({
warningList,
onCancel,
readonly,
deployButton = null,
}: {
warningList: ConnectorInfo[];
onCancel: () => void;
readonly?: boolean;
deployButton?: ReactNode;
}) => (
<div className={s['warning-content']}>
{/* TODO: 多个异常状态文案后续由接口返回, 目前只有discord存在异常状态走不到这里 */}
{warningList.length > 1 ? (
<>
<div className={s['title-box']}>
<IconAlertCircle
size="large"
style={{ color: 'var(--semi-color-warning)' }}
/>
<span className={s.title}>
{I18n.t('bot_pulish_offline_modal_title2', {
platform_number: warningList?.length,
})}
</span>
</div>
<div className={s.main}>
{warningList?.map(item => (
<div className={s['warning-list']}>
<h4>{item.name}</h4>
<span>
{I18n.t('bot_publish_offline_notice_no_certain_time', {
platform: item.name,
})}
</span>
</div>
))}
</div>
</>
) : (
<>
<div className={s['title-box']}>
<IconAlertCircle
size="large"
style={{ color: 'var(--semi-color-warning)' }}
/>
<span className={s.title}>
{warningList[0].connector_status === ConnectorDynamicStatus.Offline
? I18n.t('bot_pulish_offline_modal_title1', {
platform: warningList[0].name,
})
: warningList[0].name}
</span>
</div>
<div className={s.main}>
{warningList[0].connector_status === ConnectorDynamicStatus.Offline
? I18n.t('bot_publish_offline_notice_no_certain_time', {
platform: warningList[0].name,
})
: I18n.t('bot_publish_token_expired_notice', {
platform: warningList[0].name,
})}
</div>
</>
)}
{readonly ? (
<div className={s.footer}>
<UIButton theme="solid" type="warning" onClick={onCancel}>
{I18n.t('devops_publish_multibranch_i_know')}
</UIButton>
</div>
) : (
<div className={s.footer}>
<UIButton
theme="light"
type="tertiary"
className={s['cancel-btn']}
onClick={onCancel}
>
{I18n.t('Cancel')}
</UIButton>
{deployButton}
</div>
)}
</div>
);

View File

@@ -0,0 +1,153 @@
.bot-info-background() {
padding: 0 6px;
background: var(--Light-color-grey---grey-1, #F0F0F5);
border-radius: 4px;
}
.status-tag {
position: relative;
display: flex;
align-items: center;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: var(--light-usage-text-color-text-2, rgba(29, 28, 35, 60%));
letter-spacing: 0.12px;
&-success-box {
display: flex;
align-items: center;
padding: 4px;
border-radius: 4px;
}
&-warning-box {
cursor: pointer;
display: flex;
align-items: center;
padding: 4px;
border-radius: 4px;
&:hover {
background-color: rgba(46, 47, 56, 5%);
}
&-icon {
color: var(--semi-color-warning);
svg {
width: 10px;
height: 10px;
}
}
}
}
.status-tag-dot {
display: flex;
align-items: center;
svg {
margin-right: 4px;
}
img {
margin-right: 4px;
}
}
.saving-info {
margin-left: 8px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: var(--light-usage-text-color-text-3, rgba(29, 28, 35, 35%));
.bot-info-background();
.status-tag-spin {
display: flex;
align-items: center;
}
}
.warning-content {
width: 400px;
padding: 16px;
.title-box {
display: flex;
align-items: center;
margin-bottom: 12px;
.title {
margin-left: 8px;
font-size: 18px;
font-weight: 600;
line-height: 24px;
}
}
.main {
margin: 0 0 16px 28px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
.warning-list {
display: flex;
flex-direction: column;
margin-bottom: 8px;
span {
color: rgba(29, 28, 35, 60%);
}
}
}
.footer {
display: flex;
justify-content: flex-end;
.cancel-btn {
height: 38px;
margin-right: 16px;
}
}
}
// collaboration
.collaboration-tag {
display: flex;
align-items: center;
font-size: 12px;
font-style: normal;
line-height: 16px;
color: var(--Light-usage-text---color-text-2, rgba(29, 28, 35, 60%));
.bot-info-background();
:global(.semi-icon) {
margin-right: 4px;
}
.icon-submitted {
color: var(--semi-color-primary)
}
.icon-published {
color: var(--semi-color-success)
}
.icon-warning {
color: var(--semi-color-warning);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 { useNavigate } from 'react-router-dom';
import { useShallow } from 'zustand/react/shallow';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { getBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { type Type } from '@coze-arch/bot-semi/Button';
export interface DeployButtonProps {
btnType?: Type;
btnText?: string;
customStyle?: Record<string, string>;
readonly?: boolean;
tooltip?: string;
}
export const useDeployService = () => {
const navigate = useNavigate();
const { botId, botInfo, spaceId } = useBotInfoStore(
useShallow(s => ({
description: s.description,
botId: s.botId,
botInfo: s,
spaceId: s.space_id,
})),
);
const handleDeploy = () => {
if (!botId || getBotDetailIsReadonly()) {
return;
}
navigate(`/space/${spaceId}/bot/${botId}/publish`);
};
const handlePublish = () => {
sendTeaEvent(EVENT_NAMES.bot_publish_button_click, {
bot_id: botId || '',
bot_name: botInfo?.name || '',
});
handleDeploy();
};
return {
handlePublish,
} as const;
};

View File

@@ -0,0 +1,110 @@
/*
* 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 { Fragment } from 'react';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { I18n } from '@coze-arch/i18n';
import { IconCozCheckMarkCircleFillPalette } from '@coze-arch/coze-design/icons';
import { Tooltip } from '@coze-arch/coze-design';
import { type Type } from '@coze-arch/bot-semi/Button';
import { BotDebugButton } from '@coze-agent-ide/space-bot/component';
import { useDeployService } from './hooks/service';
export interface DeployButtonUIProps {
btnType?: Type;
btnText?: string;
customStyle?: Record<string, string>;
readonly?: boolean;
tooltip?: string;
onClick?: () => void;
showChangeTip?: boolean;
disabled?: boolean;
loading?: boolean;
}
export type DeployButtonProps = Omit<
DeployButtonUIProps,
'showChangeTip' | 'onClick' | 'disabled' | 'loading'
>;
export { useDeployService };
export const DeployButton: React.FC<DeployButtonProps> = props => {
const { handlePublish } = useDeployService();
const hasUnpublishChange = usePageRuntimeStore(s => s.hasUnpublishChange);
const showChangeTip = hasUnpublishChange;
return (
<DeployButtonUI
onClick={handlePublish}
showChangeTip={showChangeTip}
{...props}
/>
);
};
export const DeployButtonUI = ({
btnType = 'primary',
btnText = I18n.t('bot_publish_button'),
customStyle,
readonly = false,
tooltip,
showChangeTip,
onClick,
disabled,
loading,
}: DeployButtonUIProps) => {
const showTip = showChangeTip || !!tooltip;
const ToolTipCom = showTip ? Tooltip : Fragment;
const btn = (
<ToolTipCom
content={tooltip || I18n.t('bot_has_changes_tip')}
visible={showChangeTip}
>
<BotDebugButton
data-testid="agent-ide.goto.publish-button"
theme="solid"
type={btnType}
iconPosition="right"
icon={
showChangeTip ? (
<IconCozCheckMarkCircleFillPalette className="w-[5px] h-[5px]" />
) : undefined
}
style={customStyle}
disabled={disabled || readonly}
onClick={onClick}
loading={loading}
>
{btnText}
</BotDebugButton>
</ToolTipCom>
);
return disabled ? (
<Tooltip
content={I18n.t('devops_publish_multibranch_publish_disabled_tooltip')}
>
{btn}
</Tooltip>
) : (
btn
);
};

View File

@@ -0,0 +1,274 @@
/* stylelint-disable no-descending-specificity */
/* stylelint-disable max-nesting-depth */
@import '@coze-common/assets/style/mixins.less';
@import '@coze-common/assets/style/common.less';
.header {
z-index: 999;
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 8px;
padding-right: 12px;
background: var(--light-color-white);
border-bottom: 1px solid theme('colors.stroke.5');
.bot-avatar-ctn {
position: relative;
width: 32px;
height: 32px;
margin: 0 12px;
.bot-avatar {
width: 32px;
height: 32px;
border-radius: 8px;
}
}
.bot-info {
display: flex;
flex-direction: column;
.bot-info-title {
overflow: hidden;
// 最大宽度 = 视图宽度 - 一半视图宽度 - 名称到左边的距离 - 中间导航一半的距离 - 编辑icon的距离 - 编辑icon到右边导航的距离
max-width: calc(100vw - 50vw - 104px - 85px - 24px - 24px);
font-size: 14px;
font-weight: 600;
line-height: inherit;
color: var(--light-color-black-black, #000);
text-overflow: ellipsis;
white-space: nowrap;
}
.bot-info-item-gap {
margin-bottom: 2px;
}
.bot-info-item {
display: flex;
align-items: center;
height: 24px;
line-height: 24px;
}
.edit-btn {
margin-left: 2px;
color: #6b6d75;
:global {
svg {
width: 14px;
height: 14px;
}
}
}
.edit-btn-icon {
.common-svg-icon(16px, #6b6d75);
}
}
.team-info {
display: flex;
align-items: center;
margin-right: 8px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: var(--Light-usage-text---color-text-2, rgb(29 28 35 / 60%));
letter-spacing: 0.12px;
}
.team-avatar {
width: 12px;
height: 12px;
margin-right: 2px;
:global {
svg {
width: 12px;
height: 12px;
}
}
}
.bot-exit-btn {
:global {
.semi-button.semi-button-with-icon-only {
width: 32px;
padding: 4px;
}
}
}
.status-tag {
position: relative;
display: flex;
align-items: center;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: var(--Light-usage-text---color-text-2, rgb(29 28 35 / 60%));
letter-spacing: 0.12px;
&-success-box {
display: flex;
align-items: center;
border-radius: 4px;
}
&-warning-box {
cursor: pointer;
display: flex;
align-items: center;
border-radius: 4px;
&:hover {
background-color: rgb(46 47 56 / 5%);
}
&-icon {
color: var(--semi-color-warning);
svg {
width: 10px;
height: 10px;
}
}
}
}
.bot-info-background {
padding: 0 6px;
background: var(--Light-color-grey---grey-1, #F0F0F5);
border-radius: 4px;
}
.status-tag-dot {
display: flex;
align-items: center;
svg {
margin-right: 2px;
}
}
.edito-btn {
margin-left: 8px;
color: #6b6d75;
}
.status-tag-spin {
display: flex;
align-items: center;
}
.change-tip {
/* 133.333% */
margin-right: 20px;
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
color: var(--light-usage-text-color-text-2, rgb(29 28 35 / 60%));
}
.change-tip-icon {
.common-svg-icon(13px, rgba(255, 150, 0, 1));
}
.saving-info {
margin-left: 8px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: var(--Light-usage-text---color-text-2, rgb(29 28 35 / 60%));
}
button.icon-btn {
width: 32px;
min-width: 32px;
max-width: 32px;
height: 32px;
min-height: 32px;
max-height: 32px;
padding: 8px;
color: var(--light-usage-text-color-text-2, rgba(29, 28, 35, 60%));
background-color: transparent;
border: 1px solid var(--light-usage-border-color-border, rgba(29, 28, 35, 8%));
border-radius: 8px;
}
}
.warning-content {
width: 400px;
padding: 16px;
.title-box {
display: flex;
align-items: center;
margin-bottom: 12px;
.title {
margin-left: 8px;
font-size: 18px;
font-weight: 600;
line-height: 24px;
}
}
.main {
margin: 0 0 16px 28px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
.warning-list {
display: flex;
flex-direction: column;
margin-bottom: 8px;
span {
color: rgb(29 28 35 / 60%);
}
}
}
.footer {
display: flex;
justify-content: flex-end;
.cancel-btn {
height: 38px;
margin-right: 16px;
}
}
}
.bot-menu-nav {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.team-name {
max-width: 320px;
}

View File

@@ -0,0 +1,146 @@
/*
* 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 { useNavigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { type ReactNode, useEffect, useRef } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { cloneDeep } from 'lodash-es';
import cx from 'classnames';
import { useUpdateAgent } from '@coze-studio/entity-adapter';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { useDiffTaskStore } from '@coze-studio/bot-detail-store/diff-task';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import { BackButton } from '@coze-foundation/layout';
import { type SenderInfo, useBotInfo } from '@coze-common/chat-area';
import { I18n } from '@coze-arch/i18n';
import { renderHtmlTitle } from '@coze-arch/bot-utils';
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { type DraftBot } from '@coze-arch/bot-api/developer_api';
import {
ModeSelect,
type ModeSelectProps,
} from '@coze-agent-ide/space-bot/component';
import { BotInfoCard } from './bot-info-card';
import s from './index.module.less';
export interface BotHeaderProps {
pageName?: string;
isEditLocked?: boolean;
addonAfter?: ReactNode;
modeOptionList: ModeSelectProps['optionList'];
deployButton: ReactNode;
}
export const BotHeader: React.FC<BotHeaderProps> = props => {
const navigate = useNavigate();
const spaceID = useSpaceStore(state => state.space.id);
const isReadonly = useBotDetailIsReadonly();
const { pageFrom } = usePageRuntimeStore(
useShallow(state => ({
pageFrom: state.pageFrom,
})),
);
const botInfo = useBotInfoStore();
const { updateBotInfo } = useBotInfo();
const botInfoRef = useRef<DraftBot>();
useEffect(() => {
botInfoRef.current = botInfo as DraftBot;
}, [botInfo]);
const { modal: updateBotModal, startEdit: editBotInfoFn } = useUpdateAgent({
botInfoRef,
onSuccess: (
botID?: string,
spaceId?: string,
extra?: {
botName?: string;
botAvatar?: string;
},
) => {
updateBotInfo(oldBotInfo => {
const botInfoMap = cloneDeep(oldBotInfo);
if (!botID) {
return botInfoMap;
}
botInfoMap[botID] = {
url: extra?.botAvatar ?? '',
nickname: extra?.botName ?? '',
id: botID,
allowMention: false,
} satisfies SenderInfo;
return botInfoMap;
});
},
});
const diffTask = useDiffTaskStore(state => state.diffTask);
const goBackToBotList = () => {
navigate(`/space/${spaceID}/develop`);
};
return (
<>
<div className={cx(s.header, 'coz-bg-primary')}>
{/* page title */}
<Helmet>
<title>
{renderHtmlTitle(
pageFrom === BotPageFromEnum.Bot
? I18n.t('tab_bot_detail', {
bot_name: botInfo?.name ?? '',
})
: I18n.t('tab_explore_bot_detail', {
bot_name: botInfo?.name ?? '',
}),
)}
</title>
</Helmet>
{/** 1. 左侧bot信息区 */}
<div className="flex items-center">
<BackButton onClickBack={goBackToBotList} />
<BotInfoCard
isReadonly={isReadonly}
editBotInfoFn={editBotInfoFn}
deployButton={props.deployButton}
/>
{/** 模式选择器 */}
{diffTask || IS_OPEN_SOURCE ? null : (
<ModeSelect optionList={props.modeOptionList} />
)}
</div>
{/* 2. 中间bot菜单区 - 已下线 */}
{/* 3. 右侧bot状态区 */}
{props.addonAfter}
{updateBotModal}
</div>
</>
);
};

View File

@@ -0,0 +1,21 @@
div.link-img {
display: flex;
border-radius: unset;
img {
width: 100%;
height: 100%;
border: 1px solid var(--light-usage-fill-color-fill-1, rgb(46 46 56 / 8%));
border-radius: 4px;
}
}
.open-in-tips {
padding: 0 8px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: var(--light-usage-text-color-text-2, rgba(29, 28, 35, 60%));
}

View File

@@ -0,0 +1,238 @@
/*
* 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 { useNavigate } from 'react-router-dom';
import React, { type FC } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useIsPublishRecordReady } from '@coze-studio/publish-manage-hooks';
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 MORE_PLATFORM_ICON from '@coze-common/assets/image/more-platform-icon.jpg';
import { I18n } from '@coze-arch/i18n';
import { Divider } from '@coze-arch/bot-semi';
import { IconMenuLogo } from '@coze-arch/bot-icons';
import { useFlags } from '@coze-arch/bot-flags';
import { IntelligenceType } from '@coze-arch/bot-api/intelligence_api';
import {
BotMarketStatus,
ConnectorDynamicStatus,
type ConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
import { IconCozArrowRightFill, IconCozMore } from '@coze-arch/coze-design/icons';
import { Dropdown, IconButton, Tooltip } from '@coze-arch/coze-design';
import { LinkDropItem } from './link-drop-item';
import s from './index.module.less';
export type ExtendedConnectorInfo = Omit<ConnectorInfo, 'icon'> & {
icon?: string | React.ReactNode;
};
const LinkMenu = (props: {
connectors: ExtendedConnectorInfo[];
renderMorePlatform: boolean;
isReadOnly: boolean;
}) => {
const { connectors = [], renderMorePlatform, isReadOnly } = props;
return (
<Dropdown.Menu mode="menu">
{!isReadOnly && (
<>
<div className={s['open-in-tips']}>
{I18n.t('bot_list_open_button', {
platform: '',
})}
</div>
<Divider margin={2} layout="horizontal" />
</>
)}
{connectors.map(item => (
<LinkDropItem
linkInfo={item}
key={item.name}
hasMorePlatform={false}
isReadOnly={isReadOnly}
/>
))}
{renderMorePlatform ? (
<LinkDropItem
linkInfo={{
name: I18n.t('bot_share_more_platforms'),
icon: MORE_PLATFORM_ICON,
}}
hasMorePlatform
isReadOnly={isReadOnly}
/>
) : null}
</Dropdown.Menu>
);
};
// eslint-disable-next-line complexity, @coze-arch/max-line-per-function
export const MoreMenuButton: FC = () => {
const { hasPublish, connectors, version, botId, spaceId, botMarketStatus } =
useBotInfoStore(
useShallow(state => ({
botId: state.botId,
spaceId: state.space_id,
hasPublish: state.has_publish,
connectors: state.connectors,
version: state.version,
botMarketStatus: state.botMarketStatus,
})),
);
const { historyVisible } = usePageRuntimeStore(
useShallow(state => ({
historyVisible: state.historyVisible,
})),
);
const isReadOnly = useBotDetailIsReadonly();
const StoreConnector: ExtendedConnectorInfo = {
id: 'store',
name: I18n.t('bot_edit_store'),
icon: (
<div className="w-4 h-4 rounded-mini [&_.semi-icon-default]:w-full [&_.semi-icon-default]:h-full [&_svg]:w-full [&_svg]:h-full">
<IconMenuLogo />
</div>
),
share_link: `${window.location.origin}/store/agent/${botId}?bot_id=true`,
connector_status:
botMarketStatus === BotMarketStatus.Online
? ConnectorDynamicStatus.Normal
: ConnectorDynamicStatus.Offline,
};
const extendedConnectors = [
...(connectors as ExtendedConnectorInfo[]),
].concat(botMarketStatus === BotMarketStatus.Online ? [StoreConnector] : []);
const hasMorePlatform = extendedConnectors?.some(item => !item.share_link);
// 不展示open条件 1.来自 explore的bot(已经没有 explore 了) 2. 未发布过平台的bot 3. 历史版本的bot(考虑revert后展示) 4.发布过的所有平台都没有分享链接
const hideOpenIn =
!extendedConnectors?.length ||
(version && historyVisible) ||
extendedConnectors?.every(item => !item.share_link);
const navigate = useNavigate();
const [FLAGS] = useFlags();
//有编辑权限 && 有发布的业务线
const showPublishManageMenu = !isReadOnly && hasPublish;
const { ready, inited } = useIsPublishRecordReady({
type: IntelligenceType.Bot,
intelligenceId: botId,
spaceId,
enable:
showPublishManageMenu &&
// 社区版暂不支持该功能
FLAGS['bot.studio.publish_management'] &&
!IS_OPEN_SOURCE,
});
if (!showPublishManageMenu && hideOpenIn) {
return null;
}
const publishMenuItems = [
{
label: I18n.t('analytics_page_title'),
to: `/space/${spaceId}/publish/agent/${botId}?tab=analysis`,
},
{
label: I18n.t('release_management_trace'),
to: `/space/${spaceId}/publish/agent/${botId}?tab=logs`,
},
{
label: I18n.t('release_management_trigger'),
to: `/space/${spaceId}/publish/agent/${botId}?tab=triggers`,
},
];
return (
<Dropdown
render={
<Dropdown.Menu mode="menu">
{/* 社区版暂不支持该功能 */}
{showPublishManageMenu &&
FLAGS['bot.studio.publish_management'] &&
!IS_OPEN_SOURCE
? publishMenuItems.map(item => {
const menuItem = (
<Dropdown.Item
disabled={!ready}
onClick={() => navigate(item.to)}
>
{item.label}
</Dropdown.Item>
);
return ready || !inited ? (
menuItem
) : (
<Tooltip content={I18n.t('release_management_generating')}>
<div>{menuItem}</div>
</Tooltip>
);
})
: null}
{hideOpenIn ? null : (
<>
{/* 社区版暂不支持该功能 */}
{showPublishManageMenu &&
FLAGS['bot.studio.publish_management'] &&
!IS_OPEN_SOURCE ? (
<Dropdown.Divider />
) : null}
<Dropdown
render={
<LinkMenu
connectors={extendedConnectors?.filter(
item =>
item.share_link &&
// 只读态仅显示状态正常的发布渠道
(!isReadOnly ||
item.connector_status ===
ConnectorDynamicStatus.Normal),
)}
renderMorePlatform={!isReadOnly && !!hasMorePlatform}
isReadOnly={isReadOnly}
/>
}
>
<Dropdown.Item>
<div className="w-full flex items-center">
<div className="flex-1">
{I18n.t('release_management_openin')}
</div>
<IconCozArrowRightFill />
</div>
</Dropdown.Item>
</Dropdown>
</>
)}
</Dropdown.Menu>
}
>
<IconButton icon={<IconCozMore />} />
</Dropdown>
);
};

View File

@@ -0,0 +1,170 @@
/*
* 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 copy from 'copy-to-clipboard';
import { useBoolean, useRequest } from 'ahooks';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import {
getBotDetailDtoInfo,
updateHeaderStatus,
updateBotRequest,
} from '@coze-studio/bot-detail-store';
import { I18n } from '@coze-arch/i18n';
import {
Item,
UIIconButton,
Toast,
Tooltip,
Space,
Image,
} from '@coze-arch/bot-semi';
import {
ConnectorDynamicStatus,
type ConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
import { IconLink } from '@douyinfe/semi-icons';
import s from './index.module.less';
export type ExtendedConnectorInfo = Omit<ConnectorInfo, 'icon'> & {
icon?: string | ReactNode;
};
export const LinkDropItem = (props: {
linkInfo: ExtendedConnectorInfo;
hasMorePlatform: boolean;
isReadOnly: boolean;
}) => {
const { linkInfo, hasMorePlatform, isReadOnly } = props;
const [mouseIn, { setTrue, setFalse }] = useBoolean(false);
const { botId, mode } = useBotInfoStore(
useShallow(state => ({
botId: state.botId,
mode: state.mode,
})),
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onLinkCopy = (e: any) => {
if (!linkInfo.share_link) {
return;
}
e.stopPropagation();
const res = copy(linkInfo.share_link);
if (res) {
Toast.success({
showClose: false,
content: I18n.t('copy_success'),
});
}
};
const { run: updateBot } = useRequest(
async () => {
if (!botId || isReadOnly) {
return;
}
const { botSkillInfo } = getBotDetailDtoInfo();
const { data } = await updateBotRequest({
...botSkillInfo,
bot_mode: mode,
});
updateHeaderStatus(data);
},
{
manual: true,
},
);
const onConnectorClick = () => {
updateBot();
window.open(linkInfo.share_link);
};
const isDisableDropItem =
linkInfo.connector_status !== ConnectorDynamicStatus.Normal ||
hasMorePlatform;
const content = (function () {
return (
<div className={s['link-item']}>
<Space>
{linkInfo.icon ? (
typeof linkInfo.icon === 'string' ? (
<Image
src={linkInfo.icon}
width={16}
height={16}
preview={false}
className={s['link-img']}
/>
) : (
<div className="w-4 h-4 rounded-mini [&_.semi-icon-default]:w-full [&_.semi-icon-default]:h-full [&_svg]:w-full [&_svg]:h-full">
{linkInfo.icon}
</div>
)
) : null}
<div> {linkInfo.name} </div>
</Space>
<Tooltip content={I18n.t('Copy_link')} position="right">
{/* flow 目前不支持粘贴分享链接coming soon.. */}
{mouseIn &&
linkInfo.share_link &&
linkInfo.id !== FLOW_PUBLISH_ID &&
!isReadOnly ? (
<UIIconButton
icon={<IconLink />}
onClick={onLinkCopy}
type="tertiary"
className={s['copy-btn']}
iconSize="small"
/>
) : null}
</Tooltip>
</div>
);
})();
return (
<Item
onMouseEnter={setTrue}
onMouseLeave={setFalse}
disabled={isDisableDropItem}
onClick={onConnectorClick}
>
{isDisableDropItem ? (
<Tooltip
content={
hasMorePlatform
? I18n.t('bot_share_not_supported_opening')
: I18n.t('bot_publish_token_expired_notice', {
platform: linkInfo.name,
})
}
position="leftTop"
>
{content}
</Tooltip>
) : (
content
)}
</Item>
);
};

View File

@@ -0,0 +1,263 @@
/* stylelint-disable no-descending-specificity */
/* stylelint-disable no-duplicate-selectors */
/* 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%);
}
.playground-neat {
.message-area {
min-width: 258px;
}
}
.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;
}
}
:global {
.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;
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 4%),
0 0 1px 0 rgb(0 0 0 / 8%);
}
.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;
border-bottom: none !important;
}
.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;
}

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 {
BotEditorInitLayout,
type BotEditorLayoutSlot,
type BotEditorLayoutProps,
type CustomProviderProps,
} from './layout';
export { BotHeader, type BotHeaderProps } from './components/header';
export { MoreMenuButton } from './components/header/more-menu-button';
export {
DeployButtonUI,
DeployButton,
type DeployButtonProps,
type DeployButtonUIProps,
} from './components/header/deploy-button';
export {
renderWarningContent,
OriginStatus,
} from './components/header/bot-status';
export {
type BotEditorLayoutSlot,
type BotEditorLayoutProps,
type CustomProviderProps,
};
export default BotEditorInitLayout;

View File

@@ -0,0 +1,123 @@
/*
* 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 { useParams } from 'react-router-dom';
import React, {
useState,
type FC,
type PropsWithChildren,
type ReactNode,
type ComponentType,
} from 'react';
import classNames from 'classnames';
import { useUpdateEffect } from 'ahooks';
import { userStoreService } from '@coze-studio/user-store';
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import { Spin } from '@coze-arch/bot-semi';
import { CustomError } from '@coze-arch/bot-error';
import { useBotPageStore } from '@coze-agent-ide/space-bot/store';
import { BotDebugChatAreaProviderAdapter } from '@coze-agent-ide/chat-area-provider-adapter';
import s from './index.module.less';
export interface CustomProviderProps {
botId: string;
}
export interface BotEditorLayoutProps {
hasHeader?: boolean;
}
export interface BotEditorLayoutSlot {
header?: ReactNode;
headerBottom?: ReactNode;
headerTop?: ReactNode;
customProvider?: ComponentType<PropsWithChildren<CustomProviderProps>>;
}
const DefaultFragment: React.FC<PropsWithChildren<CustomProviderProps>> = ({
children,
}) => <React.Fragment>{children}</React.Fragment>;
const BotEditorInitLayoutImpl: FC<
PropsWithChildren<
Omit<BotEditorLayoutProps, 'loading'> &
BotEditorLayoutSlot &
CustomProviderProps
>
> = ({
children,
botId,
hasHeader = true,
headerBottom,
headerTop,
header,
customProvider,
}) => {
// 初次加载
const [isFirstLoad, setIsFirstLoad] = useState(true);
const init = usePageRuntimeStore(state => state.init);
const userInfo = userStoreService.useUserInfo();
const modeSwitching = useBotPageStore(state => state.bot.modeSwitching);
const CustomProvider = customProvider || DefaultFragment;
// 因为clearStore会保留init值在切换bot时init是true不会是初始值false
useUpdateEffect(() => {
// init每次initStore都会被更新状态但这里只需要记录初次的loading所以需要对isFirstLoad判断
if (isFirstLoad && init) {
// 如果init完成并且是首次load表示初次请求完成将isFirstLoad置为false
setIsFirstLoad(false);
}
}, [init]);
return (
<div className={s.wrapper}>
{isFirstLoad && !init ? (
<Spin spinning wrapperClassName="h-full w-full" />
) : (
<CustomProvider botId={botId}>
<BotDebugChatAreaProviderAdapter
botId={botId}
userId={userInfo?.user_id_str}
>
<Spin
spinning={modeSwitching}
wrapperClassName={classNames(s['spin-wrapper'], s['top-level'])}
>
{headerTop}
{hasHeader ? header : null}
{headerBottom}
{children}
</Spin>
</BotDebugChatAreaProviderAdapter>
</CustomProvider>
)}
</div>
);
};
export const BotEditorInitLayout: React.FC<
PropsWithChildren<BotEditorLayoutProps & BotEditorLayoutSlot>
> = props => {
const { bot_id } = useParams<DynamicParams>();
if (!bot_id) {
throw new CustomError('normal_error', 'failed to get bot_id');
}
return <BotEditorInitLayoutImpl {...props} botId={bot_id} />;
};
export default BotEditorInitLayout;

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' />

View File

@@ -0,0 +1,129 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"types": [],
"strictNullChecks": true,
"noImplicitAny": true,
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../arch/bot-error/tsconfig.build.json"
},
{
"path": "../../arch/bot-flags/tsconfig.build.json"
},
{
"path": "../../arch/bot-hooks/tsconfig.build.json"
},
{
"path": "../../arch/bot-space-api/tsconfig.build.json"
},
{
"path": "../../arch/bot-store/tsconfig.build.json"
},
{
"path": "../../arch/bot-tea/tsconfig.build.json"
},
{
"path": "../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../arch/bot-utils/tsconfig.build.json"
},
{
"path": "../../arch/i18n/tsconfig.build.json"
},
{
"path": "../../arch/logger/tsconfig.build.json"
},
{
"path": "../../arch/report-events/tsconfig.build.json"
},
{
"path": "../bot-input-length-limit/tsconfig.build.json"
},
{
"path": "../bot-plugin/entry/tsconfig.build.json"
},
{
"path": "../chat-area-provider-adapter/tsconfig.build.json"
},
{
"path": "../chat-debug-area/tsconfig.build.json"
},
{
"path": "../../common/assets/tsconfig.build.json"
},
{
"path": "../../common/chat-area/chat-area/tsconfig.build.json"
},
{
"path": "../../common/chat-area/chat-core/tsconfig.build.json"
},
{
"path": "../../common/md-editor-adapter/tsconfig.build.json"
},
{
"path": "../commons/tsconfig.build.json"
},
{
"path": "../../components/bot-icons/tsconfig.build.json"
},
{
"path": "../../components/bot-semi/tsconfig.build.json"
},
{
"path": "../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../context/tsconfig.build.json"
},
{
"path": "../../foundation/global-store/tsconfig.build.json"
},
{
"path": "../../foundation/layout/tsconfig.build.json"
},
{
"path": "../../foundation/space-store/tsconfig.build.json"
},
{
"path": "../onboarding/tsconfig.build.json"
},
{
"path": "../space-bot/tsconfig.build.json"
},
{
"path": "../../studio/components/tsconfig.build.json"
},
{
"path": "../../studio/entity-adapter/tsconfig.build.json"
},
{
"path": "../../studio/publish-manage-hooks/tsconfig.build.json"
},
{
"path": "../../studio/stores/bot-detail/tsconfig.build.json"
},
{
"path": "../../studio/user-store/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,18 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"types": ["vitest/globals"],
"strictNullChecks": true,
"noImplicitAny": true
}
}

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.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
});