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

View File

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

View File

@@ -0,0 +1,67 @@
# @coze-studio/bot-audit-adapter
bot audit pkg
## Overview
This package is part of the Coze Studio monorepo and provides ide features functionality. It includes hook.
## Getting Started
### Installation
Add this package to your `package.json`:
```json
{
"dependencies": {
"@coze-studio/bot-audit-adapter": "workspace:*"
}
}
```
Then run:
```bash
rush update
```
### Usage
```typescript
import { /* exported functions/components */ } from '@coze-studio/bot-audit-adapter';
// Example usage
// TODO: Add specific usage examples
```
## Features
- Hook
## API Reference
### Exports
- `botInfoAudit, useBotInfoAuditor`
- `AuditErrorMessage`
For detailed API documentation, please refer to the TypeScript definitions.
## Development
This package is built with:
- TypeScript
- Modern JavaScript
- Vitest for testing
- ESLint for code quality
## Contributing
This package is part of the Coze Studio monorepo. Please follow the monorepo contribution guidelines.
## License
Apache-2.0

View File

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

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: 'node',
rules: {},
});

View File

@@ -0,0 +1,41 @@
{
"name": "@coze-studio/bot-audit-adapter",
"version": "0.0.1",
"description": "bot audit pkg",
"license": "Apache-2.0",
"author": "lengfangbing@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": {
"@coze-studio/bot-audit-base": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-flags": "workspace:*",
"@coze-arch/bot-space-api": "workspace:*",
"@coze-arch/i18n": "workspace:*"
},
"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:*",
"@types/node": "^18",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"sucrase": "^3.32.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState } from 'react';
import {
type BotAuditInfo,
type BotInfoAuditData,
} from '@coze-arch/bot-api/playground_api';
import {
type UseBotInfoAuditorHook,
type BotInfoAuditFunc,
} from '@coze-studio/bot-audit-base';
const defaultPassState = true;
export const botInfoAudit: BotInfoAuditFunc = (
_: BotAuditInfo,
): Promise<BotInfoAuditData> => Promise.resolve({});
export const useBotInfoAuditor: UseBotInfoAuditorHook = () => {
const [pass, setPass] = useState(defaultPassState);
return {
check: (_: BotAuditInfo) => Promise.resolve({}),
pass,
setPass,
reset: () => {
setPass(defaultPassState);
},
};
};

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.node.json",
"compilerOptions": {
"types": [],
"jsx": "react",
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../arch/bot-flags/tsconfig.build.json"
},
{
"path": "../../arch/bot-space-api/tsconfig.build.json"
},
{
"path": "../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../arch/i18n/tsconfig.build.json"
},
{
"path": "../bot-audit-base/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": "../../studio/stores/bot-detail/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,17 @@
{
"extends": "@coze-arch/ts-config/tsconfig.node.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "vitest.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"types": ["vitest/globals"],
"jsx": "react"
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'node',
test: {
coverage: {
all: true,
},
},
});

View File

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

View File

@@ -0,0 +1,132 @@
# @coze-studio/bot-audit-base
> Audit base package for bot content validation and error handling
## Project Overview
This package provides foundational components and interfaces for bot content auditing within the Coze Studio platform. It includes UI components for displaying audit error messages and TypeScript interfaces for audit functionality integration.
## Features
- **AuditErrorMessage Component**: Pre-styled React component for displaying audit failure messages with customizable documentation links
- **Type Definitions**: Comprehensive TypeScript interfaces for bot audit hooks and functions
- **Internationalization Support**: Built-in i18n support for error messages
- **Storybook Integration**: Component documentation and testing environment
## Get Started
### Installation
Add this package to your `package.json` dependencies and set it to `workspace:*` version:
```json
{
"dependencies": {
"@coze-studio/bot-audit-base": "workspace:*"
}
}
```
Then run:
```bash
rush update
```
### Basic Usage
#### Using the AuditErrorMessage Component
```tsx
import { AuditErrorMessage } from '@coze-studio/bot-audit-base';
function MyComponent() {
return (
<AuditErrorMessage
link="/docs/custom-guidelines"
/>
);
}
```
#### Implementing Audit Functionality
```tsx
import type { UseBotInfoAuditorHook, BotInfoAuditFunc } from '@coze-studio/bot-audit-base';
// Example hook implementation
const useBotAuditor: UseBotInfoAuditorHook = () => {
const [pass, setPass] = useState(false);
const check: BotInfoAuditFunc = async (params) => {
// Your audit logic here
const result = await performAudit(params);
setPass(result.success);
return result;
};
const reset = () => setPass(false);
return { check, pass, setPass, reset };
};
```
## API Reference
### Components
#### `AuditErrorMessage`
Displays standardized audit error messages with documentation links.
**Props:**
- `link` (optional): Custom documentation link URL. Defaults to `/docs/guides/content_principles`
### Types
#### `UseBotInfoAuditorHook`
Hook interface for bot audit functionality.
**Returns:**
- `check`: Function to perform audit checks
- `pass`: Boolean indicating audit status
- `setPass`: State setter for audit status
- `reset`: Function to reset audit state
#### `BotInfoAuditFunc`
Function type for audit operations.
**Parameters:**
- `params`: `BotAuditInfo` - Audit parameters
**Returns:** `Promise<BotInfoAuditData>` - Audit result data
## Development
### Available Scripts
- `npm run dev` - Start Storybook development server
- `npm run build` - Build the package
- `npm run lint` - Run ESLint
- `npm run test` - Run tests with Vitest
### Project Structure
```
src/
├── components/
│ └── audit-error-message/ # AuditErrorMessage component
├── interfaces/ # TypeScript type definitions
└── index.ts # Main export file
```
## Dependencies
This package depends on:
- `@coze-arch/bot-api` - Bot API types and interfaces
- `@coze-arch/i18n` - Internationalization utilities
- `classnames` - CSS class utility
## License
Apache-2.0

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,43 @@
{
"name": "@coze-studio/bot-audit-base",
"version": "0.0.1",
"description": "audit base package",
"license": "Apache-2.0",
"author": "sunzhiyuan.evan@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": {
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/i18n": "workspace:*",
"classnames": "^2.3.2"
},
"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/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,9 @@
.error-message {
@apply text-left;
color: rgb(255, 68, 30);
.link {
color: #4d53e8;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { I18n } from '@coze-arch/i18n';
import styles from './index.module.less';
export function AuditErrorMessage({
link = '/docs/guides/content_principles',
}: {
link?: string;
}) {
return (
<div className={styles['error-message']}>
{I18n.t('audit_unsuccess_general_type', {
link: (
<a
rel="noreferrer noopener"
href={link}
target="_blank"
className={styles.link}
>
{I18n.t('audit_unsuccess_general_type_url')}
</a>
),
})}
</div>
);
}

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 { AuditErrorMessage } from './components/audit-error-message';
export { UseBotInfoAuditorHook, BotInfoAuditFunc } from './interfaces';

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type React from 'react';
import {
type BotAuditInfo,
type BotInfoAuditData,
} from '@coze-arch/bot-api/playground_api';
export declare type UseBotInfoAuditorHook = () => {
check: (params: BotAuditInfo) => Promise<BotInfoAuditData>;
pass: boolean;
setPass: React.Dispatch<React.SetStateAction<boolean>>;
reset: () => void;
};
export declare type BotInfoAuditFunc = (
params: BotAuditInfo,
) => Promise<BotInfoAuditData>;

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,37 @@
{
"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-typings/tsconfig.build.json"
},
{
"path": "../../arch/i18n/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"
}
]
}

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,18 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"target": "ES2020"
},
"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',
});

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/bot-config-area-adapter
> 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,51 @@
{
"name": "@coze-agent-ide/bot-config-area-adapter",
"version": "0.0.1",
"description": "@coze-agent-ide/bot-config-area-adapter",
"license": "Apache-2.0",
"author": "haozhenfei@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/bot-config-area": "workspace:*",
"@coze-agent-ide/model-manager": "workspace:*",
"@coze-agent-ide/space-bot": "workspace:*",
"@coze-agent-ide/tool": "workspace:*",
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@coze-studio/components": "workspace:*",
"classnames": "^2.3.2"
},
"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/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,79 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC } from 'react';
import { ToolMenu } from '@coze-agent-ide/tool';
import { useRiskWarningStore } from '@coze-agent-ide/space-bot/store';
import { CollapsibleIconButtonGroup } from '@coze-studio/components/collapsible-icon-button';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
import { BotMode, RiskAlertType } from '@coze-arch/bot-api/playground_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { MonetizeConfigButton } from '@coze-agent-ide/bot-config-area';
import { ModelConfigView } from './model-config-view';
export interface BotConfigAreaProps {
pageFrom?: BotPageFromEnum;
editable?: boolean;
modelListExtraHeaderSlot?: React.ReactNode;
}
export const BotConfigArea: FC<BotConfigAreaProps> = ({
pageFrom,
editable,
modelListExtraHeaderSlot,
}) => {
const mode = useBotInfoStore(state => state.mode);
const isReadonly = useBotDetailIsReadonly();
const toolHiddenModeNewbieGuideIsRead = useRiskWarningStore(
state => state.toolHiddenModeNewbieGuideIsRead,
);
const onNewbieGuidePopoverClose = () => {
useRiskWarningStore.getState().setToolHiddenModeNewbieGuideIsRead(true);
PlaygroundApi.UpdateUserConfig({
risk_alert_type: RiskAlertType.NewBotIDEGuide,
});
};
const isSingleLLM = mode === BotMode.SingleMode;
const isSingleWorkflow = mode === BotMode.WorkflowMode;
return (
<div className="flex items-center justify-end gap-[12px] flex-1 overflow-hidden">
<CollapsibleIconButtonGroup>
<ModelConfigView
mode={mode}
modelListExtraHeaderSlot={modelListExtraHeaderSlot}
/>
{pageFrom === BotPageFromEnum.Bot && IS_OVERSEA ? (
<MonetizeConfigButton />
) : null}
</CollapsibleIconButtonGroup>
{!isReadonly && (isSingleLLM || isSingleWorkflow) ? (
<ToolMenu
newbieGuideVisible={!toolHiddenModeNewbieGuideIsRead}
onNewbieGuidePopoverClose={onNewbieGuidePopoverClose}
rePosKey={Math.random()}
/>
) : null}
</div>
);
};

View File

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

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 { ModelConfigView } from './model-config-view';

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 { I18n } from '@coze-arch/i18n';
import { BotMode } from '@coze-arch/bot-api/playground_api';
import { useGetSingleAgentCurrentModel } from '@coze-agent-ide/model-manager';
import { DialogueConfigView } from '@coze-agent-ide/bot-config-area';
import { SingleAgentModelView } from './single-agent-model-view';
export const ModelConfigView: React.FC<{
mode: BotMode;
modelListExtraHeaderSlot?: React.ReactNode;
}> = ({ mode, modelListExtraHeaderSlot }) => {
const currentModel = useGetSingleAgentCurrentModel();
if (mode === BotMode.SingleMode) {
return currentModel?.model_type ? (
<SingleAgentModelView
modelListExtraHeaderSlot={modelListExtraHeaderSlot}
/>
) : null;
}
if (mode === BotMode.MultiMode || mode === BotMode.WorkflowMode) {
return (
<DialogueConfigView
tips={
mode === BotMode.WorkflowMode
? I18n.t('workflow_agent_dialog_set_desc')
: null
}
/>
);
}
return null;
};

View File

@@ -0,0 +1,76 @@
/*
* 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 { Image } from '@coze-arch/bot-semi';
import { Collapsible } from '@coze-studio/components/collapsible-icon-button';
import { ModelOptionThumb } from '@coze-agent-ide/model-manager/model-select-v2';
import {
SingleAgentModelView as SingleAgentModelViewBase,
type SingleAgentModelViewProps,
} from '@coze-agent-ide/bot-config-area';
import { IconCozArrowDown } from '@coze-arch/coze-design/icons';
import { Button, Tag } from '@coze-arch/coze-design';
const itemKey = Symbol.for('SingleAgentModelView');
export function SingleAgentModelView(props: SingleAgentModelViewProps) {
return (
<SingleAgentModelViewBase
{...props}
triggerRender={m => (
// 模型临期时强制完整展示临期提示
<Collapsible
itemKey={itemKey}
fullContent={
<Button
color="secondary"
size="default"
data-testid="bot.ide.bot_creator.set_model_view_button"
>
{m ? <ModelOptionThumb model={m} /> : null}
<IconCozArrowDown className="coz-fg-secondary" />
</Button>
}
collapsedContent={
<Button
size="default"
color="secondary"
icon={
<Image
preview={false}
className="leading-none"
width={16}
height={16}
src={m?.model_icon}
/>
}
>
{m?.model_status_details?.is_upcoming_deprecated ? (
<span className="h-full flex items-center">
<Tag size="mini" color="yellow" className="font-medium">
{I18n.t('model_list_willDeprecated')}
</Tag>
</span>
) : null}
</Button>
}
collapsedTooltip={m?.name}
/>
)}
/>
);
}

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.
*/
/// <reference types='@coze-arch/bot-typings' />
declare const IS_OVERSEA: boolean;

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DemoComponent } from '../src';
export default {
title: 'Example/Demo',
component: DemoComponent,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {},
};
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Base = {
args: {
name: 'tecvan',
},
};

View File

@@ -0,0 +1,34 @@
import { Meta } from "@storybook/blocks";
<Meta title="Hello world" />
<div className="sb-container">
<div className='sb-section-title'>
# Hello world
Hello world
</div>
</div>
<style>
{`
.sb-container {
margin-bottom: 48px;
}
.sb-section {
width: 100%;
display: flex;
flex-direction: row;
gap: 20px;
}
img {
object-fit: cover;
}
.sb-section-title {
margin-bottom: 32px;
}
`}
</style>

View File

@@ -0,0 +1,60 @@
{
"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"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
"references": [
{
"path": "../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../arch/i18n/tsconfig.build.json"
},
{
"path": "../bot-config-area/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": "../model-manager/tsconfig.build.json"
},
{
"path": "../space-bot/tsconfig.build.json"
},
{
"path": "../../studio/components/tsconfig.build.json"
},
{
"path": "../../studio/stores/bot-detail/tsconfig.build.json"
},
{
"path": "../tool/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',
});

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/bot-config-area
> 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,58 @@
{
"name": "@coze-agent-ide/bot-config-area",
"version": "0.0.1",
"description": "@coze-agent-ide/bot-config-area",
"license": "Apache-2.0",
"author": "haozhenfei@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": {
"@coze-agent-ide/bot-creator-context": "workspace:*",
"@coze-agent-ide/bot-editor-context-store": "workspace:*",
"@coze-agent-ide/model-manager": "workspace:*",
"@coze-agent-ide/space-bot": "workspace:*",
"@coze-agent-ide/tool": "workspace:*",
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/idl": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@coze-studio/components": "workspace:*",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"lodash-es": "^4.17.21",
"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:*",
"@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,23 @@
/*
* 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 { DialogueConfigView } from './model-config-view/dialogue-config-view';
export { SingleAgentModelView } from './model-config-view/single-agent-model-view';
export { MonetizeConfigButton } from './monetize-config/button';
export { MonetizeConfigPanel } from './monetize-config/panel';
export { QueryCollect } from './query-collect';
export { ModelConfigView } from './model-config-view';
export type { SingleAgentModelViewProps } from './model-config-view/single-agent-model-view';

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, type ReactNode } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { I18n } from '@coze-arch/i18n';
import { Popover } from '@coze-arch/bot-semi';
import { CollapsibleIconButton } from '@coze-studio/components/collapsible-icon-button';
import { InputSlider } from '@coze-studio/components';
import { useModelStore } from '@coze-studio/bot-detail-store/model';
import { ModelFormItem } from '@coze-agent-ide/model-manager';
import { IconCozChatSetting } from '@coze-arch/coze-design/icons';
const DialogueConfig: FC<{ tips: ReactNode }> = ({ tips }) => {
const { model, setModelByImmer } = useModelStore(
useShallow(state => ({
model: state,
setModelByImmer: state.setModelByImmer,
})),
);
const handleChange = (value: number) => {
setModelByImmer(draft => {
if (!draft.config.ShortMemPolicy) {
draft.config.ShortMemPolicy = { HistoryRound: value };
return;
}
draft.config.ShortMemPolicy.HistoryRound = value;
});
};
return (
<div className="p-[24px]">
<div className="leading-[32px] coz-fg-plus text-[20px] font-[500]">
{I18n.t('workflow_agent_dialog_set')}
</div>
{tips ? (
<div className="mt-[16px] coz-fg-secondary text-[14px] leading-[20px]">
{tips}
</div>
) : null}
<div className="mt-[16px] coz-fg-plus text-[14px] leading-[20px] font-[500]">
{I18n.t('workflow_agent_dialog_set_chathistory')}
</div>
<ModelFormItem
popoverContent={I18n.t('model_config_history_round_explain')}
label={I18n.t('model_config_history_round')}
>
<InputSlider
step={1}
min={0}
max={100}
decimalPlaces={0}
value={model.config.ShortMemPolicy?.HistoryRound}
onChange={handleChange}
/>
</ModelFormItem>
</div>
);
};
const itemKey = Symbol.for('DialogueConfigView');
export const DialogueConfigView: FC<{
tips: ReactNode;
}> = ({ tips }) => (
<Popover
className="overflow-hidden rounded-[12px] w-[600px]"
trigger="click"
autoAdjustOverflow={true}
content={<DialogueConfig tips={tips} />}
>
<CollapsibleIconButton
itemKey={itemKey}
data-testid="bot.ide.bot_creator.set_model_view_button"
icon={<IconCozChatSetting className="text-[16px]" />}
text={I18n.t('workflow_agent_dialog_set')}
/>
</Popover>
);

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DialogueConfigView } from './dialogue-config-view';
export { SingleAgentModelView } from './single-agent-model-view';
export { ModelConfigView } from './model-config-view';

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 { I18n } from '@coze-arch/i18n';
import { BotMode } from '@coze-arch/bot-api/playground_api';
import { useGetSingleAgentCurrentModel } from '@coze-agent-ide/model-manager';
import { SingleAgentModelView } from './single-agent-model-view';
import { DialogueConfigView } from './dialogue-config-view';
export const ModelConfigView: React.FC<{
mode: BotMode;
modelListExtraHeaderSlot?: React.ReactNode;
}> = ({ mode, modelListExtraHeaderSlot }) => {
const currentModel = useGetSingleAgentCurrentModel();
if (mode === BotMode.SingleMode) {
return currentModel?.model_type ? (
<SingleAgentModelView
modelListExtraHeaderSlot={modelListExtraHeaderSlot}
/>
) : null;
}
if (mode === BotMode.MultiMode || mode === BotMode.WorkflowMode) {
return (
<DialogueConfigView
tips={
mode === BotMode.WorkflowMode
? I18n.t('workflow_agent_dialog_set_desc')
: null
}
/>
);
}
return null;
};

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useState } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useModelStore } from '@coze-studio/bot-detail-store/model';
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { type Model } from '@coze-arch/bot-api/developer_api';
import { ModelSelect } from '@coze-agent-ide/model-manager/model-select-v2';
import {
useModelCapabilityCheckModal,
useGetSingleAgentCurrentModel,
getModelOptionList,
} from '@coze-agent-ide/model-manager';
import { useBotEditor } from '@coze-agent-ide/bot-editor-context-store';
import {
useBotCreatorContext,
BotCreatorScene,
} from '@coze-agent-ide/bot-creator-context';
export interface SingleAgentModelViewProps {
modelListExtraHeaderSlot?: React.ReactNode;
triggerRender?: (model?: Model, popoverVisible?: boolean) => React.ReactNode;
}
export function SingleAgentModelView(props: SingleAgentModelViewProps) {
const { modelListExtraHeaderSlot, triggerRender } = props;
const spaceId = useSpaceStore(store => store.space.id);
const { scene } = useBotCreatorContext();
const currentModel = useGetSingleAgentCurrentModel();
const currentModelId = currentModel?.model_type
? String(currentModel.model_type)
: undefined;
const { storeSet } = useBotEditor();
const modelStore = storeSet.useModelStore(
useShallow(state => ({
onlineModelList: state.onlineModelList,
offlineModelMap: state.offlineModelMap,
getModelPreset: state.getModelPreset,
})),
);
const [currentModelIdState, setCurrentModelIdState] = useState<
string | undefined
>(currentModelId);
const { modelConfig, setModelByImmer } = useModelStore(
useShallow(state => ({
modelConfig: state.config,
setModelByImmer: state.setModelByImmer,
})),
);
const { modalNode, checkAndOpenModal } = useModelCapabilityCheckModal({
onOk: modelId => {
setCurrentModelIdState(modelId);
},
});
const isReadonly = useBotDetailIsReadonly();
const modelList = getModelOptionList({
onlineModelList: modelStore.onlineModelList,
offlineModelMap: modelStore.offlineModelMap,
currentModelId: String(currentModel?.model_type),
});
useEffect(() => {
setCurrentModelIdState(currentModelId);
}, [currentModelId]);
return currentModelIdState ? (
<>
<ModelSelect
popoverClassName="h-auto !max-h-[70vh]"
disabled={isReadonly}
enableJumpDetail={
scene === BotCreatorScene.Bot && spaceId && !IS_OPEN_SOURCE
? { spaceId }
: undefined
}
modelListExtraHeaderSlot={modelListExtraHeaderSlot}
selectedModelId={currentModelIdState}
modelList={modelList}
onModelChange={m => {
const modelId = String(m.model_type);
const checkPassed = checkAndOpenModal(modelId);
if (checkPassed) {
setCurrentModelIdState(modelId);
}
return checkPassed;
}}
modelConfigProps={{
hideDiversityCollapseButton: true,
agentType: 'single',
currentConfig: modelConfig,
onConfigChange: v => {
setModelByImmer(draft => {
draft.config = {
model: currentModelIdState,
...v,
};
});
},
modelStore,
}}
triggerRender={triggerRender}
modalSlot={modalNode}
/>
</>
) : null;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CollapsibleIconButton } from '@coze-studio/components/collapsible-icon-button';
import { useMonetizeConfigStore } from '@coze-studio/bot-detail-store';
import { I18n } from '@coze-arch/i18n';
import { IconCozWallet } from '@coze-arch/coze-design/icons';
import { Popover } from '@coze-arch/coze-design';
import { MonetizeConfigPanel } from '../panel';
const itemKey = Symbol.for('MonetizeConfigButton');
export function MonetizeConfigButton() {
const isOn = useMonetizeConfigStore(store => store.isOn);
return (
<Popover
trigger="click"
autoAdjustOverflow={true}
content={<MonetizeConfigPanel />}
>
<CollapsibleIconButton
itemKey={itemKey}
icon={<IconCozWallet className="text-[16px]" />}
text={isOn ? I18n.t('monetization_on') : I18n.t('monetization_off')}
color={isOn ? 'highlight' : 'secondary'}
/>
</Popover>
);
}

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 { useDebounceFn } from 'ahooks';
import { useMonetizeConfigReadonly } from '@coze-agent-ide/space-bot/hook';
import {
MonetizeCreditRefreshCycle,
MonetizeDescription,
MonetizeFreeChatCount,
MonetizeSwitch,
} from '@coze-studio/components/monetize';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { useMonetizeConfigStore } from '@coze-studio/bot-detail-store';
import {
MonetizationEntityType,
type BotMonetizationRefreshPeriod,
} from '@coze-arch/idl/benefit';
import { benefitApi } from '@coze-arch/bot-api';
export function MonetizeConfigPanel() {
const botId = useBotInfoStore(store => store.botId);
const {
isOn,
freeCount,
refreshCycle,
setIsOn,
setFreeCount,
setRefreshCycle,
} = useMonetizeConfigStore();
const isReadonly = useMonetizeConfigReadonly();
const { run: debouncedSaveBotConfig } = useDebounceFn(
({
isEnable,
freeChats,
}: {
isEnable: boolean;
freeChats: number;
refreshCycle: BotMonetizationRefreshPeriod;
}) => {
benefitApi.PublicSaveBotDraftMonetizationConfig({
entity_id: botId,
entity_type: MonetizationEntityType.Bot,
is_enable: isEnable,
free_chat_allowance_count: freeChats,
refresh_period: refreshCycle,
});
},
{ wait: 300 },
);
const refreshCycleDisabled = !isOn || isReadonly || freeCount <= 0;
return (
<div className="w-[480px] p-[24px] flex flex-col gap-[24px]">
<MonetizeSwitch
disabled={isReadonly}
isOn={isOn}
onChange={value => {
setIsOn(value);
debouncedSaveBotConfig({
isEnable: value,
freeChats: freeCount,
refreshCycle,
});
}}
/>
<MonetizeDescription isOn={isOn} />
<MonetizeFreeChatCount
isOn={isOn}
disabled={isReadonly}
freeCount={freeCount}
onFreeCountChange={value => {
setFreeCount(value);
debouncedSaveBotConfig({
isEnable: isOn,
freeChats: value,
refreshCycle,
});
}}
/>
<MonetizeCreditRefreshCycle
freeCount={freeCount}
disabled={refreshCycleDisabled}
refreshCycle={refreshCycle}
onRefreshCycleChange={value => {
setRefreshCycle(value);
debouncedSaveBotConfig({
isEnable: isOn,
freeChats: freeCount,
refreshCycle: value,
});
}}
/>
</div>
);
}

View File

@@ -0,0 +1,237 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, type MouseEvent, useEffect, useRef, useState } from 'react';
import { get } from 'lodash-es';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { I18n } from '@coze-arch/i18n';
import { IconCozTamplate } from '@coze-arch/coze-design/icons';
import {
Button,
Form,
type FormApi,
IconButton,
Popover,
Typography,
} from '@coze-arch/coze-design';
import { type GenerateUserQueryCollectPolicyRequest } from '@coze-arch/bot-api/playground_api';
import { Tips } from './tips';
import s from './index.module.less';
const options = [
{
label: I18n.t('bot_dev_privacy_setting_conversation'),
value: I18n.t('bot_dev_privacy_setting_conversation'),
},
];
const defaultOptionsValue = [I18n.t('bot_dev_privacy_setting_conversation')];
interface GenerateByTemplateProps {
handleGenerate: (v: GenerateUserQueryCollectPolicyRequest) => void;
loading: boolean;
templateLink: string;
link: string;
}
// eslint-disable-next-line @coze-arch/max-line-per-function
export const GenerateByTemplate: FC<GenerateByTemplateProps> = ({
handleGenerate,
loading,
templateLink,
link,
}) => {
const { botId } = useBotInfoStore($store => ({
botId: $store.botId,
}));
const [visible, setVisible] = useState(false);
const [configInfo, setConfigInfo] =
useState<GenerateUserQueryCollectPolicyRequest>();
const [isFailToValid, setIsFailToValid] = useState(true);
const formApi = useRef<FormApi<GenerateUserQueryCollectPolicyRequest>>();
const onFormValueChange = (values: GenerateUserQueryCollectPolicyRequest) => {
const developerName = get(values, 'developer_name');
const contactInformation = get(values, 'contact_information');
setIsFailToValid(!(developerName && contactInformation));
setConfigInfo({
...values,
});
};
const onVisibleChange = (isVisble: boolean) => {
if (isVisble) {
setDefaultValue();
}
};
const setDefaultValue = () => {
if (configInfo) {
formApi.current?.setValue('developer_name', configInfo.developer_name);
formApi.current?.setValue(
'contact_information',
configInfo.contact_information,
);
}
};
useEffect(() => {
if (link) {
setConfigInfo({ developer_name: '', contact_information: '' });
setVisible(false);
}
}, [link]);
const onClickGenerate = () => {
handleGenerate({ ...configInfo, bot_id: botId });
};
const onOpen = (e: MouseEvent) => {
e.stopPropagation();
setVisible(true);
};
return (
<Popover
position="right"
trigger="custom"
stopPropagation={true}
onVisibleChange={onVisibleChange}
visible={visible}
onClickOutSide={() => setVisible(false)}
content={
<div className="p-[16px] w-[320px]">
<div className="coz-fg-plus text-[20px] font-medium leading-[32px]">
{I18n.t('bot_dev_privacy_setting_privacy_template_1')}
</div>
<div className="coz-fg-primary text-[14px] font-normal leading-[20px] pb-[12px]">
{I18n.t('bot_dev_privacy_setting_privacy_template_2', {
privacy_template: (
<Typography.Text link onClick={() => window.open(templateLink)}>
{I18n.t('bot_dev_privacy_setting_privacy_template_3')}
</Typography.Text>
),
})}
</div>
<div>
<Form<GenerateUserQueryCollectPolicyRequest>
getFormApi={api => (formApi.current = api)}
labelPosition="top"
showValidateIcon={false}
className={s['form-wrap']}
onValueChange={values =>
onFormValueChange(
values as GenerateUserQueryCollectPolicyRequest,
)
}
autoComplete="off"
disabled={loading}
>
<Form.Input
field="developer_name"
label={I18n.t('bot_dev_privacy_setting_developer_name')}
style={{ width: '100%' }}
trigger="blur"
maxLength={50}
placeholder={I18n.t(
'bot_dev_privacy_setting_developer_collect3',
)}
rules={[
{
required: true,
message: I18n.t(
'bot_dev_privacy_setting_developer_collect3',
),
},
]}
/>
<Form.Select
field="collect_detail"
label={{
text: I18n.t('bot_dev_privacy_setting_developer_collect1'),
extra: (
<Tips
content={I18n.t(
'bot_dev_privacy_setting_developer_collect7',
)}
size="small"
/>
),
}}
optionList={options}
disabled
initValue={defaultOptionsValue}
style={{ width: '100%' }}
placeholder={I18n.t(
'bot_dev_privacy_setting_developer_collect4',
)}
rules={[
{
required: true,
message: I18n.t(
'bot_dev_privacy_setting_developer_collect4',
),
},
]}
/>
<Form.Input
field="contact_information"
label={I18n.t('bot_dev_privacy_setting_developer_collect2')}
style={{ width: '100%' }}
trigger="blur"
maxLength={50}
placeholder={I18n.t(
'bot_dev_privacy_setting_developer_collect5',
)}
rules={[
{
required: true,
message: I18n.t(
'bot_dev_privacy_setting_developer_collect5',
),
},
]}
/>
</Form>
</div>
<div className="flex justify-end mt-[12px]">
<Button
loading={loading}
onClick={onClickGenerate}
disabled={isFailToValid}
>
{I18n.t(
loading
? 'bot_dev_privacy_setting_generate_link2'
: 'bot_dev_privacy_setting_generate_link1',
)}
</Button>
</div>
</div>
}
>
<IconButton
icon={<IconCozTamplate />}
iconPosition="left"
color="secondary"
size="small"
onClick={onOpen}
>
{I18n.t('bot_dev_privacy_setting_privacy_template')}
</IconButton>
</Popover>
);
};

View File

@@ -0,0 +1,96 @@
/* stylelint-disable max-nesting-depth */
/* stylelint-disable selector-class-pattern */
.query-collect-modal {
:global {
.semi-modal-header {
align-items: center;
.semi-button-borderless {
width: 40px;
height: 40px;
padding: 11px;
&:hover {
background-color: rgba(var(--coze-bg-6), var(--coze-bg-6-alpha));
}
&:active {
background-color: rgba(var(--coze-bg-8), var(--coze-bg-8-alpha));
}
.semi-button-content {
color: rgba(var(--coze-fg-2), var(--coze-fg-2-alpha));
.semi-icon {
font-size: 18px;
}
}
}
}
.semi-modal-footer {
margin: 0;
}
.semi-switch:not(.semi-switch-checked){
background-color: var(--semi-color-fill-0);
}
.semi-switch:not(.semi-switch-checked):hover {
background-color: var(--semi-color-fill-1);
}
}
}
.form-wrap {
:global {
.semi-form-field {
padding: 0;
padding-bottom: 16px;
}
.semi-form-field-label {
margin-bottom: 6px;
padding: 0 8px;
font-size: 12px;
font-weight: 500;
font-style: normal;
line-height: 16px;
color: var(--coz-fg-secondary);
}
.semi-form-field-error-message {
padding-left: 8px;
}
.semi-input-prefix-text {
font-size: 12px;
font-weight: 400;
color: var(--coz-fg-secondary);
}
.semi-input-wrapper {
background-color: transparent;
}
.semi-input-suffix {
.coz-icon-button {
display: flex;
padding-right: 4px;
.coz-button.coz-btn-small{
border-radius: 6px;
}
}
}
.semi-form-field-label-required .semi-form-field-label-text::after{
margin-left: 0;
}
.semi-input-wrapper__with-suffix .semi-input{
padding-right: 4px;
}
}
}

Some files were not shown because too many files have changed in this diff Show More