feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -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]);
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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]);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user