feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
/*
* 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,
ConnectorBindType,
ConnectorConfigStatus,
} from '@coze-arch/idl/intelligence_api';
// 未配置/授权场景
export const getConnectorNotConfigured = (
connector: PublishConnectorInfo,
): boolean => {
const { bind_type, config_status } = connector;
// 未绑定&未授权
const notConfigured =
[
ConnectorBindType.KvBind,
ConnectorBindType.AuthBind,
ConnectorBindType.KvAuthBind,
ConnectorBindType.TemplateBind, // mcp未配置时禁用模版始终为已配置
].includes(bind_type) &&
config_status === ConnectorConfigStatus.NotConfigured;
return notConfigured;
};
// 不能发布的场景:
// 1. 未绑定&未授权
// 2. 后端下发的不能发布没有workflow不能发api有私有插件不能发模板审核中不能发布的渠道
export const getDisabledPublish = (
connector: PublishConnectorInfo,
): boolean => {
const { allow_publish } = connector;
// 未绑定&未授权
const notConfigured = getConnectorNotConfigured(connector);
const connectorDisabled = notConfigured || !allow_publish;
// 审核中不能发布渠道的场景后端下发 allow_publish
return connectorDisabled;
};

View File

@@ -0,0 +1,50 @@
/*
* 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 { UIPreviewType } from '@coze-arch/idl/product_api';
import { type UIOption } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
export enum DisplayScreen {
Web = 'web',
Mobile = 'mobile',
}
export interface DisplayScreenOption {
label: string;
value: DisplayScreen;
disabled?: boolean;
tooltip?: string;
}
export function toDisplayScreenOption(uiOption: UIOption): DisplayScreenOption {
const publicProps = {
disabled: uiOption.available === false,
tooltip: uiOption.unavailable_reason,
};
if (uiOption.ui_channel === UIPreviewType.Web.toString()) {
return {
value: DisplayScreen.Web,
label: I18n.t('builder_canvas_tools_pc'),
...publicProps,
};
}
return {
value: DisplayScreen.Mobile,
label: I18n.t('builder_canvas_tools_phone'),
...publicProps,
};
}

View File

@@ -0,0 +1,103 @@
/*
* 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 ConnectorUnionInfo,
ConnectorClassification,
type PublishConnectorInfo,
} from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
export interface ConnectorGroup {
type: ConnectorClassification;
label: string;
desc: string;
connectors: PublishConnectorInfo[];
}
export function formatConnectorGroups(
connectors: PublishConnectorInfo[],
unionMap: Record<string, ConnectorUnionInfo>,
unions: Record<string, string>,
) {
const groups: ConnectorGroup[] = [
{
type: ConnectorClassification.APIOrSDK,
label: I18n.t('project_release_api1'),
desc: I18n.t('project_release_api_sdk_desc'),
connectors: [],
},
{
type: ConnectorClassification.MiniProgram,
label: I18n.t('project_release_miniprogram1'),
desc: I18n.t('project_release_h5_desc'),
connectors: [],
},
{
type: ConnectorClassification.SocialPlatform,
label: I18n.t('project_release_social1'),
desc: I18n.t('project_release_social_desc1'),
connectors: [],
},
{
type: ConnectorClassification.Coze,
label: I18n.t('project_release_coze1'),
desc: I18n.t('project_release_ts_desc'),
connectors: [],
},
{
type: ConnectorClassification.CozeSpaceExtensionLibrary,
label: I18n.t('app_publish_connector_mcp'),
desc: I18n.t('app_publish_connector_mcp'),
connectors: [],
},
];
for (const c of connectors) {
const group = groups.find(g => g.type === c.connector_classification);
if (!group) {
continue;
}
if (c.connector_union_id) {
const unionId = c.connector_union_id;
// 如果当前 union_id 已经被添加到分组中,则跳过
if (group.connectors.some(i => i.connector_union_id === unionId)) {
continue;
}
let connectorInfo = c;
// 优先取 union 选中的 connector否则取第一个
const unionSelection = connectors.find(i => i.id === unions[unionId]);
if (unionSelection) {
connectorInfo = unionSelection;
} else {
const firstId = unionMap[unionId].connector_options[0].connector_id;
const firstConnector = connectors.find(i => i.id === firstId);
if (firstConnector) {
connectorInfo = firstConnector;
}
}
const unionInfo = unionMap[unionId];
group.connectors.push({
...connectorInfo,
name: unionInfo.name,
description: unionInfo.description,
icon_url: unionInfo.icon_url,
});
} else {
group.connectors.push(c);
}
}
return groups;
}

View File

@@ -0,0 +1,92 @@
/*
* 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 {
ConnectorBindType,
ConnectorConfigStatus,
type PublishConnectorInfo,
} from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { type TagProps } from '@coze-arch/coze-design';
interface ConfigStatusUI {
text: string;
color: TagProps['color'];
}
export const getConfigStatus = (
record: PublishConnectorInfo,
): ConfigStatusUI => {
const { bind_type } = record;
if (
bind_type === ConnectorBindType.KvBind ||
bind_type === ConnectorBindType.KvAuthBind ||
bind_type === ConnectorBindType.TemplateBind
) {
return getKvBindStatus(record);
}
return getDefaultStatus(record);
};
const getKvBindStatus = (record: PublishConnectorInfo): ConfigStatusUI => {
const { config_status = ConnectorConfigStatus.Configured } = record;
const couldPublish = config_status === ConnectorConfigStatus.Configured;
const color = couldPublish ? 'green' : 'primary';
const textMap = {
[ConnectorConfigStatus.Configured]: I18n.t(
'bot_publish_columns_status_configured',
),
[ConnectorConfigStatus.NotConfigured]: I18n.t(
'bot_publish_columns_status_not_configured',
),
// 业务不会走到下面3个case
[ConnectorConfigStatus.Configuring]: '',
[ConnectorConfigStatus.Disconnected]: '',
[ConnectorConfigStatus.NeedReconfiguring]: '',
};
return {
text: textMap[config_status],
color,
};
};
const getDefaultStatus = (record: PublishConnectorInfo): ConfigStatusUI => {
const { config_status = ConnectorConfigStatus.Configured } = record;
const couldPublish = config_status === ConnectorConfigStatus.Configured;
const color = couldPublish ? 'green' : 'primary';
const textMap = {
[ConnectorConfigStatus.Configured]: I18n.t(
'bot_publish_columns_status_authorized',
),
[ConnectorConfigStatus.NotConfigured]: I18n.t(
'bot_publish_columns_status_unauthorized',
),
[ConnectorConfigStatus.Configuring]: I18n.t('publish_douyin_config_ing'),
[ConnectorConfigStatus.Disconnected]: '',
[ConnectorConfigStatus.NeedReconfiguring]: '',
};
return {
text: textMap[config_status],
color,
};
};

View File

@@ -0,0 +1,35 @@
/*
* 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 { incrementVersionNumber } from './increment-version-number';
export const getFixedVersionNumber = ({
lastPublishVersionNumber,
draftVersionNumber,
defaultVersionNumber,
}: {
lastPublishVersionNumber: string | undefined;
draftVersionNumber: string | undefined;
defaultVersionNumber: string;
}): string => {
if (lastPublishVersionNumber && !draftVersionNumber) {
return incrementVersionNumber(lastPublishVersionNumber);
}
if (draftVersionNumber) {
return draftVersionNumber;
}
return defaultVersionNumber;
};

View File

@@ -0,0 +1,31 @@
/*
* 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 const incrementVersionNumber = (input: string) => {
// 定义正则表达式,匹配 "数字.数字.数字" 的模式
const regex = /(\d+)\.(\d+)\.(\d+)/g;
// 使用 replace 方法和回调函数对匹配的部分进行替换
// eslint-disable-next-line max-params
const result = input.replace(regex, (_match, p1, p2, p3) => {
// 将最后一个数字加 1
const incrementedP3 = parseInt(String(p3), 10) + 1;
// 返回新的字符串
return `${p1}.${p2}.${incrementedP3}`;
});
return result;
};

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ConnectorClassification,
type ConnectorPublishConfig,
} from '@coze-arch/idl/intelligence_api';
import { MonetizationEntityType } from '@coze-arch/idl/benefit';
import { benefitApi, intelligenceApi } from '@coze-arch/bot-api';
import {
DEFAULT_VERSION_NUMBER,
WEB_SDK_CONNECTOR_ID,
} from '@/utils/constants';
import { useProjectPublishStore } from '@/store';
import { type ProjectPublishDraft } from './publish-draft';
import { getFixedVersionNumber } from './get-fixed-version-number';
import { getDisabledPublish } from './connector-disabled-publish';
// eslint-disable-next-line complexity -- it's complex
export async function initPublishStore(
projectId: string,
errorHandle: (e: unknown) => void,
draft?: ProjectPublishDraft,
) {
const { setProjectPublishInfo, setSelectedConnectorIds, setMonetizeConfig } =
useProjectPublishStore.getState();
setProjectPublishInfo({ pageLoading: true });
try {
const [publishResp, monetizeConfigResp] = await Promise.all([
intelligenceApi.PublishConnectorList({
project_id: projectId,
}),
IS_OVERSEA
? benefitApi.PublicGetBotMonetizationConfig({
entity_id: projectId,
entity_type: MonetizationEntityType.Project,
})
: Promise.resolve(undefined),
]);
const {
connector_list = [],
last_publish_info = {},
connector_union_info_map = {},
} = publishResp.data ?? {};
const { connector_ids = [], connector_publish_config = {} } =
last_publish_info;
// 初始化默认选中的渠道
const initSelectedConnectors: string[] = [];
const initConnectors: Record<string, Record<string, string>> = {};
for (const id of connector_ids) {
const connector = connector_list.find(c => c.id === id);
// 过滤掉不允许发布的渠道
if (!connector || getDisabledPublish(connector)) {
continue;
}
if (connector.connector_union_id) {
// 对于 union 的 connector ,选中其 union id
initSelectedConnectors.push(connector.connector_union_id);
initConnectors[connector.id] = connector.bind_info;
} else {
initSelectedConnectors.push(connector.id);
initConnectors[connector.id] = connector.bind_info;
}
}
// 初始化每个 union 选中的 connector如果上次没发布该渠道则选中第一个
const initUnions: Record<string, string> = {};
for (const [unionId, info] of Object.entries(connector_union_info_map)) {
initUnions[unionId] =
info.connector_options.find(o => connector_ids.includes(o.connector_id))
?.connector_id ?? info.connector_options[0].connector_id;
}
// 回填社交渠道选择的 chatflow优先级
// 1. draft 中保存的 chatflow
// 2. 上次发布的第一个 SocialPlatform 选择的 chatflow
let lastSocialPlatformChatflow: ConnectorPublishConfig | undefined;
if (draft?.socialPlatformConfig?.selected_workflows?.[0].workflow_id) {
lastSocialPlatformChatflow = draft.socialPlatformConfig;
} else {
for (const c of connector_list) {
if (
!initSelectedConnectors.includes(c.id) ||
c.connector_classification !== ConnectorClassification.SocialPlatform
) {
continue;
}
const lastConfig = connector_publish_config[c.id];
if (lastConfig?.selected_workflows?.[0].workflow_id) {
lastSocialPlatformChatflow = lastConfig;
break;
}
}
}
// 根据 draft 中保存的信息回填 WebSDK 渠道选择的 chatflow
if (draft?.sdkConfig?.selected_workflows?.[0].workflow_id) {
connector_publish_config[WEB_SDK_CONNECTOR_ID] = draft.sdkConfig;
}
setSelectedConnectorIds(
draft?.selectedConnectorIds ?? initSelectedConnectors,
);
const lastPublishVersionNumber = last_publish_info.version_number;
// 用户没有 draft 并且存在发布过的版本 则将上一次发布的版本号进行处理
const fixedVersionNumber = getFixedVersionNumber({
lastPublishVersionNumber,
draftVersionNumber: draft?.versionNumber,
defaultVersionNumber: DEFAULT_VERSION_NUMBER,
});
setProjectPublishInfo({
lastVersionNumber: lastPublishVersionNumber,
versionNumber: fixedVersionNumber,
versionDescription: draft?.versionDescription,
connectorPublishConfig: connector_publish_config,
connectorList: connector_list,
connectorUnionMap: connector_union_info_map,
connectors: initConnectors,
unions: draft?.unions ?? initUnions,
socialPlatformChatflow: lastSocialPlatformChatflow,
});
setMonetizeConfig(monetizeConfigResp?.data);
} catch (e) {
errorHandle(e);
} finally {
setProjectPublishInfo({ pageLoading: false });
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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 StoreBindKey } from '@/store';
type SelfMapping<T extends string> = {
[K in T]: K; // 关键语法:将每个字面量类型映射为自己
};
type KeyMapping = SelfMapping<StoreBindKey>;
export const isStoreBindConfigured = (
config: Record<string, string>,
): boolean => {
// 防止 StoreBindKey 有变动导致 bug
const { category_id, display_screen }: KeyMapping = {
category_id: 'category_id',
display_screen: 'display_screen',
};
return Boolean(config[category_id]) && Boolean(config[display_screen]);
};

View File

@@ -0,0 +1,47 @@
/*
* 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 ConnectorPublishConfig } from '@coze-arch/idl/intelligence_api';
import { typeSafeJSONParse } from '@coze-arch/bot-utils';
export const PUBLISH_DRAFT_KEY = 'coz_project_publish_draft';
export interface ProjectPublishDraft {
projectId: string;
versionNumber: string;
versionDescription: string;
selectedConnectorIds: string[];
unions: Record<string, string>;
sdkConfig?: ConnectorPublishConfig;
socialPlatformConfig?: ConnectorPublishConfig;
}
export function loadProjectPublishDraft(projectId: string) {
const str = localStorage.getItem(PUBLISH_DRAFT_KEY);
localStorage.removeItem(PUBLISH_DRAFT_KEY);
if (!str) {
return undefined;
}
const draft = typeSafeJSONParse(str) as ProjectPublishDraft | undefined;
if (draft?.projectId === projectId) {
return draft;
}
return undefined;
}
export function saveProjectPublishDraft(draft: ProjectPublishDraft) {
localStorage.setItem(PUBLISH_DRAFT_KEY, JSON.stringify(draft));
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { I18n } from '@coze-arch/i18n';
import { intelligenceApi } from '@coze-arch/bot-api';
export async function checkVersionNum(
versionNumber: string,
projectId: string,
) {
if (!versionNumber) {
return I18n.t('project_release_example2');
}
const { data } = await intelligenceApi.CheckProjectVersionNumber({
project_id: projectId,
version_number: versionNumber,
});
if (data?.is_duplicate) {
return I18n.t('project_release_example3');
} else {
return '';
}
}