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/agent-publish
> 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,74 @@
{
"name": "@coze-agent-ide/agent-publish",
"version": "0.0.1",
"description": "agent publish",
"license": "Apache-2.0",
"author": "gaoyuanhan.duty@bytedance.com",
"maintainers": [],
"main": "src/index.ts",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@blueprintjs/core": "^5.1.5",
"@coze-agent-ide/agent-ide-commons": "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/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/idl": "workspace:*",
"@coze-arch/logger": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"@coze-arch/report-tti": "workspace:*",
"@coze-foundation/layout": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@coze-studio/components": "workspace:*",
"@coze-studio/open-auth": "workspace:*",
"@coze-studio/premium-components-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",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
"react-markdown": "^8.0.3",
"react-router-dom": "^6.22.0"
},
"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:*",
"@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/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

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 { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
import { AuthStatus } from '@coze-arch/idl/developer_api';
import { I18n } from '@coze-arch/i18n';
import { UIModal } from '@coze-arch/bot-semi';
import { useResetLocationState } from '@coze-arch/bot-hooks';
// 三方授权失败callback至发布页需要显式阻塞弹窗
export const useAuthFail = () => {
const { state } = useLocation();
const { authFailMessage = '', authStatus } = (state ??
history.state ??
{}) as Record<string, unknown>;
const resetLocationState = useResetLocationState();
useEffect(() => {
if (authStatus === AuthStatus.Unauthorized && authFailMessage) {
resetLocationState();
UIModal.warning({
title: I18n.t('bot_publish_columns_status_unauthorized'),
content: authFailMessage as string,
okText: I18n.t('got_it'),
hasCancel: false,
});
}
}, [authStatus, resetLocationState, authFailMessage]);
};

View File

@@ -0,0 +1,156 @@
/*
* 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, useParams } from 'react-router-dom';
import { useEffect, useCallback } from 'react';
import { useSafeState, useUnmountedRef } from 'ahooks';
import { logger } from '@coze-arch/logger';
import { type PluginAPIDetal } from '@coze-arch/idl/playground_api';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import { getFlags } from '@coze-arch/bot-flags';
import { type PluginPricingRule } from '@coze-arch/bot-api/plugin_develop';
import {
MonetizationEntityType,
type BotMonetizationConfigData,
} from '@coze-arch/bot-api/benefit';
import {
PlaygroundApi,
PluginDevelopApi,
benefitApi,
} from '@coze-arch/bot-api';
import { useBotModeStore } from '@coze-agent-ide/space-bot/store';
import { type PublisherBotInfo } from '@coze-agent-ide/space-bot';
const DEFAULT_BOT_INFO: PublisherBotInfo = {
name: '',
description: '',
prompt: '',
};
// 获取plugin收费插件信息
const getPricingRules: (
pluginApiDetailMap?: Record<string | number, PluginAPIDetal>,
) => Promise<PluginPricingRule[] | undefined> = async pluginApiDetailMap => {
if (!pluginApiDetailMap) {
return undefined;
}
const { pricing_rules } = await PluginDevelopApi.BatchGetPluginPricingRules({
plugin_apis: Object.keys(pluginApiDetailMap)?.map(item => ({
name: pluginApiDetailMap[item].name,
plugin_id: pluginApiDetailMap[item].plugin_id,
api_id: item,
})),
});
return pricing_rules;
};
// 是否有plugin
const hasPluginApi: (
pluginApiDetailMap?: Record<string | number, PluginAPIDetal>,
) => boolean = pluginApiDetailMap =>
!!(pluginApiDetailMap && Array.isArray(Object.keys(pluginApiDetailMap)));
export const useGetPublisherInitInfo: () => {
botInfo: PublisherBotInfo;
monetizeConfig: BotMonetizationConfigData | undefined;
} = () => {
const params = useParams<DynamicParams>();
const navigate = useNavigate();
const { bot_id, commit_version } = params;
const unmountedRef = useUnmountedRef();
const setIsCollaboration = useBotModeStore(s => s.setIsCollaboration);
const setSafeIsCollaboration = useCallback((currentState: boolean) => {
/** if component is unmounted, stop update */
if (unmountedRef.current) {
return;
}
setIsCollaboration(currentState);
}, []);
const [botInfo, setBotInfo] =
useSafeState<PublisherBotInfo>(DEFAULT_BOT_INFO);
const [monetizeConfig, setMonetizeConfig] = useSafeState<
BotMonetizationConfigData | undefined
>();
useEffect(() => {
if (!bot_id) {
navigate('/', { replace: true });
return;
}
(async () => {
try {
const FLAGS = getFlags();
const [botInfoResp, monetizeResp] = await Promise.all([
PlaygroundApi.GetDraftBotInfoAgw({ bot_id, commit_version }),
FLAGS['bot.studio.monetize_config']
? benefitApi.PublicGetBotMonetizationConfig({
entity_id: bot_id,
entity_type: MonetizationEntityType.Bot,
})
: Promise.resolve(undefined),
]);
setMonetizeConfig(monetizeResp?.data);
const {
bot_info,
in_collaboration,
branch,
has_publish,
bot_option_data,
} = botInfoResp?.data ?? {};
// 获取plugin扣费信息
let pluginPricingRules: Array<PluginPricingRule> = [];
if (
hasPluginApi(bot_option_data?.plugin_api_detail_map) &&
!IS_OPEN_SOURCE
) {
pluginPricingRules =
(await getPricingRules(bot_option_data?.plugin_api_detail_map)) ??
[];
}
const {
name = '',
prompt_info,
description = '',
bot_mode,
business_type,
} = bot_info;
setBotInfo({
name,
prompt: prompt_info?.prompt ?? '',
description,
branch,
botMode: bot_mode,
hasPublished: has_publish,
pluginPricingRules,
businessType: business_type,
});
setSafeIsCollaboration(!!in_collaboration);
} catch (error) {
logger.error({ error: error as Error });
}
})();
}, []);
return {
botInfo,
monetizeConfig,
};
};

View File

@@ -0,0 +1,567 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable custom-property-pattern */
.publish-header {
@apply coz-bg-primary;
flex-shrink: 0;
height: 74px;
padding: 16px;
border-bottom: 1px solid theme('colors.stroke.5');
.header {
display: flex;
align-items: center;
align-self: stretch;
justify-content: space-between;
width: 100%;
}
.title {
margin-left: 12px;
font-size: 18px;
font-weight: 600;
font-style: normal;
color: var(--light-color-black-black, #000);
}
}
.publish-table-wrapper {
height: fit-content;
margin-bottom: 24px;
&:last-child {
margin-bottom: 64px;
}
}
.publish-content {
overflow-y: auto;
width: 100%;
height: 100%;
margin: auto;
padding: 0;
padding-top: 24px;
}
.link-underline {
text-decoration: underline;
}
.publish-wrapper .publish-title-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 40px;
margin-bottom: 16px;
font-size: 18px;
line-height: 24px;
color: var(--light-usage-text-color-text-0, #1c1d23);
.publish-title {
margin: 0;
font-size: 18px;
font-weight: 600;
line-height: 24px;
color: var(--light-usage-text-color-text-0, #1c1d23);
}
}
.changelog-textarea_disabled {
pointer-events: none;
:global {
.semi-input-clearbtn {
visibility: hidden;
}
}
}
.publish-desc {
display: block;
margin-bottom: 16px;
font-size: 14px;
line-height: 20px;
color: var(--Light-usage-text---color-text-1, rgb(29 28 35 / 80%));
}
.publish-result-container .publish-result-tip {
display: flex;
align-items: center;
margin-bottom: 24px;
padding: 24px;
font-size: 18px;
font-weight: 600;
line-height: 24px;
color: var(--light-color-black-black, #000);
:global {
.semi-banner-extra {
margin-top: 0;
}
}
}
.publish-footer {
justify-content: flex-end;
width: 100%;
padding: 24px 0;
}
.publish-wrapper .text-label {
font-size: 18px;
font-weight: 600;
font-style: normal;
line-height: 24px;
color: var(--Light-usage-text---color-text-0, #1C1D23);
}
.text-area {
margin-top: 12px;
}
.publish-result-table {
border-top: 1px solid var(--semi-color-border);
:global {
.semi-table-header {
display: none;
}
.semi-table-row:hover>.semi-table-row-cell {
background-color: transparent !important;
border-bottom: 1px solid var(--semi-color-border) !important;
}
// tr hover 无样式
.semi-table-row:hover>.semi-table-row-cell::before {
content: '';
width: 100%;
height: 0;
}
}
}
.publish-table {
margin-top: 0;
.diff-icon {
cursor: pointer !important;
width: 14px;
}
:global {
.semi-table-container {
background: #fff;
border: 1px solid rgb(29 28 35 / 12%);
// padding: 0 16px;
border-radius: 12px;
}
// 全选按钮不展示
.semi-table-thead>.semi-table-row>.semi-table-row-head:first-child {
.semi-table-selection-wrap {
display: none;
}
}
.semi-table-header {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.semi-table-body {
border-bottom-right-radius: 12px;
border-bottom-left-radius: 12px;
}
.semi-table-tbody>.semi-table-row:hover>.semi-table-row-cell {
border-radius: 0 !important;
}
.semi-table-thead>.semi-table-row>.semi-table-row-head {
height: 40px;
padding: 0;
background-color: transparent;
border-radius: unset;
// &:first-child {
// padding-left: 12px;
// }
}
.semi-table-row {
background-color: #fff !important;
}
.semi-table-tbody .semi-table-row .semi-table-row-cell {
padding: 10px 0 !important;
line-height: 14px;
// &:first-child {
// padding-left: 12px !important;
// }
}
.semi-table-tbody>.semi-table-row:last-child:hover>.semi-table-row-cell {
border-bottom: none !important;
}
.semi-table .semi-table-selection-wrap {
width: 16px;
}
// .semi-table-row:hover>.semi-table-row-cell {
// background-color: #fff !important;
// border-bottom: 1px solid var(--semi-color-border) !important;
// }
// tr hover 无样式
.semi-table-row:hover>.semi-table-row-cell::before {
content: '';
width: 100%;
height: 0;
}
.semi-table-tbody>.semi-table-row:last-child>.semi-table-row-cell {
border-bottom: none;
}
}
.orange-tag {
margin-left: 8px;
font-size: 12px;
font-weight: 500;
font-style: normal;
line-height: 16px;
color: var(--light-color-orange-orange-5, rgb(255 150 0 / 100%));
background: var(--light-color-orange-orange-1, #FFF1CC);
border-radius: 6px;
svg {
width: 10px;
height: 10px;
margin-left: 4px;
}
}
.grey-info {
padding: 5px;
color: var(--light-color-grey-grey-5, rgb(107 107 117 / 100%));
background: var(--light-color-grey-grey-1, #F0F0F5);
border-radius: 6px;
svg {
width: 10px;
height: 10px;
}
}
.disable-tooltip {
position: absolute;
top: 2px;
left: 0;
width: 16px;
height: 16px;
}
.platform-avater {
flex-shrink: 0;
width: 26px;
height: 26px;
background: #fff;
border: 1px solid var(--light-usage-fill-color-fill-1, rgb(46 46 56 / 8%));
border-radius: 4px;
}
.platform-name {
max-width: 140px !important;
font-size: 14px !important;
font-weight: 600 !important;
line-height: 20px;
color: var(--light-usage-text-color-text-0, #1c1d23);
}
.platform-info {
width: calc(100% - 50px);
.platform-desc {
display: block;
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
}
}
.config-status {
.markdown {
max-width: 265px;
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-0, #1D1C23);
}
:global {
.semi-tag {
margin-right: 4px;
font-weight: 500;
svg {
width: 10px;
height: 10px;
margin-left: 4px;
}
}
.semi-button-borderless {
height: 24px;
padding: 0 8px;
font-size: 12px;
line-height: 20px;
}
}
}
}
.config-area {
.config-step-area {
display: flex;
// padding: 16px;
// padding-bottom: 32px;
flex-direction: column;
gap: 8px;
align-items: flex-start;
align-self: stretch;
margin-top: 32px;
border-radius: 8px;
// border: 1px solid var(--light-usage-border-color-border-1, rgba(29, 28, 35, 0.12));
.step-order {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
font-weight: 600;
color: var(--light-color-white-white, #fff);
background: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 50%;
}
.step-content {
// gap: 8px;
// display: flex;
// flex-direction: column;
}
.step-title {
/* 157.143% */
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: #000;
}
.step-link {
margin-bottom: 8px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-3, rgb(29 28 35 / 35%));
}
}
.config-form {
width: 100%;
.disable-field {
padding: 12px 0 24px;
.title {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
}
}
:global {
.semi-form-field-label {
margin-bottom: 8px;
}
.semi-form-field-label-text {
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
}
.no-eye {
:global {
// app_secret纯密文不展示眼睛
.semi-input-modebtn {
display: none;
}
}
}
}
.start-text {
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-0, #1D1C23);
}
.config-link {
font-size: 12px;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4D53E8);
}
.step-order {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
font-weight: 600;
color: var(--light-color-white-white, #fff);
background: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 50%;
}
.step-title {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: #000;
}
.markdown {
font-size: 14px;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
.link-area {
margin-top: 32px;
}
.link-list {
margin-top: 16px;
.title {
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
.semi-form-field-error-message {
position: absolute;
}
}
}
.publish-modal {
:global {
.semi-modal {
width: 560px;
}
.semi-modal-content {
.semi-modal-header {
margin-bottom: 0;
}
.semi-modal-body {
padding-bottom: 12px;
}
}
.semi-modal-footer {
margin-top: 32px;
}
}
}
.check-error {
ul {
margin: 0;
padding-left: 14px;
}
li {
padding-top: 6px;
}
}
.diff-modal {
:global {
.semi-modal {
width: 960px;
}
.semi-modal-content {
height: 740px;
}
}
}
.diff-modal-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,200 @@
/*
* 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, useParams } from 'react-router-dom';
import { useMemo, useRef, useState } from 'react';
import { useRequest } from 'ahooks';
import { userStoreService } from '@coze-studio/user-store';
import { BackButton } from '@coze-foundation/layout';
import { useReportTti } from '@coze-arch/report-tti';
import {
createReportEvent,
REPORT_EVENTS as ReportEventNames,
} from '@coze-arch/report-events';
import { useErrorHandler } from '@coze-arch/logger';
import { I18n } from '@coze-arch/i18n';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import { SpaceApi } from '@coze-arch/bot-space-api';
import { UILayout, UIButton, Spin, Tooltip } from '@coze-arch/bot-semi';
import {
Publish,
type PublishConnectorInfo,
type ConnectorBrandInfo,
} from '@coze-arch/bot-api/developer_api';
import {
type PublishResultInfo,
type PublishRef,
PublishDisabledType,
} from '@coze-agent-ide/space-bot';
import { PublishTableContext, PublishTable } from './publish-table';
import { PublishResult } from './publish-result';
import { useGetPublisherInitInfo } from './hooks/use-get-bot-info';
import { useAuthFail } from './hooks/use-auth-fail';
import styles from './index.module.less';
const getPublishPlatformEvent = createReportEvent({
eventName: ReportEventNames.publishPlatform,
});
export const AgentPublishPage = () => {
const params = useParams<DynamicParams>();
const navigate = useNavigate();
const errorHandler = useErrorHandler();
const { bot_id, commit_version } = params;
const { botInfo, monetizeConfig } = useGetPublisherInitInfo();
const [publishStatus, setPublishStatus] = useState(Publish.NoPublish);
const [connectInfoList, setConnectInfoList] =
useState<PublishConnectorInfo[]>();
const [connectorBrandInfoMap, setConnectorBrandInfoMap] =
useState<Record<string, ConnectorBrandInfo>>();
const [publishResult, setPublishResult] = useState<PublishResultInfo>();
const [publishDisabled, setPublishDisabled] = useState<PublishDisabledType>();
const [publishLoading, setPublishLoading] = useState(false);
const [canOpenSource, setCanOpenSource] = useState(false);
const [publishTips, setPublishTips] = useState<string>('');
const publishRef = useRef<PublishRef>(null);
const userAuthInfos = userStoreService.useUserAuthInfo();
useAuthFail();
const { loading, refresh } = useRequest(
async () => {
const res = await SpaceApi.PublishConnectorList({
bot_id: bot_id ?? '',
commit_version,
});
return res;
},
{
onBefore: () => {
getPublishPlatformEvent.start();
},
onSuccess: data => {
getPublishPlatformEvent.success();
setConnectInfoList(data?.publish_connector_list);
setConnectorBrandInfoMap(data?.connector_brand_info_map);
setCanOpenSource(
data?.submit_bot_market_option?.can_open_source ?? true,
);
setPublishTips(data?.publish_tips?.cost_tips ?? '');
},
onError: error => {
getPublishPlatformEvent.error({ error, reason: error.message });
errorHandler(error);
},
refreshDeps: [userAuthInfos],
},
);
useReportTti({ isLive: !loading });
const goBack = () => {
navigate(`/space/${params.space_id}/bot/${params.bot_id}`);
};
const handlePublish = () => {
publishRef.current?.publish();
};
const disabledTooltip = useMemo(() => {
if (publishDisabled === PublishDisabledType.NotSelectCategory) {
return I18n.t('publish_tooltip_select_category');
} else if (publishDisabled === PublishDisabledType.NotSelectPlatform) {
return I18n.t('publish_tooltip_select_platform');
} else if (publishDisabled === PublishDisabledType.NotSelectIndustry) {
return I18n.t('dy_avatar_evaluation_publish_tip');
}
}, [publishDisabled]);
const publishBtn = (
<UIButton
theme="solid"
//解决异步请求botInfo未返回时可以点击publish产生的错误
disabled={Boolean(publishDisabled) || !botInfo.name}
loading={publishLoading}
onClick={handlePublish}
data-testid="agent-ide.publish-button"
>
{I18n.t('Publish')}
</UIButton>
);
return (
<UILayout title={`${botInfo?.name} - Publish`}>
<UILayout.Header className={styles['publish-header']}>
<div className={styles.header}>
<div className="flex items-center">
<BackButton onClickBack={goBack} />
<div className={styles.title}>
{I18n.t('card_builder_releaseBtn_release_btn')}
</div>
</div>
{publishStatus === Publish.NoPublish ? (
disabledTooltip ? (
<Tooltip content={disabledTooltip}>{publishBtn}</Tooltip>
) : (
publishBtn
)
) : (
<UIButton
theme="solid"
onClick={() => {
goBack();
}}
>
{I18n.t('bot_publish_success_back')}
</UIButton>
)}
</div>
</UILayout.Header>
<UILayout.Content className={styles['publish-content']}>
<PublishTableContext.Provider
value={{
publishLoading,
refreshTableData: refresh,
}}
>
<Spin spinning={loading} style={{ width: 800, margin: '0 auto' }}>
{publishStatus === Publish.NoPublish ? (
<PublishTable
setPublishStatus={setPublishStatus}
setPublishResult={setPublishResult}
connectInfoList={connectInfoList ?? []}
connectorBrandInfoMap={connectorBrandInfoMap ?? {}}
botInfo={botInfo}
monetizeConfig={monetizeConfig}
publishTips={publishTips}
getPublishDisabled={disabled => {
setPublishDisabled(disabled);
}}
getPublishLoading={pubLoading => setPublishLoading(pubLoading)}
ref={publishRef}
canOpenSource={canOpenSource}
/>
) : (
<PublishResult publishResult={publishResult} />
)}
</Spin>
</PublishTableContext.Provider>
</UILayout.Content>
</UILayout>
);
};

View File

@@ -0,0 +1,30 @@
.config-status {
.markdown {
max-width: 265px;
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-0, #1D1C23);
}
:global {
.semi-tag {
margin-right: 4px;
font-weight: 500;
svg {
width: 10px;
height: 10px;
margin-left: 4px;
}
}
.semi-button-borderless {
height: 24px;
padding: 0 8px;
font-size: 12px;
line-height: 20px;
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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 ReactMarkdown from 'react-markdown';
import copy from 'copy-to-clipboard';
import { I18n } from '@coze-arch/i18n';
import { Tooltip, UIButton, UITag, UIToast, Space } from '@coze-arch/bot-semi';
import { CustomError } from '@coze-arch/bot-error';
import { BindType } from '@coze-arch/bot-api/developer_api';
import { IconInfoCircle } from '@douyinfe/semi-icons';
import { type ConnectResultInfo } from '../../typings';
import styles from './index.module.less';
interface PublishStatusProp {
record: ConnectResultInfo;
}
export const PublishResultArea = (props: PublishStatusProp) => {
const { record } = props;
const onCopy = (text: string) => {
const res = copy(text);
if (!res) {
throw new CustomError('normal_error', 'custom error');
}
UIToast.success({
content: I18n.t('copy_success'),
showClose: false,
});
};
return (
<>
<Space className={styles['config-status']}>
{record.fail_text ? (
<Tooltip
content={
record.fail_text ? (
<ReactMarkdown
skipHtml={true}
className={styles.markdown}
linkTarget="_blank"
>
{record.fail_text}
</ReactMarkdown>
) : null
}
>
<UITag color={'green'}>
{I18n.t('Success')}
<IconInfoCircle />
</UITag>
</Tooltip>
) : (
<UITag color={'green'}>{I18n.t('Success')}</UITag>
)}
{record.share_link ? (
<UIButton
theme="borderless"
onClick={() => {
window.open(record.share_link);
}}
>
{I18n.t('bot_list_open_button', {
platform: record.name,
})}
</UIButton>
) : null}
{record.share_link ? (
<UIButton
theme="borderless"
onClick={() => {
onCopy(record.share_link);
}}
>
{I18n.t('bot_publish_result_copy_bot_link')}
</UIButton>
) : null}
{record.bind_type === BindType.ApiBind ? (
<UIButton
theme="borderless"
onClick={() => window.open('/docs/developer_guides')}
>
{I18n.t('coze_api_instru')}
</UIButton>
) : null}
</Space>
</>
);
};

View File

@@ -0,0 +1,282 @@
/*
* 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 { NavLink, useParams } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import { useMemo } from 'react';
import { partition } from 'lodash-es';
import classNames from 'classnames';
import { useIsPublishRecordReady } from '@coze-studio/publish-manage-hooks';
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { IconCozInfoCircleFill } from '@coze-arch/coze-design/icons';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
import {
Avatar,
Banner,
UITable,
UITag,
Typography,
Tag,
Space,
} from '@coze-arch/bot-semi';
import { useFlags } from '@coze-arch/bot-flags';
import {
BindType,
PublishResultStatus,
} from '@coze-arch/bot-api/developer_api';
import { PublishPlatformDescription } from '@coze-agent-ide/space-bot/component';
import {
type PublishResultInfo,
type ConnectResultInfo,
} from '@coze-agent-ide/space-bot';
import styles from '../index.module.less';
import { PublishResultArea } from './component/publish-result-area';
interface PublishResultProps {
// 隐藏Banner
hiddenBanner?: boolean;
publishResult?: PublishResultInfo;
}
// eslint-disable-next-line complexity
export const PublishResult = ({
hiddenBanner,
publishResult,
}: PublishResultProps) => {
const { bot_id: botId, space_id: spaceId } = useParams<DynamicParams>();
const columns = useMemo(() => {
const columnList: ColumnProps<ConnectResultInfo>[] = [
{
title: (
<div className="pl-4">{I18n.t('bot_publish_columns_platform')}</div>
),
render: record => (
<Space style={{ width: '100%' }} className="pl-4">
<Avatar
size="small"
shape="square"
src={record.icon}
className={styles['platform-avater']}
></Avatar>
<Typography.Text
className={styles['platform-name']}
ellipsis={{
showTooltip: {
opts: {
content: record?.name,
style: { wordWrap: 'break-word' },
},
},
}}
>
{record?.name}
</Typography.Text>
{record?.desc ? (
<PublishPlatformDescription desc={record.desc} />
) : null}
</Space>
),
},
{
title: I18n.t('bot_publish_columns_result'),
dataIndex: 'publish_status',
width: 400,
render: (status, record) => {
const color =
status === PublishResultStatus.InReview ? 'orange' : 'red';
const showStatus =
status === PublishResultStatus.InReview
? I18n.t('bot_publish_columns_status_in_review')
: I18n.t('bot_publish_columns_status_failed');
switch (status) {
case PublishResultStatus.Success:
return record.id !== FLOW_PUBLISH_ID ? (
<PublishResultArea record={record} />
) : (
<Space className={styles['config-status']}>
<UITag color={'green'}>{I18n.t('Success')}</UITag>
</Space>
);
case PublishResultStatus.InReview:
case PublishResultStatus.Failed:
return (
<Space
className={classNames(styles['config-status'], 'w-full pr-3')}
>
<Tag color={color} className="min-w-min ">
{showStatus}
</Tag>
{record?.fail_text ? (
<Typography.Text
ellipsis={{
showTooltip: {
opts: {
content: (
<ReactMarkdown
skipHtml
className={styles.markdown}
linkTarget="_blank"
>
{record.fail_text}
</ReactMarkdown>
),
style: { wordWrap: 'break-word' },
},
},
}}
>
{
<ReactMarkdown
skipHtml
className={styles.markdown}
linkTarget="_blank"
>
{record.fail_text}
</ReactMarkdown>
}
</Typography.Text>
) : null}
</Space>
);
default:
break;
}
},
},
];
return columnList;
}, [publishResult]);
const isAllPlatformSuccess = publishResult?.connectorResult?.every(
r => r.publish_status === PublishResultStatus.Success,
);
const isAllFailPublish = isAllPlatformSuccess
? false
: publishResult?.connectorResult?.every(
item => item.publish_status === PublishResultStatus.Failed,
);
const [publishResultForOpen, publishResultForChannel] = useMemo(
() =>
partition(publishResult?.connectorResult ?? [], d =>
[BindType.ApiBind, BindType.WebSDKBind].includes(d?.bind_type),
),
[publishResult],
);
const [FLAGS] = useFlags();
const { ready, inited } = useIsPublishRecordReady({
type: IntelligenceType.Bot,
spaceId: String(spaceId),
intelligenceId: String(botId),
// 社区版暂不支持该功能
enable: FLAGS['bot.studio.publish_management'] && !IS_OPEN_SOURCE,
});
return (
<div className={styles['publish-result-container']}>
{Boolean(publishResult?.connectorResult?.length) && (
<section>
{!hiddenBanner && (
<Banner
className="mb-[24px] p-[24px] flex flex-col"
fullMode={false}
type="info"
bordered
icon={null}
closeIcon={null}
data-testid="agent-ide.publish-result"
>
<div className={styles['publish-result-tip']}>
{isAllFailPublish
? `⚠️ ${I18n.t('publish_result_all_failed')}`
: `🎉 ${I18n.t('publish_success')}`}
</div>
{/* 社区版暂不支持该功能 */}
{IS_OVERSEA && !publishResult?.monetizeConfigSuccess ? (
<div className="mt-[12px] flex items-center gap-[8px] coz-fg-primary">
<IconCozInfoCircleFill className="coz-fg-hglt-yellow" />
<span className="text-[12px] leading-[16px]">
{I18n.t('monetization_publish_fail')}
</span>
</div>
) : null}
{/* 社区版暂不支持该功能 */}
{FLAGS['bot.studio.publish_management'] && !IS_OPEN_SOURCE ? (
<div className="coz-fg-dim text-[12px]">
{I18n.t('release_management_detail1', {
button: (
<NavLink
className={classNames(
'no-underline',
ready || !inited
? 'coz-fg-hglt'
: 'coz-fg-secondary cursor-not-allowed',
)}
onClick={e => {
if (!ready) {
e.preventDefault();
}
}}
to={`/space/${spaceId}/publish/agent/${botId}`}
>
{I18n.t('release_management')}
{ready || !inited
? null
: `(${I18n.t('release_management_generating')})`}
</NavLink>
),
})}
</div>
) : null}
</Banner>
)}
{!!publishResultForChannel.length && (
<UITable
tableProps={{
columns,
dataSource: publishResultForChannel,
className: classNames(styles['publish-table']),
rowKey: 'id',
}}
wrapperClassName={styles['publish-table-wrapper']}
/>
)}
{!!publishResultForOpen.length && (
<UITable
tableProps={{
columns,
dataSource: publishResultForOpen,
className: classNames(styles['publish-table']),
rowKey: 'id',
}}
wrapperClassName={styles['publish-table-wrapper']}
/>
)}
</section>
)}
</div>
);
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type PublishConnectorInfo,
type PublishResultStatus,
} from '@coze-arch/bot-api/developer_api';
export type ConnectResultInfo = PublishConnectorInfo & {
publish_status: PublishResultStatus;
fail_text?: string;
};

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createContext, useContext } from 'react';
export const PublishTableContext = createContext<{
refreshTableData: () => void;
publishLoading: boolean;
}>({
refreshTableData: () => undefined,
publishLoading: false,
});
export const usePublishTableContext = () => useContext(PublishTableContext);
export const useRefreshPublishTableData = () =>
useContext(PublishTableContext).refreshTableData;

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type PublisherBotInfo } from '@coze-agent-ide/space-bot';
import {
BotConnectorStatus,
ConfigStatus,
UserAuthStatus,
type PublishConnectorInfo,
BindType,
} from '@coze-arch/bot-api/developer_api';
const getAuthAndConfigSelectable = (item: PublishConnectorInfo) =>
Boolean(
item.is_last_published &&
item.connector_status === BotConnectorStatus.Normal &&
item.config_status === ConfigStatus.Configured &&
item.auth_status === UserAuthStatus.Authorized,
);
const getKvBindSelectable = (item: PublishConnectorInfo) =>
Boolean(
item.is_last_published &&
item.bind_info &&
item.config_status !== ConfigStatus.Disconnected &&
item.connector_status !== BotConnectorStatus.InReview,
);
const getAuthBindOrKvAuthBindSelectable = (item: PublishConnectorInfo) =>
Boolean(
item.is_last_published &&
item.config_status === ConfigStatus.Configured &&
item.connector_status !== BotConnectorStatus.InReview,
);
const getApiBindOrWebSDKBindSelectable = (item: PublishConnectorInfo) =>
Boolean(
item.is_last_published &&
item.config_status === ConfigStatus.Configured &&
item.connector_status === BotConnectorStatus.Normal,
);
export const getConnectorIsSelectable = (
item: PublishConnectorInfo,
botInfo: PublisherBotInfo,
): boolean => {
switch (item.bind_type) {
case BindType.KvBind:
return getKvBindSelectable(item);
case BindType.AuthBind:
case BindType.KvAuthBind:
return getAuthBindOrKvAuthBindSelectable(item);
case BindType.ApiBind:
case BindType.WebSDKBind:
return getApiBindOrWebSDKBindSelectable(item);
case BindType.AuthAndConfig:
return getAuthAndConfigSelectable(item);
case BindType.StoreBind:
return Boolean(!botInfo.hasPublished || item.is_last_published);
default:
return Boolean(
item.is_last_published &&
item.connector_status !== BotConnectorStatus.InReview,
);
}
};

View File

@@ -0,0 +1,118 @@
/*
* 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 { useLockFn, useRequest } from 'ahooks';
import { type PublisherBotInfo } from '@coze-agent-ide/space-bot';
import { verifyBracesAndToast } from '@coze-studio/bot-detail-store';
import {
createReportEvent,
REPORT_EVENTS as ReportEventNames,
} from '@coze-arch/report-events';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import { CustomError } from '@coze-arch/bot-error';
import {
type PublishDraftBotData,
PublishType,
} from '@coze-arch/bot-api/developer_api';
import { DeveloperApi } from '@coze-arch/bot-api';
export interface UsePublishParamsType {
botId: string;
changeLog: string;
connectors: Record<string, Record<string, string>>;
publishId: string;
}
export interface UsePublishProps {
onSuccess: (res: PublishDraftBotData) => void;
botInfo: PublisherBotInfo;
}
export interface UsePublishType {
handlePublishBot: (params: UsePublishParamsType) => void;
loading: boolean;
}
const publishBotEvent = createReportEvent({
eventName: ReportEventNames.publishBot,
});
const hasBracesErrorI18nKey = 'bot_prompt_bracket_error';
export const useConnectorsPublish = ({
onSuccess,
botInfo,
}: UsePublishProps): UsePublishType => {
const { commit_version, space_id = '' } = useParams<DynamicParams>();
const { runAsync: publishBot, loading } = useRequest(
async (params: UsePublishParamsType) => {
const mode = botInfo.botMode;
const { botId, changeLog, connectors, publishId } = params;
if (!verifyBracesAndToast(botInfo.prompt)) {
throw new CustomError(
ReportEventNames.publishBot,
hasBracesErrorI18nKey,
);
}
const resp = await DeveloperApi.PublishDraftBot({
space_id,
bot_id: botId,
history_info: changeLog,
connectors,
botMode: mode,
publish_id: publishId,
commit_version: commit_version ?? '',
publish_type: PublishType.OnlinePublish,
});
return resp.data;
},
{
manual: true,
onBefore: () => {
publishBotEvent.start();
},
onSuccess: resp => {
publishBotEvent.success();
if (resp?.publish_result) {
onSuccess(resp);
}
},
onError: e => {
if (e.message === hasBracesErrorI18nKey) {
return;
}
publishBotEvent.error({
error: e,
reason: 'publish_bot_error',
});
},
},
);
const handlePublishBot = useLockFn(async (params: UsePublishParamsType) => {
await publishBot(params);
});
return {
handlePublishBot,
loading,
};
};

View File

@@ -0,0 +1,588 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable custom-property-pattern */
.header {
display: flex;
align-items: center;
align-self: stretch;
justify-content: space-between;
width: 100%;
background: #F7F7FA;
}
.publish-header {
background: #F7F7FA;
box-shadow: 0 2px 2px 0 rgb(29 28 35 / 4%), 0 0 2px 0 rgb(29 28 35 / 18%);
.exitbtn {
margin-left: 12px;
font-size: 18px;
font-weight: 600;
font-style: normal;
line-height: 24px;
color: var(--light-color-black-black, #000);
}
}
.publish-table-wrapper {
height: fit-content;
margin-bottom: 24px;
&:last-child {
margin-bottom: 64px;
}
}
.publish-content {
overflow-y: auto;
width: 100%;
height: 100%;
margin: auto;
padding: 0;
padding-top: 24px;
}
.link-underline {
margin-left: 8px;
text-decoration: underline;
}
.publish-wrapper .publish-title-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 40px;
margin-bottom: 16px;
font-size: 18px;
line-height: 24px;
color: var(--light-usage-text-color-text-0, #1c1d23);
.publish-title {
margin: 0;
font-size: 18px;
font-weight: 600;
line-height: 24px;
color: var(--light-usage-text-color-text-0, #1c1d23);
}
}
.changelog-textarea_disabled {
pointer-events: none;
:global {
.semi-input-clearbtn {
visibility: hidden;
}
}
}
.publish-desc {
display: block;
margin-bottom: 16px;
font-size: 14px;
line-height: 20px;
color: var(--Light-usage-text---color-text-1, rgb(29 28 35 / 80%));
}
.publish-result-container {
:global {
.semi-banner-extra {
margin-top: 0;
}
}
.publish-result-tip {
display: flex;
align-items: center;
font-size: 18px;
font-weight: 600;
line-height: 24px;
color: var(--light-color-black-black, #000);
}
}
.publish-footer {
justify-content: flex-end;
width: 100%;
padding: 24px 0;
}
.publish-wrapper .text-label {
font-size: 18px;
font-weight: 600;
font-style: normal;
line-height: 24px;
color: var(--Light-usage-text---color-text-0, #1C1D23);
}
.text-area {
margin-top: 12px;
}
.publish-result-table {
border-top: 1px solid var(--semi-color-border);
:global {
.semi-table-header {
display: none;
}
.semi-table-row:hover>.semi-table-row-cell {
background-color: transparent !important;
border-bottom: 1px solid var(--semi-color-border) !important;
}
.semi-table-row:hover>.semi-table-row-cell::before {
content: '';
width: 100%;
height: 0;
}
}
}
.publish-table {
margin-top: 0;
.diff-icon {
cursor: pointer !important;
width: 14px;
}
:global {
.semi-table-container {
background: #fff;
border: 1px solid rgb(29 28 35 / 12%);
// padding: 0 16px;
border-radius: 12px;
}
// 全选按钮不展示
.semi-table-thead>.semi-table-row>.semi-table-row-head:first-child {
.semi-table-selection-wrap {
display: none;
}
}
.semi-table-header {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.semi-table-body {
border-bottom-right-radius: 12px;
border-bottom-left-radius: 12px;
}
.semi-table-tbody>.semi-table-row:hover>.semi-table-row-cell {
border-radius: 0 !important;
}
.semi-table-thead>.semi-table-row>.semi-table-row-head {
height: 40px;
padding: 0;
background-color: transparent;
border-radius: unset;
// &:first-child {
// padding-left: 12px;
// }
}
.semi-table-row {
background-color: #fff !important;
}
.semi-table-tbody .semi-table-row .semi-table-row-cell {
padding: 10px 0 !important;
line-height: 14px;
// &:first-child {
// padding-left: 12px !important;
// }
}
.semi-table-tbody>.semi-table-row:last-child:hover>.semi-table-row-cell {
border-bottom: none !important;
}
.semi-table .semi-table-selection-wrap {
width: 16px;
}
// .semi-table-row:hover>.semi-table-row-cell {
// background-color: #fff !important;
// border-bottom: 1px solid var(--semi-color-border) !important;
// }
// tr hover 无样式
.semi-table-row:hover>.semi-table-row-cell::before {
content: '';
width: 100%;
height: 0;
}
.semi-table-tbody>.semi-table-row:last-child>.semi-table-row-cell {
border-bottom: none;
}
}
.common-tag {
margin-left: 8px;
font-size: 12px;
font-weight: 500;
font-style: normal;
line-height: 16px;
border-radius: 6px;
svg {
width: 10px;
height: 10px;
margin-left: 4px;
}
}
.orange-tag {
color: var(--light-color-orange-orange-5, rgb(255 150 0 / 100%));
background: var(--light-color-orange-orange-1, #FFF1CC);
.common-tag();
}
.grey-info {
padding: 5px;
color: var(--light-color-grey-grey-5, rgb(107 107 117 / 100%));
background: var(--light-color-grey-grey-1, #F0F0F5);
border-radius: 6px;
svg {
width: 10px;
height: 10px;
}
}
.disable-tooltip {
position: absolute;
top: 2px;
left: 0;
width: 16px;
height: 16px;
}
.platform-avater {
flex-shrink: 0;
width: 26px;
height: 26px;
background: #fff;
border: 1px solid var(--light-usage-fill-color-fill-1, rgb(46 46 56 / 8%));
border-radius: 4px;
}
.platform-name {
max-width: 140px !important;
font-size: 14px !important;
font-weight: 600 !important;
line-height: 20px;
color: var(--light-usage-text-color-text-0, #1c1d23);
}
.platform-info {
width: calc(100% - 50px);
.platform-desc {
display: block;
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
}
}
.config-status {
.markdown {
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-0, #1D1C23);
:global {
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
:global {
.semi-tag {
margin-right: 4px;
font-weight: 500;
svg {
width: 10px;
height: 10px;
margin-left: 4px;
}
}
.semi-button-borderless {
height: 24px;
padding: 0 8px;
font-size: 12px;
line-height: 20px;
}
}
}
}
.config-area {
.config-step-area {
display: flex;
// padding: 16px;
// padding-bottom: 32px;
flex-direction: column;
gap: 8px;
align-items: flex-start;
align-self: stretch;
margin-top: 32px;
border-radius: 8px;
// border: 1px solid var(--light-usage-border-color-border-1, rgba(29, 28, 35, 0.12));
.step-order {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
font-weight: 600;
color: var(--light-color-white-white, #fff);
background: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 50%;
}
.step-content {
// gap: 8px;
// display: flex;
// flex-direction: column;
}
.step-title {
/* 157.143% */
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: #000;
}
.step-link {
margin-bottom: 8px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-3, rgb(29 28 35 / 35%));
}
}
.config-form {
width: 100%;
.disable-field {
padding: 12px 0 24px;
.title {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
}
}
:global {
.semi-form-field-label {
margin-bottom: 8px;
}
.semi-form-field-label-text {
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
}
.no-eye {
:global {
// app_secret纯密文不展示眼睛
.semi-input-modebtn {
display: none;
}
}
}
}
.start-text {
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-0, #1D1C23);
}
.config-link {
font-size: 12px;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4D53E8);
}
.step-order {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
font-weight: 600;
color: var(--light-color-white-white, #fff);
background: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 50%;
}
.step-title {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: #000;
}
.markdown {
font-size: 14px;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
.link-area {
margin-top: 32px;
}
.link-list {
margin-top: 16px;
.title {
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
.semi-form-field-error-message {
position: absolute;
}
}
}
.publish-modal {
:global {
.semi-modal {
width: 560px;
}
.semi-modal-content {
.semi-modal-header {
margin-bottom: 0;
}
.semi-modal-body {
padding-bottom: 12px;
}
}
.semi-modal-footer {
margin-top: 32px;
}
}
}
.check-error {
ul {
margin: 0;
padding-left: 14px;
}
li {
padding-top: 6px;
}
}
.diff-modal {
:global {
.semi-modal {
width: 960px;
}
.semi-modal-content {
height: 740px;
}
.semi-modal-body{
padding: 0 !important;
}
}
}
.diff-modal-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.table-row-icon-wrapper {
:global(.semi-table-expand-icon) {
margin-right: 0;
}
}

View File

@@ -0,0 +1,256 @@
/*
* 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 { useParams } from 'react-router-dom';
import {
type ForwardedRef,
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import { nanoid } from 'nanoid';
import classNames from 'classnames';
import {
PublishDisabledType,
type PublisherBotInfo,
type PublishRef,
type PublishResultInfo,
STORE_CONNECTOR_ID,
getPublishResult,
} from '@coze-agent-ide/space-bot';
import { I18n } from '@coze-arch/i18n';
import { IconCozEmpty, IconCozInfoCircleFill } from '@coze-arch/coze-design/icons';
import { Tooltip } from '@coze-arch/coze-design';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { Form, Space, TextArea } from '@coze-arch/bot-semi';
import {
AllowPublishStatus,
type ConnectorBrandInfo,
Publish,
type PublishConnectorInfo,
SpaceType,
} from '@coze-arch/bot-api/developer_api';
import { type BotMonetizationConfigData } from '@coze-arch/bot-api/benefit';
import { PublishTermService } from '@coze-agent-ide/agent-ide-commons';
import { TableCollection } from './table-collection';
import { useConnectorsPublish } from './hooks/use-connectors-publish';
import { getConnectorIsSelectable } from './get-connector-selectable';
import styles from './index.module.less';
export { PublishTableContext, usePublishTableContext } from './context';
interface PublishTableProps {
setPublishStatus: (status: Publish) => void;
setPublishResult: (result: PublishResultInfo) => void;
connectInfoList: PublishConnectorInfo[];
connectorBrandInfoMap: Record<string, ConnectorBrandInfo>;
botInfo: PublisherBotInfo;
monetizeConfig?: BotMonetizationConfigData;
getPublishDisabled: (disabledType: PublishDisabledType) => void;
getPublishLoading: (loading: boolean) => void;
canOpenSource: boolean;
publishTips?: string;
}
export const PublishTable = forwardRef(
(
{
connectInfoList = [],
connectorBrandInfoMap = {},
setPublishStatus,
setPublishResult,
botInfo,
monetizeConfig,
getPublishDisabled,
getPublishLoading,
canOpenSource,
publishTips,
}: PublishTableProps,
ref: ForwardedRef<PublishRef>,
) => {
const publishId = useMemo(() => nanoid(), []);
const [dataSource, setDataSource] =
useState<PublishConnectorInfo[]>(connectInfoList);
const [selectedPlatforms, setSelectedPlatforms] = useState<string[]>([]);
const { space_id: spaceId = '', bot_id: botId = '' } =
useParams<DynamicParams>();
const { space_type: spaceType } = useSpaceStore(s => s.space);
const [changeLog, setChangeLog] = useState('');
const [hasCategoryList, setHasCategoryList] = useState(true);
const isPersonal = spaceType === SpaceType.Personal;
useEffect(() => {
if (connectInfoList?.length) {
setDataSource(connectInfoList);
const selectKeys = connectInfoList
.filter(item => item.allow_punish === AllowPublishStatus.Allowed)
.filter(item => getConnectorIsSelectable(item, botInfo))
.map(node => node.id);
setSelectedPlatforms(selectKeys);
}
}, [connectInfoList, botInfo]);
useImperativeHandle(ref, () => ({
publish: () => handlePublish(),
}));
const { loading: publishLoading, handlePublishBot } = useConnectorsPublish({
onSuccess: resp => {
setPublishStatus(Publish.HadPublished);
const publishResult = getPublishResult(
resp.publish_result ?? {},
dataSource.filter(item => selectedPlatforms?.includes(item.id)),
);
setPublishResult({
connectorResult: publishResult,
marketResult: resp.submit_bot_market_result ?? {},
monetizeConfigSuccess: resp.publish_monetization_result ?? false,
});
},
botInfo,
});
const notSelectPlatform = !selectedPlatforms?.length;
const notSelectCategory =
hasCategoryList &&
selectedPlatforms.includes(STORE_CONNECTOR_ID) &&
!dataSource?.find(item => item.id === STORE_CONNECTOR_ID)?.bind_info
?.category_id;
useEffect(() => {
let publishDisableType;
if (notSelectPlatform) {
publishDisableType = PublishDisabledType.NotSelectPlatform;
} else if (notSelectCategory) {
publishDisableType = PublishDisabledType.NotSelectCategory;
}
getPublishDisabled(publishDisableType);
}, [notSelectPlatform, notSelectCategory]);
useEffect(() => {
getPublishLoading(publishLoading);
}, [publishLoading]);
const handlePublish = () => {
const connectors: Record<string, Record<string, string>> = {};
const list = dataSource.filter(item =>
selectedPlatforms.includes(item.id),
);
list.forEach(i => {
connectors[i.id] = i.bind_info;
});
sendTeaEvent(EVENT_NAMES.bot_publish, {
space_id: spaceId,
bot_id: botId,
publish_id: publishId,
workspace_id: spaceId,
workspace_type: isPersonal ? 'personal_workspace' : 'team_workspace',
is_auto_gen_changelog_empty: true,
is_changelog_empty: !changeLog,
is_modified: false,
});
handlePublishBot({
botId,
connectors,
changeLog,
publishId,
});
};
return (
<div className={styles['publish-wrapper']}>
<Form.Label
text={I18n.t('bot_publish_changelog')}
className={styles['text-label']}
/>
<TextArea
disabled={publishLoading}
className={classNames(styles['text-area'])}
value={changeLog}
rows={3}
showClear
maxCount={2000}
maxLength={2000}
onChange={setChangeLog}
/>
<Space className={styles['publish-title-container']}>
<Form.Label required className={styles['publish-title']}>
{I18n.t('bot_publish_select_title')}
</Form.Label>
{publishTips ? (
<Tooltip content={publishTips}>
<div className="coz-fg-hglt-yellow cursor-pointer text-[12px] font-[500]">
<Space spacing={2}>
<IconCozInfoCircleFill />
<div>{I18n.t('coze_cost_sharing')}</div>
</Space>
</div>
</Tooltip>
) : null}
</Space>
<PublishTermService
termServiceData={dataSource
.filter(item => item.privacy_policy || item.user_agreement)
?.map(i => ({
name: i.name,
icon: i.icon,
privacy_policy: i.privacy_policy,
user_agreement: i.user_agreement,
}))}
/>
{dataSource.length ? (
<TableCollection
dataSource={dataSource}
connectorBrandInfoMap={connectorBrandInfoMap ?? {}}
setSelectedPlatforms={setSelectedPlatforms}
selectedPlatforms={selectedPlatforms}
setDataSource={setDataSource}
canOpenSource={canOpenSource}
setHasCategoryList={setHasCategoryList}
botInfo={botInfo}
monetizeConfig={monetizeConfig}
disabled={publishLoading}
/>
) : (
<div className="flex flex-col justify-center items-center w-full h-full gap-[4px] mt-[80px]">
<IconCozEmpty className="w-[32px] h-[32px] coz-fg-dim" />
<div className="text-[14px] font-medium coz-fg-primary">
{I18n.t('publish_page_no_channel_status_title')}
</div>
<div className="text-[12px] coz-fg-primary">
{I18n.t('publish_page_no_channel_status_desc')}
</div>
</div>
)}
</div>
);
},
);

View File

@@ -0,0 +1,211 @@
/*
* 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 {
Tag,
Tooltip,
Space,
UITag,
type UITagProps,
} from '@coze-arch/bot-semi';
import { IconInfoCircle } from '@coze-arch/bot-icons';
import {
AllowPublishStatus,
BindType,
BotConnectorStatus,
ConfigStatus,
UserAuthStatus,
type PublishConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
import {
KvBindButton,
DiffViewButton,
AuthorizeButton,
} from '@coze-agent-ide/space-bot/component';
import { type ActionColumnProps } from '@coze-agent-ide/space-bot';
import styles from '../index.module.less';
import { useAuthSuccess } from './hooks/use-auth-success';
import { getConfigStatus } from './get-config-status';
import { StoreBind, ApiBindButton } from './connector-action';
interface TipTagProps {
showText: string;
tip: string;
tagProps?: UITagProps;
}
const TipTag: React.FC<TipTagProps> = ({ showText, tip, tagProps }) => (
<Tooltip content={tip}>
{showText ? (
<Tag className={styles['orange-tag']} color="orange" {...tagProps}>
{showText}
<IconInfoCircle />
</Tag>
) : (
<IconInfoCircle className={styles['grey-info']} />
)}
</Tooltip>
);
export const PublishConnectorAction: React.FC<ActionColumnProps> = ({
record,
setSelectedPlatforms,
setDataSource,
canOpenSource,
botInfo,
isMouseIn,
selectedPlatforms,
setHasCategoryList,
dataSource,
}) => {
const revokeSuccess = (id: string) => {
setDataSource((list: PublishConnectorInfo[]) => {
const target = list.find(item => item.id === id);
if (target) {
target.config_status = ConfigStatus.NotConfigured;
target.config_status_toast = undefined;
target.auth_status = UserAuthStatus.UnAuthorized;
}
return [...list];
});
setSelectedPlatforms(list => list.filter(i => i !== id));
};
useAuthSuccess((id: string) => {
const currentConnectorInfo = dataSource.find(item => item.id === id);
if (
currentConnectorInfo?.allow_punish === AllowPublishStatus.Allowed &&
currentConnectorInfo?.connector_status === BotConnectorStatus.Normal &&
currentConnectorInfo?.config_status === ConfigStatus.Configured
) {
setSelectedPlatforms(list => [...list, id]);
}
});
const action = (() => {
switch (record.bind_type) {
case BindType.KvBind: //仅绑定
case BindType.KvAuthBind: //绑定+授权,取消绑定后自动取消授权
return (
<KvBindButton
record={record}
setDataSource={setDataSource}
setSelectedPlatforms={setSelectedPlatforms}
/>
);
case BindType.AuthBind:
return record.auth_login_info ? (
<AuthorizeButton
origin="publish"
id={record.id}
channelName={record.name}
status={record.config_status}
revokeSuccess={revokeSuccess}
authInfo={record.auth_login_info}
isMouseIn={isMouseIn}
/>
) : null;
case BindType.ApiBind:
return <ApiBindButton />;
case BindType.StoreBind:
return (
<StoreBind
record={record}
canOpenSource={canOpenSource}
setDataSource={setDataSource}
botInfo={botInfo}
selectedPlatforms={selectedPlatforms}
setHasCategoryList={setHasCategoryList}
/>
);
default:
return null;
}
})();
return (
<div className="mr-7 flex gap-[8px]" onClick={e => e.stopPropagation()}>
{record.config_status !== ConfigStatus.NotConfigured && (
<DiffViewButton record={record} isMouseIn={isMouseIn} />
)}
{action}
</div>
);
};
export const ConfigStatusColumn: React.FC<ActionColumnProps> = props => {
const { record } = props;
if (record.config_status === ConfigStatus.Disconnected) {
return (
<Space>
<TipTag
showText={I18n.t('bot_publish_columns_status_disconnected')}
tip={I18n.t('bot_publish_token_expired_notice', {
platform: record.name,
})}
/>
<PublishConnectorAction {...props} />
</Space>
);
}
const { text, color } = getConfigStatus(record);
return (
<Space
className={styles['config-status']}
style={{ justifyContent: 'space-between', width: '100%' }}
>
<div>
{record?.config_status_toast ? (
<TipTag
showText={text}
tip={record?.config_status_toast || ''}
tagProps={{
color,
style: { margin: 0 },
// 覆盖原来的orange-tag
className: styles['common-tag'],
}}
/>
) : (
<UITag color={color}>{text}</UITag>
)}
{record?.connector_status === BotConnectorStatus.Normal ? null : (
<TipTag
showText={
record?.connector_status === BotConnectorStatus.InReview
? I18n.t('bot_publish_columns_status_in_review')
: I18n.t('bot_publish_columns_status_offline')
}
tip={
record?.connector_status === BotConnectorStatus.InReview
? I18n.t('bot_publish_in_review_notice')
: I18n.t('bot_publish_offline_notice_no_certain_time', {
platform: record?.name,
})
}
/>
)}
</div>
<PublishConnectorAction {...props} />
</Space>
);
};

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState } from 'react';
import { PatBody } from '@coze-studio/open-auth';
import { I18n } from '@coze-arch/i18n';
import { Modal } from '@coze-arch/coze-design';
import { UIButton } from '@coze-arch/bot-semi';
export const ApiBindButton: React.FC = () => {
const [visible, setVisible] = useState(false);
return (
<>
<UIButton
theme="borderless"
onClick={() => {
setVisible(true);
}}
>
{I18n.t('bot_publish_action_configure')}
</UIButton>
<Modal
size="xl"
title={I18n.t('settings_api_authorization')}
visible={visible}
onCancel={() => {
setVisible(false);
}}
>
<PatBody size="small" type="primary" />
</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 { StoreBind } from './store-bind';
export { ApiBindButton } from './api-bind-button';

View File

@@ -0,0 +1,282 @@
/*
* 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 { useParams } from 'react-router-dom';
import { useEffect, type SetStateAction, useState } from 'react';
import { isEmpty, map } from 'lodash-es';
import { useRequest } from 'ahooks';
import { type PublisherBotInfo } from '@coze-agent-ide/space-bot';
import { I18n } from '@coze-arch/i18n';
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
import {
Select,
Space,
Spin,
Tooltip,
Typography,
UIIconButton,
} from '@coze-arch/bot-semi';
import { IconInfo } from '@coze-arch/bot-icons';
import { ProductEntityType } from '@coze-arch/bot-api/product_api';
import { type PublishConnectorInfo } from '@coze-arch/bot-api/developer_api';
import { PlaygroundApi, ProductApi } from '@coze-arch/bot-api';
import { usePublishTableContext } from '../../context';
interface StoreBindProps {
record: PublishConnectorInfo;
canOpenSource: boolean;
setDataSource: (value: SetStateAction<PublishConnectorInfo[]>) => void;
botInfo: PublisherBotInfo;
selectedPlatforms: string[];
setHasCategoryList: (value: SetStateAction<boolean>) => void;
}
enum BotSubmitStatus {
Private = 'false',
Public = 'true',
}
const OpenSourceConfig = {
[BotSubmitStatus.Private]: {
title: () => I18n.t('mkpl_bots_private_configuration'),
desc: () => I18n.t('mkpl_bots_private_configuration_description'),
},
[BotSubmitStatus.Public]: {
title: () => I18n.t('mkpl_bots_public_configuration'),
desc: () => I18n.t('mkpl_bots_public_configuration_description'),
},
};
// eslint-disable-next-line complexity
export const StoreBind: React.FC<StoreBindProps> = ({
record,
canOpenSource,
setDataSource,
botInfo,
selectedPlatforms,
setHasCategoryList,
}) => {
const params = useParams<DynamicParams>();
const [sourceConfig, setSourceConfig] = useState<boolean>(false);
useEffect(() => {
if (!record.bind_info?.open_source) {
handleSelect('open_source', BotSubmitStatus.Private);
}
}, [record.bind_info]);
const getSpaceId = () => params.space_id || '';
useRequest(
async () => {
const res = await ProductApi.PublicGetTemplateWhiteListConfig(
{},
{ __disableErrorToast: true },
);
return res.data?.space_ids || [];
},
{
onFinally: (_, data, e) => {
// @ts-expect-error - skip
if (!e && data.includes(getSpaceId())) {
setSourceConfig(true);
} else {
setSourceConfig(false);
// 兜底逻辑:如果出错,或不在白名单中,则自动更改为“私有配置”发布
handleSelect('open_source', BotSubmitStatus.Private);
}
},
},
);
const { data: categoryList } = useRequest(
async () => {
const res = await ProductApi.PublicGetProductCategoryList(
{
// 代表含义:无商品也返回类型,即为全量的类型
need_empty_category: true,
entity_type: ProductEntityType.Bot,
},
{ __disableErrorToast: true },
);
return res.data?.categories || [];
},
{
onSuccess: data => {
setHasCategoryList?.(Boolean(data.length));
},
onError: () => {
setHasCategoryList?.(false);
},
refreshDeps: [record.id],
},
);
const { loading: autoCategoryLoading } = useRequest(
async () => {
const { description, name, prompt } = botInfo;
const res = await PlaygroundApi.GenerateStoreCategory(
{
prompt,
bot_name: name,
bot_description: description,
},
{ __disableErrorToast: true },
);
return res.data?.category_id;
},
{
ready:
selectedPlatforms.includes(record.id) &&
!record.bind_info?.category_id &&
!isEmpty(botInfo.name),
onSuccess: data => {
handleSelect('category_id', data);
},
},
);
const handleSelect = (key: 'open_source' | 'category_id', value) => {
setDataSource((list: PublishConnectorInfo[]) => {
const target = list.find(l => l.id === record?.id);
if (target) {
target.bind_info = {
...target.bind_info,
[key]: value,
};
}
return [...list];
});
};
const isCategoryError =
selectedPlatforms?.includes(record.id) &&
!record.bind_info?.category_id &&
!autoCategoryLoading;
const selectedStore = selectedPlatforms?.includes(record.id);
const renderLabel = (title, desc, key) => (
<span className="flex justify-between w-full">
{!canOpenSource && key === BotSubmitStatus.Public ? (
<Tooltip
content={I18n.t('publisher_market_public_disabled')}
position="bottom"
>
{title}
</Tooltip>
) : (
title
)}
<Tooltip content={desc}>
<UIIconButton icon={<IconInfo className="text-[#1D1C2399]" />} />
</Tooltip>
</span>
);
const { publishLoading } = usePublishTableContext();
const selectSourceConfig = () => {
if (!sourceConfig) {
return null;
}
return (
<Select
className="w-[180px] mr-2"
defaultValue={BotSubmitStatus.Private}
disabled={!selectedStore || publishLoading}
optionList={map(OpenSourceConfig, ({ title, desc }, key) => ({
label: (
<Space className="w-full">
{renderLabel(title(), desc(), key)}
</Space>
),
value: key,
title: title(),
disabled: !canOpenSource && key === BotSubmitStatus.Public,
}))}
value={record.bind_info?.open_source}
onSelect={value => handleSelect('open_source', value)}
insetLabel={I18n.t('mkpl_bots_visibility')}
renderSelectedItem={option => (
<Typography.Text
disabled={!selectedStore}
ellipsis={{
showTooltip: {
opts: {
content: option.title,
style: { wordBreak: 'break-word' },
},
},
}}
>
{option.title}
</Typography.Text>
)}
></Select>
);
};
return (
<div onClick={e => e.stopPropagation()}>
{selectSourceConfig()}
<Select
style={{ width: 180 }}
disabled={!selectedStore || !categoryList?.length || publishLoading}
optionList={categoryList?.map(item => ({
label: item.name,
value: item.id,
}))}
value={
categoryList?.length
? record.bind_info?.category_id || undefined
: undefined
}
onSelect={value => handleSelect('category_id', value)}
insetLabel={I18n.t('mkpl_bots_category')}
placeholder={
!autoCategoryLoading ? (
categoryList?.length ? (
I18n.t('select_category')
) : (
I18n.t('select_later')
)
) : (
<Spin spinning style={{ verticalAlign: 'middle' }}>
{I18n.t('select_category')}
</Spin>
)
}
validateStatus={isCategoryError ? 'error' : 'default'}
renderSelectedItem={option => (
<Typography.Text
disabled={!selectedStore || !categoryList?.length}
ellipsis={{
showTooltip: {
opts: {
content: option.label,
style: { wordBreak: 'break-word' },
},
},
}}
>
{option.label}
</Typography.Text>
)}
></Select>
</div>
);
};

View File

@@ -0,0 +1,122 @@
/*
* 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 { reporter } from '@coze-arch/logger';
import { I18n } from '@coze-arch/i18n';
import { type TagColor } from '@coze-arch/bot-semi';
import {
BindType,
ConfigStatus,
UserAuthStatus,
type PublishConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
interface ConfigStatusUI {
text: string;
color: TagColor;
}
export const getConfigStatus = (
record: PublishConnectorInfo,
): ConfigStatusUI => {
const { bind_type } = record;
if (bind_type === BindType.KvBind) {
return getKvBindStatus(record);
}
if (bind_type === BindType.AuthAndConfig) {
return getAuthAndConfigStatus(record);
}
return getDefaultStatus(record);
};
const getKvBindStatus = (record: PublishConnectorInfo): ConfigStatusUI => {
const { config_status } = record;
const couldPublish = config_status === ConfigStatus.Configured;
const color = couldPublish ? 'green' : 'grey';
const textMap = {
[ConfigStatus.Configured]: I18n.t('bot_publish_columns_status_configured'),
[ConfigStatus.NotConfigured]: I18n.t(
'bot_publish_columns_status_not_configured',
),
[ConfigStatus.Configuring]: '',
};
return {
text: textMap[config_status],
color,
};
};
const getAuthAndConfigStatus = (
record: PublishConnectorInfo,
): ConfigStatusUI => {
const { config_status, auth_status } = record;
if (auth_status === UserAuthStatus.UnAuthorized) {
return {
text: I18n.t('bot_publish_columns_status_unauthorized'),
color: 'grey',
};
}
switch (config_status) {
case ConfigStatus.Configured:
return {
text: I18n.t('bot_publish_columns_status_configured'),
color: 'green',
};
case ConfigStatus.NeedReconfiguring:
return {
text: I18n.t('publish_base_config_needReconfigure'),
color: 'orange',
};
case ConfigStatus.NotConfigured:
return {
text: I18n.t('bot_publish_columns_status_not_configured'),
color: 'grey',
};
default:
reporter.errorEvent({
eventName: 'fail_to_handle_config_status',
error: new Error(`config status: ${config_status}`),
});
return {
text: '',
color: 'grey',
};
}
};
const getDefaultStatus = (record: PublishConnectorInfo): ConfigStatusUI => {
const { config_status } = record;
const couldPublish = config_status === ConfigStatus.Configured;
const color = couldPublish ? 'green' : 'grey';
const textMap = {
[ConfigStatus.Configured]: I18n.t('bot_publish_columns_status_authorized'),
[ConfigStatus.NotConfigured]: I18n.t(
'bot_publish_columns_status_unauthorized',
),
[ConfigStatus.Configuring]: I18n.t('publish_douyin_config_ing'),
};
return {
text: textMap[config_status],
color,
};
};

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
import { AuthStatus } from '@coze-arch/idl/developer_api';
import { useResetLocationState } from '@coze-arch/bot-hooks';
// 三方授权成功,调用成功回调
export const useAuthSuccess = (bindSuccess: (id: string) => void) => {
const { state } = useLocation();
const { oauth2, authStatus } = (state ?? history.state ?? {}) as Record<
string,
unknown
>;
const { platform = '' } = (oauth2 ?? {}) as Record<string, string>;
const resetLocationState = useResetLocationState();
useEffect(() => {
if (authStatus === AuthStatus.Authorized) {
resetLocationState();
bindSuccess(platform);
}
}, [platform, authStatus]);
};

View File

@@ -0,0 +1,377 @@
/*
* 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, useMemo, useState } from 'react';
import { partition } from 'lodash-es';
import classNames from 'classnames';
import { PublishPlatformDescription } from '@coze-agent-ide/space-bot/component';
import {
type PublisherBotInfo,
type MouseInValue,
type PublishTableProps,
} from '@coze-agent-ide/space-bot';
import { MonetizePublishInfo } from '@coze-studio/components/monetize';
import { I18n } from '@coze-arch/i18n';
import { IconCozArrowRight, IconCozDiamondFill } from '@coze-arch/coze-design/icons';
import { Tooltip } from '@coze-arch/coze-design';
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
import { Avatar, Space, Typography, UITable } from '@coze-arch/bot-semi';
import {
AllowPublishStatus,
BindType,
BotConnectorStatus,
ConfigStatus,
type ConnectorBrandInfo,
type PublishConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
import { type BotMonetizationConfigData } from '@coze-arch/bot-api/benefit';
import {
useBenefitAvailable,
PremiumPaywallScene,
usePremiumPaywallModal,
} from '@coze-studio/premium-components-adapter';
import styles from '../index.module.less';
import { usePublishTableContext } from '../context';
import { PluginPricingInfo } from './plugin-limit-tooltip';
import { ConfigStatusColumn } from './config-status';
const convertFlatListToTreeList = (
list: PublishConnectorInfo[],
brandMap: Record<string, ConnectorBrandInfo> = {},
) => {
const result = {};
const finalResult = [];
list.forEach(item => {
const brandId = item?.brand_id;
if (brandId) {
if (!result[brandId]) {
result[brandId] = {
...(brandMap[brandId] || {}),
children: [],
};
// @ts-expect-error -- skip
finalResult.push(result[brandId]);
}
result[brandId].children.push(item);
} else {
// @ts-expect-error -- skip
finalResult.push(item);
}
});
return finalResult;
};
const getCheckBoxDisabledTips = (record: PublishConnectorInfo) => {
const configStatusArray = [
ConfigStatus.NotConfigured,
ConfigStatus.Configuring,
];
if (
record.allow_punish === AllowPublishStatus.Forbid &&
record.not_allow_reason
) {
return record.not_allow_reason;
}
if (configStatusArray.some(status => record.config_status === status)) {
return I18n.t('bot_publish_disable_check_tip');
}
if (record.connector_status === BotConnectorStatus.InReview) {
return I18n.t('bot_publish_in_review_disable_check_tip');
}
if (record.config_status === ConfigStatus.NeedReconfiguring) {
return I18n.t('publish_base_config_needReconfigure');
}
};
const getCheckboxProps = (record: PublishConnectorInfo, disabled: boolean) => {
const disableTip = getCheckBoxDisabledTips(record);
return {
disabled: !!disableTip || disabled,
id: record.id,
// Offline状态没有tooltip
children: disableTip ? (
<Tooltip content={disableTip}>
<span className={styles['disable-tooltip']} />
</Tooltip>
) : null,
};
};
const doIfDisabled = (source: PublishConnectorInfo[], disabled: boolean) =>
source?.every(
item =>
item.config_status !== ConfigStatus.Configured ||
item.allow_punish !== AllowPublishStatus.Allowed ||
item.connector_status === BotConnectorStatus.InReview,
) || disabled;
export const TableCollection = (props: PublishTableProps) => {
const {
dataSource,
connectorBrandInfoMap = {},
selectedPlatforms,
setSelectedPlatforms,
} = props;
const [mouseInfo, setMouseInfo] = useState<MouseInValue>({});
const onMouseEnter = (record: PublishConnectorInfo) => {
setMouseInfo({ [record.id]: true });
};
const onMouseLeave = (record: PublishConnectorInfo) => {
setMouseInfo({ [record.id]: false });
};
const [dataSourceForOpen, dataSourceForChannel] = partition(
dataSource ?? [],
d =>
d?.bind_type === BindType.ApiBind || d?.bind_type === BindType.WebSDKBind,
);
const { publishLoading } = usePublishTableContext();
const dataTreeForOpen = useMemo(
() => convertFlatListToTreeList(dataSourceForOpen, connectorBrandInfoMap),
[dataSourceForOpen, connectorBrandInfoMap],
);
const dataTreeForChannel = useMemo(
() =>
convertFlatListToTreeList(dataSourceForChannel, connectorBrandInfoMap),
[dataSourceForChannel, connectorBrandInfoMap],
);
// 无全选按钮因此所有表格使用相同check配置
const baseConfigForChecker = {
hidden: true,
fixed: 'left' as const,
selectedRowKeys: selectedPlatforms,
onChange: (selectedRowKeys: (string | number)[] | undefined) => {
setSelectedPlatforms(selectedRowKeys as string[]);
},
getCheckboxProps: (record: PublishConnectorInfo) =>
getCheckboxProps(record, publishLoading),
};
const getColumns = (
type: 'connector' | 'api',
): ColumnProps<PublishConnectorInfo>[] => [
{
title: <TableTitle type={type} />,
width: 220,
useFullRender: true,
render: (...params) => {
// @ts-expect-error -- skip
const [record, , , { expandIcon, selection }] = params;
return (
<PlatformInfoColumn
platform={record}
rowIcon={record.children ? expandIcon : selection}
/>
);
},
},
{
title: (
<TableTittleExtra
botInfo={props.botInfo}
type={type}
monetizeConfig={props.monetizeConfig}
platforms={dataSource}
/>
),
render: (
record: PublishConnectorInfo & { children?: PublishConnectorInfo[] },
) =>
record.children ? null : (
<ConfigStatusColumn
isMouseIn={Boolean(mouseInfo[record.id])}
record={record}
{...props}
/>
),
},
];
const handleOnRow = (record: PublishConnectorInfo) => ({
onClick: () => {
if (!doIfDisabled([record], publishLoading)) {
setSelectedPlatforms(ids =>
ids?.includes(record.id)
? ids.filter(i => i !== record.id)
: [record.id, ...ids],
);
}
}, // 点击行选中
onMouseEnter: () => onMouseEnter(record), // 鼠标移入行
onMouseLeave: () => onMouseLeave(record), // 鼠标移出行
});
const tableCommonProps = {
className: classNames(styles['publish-table']),
rowKey: 'id',
onRow: handleOnRow,
defaultExpandAllRows: true,
expandIcon: <IconCozArrowRight style={{ fontSize: 16 }} />,
keepDOM: true,
expandRowByClick: true,
};
return (
<>
{!!dataSourceForChannel.length && (
<UITable
tableProps={{
columns: getColumns('connector'),
dataSource: dataTreeForChannel,
rowSelection: baseConfigForChecker,
...tableCommonProps,
}}
wrapperClassName={classNames(styles['publish-table-wrapper'])}
/>
)}
{!!dataSourceForOpen.length && (
<UITable
tableProps={{
columns: getColumns('api'),
dataSource: dataTreeForOpen,
rowSelection: baseConfigForChecker,
...tableCommonProps,
}}
wrapperClassName={styles['publish-table-wrapper']}
/>
)}
</>
);
};
function TableTitle({ type }: { type: 'connector' | 'api' }) {
const apiTitle = IS_OVERSEA
? I18n.t('api_sdk_published', {
coze_token: (
<Typography.Text
link
size="small"
onClick={() => window.open('/docs/guides/token')}
>
{I18n.t('Coze_token_title')}
</Typography.Text>
),
})
: I18n.t('api');
return (
<div className="pl-3">
{type === 'connector' ? I18n.t('bot_publish_columns_platform') : apiTitle}
</div>
);
}
function TableTittleExtra({
monetizeConfig,
platforms,
type,
botInfo,
}: {
type: 'connector' | 'api';
monetizeConfig?: BotMonetizationConfigData;
platforms: PublishConnectorInfo[];
botInfo: PublisherBotInfo;
}) {
// 付费墙
const isAvailable = useBenefitAvailable({
scene: PremiumPaywallScene.API,
});
const { node: premiumPaywallModal, open: openPremiumPaywallModal } =
usePremiumPaywallModal({ scene: PremiumPaywallScene.API });
if (type === 'api' && !isAvailable) {
return (
<>
<Space className="text-[12px] w-full justify-end pr-[28px]" spacing={2}>
<IconCozDiamondFill className="coz-fg-hglt" />
100
<div
className="coz-fg-hglt cursor-pointer"
onClick={openPremiumPaywallModal}
>
</div>
</Space>
{premiumPaywallModal}
</>
);
}
if (IS_OVERSEA && type === 'api') {
return (
<PluginPricingInfo pluginPricingRules={botInfo.pluginPricingRules} />
);
}
if (!IS_OVERSEA || !monetizeConfig || type !== 'connector') {
return null;
}
return (
<MonetizePublishInfo
className="pr-[24px]"
monetizeConfig={monetizeConfig}
supportPlatforms={platforms.filter(p => p.support_monetization)}
/>
);
}
function PlatformInfoColumn({
platform,
rowIcon,
}: {
platform: PublishConnectorInfo;
rowIcon: ReactNode;
}) {
return (
<Space style={{ width: '100%' }}>
<div
className={classNames(
styles['table-row-icon-wrapper'],
'pr-1',
platform.brand_id ? 'pl-1' : 'pl-3',
{ 'ml-10': !!platform.brand_id },
)}
>
{rowIcon}
</div>
<Avatar
size="small"
shape="square"
src={platform.icon}
className={styles['platform-avater']}
></Avatar>
<Typography.Text
className={styles['platform-name']}
ellipsis={{
showTooltip: {
opts: {
content: platform?.name,
style: { wordWrap: 'break-word' },
},
},
}}
>
{platform?.name}
</Typography.Text>
{platform?.desc ? (
<PublishPlatformDescription desc={platform.desc} />
) : null}
</Space>
);
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC } from 'react';
import {
transPricingRules,
usePluginLimitModal,
} from '@coze-studio/components';
import { I18n } from '@coze-arch/i18n';
import { Typography } from '@coze-arch/coze-design';
import { type PluginPricingRule } from '@coze-arch/bot-api/plugin_develop';
// 发布页提示
export const PluginPricingInfo: FC<{
pluginPricingRules?: Array<PluginPricingRule>;
}> = ({ pluginPricingRules }) => {
const pricingRules = transPricingRules(pluginPricingRules);
const { node, open } = usePluginLimitModal({
// @ts-expect-error - skip
dataSource: pricingRules,
content: (
<div>
{I18n.t('professional_plan_n_paid_plugins_included_in_bot', {
count: pricingRules.length,
})}
</div>
),
});
if (pricingRules.length === 0) {
return null;
}
return (
<>
{node}
<div className="pr-[24px] flex justify-end items-center gap-[6px]">
{I18n.t('plugins_with_limited_calls_added_tip')}
<Typography.Text className="font-bold" link size="small" onClick={open}>
{I18n.t('plugin_usage_limits_modal_view_details')}
</Typography.Text>
</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.
*/
export { AgentPublishPage } from './components/bot-publish';

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,100 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"target": "ES2020",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
"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/i18n/tsconfig.build.json"
},
{
"path": "../../arch/idl/tsconfig.build.json"
},
{
"path": "../../arch/logger/tsconfig.build.json"
},
{
"path": "../../arch/report-events/tsconfig.build.json"
},
{
"path": "../../arch/report-tti/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": "../../foundation/layout/tsconfig.build.json"
},
{
"path": "../space-bot/tsconfig.build.json"
},
{
"path": "../../studio/components/tsconfig.build.json"
},
{
"path": "../../studio/open-platform/open-auth/tsconfig.build.json"
},
{
"path": "../../studio/premium/premium-components-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",
"exclude": ["**/*"],
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
]
}

View File

@@ -0,0 +1,20 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler"
},
"include": ["__tests__", "vitest.config.ts", "stories"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
]
}

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