feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export const useOpenWorkflowDetail = () => {
|
||||
const { bot_id: botId } = useParams<DynamicParams>();
|
||||
|
||||
/** 打开流程详情页 */
|
||||
const openWorkflowDetailPage = ({
|
||||
workflowId,
|
||||
spaceId,
|
||||
projectId,
|
||||
ideNavigate,
|
||||
}: {
|
||||
workflowId: string;
|
||||
spaceId: string;
|
||||
projectId?: string;
|
||||
ideNavigate?: (uri: string) => void;
|
||||
}) => {
|
||||
if (projectId && ideNavigate) {
|
||||
ideNavigate(`/workflow/${workflowId}?from=createSuccess`);
|
||||
} else {
|
||||
const query = new URLSearchParams();
|
||||
botId && query.append('bot_id', botId);
|
||||
query.append('space_id', spaceId ?? '');
|
||||
query.append('workflow_id', workflowId);
|
||||
query.append('from', 'createSuccess');
|
||||
window.open(`/work_flow?${query.toString()}`, '_blank');
|
||||
}
|
||||
};
|
||||
return openWorkflowDetailPage;
|
||||
};
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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 max-lines-per-function */
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
|
||||
import {
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { omit } from 'lodash-es';
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
type UseInfiniteQueryResult,
|
||||
} from '@tanstack/react-query';
|
||||
import {
|
||||
WorkflowMode,
|
||||
type GetWorkFlowListRequest,
|
||||
type GetExampleWorkFlowListRequest,
|
||||
type GetWorkFlowListResponse,
|
||||
type GetExampleWorkFlowListResponse,
|
||||
WorkFlowType,
|
||||
DeleteType,
|
||||
workflowApi,
|
||||
DeleteAction,
|
||||
type WorkflowListByBindBizRequest,
|
||||
SchemaType,
|
||||
BindBizType,
|
||||
CheckType,
|
||||
} from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { reporter, wait } from '../utils';
|
||||
import { type WorkflowInfo, WorkflowModalFrom } from '../types';
|
||||
|
||||
interface FetchWorkflowListResult {
|
||||
total: number;
|
||||
workflow_list: WorkflowInfo[];
|
||||
}
|
||||
|
||||
interface WorkflowListReturn {
|
||||
flowType: WorkFlowType;
|
||||
setFlowType: Dispatch<SetStateAction<WorkFlowType>>;
|
||||
flowMode: WorkflowMode;
|
||||
setFlowMode: Dispatch<SetStateAction<WorkflowMode>>;
|
||||
spaceId: string;
|
||||
setSpaceId: Dispatch<SetStateAction<string>>;
|
||||
status: GetWorkFlowListRequest['status'] | undefined;
|
||||
setStatus: Dispatch<
|
||||
SetStateAction<GetWorkFlowListRequest['status'] | undefined>
|
||||
>;
|
||||
name: GetWorkFlowListRequest['name'] | undefined;
|
||||
setName: Dispatch<SetStateAction<GetWorkFlowListRequest['name'] | undefined>>;
|
||||
tags: GetWorkFlowListRequest['tags'] | undefined;
|
||||
setTags: Dispatch<SetStateAction<GetWorkFlowListRequest['tags'] | undefined>>;
|
||||
orderBy: GetWorkFlowListRequest['order_by'] | undefined;
|
||||
setOrderBy: Dispatch<
|
||||
SetStateAction<GetWorkFlowListRequest['order_by'] | undefined>
|
||||
>;
|
||||
loginUserCreate: boolean | undefined;
|
||||
setLoginUserCreate: Dispatch<SetStateAction<boolean | undefined>>;
|
||||
updatePageParam: (
|
||||
newParam: Partial<GetWorkFlowListRequest & WorkflowListByBindBizRequest>,
|
||||
) => void;
|
||||
workflowList: WorkflowInfo[];
|
||||
total: number;
|
||||
queryError: UseInfiniteQueryResult['error'];
|
||||
fetchNextPage: UseInfiniteQueryResult['fetchNextPage'];
|
||||
hasNextPage: UseInfiniteQueryResult['hasNextPage'];
|
||||
isFetching: UseInfiniteQueryResult['isFetching'];
|
||||
isFetchingNextPage: UseInfiniteQueryResult['isFetchingNextPage'];
|
||||
loadingStatus: UseInfiniteQueryResult['status'];
|
||||
refetch: UseInfiniteQueryResult['refetch'];
|
||||
handleCopy: (item: WorkflowInfo) => Promise<void>;
|
||||
handleDelete: (item: WorkflowInfo) => Promise<{
|
||||
canDelete: boolean;
|
||||
deleteType: DeleteType;
|
||||
handleDelete:
|
||||
| ((params?: { needDeleteBlockwise: boolean }) => Promise<void>)
|
||||
| undefined;
|
||||
}>;
|
||||
}
|
||||
|
||||
const defaultPageSize = 20;
|
||||
|
||||
/**
|
||||
* 流程列表
|
||||
*/
|
||||
export function useWorkflowList({
|
||||
pageSize = defaultPageSize,
|
||||
enabled = false,
|
||||
from,
|
||||
fetchWorkflowListApi = workflowApi.GetWorkFlowList.bind(workflowApi),
|
||||
}: {
|
||||
pageSize?: number;
|
||||
/** 是否开启数据获取 */
|
||||
enabled?: boolean;
|
||||
from?: WorkflowModalFrom;
|
||||
fetchWorkflowListApi?: (
|
||||
params: GetWorkFlowListRequest | GetExampleWorkFlowListRequest,
|
||||
) => Promise<GetWorkFlowListResponse | GetExampleWorkFlowListResponse>;
|
||||
} = {}): Readonly<WorkflowListReturn> {
|
||||
const [flowMode, setFlowMode] = useState<WorkflowMode>(WorkflowMode.All);
|
||||
const [flowType, setFlowType] = useState<WorkFlowType>(WorkFlowType.User);
|
||||
const [spaceId, setSpaceId] = useState<string>('');
|
||||
const [name, setName] = useState<GetWorkFlowListRequest['name']>();
|
||||
const [status, setStatus] = useState<GetWorkFlowListRequest['status']>();
|
||||
const [orderBy, setOrderBy] = useState<GetWorkFlowListRequest['order_by']>();
|
||||
const [tags, setTags] = useState<GetWorkFlowListRequest['tags']>();
|
||||
const [bindBizId, setBindBizId] =
|
||||
useState<WorkflowListByBindBizRequest['bind_biz_id']>();
|
||||
const [bindBizType, setBindBizType] =
|
||||
useState<WorkflowListByBindBizRequest['bind_biz_type']>();
|
||||
const [loginUserCreate, setLoginUserCreate] =
|
||||
useState<GetWorkFlowListRequest['login_user_create']>();
|
||||
const [projectId, setProjectId] =
|
||||
useState<GetWorkFlowListRequest['project_id']>('');
|
||||
const initialPageParam = useMemo<GetWorkFlowListRequest>(
|
||||
() => ({
|
||||
page: 1,
|
||||
size: pageSize,
|
||||
type: flowType,
|
||||
name,
|
||||
space_id: spaceId,
|
||||
status,
|
||||
tags,
|
||||
order_by: orderBy,
|
||||
login_user_create: loginUserCreate,
|
||||
flow_mode: flowMode,
|
||||
bind_biz_id: bindBizId,
|
||||
bind_biz_type: bindBizType,
|
||||
project_id: projectId,
|
||||
}),
|
||||
[
|
||||
flowType,
|
||||
status,
|
||||
name,
|
||||
flowMode,
|
||||
orderBy,
|
||||
spaceId,
|
||||
loginUserCreate,
|
||||
tags,
|
||||
bindBizId,
|
||||
bindBizType,
|
||||
projectId,
|
||||
],
|
||||
);
|
||||
|
||||
const updatePageParam = useCallback(
|
||||
(newParam: Partial<GetWorkFlowListRequest>) => {
|
||||
[
|
||||
{ key: 'type', func: setFlowType, defaultValue: WorkFlowType.User },
|
||||
{ key: 'name', func: setName },
|
||||
{ key: 'space_id', func: setSpaceId, defaultValue: '' },
|
||||
{ key: 'status', func: setStatus },
|
||||
{ key: 'tags', func: setTags },
|
||||
{ key: 'order_by', func: setOrderBy },
|
||||
{ key: 'login_user_create', func: setLoginUserCreate },
|
||||
{
|
||||
key: 'flow_mode',
|
||||
func: setFlowMode,
|
||||
defaultValue: WorkflowMode.All,
|
||||
},
|
||||
{ key: 'bind_biz_id', func: setBindBizId },
|
||||
{ key: 'bind_biz_type', func: setBindBizType },
|
||||
{ key: 'project_id', func: setProjectId },
|
||||
]
|
||||
.filter(({ key }) => key in newParam)
|
||||
.forEach(({ key, defaultValue, func }) =>
|
||||
func?.(newParam[key] ?? defaultValue),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchWorkflowList = async (
|
||||
params: GetWorkFlowListRequest & WorkflowListByBindBizRequest,
|
||||
): Promise<FetchWorkflowListResult> => {
|
||||
try {
|
||||
reporter.info({
|
||||
message: 'workflow_list_get_list',
|
||||
});
|
||||
|
||||
const result: FetchWorkflowListResult = {
|
||||
total: 0,
|
||||
workflow_list: [],
|
||||
};
|
||||
|
||||
if (params.bind_biz_type === BindBizType.Scene && params.bind_biz_id) {
|
||||
const resp = await workflowApi.WorkflowListByBindBiz(params);
|
||||
result.total = (resp.data.total as number) ?? 0;
|
||||
// 设置流程权限
|
||||
result.workflow_list = (resp.data.workflow_list ?? []).map(
|
||||
(item): WorkflowInfo => {
|
||||
const authInfo = {
|
||||
can_edit: true,
|
||||
can_copy: true,
|
||||
can_delete: !!item?.creator?.self,
|
||||
};
|
||||
return {
|
||||
...item,
|
||||
authInfo,
|
||||
};
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// 多人协作场景,DEV 模式需要展示 Blockwise workflow(除了流程列表引用)
|
||||
Object.assign(params, {
|
||||
schema_type_list: [SchemaType.FDL],
|
||||
checker:
|
||||
from === WorkflowModalFrom.WorkflowAgent
|
||||
? [CheckType.BotAgent]
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const isDouyinBot = params.bind_biz_type === BindBizType.DouYinBot;
|
||||
// 如果不是抖音分身模式,搜索参数不携带 bind_biz_id 参数
|
||||
// 否则会导致某个工作流关联到 Agent 后0,之后在该工作流添加子工作流时看不到工作流列表
|
||||
const fetchParams = isDouyinBot
|
||||
? params
|
||||
: omit(params, ['bind_biz_id']);
|
||||
|
||||
const resp = await fetchWorkflowListApi(fetchParams);
|
||||
result.total = (resp.data.total as number) ?? 0;
|
||||
// 设置流程权限
|
||||
result.workflow_list = (resp.data.workflow_list ?? []).map(
|
||||
(item): WorkflowInfo => {
|
||||
let authInfo = {
|
||||
can_edit: true,
|
||||
can_copy: true,
|
||||
can_delete: !!item?.creator?.self,
|
||||
};
|
||||
const authItem = (resp.data.auth_list ?? []).find(
|
||||
it => it.workflow_id === item.workflow_id,
|
||||
);
|
||||
if (authItem) {
|
||||
authInfo = { ...authInfo, ...authItem.auth };
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
authInfo,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_get_list_success',
|
||||
meta: {
|
||||
currentPage: params.page,
|
||||
pageSize: params.size,
|
||||
order_by: params.order_by,
|
||||
name: params.name,
|
||||
total: result.total,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
message: 'workflow_list_get_list_fail',
|
||||
error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
data: pageData,
|
||||
error: queryError,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
status: loadingStatus,
|
||||
refetch,
|
||||
} = useInfiniteQuery({
|
||||
enabled: enabled && !!spaceId,
|
||||
queryKey: ['space_workflow_list', 'vcs', JSON.stringify(initialPageParam)],
|
||||
queryFn: ({ pageParam }) => fetchWorkflowList(pageParam),
|
||||
initialPageParam,
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam) => {
|
||||
if ((lastPageParam.page ?? 1) * pageSize > lastPage.total) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...lastPageParam,
|
||||
page: (lastPageParam.page ?? 1) + 1,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const workflowList = useMemo(() => {
|
||||
const result: WorkflowInfo[] = [];
|
||||
const idMap: Record<string, boolean> = {};
|
||||
pageData?.pages.forEach(page => {
|
||||
page.workflow_list.forEach(workflow => {
|
||||
if (!workflow.workflow_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!idMap[workflow.workflow_id]) {
|
||||
result.push(workflow);
|
||||
}
|
||||
idMap[workflow.workflow_id] = true;
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, [pageData]);
|
||||
|
||||
const total = useMemo(() => {
|
||||
if (
|
||||
!pageData?.pages ||
|
||||
!Array.isArray(pageData.pages) ||
|
||||
pageData.pages.length === 0
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pageData.pages[pageData.pages.length - 1].total ?? 0;
|
||||
}, [pageData]);
|
||||
|
||||
// 复制
|
||||
const handleCopy = async (item: WorkflowInfo) => {
|
||||
if (!item.workflow_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
|
||||
// 检查复制权限
|
||||
if (!item.authInfo.can_copy) {
|
||||
throw new CustomError('normal_error', 'no copy permission');
|
||||
}
|
||||
reporter.info({
|
||||
message: 'workflow_list_copy_row',
|
||||
meta: {
|
||||
workflowId: item.workflow_id,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
let isError = false;
|
||||
const { data } = await workflowApi.CopyWorkflow({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.workflow_id,
|
||||
});
|
||||
isError = !data?.workflow_id;
|
||||
|
||||
if (isError) {
|
||||
Toast.error(I18n.t('workflow_detail_toast_createcopy_failed'));
|
||||
reporter.error({
|
||||
message: 'workflow_list_copy_row_fail',
|
||||
error: new CustomError('normal_error', 'result no workflow'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content:
|
||||
flowMode === WorkflowMode.Imageflow
|
||||
? I18n.t('imageflow_detail_toast_createcopy_succeed')
|
||||
: I18n.t('workflow_detail_toast_createcopy_succeed'),
|
||||
showClose: false,
|
||||
});
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_copy_row_success',
|
||||
meta: {
|
||||
workflowId: item.workflow_id,
|
||||
},
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
await wait(300);
|
||||
|
||||
// 刷新列表
|
||||
refetch();
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
message: 'workflow_list_copy_row_fail',
|
||||
error,
|
||||
});
|
||||
Toast.error(I18n.t('workflow_detail_toast_createcopy_failed'));
|
||||
}
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (item: WorkflowInfo) => {
|
||||
if (!item.workflow_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
|
||||
// 先检查删除权限
|
||||
if (!item.authInfo.can_delete) {
|
||||
throw new CustomError('normal_error', 'no delete permission');
|
||||
}
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_delete_row',
|
||||
meta: {
|
||||
workflowId: item.workflow_id,
|
||||
},
|
||||
});
|
||||
|
||||
let deleteType = DeleteType.CanDelete;
|
||||
|
||||
// 从服务端查询删除模式
|
||||
const resp = await workflowApi.GetDeleteStrategy({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.workflow_id,
|
||||
});
|
||||
deleteType = resp.data;
|
||||
|
||||
const canDelete = [
|
||||
DeleteType.CanDelete,
|
||||
DeleteType.RejectProductDraft,
|
||||
].includes(deleteType);
|
||||
|
||||
const deleteFuc = async (deleteParams?: {
|
||||
needDeleteBlockwise: boolean;
|
||||
}) => {
|
||||
const needDeleteBlockwise = deleteParams?.needDeleteBlockwise;
|
||||
const action = needDeleteBlockwise
|
||||
? DeleteAction.BlockwiseDelete
|
||||
: DeleteAction.BlockwiseUnbind;
|
||||
|
||||
if (!item.workflow_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
try {
|
||||
await workflowApi.DeleteWorkflow({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.workflow_id,
|
||||
action,
|
||||
});
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('workflow_add_delete_success'),
|
||||
showClose: false,
|
||||
});
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_delete_row_success',
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
await wait(300);
|
||||
|
||||
// 刷新列表
|
||||
refetch();
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
message: 'workflow_list_delete_row_fail',
|
||||
error,
|
||||
});
|
||||
Toast.error({
|
||||
content: I18n.t('workflow_add_delete_fail'),
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
/** 是否可删除 */
|
||||
canDelete,
|
||||
/** 删除策略 */
|
||||
deleteType,
|
||||
/** 删除方法 */
|
||||
handleDelete: canDelete ? deleteFuc : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// 列表筛选状态
|
||||
flowType,
|
||||
setFlowType,
|
||||
flowMode,
|
||||
setFlowMode,
|
||||
spaceId,
|
||||
setSpaceId,
|
||||
status,
|
||||
setStatus,
|
||||
name,
|
||||
setName,
|
||||
tags,
|
||||
setTags,
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
loginUserCreate,
|
||||
setLoginUserCreate,
|
||||
/** 更新筛选参数 */
|
||||
updatePageParam,
|
||||
// 列表获取
|
||||
/** 流程列表数据 */
|
||||
workflowList,
|
||||
/** 流程总数 */
|
||||
total,
|
||||
/** 获取列表请求错误 */
|
||||
queryError,
|
||||
/** 拉取下一页数据 */
|
||||
fetchNextPage,
|
||||
/** 是否有下一页 */
|
||||
hasNextPage,
|
||||
/** 获取数据中 */
|
||||
isFetching,
|
||||
/** 获取下一页数据中 */
|
||||
isFetchingNextPage,
|
||||
/** 加载状态 */
|
||||
loadingStatus,
|
||||
/** 重新加载 */
|
||||
refetch,
|
||||
// 列表操作
|
||||
/** 复制流程 */
|
||||
handleCopy,
|
||||
// /** 删除流程 */
|
||||
handleDelete,
|
||||
} as const;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { useBoolean } from 'ahooks';
|
||||
|
||||
import { type WorkFlowModalModeProps } from '../workflow-modal/type';
|
||||
import WorkflowModal from '../workflow-modal';
|
||||
|
||||
interface UseWorkFlowListReturnValue {
|
||||
node: ReactNode;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const useWorkflowModal = (
|
||||
props?: WorkFlowModalModeProps,
|
||||
): UseWorkFlowListReturnValue => {
|
||||
const { onClose, ...restProps } = props || {};
|
||||
const [visible, { setTrue: showModal, setFalse: hideModal }] =
|
||||
useBoolean(false);
|
||||
const closeModal = () => {
|
||||
onClose?.();
|
||||
hideModal();
|
||||
};
|
||||
return {
|
||||
node: visible ? (
|
||||
<WorkflowModal visible onClose={closeModal} {...restProps} />
|
||||
) : null,
|
||||
close: closeModal,
|
||||
open: showModal,
|
||||
};
|
||||
};
|
||||
@@ -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 { useWorkflowResourceMenuActions } from './use-workflow-resource-menu-actions';
|
||||
import { useWorkflowResourceClick } from './use-workflow-resource-click';
|
||||
import { useCreateWorkflowModal } from './use-create-workflow-modal';
|
||||
import {
|
||||
type UseWorkflowResourceAction,
|
||||
type WorkflowResourceActionProps,
|
||||
type WorkflowResourceActionReturn,
|
||||
} from './type';
|
||||
export { useWorkflowPublishEntry } from './use-workflow-publish-entry';
|
||||
export const useWorkflowResourceAction: UseWorkflowResourceAction = props => {
|
||||
const { spaceId, userId, getCommonActions } = props;
|
||||
const { handleWorkflowResourceClick, goWorkflowDetail } =
|
||||
useWorkflowResourceClick(spaceId);
|
||||
const {
|
||||
openCreateModal,
|
||||
workflowModal,
|
||||
createWorkflowModal,
|
||||
handleEditWorkflow,
|
||||
} = useCreateWorkflowModal({ ...props, goWorkflowDetail });
|
||||
const { renderWorkflowResourceActions, modals } =
|
||||
useWorkflowResourceMenuActions({
|
||||
...props,
|
||||
userId,
|
||||
onEditWorkflowInfo: handleEditWorkflow,
|
||||
getCommonActions,
|
||||
});
|
||||
|
||||
return {
|
||||
workflowResourceModals: [createWorkflowModal, workflowModal, ...modals],
|
||||
openCreateModal,
|
||||
handleWorkflowResourceClick,
|
||||
renderWorkflowResourceActions,
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
type WorkflowResourceActionProps,
|
||||
type WorkflowResourceActionReturn,
|
||||
useCreateWorkflowModal,
|
||||
useWorkflowResourceClick,
|
||||
useWorkflowResourceMenuActions,
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
type WorkflowMode,
|
||||
type ProductDraftStatus,
|
||||
type SchemaType,
|
||||
} from '@coze-workflow/base';
|
||||
import { type TableActionProps } from '@coze-arch/coze-design';
|
||||
import { type ResourceInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
export { type ResourceInfo };
|
||||
|
||||
export interface WorkflowResourceActionProps {
|
||||
/* 刷新列表函数 */
|
||||
refreshPage?: () => void;
|
||||
spaceId?: string;
|
||||
/* 当前登录用户 id */
|
||||
userId?: string;
|
||||
getCommonActions?: (
|
||||
libraryResource: ResourceInfo,
|
||||
) => NonNullable<TableActionProps['actionList']>;
|
||||
}
|
||||
export interface WorkflowResourceActionReturn {
|
||||
/* 打开 workflow 创建弹窗 */
|
||||
openCreateModal: (flowMode?: WorkflowMode) => void;
|
||||
/* 创建、删除等操作的全局弹窗,直接挂载到列表父容器上 */
|
||||
workflowResourceModals: ReactNode[];
|
||||
/* 在 Table 组件的 columns 的 render 里调用,返回 Table.TableAction 组件 */
|
||||
renderWorkflowResourceActions: (record: ResourceInfo) => ReactNode;
|
||||
/* 资源 item 点击 */
|
||||
handleWorkflowResourceClick: (record: ResourceInfo) => void;
|
||||
}
|
||||
|
||||
export type UseWorkflowResourceAction = (
|
||||
props: WorkflowResourceActionProps,
|
||||
) => WorkflowResourceActionReturn;
|
||||
|
||||
export interface WorkflowResourceBizExtend {
|
||||
product_draft_status: ProductDraftStatus;
|
||||
external_flow_info?: string;
|
||||
schema_type: SchemaType;
|
||||
plugin_id?: string;
|
||||
icon_uri: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface DeleteModalConfig {
|
||||
title: string;
|
||||
desc: string;
|
||||
okText: string;
|
||||
okHandle: () => void;
|
||||
cancelText: string;
|
||||
}
|
||||
|
||||
export interface CommonActionProps extends WorkflowResourceActionProps {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface CommonActionReturn {
|
||||
actionHandler: (record: ResourceInfo) => void;
|
||||
}
|
||||
export interface DeleteActionReturn extends CommonActionReturn {
|
||||
deleteModal?: ReactNode;
|
||||
}
|
||||
|
||||
export interface PublishActionReturn extends CommonActionReturn {
|
||||
publishModal: ReactNode;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { workflowApi } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import { WorkflowMode } from '@coze-arch/bot-api/workflow_api';
|
||||
import { type ResourceInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
export const useChatflowSwitch = ({
|
||||
spaceId,
|
||||
refreshPage,
|
||||
}: {
|
||||
spaceId: string;
|
||||
refreshPage?: () => void;
|
||||
}) => {
|
||||
const changeFlowMode = async (flowMode: WorkflowMode, workflowId: string) => {
|
||||
await workflowApi.UpdateWorkflowMeta({
|
||||
space_id: spaceId,
|
||||
workflow_id: workflowId,
|
||||
flow_mode: flowMode,
|
||||
});
|
||||
Toast.success(
|
||||
I18n.t('wf_chatflow_123', {
|
||||
Chatflow: I18n.t(
|
||||
flowMode === WorkflowMode.ChatFlow ? 'wf_chatflow_76' : 'Workflow',
|
||||
),
|
||||
}),
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
refreshPage?.();
|
||||
};
|
||||
const switchToWorkflow = async (record: ResourceInfo) =>
|
||||
changeFlowMode(WorkflowMode.Workflow, record.res_id ?? '');
|
||||
const switchToChatflow = async (record: ResourceInfo) =>
|
||||
changeFlowMode(WorkflowMode.ChatFlow, record.res_id ?? '');
|
||||
return { switchToWorkflow, switchToChatflow };
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { workflowApi } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { type ResourceInfo, ResType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { reporter, wait } from '@/utils';
|
||||
|
||||
import { type CommonActionProps, type CommonActionReturn } from './type';
|
||||
|
||||
export const useCopyAction = (props: CommonActionProps): CommonActionReturn => {
|
||||
const { spaceId } = props;
|
||||
const navigate = useNavigate();
|
||||
// 复制
|
||||
const handleCopy = async (item: ResourceInfo) => {
|
||||
if (!item.res_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_copy_row',
|
||||
meta: {
|
||||
workflowId: item.res_id,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
let isError = false;
|
||||
const { data } = await workflowApi.CopyWorkflow({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.res_id,
|
||||
});
|
||||
isError = !data?.workflow_id;
|
||||
|
||||
if (isError) {
|
||||
Toast.error(I18n.t('workflow_detail_toast_createcopy_failed'));
|
||||
reporter.error({
|
||||
message: 'workflow_list_copy_row_fail',
|
||||
error: new CustomError('normal_error', 'result no workflow'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content:
|
||||
item.res_type === ResType.Imageflow
|
||||
? I18n.t('imageflow_detail_toast_createcopy_succeed')
|
||||
: I18n.t('workflow_detail_toast_createcopy_succeed'),
|
||||
showClose: false,
|
||||
});
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_copy_row_success',
|
||||
meta: {
|
||||
workflowId: item.res_id,
|
||||
},
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
await wait(300);
|
||||
// 复制后跳转到详情页
|
||||
navigate(
|
||||
`/work_flow?workflow_id=${data.workflow_id}&space_id=${spaceId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
message: 'workflow_list_copy_row_fail',
|
||||
error,
|
||||
});
|
||||
Toast.error(I18n.t('workflow_detail_toast_createcopy_failed'));
|
||||
}
|
||||
};
|
||||
return { actionHandler: handleCopy };
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 { useMemo, useState } from 'react';
|
||||
|
||||
import { useBoolean } from 'ahooks';
|
||||
import {
|
||||
type FrontWorkflowInfo,
|
||||
WorkflowMode,
|
||||
isGeneralWorkflow,
|
||||
type BindBizType,
|
||||
} from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
|
||||
import { DataSourceType, WorkflowModalFrom } from '@/workflow-modal';
|
||||
import { CreateWorkflowModal, type RuleItem } from '@/workflow-edit';
|
||||
import { reporter } from '@/utils';
|
||||
import { useWorkflowModal } from '@/hooks/use-workflow-modal';
|
||||
|
||||
import { type WorkflowResourceActionProps } from './type';
|
||||
|
||||
export const useCreateWorkflowModal = ({
|
||||
from = WorkflowModalFrom.SpaceWorkflowList,
|
||||
bindBizType,
|
||||
bindBizId,
|
||||
refreshPage,
|
||||
spaceId,
|
||||
goWorkflowDetail,
|
||||
projectId,
|
||||
onCreateSuccess,
|
||||
hiddenTemplateEntry,
|
||||
nameValidators,
|
||||
}: WorkflowResourceActionProps & {
|
||||
from?: WorkflowModalFrom;
|
||||
/** 当前项目 id,只在项目内的 workflow 有该字段 */
|
||||
projectId?: string;
|
||||
bindBizType?: BindBizType;
|
||||
bindBizId?: string;
|
||||
onCreateSuccess?: ({ workflowId }: { workflowId: string }) => void;
|
||||
goWorkflowDetail?: (workflowId?: string, spaceId?: string) => void;
|
||||
/** 隐藏通过模板创建入口 */
|
||||
hiddenTemplateEntry?: boolean;
|
||||
nameValidators?: RuleItem[];
|
||||
}) => {
|
||||
const [currentWorkflow, setCurrentWorkflow] = useState<FrontWorkflowInfo>();
|
||||
const [formMode, setFormMode] = useState<'add' | 'update'>('add');
|
||||
const [flowMode, setFlowMode] = useState<WorkflowMode>(WorkflowMode.Workflow);
|
||||
const [createModalVisible, { setTrue, setFalse: closeCreateModal }] =
|
||||
useBoolean(false);
|
||||
|
||||
const [FLAGS] = useFlags();
|
||||
|
||||
const openCreateModal = (mode?: WorkflowMode) => {
|
||||
setFormMode('add');
|
||||
setFlowMode(mode || WorkflowMode.Workflow);
|
||||
reporter.info({
|
||||
message: 'workflow_list_open_create_modal',
|
||||
});
|
||||
setTrue();
|
||||
};
|
||||
|
||||
const openEditModal = () => {
|
||||
setFormMode('update');
|
||||
reporter.info({
|
||||
message: 'workflow_list_open_create_modal',
|
||||
});
|
||||
setTrue();
|
||||
};
|
||||
|
||||
const handleEditWorkflow = (partialWorkflowInfo: FrontWorkflowInfo) => {
|
||||
setCurrentWorkflow(partialWorkflowInfo);
|
||||
setFlowMode(partialWorkflowInfo.flow_mode || WorkflowMode.Workflow);
|
||||
openEditModal();
|
||||
};
|
||||
|
||||
const workflowModalInitState = useMemo(() => {
|
||||
// 社区版暂不支持该功能
|
||||
if (isWorkflowMode || FLAGS['bot.community.store_imageflow']) {
|
||||
return {
|
||||
productCategory: 'all',
|
||||
isSpaceWorkflow: false,
|
||||
dataSourceType: DataSourceType.Product,
|
||||
};
|
||||
}
|
||||
return {
|
||||
workflowTag: 1,
|
||||
isSpaceWorkflow: false,
|
||||
dataSource: DataSourceType.Workflow,
|
||||
};
|
||||
}, [FLAGS, flowMode]);
|
||||
|
||||
const { node: workflowModal } = useWorkflowModal({
|
||||
from,
|
||||
flowMode,
|
||||
dupText: I18n.t('Copy'),
|
||||
hiddenCreate: true,
|
||||
hiddenSpaceList: true,
|
||||
initState: workflowModalInitState,
|
||||
projectId,
|
||||
onAdd: () => {
|
||||
refreshPage?.();
|
||||
},
|
||||
onDupSuccess: val => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${spaceId}&workflow_id=${val.workflow_id}&from=dupSuccess`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const isWorkflowMode = useMemo(() => isGeneralWorkflow(flowMode), [flowMode]);
|
||||
|
||||
return {
|
||||
openCreateModal,
|
||||
handleEditWorkflow,
|
||||
workflowModal,
|
||||
createWorkflowModal: (
|
||||
<CreateWorkflowModal
|
||||
initConfirmDisabled
|
||||
mode={formMode}
|
||||
flowMode={flowMode}
|
||||
projectId={projectId}
|
||||
visible={createModalVisible}
|
||||
onCancel={closeCreateModal}
|
||||
bindBizType={bindBizType}
|
||||
bindBizId={bindBizId}
|
||||
workFlow={formMode === 'update' ? currentWorkflow : undefined}
|
||||
getLatestWorkflowJson={undefined}
|
||||
customTitleRender={undefined}
|
||||
onSuccess={({ workflowId }) => {
|
||||
closeCreateModal();
|
||||
if (!workflowId) {
|
||||
throw new CustomError(
|
||||
'[Workflow] create failed',
|
||||
'create workflow failed, no workflow id',
|
||||
);
|
||||
}
|
||||
|
||||
if (onCreateSuccess && formMode === 'add') {
|
||||
onCreateSuccess({ workflowId });
|
||||
return;
|
||||
}
|
||||
// 编辑模式,不跳转,刷新当前列表
|
||||
if (formMode === 'update') {
|
||||
refreshPage?.();
|
||||
return;
|
||||
}
|
||||
|
||||
const navigateDelay = 500;
|
||||
// 由于后端数据同步慢,这里delay 500 ms
|
||||
setTimeout(() => {
|
||||
goWorkflowDetail?.(workflowId, spaceId);
|
||||
}, navigateDelay);
|
||||
}}
|
||||
spaceID={spaceId}
|
||||
nameValidators={nameValidators}
|
||||
/>
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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 { DeleteAction, DeleteType, workflowApi } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { type ResourceInfo, ResType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { Modal, Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { reporter, wait } from '@/utils';
|
||||
|
||||
import {
|
||||
type CommonActionProps,
|
||||
type DeleteActionReturn,
|
||||
type DeleteModalConfig,
|
||||
} from './type';
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export const useDeleteAction = (
|
||||
props: CommonActionProps,
|
||||
): DeleteActionReturn => {
|
||||
const { spaceId, userId, refreshPage } = props;
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [deleteModalConfig, setDeleteModalConfig] =
|
||||
useState<DeleteModalConfig>();
|
||||
/**
|
||||
* 逻辑来自 useWorkflowList(@coze-workflow/components),由于入参变了,不再复用
|
||||
* @param item
|
||||
*/
|
||||
const handleDeleteWorkflowResource = async (item: ResourceInfo) => {
|
||||
if (!item.res_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_delete_row',
|
||||
meta: {
|
||||
workflowId: item.res_id,
|
||||
},
|
||||
});
|
||||
|
||||
let deleteType = DeleteType.CanDelete;
|
||||
|
||||
// 从服务端查询删除模式
|
||||
const resp = await workflowApi.GetDeleteStrategy({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.res_id,
|
||||
});
|
||||
deleteType = resp.data;
|
||||
|
||||
const canDelete = [
|
||||
DeleteType.CanDelete,
|
||||
DeleteType.RejectProductDraft,
|
||||
].includes(deleteType);
|
||||
|
||||
const deleteFuc = async (deleteParams?: {
|
||||
needDeleteBlockwise: boolean;
|
||||
}) => {
|
||||
const needDeleteBlockwise = deleteParams?.needDeleteBlockwise;
|
||||
const action = needDeleteBlockwise
|
||||
? DeleteAction.BlockwiseDelete
|
||||
: DeleteAction.BlockwiseUnbind;
|
||||
|
||||
if (!item.res_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
try {
|
||||
await workflowApi.DeleteWorkflow({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.res_id,
|
||||
action,
|
||||
});
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('workflow_add_delete_success'),
|
||||
showClose: false,
|
||||
});
|
||||
|
||||
reporter.info({
|
||||
message: 'workflow_list_delete_row_success',
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
await wait(300);
|
||||
|
||||
// 刷新列表
|
||||
refreshPage?.();
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
message: 'workflow_list_delete_row_fail',
|
||||
error,
|
||||
});
|
||||
Toast.error({
|
||||
content: I18n.t('workflow_add_delete_fail'),
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
canDelete,
|
||||
deleteType,
|
||||
handleDelete: canDelete ? deleteFuc : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const deleteAction = async (record: ResourceInfo) => {
|
||||
const isSelfCreator = record.creator_id === userId;
|
||||
const deleteConfig = await handleDeleteWorkflowResource(record);
|
||||
let title = I18n.t('delete_title');
|
||||
if (deleteConfig.deleteType === DeleteType.UnListProduct) {
|
||||
title = I18n.t('workflowstore_unable_to_delete_workflow');
|
||||
}
|
||||
|
||||
let desc = I18n.t('library_delete_desc');
|
||||
if (deleteConfig.deleteType === DeleteType.UnListProduct) {
|
||||
if (isSelfCreator) {
|
||||
desc = I18n.t('workflowstore_the_workflow_has_been');
|
||||
} else {
|
||||
desc = I18n.t('workflowstore_delete_permission');
|
||||
}
|
||||
}
|
||||
|
||||
let okText = deleteConfig.canDelete
|
||||
? I18n.t('confirm')
|
||||
: I18n.t('workflowstore_remove_wf');
|
||||
if (
|
||||
deleteConfig.deleteType === DeleteType.UnListProduct &&
|
||||
!isSelfCreator
|
||||
) {
|
||||
okText = '';
|
||||
}
|
||||
|
||||
let cancelText = I18n.t('cancel');
|
||||
if (
|
||||
deleteConfig.deleteType === DeleteType.UnListProduct &&
|
||||
!isSelfCreator
|
||||
) {
|
||||
cancelText = I18n.t('confirm');
|
||||
}
|
||||
|
||||
setDeleteModalConfig({
|
||||
title,
|
||||
desc,
|
||||
cancelText,
|
||||
okText,
|
||||
okHandle: () => {
|
||||
if (deleteConfig.canDelete) {
|
||||
deleteConfig?.handleDelete?.({
|
||||
needDeleteBlockwise: false,
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
deleteConfig.deleteType === DeleteType.UnListProduct &&
|
||||
isSelfCreator
|
||||
) {
|
||||
window.open(
|
||||
`/store/${
|
||||
record.res_type === ResType.Workflow ? 'workflow' : 'imageflow'
|
||||
}/${record.res_id}?entity_id=true`,
|
||||
'_blank',
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
const deleteModal = (
|
||||
<Modal
|
||||
maskClosable={false}
|
||||
centered={true}
|
||||
visible={modalVisible}
|
||||
title={deleteModalConfig?.title ?? ''}
|
||||
onOk={() => {
|
||||
setModalVisible(false);
|
||||
deleteModalConfig?.okHandle();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
}}
|
||||
closeOnEsc={true}
|
||||
cancelText={deleteModalConfig?.cancelText}
|
||||
okText={deleteModalConfig?.okText}
|
||||
okButtonColor={'red'}
|
||||
>
|
||||
<div className="coz-common-content">{deleteModalConfig?.desc ?? ''}</div>
|
||||
</Modal>
|
||||
);
|
||||
return { deleteModal, actionHandler: deleteAction };
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { WorkflowMode } from '@coze-workflow/base';
|
||||
import { AddWorkflowToStoreEntry } from '@coze-arch/bot-tea';
|
||||
import { type ResourceInfo, ResType } from '@coze-arch/bot-api/plugin_develop';
|
||||
import {
|
||||
PublishWorkflowModal,
|
||||
usePublishWorkflowModal,
|
||||
} from '@coze-workflow/resources-adapter';
|
||||
|
||||
import { type CommonActionProps, type PublishActionReturn } from './type';
|
||||
|
||||
export const usePublishAction = ({
|
||||
spaceId = '',
|
||||
refreshPage,
|
||||
}: CommonActionProps): PublishActionReturn => {
|
||||
const [flowMode, setFlowMode] = useState<WorkflowMode>(WorkflowMode.Workflow);
|
||||
const publishWorkflowModalHook = usePublishWorkflowModal({
|
||||
onPublishSuccess: () => {
|
||||
refreshPage?.();
|
||||
},
|
||||
fromSpace: true,
|
||||
flowMode,
|
||||
});
|
||||
|
||||
/**
|
||||
* NOTICE: 此函数由商店侧维护, 可联系 @gaoding
|
||||
* 发布/更新流程商品
|
||||
*/
|
||||
const onPublishStore = (item: ResourceInfo) => {
|
||||
setFlowMode(
|
||||
item.res_type === ResType.Imageflow
|
||||
? WorkflowMode.Imageflow
|
||||
: WorkflowMode.Workflow,
|
||||
);
|
||||
// 商店渲染流程需要 spaceId 信息, 在这个场景需要手动设置对应信息
|
||||
publishWorkflowModalHook.setSpace(spaceId);
|
||||
publishWorkflowModalHook.showModal({
|
||||
type: PublishWorkflowModal.WORKFLOW_INFO,
|
||||
product: {
|
||||
meta_info: {
|
||||
entity_id: item.res_id,
|
||||
name: item.name,
|
||||
},
|
||||
},
|
||||
source: AddWorkflowToStoreEntry.WORKFLOW_PERSONAL_LIST,
|
||||
});
|
||||
};
|
||||
return {
|
||||
actionHandler: onPublishStore,
|
||||
publishModal: publishWorkflowModalHook.ModalComponent,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useWorkflowPublishEntry } from '@coze-workflow/resources-adapter';
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 { ResourceInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { reporter } from '@/utils';
|
||||
|
||||
export const useWorkflowResourceClick = (spaceId?: string) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onEditWorkFlow = (workflowId?: string) => {
|
||||
reporter.info({
|
||||
message: 'workflow_list_edit_row',
|
||||
meta: {
|
||||
workflowId,
|
||||
},
|
||||
});
|
||||
goWorkflowDetail(workflowId, spaceId);
|
||||
};
|
||||
|
||||
/** 打开流程编辑页 */
|
||||
const goWorkflowDetail = (workflowId?: string, sId?: string) => {
|
||||
if (!workflowId || !sId) {
|
||||
return;
|
||||
}
|
||||
reporter.info({
|
||||
message: 'workflow_list_navigate_to_detail',
|
||||
meta: {
|
||||
workflowId,
|
||||
},
|
||||
});
|
||||
|
||||
navigate(`/work_flow?workflow_id=${workflowId}&space_id=${sId}`);
|
||||
};
|
||||
const handleWorkflowResourceClick = (record: ResourceInfo) => {
|
||||
reporter.info({
|
||||
message: 'workflow_list_click_row',
|
||||
});
|
||||
onEditWorkFlow(record?.res_id);
|
||||
};
|
||||
|
||||
return { handleWorkflowResourceClick, goWorkflowDetail };
|
||||
};
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
ProductDraftStatus,
|
||||
type FrontWorkflowInfo,
|
||||
} from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Table, type TableActionProps } from '@coze-arch/coze-design';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import {
|
||||
resource_resource_common,
|
||||
type ResourceInfo,
|
||||
ResType,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import {
|
||||
parseWorkflowResourceBizExtend,
|
||||
transformResourceToWorkflowEditInfo,
|
||||
} from './utils';
|
||||
import { useWorkflowPublishEntry } from './use-workflow-publish-entry';
|
||||
import { usePublishAction } from './use-publish-action';
|
||||
import { useDeleteAction } from './use-delete-action';
|
||||
import { useCopyAction } from './use-copy-action';
|
||||
import { useChatflowSwitch } from './use-chatflow-switch';
|
||||
import {
|
||||
type WorkflowResourceActionProps,
|
||||
type WorkflowResourceActionReturn,
|
||||
} from './type';
|
||||
|
||||
const { ActionKey } = resource_resource_common;
|
||||
|
||||
type ActionItemProps = NonNullable<TableActionProps['actionList']>[number];
|
||||
|
||||
export const useWorkflowResourceMenuActions = (
|
||||
props: WorkflowResourceActionProps & {
|
||||
userId?: string;
|
||||
onEditWorkflowInfo: (partialWorkflowInfo: FrontWorkflowInfo) => void;
|
||||
},
|
||||
): Pick<WorkflowResourceActionReturn, 'renderWorkflowResourceActions'> & {
|
||||
modals: ReactNode[];
|
||||
} => {
|
||||
const [FLAGS] = useFlags();
|
||||
const { userId, onEditWorkflowInfo, getCommonActions } = props;
|
||||
const { actionHandler: deleteAction, deleteModal } = useDeleteAction(props);
|
||||
const { actionHandler: copyAction } = useCopyAction(props);
|
||||
const { actionHandler: publishAction, publishModal } =
|
||||
usePublishAction(props);
|
||||
const { switchToChatflow, switchToWorkflow } = useChatflowSwitch({
|
||||
spaceId: props.spaceId ?? '',
|
||||
refreshPage: props.refreshPage,
|
||||
});
|
||||
const actionMap = {
|
||||
[ActionKey.Copy]: copyAction,
|
||||
[ActionKey.Delete]: deleteAction,
|
||||
[ActionKey.Edit]: (record: ResourceInfo) => {
|
||||
const workflowPartialInfo = transformResourceToWorkflowEditInfo(record);
|
||||
onEditWorkflowInfo(workflowPartialInfo as FrontWorkflowInfo);
|
||||
},
|
||||
[ActionKey.SwitchToFuncflow]: switchToWorkflow,
|
||||
[ActionKey.SwitchToChatflow]: switchToChatflow,
|
||||
};
|
||||
|
||||
const { enablePublishEntry } = useWorkflowPublishEntry();
|
||||
// eslint-disable-next-line complexity
|
||||
const renderWorkflowResourceActions = (record: ResourceInfo): ReactNode => {
|
||||
const bizExtend = parseWorkflowResourceBizExtend(record.biz_extend);
|
||||
const productDraftStatus = bizExtend?.product_draft_status;
|
||||
const isImageFlow = record.res_type === ResType.Imageflow;
|
||||
const { actions } = record;
|
||||
const deleteActionConfig = actions?.find(
|
||||
action => action.key === ActionKey.Delete,
|
||||
);
|
||||
const copyActionConfig = actions?.find(
|
||||
action => action.key === ActionKey.Copy,
|
||||
);
|
||||
const editConfig = actions?.find(action => action.key === ActionKey.Edit);
|
||||
const chatflowConfig = actions?.find(
|
||||
action => action.key === ActionKey.SwitchToChatflow,
|
||||
);
|
||||
const workflowConfig = actions?.find(
|
||||
action => action.key === ActionKey.SwitchToFuncflow,
|
||||
);
|
||||
|
||||
const isSelfCreator = record.creator_id === userId;
|
||||
const extraActions: ActionItemProps[] = [
|
||||
{
|
||||
hide: !editConfig,
|
||||
disabled: editConfig?.enable === false,
|
||||
actionKey: 'edit',
|
||||
actionText: I18n.t('Edit'),
|
||||
handler: () => actionMap?.[ActionKey.Edit]?.(record),
|
||||
},
|
||||
{
|
||||
hide: !chatflowConfig,
|
||||
disabled: chatflowConfig?.enable === false,
|
||||
actionKey: 'switchChatflow',
|
||||
actionText: I18n.t('wf_chatflow_121', {
|
||||
flowMode: I18n.t('wf_chatflow_76'),
|
||||
}),
|
||||
handler: () => actionMap?.[ActionKey.SwitchToChatflow]?.(record),
|
||||
},
|
||||
{
|
||||
hide: !workflowConfig,
|
||||
disabled: workflowConfig?.enable === false,
|
||||
actionKey: 'switchWorkflow',
|
||||
actionText: I18n.t('wf_chatflow_121', { flowMode: I18n.t('Workflow') }),
|
||||
handler: () => actionMap?.[ActionKey.SwitchToFuncflow]?.(record),
|
||||
},
|
||||
...(getCommonActions?.(record) ?? []),
|
||||
{
|
||||
hide:
|
||||
!enablePublishEntry || // 上架入口加白
|
||||
// 社区版暂不支持该功能
|
||||
(!FLAGS['bot.community.store_imageflow'] && isImageFlow) || // Imageflow 不支持商店
|
||||
!isSelfCreator ||
|
||||
bizExtend?.plugin_id === '0',
|
||||
actionKey: 'publishWorkflowProduct',
|
||||
actionText:
|
||||
productDraftStatus === ProductDraftStatus.Default
|
||||
? I18n.t('workflowstore_submit')
|
||||
: I18n.t('workflowstore_submit_update'),
|
||||
handler: () => {
|
||||
publishAction?.(record);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Table.TableAction
|
||||
deleteProps={{
|
||||
hide: !deleteActionConfig,
|
||||
disabled: deleteActionConfig?.enable === false,
|
||||
disableConfirm: true,
|
||||
handler: () => actionMap[ActionKey.Delete]?.(record),
|
||||
}}
|
||||
copyProps={{
|
||||
hide: !copyActionConfig,
|
||||
disabled: copyActionConfig?.enable === false,
|
||||
handler: () => actionMap[ActionKey.Copy]?.(record),
|
||||
}}
|
||||
actionList={extraActions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return { renderWorkflowResourceActions, modals: [deleteModal, publishModal] };
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 {
|
||||
ProductDraftStatus,
|
||||
SchemaType,
|
||||
type FrontWorkflowInfo,
|
||||
} from '@coze-workflow/base';
|
||||
import { type ResourceInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { type WorkflowResourceBizExtend } from './type';
|
||||
|
||||
export const parseWorkflowResourceBizExtend = (
|
||||
bizExtend?: Record<string, string>,
|
||||
): WorkflowResourceBizExtend | undefined => {
|
||||
if (!bizExtend) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
product_draft_status:
|
||||
bizExtend.product_draft_status !== undefined
|
||||
? parseInt(bizExtend.product_draft_status || '0')
|
||||
: ProductDraftStatus.Default,
|
||||
external_flow_info: bizExtend.external_flow_info,
|
||||
schema_type:
|
||||
bizExtend.schema_type !== undefined
|
||||
? parseInt(bizExtend.schema_type || '0')
|
||||
: SchemaType.DAG,
|
||||
plugin_id: bizExtend.plugin_id,
|
||||
icon_uri: bizExtend.icon_uri,
|
||||
url: bizExtend.url,
|
||||
};
|
||||
};
|
||||
/**
|
||||
* 转换 ResourceInfo 为编辑 workflow 所需的 WorkflowInfoLocal 结构
|
||||
* @param resource
|
||||
*/
|
||||
export const transformResourceToWorkflowEditInfo = (
|
||||
resource: ResourceInfo,
|
||||
): Pick<
|
||||
FrontWorkflowInfo,
|
||||
| 'workflow_id'
|
||||
| 'url'
|
||||
| 'icon_uri'
|
||||
| 'name'
|
||||
| 'desc'
|
||||
| 'schema_type'
|
||||
| 'external_flow_info'
|
||||
| 'space_id'
|
||||
> => {
|
||||
const bizExtend = parseWorkflowResourceBizExtend(resource.biz_extend);
|
||||
return {
|
||||
workflow_id: resource.res_id,
|
||||
url: bizExtend?.url,
|
||||
icon_uri: bizExtend?.icon_uri,
|
||||
name: resource.name,
|
||||
desc: resource.desc,
|
||||
schema_type: bizExtend?.schema_type,
|
||||
external_flow_info: bizExtend?.external_flow_info,
|
||||
space_id: resource.space_id,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user