feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 { size } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
IconCozCheckMarkCircleFill,
|
||||
IconCozInfoCircleFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Typography } from '@coze-arch/bot-semi';
|
||||
import { type TransferResourceInfo } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
interface IResource extends TransferResourceInfo {
|
||||
spaceID: string;
|
||||
}
|
||||
interface IItemGridView {
|
||||
title: string;
|
||||
resources: Array<IResource>;
|
||||
onResourceClick?: (id: string, spaceID: string) => void;
|
||||
showStatus?: boolean;
|
||||
}
|
||||
|
||||
export function ItemGridView(props: IItemGridView) {
|
||||
const { title, resources, showStatus = false, onResourceClick } = props;
|
||||
// HACK: 由于 grid 布局下边界线是透出的背景色,所以 resource 数量为单数的时候需要补齐一个
|
||||
const isEven = size(resources) % 2 === 0;
|
||||
const finalResources = isEven
|
||||
? resources
|
||||
: [...resources, { name: '', id: '', icon: '', spaceID: '' }];
|
||||
return (
|
||||
<>
|
||||
<p className="text-[12px] leading-[16px] font-[500] coz-fg-secondary text-left align-top w-full mb-[6px]">
|
||||
{title}
|
||||
</p>
|
||||
<div className="mb-[12px]">
|
||||
<div className="grid grid-cols-2 rounded-[6px] overflow-hidden border border-solid coz-stroke-primary gap-[1px] bg-[var(--coz-stroke-primary)] rounded-[4px]">
|
||||
{finalResources.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={classNames(
|
||||
'flex justify-center items-center gap-x-[4px] p-[8px] w-full coz-bg-plus',
|
||||
item.id ? 'hover:cursor-pointer' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (item.id) {
|
||||
onResourceClick?.(item.id, item.spaceID);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={item.icon}
|
||||
className="w-[16px] h-[16px] rounded-[2px]"
|
||||
/>
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top grow"
|
||||
>
|
||||
{item.name}
|
||||
</Typography.Text>
|
||||
{showStatus && item.status === 1 ? (
|
||||
<div className="coz-fg-hglt-green flex justify-center items-center">
|
||||
<IconCozCheckMarkCircleFill />
|
||||
</div>
|
||||
) : null}
|
||||
{showStatus && item.status === 0 ? (
|
||||
<div className="coz-fg-hglt-red flex justify-center items-center">
|
||||
<IconCozInfoCircleFill />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import { useRequest, useUnmount } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { MoveAction } from '@coze-arch/bot-api/playground_api';
|
||||
import { type BotSpace } from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { SelectorItem } from '../selector-item';
|
||||
import { ItemGridView } from '../item-grid-view';
|
||||
|
||||
interface IMoveDetailPaneProps {
|
||||
targetSpace: BotSpace | null;
|
||||
botID: string;
|
||||
fromSpaceID: string;
|
||||
onUnmount?: () => void;
|
||||
onDetailLoaded?: () => void;
|
||||
}
|
||||
|
||||
export function MoveDetailPane(props: IMoveDetailPaneProps) {
|
||||
const { targetSpace, botID, fromSpaceID, onUnmount, onDetailLoaded } = props;
|
||||
|
||||
const { data: moveDetails } = useRequest(
|
||||
async () => {
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: botID,
|
||||
target_spaceId: targetSpace.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: MoveAction.Preview,
|
||||
});
|
||||
return {
|
||||
...data?.async_task,
|
||||
cannotMove: data?.forbid_move,
|
||||
};
|
||||
},
|
||||
{
|
||||
onSuccess: data => {
|
||||
if (data && !data.cannotMove) {
|
||||
onDetailLoaded?.();
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useUnmount(() => {
|
||||
onUnmount?.();
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full border-[0.5px] border-solid coz-stroke-primary mb-[12px]"></div>
|
||||
<div className="flex flex-col max-h-[406px] overflow-y-auto">
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('resource_move_target_team')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col rounded-[6px] overflow-hidden mb-[16px]">
|
||||
<SelectorItem space={targetSpace} selected disabled />
|
||||
</div>
|
||||
</div>
|
||||
{moveDetails?.cannotMove ? (
|
||||
<div className="flex items-center gap-x-[8px] p-[12px] w-full coz-mg-hglt-red rounded-[4px] mb-[12px]">
|
||||
<p className="text-[12px] leading-[16px] font-[400] coz-fg-hglt-red text-left align-top grow">
|
||||
{I18n.t('move_not_allowed_contain_bot_nodes')}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{!moveDetails?.cannotMove &&
|
||||
(size(moveDetails?.transfer_resource_plugin_list) ||
|
||||
size(moveDetails?.transfer_resource_workflow_list) ||
|
||||
size(moveDetails?.transfer_resource_knowledge_list)) ? (
|
||||
<>
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('resource_move_together')}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-[8px] p-[8px] w-full coz-mg-hglt-red rounded-[4px] mb-[12px]">
|
||||
<p className="text-[12px] leading-[16px] font-[400] coz-fg-hglt-red text-left align-top grow">
|
||||
{I18n.t('resource_move_together_desc')}
|
||||
</p>
|
||||
</div>
|
||||
{size(moveDetails?.transfer_resource_plugin_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result2')}
|
||||
resources={moveDetails.transfer_resource_plugin_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: fromSpaceID,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/plugin/${id}`);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_workflow_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result3')}
|
||||
resources={moveDetails.transfer_resource_workflow_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: fromSpaceID,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${spaceID}&workflow_id=${id}`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_knowledge_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('performance_knowledge')}
|
||||
resources={moveDetails.transfer_resource_knowledge_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: fromSpaceID,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/knowledge/${id}`);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceList } from '@coze-arch/bot-studio-store';
|
||||
import { type BotSpace, SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { SelectorItem } from '../selector-item';
|
||||
|
||||
export function useSelectSpacePane() {
|
||||
const { spaces } = useSpaceList();
|
||||
const [targetSpace, setTargetSpace] = useState<BotSpace | null>(null);
|
||||
|
||||
const personalSpace = spaces.find(
|
||||
item => item.space_type === SpaceType.Personal,
|
||||
);
|
||||
const teamSpaces = spaces.filter(item => item.space_type === SpaceType.Team);
|
||||
|
||||
const selectSpacePane = (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full border-[0.5px] border-solid coz-stroke-primary mb-[12px]"></div>
|
||||
<div className="flex flex-col max-h-[406px] overflow-y-auto">
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('menu_title_personal_space')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col rounded-[6px] overflow-hidden mb-[16px]">
|
||||
<SelectorItem space={personalSpace} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('resource_move_target_team')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col rounded-[6px] overflow-hidden">
|
||||
{size(teamSpaces) > 0 ? (
|
||||
spaces
|
||||
.filter(item => item.space_type !== SpaceType.Personal)
|
||||
.map(item => (
|
||||
<SelectorItem
|
||||
key={item.id}
|
||||
space={item}
|
||||
selected={item.id === targetSpace?.id}
|
||||
onSelect={space => {
|
||||
setTargetSpace(space);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<SelectorItem
|
||||
space={{
|
||||
// MOCK: 用于展示未加入任何空间的兜底情况
|
||||
name: I18n.t('resource_move_no_team_joined'),
|
||||
}}
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
targetSpace,
|
||||
setTargetSpace,
|
||||
selectSpacePane,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 classnames from 'classnames';
|
||||
import { IconCozCheckMarkFill } from '@coze-arch/coze-design/icons';
|
||||
import { type BotSpace } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
interface ISelectorItemProps {
|
||||
space: BotSpace;
|
||||
disabled?: boolean;
|
||||
selected?: boolean;
|
||||
onSelect?: (space: BotSpace) => void;
|
||||
}
|
||||
|
||||
export function SelectorItem(props: ISelectorItemProps) {
|
||||
const { space, disabled = false, selected = false, onSelect } = props;
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'flex justify-between items-center gap-x-[8px] p-[8px] w-full coz-mg-primary',
|
||||
disabled ? '' : 'hover:coz-mg-primary-hovered cursor-pointer',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
onSelect?.(space);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{space.icon_url ? (
|
||||
<img
|
||||
src={space.icon_url}
|
||||
className="w-[24px] h-[24px] rounded-full mr-[8px]"
|
||||
/>
|
||||
) : null}
|
||||
<p
|
||||
className={classnames(
|
||||
'text-[14px] leading-[20px] font-[400] text-left align-middle whitespace-normal -webkit-box line-clamp-1 overflow-hidden grow',
|
||||
disabled ? 'coz-fg-secondary' : 'coz-fg-primary',
|
||||
)}
|
||||
>
|
||||
{space.name}
|
||||
</p>
|
||||
</div>
|
||||
{selected ? (
|
||||
<div className="w-[24px] h-[24px] flex justify-center items-center">
|
||||
<IconCozCheckMarkFill className="coz-fg-secondary" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useBotMoveModal } from './move-modal';
|
||||
export { useBotMoveFailedModal } from './move-failed-modal';
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* 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 React, { useCallback, useState } from 'react';
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean, useRequest } from 'ahooks';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import { Button, Modal, Toast } from '@coze-arch/coze-design';
|
||||
import { useSpaceList } from '@coze-arch/bot-studio-store';
|
||||
import { MoveAction } from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
DraftBotStatus,
|
||||
type DraftBot,
|
||||
SpaceType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { ItemGridView } from './components/item-grid-view';
|
||||
|
||||
interface BotMoveFailedModalOptions {
|
||||
/**
|
||||
* botInfo
|
||||
*/
|
||||
botInfo: Pick<DraftBot, 'id' | 'name'> | null;
|
||||
/**
|
||||
* 更新 bot 状态
|
||||
*/
|
||||
onUpdateBotStatus?: (status: DraftBotStatus) => void;
|
||||
/**
|
||||
* 迁移成功等效于删除 bot
|
||||
*/
|
||||
onMoveSuccess?: () => void;
|
||||
}
|
||||
|
||||
interface UseBotMoveFailedModalValue {
|
||||
/**
|
||||
* 打开弹窗的方法
|
||||
* @param {BotMoveModalOptions} [options] - 此次打开Modal的配置项
|
||||
*/
|
||||
open: (options?: BotMoveFailedModalOptions) => void;
|
||||
/**
|
||||
* 关闭弹窗的方法
|
||||
*/
|
||||
close: () => void;
|
||||
/**
|
||||
* 弹窗组件实例,需要手动挂载一下
|
||||
*/
|
||||
modalNode: React.ReactNode;
|
||||
}
|
||||
const DefaultOptions: BotMoveFailedModalOptions = { botInfo: null };
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export function useBotMoveFailedModal(): UseBotMoveFailedModalValue {
|
||||
const [options, setOptions] =
|
||||
useState<BotMoveFailedModalOptions>(DefaultOptions);
|
||||
const [visible, { setTrue: setVisibleTrue, setFalse: setVisibleFalse }] =
|
||||
useBoolean(false);
|
||||
|
||||
const [paneType, setPaneType] = useState<
|
||||
'detail' | 'confirm_cancel' | 'confirm_force'
|
||||
>('detail');
|
||||
|
||||
const title = (
|
||||
<span className="mb-[20px] coz-fg-plus text-[16px] font-medium leading-[22px]">
|
||||
{paneType === 'detail'
|
||||
? I18n.t('move_failed')
|
||||
: paneType === 'confirm_cancel'
|
||||
? I18n.t('move_failed_cancel_confirm_title')
|
||||
: paneType === 'confirm_force'
|
||||
? I18n.t('move_failed_force_confirm_title')
|
||||
: ''}
|
||||
</span>
|
||||
);
|
||||
|
||||
const open = useCallback((opts?: BotMoveFailedModalOptions) => {
|
||||
setOptions(opts || DefaultOptions);
|
||||
setPaneType('detail');
|
||||
setVisibleTrue();
|
||||
}, []);
|
||||
const close = useCallback(() => {
|
||||
setVisibleFalse();
|
||||
}, []);
|
||||
|
||||
const { spaces } = useSpaceList();
|
||||
const fromSpaceID =
|
||||
spaces?.find(s => s.space_type === SpaceType.Personal)?.id ?? '';
|
||||
|
||||
const { data: moveDetails } = useRequest(
|
||||
async () => {
|
||||
if (!options.botInfo) {
|
||||
return;
|
||||
}
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: options.botInfo.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: MoveAction.ViewTask,
|
||||
});
|
||||
return data.async_task;
|
||||
},
|
||||
{ refreshDeps: [options.botInfo] },
|
||||
);
|
||||
|
||||
const { loading, run } = useRequest(
|
||||
async (moveAction: MoveAction) => {
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: options.botInfo.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: moveAction,
|
||||
});
|
||||
return { ...data, moveAction };
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: data => {
|
||||
if (data.bot_status === DraftBotStatus.Using) {
|
||||
if (data.moveAction === MoveAction.CancelTask) {
|
||||
options.onUpdateBotStatus?.(data.bot_status);
|
||||
} else {
|
||||
Toast.success(I18n.t('resource_move_bot_success_toast'));
|
||||
options.onMoveSuccess?.();
|
||||
}
|
||||
close();
|
||||
} else if (data.bot_status === DraftBotStatus.MoveFail) {
|
||||
options.onUpdateBotStatus?.(data.bot_status);
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('move_failed_toast')),
|
||||
});
|
||||
close();
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(
|
||||
error?.message || I18n.t('move_failed_toast'),
|
||||
),
|
||||
showClose: false,
|
||||
});
|
||||
close();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const retry = async () => {
|
||||
await run(MoveAction.RetryMove);
|
||||
};
|
||||
const forceMove = async () => {
|
||||
await run(MoveAction.ForcedMove);
|
||||
};
|
||||
const cancelMove = async () => {
|
||||
await run(MoveAction.CancelTask);
|
||||
};
|
||||
|
||||
const footer = (
|
||||
<div
|
||||
className={classNames(
|
||||
'coz-modal-footer flex gap-2 justify-end',
|
||||
'w-full',
|
||||
)}
|
||||
>
|
||||
{paneType === 'detail' ? (
|
||||
<>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
size="large"
|
||||
color="primary"
|
||||
disabled={!moveDetails || loading}
|
||||
onClick={() => {
|
||||
setPaneType('confirm_cancel');
|
||||
}}
|
||||
>
|
||||
{I18n.t('move_failed_btn_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
size="large"
|
||||
color="primary"
|
||||
disabled={!moveDetails || loading}
|
||||
onClick={() => {
|
||||
setPaneType('confirm_force');
|
||||
}}
|
||||
>
|
||||
{I18n.t('move_failed_btn_force')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
color="brand"
|
||||
size="large"
|
||||
loading={loading}
|
||||
disabled={!moveDetails || loading}
|
||||
onClick={() => {
|
||||
retry();
|
||||
}}
|
||||
>
|
||||
{I18n.t('Retry')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm_cancel' ? (
|
||||
<>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('detail');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="brand"
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
cancelMove();
|
||||
}}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm_force' ? (
|
||||
<>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('detail');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="brand"
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
forceMove();
|
||||
}}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const modalNode = (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={title}
|
||||
footer={footer}
|
||||
width={paneType !== 'detail' ? '448px' : '480px'}
|
||||
footerFill
|
||||
onCancel={close}
|
||||
closable={!['confirm_cancel', 'confirm_force'].includes(paneType)}
|
||||
maskClosable={false}
|
||||
keepDOM={false}
|
||||
closeIcon={<IconCozCross className="coz-fg-secondary" />}
|
||||
>
|
||||
{paneType === 'detail' ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full border-[0.5px] border-solid coz-stroke-primary mb-[12px]"></div>
|
||||
<div className="flex flex-col max-h-[406px] overflow-y-auto">
|
||||
<div className="flex items-center gap-x-[8px] p-[8px] w-full coz-mg-primary rounded-[6px] mb-[12px]">
|
||||
<p className="text-[12px] leading-[16px] font-[400] coz-fg-secondary text-left align-top grow">
|
||||
{I18n.t('move_failed_desc')}
|
||||
</p>
|
||||
</div>
|
||||
{size(moveDetails?.transfer_resource_plugin_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result2')}
|
||||
resources={moveDetails?.transfer_resource_plugin_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: item.status
|
||||
? moveDetails?.task_info.TargetSpaceId
|
||||
: moveDetails?.task_info.OriSpaceId,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/plugin/${id}`);
|
||||
}}
|
||||
showStatus
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_workflow_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result3')}
|
||||
resources={moveDetails?.transfer_resource_workflow_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: item.status
|
||||
? moveDetails?.task_info.TargetSpaceId
|
||||
: moveDetails?.task_info.OriSpaceId,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${spaceID}&workflow_id=${id}`,
|
||||
);
|
||||
}}
|
||||
showStatus
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_knowledge_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('performance_knowledge')}
|
||||
resources={moveDetails?.transfer_resource_knowledge_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: item.status
|
||||
? moveDetails?.task_info.TargetSpaceId
|
||||
: moveDetails?.task_info.OriSpaceId,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/knowledge/${id}`);
|
||||
}}
|
||||
showStatus
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{paneType === 'confirm_force' ? (
|
||||
<div className="mt-[20px]">
|
||||
{I18n.t('move_failed_force_confirm_content')}
|
||||
</div>
|
||||
) : null}
|
||||
{paneType === 'confirm_cancel' ? (
|
||||
<div className="mt-[20px]">
|
||||
{I18n.t('move_failed_cancel_confirm_content')}
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return {
|
||||
modalNode,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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 React, { useCallback, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean, useRequest } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceList } from '@coze-arch/bot-studio-store';
|
||||
import { MoveAction } from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
DraftBotStatus,
|
||||
type DraftBot,
|
||||
SpaceType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { cozeMitt } from '@coze-common/coze-mitt';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Modal,
|
||||
Toast,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useSelectSpacePane } from './components/select-space-pane';
|
||||
import { MoveDetailPane } from './components/move-detail-pane';
|
||||
|
||||
interface BotMoveModalOptions {
|
||||
/**
|
||||
* botInfo
|
||||
*/
|
||||
botInfo: Pick<DraftBot, 'name' | 'id'> | null;
|
||||
/**
|
||||
* 更新 bot 状态
|
||||
*/
|
||||
onUpdateBotStatus?: (status: DraftBotStatus) => void;
|
||||
/**
|
||||
* 迁移成功等效于删除 bot
|
||||
*/
|
||||
onMoveSuccess?: () => void;
|
||||
/**
|
||||
* 关闭 modal
|
||||
*/
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
interface UseBotMoveModalValue {
|
||||
/**
|
||||
* 打开弹窗的方法
|
||||
* @param {BotMoveModalOptions} [options] - 此次打开Modal的配置项
|
||||
*/
|
||||
open: (options?: BotMoveModalOptions) => void;
|
||||
/**
|
||||
* 关闭弹窗的方法
|
||||
*/
|
||||
close: () => void;
|
||||
/**
|
||||
* 弹窗组件实例,需要手动挂载一下
|
||||
*/
|
||||
modalNode: React.ReactNode;
|
||||
}
|
||||
const DefaultOptions: BotMoveModalOptions = { botInfo: null };
|
||||
|
||||
export function useBotMoveModal(): UseBotMoveModalValue {
|
||||
const [options, setOptions] = useState<BotMoveModalOptions>(DefaultOptions);
|
||||
const [visible, { setTrue: setVisibleTrue, setFalse: setVisibleFalse }] =
|
||||
useBoolean(false);
|
||||
|
||||
const [paneType, setPaneType] = useState<'select' | 'move' | 'confirm'>(
|
||||
'select',
|
||||
);
|
||||
const { targetSpace, selectSpacePane, setTargetSpace } = useSelectSpacePane();
|
||||
|
||||
const title =
|
||||
paneType !== 'confirm' ? (
|
||||
<div className="flex justify-start items-center mb-[24px] w-[380px]">
|
||||
<div className="coz-fg-plus text-[16px] font-medium leading-[22px] max-w-full">
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: true,
|
||||
}}
|
||||
className="text-[16px]"
|
||||
>
|
||||
{I18n.t('resource_move_title', {
|
||||
bot_name: options.botInfo?.name ?? '',
|
||||
})}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Tooltip content={I18n.t('resource_move_notice')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
icon={<IconInfo className="coz-fg-secondary" />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
I18n.t('resource_move_confirm_title')
|
||||
);
|
||||
|
||||
const open = useCallback((opts?: BotMoveModalOptions) => {
|
||||
setOptions(opts || DefaultOptions);
|
||||
setPaneType('select');
|
||||
setTargetSpace(null);
|
||||
setVisibleTrue();
|
||||
}, []);
|
||||
const close = useCallback(() => {
|
||||
setVisibleFalse();
|
||||
}, []);
|
||||
|
||||
const { spaces } = useSpaceList();
|
||||
const fromSpaceID = spaces.find(s => s.space_type === SpaceType.Personal)?.id;
|
||||
|
||||
const { loading: moveLoading, run: moveBot } = useRequest(
|
||||
async () => {
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: options.botInfo.id,
|
||||
target_spaceId: targetSpace.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: MoveAction.Move,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: data => {
|
||||
if (data.bot_status === DraftBotStatus.Using) {
|
||||
Toast.success(I18n.t('resource_move_bot_success_toast'));
|
||||
options.onMoveSuccess?.();
|
||||
close();
|
||||
cozeMitt.emit('refreshFavList', {
|
||||
numDelta: -1,
|
||||
});
|
||||
} else if (data.bot_status === DraftBotStatus.MoveFail) {
|
||||
options.onUpdateBotStatus?.(data.bot_status);
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('move_failed_toast')),
|
||||
});
|
||||
close();
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(
|
||||
error?.message || I18n.t('move_failed_toast'),
|
||||
),
|
||||
showClose: false,
|
||||
});
|
||||
close();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const onConfirm = async () => {
|
||||
await moveBot();
|
||||
};
|
||||
|
||||
const [moveDisabled, setMoveDisabled] = useState(true);
|
||||
const footer = (
|
||||
<div
|
||||
className={classNames(
|
||||
'coz-modal-footer flex gap-2 justify-end',
|
||||
paneType !== 'confirm' && 'w-full',
|
||||
)}
|
||||
>
|
||||
{paneType === 'select' ? (
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
color="brand"
|
||||
size="large"
|
||||
disabled={!targetSpace}
|
||||
onClick={() => {
|
||||
setPaneType('move');
|
||||
}}
|
||||
>
|
||||
{I18n.t('next')}
|
||||
</Button>
|
||||
) : null}
|
||||
{paneType === 'move' ? (
|
||||
<>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
size="large"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('select');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
color="brand"
|
||||
size="large"
|
||||
disabled={moveDisabled}
|
||||
onClick={() => {
|
||||
setPaneType('confirm');
|
||||
}}
|
||||
>
|
||||
{I18n.t('resource_move')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm' ? (
|
||||
<>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('move');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="brand"
|
||||
loading={moveLoading}
|
||||
onClick={() => {
|
||||
onConfirm();
|
||||
}}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const modalNode = (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={title}
|
||||
footer={footer}
|
||||
width={paneType === 'confirm' ? '448px' : '480px'}
|
||||
footerFill
|
||||
onCancel={() => {
|
||||
close?.();
|
||||
options.onClose?.();
|
||||
}}
|
||||
closable={paneType !== 'confirm'}
|
||||
maskClosable={false}
|
||||
keepDOM={false}
|
||||
closeIcon={<IconCozCross className="coz-fg-secondary" />}
|
||||
>
|
||||
{paneType === 'select' ? selectSpacePane : null}
|
||||
{paneType === 'move' ? (
|
||||
<>
|
||||
<MoveDetailPane
|
||||
targetSpace={targetSpace}
|
||||
botID={options.botInfo.id}
|
||||
fromSpaceID={fromSpaceID}
|
||||
onUnmount={() => setMoveDisabled(true)}
|
||||
onDetailLoaded={() => setMoveDisabled(false)}
|
||||
/>
|
||||
{IS_CN_REGION ? (
|
||||
<div className="coz-fg-hglt-red">{I18n.t('move_desc1')}</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm' ? (
|
||||
<div className="mt-[20px]">
|
||||
{I18n.t('resource_move_confirm_content')}
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return {
|
||||
modalNode,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user