feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
158
frontend/packages/agent-ide/space-bot/src/hook/index.ts
Normal file
158
frontend/packages/agent-ide/space-bot/src/hook/index.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 { useEffect, useMemo } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { size } from 'lodash-es';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
messageReportEvent,
|
||||
skillKeyToApiStatusKeyTransformer,
|
||||
} from '@coze-arch/bot-utils';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { type TRouteConfigGlobal, useRouteConfig } from '@coze-arch/bot-hooks';
|
||||
import {
|
||||
type TabDisplayItems,
|
||||
TabStatus,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { type SkillKeyEnum } from '@coze-agent-ide/tool-config';
|
||||
|
||||
/**hook */
|
||||
export {
|
||||
useAgentPersistence,
|
||||
useAgentFormManagement,
|
||||
AgentInfoForm,
|
||||
type AgentInfoFormProps,
|
||||
type UseAgentFormManagementProps,
|
||||
type UseAgentPersistenceProps,
|
||||
type AgentInfoFormValue,
|
||||
} from './use-create-bot';
|
||||
export { useSubscribeOnboardingAndUpdateChatArea } from './use-subscribe-onboarding-and-update-chat-area';
|
||||
export { useWorkflowPublishedModel } from './tools-publish-back-modal';
|
||||
export { useEditConfirm } from './use-edit-confirm';
|
||||
export { useInit, AgentInitCallback, AgentInitProps } from './use-init';
|
||||
export { useCurrentNodeId } from './use-node-id';
|
||||
export { useDataSetArea, Setting } from '../component/data-set/data-set-area';
|
||||
|
||||
export { useMonetizeConfigReadonly } from './use-monetize-config-readonly';
|
||||
|
||||
export { useDatasetAutoChangeConfirm } from './use-dataset-auto-change-confirm';
|
||||
export { useUnbindPlatformModal } from './use-unbind-platform';
|
||||
export { useSpaceRole } from './use-space-role';
|
||||
export {
|
||||
useGenerateLink,
|
||||
useGetUserQueryCollectOption,
|
||||
} from './use-query-collect';
|
||||
|
||||
export type TBotRouteConfig = (
|
||||
| {
|
||||
requireBotEditorInit: true;
|
||||
pageName: 'bot' | 'analysis' | 'evaluation';
|
||||
hasHeader?: true;
|
||||
}
|
||||
| {
|
||||
requireBotEditorInit: false;
|
||||
pageName: 'publish';
|
||||
hasHeader?: true;
|
||||
}
|
||||
| {
|
||||
requireBotEditorInit: true;
|
||||
pageName: 'publish-detail' | 'publish-gray' | 'publish-ppe';
|
||||
hasHeader?: false;
|
||||
}
|
||||
) &
|
||||
TRouteConfigGlobal;
|
||||
|
||||
export const useBotRouteConfig = useRouteConfig<TBotRouteConfig>;
|
||||
|
||||
export const useMessageReportEvent = () => {
|
||||
const params = useParams<DynamicParams>();
|
||||
useEffect(() => {
|
||||
if (params.bot_id) {
|
||||
messageReportEvent.start(params.bot_id);
|
||||
}
|
||||
return () => {
|
||||
messageReportEvent.interrupt();
|
||||
};
|
||||
}, [params.bot_id]);
|
||||
};
|
||||
|
||||
/**
|
||||
* 用于校验当前模块默认展开收起状态
|
||||
*
|
||||
* @deprecated 废弃,请使用@coze-agent-ide/tool中的useToolContentBlockDefaultExpand
|
||||
* @param blockKey 主键
|
||||
* @param configured 是否有配置内容
|
||||
* @param when 是否校验
|
||||
*
|
||||
* @see
|
||||
*/
|
||||
export const useDefaultExPandCheck = (
|
||||
$params: {
|
||||
blockKey: SkillKeyEnum;
|
||||
configured: boolean;
|
||||
},
|
||||
$when = true,
|
||||
) => {
|
||||
const { blockKey, configured = false } = $params;
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
const { init, editable, botSkillBlockCollapsibleState } = usePageRuntimeStore(
|
||||
useShallow(store => ({
|
||||
init: store.init,
|
||||
editable: store.editable,
|
||||
botSkillBlockCollapsibleState: store.botSkillBlockCollapsibleState,
|
||||
})),
|
||||
);
|
||||
return useMemo(() => {
|
||||
// 不做校验
|
||||
if (!$when) {
|
||||
return undefined;
|
||||
// 状态机未就绪
|
||||
} else if (!init || size(botSkillBlockCollapsibleState) === 0) {
|
||||
return undefined;
|
||||
/**
|
||||
* @description 仅在满足以下条件时用户行为记录才能生效
|
||||
*
|
||||
* 1. 拥有编辑权限
|
||||
* 2. 不能是历史预览环境
|
||||
* 3. 必须已配置
|
||||
*/
|
||||
} else if (editable && !isReadonly && configured) {
|
||||
const transformerBlockKey = skillKeyToApiStatusKeyTransformer(blockKey);
|
||||
const collapsibleState =
|
||||
botSkillBlockCollapsibleState[
|
||||
transformerBlockKey as keyof TabDisplayItems
|
||||
];
|
||||
if (collapsibleState === TabStatus.Open) {
|
||||
return true;
|
||||
} else if (collapsibleState === TabStatus.Close) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return configured;
|
||||
}, [
|
||||
$when,
|
||||
blockKey,
|
||||
configured,
|
||||
init,
|
||||
isReadonly,
|
||||
editable,
|
||||
botSkillBlockCollapsibleState,
|
||||
]);
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 copy from 'copy-to-clipboard';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { reporter } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import { connectorApi } from '@coze-arch/bot-api';
|
||||
import { useCurrentEnterpriseInfo } from '@coze-foundation/enterprise-store-adapter';
|
||||
|
||||
export interface IActionTarget {
|
||||
target: 'oauth' | 'platform';
|
||||
action: 'create' | 'update' | 'delete' | 'view';
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const useCustomPlatformController = () => {
|
||||
const { organization_id: organizationId } = useCurrentEnterpriseInfo() ?? {};
|
||||
const {
|
||||
data: dataSource,
|
||||
loading,
|
||||
run: doRefreshDatasource,
|
||||
} = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
const res = await connectorApi.ListConnector({
|
||||
account_id: organizationId,
|
||||
page_size: 100,
|
||||
});
|
||||
|
||||
return res?.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.GetCustomPlatList,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: !0,
|
||||
refreshOnWindowFocus: !0,
|
||||
},
|
||||
);
|
||||
|
||||
const [actionTarget, setActionTarget] = useState<IActionTarget | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const doCopy = (id: string) => {
|
||||
try {
|
||||
const res = copy(id);
|
||||
|
||||
if (!res) {
|
||||
throw new Error(I18n.t('copy_failed'));
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('copy_success'),
|
||||
showClose: false,
|
||||
});
|
||||
} catch (error) {
|
||||
Toast.warning({
|
||||
content: error.message,
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
dataSource,
|
||||
actionTarget,
|
||||
doCopy,
|
||||
doSetActionTarget: setActionTarget,
|
||||
doRefreshDatasource,
|
||||
};
|
||||
};
|
||||
|
||||
export { useCustomPlatformController };
|
||||
@@ -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 { useEffect, useMemo } from 'react';
|
||||
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useMemoizedFn, useRequest } from 'ahooks';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { reporter } from '@coze-arch/logger';
|
||||
import { AppType } from '@coze-arch/idl/pat_permission_api';
|
||||
import { SpaceRoleType } from '@coze-arch/idl/developer_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import {
|
||||
type UpdateConnectorRequest,
|
||||
type CreateConnectorRequest,
|
||||
type DeleteConnectorRequest,
|
||||
} from '@coze-arch/bot-api/connector_api';
|
||||
import {
|
||||
PlaygroundApi,
|
||||
connectorApi,
|
||||
patPermissionApi,
|
||||
} from '@coze-arch/bot-api';
|
||||
import {
|
||||
useIsCurrentPersonalEnterprise,
|
||||
useCurrentEnterpriseInfo,
|
||||
} from '@coze-foundation/enterprise-store-adapter';
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
const useCustomPlatformSettingModalController = (
|
||||
successCb: (
|
||||
token?: string,
|
||||
params?:
|
||||
| CreateConnectorRequest
|
||||
| UpdateConnectorRequest
|
||||
| DeleteConnectorRequest,
|
||||
) => void,
|
||||
failCb?: () => void,
|
||||
) => {
|
||||
const { organization_id: organizationId } = useCurrentEnterpriseInfo() ?? {};
|
||||
const isPersonalEnterprise = useIsCurrentPersonalEnterprise();
|
||||
|
||||
const {
|
||||
loading: isLoadingSpace,
|
||||
run: doAsyncGetSpaceList,
|
||||
data: spaceDatasource,
|
||||
} = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
const res = await PlaygroundApi.GetSpaceListV2();
|
||||
|
||||
return res?.data?.bot_space_list;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.GetSpaceListFromCustomPlat,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
const spaceOptionList = useMemo(
|
||||
() =>
|
||||
spaceDatasource?.map(spaceItem => ({
|
||||
label: spaceItem.name,
|
||||
value: spaceItem.id,
|
||||
disabled:
|
||||
spaceItem?.role_type !== SpaceRoleType.Owner &&
|
||||
spaceItem?.role_type !== SpaceRoleType.Admin,
|
||||
})),
|
||||
[spaceDatasource],
|
||||
);
|
||||
|
||||
const {
|
||||
data: oauthDatasource,
|
||||
loading: isLoadingOauthDatasource,
|
||||
run: doAsyncGetOauthData,
|
||||
} = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
const res = await patPermissionApi.ListAppMeta(
|
||||
isPersonalEnterprise
|
||||
? {}
|
||||
: {
|
||||
organization_id: organizationId,
|
||||
},
|
||||
);
|
||||
|
||||
return res?.data?.apps ?? [];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.GetOauthAppListFromCustomPlat,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [isPersonalEnterprise, organizationId],
|
||||
},
|
||||
);
|
||||
|
||||
const oauthOptionsList = useMemo(
|
||||
() =>
|
||||
oauthDatasource?.map(oauthItem => ({
|
||||
label: `${oauthItem.name}(ID: ${oauthItem.appid})`,
|
||||
value: oauthItem.appid,
|
||||
disabled:
|
||||
!!oauthItem?.connector?.connector_id ||
|
||||
oauthItem?.app_type === AppType.normal,
|
||||
})),
|
||||
[oauthDatasource],
|
||||
);
|
||||
|
||||
const { loading: isCreating, runAsync: doAsyncCreate } = useRequest(
|
||||
async (values: CreateConnectorRequest) =>
|
||||
await connectorApi.CreateConnector({
|
||||
...values,
|
||||
account_id: organizationId,
|
||||
}),
|
||||
{ manual: !0 },
|
||||
);
|
||||
|
||||
const { loading: isUpdating, runAsync: doAsyncUpdate } = useRequest(
|
||||
async (values: UpdateConnectorRequest) =>
|
||||
await connectorApi.UpdateConnector(values),
|
||||
{ manual: !0 },
|
||||
);
|
||||
|
||||
const { loading: isDeleting, runAsync: doAsyncDelete } = useRequest(
|
||||
async (values: DeleteConnectorRequest) => {
|
||||
await connectorApi.DeleteConnector(values);
|
||||
},
|
||||
{ manual: !0 },
|
||||
);
|
||||
|
||||
const doCreate = useMemoizedFn(async (values: CreateConnectorRequest) => {
|
||||
try {
|
||||
const res = await doAsyncCreate(values);
|
||||
|
||||
successCb(res?.callback_token, values);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.CreateCustomPlat,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
failCb?.();
|
||||
}
|
||||
});
|
||||
|
||||
const doUpdate = useMemoizedFn(async (values: UpdateConnectorRequest) => {
|
||||
try {
|
||||
const res = await doAsyncUpdate(values);
|
||||
|
||||
successCb(res?.callback_token, values);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.UpdateCustomPlat,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
failCb?.();
|
||||
}
|
||||
});
|
||||
|
||||
const doDel = useMemoizedFn(async (values: DeleteConnectorRequest) => {
|
||||
try {
|
||||
await doAsyncDelete(values);
|
||||
|
||||
successCb(undefined, values);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.DeleteCustomPlat,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
failCb?.();
|
||||
}
|
||||
});
|
||||
|
||||
const doCopy = (id: string) => {
|
||||
try {
|
||||
const res = copy(id);
|
||||
|
||||
if (!res) {
|
||||
throw new Error(I18n.t('copy_failed'));
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('copy_success'),
|
||||
showClose: false,
|
||||
});
|
||||
} catch (error) {
|
||||
Toast.warning({
|
||||
content: error.message,
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
doAsyncGetSpaceList();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
oauthOptionsList,
|
||||
spaceOptionList,
|
||||
isLoadingSpace,
|
||||
isLoadingOauthDatasource,
|
||||
isIdle: !isCreating && !isDeleting && !isUpdating,
|
||||
doDel,
|
||||
doUpdate,
|
||||
doCreate,
|
||||
doAsyncGetOauthData,
|
||||
doAsyncGetSpaceList,
|
||||
doCopy,
|
||||
};
|
||||
};
|
||||
|
||||
export { useCustomPlatformSettingModalController };
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import {
|
||||
createReportEvent,
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
} from '@coze-arch/report-events';
|
||||
|
||||
const useNormalPlatformController = () => {
|
||||
const userAuthInfos = userStoreService.useUserAuthInfo();
|
||||
const { getUserAuthInfos } = userStoreService;
|
||||
|
||||
const revokeSuccess = async () => {
|
||||
const getUserAuthListEvent = createReportEvent({
|
||||
eventName: ReportEventNames.getUserAuthList,
|
||||
});
|
||||
try {
|
||||
await getUserAuthInfos();
|
||||
getUserAuthListEvent.success();
|
||||
} catch (error) {
|
||||
getUserAuthListEvent.error({ error, reason: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
revokeSuccess,
|
||||
userAuthInfos,
|
||||
};
|
||||
};
|
||||
|
||||
export { useNormalPlatformController };
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 { useMemoizedFn, useRequest } from 'ahooks';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { reporter } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type UpdateOauthConfigRequest } from '@coze-arch/bot-api/connector_api';
|
||||
import { connectorApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type IActionTarget } from './use-custom-platform-controller';
|
||||
|
||||
const useOauthSettingModalController = (
|
||||
actionTarget: IActionTarget,
|
||||
successCb: () => void,
|
||||
failCb?: () => void,
|
||||
) => {
|
||||
const { data: oauthFormConfig, loading: isOauthConfigLoading } = useRequest(
|
||||
async () => {
|
||||
try {
|
||||
const configRes = await connectorApi.GetOauthConfigSchema();
|
||||
|
||||
return configRes?.oauth_schema;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.GetOauthConfig,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
ready:
|
||||
actionTarget?.target === 'oauth' && actionTarget?.action === 'update',
|
||||
},
|
||||
);
|
||||
|
||||
const oauthFormItemConfigs = oauthFormConfig?.schema_area?.schema_list ?? [];
|
||||
|
||||
const oauthModalDesc = oauthFormConfig?.schema_area?.description
|
||||
? oauthFormConfig?.schema_area?.description
|
||||
: I18n.t('coze_custom_publish_platform_43');
|
||||
|
||||
const oauthModalTitle = oauthFormConfig?.schema_area?.title_text
|
||||
? oauthFormConfig?.schema_area?.title_text
|
||||
: I18n.t('coze_custom_publish_platform_42');
|
||||
|
||||
const { runAsync, loading: isUpdateOauthConfigLoading } = useRequest(
|
||||
async (values: UpdateOauthConfigRequest) => {
|
||||
await connectorApi.UpdateOauthConfig(values);
|
||||
},
|
||||
{ manual: !0 },
|
||||
);
|
||||
|
||||
const doUpdate = useMemoizedFn(async (values: UpdateOauthConfigRequest) => {
|
||||
try {
|
||||
await runAsync(values);
|
||||
|
||||
successCb();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reporter.errorEvent({
|
||||
eventName: REPORT_EVENTS.UpdateCustomPlatOauthConfig,
|
||||
error,
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
failCb?.();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
isOauthConfigLoading,
|
||||
isUpdateOauthConfigLoading,
|
||||
oauthModalTitle,
|
||||
oauthModalDesc,
|
||||
oauthFormItemConfigs,
|
||||
doUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
export { useOauthSettingModalController };
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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 { useCallback, useEffect } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { type WorkFlowItemType } from '@coze-studio/bot-detail-store';
|
||||
import { reporter } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { OpenBlockEvent, emitEvent } from '@coze-arch/bot-utils';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UIModal, UIToast } from '@coze-arch/bot-semi';
|
||||
import { type SceneResponseType } from '@coze-arch/bot-hooks/src/page-jump';
|
||||
import {
|
||||
usePageJumpResponse,
|
||||
PageType,
|
||||
SceneType,
|
||||
OpenModeType,
|
||||
} from '@coze-arch/bot-hooks';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { WorkflowMode } from '@coze-arch/bot-api/workflow_api';
|
||||
import { PluginType } from '@coze-arch/bot-api/developer_api';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
/**
|
||||
* workflow 发布成功后跳转回 bot 编辑页,弹窗提示是否添加到 bot
|
||||
*/
|
||||
export const useWorkflowPublishedModel = ({
|
||||
flowMode,
|
||||
addedWorkflows,
|
||||
onOk,
|
||||
skipByExternal,
|
||||
title = I18n.t('PublishSuccessConfirm'),
|
||||
pageType = PageType.BOT,
|
||||
}: {
|
||||
flowMode?: WorkflowMode;
|
||||
/** 已添加的 workflow(若已添加该 workflow 则不弹窗)。为了兼容 single 和 multi 模式,所以由外部传入 */
|
||||
addedWorkflows: WorkFlowItemType[];
|
||||
/** 点击确认并查询 workflow 对应的 plugin 成功后的回调,也是为了兼容 single 和 multi 两种模式才由外部传入 */
|
||||
onOk: (workflow: WorkFlowItemType) => unknown;
|
||||
/** 允许业务方额外附带禁止弹窗的条件。主要用于 multi 模式 */
|
||||
skipByExternal?: (
|
||||
jumpResponse: SceneResponseType<
|
||||
| SceneType.WORKFLOW_PUBLISHED__BACK__BOT
|
||||
| SceneType.WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT
|
||||
| SceneType.WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE
|
||||
>,
|
||||
) => boolean;
|
||||
title?: string;
|
||||
pageType?: PageType;
|
||||
}): void => {
|
||||
const isImageflow = flowMode === WorkflowMode.Imageflow;
|
||||
const jumpResponse = usePageJumpResponse(pageType);
|
||||
|
||||
// 使用 useCallback 缓存防抖函数,避免 UIModal 弹窗出现多次
|
||||
const debouncedEffect = useCallback(
|
||||
debounce(() => {
|
||||
const isNotWorkflowPublishedBackBot =
|
||||
jumpResponse?.scene !== SceneType.WORKFLOW_PUBLISHED__BACK__BOT;
|
||||
const isNotWorkflowPublishedBackSocialScene =
|
||||
jumpResponse?.scene !==
|
||||
SceneType.WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE;
|
||||
const isOnlyOnceAdd =
|
||||
(
|
||||
jumpResponse as SceneResponseType<SceneType.WORKFLOW_PUBLISHED__BACK__BOT>
|
||||
)?.workflowOpenMode === OpenModeType.OnlyOnceAdd;
|
||||
|
||||
const isNotWorkflowPublishedBackDouyinBot =
|
||||
jumpResponse?.scene !== SceneType.WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT;
|
||||
|
||||
if (
|
||||
(isNotWorkflowPublishedBackBot &&
|
||||
isNotWorkflowPublishedBackDouyinBot &&
|
||||
isNotWorkflowPublishedBackSocialScene) ||
|
||||
isOnlyOnceAdd
|
||||
) {
|
||||
// 不是发布跳转的场景,或者是仅添加一次,直接不弹窗
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置了 flowMode 才判断,否则不进行 flowMode 限制(适配 ChatFlow)
|
||||
if (
|
||||
typeof flowMode !== 'undefined' &&
|
||||
(jumpResponse?.flowMode || WorkflowMode.Workflow) !== flowMode
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skipByExternal?.(jumpResponse)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
addedWorkflows.some(
|
||||
workflow => workflow.workflow_id === jumpResponse.workflowID,
|
||||
)
|
||||
) {
|
||||
// 已经添加过该 workflow 了,不弹窗
|
||||
return;
|
||||
}
|
||||
|
||||
const { workflowID, pluginID } = jumpResponse;
|
||||
UIModal.success({
|
||||
title,
|
||||
cancelText: I18n.t('Cancel'),
|
||||
okText: I18n.t('Confirm'),
|
||||
onCancel: () => jumpResponse.clearScene(true),
|
||||
onOk: async () => {
|
||||
try {
|
||||
const plugin = (
|
||||
await PluginDevelopApi.GetPlaygroundPluginList({
|
||||
space_id: useSpaceStore.getState().getSpaceId(),
|
||||
page: 1,
|
||||
size: 1,
|
||||
plugin_ids: [pluginID],
|
||||
plugin_types: [
|
||||
isImageflow ? PluginType.IMAGEFLOW : PluginType.WORKFLOW,
|
||||
],
|
||||
})
|
||||
).data?.plugin_list?.[0];
|
||||
if (!plugin) {
|
||||
const msg = I18n.t('AddFailedToast');
|
||||
UIToast.error({
|
||||
content: withSlardarIdButton(msg),
|
||||
});
|
||||
throw new CustomError('normal_error', msg);
|
||||
}
|
||||
const workflow: WorkFlowItemType = {
|
||||
workflow_id: workflowID,
|
||||
plugin_id: plugin.id || '',
|
||||
name: plugin.name || '',
|
||||
desc: plugin.desc_for_human || '',
|
||||
parameters: plugin.plugin_apis?.at(0)?.parameters ?? [],
|
||||
plugin_icon: plugin.plugin_icon || '',
|
||||
flow_mode:
|
||||
plugin.plugin_type === PluginType.IMAGEFLOW
|
||||
? WorkflowMode.Imageflow
|
||||
: (jumpResponse?.flowMode ?? WorkflowMode.Workflow),
|
||||
};
|
||||
const onOkResult = onOk(workflow);
|
||||
const res = await Promise.resolve(onOkResult);
|
||||
if (res !== false) {
|
||||
UIToast.success(
|
||||
I18n.t('AddSuccessToast', { name: plugin.name || workflowID }),
|
||||
);
|
||||
emitEvent(
|
||||
isImageflow
|
||||
? OpenBlockEvent.IMAGEFLOW_BLOCK_OPEN
|
||||
: OpenBlockEvent.WORKFLOW_BLOCK_OPEN,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
reporter.error({
|
||||
message: e instanceof Error ? e.message : e?.toString(),
|
||||
error: e,
|
||||
});
|
||||
} finally {
|
||||
jumpResponse.clearScene(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
}, 1000),
|
||||
[], // 空依赖数组
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedEffect();
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
debouncedEffect.cancel();
|
||||
};
|
||||
}, [debouncedEffect]);
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import {
|
||||
createReportEvent,
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
} from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { Spin, useUIModal } from '@coze-arch/bot-semi';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { Branch } from '@coze-arch/bot-api/dp_manage_api';
|
||||
import { type PublishConnectorInfo } from '@coze-arch/bot-api/developer_api';
|
||||
import { dpManageApi } from '@coze-arch/bot-api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import styles from '../pages/publish/index.module.less';
|
||||
import { NewBotDiffView } from '../component/bot-diff-view/new-diff-view';
|
||||
import { BotDiffView } from '../component/bot-diff-view';
|
||||
const getBotPublishDiffReportEvent = createReportEvent({
|
||||
eventName: ReportEventNames.getBotDiffError,
|
||||
});
|
||||
|
||||
export const useConnectorDiffModal = () => {
|
||||
const [selectedRecord, setSelectedRecord] = useState<PublishConnectorInfo>();
|
||||
const params = useParams<DynamicParams>();
|
||||
const [Flags] = useFlags();
|
||||
const isUseNewTemplate = !!Flags?.['bot.devops.merge_prompt_diff'];
|
||||
const {
|
||||
data: botDiffData,
|
||||
loading,
|
||||
run,
|
||||
mutate,
|
||||
error: requestError,
|
||||
} = useRequest(
|
||||
async (record: PublishConnectorInfo) => {
|
||||
const { bot_id = '', space_id = '', commit_version } = params;
|
||||
const result = await dpManageApi.BotDiff({
|
||||
space_id,
|
||||
bot_id,
|
||||
left: {
|
||||
branch: Branch.Publish,
|
||||
connector_id: record.id,
|
||||
},
|
||||
template_key: isUseNewTemplate ? 'diff_template_when_publish_v2' : '',
|
||||
right: { branch: Branch.Base, version_id: commit_version },
|
||||
});
|
||||
return result.data;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onBefore: () => {
|
||||
getBotPublishDiffReportEvent.start();
|
||||
},
|
||||
onSuccess: data => {
|
||||
getBotPublishDiffReportEvent.success();
|
||||
},
|
||||
onError: error => {
|
||||
getBotPublishDiffReportEvent.error({
|
||||
reason: 'get publish diff error',
|
||||
error,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
const closeModal = () => {
|
||||
mutate({ origin_bot_dl: '', diff_display_node: [] });
|
||||
close();
|
||||
};
|
||||
const openModal = (record: PublishConnectorInfo) => {
|
||||
setSelectedRecord(record);
|
||||
run(record);
|
||||
open();
|
||||
};
|
||||
const { modal, open, close } = useUIModal({
|
||||
type: 'info',
|
||||
okText: I18n.t('devops_publish_multibranch_done'),
|
||||
okType: 'tertiary',
|
||||
okButtonProps: { className: 'semi-button-light' },
|
||||
hasCancel: false,
|
||||
|
||||
className: styles['diff-modal'],
|
||||
onCancel: closeModal,
|
||||
onOk: closeModal,
|
||||
title: I18n.t('devops_publish_multibranch_diffwithin', {
|
||||
connectorName: selectedRecord?.name ?? '',
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
node: modal(
|
||||
<div className={styles['diff-modal-container']}>
|
||||
{loading ? (
|
||||
<Spin />
|
||||
) : isUseNewTemplate ? (
|
||||
<NewBotDiffView
|
||||
diffData={botDiffData?.diff_display_node || []}
|
||||
hasError={requestError !== undefined}
|
||||
type={'publish'}
|
||||
/>
|
||||
) : (
|
||||
<BotDiffView
|
||||
diffData={botDiffData?.diff_display_node || []}
|
||||
hasError={requestError !== undefined}
|
||||
/>
|
||||
)}
|
||||
</div>,
|
||||
),
|
||||
open: openModal,
|
||||
close,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { type ComponentProps, Suspense, forwardRef, lazy } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type BotSpace,
|
||||
SpaceType,
|
||||
type DraftBot,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { type UploadValue } from '@coze-common/biz-components/picture-upload';
|
||||
import { IconTeamDefault } from '@coze-arch/bot-icons';
|
||||
import { botInputLengthService } from '@coze-agent-ide/bot-input-length-limit';
|
||||
import {
|
||||
FormTextArea,
|
||||
FormInput,
|
||||
Tag,
|
||||
Form,
|
||||
FormSelect,
|
||||
Avatar,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { FormSwitch } from './form-switch';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const LazyReactMarkdown = lazy(() => import('react-markdown'));
|
||||
|
||||
const ReactMarkdown = (props: ComponentProps<typeof LazyReactMarkdown>) => (
|
||||
<Suspense fallback={null}>
|
||||
<LazyReactMarkdown {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export type AgentInfoFormValue = Partial<{
|
||||
bot_uri: UploadValue;
|
||||
name: string;
|
||||
target: string;
|
||||
spaceId?: string;
|
||||
enableMonetize?: boolean;
|
||||
}>;
|
||||
|
||||
export interface AgentInfoFormProps {
|
||||
className?: string;
|
||||
mode: 'add' | 'update';
|
||||
showSpace: boolean;
|
||||
initialValues: Partial<DraftBot>;
|
||||
spacesList: BotSpace[];
|
||||
currentSpaceId?: string; // Current space ID from store
|
||||
hideOperation?: boolean; // hide_operation from store
|
||||
checkErr: boolean;
|
||||
errMsg: string;
|
||||
onValuesChange: (values: AgentInfoFormValue) => void;
|
||||
slot?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AgentInfoForm = forwardRef<
|
||||
Form<AgentInfoFormValue>,
|
||||
AgentInfoFormProps
|
||||
>(
|
||||
// eslint-disable-next-line complexity
|
||||
(
|
||||
{
|
||||
className,
|
||||
mode,
|
||||
showSpace,
|
||||
initialValues,
|
||||
spacesList,
|
||||
currentSpaceId,
|
||||
hideOperation,
|
||||
checkErr,
|
||||
errMsg,
|
||||
onValuesChange,
|
||||
slot,
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
<Form<AgentInfoFormValue>
|
||||
ref={ref}
|
||||
showValidateIcon={false}
|
||||
className={classNames(s['upload-form'], className)} // Ensure class name is correct
|
||||
onValueChange={values => {
|
||||
onValuesChange(values);
|
||||
}}
|
||||
>
|
||||
<FormInput
|
||||
initValue={botInputLengthService.sliceStringByMaxLength({
|
||||
value: initialValues?.name ?? '',
|
||||
field: 'botName',
|
||||
})}
|
||||
field="name"
|
||||
label={I18n.t('bot_create_name')}
|
||||
noErrorMessage
|
||||
maxLength={botInputLengthService.getInputLengthLimit('botName')}
|
||||
rules={[{ required: true }]}
|
||||
placeholder={I18n.t('bot_create_name_placeholder')}
|
||||
getValueLength={reactText =>
|
||||
botInputLengthService.getValueLength(reactText)
|
||||
}
|
||||
/>
|
||||
{IS_OVERSEA && mode === 'add' ? (
|
||||
<FormSwitch
|
||||
field="enableMonetize"
|
||||
label={I18n.t('monetization')}
|
||||
desc={I18n.t('monetization_des')}
|
||||
initValue={true} // Consider if initial value should be prop
|
||||
rules={[{ required: true }]}
|
||||
/>
|
||||
) : null}
|
||||
<FormTextArea
|
||||
field="target"
|
||||
initValue={botInputLengthService.sliceStringByMaxLength({
|
||||
value: initialValues?.description ?? '',
|
||||
field: 'botDescription',
|
||||
})}
|
||||
label={I18n.t('bot_create_desciption')}
|
||||
placeholder={I18n.t('bot_create_description_placeholder')}
|
||||
maxCount={botInputLengthService.getInputLengthLimit('botDescription')}
|
||||
maxLength={botInputLengthService.getInputLengthLimit('botDescription')}
|
||||
getValueLength={botInputLengthService.getValueLength}
|
||||
/>
|
||||
{showSpace && mode === 'add' ? (
|
||||
<FormSelect
|
||||
label={I18n.t('duplicate_select_workspace')}
|
||||
field="spaceId"
|
||||
initValue={
|
||||
hideOperation
|
||||
? spacesList?.[0]?.id
|
||||
: (currentSpaceId ?? spacesList?.[0]?.id)
|
||||
}
|
||||
placeholder={I18n.t('select_team')}
|
||||
noErrorMessage
|
||||
className={classNames(s.select)}
|
||||
rules={[{ required: true }]}
|
||||
renderSelectedItem={(optionNode: BotSpace) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
src={optionNode.icon_url}
|
||||
size="extra-extra-small"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{optionNode.name}
|
||||
</Avatar>
|
||||
<span className={classNames(s['select-name'])}>
|
||||
{optionNode.name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{spacesList
|
||||
?.filter(t => !t.hide_operation)
|
||||
?.map(item => (
|
||||
<FormSelect.Option value={item.id} {...item} key={item.id}>
|
||||
<div className="ml-[8px]">
|
||||
{item.icon_url ? (
|
||||
<Avatar size="extra-small" src={item.icon_url} />
|
||||
) : (
|
||||
<IconTeamDefault
|
||||
className={classNames(s['select-item-icon'])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classNames(s['select-item-name'])}>
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: false,
|
||||
}}
|
||||
style={{
|
||||
maxWidth: '280px',
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</div>
|
||||
{item.space_type === SpaceType.Team && (
|
||||
<Tag color="brand">{I18n.t('develop_team_team')}</Tag>
|
||||
)}
|
||||
</FormSelect.Option>
|
||||
))}
|
||||
</FormSelect>
|
||||
) : null}
|
||||
{slot}
|
||||
{checkErr ? (
|
||||
<div className={s['content-check-error']}>
|
||||
<ReactMarkdown
|
||||
skipHtml={true}
|
||||
className={s.markdown}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{errMsg ?? I18n.t('publish_audit_pop7')}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
) : null}
|
||||
</Form>
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 cls from 'classnames';
|
||||
import { Switch, withField, type SwitchProps } from '@coze-arch/coze-design';
|
||||
|
||||
function SwitchWithDesc({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
desc,
|
||||
descClassName,
|
||||
switchClassName,
|
||||
...rest
|
||||
}: Omit<SwitchProps, 'checked'> & {
|
||||
value?: boolean;
|
||||
desc: string;
|
||||
descClassName?: string;
|
||||
switchClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cls('flex items-center justify-between', className)}>
|
||||
<span className={cls('coz-fg-primary', descClassName)}>{desc}</span>
|
||||
<Switch
|
||||
size="small"
|
||||
{...rest}
|
||||
checked={value}
|
||||
onChange={onChange}
|
||||
className={cls('shrink-0', switchClassName)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FormSwitch = withField(SwitchWithDesc);
|
||||
@@ -0,0 +1,408 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
min-width: 323px;
|
||||
height: 180px;
|
||||
padding: 12px 16px 16px 20px;
|
||||
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 8px 0 rgb(28 31 35 / 6%);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 10px 12px 0 rgb(28 31 35 / 10%);
|
||||
}
|
||||
|
||||
// 更多操作按钮active时,不触发父级active
|
||||
&:active:not(:focus-within) {
|
||||
background-color: #e6e7ea;
|
||||
}
|
||||
|
||||
.bot-info {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: auto;
|
||||
|
||||
.bot-avatar {
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
margin-top: 12px;
|
||||
margin-left: 4px;
|
||||
|
||||
background-color: #2E2E3814;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bot-text {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
margin-left: 16px;
|
||||
|
||||
.bot-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.bot-name {
|
||||
margin-top: 12px;
|
||||
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #1c1d23;
|
||||
}
|
||||
}
|
||||
|
||||
.bot-description {
|
||||
height: 40px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: rgb(28 29 35 / 80%);
|
||||
}
|
||||
|
||||
.bot-hot-and-category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bot-hot-value {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #1C1D2359;
|
||||
}
|
||||
|
||||
.bot-category-tag {
|
||||
margin-left: 16px;
|
||||
border: 1px solid #1D1C2314;
|
||||
border-radius: 6px;
|
||||
|
||||
.bot-category-text {
|
||||
font-size: 12px;
|
||||
color: #6B6B75;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-model-and-publish {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bot-publish-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 60%);
|
||||
letter-spacing: 0.12px;
|
||||
|
||||
&-icon {
|
||||
margin-right: 4px;
|
||||
color: rgb(62 194 84);
|
||||
}
|
||||
|
||||
&-warning-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--semi-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.bot-model {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 35%);
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: calc(100% - 4px);
|
||||
}
|
||||
|
||||
.bot-publish-platform {
|
||||
display: flex;
|
||||
column-gap: 6px;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 60%);
|
||||
letter-spacing: 0.12px;
|
||||
|
||||
:global {
|
||||
.semi-avatar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-creator-info {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 35%);
|
||||
letter-spacing: 0.12px;
|
||||
|
||||
.creator-avatar {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.bot-creator {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 35%);
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.bot-edit-time {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore-height {
|
||||
height: 136px;
|
||||
}
|
||||
|
||||
.publish-popover {
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid #efefef;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:global {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-description-tooltip {
|
||||
color: var(--semi-color-bg-0);
|
||||
}
|
||||
|
||||
.add-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.add-card-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.name-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 16px 16px 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 48px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
max-width: calc(100% - 156px);
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #000;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extra {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #494c4f;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.recent-modify {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 31 35 / 60%);
|
||||
}
|
||||
|
||||
.creator {
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #346ef8;
|
||||
|
||||
background: rgb(51 112 255 / 10%);
|
||||
border: none !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
.upload-filed {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.textarea-multi-line {
|
||||
:global {
|
||||
.semi-input-textarea {
|
||||
padding-right: 66px;
|
||||
}
|
||||
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 8px;
|
||||
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
|
||||
color: var(--light-usage-text-color-text-3, rgb(28 29 35 / 35%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-form-field-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-form-item {
|
||||
:global {
|
||||
.semi-form-field-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-form-field-label-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-form-field {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collect-num {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 4px;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.content-check-error {
|
||||
margin-top: -8px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.bot-ui-modal {
|
||||
:global {
|
||||
.semi-modal-content .semi-modal-body {
|
||||
padding: 12px 0 22px;
|
||||
}
|
||||
|
||||
.semi-form-vertical .semi-form-field {
|
||||
padding-bottom: 14px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
|
||||
.select-name {
|
||||
margin-left: 6px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.select-item-icon {
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-item-name {
|
||||
margin-right: 16px;
|
||||
margin-left: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export {
|
||||
useAgentFormManagement,
|
||||
type UseAgentFormManagementProps,
|
||||
} from './use-agent-form-management';
|
||||
export {
|
||||
useAgentPersistence,
|
||||
type UseAgentPersistenceProps,
|
||||
} from './use-agent-persistence';
|
||||
export {
|
||||
AgentInfoForm,
|
||||
type AgentInfoFormProps,
|
||||
type AgentInfoFormValue,
|
||||
} from './agent-info-form';
|
||||
@@ -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 { useState, useRef } from 'react';
|
||||
|
||||
import { type Form } from '@coze-arch/coze-design';
|
||||
import { type DraftBot } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { type AgentInfoFormValue } from './agent-info-form';
|
||||
|
||||
export interface UseAgentFormManagementProps {
|
||||
initialBotInfo?: DraftBot;
|
||||
}
|
||||
|
||||
export const useAgentFormManagement = ({
|
||||
initialBotInfo,
|
||||
}: UseAgentFormManagementProps) => {
|
||||
const formRef = useRef<Form<AgentInfoFormValue>>(null);
|
||||
const [isOkButtonDisable, setOkButtonDisable] = useState(
|
||||
!initialBotInfo?.name?.trim(),
|
||||
);
|
||||
const [botInfo4Generate, setBotInfo4Generate] = useState<{
|
||||
name: string;
|
||||
desc: string;
|
||||
avatar: { uri: string; url: string };
|
||||
}>({
|
||||
name: initialBotInfo?.name || '',
|
||||
desc: initialBotInfo?.description || '',
|
||||
avatar: {
|
||||
uri: initialBotInfo?.icon_uri || '',
|
||||
url: initialBotInfo?.icon_url || '',
|
||||
},
|
||||
});
|
||||
const [checkErr, setCheckErr] = useState(false);
|
||||
const [errMsg, setErrMsg] = useState('');
|
||||
const [confirmDisabled, setConfirmDisabled] = useState(false);
|
||||
|
||||
const resetFormState = () => {
|
||||
setOkButtonDisable(!initialBotInfo?.name?.trim());
|
||||
setBotInfo4Generate({
|
||||
name: initialBotInfo?.name || '',
|
||||
desc: initialBotInfo?.description || '',
|
||||
avatar: {
|
||||
uri: initialBotInfo?.icon_uri || '',
|
||||
url: initialBotInfo?.icon_url || '',
|
||||
},
|
||||
});
|
||||
setCheckErr(false);
|
||||
setErrMsg('');
|
||||
};
|
||||
|
||||
const handleFormValuesChange = (values: AgentInfoFormValue) => {
|
||||
setBotInfo4Generate({
|
||||
name: values.name?.trim() || '',
|
||||
desc: values.target?.trim() || '',
|
||||
avatar: {
|
||||
uri: values.bot_uri?.[0]?.uid || '',
|
||||
url: values.bot_uri?.[0]?.url || '',
|
||||
},
|
||||
});
|
||||
setCheckErr(false);
|
||||
setErrMsg('');
|
||||
setOkButtonDisable(!values.name?.trim());
|
||||
};
|
||||
|
||||
const getValues = async () => {
|
||||
const formApi = formRef.current?.formApi;
|
||||
await formApi?.validate();
|
||||
return formApi?.getValues();
|
||||
};
|
||||
|
||||
const setBotIcon = (val: { url: string; uid: string }) => {
|
||||
const formApi = formRef.current?.formApi;
|
||||
formApi?.setValue('bot_uri', [val]);
|
||||
};
|
||||
|
||||
return {
|
||||
formRef,
|
||||
isOkButtonDisable,
|
||||
botInfo4Generate,
|
||||
checkErr,
|
||||
errMsg,
|
||||
confirmDisabled,
|
||||
setCheckErr,
|
||||
setErrMsg,
|
||||
setConfirmDisabled,
|
||||
setOkButtonDisable,
|
||||
handleFormValuesChange,
|
||||
getValues,
|
||||
setBotIcon,
|
||||
resetFormState,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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 complexity */
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useCollaborationStore } from '@coze-studio/bot-detail-store/collaboration';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { updateBotRequest } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
createReportEvent,
|
||||
} from '@coze-arch/report-events';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
type BotSpace,
|
||||
SpaceType,
|
||||
type DraftBotCreateResponse,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type AgentInfoFormValue } from './agent-info-form';
|
||||
|
||||
type OnSuccessCallback = (
|
||||
botId?: string,
|
||||
spaceId?: string,
|
||||
extra?: { botName?: string; botAvatar?: string; botDesc?: string },
|
||||
) => void | Promise<void>;
|
||||
|
||||
export interface UseAgentPersistenceProps {
|
||||
mode: 'add' | 'update';
|
||||
botId?: string; // Current bot ID for update mode
|
||||
currentSpaceId?: string; // Current space ID from store
|
||||
outerSpaceId?: string; // Space ID passed via props
|
||||
getValues: () => Promise<AgentInfoFormValue | undefined>;
|
||||
onSuccess?: OnSuccessCallback;
|
||||
onError?: () => void;
|
||||
onBefore?: () => void;
|
||||
setVisible: (visible: boolean) => void;
|
||||
setCheckErr: (isError: boolean) => void;
|
||||
setErrMsg: (message: string) => void;
|
||||
bizCreateFrom?: 'navi' | 'space';
|
||||
showSpace?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function, @coze-arch/max-line-per-function
|
||||
export const useAgentPersistence = ({
|
||||
mode,
|
||||
botId,
|
||||
currentSpaceId,
|
||||
outerSpaceId,
|
||||
getValues,
|
||||
onSuccess,
|
||||
onError,
|
||||
onBefore,
|
||||
setVisible,
|
||||
setCheckErr,
|
||||
setErrMsg,
|
||||
bizCreateFrom,
|
||||
showSpace,
|
||||
}: UseAgentPersistenceProps) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const setBotInfoByImmer = useBotInfoStore(state => state.setBotInfoByImmer);
|
||||
const setCollaborationByImmer = useCollaborationStore(
|
||||
state => state.setCollaborationByImmer,
|
||||
);
|
||||
const setPageRuntimeByImmer = usePageRuntimeStore(
|
||||
state => state.setPageRuntimeByImmer,
|
||||
);
|
||||
const {
|
||||
spaces: { bot_space_list: list },
|
||||
} = useSpaceStore();
|
||||
|
||||
const reportEvent = createReportEvent({
|
||||
eventName:
|
||||
mode === 'add' ? ReportEventNames.createBot : ReportEventNames.updateBot,
|
||||
});
|
||||
|
||||
const reportTea = ({
|
||||
resp,
|
||||
values,
|
||||
personalSpaceInfo,
|
||||
paramsSpaceId,
|
||||
}: {
|
||||
resp: DraftBotCreateResponse;
|
||||
values: AgentInfoFormValue | undefined;
|
||||
personalSpaceInfo: BotSpace | undefined;
|
||||
paramsSpaceId: string;
|
||||
}) => {
|
||||
if (resp.code === 0) {
|
||||
sendTeaEvent(EVENT_NAMES.create_bot_result, {
|
||||
source: showSpace ? 'menu_bar' : 'bot_list',
|
||||
workspace_type:
|
||||
personalSpaceInfo?.id === paramsSpaceId
|
||||
? 'personal_workspace'
|
||||
: 'team_workspace',
|
||||
result: 'success',
|
||||
bot_name: values?.name || '',
|
||||
bot_desc: values?.target || '',
|
||||
});
|
||||
} else {
|
||||
sendTeaEvent(EVENT_NAMES.create_bot_result, {
|
||||
source: showSpace ? 'menu_bar' : 'bot_list',
|
||||
workspace_type:
|
||||
personalSpaceInfo?.id === paramsSpaceId
|
||||
? 'personal_workspace'
|
||||
: 'team_workspace',
|
||||
result: 'failed',
|
||||
error_code: resp.code,
|
||||
error_message: resp.msg,
|
||||
bot_name: values?.name || '',
|
||||
bot_desc: values?.target || '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateBot = async () => {
|
||||
if (!botId) {
|
||||
const msg = I18n.t('bot_copy_id_error');
|
||||
throw new CustomError(ReportEventNames.updateBot, msg);
|
||||
}
|
||||
const values = await getValues();
|
||||
logger.info({ message: 'update values', meta: { values } });
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const botBaseInfo = {
|
||||
icon_uri: values?.bot_uri?.[0].uid || '',
|
||||
name: values?.name,
|
||||
description: values?.target ? values.target : '',
|
||||
};
|
||||
const { data } = await updateBotRequest(botBaseInfo);
|
||||
|
||||
if (data.check_not_pass) {
|
||||
setCheckErr(true);
|
||||
setErrMsg(data.check_not_pass_msg);
|
||||
onError?.();
|
||||
return;
|
||||
}
|
||||
setBotInfoByImmer(store => {
|
||||
store.icon_uri = values?.bot_uri?.[0].uid;
|
||||
store.icon_url = values?.bot_uri?.[0].url;
|
||||
store.name = values?.name;
|
||||
store.description = values?.target;
|
||||
});
|
||||
setCollaborationByImmer(store => {
|
||||
store.sameWithOnline = data.same_with_online ?? false;
|
||||
});
|
||||
setPageRuntimeByImmer(store => {
|
||||
store.hasUnpublishChange = data.has_change ?? false;
|
||||
});
|
||||
await onSuccess?.(botId, currentSpaceId, {
|
||||
botAvatar: values?.bot_uri?.[0].url,
|
||||
botName: values?.name,
|
||||
botDesc: values?.target,
|
||||
});
|
||||
|
||||
setVisible(false);
|
||||
|
||||
reportEvent.success();
|
||||
Toast.success({
|
||||
content: I18n.t('Update_success'),
|
||||
showClose: false,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
reportEvent.error({ error: e, reason: e.message });
|
||||
}
|
||||
onError?.();
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('Update_failed')),
|
||||
showClose: false,
|
||||
});
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateBot = async () => {
|
||||
const values = await getValues();
|
||||
setLoading(true);
|
||||
const paramsSpaceId =
|
||||
values?.spaceId || outerSpaceId || currentSpaceId || list?.[0]?.id || '';
|
||||
const personalSpaceInfo = list?.find(
|
||||
item => item.space_type === SpaceType.Personal,
|
||||
);
|
||||
try {
|
||||
onBefore?.();
|
||||
const resp = await DeveloperApi.DraftBotCreate({
|
||||
name: values?.name,
|
||||
description: values?.target,
|
||||
icon_uri: values?.bot_uri?.[0]?.uid,
|
||||
space_id: paramsSpaceId,
|
||||
...(IS_OVERSEA && {
|
||||
monetization_conf: { is_enable: values?.enableMonetize },
|
||||
}),
|
||||
create_from: bizCreateFrom,
|
||||
});
|
||||
if (resp.data.check_not_pass) {
|
||||
setCheckErr(true);
|
||||
setErrMsg(resp.data.check_not_pass_msg);
|
||||
onError?.();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('bot_created_toast'),
|
||||
showClose: false,
|
||||
});
|
||||
// 兼容 onSuccess 回调为同步函数的场景
|
||||
await onSuccess?.(resp.data?.bot_id, paramsSpaceId, {
|
||||
botName: values?.name,
|
||||
botDesc: values?.target,
|
||||
botAvatar: values?.bot_uri?.[0]?.url,
|
||||
});
|
||||
sendTeaEvent(EVENT_NAMES.click_create_bot_confirm, {
|
||||
click: 'success',
|
||||
bot_id: resp.data?.bot_id,
|
||||
create_type: 'create',
|
||||
});
|
||||
reportTea({ resp, values, personalSpaceInfo, paramsSpaceId });
|
||||
reportEvent.success();
|
||||
setVisible(false);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('Create_failed')),
|
||||
showClose: false,
|
||||
});
|
||||
if (e instanceof Error) {
|
||||
reportEvent.error({ error: e, reason: e.message });
|
||||
sendTeaEvent(EVENT_NAMES.click_create_bot_confirm, {
|
||||
click: 'failed',
|
||||
create_type: 'create',
|
||||
error_message: e.message,
|
||||
});
|
||||
}
|
||||
onError?.();
|
||||
// 阻止弹窗关闭
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
handleCreateBot,
|
||||
handleUpdateBot,
|
||||
};
|
||||
};
|
||||
@@ -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 { I18n } from '@coze-arch/i18n';
|
||||
import { Modal } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
ModelFuncConfigStatus,
|
||||
ModelFuncConfigType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { useBotEditor } from '@coze-agent-ide/bot-editor-context-store';
|
||||
|
||||
export const useDatasetAutoChangeConfirm = () => {
|
||||
const {
|
||||
storeSet: { useModelStore },
|
||||
} = useBotEditor();
|
||||
return async (auto: boolean, modelId: string) => {
|
||||
const model = useModelStore.getState().getModelById(modelId);
|
||||
if (!model) {
|
||||
return true;
|
||||
}
|
||||
const modelName = model.name;
|
||||
const modelConfig = model.func_config;
|
||||
const status =
|
||||
modelConfig?.[
|
||||
auto
|
||||
? ModelFuncConfigType.KnowledgeAutoCall
|
||||
: ModelFuncConfigType.KnowledgeOnDemandCall
|
||||
];
|
||||
if (
|
||||
status === ModelFuncConfigStatus.NotSupport ||
|
||||
status === ModelFuncConfigStatus.PoorSupport
|
||||
) {
|
||||
const callMethod = auto
|
||||
? I18n.t('dataset_automatic_call')
|
||||
: I18n.t('dataset_on_demand_call');
|
||||
const toolName = I18n.t('Datasets');
|
||||
return new Promise(resolve => {
|
||||
const modal = Modal.confirm({
|
||||
zIndex: 1031,
|
||||
title: I18n.t('confirm_switch_to_on_demand_call', {
|
||||
call_method: callMethod,
|
||||
}),
|
||||
content: {
|
||||
[ModelFuncConfigStatus.NotSupport]: I18n.t(
|
||||
'switch_to_on_demand_call_warning_notsupported',
|
||||
{ call_method: callMethod, modelName, toolName },
|
||||
),
|
||||
[ModelFuncConfigStatus.PoorSupport]: I18n.t(
|
||||
'switch_to_on_demand_call_warning_supportpoor',
|
||||
{ callMethod, modelName, toolName },
|
||||
),
|
||||
}[status],
|
||||
onCancel: () => {
|
||||
resolve(false);
|
||||
modal.destroy();
|
||||
},
|
||||
onOk: () => {
|
||||
resolve(true);
|
||||
modal.destroy();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
return true;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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, useLocation } from 'react-router-dom';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
const useEditConfirm = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { savingInfo } = usePageRuntimeStore.getState();
|
||||
const leaveWarningInfo = I18n.t('pop_edit_save_confirm');
|
||||
|
||||
// 保存navigate和location.pathname的引用
|
||||
const navigateRef = useRef(navigate);
|
||||
const locationRef = useRef(location.pathname);
|
||||
const debouncingRef = useRef(savingInfo.debouncing);
|
||||
|
||||
function isNoNeedConfirm() {
|
||||
return !debouncingRef.current;
|
||||
}
|
||||
|
||||
function handleBeforeUnload(event) {
|
||||
if (isNoNeedConfirm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.returnValue = leaveWarningInfo;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// popstate 执行回调的时候,由于产生了闭包,会保存支持的值,所以需要在这里处理一下
|
||||
navigateRef.current = navigate;
|
||||
locationRef.current = location.pathname;
|
||||
|
||||
const unSubDebouncing = usePageRuntimeStore.subscribe(
|
||||
store => store.savingInfo.debouncing,
|
||||
debouncing => {
|
||||
debouncingRef.current = debouncing;
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unSubDebouncing();
|
||||
};
|
||||
}, [navigate, location]);
|
||||
|
||||
useEffect(() => {
|
||||
// 刷新页面 & 关闭页面情况,用 beforeunload
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
};
|
||||
}, [history]);
|
||||
};
|
||||
|
||||
export { useEditConfirm };
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, type RefObject } from 'react';
|
||||
|
||||
import { useBotEditor } from '@coze-agent-ide/bot-editor-context-store';
|
||||
|
||||
import { useBotEditorService } from '../context/bot-editor-service';
|
||||
|
||||
export const useFreeDragModalHierarchy = ({
|
||||
key,
|
||||
ref,
|
||||
}: {
|
||||
key: string;
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
const { freeGrabModalHierarchyService } = useBotEditorService();
|
||||
const {
|
||||
storeSet: { useFreeGrabModalHierarchyStore },
|
||||
} = useBotEditor();
|
||||
|
||||
const zIndex = useFreeGrabModalHierarchyStore(state =>
|
||||
freeGrabModalHierarchyService.getModalZIndex(state.getModalIndex(key)),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
freeGrabModalHierarchyService.registerModal(key);
|
||||
const target = ref.current;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const onFocus = () => {
|
||||
freeGrabModalHierarchyService.onFocus(key);
|
||||
};
|
||||
|
||||
target.addEventListener('focus', onFocus);
|
||||
|
||||
return () => {
|
||||
freeGrabModalHierarchyService.removeModal(key);
|
||||
target.removeEventListener('focus', onFocus);
|
||||
};
|
||||
}, [key]);
|
||||
|
||||
return zIndex;
|
||||
};
|
||||
267
frontend/packages/agent-ide/space-bot/src/hook/use-init.tsx
Normal file
267
frontend/packages/agent-ide/space-bot/src/hook/use-init.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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, useRef } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { parse } from 'qs';
|
||||
import { isNumber } from 'lodash-es';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import {
|
||||
autosaveManager,
|
||||
avatarBackgroundWebSocket,
|
||||
getBotDetailDtoInfo,
|
||||
getBotDetailIsReadonly,
|
||||
initGenerateImageStore,
|
||||
updateBotRequest,
|
||||
updateHeaderStatus,
|
||||
useGenerateImageStore,
|
||||
useMonetizeConfigStore,
|
||||
initBotDetailStore,
|
||||
useBotDetailStoreSet,
|
||||
} from '@coze-studio/bot-detail-store';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { useErrorHandler } from '@coze-arch/logger';
|
||||
import { BotMode } from '@coze-arch/idl/developer_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { exhaustiveCheck } from '@coze-arch/bot-utils';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
|
||||
import { Toast } from '@coze-arch/bot-semi';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
|
||||
import { useBotPageStore } from '../store/bot-page/store';
|
||||
interface TaskParams {
|
||||
from?: 'copy' | 'multi_agent_view_old_ver';
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const maxTokenAlertId = '__MODEL_MAX_TOKEN_ALERT__';
|
||||
|
||||
const checkShouldAlertMaxToken = (inputMaxToken: number | undefined) => {
|
||||
const maxTokenWarningValue = 5;
|
||||
if (!isNumber(inputMaxToken)) {
|
||||
return false;
|
||||
}
|
||||
return inputMaxToken <= maxTokenWarningValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* 当用户模型 max_token 字段 <= 5 时进行提示
|
||||
* 避免用户误操作无法正常使用模型
|
||||
*/
|
||||
const modelMaxTokenAlert = () => {
|
||||
const { useModelStore, useMultiAgentStore } = useBotDetailStoreSet.getStore();
|
||||
const botMode = useBotInfoStore.getState().mode;
|
||||
const maxTokens = useModelStore.getState().config.max_tokens;
|
||||
const alertI18nKey = 'model_max_token_alert';
|
||||
const toastOptions = {
|
||||
content: I18n.t(alertI18nKey),
|
||||
showClose: true,
|
||||
// 当 duration 设置为 0 时,toast 不会自动关闭,此时必须通过手动关闭。
|
||||
duration: 0,
|
||||
id: maxTokenAlertId,
|
||||
};
|
||||
if (botMode === BotMode.WorkflowMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (botMode === BotMode.SingleMode) {
|
||||
if (checkShouldAlertMaxToken(maxTokens)) {
|
||||
Toast.warning(toastOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (botMode === BotMode.MultiMode) {
|
||||
const agentList = useMultiAgentStore.getState().agents;
|
||||
if (
|
||||
agentList.some(agent => checkShouldAlertMaxToken(agent.model.max_tokens))
|
||||
) {
|
||||
Toast.warning(toastOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
exhaustiveCheck(botMode);
|
||||
};
|
||||
|
||||
export type InitStoreSuccessResult = undefined | { disableAutoSave?: boolean };
|
||||
export type UnmountCallbackResult = undefined | { disableSaveAll?: boolean };
|
||||
export interface AgentInitCallback {
|
||||
onBeforeInitStore: () => void;
|
||||
onInitStoreSuccess: (params: { isAbort: boolean }) => InitStoreSuccessResult;
|
||||
onUnmount: (params: { isInitializing: boolean }) => UnmountCallbackResult;
|
||||
}
|
||||
export interface AgentInitProps {
|
||||
initCallback?: Partial<AgentInitCallback>;
|
||||
}
|
||||
|
||||
const startAutosaveManagerConditionally = ({
|
||||
init,
|
||||
editable,
|
||||
callbackRes,
|
||||
}: {
|
||||
init: boolean;
|
||||
editable: boolean;
|
||||
callbackRes: InitStoreSuccessResult;
|
||||
}) => {
|
||||
if (!(init && editable)) {
|
||||
return;
|
||||
}
|
||||
const disableAutoSave = callbackRes?.disableAutoSave;
|
||||
if (disableAutoSave) {
|
||||
return;
|
||||
}
|
||||
autosaveManager.start();
|
||||
};
|
||||
|
||||
const saveAllEdit = async ({ disable }: { disable: boolean | undefined }) => {
|
||||
const { botId, mode } = useBotInfoStore.getState();
|
||||
|
||||
// readonly如果依赖hook会更新滞后。因为页面卸载时clearStore,保留了overall的值
|
||||
// 所以这里的readonly需要执行时去获取
|
||||
// 增加是否是offline或者没有锁的的状态判断,如果是,也不进行保存。
|
||||
if (!botId || getBotDetailIsReadonly() || disable) {
|
||||
return;
|
||||
}
|
||||
const { botSkillInfo } = getBotDetailDtoInfo();
|
||||
const resp = await updateBotRequest({
|
||||
...botSkillInfo,
|
||||
bot_mode: mode,
|
||||
});
|
||||
updateHeaderStatus(resp.data);
|
||||
};
|
||||
|
||||
const useInit = (props: AgentInitProps = { initCallback: {} }) => {
|
||||
const { initCallback } = props;
|
||||
// TODO: 后续加锁操作会收敛到hooks中,不再侵入到业务
|
||||
// 初始化 store 锁,在未执行结束前,不执行页面销毁的回调
|
||||
const lock = useRef(false);
|
||||
// 取消初始化函数标志位,在先执行页面销毁的回调时,退出初始化函数执行
|
||||
const abort = useRef(false);
|
||||
const searchParams = parse(location.search.slice(1)) as TaskParams;
|
||||
const params = useParams<DynamicParams>();
|
||||
const errorHandler = useErrorHandler();
|
||||
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
|
||||
const { setBotInfo } = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
setBotInfo: state.setBotInfo,
|
||||
})),
|
||||
);
|
||||
const { setPageRuntimeBotInfo, getBotSkillBlockCollapsibleState } =
|
||||
usePageRuntimeStore(
|
||||
useShallow(state => ({
|
||||
setPageRuntimeBotInfo: state.setPageRuntimeBotInfo,
|
||||
getBotSkillBlockCollapsibleState:
|
||||
state.getBotSkillBlockCollapsibleState,
|
||||
})),
|
||||
);
|
||||
|
||||
const { setBotState } = useBotPageStore(
|
||||
useShallow(state => ({
|
||||
setBotState: state.setBotState,
|
||||
})),
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!params.bot_id) {
|
||||
navigate('/', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
setBotInfo({ botId: params.bot_id });
|
||||
setPageRuntimeBotInfo({ pageFrom: BotPageFromEnum.Bot });
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
lock.current = true;
|
||||
initCallback?.onBeforeInitStore?.();
|
||||
getBotSkillBlockCollapsibleState();
|
||||
await initBotDetailStore({ version: searchParams.version });
|
||||
// 需要判断是否为只读态,依赖store初始化,因此放在initBotDetailStore之后
|
||||
initGenerateImageStore();
|
||||
const isAbort = abort.current;
|
||||
const callbackRes = initCallback?.onInitStoreSuccess?.({ isAbort });
|
||||
// 判断是否先执行页面销毁的回调,是退出函数执行
|
||||
if (isAbort) {
|
||||
return;
|
||||
}
|
||||
//explore中的bot都不是自己的!!!
|
||||
const isSelf =
|
||||
useBotInfoStore.getState().creator_id === userInfo?.user_id_str;
|
||||
|
||||
setPageRuntimeBotInfo({ isSelf });
|
||||
const { init, editable } = usePageRuntimeStore.getState();
|
||||
startAutosaveManagerConditionally({ init, editable, callbackRes });
|
||||
if (searchParams.from === 'copy') {
|
||||
Toast.success({
|
||||
content: I18n.t('bot_copy_success'),
|
||||
showClose: false,
|
||||
});
|
||||
navigate({ search: '' });
|
||||
}
|
||||
modelMaxTokenAlert();
|
||||
lock.current = false;
|
||||
} catch (e) {
|
||||
errorHandler(
|
||||
new CustomError(
|
||||
REPORT_EVENTS.BotDetailInitHooks,
|
||||
`init hooks error: ${e.message}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
Toast.close(maxTokenAlertId);
|
||||
// 设置取消标志位
|
||||
abort.current = true;
|
||||
const unmountRes = initCallback?.onUnmount?.({
|
||||
isInitializing: lock.current,
|
||||
});
|
||||
// 有锁的情况下不执行销毁回调
|
||||
if (lock.current) {
|
||||
return;
|
||||
}
|
||||
// 页面离开时,全量保存一下编辑的内容
|
||||
const { botId } = useBotInfoStore.getState();
|
||||
saveAllEdit({ disable: unmountRes?.disableSaveAll });
|
||||
|
||||
setBotState({
|
||||
// 页面卸载时,将上一个botId改为当前的botId
|
||||
previousBotID: botId,
|
||||
});
|
||||
|
||||
autosaveManager.close();
|
||||
setPageRuntimeBotInfo({
|
||||
editable: false,
|
||||
});
|
||||
|
||||
useMonetizeConfigStore.getState().reset();
|
||||
useBotDetailStoreSet.clear();
|
||||
useGenerateImageStore.getState().clearGenerateImageStore();
|
||||
avatarBackgroundWebSocket.destroy();
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export { useInit };
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 { userStoreService } from '@coze-studio/user-store';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
|
||||
/**
|
||||
* bot 付费配置是否可编辑
|
||||
*
|
||||
* 与 bot 是否可编辑的区别:作者本人可以编辑,有 bot 编辑权限的协作者也无法修改付费配置
|
||||
*/
|
||||
export function useMonetizeConfigReadonly() {
|
||||
const userId = userStoreService.useUserInfo()?.user_id_str;
|
||||
const botCreatorId = useBotInfoStore(s => s.creator_id);
|
||||
const botDetailReadonly = useBotDetailIsReadonly();
|
||||
const isSelf = userId === botCreatorId;
|
||||
return botDetailReadonly || !isSelf;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { useCurrentEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
/**
|
||||
* 获取当前节点 id,若返回 undefined 则代表不在 multi 模式的节点上下文
|
||||
*
|
||||
* Q1:这个 hook 应该放到 multi-agent 模块下?
|
||||
* A1:multi-agent 模块下已有类似 hook。
|
||||
* 单独提出来是为了避免某些在 single 和 multi 都会复用的组件要使用 node id 时,产生不合理的 import 路径
|
||||
*
|
||||
* Q2:在 single 和 multi 都复用的组件,应该由调用方传入 node id?
|
||||
* A2:太理想了。有些组件业务逻辑过于复杂,由调用方传入的话会导致层层传参的深度略显夸张,比如 bot 的 workflow 弹窗。
|
||||
*/
|
||||
export function useCurrentNodeId() {
|
||||
let nodeId: string | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- 并未条件性调用 hook
|
||||
nodeId = useCurrentEntity().id;
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- SDK 符合预期的报错,无需额外处理
|
||||
} catch {
|
||||
nodeId = undefined;
|
||||
}
|
||||
return nodeId;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
export const usePluginPermissionManage = ({ botId }: { botId: string }) => {
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
data,
|
||||
run: runGetList,
|
||||
} = useRequest(
|
||||
() => PluginDevelopApi.GetQueriedOAuthPluginList({ bot_id: botId }),
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
const { runAsync: runRevoke } = useRequest(
|
||||
pluginId =>
|
||||
PluginDevelopApi.RevokeAuthToken({
|
||||
plugin_id: pluginId,
|
||||
bot_id: botId,
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
runGetList();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
runGetList();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
data: data?.oauth_plugin_list ?? [],
|
||||
runGetList,
|
||||
runRevoke,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 Dispatch, type SetStateAction, useState } from 'react';
|
||||
|
||||
import { type PopoverProps } from '@coze-arch/bot-semi/Popover';
|
||||
|
||||
export const usePopoverLock = ({
|
||||
defaultLocked,
|
||||
defaultVisible,
|
||||
}: {
|
||||
defaultVisible?: boolean;
|
||||
defaultLocked?: boolean;
|
||||
} = {}): {
|
||||
props: Pick<PopoverProps, 'trigger' | 'visible' | 'onClickOutSide'>;
|
||||
locked: boolean;
|
||||
visible: boolean;
|
||||
setVisible: Dispatch<SetStateAction<boolean>>;
|
||||
setLocked: Dispatch<SetStateAction<boolean>>;
|
||||
} => {
|
||||
const [locked, setLocked] = useState(defaultLocked ?? false);
|
||||
const [visible, setVisible] = useState(defaultVisible ?? false);
|
||||
|
||||
return {
|
||||
props: {
|
||||
trigger: 'custom',
|
||||
visible,
|
||||
onClickOutSide: () => {
|
||||
if (!locked) {
|
||||
setVisible(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
visible,
|
||||
locked,
|
||||
setVisible,
|
||||
setLocked,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 { useRequest } from 'ahooks';
|
||||
import {
|
||||
type GenerateUserQueryCollectPolicyRequest,
|
||||
type GetUserQueryCollectOptionData,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
export const useGenerateLink = () => {
|
||||
const [link, setLink] = useState('');
|
||||
const { loading, run: runGenerate } = useRequest(
|
||||
(info: GenerateUserQueryCollectPolicyRequest) =>
|
||||
PlaygroundApi.GenerateUserQueryCollectPolicy(info),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: dataSourceData => {
|
||||
setLink(dataSourceData.data.policy_link);
|
||||
},
|
||||
},
|
||||
);
|
||||
return {
|
||||
runGenerate,
|
||||
loading,
|
||||
link,
|
||||
};
|
||||
};
|
||||
|
||||
export const useGetUserQueryCollectOption = () => {
|
||||
const [queryCollectOption, setQueryCollectOption] =
|
||||
useState<GetUserQueryCollectOptionData>();
|
||||
const [supportText, setSupportText] = useState('');
|
||||
useRequest(() => PlaygroundApi.GetUserQueryCollectOption(), {
|
||||
onSuccess: dataSourceData => {
|
||||
setQueryCollectOption(dataSourceData.data);
|
||||
setSupportText(
|
||||
dataSourceData.data?.support_connectors
|
||||
?.map(item => item.name)
|
||||
.join('、'),
|
||||
);
|
||||
},
|
||||
});
|
||||
return {
|
||||
queryCollectOption,
|
||||
supportText,
|
||||
};
|
||||
};
|
||||
@@ -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 { useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { SpaceApiV2 } from '@coze-arch/bot-space-api';
|
||||
import { SpaceRoleType } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
export const useSpaceRole = () => {
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
useRequest(
|
||||
() =>
|
||||
SpaceApiV2.SpaceMemberDetailV2({
|
||||
page: 1,
|
||||
size: 1,
|
||||
}),
|
||||
{
|
||||
onSuccess: res => {
|
||||
setIsOwner(res.data?.space_role_type === SpaceRoleType.Owner);
|
||||
},
|
||||
},
|
||||
);
|
||||
return {
|
||||
isOwner,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { shuffle } from 'lodash-es';
|
||||
import { SuggestedQuestionsShowMode } from '@coze-arch/bot-api/developer_api';
|
||||
import { type ExtendOnboardingContent } from '@coze-studio/bot-detail-store/src/types/skill';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useChatArea } from '@coze-common/chat-area';
|
||||
import { getShuffledSuggestions } from '@coze-agent-ide/onboarding';
|
||||
import { type OnboardingDirtyLogicCompatibilityStore } from '@coze-agent-ide/bot-editor-context-store/src/store/onboarding-dirty-logic-compatibility';
|
||||
import { useBotEditor } from '@coze-agent-ide/bot-editor-context-store';
|
||||
const maxLength = 3;
|
||||
|
||||
interface UseSubscribeOnboardingAndUpdateChatAreaProps {
|
||||
setOnboardingSuggestionList?: (
|
||||
suggestions: ExtendOnboardingContent['suggested_questions'],
|
||||
) => void;
|
||||
updatePrologue?: (prologue: string) => void;
|
||||
useOnboardingDirtyLogicCompatibilityStore?: OnboardingDirtyLogicCompatibilityStore;
|
||||
}
|
||||
|
||||
export const useSubscribeOnboardingAndUpdateChatArea = (
|
||||
props?: UseSubscribeOnboardingAndUpdateChatAreaProps,
|
||||
) => {
|
||||
const chatArea = useChatArea();
|
||||
const { storeSet } = useBotEditor();
|
||||
const setOnboardingSuggestionList =
|
||||
props?.setOnboardingSuggestionList ?? chatArea.setOnboardingSuggestionList;
|
||||
const updatePrologue = props?.updatePrologue ?? chatArea.updatePrologue;
|
||||
const useOnboardingDirtyLogicCompatibilityStore =
|
||||
props?.useOnboardingDirtyLogicCompatibilityStore ??
|
||||
storeSet.useOnboardingDirtyLogicCompatibilityStore;
|
||||
|
||||
const getHasContentSuggestion = (
|
||||
suggestion: ExtendOnboardingContent['suggested_questions'][0],
|
||||
) => Boolean(suggestion.content.trim());
|
||||
|
||||
const initRecordingOnboarding = () => {
|
||||
const botSkillOnboarding = useBotSkillStore.getState().onboardingContent;
|
||||
|
||||
const hasContentSuggestion = botSkillOnboarding.suggested_questions.filter(
|
||||
getHasContentSuggestion,
|
||||
);
|
||||
updatePrologue(botSkillOnboarding.prologue);
|
||||
|
||||
if (isShowAllSuggestion(botSkillOnboarding)) {
|
||||
setOnboardingSuggestionList(hasContentSuggestion);
|
||||
return;
|
||||
}
|
||||
const preVisibleSuggestion =
|
||||
hasContentSuggestion.length > maxLength
|
||||
? shuffle(hasContentSuggestion)
|
||||
: hasContentSuggestion;
|
||||
|
||||
const visibleSuggestion = preVisibleSuggestion.slice(0, maxLength);
|
||||
useOnboardingDirtyLogicCompatibilityStore
|
||||
.getState()
|
||||
.addShuffledSuggestions(visibleSuggestion);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const offDirtyOnboardingSubscribe =
|
||||
useOnboardingDirtyLogicCompatibilityStore.subscribe(
|
||||
state => state.shuffledSuggestions,
|
||||
shuffledSuggestions => {
|
||||
setOnboardingSuggestionList(shuffledSuggestions);
|
||||
},
|
||||
);
|
||||
|
||||
const offBotDetailSubscribe = useBotSkillStore.subscribe(
|
||||
state => state.onboardingContent,
|
||||
botSkillOnboardingContent => {
|
||||
const hasContentSuggestion =
|
||||
botSkillOnboardingContent.suggested_questions.filter(
|
||||
getHasContentSuggestion,
|
||||
);
|
||||
|
||||
updatePrologue(botSkillOnboardingContent.prologue);
|
||||
|
||||
if (isShowAllSuggestion(botSkillOnboardingContent)) {
|
||||
setOnboardingSuggestionList(hasContentSuggestion);
|
||||
return;
|
||||
}
|
||||
|
||||
const { shuffledSuggestions, setShuffledSuggestions } =
|
||||
useOnboardingDirtyLogicCompatibilityStore.getState();
|
||||
|
||||
setShuffledSuggestions(
|
||||
getShuffledSuggestions({
|
||||
originSuggestions: hasContentSuggestion,
|
||||
shuffledSuggestions,
|
||||
maxLength,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
initRecordingOnboarding();
|
||||
|
||||
return () => {
|
||||
offBotDetailSubscribe();
|
||||
offDirtyOnboardingSubscribe();
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
const isShowAllSuggestion = (onboardingContent: ExtendOnboardingContent) =>
|
||||
onboardingContent.suggested_questions_show_mode ===
|
||||
SuggestedQuestionsShowMode.All;
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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 { useRequest } from 'ahooks';
|
||||
import {
|
||||
createReportEvent,
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
} from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { SpaceApi } from '@coze-arch/bot-space-api';
|
||||
import { useUIModal, Typography } from '@coze-arch/bot-semi';
|
||||
import { type PublishConnectorInfo } from '@coze-arch/bot-api/developer_api';
|
||||
import { IconAlertTriangle } from '@douyinfe/semi-icons';
|
||||
|
||||
import styles from '../pages/publish/index.module.less';
|
||||
|
||||
interface DiscordConfigureProps {
|
||||
botId: string;
|
||||
origin?: 'project' | 'bot';
|
||||
platformInfo: PublishConnectorInfo;
|
||||
onUnbind: () => void;
|
||||
}
|
||||
|
||||
const unbindPublishPlatformEvent = createReportEvent({
|
||||
eventName: ReportEventNames.unbindPublishPlatform,
|
||||
});
|
||||
|
||||
export const useUnbindPlatformModal = ({
|
||||
botId,
|
||||
origin = 'bot',
|
||||
platformInfo,
|
||||
onUnbind,
|
||||
}: DiscordConfigureProps) => {
|
||||
const { loading, run: unbindConntect } = useRequest(
|
||||
async () => {
|
||||
await SpaceApi.UnBindConnector({
|
||||
bot_id: botId,
|
||||
agent_type: origin === 'bot' ? 0 : 1,
|
||||
bind_id: platformInfo.bind_id ?? '',
|
||||
connector_id: platformInfo.id,
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onBefore: () => {
|
||||
unbindPublishPlatformEvent.start();
|
||||
},
|
||||
onSuccess: () => {
|
||||
onUnbind();
|
||||
close();
|
||||
unbindPublishPlatformEvent.success();
|
||||
},
|
||||
onError: (error, params) => {
|
||||
unbindPublishPlatformEvent.error({
|
||||
error,
|
||||
reason: error?.message,
|
||||
meta: { ...params },
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { modal, open, close } = useUIModal({
|
||||
type: 'info',
|
||||
icon: (
|
||||
<IconAlertTriangle
|
||||
style={{ color: 'var(--semi-color-danger)' }}
|
||||
size="extra-large"
|
||||
/>
|
||||
),
|
||||
onOk: () => {
|
||||
unbindConntect();
|
||||
},
|
||||
okText: I18n.t('Confirm'),
|
||||
okButtonProps: {
|
||||
loading,
|
||||
type: 'danger',
|
||||
},
|
||||
cancelText: I18n.t('Cancel'),
|
||||
onCancel: () => close(),
|
||||
title: I18n.t('bot_publish_disconnect_title', {
|
||||
platform: platformInfo?.name ?? '',
|
||||
}),
|
||||
closable: false,
|
||||
});
|
||||
|
||||
return {
|
||||
node: modal(
|
||||
<Typography.Paragraph className={styles['unbind-text']}>
|
||||
{I18n.t('bot_publish_disconnect_desc', {
|
||||
platform: platformInfo?.name ?? '',
|
||||
})}
|
||||
</Typography.Paragraph>,
|
||||
),
|
||||
open,
|
||||
close,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user