feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OrderBy, WorkFlowListStatus } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { WORKFLOW_LIST_STATUS_ALL } from '@/workflow-modal/type';
|
||||
|
||||
/** 流程所有者选项, 全部/我的 */
|
||||
export const scopeOptions = [
|
||||
{
|
||||
label: I18n.t('workflow_list_scope_all'),
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
label: I18n.t('workflow_list_scope_mine'),
|
||||
value: 'me',
|
||||
},
|
||||
];
|
||||
|
||||
/** 流程状态选项, 全部/已发布/未发布 */
|
||||
export const statusOptions = [
|
||||
{
|
||||
label: I18n.t('workflow_list_status_all'),
|
||||
value: WORKFLOW_LIST_STATUS_ALL,
|
||||
},
|
||||
{
|
||||
label: I18n.t('workflow_list_status_published'),
|
||||
value: WorkFlowListStatus.HadPublished,
|
||||
},
|
||||
{
|
||||
label: I18n.t('workflow_list_status_unpublished'),
|
||||
value: WorkFlowListStatus.UnPublished,
|
||||
},
|
||||
];
|
||||
|
||||
/** 流程排序选项, 创建时间/更新时间 */
|
||||
export const sortOptions = [
|
||||
{
|
||||
label: I18n.t('workflow_list_sort_create_time'),
|
||||
value: OrderBy.CreateTime,
|
||||
},
|
||||
{
|
||||
label: I18n.t('workflow_list_sort_edit_time'),
|
||||
value: OrderBy.UpdateTime,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
button.button {
|
||||
min-width: 76px;
|
||||
|
||||
&.moreLevel {
|
||||
color: var(--light-usage-primary-color-primary-disabled, #b4baf6);
|
||||
background: var(--light-usage-bg-color-bg-0, #fff);
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.mouseIn {
|
||||
color: #fff;
|
||||
background-color: rgba(var(--coze-red-5), 1) !important;
|
||||
}
|
||||
}
|
||||
@@ -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 React, { type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, type ButtonProps } from '@coze-arch/coze-design';
|
||||
|
||||
import { useI18nText } from '@/workflow-modal/hooks/use-i18n-text';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
type WorkflowAddedButtonProps = ButtonProps;
|
||||
|
||||
export const WorkflowAddedButton: FC<
|
||||
WorkflowAddedButtonProps
|
||||
> = buttonProps => {
|
||||
const [isMouseIn, { setFalse, setTrue }] = useBoolean(false);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
setTrue();
|
||||
};
|
||||
const onMouseLeave = () => {
|
||||
setFalse();
|
||||
};
|
||||
const { i18nText, ModalI18nKey } = useI18nText();
|
||||
return (
|
||||
<Button
|
||||
{...buttonProps}
|
||||
color={isMouseIn ? 'red' : 'primary'}
|
||||
className={classNames({
|
||||
[styles.button]: true,
|
||||
[styles.moreLevel]: true,
|
||||
})}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
data-testid="workflow.modal.button.added"
|
||||
>
|
||||
{isMouseIn
|
||||
? i18nText(ModalI18nKey.ListItemRemove)
|
||||
: I18n.t('workflow_add_list_added')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.font-normal {
|
||||
cursor: text;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-left: 8px;
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
width: 76px;
|
||||
}
|
||||
}
|
||||
|
||||
.not_publish_tooltip {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
|
||||
.content {
|
||||
.font-normal();
|
||||
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.workflow_count_span {
|
||||
display: inline-block;
|
||||
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 6px;
|
||||
|
||||
font-size: 10px;
|
||||
line-height: 17px;
|
||||
color: #fff;
|
||||
vertical-align: 1px;
|
||||
|
||||
background-color: rgba(77, 83, 232, 100%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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 FC, useContext, useMemo, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { LoadingButton } from '@coze-arch/coze-design';
|
||||
import { Popconfirm, Tooltip } from '@coze-arch/bot-semi';
|
||||
import { CheckType, type CheckResult } from '@coze-arch/bot-api/workflow_api';
|
||||
import { type WorkflowNodeJSON } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import WorkflowModalContext from '../../../workflow-modal-context';
|
||||
import { isSelectProjectCategory } from '../../../utils';
|
||||
import { type WorkflowInfo, WorkflowModalFrom } from '../../../type';
|
||||
import { useI18nText } from '../../../hooks/use-i18n-text';
|
||||
import { WorkflowAddedButton } from './added-button';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface WorkflowBotButtonProps {
|
||||
data?: WorkflowInfo;
|
||||
isAdded?: boolean;
|
||||
from?: WorkflowModalFrom;
|
||||
loading?: boolean;
|
||||
workflowNodes?: WorkflowNodeJSON[];
|
||||
onAdd: () => Promise<boolean>;
|
||||
onRemove: () => void;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const WorkflowBotButton: FC<WorkflowBotButtonProps> = ({
|
||||
data,
|
||||
style,
|
||||
isAdded,
|
||||
onAdd,
|
||||
onRemove,
|
||||
className,
|
||||
from,
|
||||
workflowNodes,
|
||||
loading,
|
||||
}) => {
|
||||
const { plugin_id } = data || {};
|
||||
const isPublished = plugin_id !== '0';
|
||||
const isFromWorkflow =
|
||||
from === WorkflowModalFrom.WorkflowAddNode ||
|
||||
from === WorkflowModalFrom.ProjectWorkflowAddNode;
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const isAddProjectWorkflow = isSelectProjectCategory(context?.modalState);
|
||||
const canAdd = isPublished || isAddProjectWorkflow;
|
||||
const isFromSocialScene = from === WorkflowModalFrom.SocialSceneHost;
|
||||
const [count, setCount] = useState((workflowNodes || []).length);
|
||||
|
||||
const isFromWorkflowAgent = from === WorkflowModalFrom.WorkflowAgent;
|
||||
const botAgentCheckResult = useMemo<CheckResult | undefined>(
|
||||
() => data?.check_result?.find(check => check.type === CheckType.BotAgent),
|
||||
[data],
|
||||
);
|
||||
|
||||
const { i18nText, ModalI18nKey } = useI18nText();
|
||||
const renderContent = () => {
|
||||
if (isFromWorkflowAgent) {
|
||||
if (botAgentCheckResult && !botAgentCheckResult.is_pass) {
|
||||
return (
|
||||
<Tooltip
|
||||
position="top"
|
||||
className={styles.not_publish_tooltip}
|
||||
content={
|
||||
<span className={styles.content}>
|
||||
{botAgentCheckResult.reason}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<LoadingButton
|
||||
disabled
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
data-testid="workflow.modal.add"
|
||||
>
|
||||
{I18n.t('workflow_add_list_add')}
|
||||
</LoadingButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已添加,展示已添加按钮
|
||||
if (isAdded) {
|
||||
return (
|
||||
<Popconfirm
|
||||
title={i18nText(ModalI18nKey.ListItemRemoveConfirmTitle)}
|
||||
content={i18nText(ModalI18nKey.ListItemRemoveConfirmDescription)}
|
||||
okType="danger"
|
||||
position="topRight"
|
||||
onConfirm={onRemove}
|
||||
zIndex={9999}
|
||||
okText={I18n.t('neutral_age_gate_confirm', {}, 'Confirm')}
|
||||
cancelText={I18n.t('workflow_240218_17', {}, 'Cancel')}
|
||||
>
|
||||
<div>
|
||||
<WorkflowAddedButton />
|
||||
</div>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
// 未添加,判断发布状态
|
||||
// 未发布,展示下面的按钮
|
||||
if (!canAdd) {
|
||||
let key: I18nKeysNoOptionsType = 'workflow_add_not_allow_before_publish';
|
||||
if (isFromWorkflow) {
|
||||
key = 'wf_node_add_wf_modal_tip_must_publish_to_add';
|
||||
} else if (isFromSocialScene) {
|
||||
key = 'scene_workflow_popup_add_forbidden';
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
position="top"
|
||||
className={styles.not_publish_tooltip}
|
||||
content={<span className={styles.content}>{I18n.t(key)}</span>}
|
||||
>
|
||||
<LoadingButton
|
||||
disabled
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
data-testid="workflow.modal.add"
|
||||
>
|
||||
{I18n.t('workflow_add_list_add')}
|
||||
</LoadingButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
// 已发布并且未添加,展示添加按钮
|
||||
if (!isAdded) {
|
||||
return (
|
||||
<LoadingButton
|
||||
onClick={async () => {
|
||||
const isSuccess = await onAdd?.();
|
||||
if (isSuccess) {
|
||||
setCount(prev => prev + 1);
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
data-testid="workflow.modal.add"
|
||||
>
|
||||
{I18n.t('workflow_add_list_add')}
|
||||
{isFromWorkflow && count !== 0 ? (
|
||||
<span className={styles.workflow_count_span}>{count}</span>
|
||||
) : null}
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.container, className)}
|
||||
style={style}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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, type MouseEvent } from 'react';
|
||||
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Popconfirm } from '@coze-arch/coze-design';
|
||||
|
||||
export const DeleteButton = ({
|
||||
className,
|
||||
onDelete,
|
||||
}: {
|
||||
className?: string;
|
||||
onDelete?: () => Promise<void>;
|
||||
}) => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const handleClose = () => setModalVisible(false);
|
||||
const showDeleteConfirm = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const handleDelete = () =>
|
||||
// 使用 promise 让按钮出现 loading 的效果,参见
|
||||
// https://semi.design/zh-CN/feedback/popconfirm
|
||||
new Promise((resolve, reject) => {
|
||||
onDelete?.()
|
||||
.then(() => {
|
||||
handleClose();
|
||||
resolve(true);
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
logger.error({
|
||||
error: error as Error,
|
||||
eventName: 'delete workflow error',
|
||||
});
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div className={className} onClick={e => e.stopPropagation()}>
|
||||
<Popconfirm
|
||||
visible={modalVisible}
|
||||
title={I18n.t('scene_workflow_popup_delete_confirm_title')}
|
||||
content={I18n.t('scene_workflow_popup_delete_confirm_subtitle')}
|
||||
okText={I18n.t('shortcut_modal_confirm')}
|
||||
cancelText={I18n.t('shortcut_modal_cancel')}
|
||||
trigger="click"
|
||||
position="bottomRight"
|
||||
onConfirm={handleDelete}
|
||||
onCancel={handleClose}
|
||||
okButtonColor="red"
|
||||
>
|
||||
<IconButton
|
||||
icon={<IconCozTrashCan />}
|
||||
type="primary"
|
||||
onClick={showDeleteConfirm}
|
||||
/>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.font-normal {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
padding: 12px 16px 14px;
|
||||
|
||||
border-top: 1px solid
|
||||
var(--light-usage-border-color-border, rgba(28, 29, 35, 12%));
|
||||
|
||||
&:first-child {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--light-usage-fill-color-fill-0, rgba(46, 47, 56, 4%));
|
||||
border-top: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
& + div {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
margin-right: 16px;
|
||||
|
||||
.icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: 0;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
width: 100%;
|
||||
padding-bottom: 2px;
|
||||
|
||||
.title_wrapper {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
|
||||
width: 0;
|
||||
|
||||
.title {
|
||||
.font-normal();
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--coz-fg-primary, #060709);
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
|
||||
.text {
|
||||
margin-left: 4px;
|
||||
color: var(--coz-fg-primary, #060709);
|
||||
.font-normal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
|
||||
.desc {
|
||||
.font-normal();
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--coz-fg-secondary, #06070980);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.creator {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
& img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
.font-normal();
|
||||
|
||||
max-width: 106px;
|
||||
margin-left: 4px;
|
||||
color: var(--coz-fg-secondary, #06070980);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.creator {
|
||||
background: unset;
|
||||
|
||||
&-avatar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
max-width: 70px;
|
||||
margin-left: 4px;
|
||||
.font-normal();
|
||||
|
||||
color: var(--coz-fg-secondary, #06070980);
|
||||
}
|
||||
}
|
||||
|
||||
.symbol {
|
||||
.font-normal();
|
||||
|
||||
margin: 0 8px;
|
||||
line-height: 16px;
|
||||
color: var(--coz-stroke-primary);
|
||||
// color: var(--coz-fg-dim, #06070966);
|
||||
}
|
||||
|
||||
.date {
|
||||
.font-normal();
|
||||
|
||||
line-height: 16px;
|
||||
color: var(--coz-fg-dim, #06070966);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 16px;
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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, { type FC, useContext } from 'react';
|
||||
|
||||
import isNil from 'lodash-es/isNil';
|
||||
import { unix } from 'dayjs';
|
||||
import classNames from 'classnames';
|
||||
import { OrderBy, WorkFlowListStatus } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozClockFill,
|
||||
IconCozCheckMarkCircleFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Typography, LoadingButton } from '@coze-arch/coze-design';
|
||||
import { Avatar, Image, Tooltip } from '@coze-arch/bot-semi';
|
||||
import { CheckType } from '@coze-arch/bot-api/workflow_api';
|
||||
import { type Int64, SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { LibButton } from '@/workflow-modal/content/card/lib-button';
|
||||
|
||||
import WorkflowModalContext from '../../workflow-modal-context';
|
||||
import { isSelectProjectCategory } from '../../utils';
|
||||
import {
|
||||
DataSourceType,
|
||||
MineActiveEnum,
|
||||
type ProductInfo,
|
||||
WorkflowCategory,
|
||||
type WorkflowInfo,
|
||||
WorkflowModalFrom,
|
||||
} from '../../type';
|
||||
import {
|
||||
useWorkflowAction,
|
||||
type WorkflowCardProps,
|
||||
} from '../../hooks/use-workflow-action';
|
||||
import { WorkflowParameters } from './parameters';
|
||||
import { DeleteButton } from './delete-button';
|
||||
import { WorkflowBotButton } from './bot-button';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const formatTime = (time?: Int64) => unix(Number(time)).format('YYYY-MM-DD');
|
||||
|
||||
const defaultWorkFlowList = [];
|
||||
|
||||
export const WorkflowCard: FC<WorkflowCardProps> = props => {
|
||||
const {
|
||||
data,
|
||||
workFlowList = defaultWorkFlowList,
|
||||
from,
|
||||
workflowNodes,
|
||||
dupText,
|
||||
itemShowDelete,
|
||||
} = props;
|
||||
const context = useContext(WorkflowModalContext);
|
||||
|
||||
const isProfessionalTemplate = (data as ProductInfo)?.meta_info
|
||||
?.is_professional;
|
||||
|
||||
const {
|
||||
dupWorkflowTpl,
|
||||
addWorkflow,
|
||||
removeWorkflow,
|
||||
deleteWorkflow,
|
||||
itemClick,
|
||||
} = useWorkflowAction({ ...props, isProfessionalTemplate });
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const StatusMap = {
|
||||
unpublished: {
|
||||
label: I18n.t('workflow_add_status_unpublished'),
|
||||
icon: <IconCozClockFill className="coz-fg-dim text-xs" />,
|
||||
},
|
||||
published: {
|
||||
label: I18n.t('workflow_add_status_published'),
|
||||
icon: (
|
||||
<IconCozCheckMarkCircleFill className="text-xs coz-fg-hglt-green" />
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const { orderBy, spaceType } = context;
|
||||
const {
|
||||
creator: creator,
|
||||
status,
|
||||
isSpaceWorkflow,
|
||||
workflowCategory,
|
||||
} = context.modalState;
|
||||
const isTeam = spaceType === SpaceType.Team;
|
||||
|
||||
function isTypeWorkflow(
|
||||
target: WorkflowInfo | ProductInfo,
|
||||
): target is WorkflowInfo {
|
||||
return context?.modalState.dataSourceType === DataSourceType.Workflow;
|
||||
}
|
||||
|
||||
const pluginId = isTypeWorkflow(data) ? data.plugin_id : '';
|
||||
const statusValue =
|
||||
!isNil(pluginId) && isSpaceWorkflow
|
||||
? StatusMap[pluginId === '0' ? 'unpublished' : 'published']
|
||||
: undefined;
|
||||
|
||||
const renderStatusValue = () => {
|
||||
// 添加项目里的工作流节点、官方示例不展示发布状态
|
||||
if (
|
||||
isSelectProjectCategory(context?.modalState) ||
|
||||
workflowCategory === WorkflowCategory.Example
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (statusValue) {
|
||||
return (
|
||||
<div className={classNames(styles.status)}>
|
||||
{statusValue.icon}
|
||||
<span className={styles.text}>{statusValue.label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const renderBottomLeftDesc = () => {
|
||||
// 商品底部
|
||||
if (!isTypeWorkflow(data)) {
|
||||
const timeRender = `${I18n.t('workflow_add_list_updated')} ${formatTime(
|
||||
data.meta_info.listed_at,
|
||||
)}`;
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<div className={styles.creator}>
|
||||
<Avatar
|
||||
className={styles['creator-avatar']}
|
||||
src={data.meta_info.user_info?.avatar_url}
|
||||
/>
|
||||
<Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
className={styles['creator-name']}
|
||||
>
|
||||
{data.meta_info.user_info?.name ??
|
||||
I18n.t('workflow_add_list_unknown')}
|
||||
</Text>
|
||||
<span className={styles.symbol}>|</span>
|
||||
</div>
|
||||
<span className={styles.date}>{timeRender}</span>
|
||||
|
||||
{(Number(data?.workflow_extra?.duplicate_count) || 0) > 0 ? (
|
||||
<>
|
||||
<span className={styles.symbol}>|</span>
|
||||
<Text className={styles.date}>
|
||||
{Number(data?.workflow_extra?.duplicate_count) || 0}{' '}
|
||||
{I18n.t('workflowstore_card_duplicate')}
|
||||
</Text>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 用户创建的,展示修改时间
|
||||
if (isSpaceWorkflow || workflowCategory === WorkflowCategory.Example) {
|
||||
const showCreator =
|
||||
(creator !== MineActiveEnum.Mine && isTeam) ||
|
||||
from === WorkflowModalFrom.ProjectImportLibrary;
|
||||
const timeRender =
|
||||
orderBy === OrderBy.CreateTime
|
||||
? `${I18n.t('workflow_add_list_created')} ${formatTime(
|
||||
data.create_time,
|
||||
)}`
|
||||
: status === WorkFlowListStatus.HadPublished
|
||||
? `${I18n.t('workflow_add_list_publised')} ${formatTime(
|
||||
data.update_time,
|
||||
)}`
|
||||
: `${I18n.t('workflow_add_list_updated')} ${formatTime(
|
||||
data.update_time,
|
||||
)}`;
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{showCreator ? (
|
||||
<div className={styles.creator}>
|
||||
<Avatar
|
||||
className={styles['creator-avatar']}
|
||||
src={data.creator?.avatar_url}
|
||||
/>
|
||||
<Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
className={styles['creator-name']}
|
||||
>
|
||||
{data.creator?.name ?? I18n.t('workflow_add_list_unknown')}
|
||||
</Text>
|
||||
<span className={styles.symbol}>|</span>
|
||||
</div>
|
||||
) : null}
|
||||
<span className={styles.date}>{timeRender}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 官方模板,展示创作者
|
||||
if (!isSpaceWorkflow) {
|
||||
return (
|
||||
<div className={styles.creator}>
|
||||
<Image
|
||||
preview={false}
|
||||
src={data.template_author_picture_url}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
<Text ellipsis={{ showTooltip: true }} className={styles.name}>
|
||||
{data.template_author_name || '-'}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const renderBotButton = () => {
|
||||
if (workflowCategory === WorkflowCategory.Example && isTypeWorkflow(data)) {
|
||||
const botAgentCheckResult = data?.check_result?.find(
|
||||
check => check.type === CheckType.BotAgent,
|
||||
);
|
||||
const ButtonContent = (
|
||||
<LoadingButton
|
||||
color="primary"
|
||||
data-testid="workflow.modal.add"
|
||||
disabled={botAgentCheckResult && !botAgentCheckResult?.is_pass}
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
await dupWorkflowTpl();
|
||||
}}
|
||||
>
|
||||
{dupText || I18n.t('workflowstore_duplicate_and_add')}
|
||||
</LoadingButton>
|
||||
);
|
||||
|
||||
if (
|
||||
botAgentCheckResult &&
|
||||
!botAgentCheckResult.is_pass &&
|
||||
botAgentCheckResult.reason
|
||||
) {
|
||||
return (
|
||||
<Tooltip content={botAgentCheckResult.reason}>
|
||||
{ButtonContent}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return ButtonContent;
|
||||
}
|
||||
|
||||
if (from === WorkflowModalFrom.ProjectImportLibrary) {
|
||||
return (
|
||||
<LibButton data={data as WorkflowInfo} onImport={props.onImport} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<WorkflowBotButton
|
||||
isAdded={workFlowList.some(
|
||||
workflow =>
|
||||
workflow.workflow_id === (data as WorkflowInfo)?.workflow_id,
|
||||
)}
|
||||
workflowNodes={workflowNodes}
|
||||
from={from}
|
||||
data={data as WorkflowInfo}
|
||||
onAdd={() => addWorkflow()}
|
||||
onRemove={() => {
|
||||
removeWorkflow();
|
||||
}}
|
||||
/>
|
||||
{itemShowDelete ? (
|
||||
<DeleteButton className="ml-[4px]" onDelete={deleteWorkflow} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
onClick={() => {
|
||||
itemClick();
|
||||
}}
|
||||
>
|
||||
<div className={styles.left}>
|
||||
<div className={styles.icon}>
|
||||
<Image
|
||||
preview={false}
|
||||
src={isTypeWorkflow(data) ? data.url : data.meta_info.icon_url}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title_wrapper}>
|
||||
<Text ellipsis={{ showTooltip: true }} className={styles.title}>
|
||||
{isTypeWorkflow(data) ? data.name : data.meta_info.name}
|
||||
</Text>
|
||||
{renderStatusValue()}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
style: {
|
||||
maxWidth: 600,
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
className={styles.desc}
|
||||
>
|
||||
{(isTypeWorkflow(data) ? data.desc : data.meta_info.description) ||
|
||||
''}
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<WorkflowParameters data={data} />
|
||||
{renderBottomLeftDesc()}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<div className={styles.buttons}>{renderBotButton()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 React from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { type WorkFlowModalModeProps, type WorkflowInfo } from '../../type';
|
||||
export type LibButtonProps = Pick<WorkFlowModalModeProps, 'onImport'> & {
|
||||
data?: WorkflowInfo;
|
||||
};
|
||||
export const LibButton: React.FC<LibButtonProps> = ({ data, onImport }) => {
|
||||
const isPublished = data?.plugin_id && data?.plugin_id !== '0';
|
||||
const content = (
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
<Button
|
||||
disabled={!isPublished}
|
||||
color="primary"
|
||||
data-testid="workflow.modal.add"
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
data?.workflow_id &&
|
||||
onImport?.({
|
||||
workflow_id: data.workflow_id,
|
||||
name: data.name || '',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{I18n.t('project_resource_modal_copy_to_project')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
if (isPublished) {
|
||||
return content;
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
position="top"
|
||||
content={I18n.t('project_toast_only_published_resources_can_be_imported')}
|
||||
>
|
||||
{content}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.font-normal {
|
||||
/* Paragraph/small/EN-Regular */
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: rgba(28, 31, 35, 80%);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 4px 0 8px;
|
||||
|
||||
.wrapper {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.popover_help_block {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
overflow: auto;
|
||||
|
||||
width: 260px;
|
||||
max-height: 400px;
|
||||
padding: 12px;
|
||||
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 10%), 0 0 1px 0 rgba(0, 0, 0, 30%);
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 4px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
.font-normal();
|
||||
|
||||
font-weight: 700;
|
||||
color: #1c1f23;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.type {
|
||||
.font-normal();
|
||||
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.required {
|
||||
.font-normal();
|
||||
|
||||
margin-left: 12px;
|
||||
color: #fc8800;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
.font-normal();
|
||||
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
color: rgba(28, 31, 35, 60%);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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, { useContext, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
PARAM_TYPE_LABEL_MAP,
|
||||
STRING_ASSIST_TYPE_LABEL_MAP,
|
||||
} from '@coze-workflow/base/types';
|
||||
import { InputType, PluginParamTypeFormat } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tag, Typography } from '@coze-arch/coze-design';
|
||||
import { type OverflowListProps } from '@coze-arch/bot-semi/OverflowList';
|
||||
import { Popover, OverflowList } from '@coze-arch/bot-semi';
|
||||
|
||||
import WorkflowModalContext from '../../../workflow-modal-context';
|
||||
import {
|
||||
DataSourceType,
|
||||
type ProductInfo,
|
||||
type WorkflowInfo,
|
||||
} from '../../../type';
|
||||
import { type WorkflowCardProps } from '../../../hooks/use-workflow-action';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
const getInputType = ({ type, format, assist_type }) => {
|
||||
let inputType = '';
|
||||
if (type) {
|
||||
if (
|
||||
type === InputType.String &&
|
||||
format === PluginParamTypeFormat.ImageUrl
|
||||
) {
|
||||
inputType = 'Image';
|
||||
} else if (type === InputType.String && assist_type) {
|
||||
inputType = STRING_ASSIST_TYPE_LABEL_MAP[assist_type];
|
||||
} else {
|
||||
inputType = PARAM_TYPE_LABEL_MAP[type as InputType];
|
||||
}
|
||||
}
|
||||
|
||||
return inputType;
|
||||
};
|
||||
|
||||
export interface WorkflowParameterItem {
|
||||
name?: string;
|
||||
desc?: string;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
type WorkflowParametersProps = Pick<WorkflowCardProps, 'data'> & {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
interface CustomParameterPopoverProps {
|
||||
items: WorkflowParameterItem[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const CustomParameterPopover: FC<CustomParameterPopoverProps> = ({
|
||||
children,
|
||||
items,
|
||||
}) => (
|
||||
<Popover
|
||||
stopPropagation
|
||||
position="top"
|
||||
spacing={0}
|
||||
content={
|
||||
<div className={styles.popover}>
|
||||
{items.map((item, index) => (
|
||||
<div key={`item${index}`} className={styles.item}>
|
||||
<div className={styles.header}>
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: item.name || '',
|
||||
position: 'top',
|
||||
style: {
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<span className={styles.name}>{item.name || '-'}</span>
|
||||
</Text>
|
||||
<span className={styles.type}>{item.type || '-'}</span>
|
||||
{Boolean(item.required) && (
|
||||
<span className={styles.required}>
|
||||
{I18n.t('workflow_add_parameter_required')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<Paragraph
|
||||
ellipsis={{
|
||||
rows: 2,
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: item.desc || '',
|
||||
position: 'top',
|
||||
style: {
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<span className={styles.footer}>{item.desc || '-'}</span>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
|
||||
export const WorkflowParameters: FC<WorkflowParametersProps> = ({
|
||||
data,
|
||||
style,
|
||||
className,
|
||||
}) => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
function isTypeWorkflow(
|
||||
target: WorkflowInfo | ProductInfo,
|
||||
): target is WorkflowInfo {
|
||||
return context?.modalState.dataSourceType === DataSourceType.Workflow;
|
||||
}
|
||||
const getParameters = (): Array<WorkflowParameterItem> => {
|
||||
// 这么拆分虽然有点冗余, 但是可以正确进行类型推导
|
||||
if (isTypeWorkflow(data)) {
|
||||
return (
|
||||
data.start_node?.node_param?.input_parameters?.map(item => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const inputType = getInputType(item as any);
|
||||
|
||||
return {
|
||||
name: item.name,
|
||||
desc: item.desc,
|
||||
required: item.required,
|
||||
type: inputType,
|
||||
};
|
||||
}) || []
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
data?.workflow_extra?.start_node?.node_param?.input_parameters?.map(
|
||||
item => {
|
||||
const inputType = getInputType({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
...(item as any),
|
||||
type: item.input_type,
|
||||
});
|
||||
|
||||
return {
|
||||
name: item.name,
|
||||
desc: item.desc,
|
||||
required: item.is_required,
|
||||
type: inputType,
|
||||
};
|
||||
},
|
||||
) || []
|
||||
);
|
||||
};
|
||||
|
||||
const items = getParameters();
|
||||
|
||||
const overflowRenderer: OverflowListProps['overflowRenderer'] = (
|
||||
overflowItems: Array<WorkflowParameterItem>,
|
||||
) => {
|
||||
const slicedItems = overflowItems.slice(overflowItems.length * -1);
|
||||
return slicedItems.length ? (
|
||||
<CustomParameterPopover items={items}>
|
||||
<div>
|
||||
<Tag style={{ flex: '0 0 auto' }} size="mini" color="primary">
|
||||
+{slicedItems.length}
|
||||
</Tag>
|
||||
</div>
|
||||
</CustomParameterPopover>
|
||||
) : null;
|
||||
};
|
||||
const visibleItemRenderer: OverflowListProps['visibleItemRenderer'] = (
|
||||
item: WorkflowParameterItem,
|
||||
) => (
|
||||
<CustomParameterPopover items={items}>
|
||||
<div style={{ marginRight: 8 }}>
|
||||
<Tag size="mini" color="primary">
|
||||
{item.name}
|
||||
</Tag>
|
||||
</div>
|
||||
</CustomParameterPopover>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)} style={style}>
|
||||
<div className={styles.wrapper}>
|
||||
<OverflowList
|
||||
items={items}
|
||||
overflowRenderer={overflowRenderer}
|
||||
visibleItemRenderer={visibleItemRenderer}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.scroll_load_more {
|
||||
padding: 8px;
|
||||
|
||||
&.empty {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.workflow-content {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.loading-more {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.spin {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.semi-spin-children {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* 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 */
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { type FC, useContext, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import groupBy from 'lodash-es/groupBy';
|
||||
import { useInViewport, useUpdateEffect } from 'ahooks';
|
||||
import { StandardNodeType } from '@coze-workflow/base/types';
|
||||
import { useWorkflowStore } from '@coze-workflow/base/store';
|
||||
import {
|
||||
WorkflowMode,
|
||||
WorkFlowType,
|
||||
Tag,
|
||||
BindBizType,
|
||||
} from '@coze-workflow/base/api';
|
||||
import { isGeneralWorkflow, workflowApi } from '@coze-workflow/base';
|
||||
import { SearchNoResult } from '@coze-studio/components/search-no-result';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozLoading } from '@coze-arch/coze-design/icons';
|
||||
import { Spin } from '@coze-arch/coze-design';
|
||||
import { UICompositionModalMain, UIEmpty } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
ProductEntityType,
|
||||
ProductListSource,
|
||||
} from '@coze-arch/bot-api/product_api';
|
||||
|
||||
import { useWorkflowList } from '@/hooks/use-workflow-list';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import { isSelectProjectCategory } from '../utils';
|
||||
import {
|
||||
DataSourceType,
|
||||
MineActiveEnum,
|
||||
type ProductInfo,
|
||||
WORKFLOW_LIST_STATUS_ALL,
|
||||
WorkflowCategory,
|
||||
type WorkflowInfo,
|
||||
WorkflowModalFrom,
|
||||
type WorkFlowModalModeProps,
|
||||
type WorkflowModalState,
|
||||
} from '../type';
|
||||
import { useWorkflowProductList } from '../hooks/use-workflow-product-list';
|
||||
import { useI18nText } from '../hooks/use-i18n-text';
|
||||
import { WorkflowCard } from './card';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
const { excludedWorkflowIds, from, projectId } = props;
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { i18nText, ModalI18nKey } = useI18nText();
|
||||
const {
|
||||
updatePageParam: updateWorkflowPageParam,
|
||||
isFetching,
|
||||
workflowList,
|
||||
fetchNextPage,
|
||||
loadingStatus,
|
||||
refetch,
|
||||
hasNextPage,
|
||||
handleDelete,
|
||||
} = useWorkflowList({
|
||||
pageSize: 10,
|
||||
enabled: context?.modalState.dataSourceType === DataSourceType.Workflow,
|
||||
from,
|
||||
fetchWorkflowListApi:
|
||||
context?.modalState?.workflowCategory !== WorkflowCategory.Example
|
||||
? workflowApi.GetWorkFlowList.bind(workflowApi)
|
||||
: workflowApi.GetExampleWorkFlowList.bind(workflowApi),
|
||||
});
|
||||
|
||||
const {
|
||||
workflowProductList,
|
||||
updatePageParam: updateProductPageParam,
|
||||
fetchNextPage: fetchNextProductPage,
|
||||
isFetching: productIsFetching,
|
||||
loadingStatus: productLoadingStatus,
|
||||
hasNextPage: productHasNextPage,
|
||||
copyProduct,
|
||||
} = useWorkflowProductList({
|
||||
pageSize: 10,
|
||||
enabled: context?.modalState.dataSourceType === DataSourceType.Product,
|
||||
});
|
||||
// 转换筛选参数
|
||||
useEffect(() => {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { modalState, flowMode } = context;
|
||||
|
||||
if (modalState.dataSourceType === DataSourceType.Workflow) {
|
||||
const isAddProjectWorkflow = isSelectProjectCategory(modalState);
|
||||
let targetTags;
|
||||
if (!modalState.isSpaceWorkflow) {
|
||||
if (modalState.query) {
|
||||
targetTags = 1;
|
||||
} else {
|
||||
targetTags = modalState.workflowTag;
|
||||
}
|
||||
}
|
||||
let type: WorkFlowType;
|
||||
if (modalState.workflowCategory === WorkflowCategory.Example) {
|
||||
targetTags = Tag.All;
|
||||
type = WorkFlowType.GuanFang;
|
||||
} else {
|
||||
type = modalState.isSpaceWorkflow
|
||||
? WorkFlowType.User
|
||||
: WorkFlowType.GuanFang;
|
||||
}
|
||||
let status: WorkflowModalState['status'] | undefined = undefined;
|
||||
if (modalState.isSpaceWorkflow) {
|
||||
status =
|
||||
// isAddProjectWorkflow:项目里添加子工作流,没有发布状态概念,筛选状态传 undefined
|
||||
modalState.status === WORKFLOW_LIST_STATUS_ALL || isAddProjectWorkflow
|
||||
? undefined
|
||||
: modalState.status;
|
||||
}
|
||||
updateWorkflowPageParam({
|
||||
space_id: context.spaceId,
|
||||
flow_mode: modalState.listFlowMode,
|
||||
name: modalState.query,
|
||||
order_by: modalState.isSpaceWorkflow ? context.orderBy : undefined,
|
||||
status,
|
||||
type,
|
||||
project_id: isSelectProjectCategory(modalState) ? projectId : undefined,
|
||||
login_user_create: modalState.isSpaceWorkflow
|
||||
? modalState.creator === MineActiveEnum.Mine
|
||||
: undefined,
|
||||
tags: targetTags,
|
||||
bind_biz_type: context.bindBizType,
|
||||
bind_biz_id: context.bindBizId,
|
||||
});
|
||||
} else {
|
||||
if (modalState.productCategory === 'recommend') {
|
||||
updateProductPageParam({
|
||||
keyword: modalState.query,
|
||||
sort_type: modalState.sortType,
|
||||
category_id: undefined,
|
||||
source: ProductListSource.Recommend,
|
||||
entity_type: isGeneralWorkflow(flowMode)
|
||||
? ProductEntityType.WorkflowTemplateV2
|
||||
: ProductEntityType.ImageflowTemplateV2,
|
||||
});
|
||||
} else if (modalState.productCategory === 'all') {
|
||||
updateProductPageParam({
|
||||
keyword: modalState.query,
|
||||
sort_type: modalState.sortType,
|
||||
category_id: undefined,
|
||||
source: undefined,
|
||||
entity_type: isGeneralWorkflow(flowMode)
|
||||
? ProductEntityType.WorkflowTemplateV2
|
||||
: ProductEntityType.ImageflowTemplateV2,
|
||||
});
|
||||
} else {
|
||||
updateProductPageParam({
|
||||
keyword: modalState.query,
|
||||
sort_type: modalState.sortType,
|
||||
category_id: modalState.productCategory,
|
||||
source: undefined,
|
||||
entity_type: isGeneralWorkflow(flowMode)
|
||||
? ProductEntityType.WorkflowTemplateV2
|
||||
: ProductEntityType.ImageflowTemplateV2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [context]);
|
||||
|
||||
const { nodes } = useWorkflowStore(
|
||||
useShallow(state => ({
|
||||
nodes: state.nodes,
|
||||
})),
|
||||
);
|
||||
|
||||
// 子流程节点 map,例如 { 'workflowId': [node1, node2, ...] }
|
||||
const workflowNodesMap = useMemo(() => {
|
||||
const subFlowNodes = nodes.filter(
|
||||
v => v.type === StandardNodeType.SubWorkflow,
|
||||
);
|
||||
const groups = groupBy(
|
||||
subFlowNodes,
|
||||
item => item?.data?.inputs?.workflowId,
|
||||
);
|
||||
return groups;
|
||||
}, [nodes]);
|
||||
|
||||
const targetWorkflowList = useMemo(() => {
|
||||
if (!excludedWorkflowIds) {
|
||||
return workflowList;
|
||||
}
|
||||
return workflowList.filter(
|
||||
v => !excludedWorkflowIds.includes(v.workflow_id || ''),
|
||||
);
|
||||
}, [excludedWorkflowIds, workflowList]);
|
||||
|
||||
/** scroll的container */
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
/** 监听触底的observer */
|
||||
const intersectionObserverDom = useRef<HTMLDivElement>(null);
|
||||
// 是否触底
|
||||
const [inViewPort] = useInViewport(intersectionObserverDom, {
|
||||
root: () => scrollContainerRef.current,
|
||||
threshold: 0.8,
|
||||
});
|
||||
|
||||
// 首次effect不执行,这个是切换状态的effect
|
||||
useUpdateEffect(() => {
|
||||
// 当筛选项改变时,回到顶部
|
||||
if (scrollContainerRef.current) {
|
||||
scrollContainerRef.current.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
// 只要是query中非page改变,就执行此effect
|
||||
}, [context?.modalState]);
|
||||
|
||||
// 获取下一页逻辑
|
||||
useEffect(() => {
|
||||
if (!inViewPort) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dataSourceType === DataSourceType.Workflow) {
|
||||
if (loadingStatus !== 'success' || isFetching || !hasNextPage) {
|
||||
return;
|
||||
}
|
||||
fetchNextPage();
|
||||
} else {
|
||||
if (
|
||||
productLoadingStatus !== 'success' ||
|
||||
productIsFetching ||
|
||||
!productHasNextPage
|
||||
) {
|
||||
return;
|
||||
}
|
||||
fetchNextProductPage();
|
||||
}
|
||||
}, [
|
||||
inViewPort,
|
||||
loadingStatus,
|
||||
isFetching,
|
||||
hasNextPage,
|
||||
productLoadingStatus,
|
||||
productIsFetching,
|
||||
productHasNextPage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!context?.modalState.isSpaceWorkflow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const visibilityChangeHandler = () => {
|
||||
const needRefresh = document.visibilityState === 'visible';
|
||||
if (needRefresh) {
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', visibilityChangeHandler);
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', visibilityChangeHandler);
|
||||
};
|
||||
}, [context?.modalState.isSpaceWorkflow, refetch]);
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isTypeWorkflow(
|
||||
_target: WorkflowInfo | ProductInfo,
|
||||
): _target is WorkflowInfo {
|
||||
return context?.modalState.dataSourceType === DataSourceType.Workflow;
|
||||
}
|
||||
|
||||
const { modalState, flowMode } = context;
|
||||
const { dataSourceType } = context.modalState;
|
||||
|
||||
const targetLoadingStatus =
|
||||
dataSourceType === DataSourceType.Workflow
|
||||
? loadingStatus
|
||||
: productLoadingStatus;
|
||||
const targetHasNextPage =
|
||||
dataSourceType === DataSourceType.Workflow
|
||||
? hasNextPage
|
||||
: productHasNextPage;
|
||||
const targetList =
|
||||
dataSourceType === DataSourceType.Workflow
|
||||
? targetWorkflowList
|
||||
: workflowProductList;
|
||||
|
||||
const isAgentWorkflow = from === WorkflowModalFrom.WorkflowAgent;
|
||||
const renderEmpty = () => {
|
||||
const isNotFound = Boolean(modalState.query);
|
||||
if (flowMode === WorkflowMode.SceneFlow) {
|
||||
return (
|
||||
<SearchNoResult
|
||||
title={i18nText(ModalI18nKey.CreatedListEmptyTitle)}
|
||||
type={'social-scene-flow'}
|
||||
isNotFound={isNotFound}
|
||||
notFound={isNotFound ? i18nText(ModalI18nKey.ListEmptyTitle) : ''}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<UIEmpty
|
||||
isNotFound={isNotFound}
|
||||
notFound={{
|
||||
title: i18nText(ModalI18nKey.ListEmptyTitle),
|
||||
}}
|
||||
empty={{
|
||||
title: i18nText(ModalI18nKey.CreatedListEmptyTitle),
|
||||
description: i18nText(ModalI18nKey.CreatedListEmptyDescription),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<UICompositionModalMain>
|
||||
<Spin
|
||||
spinning={targetLoadingStatus === 'pending'}
|
||||
wrapperClassName={s.spin}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>
|
||||
{/* Workflow as agent 支持添加带自定义入参的对话流 */}
|
||||
{/* {isAgentWorkflow ? (
|
||||
<div className="coz-mg-hglt px-[36px] py-[8px] mx-[24px] my-[0] rounded-[8px]">
|
||||
{I18n.t('wf_chatflow_133')}
|
||||
</div>
|
||||
) : null} */}
|
||||
<div
|
||||
className={`${s['workflow-content']} new-workflow-modal-content`}
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
{/* 内容渲染 */}
|
||||
{targetLoadingStatus !== 'pending' && targetList.length > 0 && (
|
||||
<UICompositionModalMain.Content
|
||||
style={{
|
||||
minHeight: '100%',
|
||||
paddingBottom: isAgentWorkflow ? '60px' : 0,
|
||||
}}
|
||||
>
|
||||
{/* 数据呈现样式, 列表样式/卡片样式. 展示图像流商品列表时使用卡片样式 */}
|
||||
<>
|
||||
{targetList.map((item: WorkflowInfo | ProductInfo) => (
|
||||
<WorkflowCard
|
||||
key={
|
||||
isTypeWorkflow(item)
|
||||
? item.workflow_id
|
||||
: item.meta_info.entity_id
|
||||
}
|
||||
data={item}
|
||||
itemShowDelete={
|
||||
context?.bindBizType === BindBizType.DouYinBot
|
||||
}
|
||||
workflowNodes={
|
||||
isTypeWorkflow(item)
|
||||
? (workflowNodesMap[item.workflow_id || ''] ?? [])
|
||||
: []
|
||||
}
|
||||
handleDeleteWorkflow={handleDelete}
|
||||
copyProductHandle={copyProduct}
|
||||
{...props}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
{targetHasNextPage ? (
|
||||
<div ref={intersectionObserverDom}>
|
||||
<div className={s['loading-more']}>
|
||||
<IconCozLoading className="animate-spin coz-fg-dim mr-[4px]" />
|
||||
<div>{I18n.t('Loading')}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</UICompositionModalMain.Content>
|
||||
)}
|
||||
{targetLoadingStatus === 'success' &&
|
||||
targetList.length === 0 &&
|
||||
renderEmpty()}
|
||||
</div>
|
||||
</Spin>
|
||||
</UICompositionModalMain>
|
||||
);
|
||||
};
|
||||
|
||||
export { WorkflowModalContent };
|
||||
@@ -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 { type FC, useContext } from 'react';
|
||||
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { Banner, Select } from '@coze-arch/coze-design';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import {
|
||||
WORKFLOW_LIST_STATUS_ALL,
|
||||
type WorkFlowModalModeProps,
|
||||
type WorkflowModalState,
|
||||
} from '../type';
|
||||
import { CreateWorkflowBtn } from '../sider/create-workflow-btn';
|
||||
import styles from '../index.module.less';
|
||||
import { useWorkflowSearch } from '../hooks/use-workflow-search';
|
||||
import {
|
||||
ModalI18nKey,
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP,
|
||||
} from '../hooks/use-i18n-text';
|
||||
import { statusOptions } from '../constants';
|
||||
|
||||
const getStatusOptions = (showAll?: boolean) =>
|
||||
showAll
|
||||
? statusOptions
|
||||
: statusOptions.filter(item => item.value !== WORKFLOW_LIST_STATUS_ALL);
|
||||
|
||||
const WorkflowModalFilterForDouyin: FC<WorkFlowModalModeProps> = props => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const searchNode = useWorkflowSearch();
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { updateModalState, flowMode } = context;
|
||||
const { status } = context.modalState;
|
||||
const { filterOptionShowAll = false } = props;
|
||||
|
||||
const title = I18n.t(
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP[flowMode]?.[
|
||||
ModalI18nKey.Title
|
||||
] as I18nKeysNoOptionsType,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center w-full justify-between mt-[-4px]">
|
||||
<div className="flex items-center gap-[24px]">
|
||||
<div className={styles.titleForAvatar}>{title}</div>
|
||||
|
||||
<Select
|
||||
insetLabel={I18n.t('publish_list_header_status')}
|
||||
showClear={false}
|
||||
value={status}
|
||||
optionList={getStatusOptions(filterOptionShowAll)}
|
||||
onChange={value => {
|
||||
updateModalState({
|
||||
status: value as WorkflowModalState['status'],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-[12px] mr-[12px]">
|
||||
<div className="w-[208px]">{searchNode}</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<CreateWorkflowBtn
|
||||
onCreateSuccess={props.onCreateSuccess}
|
||||
nameValidators={props.nameValidators}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Banner
|
||||
type="info"
|
||||
className="mt-[16px] pt-[7px] pb-[7px] rounded-lg"
|
||||
description={I18n.t('dy_avatar_add_workflow_limit')}
|
||||
closeIcon={null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { WorkflowModalFilterForDouyin };
|
||||
@@ -0,0 +1,32 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.header {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.workflow-status-radio {
|
||||
:global(.semi-radio-buttonRadioGroup) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:global(.semi-radio-buttonRadioGroup:first-child .semi-radio-addon-buttonRadio) {
|
||||
border-radius: 0;
|
||||
border-right: 1px solid var(--semi-color-fill-2);
|
||||
}
|
||||
|
||||
:global(.semi-radio-addon-buttonRadio) {
|
||||
font-size: 14px;
|
||||
padding: 0 16px;
|
||||
color: rgb(28 31 35 / 40%);
|
||||
}
|
||||
|
||||
:global(.semi-radio-addon-buttonRadio-hover) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
:global(.semi-radio-addon-buttonRadio-checked) {
|
||||
background-color: transparent;
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23)
|
||||
}
|
||||
}
|
||||
@@ -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 { type FC, useContext, useMemo } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UISelect } from '@coze-arch/bot-semi';
|
||||
import { WorkflowMode } from '@coze-arch/bot-api/workflow_api';
|
||||
import { SpaceType } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import { isSelectProjectCategory } from '../utils';
|
||||
import {
|
||||
DataSourceType,
|
||||
MineActiveEnum,
|
||||
WORKFLOW_LIST_STATUS_ALL,
|
||||
type WorkFlowModalModeProps,
|
||||
type WorkflowModalState,
|
||||
WorkflowCategory,
|
||||
} from '../type';
|
||||
import { CreateWorkflowBtn } from '../sider/create-workflow-btn';
|
||||
import { useI18nText } from '../hooks/use-i18n-text';
|
||||
import { statusOptions } from '../constants';
|
||||
import { SortTypeSelect } from './sort-type-select';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const getStatusOptions = (showAll?: boolean) =>
|
||||
showAll
|
||||
? statusOptions
|
||||
: statusOptions.filter(item => item.value !== WORKFLOW_LIST_STATUS_ALL);
|
||||
const flowModeOptions = [
|
||||
{
|
||||
label: I18n.t('filter_all'),
|
||||
value: WorkflowMode.All,
|
||||
},
|
||||
{
|
||||
label: I18n.t('library_resource_type_workflow'),
|
||||
value: WorkflowMode.Workflow,
|
||||
},
|
||||
{
|
||||
label: I18n.t('wf_chatflow_76'),
|
||||
value: WorkflowMode.ChatFlow,
|
||||
},
|
||||
].filter(item => {
|
||||
// 社区版本暂不支持对话流
|
||||
if (item.value === WorkflowMode.ChatFlow && IS_OPEN_SOURCE) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const WorkflowModalFilter: FC<WorkFlowModalModeProps> = props => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { i18nText, ModalI18nKey } = useI18nText();
|
||||
const scopeOptions = useMemo(() => {
|
||||
if (!context) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: i18nText(ModalI18nKey.TabAll),
|
||||
value: MineActiveEnum.All,
|
||||
},
|
||||
{
|
||||
label: i18nText(ModalI18nKey.TabMine),
|
||||
value: MineActiveEnum.Mine,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { spaceType, updateModalState, modalState } = context;
|
||||
const {
|
||||
dataSourceType,
|
||||
isSpaceWorkflow,
|
||||
status,
|
||||
creator,
|
||||
listFlowMode,
|
||||
workflowCategory,
|
||||
} = context.modalState;
|
||||
const {
|
||||
hideSider,
|
||||
hiddenCreate,
|
||||
filterOptionShowAll = false,
|
||||
hideCreatorSelect = false,
|
||||
hiddenListFlowModeFilter = false,
|
||||
} = props;
|
||||
|
||||
const isExampleWorkflow = workflowCategory === WorkflowCategory.Example;
|
||||
const isAddProjectWorkflow = isSelectProjectCategory(modalState);
|
||||
return (
|
||||
<div
|
||||
className={`${styles.header} ${
|
||||
hideSider ? 'w-full justify-between' : ''
|
||||
}`}
|
||||
>
|
||||
{(isSpaceWorkflow || isExampleWorkflow) &&
|
||||
dataSourceType === DataSourceType.Workflow ? (
|
||||
<>
|
||||
{!hiddenListFlowModeFilter ? (
|
||||
<UISelect
|
||||
label={I18n.t('Type')}
|
||||
showClear={false}
|
||||
value={listFlowMode}
|
||||
optionList={flowModeOptions}
|
||||
onChange={value => {
|
||||
updateModalState({
|
||||
listFlowMode: value as WorkflowMode,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{isAddProjectWorkflow || isExampleWorkflow ? null : (
|
||||
<UISelect
|
||||
label={I18n.t('publish_list_header_status')}
|
||||
showClear={false}
|
||||
value={status}
|
||||
optionList={getStatusOptions(filterOptionShowAll)}
|
||||
onChange={value => {
|
||||
updateModalState({
|
||||
status: value as WorkflowModalState['status'],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{spaceType === SpaceType.Team &&
|
||||
!hideCreatorSelect &&
|
||||
!isExampleWorkflow && (
|
||||
<UISelect
|
||||
label={I18n.t('Creator')}
|
||||
showClear={false}
|
||||
value={creator}
|
||||
onChange={value => {
|
||||
updateModalState({ creator: value as MineActiveEnum });
|
||||
}}
|
||||
optionList={scopeOptions}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
{hideSider ? (
|
||||
<div className="flex items-center mr-[-24px]">
|
||||
{!hiddenCreate && (
|
||||
<CreateWorkflowBtn
|
||||
className="ml-12px"
|
||||
onCreateSuccess={props.onCreateSuccess}
|
||||
nameValidators={props.nameValidators}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{dataSourceType === DataSourceType.Product ? <SortTypeSelect /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { WorkflowModalFilter };
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 { useContext } from 'react';
|
||||
|
||||
import { SortType } from '@coze-arch/idl/product_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UISelect } from '@coze-arch/bot-semi';
|
||||
|
||||
import WorkflowModalContext, {
|
||||
type WorkflowModalContextValue,
|
||||
} from '../workflow-modal-context';
|
||||
|
||||
const defaultDataSource = [
|
||||
{
|
||||
label: I18n.t('Popular', {}, '最受欢迎'),
|
||||
value: SortType.Heat,
|
||||
},
|
||||
{
|
||||
label: I18n.t('mkpl_published', {}, '最近发布'),
|
||||
value: SortType.Newest,
|
||||
},
|
||||
];
|
||||
|
||||
const queryDataSource = [
|
||||
{
|
||||
label: I18n.t('store_search_rank_default', {}, '相关性'),
|
||||
value: SortType.Relative,
|
||||
},
|
||||
].concat(defaultDataSource);
|
||||
|
||||
export const SortTypeSelect = () => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { updateModalState } = context as WorkflowModalContextValue;
|
||||
const { query, sortType } = context?.modalState || {};
|
||||
|
||||
const handleOnChange = value => {
|
||||
updateModalState({ sortType: value as SortType });
|
||||
};
|
||||
|
||||
return (
|
||||
<UISelect
|
||||
label={I18n.t('Sort')}
|
||||
value={sortType}
|
||||
optionList={query ? queryDataSource : defaultDataSource}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 { useContext, type ReactNode } from 'react';
|
||||
|
||||
import { WorkflowMode } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
|
||||
export enum ModalI18nKey {
|
||||
Title = 'title',
|
||||
NavigationMy = 'navigation_my',
|
||||
NavigationTeam = 'navigation_team',
|
||||
NavigationExplore = 'navigation_explore',
|
||||
TabAll = 'tab_all',
|
||||
TabMine = 'tab_mine',
|
||||
ListEmptyTitle = 'list_empty_title',
|
||||
CreatedListEmptyTitle = 'created_list_empty_title',
|
||||
CreatedListEmptyDescription = 'created_list_empty_description',
|
||||
NavigationCreate = 'navigation_create',
|
||||
ListError = 'list_error',
|
||||
ListItemRemove = 'list_item_remove',
|
||||
ListItemRemoveConfirmTitle = 'list_item_remove_confirm_title',
|
||||
ListItemRemoveConfirmDescription = 'list_item_remove_confirm_description',
|
||||
}
|
||||
|
||||
/**
|
||||
* i18n 文案有变量时使用这个结构
|
||||
*/
|
||||
interface I18nKeyWithOptions {
|
||||
/* i18n 文案的 key */
|
||||
key: string;
|
||||
/* 变量参数对象 */
|
||||
options?: Record<string, ReactNode>;
|
||||
}
|
||||
export type I18nKey = string | I18nKeyWithOptions;
|
||||
|
||||
// 用于存放 workflow 和 imageflow 的各个 i18n 文案的 key
|
||||
export const WORKFLOW_MODAL_I18N_KEY_MAP: {
|
||||
[WorkflowMode.Workflow]: Record<ModalI18nKey, I18nKey>;
|
||||
[WorkflowMode.Imageflow]: Record<ModalI18nKey, I18nKey>;
|
||||
[WorkflowMode.SceneFlow]: Record<ModalI18nKey, I18nKey>;
|
||||
} = {
|
||||
[WorkflowMode.Workflow]: {
|
||||
[ModalI18nKey.Title]: 'workflow_add_title',
|
||||
[ModalI18nKey.NavigationMy]: 'workflow_add_navigation_my',
|
||||
[ModalI18nKey.NavigationTeam]: 'workflow_add_navigation_team',
|
||||
[ModalI18nKey.NavigationExplore]: 'workflow_add_navigation_explore',
|
||||
[ModalI18nKey.TabAll]: 'workflow_add_created_tab_all',
|
||||
[ModalI18nKey.TabMine]: 'workflow_add_created_tab_mine',
|
||||
[ModalI18nKey.ListEmptyTitle]: 'workflow_add_list_empty_title',
|
||||
[ModalI18nKey.CreatedListEmptyTitle]:
|
||||
'workflow_add_created_list_empty_title',
|
||||
[ModalI18nKey.CreatedListEmptyDescription]:
|
||||
'workflow_add_created_list_empty_description',
|
||||
[ModalI18nKey.NavigationCreate]: 'workflow_add_create_library',
|
||||
[ModalI18nKey.ListError]: 'workflow_add_list_added_id_empty',
|
||||
[ModalI18nKey.ListItemRemove]: 'workflow_add_list_remove',
|
||||
[ModalI18nKey.ListItemRemoveConfirmTitle]:
|
||||
'workflow_add_remove_confirm_title',
|
||||
[ModalI18nKey.ListItemRemoveConfirmDescription]:
|
||||
'workflow_add_remove_confirm_content',
|
||||
},
|
||||
[WorkflowMode.Imageflow]: {
|
||||
[ModalI18nKey.Title]: 'imageflow_add',
|
||||
[ModalI18nKey.NavigationMy]: 'workflow_add_navigation_my',
|
||||
[ModalI18nKey.NavigationTeam]: 'imageflow_workspace2',
|
||||
[ModalI18nKey.NavigationExplore]: 'imageflow_explore',
|
||||
[ModalI18nKey.TabAll]: 'workflow_add_created_tab_all',
|
||||
[ModalI18nKey.TabMine]: 'workflow_add_created_tab_mine',
|
||||
[ModalI18nKey.ListEmptyTitle]: 'imageflow_detail_no_search_result',
|
||||
[ModalI18nKey.CreatedListEmptyTitle]: 'imageflow_title',
|
||||
[ModalI18nKey.CreatedListEmptyDescription]: 'imageflow_title_description',
|
||||
[ModalI18nKey.NavigationCreate]: 'imageflow_create',
|
||||
[ModalI18nKey.ListError]: 'imageflow_add_toast_error',
|
||||
[ModalI18nKey.ListItemRemove]: 'workflow_add_list_remove',
|
||||
[ModalI18nKey.ListItemRemoveConfirmTitle]:
|
||||
'workflow_add_remove_confirm_title',
|
||||
[ModalI18nKey.ListItemRemoveConfirmDescription]:
|
||||
'workflow_add_remove_confirm_content',
|
||||
},
|
||||
[WorkflowMode.SceneFlow]: {
|
||||
[ModalI18nKey.Title]: 'scene_workflow_popup_title',
|
||||
[ModalI18nKey.NavigationMy]: 'workflow_add_navigation_my',
|
||||
[ModalI18nKey.NavigationTeam]: 'workflow_add_navigation_team',
|
||||
[ModalI18nKey.NavigationExplore]: 'workflow_add_navigation_explore',
|
||||
[ModalI18nKey.TabAll]: 'workflow_add_created_tab_all',
|
||||
[ModalI18nKey.TabMine]: 'workflow_add_created_tab_mine',
|
||||
[ModalI18nKey.ListEmptyTitle]: 'scene_workflow_popup_search_empty',
|
||||
[ModalI18nKey.CreatedListEmptyTitle]: 'scene_workflow_popup_list_empty',
|
||||
// 场景工作流没有描述
|
||||
[ModalI18nKey.CreatedListEmptyDescription]: '',
|
||||
[ModalI18nKey.NavigationCreate]: 'workflow_add_navigation_create',
|
||||
[ModalI18nKey.ListError]: 'workflow_add_list_added_id_empty',
|
||||
[ModalI18nKey.ListItemRemove]: {
|
||||
key: 'scene_workflow_delete_workflow_button',
|
||||
options: { source: I18n.t('scene_mkpl_search_title') },
|
||||
},
|
||||
[ModalI18nKey.ListItemRemoveConfirmTitle]: {
|
||||
key: 'scene_workflow_delete_workflow_popup_title',
|
||||
options: { source: I18n.t('scene_mkpl_search_title') },
|
||||
},
|
||||
[ModalI18nKey.ListItemRemoveConfirmDescription]: {
|
||||
key: 'scene_workflow_delete_workflow_popup_subtitle',
|
||||
options: { source: I18n.t('scene_mkpl_search_title') },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 自动根据 flowMode 返回对应国际化文案
|
||||
*/
|
||||
export function useI18nText() {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const flowMode = context?.flowMode ?? WorkflowMode.Workflow;
|
||||
|
||||
const i18nText = (key: ModalI18nKey) => {
|
||||
const i18nKey =
|
||||
context?.i18nMap?.[key] ||
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP[flowMode]?.[key] ||
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP[WorkflowMode.Workflow]?.[key];
|
||||
const finalKey = typeof i18nKey === 'string' ? i18nKey : i18nKey.key;
|
||||
return I18n.t(finalKey || '', i18nKey?.options);
|
||||
};
|
||||
|
||||
return { i18nText, ModalI18nKey };
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
/*
|
||||
* 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 { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import { isBoolean } from 'lodash-es';
|
||||
import { type WorkflowNodeJSON } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
useCozeProRightsStore,
|
||||
getIsCozePro,
|
||||
} from '@coze-workflow/resources-adapter';
|
||||
import {
|
||||
type DeleteType,
|
||||
type Workflow,
|
||||
workflowApi,
|
||||
WorkflowMode,
|
||||
} from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { ProductEntityType } from '@coze-arch/bot-api/product_api';
|
||||
import { PluginType } from '@coze-arch/bot-api/developer_api';
|
||||
import { PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
import { Button, Space, Toast, Typography } from '@coze-arch/coze-design';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import { isSelectProjectCategory } from '../utils';
|
||||
import {
|
||||
type BotPluginWorkFlowItem,
|
||||
DataSourceType,
|
||||
type ProductInfo,
|
||||
type WorkflowInfo,
|
||||
type WorkflowItemType,
|
||||
type WorkFlowModalModeProps,
|
||||
} from '../type';
|
||||
import { reporter, wait } from '../../utils';
|
||||
|
||||
/**
|
||||
* 特殊错误码
|
||||
* - 788664021: 由于模型原因,暂不支持复制商店中的工作流
|
||||
* - 788664024: 模板未购买,请前往模板详情页购买后再复制
|
||||
*/
|
||||
const copyProductErrorCodes = ['788664021', '788664024'];
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export interface WorkflowCardProps extends WorkFlowModalModeProps {
|
||||
data: WorkflowInfo | ProductInfo;
|
||||
workflowNodes?: WorkflowNodeJSON[];
|
||||
copyProductHandle: (
|
||||
item: ProductInfo,
|
||||
targetSpaceId: string,
|
||||
) => Promise<{
|
||||
workflowId: string;
|
||||
pluginId: string;
|
||||
}>;
|
||||
/**
|
||||
* workflow 删除按钮点击时触发的 handler
|
||||
* @param row
|
||||
*/
|
||||
handleDeleteWorkflow?: (row: WorkflowInfo) => Promise<{
|
||||
canDelete: boolean;
|
||||
deleteType: DeleteType;
|
||||
handleDelete:
|
||||
| ((params?: { needDeleteBlockwise: boolean }) => Promise<void>)
|
||||
| undefined;
|
||||
}>;
|
||||
/**
|
||||
* 是否为专业版特供
|
||||
*/
|
||||
isProfessionalTemplate?: boolean;
|
||||
}
|
||||
|
||||
interface UseWorkflowActionReturn {
|
||||
/** 复制官方流程模板 */
|
||||
dupWorkflowTpl: () => Promise<void>;
|
||||
/** 复制流程商品 */
|
||||
dupProduct: () => Promise<void>;
|
||||
/** 添加流程 */
|
||||
addWorkflow: () => Promise<boolean>;
|
||||
/** 移除流程 */
|
||||
removeWorkflow: () => void;
|
||||
/**
|
||||
* 删除流程
|
||||
*/
|
||||
deleteWorkflow: () => Promise<void>;
|
||||
/** 流程项点击 */
|
||||
itemClick: () => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
export function useWorkflowAction({
|
||||
data,
|
||||
workFlowList,
|
||||
copyProductHandle,
|
||||
onWorkFlowListChange,
|
||||
onAdd,
|
||||
onRemove,
|
||||
onItemClick,
|
||||
onDupSuccess,
|
||||
onDelete,
|
||||
handleDeleteWorkflow,
|
||||
isProfessionalTemplate,
|
||||
}: WorkflowCardProps): UseWorkflowActionReturn {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { bot_id: botId } = useParams<DynamicParams>();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isCozePro = useCozeProRightsStore(state =>
|
||||
getIsCozePro(state?.rightsInfo),
|
||||
);
|
||||
useEffect(() => {
|
||||
useCozeProRightsStore.getState().getRights();
|
||||
}, []);
|
||||
|
||||
async function getWorkflowItem(config: {
|
||||
spaceId?: string;
|
||||
workflowId?: string;
|
||||
pluginId?: string;
|
||||
isImageflow: boolean;
|
||||
flowMode?: WorkflowMode;
|
||||
}): Promise<BotPluginWorkFlowItem> {
|
||||
if (isSelectProjectCategory(context?.modalState)) {
|
||||
return getProjectWorkflow(config);
|
||||
}
|
||||
return getWorkflowItemByPluginId(config);
|
||||
}
|
||||
|
||||
async function getProjectWorkflow(config: {
|
||||
workflowId?: string;
|
||||
spaceId?: string;
|
||||
}): Promise<BotPluginWorkFlowItem> {
|
||||
if (!config.spaceId || !config.workflowId) {
|
||||
throw new CustomError('normal_error', 'getProjectWorkflow: empty id');
|
||||
}
|
||||
const resp = await workflowApi.GetWorkflowDetail(
|
||||
{
|
||||
space_id: config.spaceId,
|
||||
workflow_ids: [config.workflowId],
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
// 先获取工作流的信息
|
||||
const workflowInfos = resp.data ?? [];
|
||||
if (!workflowInfos?.length) {
|
||||
Toast.error(I18n.t('workflow_add_list_added_id_empty'));
|
||||
throw new CustomError('normal_error', 'project workflow list no item');
|
||||
}
|
||||
return workflowInfos.at(0) as BotPluginWorkFlowItem;
|
||||
}
|
||||
/**
|
||||
* 通过插件 ID 构造新 workflowItem
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
async function getWorkflowItemByPluginId(config: {
|
||||
spaceId?: string;
|
||||
workflowId?: string;
|
||||
pluginId?: string;
|
||||
isImageflow: boolean;
|
||||
flowMode?: WorkflowMode;
|
||||
}) {
|
||||
if (!config.spaceId || !config.workflowId || !config.pluginId) {
|
||||
throw new CustomError(
|
||||
'normal_error',
|
||||
'getWorkflowItemByPluginId: empty id',
|
||||
);
|
||||
}
|
||||
const resp = await PluginDevelopApi.GetPlaygroundPluginList(
|
||||
{
|
||||
space_id: config.spaceId,
|
||||
page: 1,
|
||||
size: 1,
|
||||
plugin_ids: [config.pluginId || ''],
|
||||
plugin_types: [
|
||||
config.isImageflow ? PluginType.IMAGEFLOW : PluginType.WORKFLOW,
|
||||
],
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
// 先获取工作流的信息
|
||||
const pluginInfos = resp.data?.plugin_list ?? [];
|
||||
if (!pluginInfos?.length) {
|
||||
Toast.error(
|
||||
I18n.t(
|
||||
config.isImageflow
|
||||
? 'imageflow_add_toast_error'
|
||||
: 'workflow_add_list_added_id_empty',
|
||||
),
|
||||
);
|
||||
throw new CustomError('normal_error', 'plugin_list no item');
|
||||
}
|
||||
const target = pluginInfos.at(0);
|
||||
const newWorkflow: BotPluginWorkFlowItem = {
|
||||
workflow_id: config.workflowId || '',
|
||||
plugin_id: config.pluginId || '',
|
||||
name: target?.name || '',
|
||||
desc: target?.desc_for_human || '',
|
||||
parameters: target?.plugin_apis?.at(0)?.parameters ?? [],
|
||||
plugin_icon: target?.plugin_icon || '',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
version_name: (target as any)?.version_name,
|
||||
flow_mode:
|
||||
target?.plugin_type === PluginType.IMAGEFLOW
|
||||
? WorkflowMode.Imageflow
|
||||
: (config.flowMode ?? WorkflowMode.Workflow),
|
||||
};
|
||||
|
||||
return newWorkflow;
|
||||
}
|
||||
|
||||
function isTypeWorkflow(
|
||||
target: WorkflowInfo | ProductInfo,
|
||||
): target is WorkflowInfo {
|
||||
return context?.modalState.dataSourceType === DataSourceType.Workflow;
|
||||
}
|
||||
|
||||
/** 打开流程详情页 */
|
||||
function openWorkflowDetailPage(workflow: WorkflowInfo | ProductInfo) {
|
||||
const flowData = workflow as Workflow;
|
||||
const wId = (flowData as WorkflowInfo).workflow_id ?? '';
|
||||
|
||||
const query = new URLSearchParams();
|
||||
botId && query.append('bot_id', botId);
|
||||
query.append('space_id', context?.spaceId ?? '');
|
||||
query.append('workflow_id', wId);
|
||||
window.open(`/work_flow?${query.toString()}`, '_blank');
|
||||
}
|
||||
|
||||
const dupProduct = async () => {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTypeWorkflow(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isProfessionalTemplate && !isCozePro) {
|
||||
// 跳转到专业版登录
|
||||
navigate(
|
||||
`/sign/oauth?redirect=${encodeURIComponent(
|
||||
'/store/bot',
|
||||
)}&platform=volcano&page_from=coze_pro_sign_in`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
reporter.info({ message: 'workflow_modal: dupProduct' });
|
||||
|
||||
let newPluginId = '';
|
||||
let newWorkflowId = '';
|
||||
try {
|
||||
const resp = await copyProductHandle(data, context.spaceId);
|
||||
newPluginId = resp.pluginId;
|
||||
newWorkflowId = resp.workflowId;
|
||||
} catch (e) {
|
||||
if (copyProductErrorCodes.includes(e?.code)) {
|
||||
Toast.error(e.message);
|
||||
} else {
|
||||
Toast.error(I18n.t('copy_failed'));
|
||||
reporter.error({
|
||||
message: 'dupProduct: copyProductHandle error',
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 延迟刷新列表, 兜底服务端主从延迟导致问题
|
||||
await wait(100);
|
||||
|
||||
try {
|
||||
const newWorkflow = await getWorkflowItemByPluginId({
|
||||
spaceId: context.spaceId,
|
||||
workflowId: newWorkflowId,
|
||||
pluginId: newPluginId,
|
||||
isImageflow:
|
||||
data?.meta_info?.entity_type ===
|
||||
ProductEntityType.ImageflowTemplateV2,
|
||||
});
|
||||
|
||||
// 构造新的绑定的工作流列表
|
||||
onWorkFlowListChange?.([...(workFlowList ?? []), newWorkflow]);
|
||||
onAdd?.(newWorkflow, { isDup: true, spaceId: context.spaceId });
|
||||
|
||||
if (onDupSuccess) {
|
||||
onDupSuccess(newWorkflow);
|
||||
} else {
|
||||
// 复制商品成功
|
||||
Toast.success({
|
||||
content: (
|
||||
<Space spacing={6}>
|
||||
<Text>{I18n.t('workflowstore_workflow_copy_successful')}</Text>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${context.spaceId}&workflow_id=${newWorkflow.workflow_id}&from=dupSuccess`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('workflowstore_continue_editing')}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.error(I18n.t('workflow_add_list_added_fail'));
|
||||
reporter.error({
|
||||
message: 'dupProduct: getWorkflowItemByPluginId error',
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const dupWorkflowTpl = async () => {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTypeWorkflow(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reporter.info({ message: 'workflow_modal: dupWorkflowTpl' });
|
||||
|
||||
let newPluginId = '';
|
||||
let newWorkflowId = '';
|
||||
try {
|
||||
const resp = await workflowApi.CopyWkTemplateApi(
|
||||
{
|
||||
workflow_ids: [data.workflow_id || ''],
|
||||
target_space_id: context.spaceId,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
newWorkflowId = resp.data[data.workflow_id ?? '']?.workflow_id || '';
|
||||
newPluginId = resp.data[data.workflow_id ?? '']?.plugin_id || '0';
|
||||
} catch (e) {
|
||||
Toast.error(I18n.t('copy_failed'));
|
||||
reporter.error({
|
||||
message: 'dupWorkflowTpl: CopyWkTemplateApi error',
|
||||
error: e,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newWorkflowId || newPluginId === '0') {
|
||||
Toast.error(I18n.t('copy_failed'));
|
||||
reporter.error({
|
||||
message: 'dupWorkflowTpl: CopyWkTemplateApi error',
|
||||
error: new CustomError(
|
||||
'normal_error',
|
||||
`CopyWkTemplateApi: plugin_id is ${newPluginId}, workflow_id is ${newWorkflowId}`,
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 延迟刷新列表, 兜底服务端主从延迟导致问题
|
||||
await wait(100);
|
||||
|
||||
try {
|
||||
const newWorkflow = await getWorkflowItemByPluginId({
|
||||
spaceId: context.spaceId,
|
||||
workflowId: newWorkflowId,
|
||||
pluginId: newPluginId,
|
||||
isImageflow: context.flowMode === WorkflowMode.Imageflow,
|
||||
flowMode: data.flow_mode,
|
||||
});
|
||||
|
||||
const sourceFlowMode = data?.flow_mode ?? context?.flowMode;
|
||||
if (typeof sourceFlowMode !== 'undefined') {
|
||||
newWorkflow.flow_mode = sourceFlowMode;
|
||||
}
|
||||
|
||||
// 构造新的绑定的工作流列表
|
||||
onWorkFlowListChange?.([...(workFlowList ?? []), newWorkflow]);
|
||||
onAdd?.(newWorkflow, { isDup: true, spaceId: context.spaceId });
|
||||
|
||||
if (onDupSuccess) {
|
||||
onDupSuccess(newWorkflow);
|
||||
} else {
|
||||
Toast.success({
|
||||
content: (
|
||||
<Space spacing={6}>
|
||||
<Text>{I18n.t('workflowstore_workflow_copy_successful')}</Text>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${context.spaceId}&workflow_id=${newWorkflow.workflow_id}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('workflowstore_continue_editing')}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.error(e.message || I18n.t('workflow_add_list_added_fail'));
|
||||
reporter.error({
|
||||
message: 'dupWorkflowTpl: getWorkflowItemByPluginId error',
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const removeWorkflow = () => {
|
||||
if (!workFlowList || !isTypeWorkflow(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reporter.info({ message: 'workflow_modal: removeWorkflow' });
|
||||
|
||||
const target = workFlowList.find(
|
||||
item => item.workflow_id === data.workflow_id,
|
||||
);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
onRemove?.(target);
|
||||
onWorkFlowListChange?.(
|
||||
workFlowList.filter(item => item.workflow_id !== data.workflow_id),
|
||||
);
|
||||
};
|
||||
|
||||
const addWorkflow = async () => {
|
||||
if (!context || !isTypeWorkflow(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
reporter.info({ message: 'workflow_modal: addWorkflow' });
|
||||
|
||||
try {
|
||||
const newWorkflow = await getWorkflowItem({
|
||||
spaceId: context.spaceId,
|
||||
workflowId: data.workflow_id,
|
||||
pluginId: data.plugin_id,
|
||||
isImageflow: data?.flow_mode === WorkflowMode.Imageflow,
|
||||
flowMode: data?.flow_mode,
|
||||
});
|
||||
|
||||
if (typeof data?.flow_mode !== 'undefined') {
|
||||
newWorkflow.flow_mode = data?.flow_mode;
|
||||
}
|
||||
|
||||
// 构造新的绑定的工作流列表
|
||||
onWorkFlowListChange?.([...(workFlowList ?? []), newWorkflow]);
|
||||
const addResult = await onAdd?.(newWorkflow, {
|
||||
isDup: false,
|
||||
spaceId: context.spaceId,
|
||||
});
|
||||
/**
|
||||
* 允许外部业务逻辑添加失败
|
||||
*/
|
||||
if (isBoolean(addResult)) {
|
||||
return addResult as unknown as boolean;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
Toast.error(e.message || I18n.t('workflow_add_list_added_fail'));
|
||||
reporter.error({
|
||||
message: 'addWorkflow: getWorkflowItemByPluginId error',
|
||||
error: e,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteWorkflow = async () => {
|
||||
if (!isTypeWorkflow(data)) {
|
||||
return;
|
||||
}
|
||||
if (!handleDeleteWorkflow) {
|
||||
return;
|
||||
}
|
||||
reporter.info({ message: 'workflow_modal: deleteWorkflow' });
|
||||
// delete api
|
||||
const deleteConfig = await handleDeleteWorkflow?.(data);
|
||||
if (deleteConfig?.canDelete) {
|
||||
await deleteConfig?.handleDelete?.();
|
||||
}
|
||||
if (!workFlowList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = workFlowList.find(
|
||||
item => item.workflow_id === data.workflow_id,
|
||||
);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
onDelete?.(target);
|
||||
onWorkFlowListChange?.(
|
||||
workFlowList.filter(item => item.workflow_id !== data.workflow_id),
|
||||
);
|
||||
};
|
||||
|
||||
const itemClick = () => {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
reporter.info({ message: 'workflow_modal: itemClick' });
|
||||
|
||||
if (onItemClick) {
|
||||
// @ts-expect-error 符合预期
|
||||
const item: WorkflowItemType = {
|
||||
item: data,
|
||||
type: context.modalState.dataSourceType,
|
||||
};
|
||||
const ret = onItemClick(item, context.getModalState(context));
|
||||
if (!ret || ret.handled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTypeWorkflow(data)) {
|
||||
openWorkflowDetailPage(data);
|
||||
} else {
|
||||
window.open(
|
||||
`/template/workflow/${data.meta_info.id}?entity_id=${ProductEntityType.WorkflowTemplateV2}`,
|
||||
'_blank',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
dupWorkflowTpl,
|
||||
dupProduct,
|
||||
addWorkflow,
|
||||
removeWorkflow,
|
||||
deleteWorkflow,
|
||||
itemClick,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* 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, useRef, useState } from 'react';
|
||||
|
||||
import { useMount } from 'ahooks';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
BindBizType,
|
||||
OrderBy,
|
||||
WorkFlowListStatus,
|
||||
WorkflowMode,
|
||||
workflowQueryClient,
|
||||
} from '@coze-workflow/base/api';
|
||||
import { isGeneralWorkflow } from '@coze-workflow/base';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
sendTeaEvent,
|
||||
FlowResourceFrom,
|
||||
FlowStoreType,
|
||||
FlowDuplicateType,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { SpaceType } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import WorkflowModalContext, {
|
||||
type WorkflowModalContextValue,
|
||||
} from '../workflow-modal-context';
|
||||
import {
|
||||
type WorkFlowModalModeProps,
|
||||
DataSourceType,
|
||||
MineActiveEnum,
|
||||
type WorkflowModalState,
|
||||
WorkflowModalFrom,
|
||||
WorkflowCategory,
|
||||
} from '../type';
|
||||
import { type WorkflowFilterRef } from '../sider/workflow-filter';
|
||||
import { WorkflowModalSider } from '../sider';
|
||||
import styles from '../index.module.less';
|
||||
import { WorkflowModalFilterForDouyin } from '../filter-douyin';
|
||||
import { WorkflowModalFilter } from '../filter';
|
||||
import { WorkflowModalContent } from '../content';
|
||||
import { reporter } from '../../utils';
|
||||
import { ModalI18nKey, WORKFLOW_MODAL_I18N_KEY_MAP } from './use-i18n-text';
|
||||
/**
|
||||
* 返回流程弹窗的各部分组件, 内容,侧边,筛选组件, 拆分组件可用于不同布局
|
||||
* 本用于流程选择
|
||||
*/
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
const {
|
||||
flowMode = WorkflowMode.Workflow,
|
||||
initState,
|
||||
hideSider = false,
|
||||
bindBizId,
|
||||
bindBizType,
|
||||
projectId,
|
||||
i18nMap,
|
||||
from,
|
||||
} = props;
|
||||
const { space_type: spaceType, id: spaceId } = useSpaceStore(
|
||||
state => state.space,
|
||||
);
|
||||
const sideRef = useRef<WorkflowFilterRef>(null);
|
||||
const [modalState, setModalState] = useState<WorkflowModalState>({
|
||||
status: initState?.status ?? WorkFlowListStatus.HadPublished,
|
||||
dataSourceType: initState?.dataSourceType ?? DataSourceType.Workflow,
|
||||
creator: initState?.creator ?? MineActiveEnum.All,
|
||||
workflowTag: initState?.workflowTag ?? 0,
|
||||
productCategory: initState?.productCategory ?? '',
|
||||
query: initState?.query ?? '',
|
||||
isSpaceWorkflow: initState?.isSpaceWorkflow ?? true,
|
||||
workflowCategory:
|
||||
from === WorkflowModalFrom.ProjectWorkflowAddNode
|
||||
? WorkflowCategory.Project
|
||||
: WorkflowCategory.Library,
|
||||
listFlowMode: initState?.listFlowMode ?? WorkflowMode.All,
|
||||
});
|
||||
|
||||
const updateModalState = useCallback(
|
||||
(newState: Partial<WorkflowModalState>) => {
|
||||
setModalState({
|
||||
...modalState,
|
||||
...newState,
|
||||
});
|
||||
},
|
||||
[modalState],
|
||||
);
|
||||
|
||||
// 排序规则(流程数据源)
|
||||
const [orderBy, setOrderBy] = useState(OrderBy.UpdateTime);
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
|
||||
useMount(() => {
|
||||
setModalState({
|
||||
status: initState?.status ?? WorkFlowListStatus.HadPublished,
|
||||
dataSourceType: initState?.dataSourceType ?? DataSourceType.Workflow,
|
||||
creator: initState?.creator ?? MineActiveEnum.All,
|
||||
workflowTag: initState?.workflowTag ?? 0,
|
||||
productCategory: initState?.productCategory ?? '',
|
||||
query: initState?.query ?? '',
|
||||
isSpaceWorkflow: initState?.isSpaceWorkflow ?? true,
|
||||
workflowCategory:
|
||||
from === WorkflowModalFrom.ProjectWorkflowAddNode
|
||||
? WorkflowCategory.Project
|
||||
: WorkflowCategory.Library,
|
||||
listFlowMode: initState?.listFlowMode ?? WorkflowMode.All,
|
||||
});
|
||||
|
||||
reporter.info({
|
||||
message: 'useWorkflowModalParts mounted',
|
||||
meta: { from: props.from },
|
||||
});
|
||||
});
|
||||
|
||||
const contextValue: WorkflowModalContextValue = {
|
||||
spaceId: spaceId ?? '',
|
||||
spaceType: spaceType ?? SpaceType.Team,
|
||||
bindBizId,
|
||||
bindBizType,
|
||||
projectId,
|
||||
flowMode,
|
||||
modalState,
|
||||
updateModalState,
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
createModalVisible,
|
||||
setCreateModalVisible,
|
||||
getModalState: ctx => ({
|
||||
...ctx.modalState,
|
||||
}),
|
||||
i18nMap,
|
||||
};
|
||||
|
||||
if (!spaceType || !spaceId) {
|
||||
reporter.errorEvent({
|
||||
eventName: 'workflow_modal_in_bot_no_spaceId',
|
||||
error: new CustomError('normal_error', 'no spaceId'),
|
||||
});
|
||||
return {
|
||||
sider: null,
|
||||
content: null,
|
||||
filter: null,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const isBindDouyin = bindBizType === BindBizType.DouYinBot;
|
||||
const hideSidebar = hideSider || isBindDouyin;
|
||||
|
||||
/** 侧边栏组件 */
|
||||
const sider = hideSidebar ? null : (
|
||||
<QueryClientProvider client={workflowQueryClient}>
|
||||
<WorkflowModalContext.Provider value={contextValue}>
|
||||
<WorkflowModalSider ref={sideRef} {...props} />
|
||||
</WorkflowModalContext.Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
/** 流程列表组件 */
|
||||
const content = (
|
||||
<QueryClientProvider client={workflowQueryClient}>
|
||||
<WorkflowModalContext.Provider value={contextValue}>
|
||||
<WorkflowModalContent
|
||||
{...props}
|
||||
onDupSuccess={val => {
|
||||
if (!props.onDupSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modalState.dataSourceType === DataSourceType.Product) {
|
||||
const resourceMap: Record<string, FlowResourceFrom> = {
|
||||
[WorkflowModalFrom.SpaceWorkflowList]:
|
||||
FlowResourceFrom.template,
|
||||
[WorkflowModalFrom.WorkflowAddNode]: FlowResourceFrom.flowIde,
|
||||
[WorkflowModalFrom.BotSkills]: FlowResourceFrom.botIde,
|
||||
[WorkflowModalFrom.BotMultiSkills]: FlowResourceFrom.botIde,
|
||||
[WorkflowModalFrom.BotTrigger]: FlowResourceFrom.botIde,
|
||||
[WorkflowModalFrom.BotShortcut]: FlowResourceFrom.botIde,
|
||||
[WorkflowModalFrom.WorkflowAgent]: FlowResourceFrom.botIde,
|
||||
};
|
||||
|
||||
const resource =
|
||||
resourceMap[props.from || ''] ?? FlowResourceFrom.botIde;
|
||||
sendTeaEvent(EVENT_NAMES.flow_duplicate_click, {
|
||||
store_type: isGeneralWorkflow(flowMode)
|
||||
? FlowStoreType.workflow
|
||||
: FlowStoreType.imageflow,
|
||||
resource,
|
||||
category_name: sideRef.current?.getCurrent()?.name || '',
|
||||
duplicate_type:
|
||||
props.from === WorkflowModalFrom.BotSkills
|
||||
? FlowDuplicateType.toBot
|
||||
: FlowDuplicateType.toWorkspace,
|
||||
});
|
||||
}
|
||||
props.onDupSuccess(val);
|
||||
}}
|
||||
/>
|
||||
</WorkflowModalContext.Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
/** 筛选组件 */
|
||||
let filter = (
|
||||
<QueryClientProvider client={workflowQueryClient}>
|
||||
<WorkflowModalContext.Provider value={contextValue}>
|
||||
{isBindDouyin ? (
|
||||
<WorkflowModalFilterForDouyin {...props} />
|
||||
) : (
|
||||
<WorkflowModalFilter {...props} />
|
||||
)}
|
||||
</WorkflowModalContext.Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
// 隐藏 sider 后,把 title 放到 filter 上边
|
||||
if (hideSidebar && !isBindDouyin) {
|
||||
const title = I18n.t(
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP[flowMode]?.[
|
||||
ModalI18nKey.Title
|
||||
] as I18nKeysNoOptionsType,
|
||||
);
|
||||
filter = (
|
||||
<div className="flex flex-col items-start flex-grow flex-shrink-0">
|
||||
<div className={styles.title}>{title}</div>
|
||||
{filter}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return {
|
||||
sider,
|
||||
filter,
|
||||
content,
|
||||
} as const;
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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 {
|
||||
useInfiniteQuery,
|
||||
type UseInfiniteQueryResult,
|
||||
} from '@tanstack/react-query';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
ProductEntityType,
|
||||
type ProductInfo,
|
||||
type public_api,
|
||||
SortType,
|
||||
} from '@coze-arch/bot-api/product_api';
|
||||
import { ProductApi } from '@coze-arch/bot-api';
|
||||
|
||||
export type GetProductListRequest = public_api.GetProductListRequest;
|
||||
|
||||
interface WorkflowProductListReturn {
|
||||
updatePageParam: (newParam: Partial<GetProductListRequest>) => void;
|
||||
|
||||
workflowProductList: ProductInfo[];
|
||||
queryError: UseInfiniteQueryResult['error'];
|
||||
loadingStatus: UseInfiniteQueryResult['status'];
|
||||
refetch: UseInfiniteQueryResult['refetch'];
|
||||
fetchNextPage: UseInfiniteQueryResult['fetchNextPage'];
|
||||
isFetching: UseInfiniteQueryResult['isFetching'];
|
||||
isFetchingNextPage: UseInfiniteQueryResult['isFetchingNextPage'];
|
||||
hasNextPage: UseInfiniteQueryResult['hasNextPage'];
|
||||
|
||||
copyProduct: (
|
||||
item: ProductInfo,
|
||||
targetSpaceId: string,
|
||||
) => Promise<{
|
||||
workflowId: string;
|
||||
pluginId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function,@coze-arch/max-line-per-function
|
||||
export function useWorkflowProductList({
|
||||
pageSize = 12,
|
||||
enabled = false,
|
||||
}: {
|
||||
pageSize?: number;
|
||||
enabled?: boolean;
|
||||
} = {}): Readonly<WorkflowProductListReturn> {
|
||||
const [keyword, setKeyword] = useState<string>();
|
||||
const [sortType, setSortType] = useState<SortType>(SortType.Heat);
|
||||
const [categoryId, setCategoryId] = useState<string>();
|
||||
const [source, setSource] = useState<GetProductListRequest['source']>();
|
||||
|
||||
const initialPageParam = useMemo<GetProductListRequest>(
|
||||
() => ({
|
||||
entity_types: [
|
||||
ProductEntityType.WorkflowTemplateV2,
|
||||
ProductEntityType.ImageflowTemplateV2,
|
||||
],
|
||||
page_num: 1,
|
||||
page_size: pageSize,
|
||||
category_id: categoryId,
|
||||
sort_type: sortType,
|
||||
source,
|
||||
keyword,
|
||||
}),
|
||||
[keyword, sortType, categoryId, source],
|
||||
);
|
||||
|
||||
const updatePageParam = (newParam: Partial<GetProductListRequest>) => {
|
||||
if ('category_id' in newParam) {
|
||||
setCategoryId(newParam.category_id);
|
||||
}
|
||||
if ('sort_type' in newParam) {
|
||||
setSortType(newParam.sort_type ?? SortType.Newest);
|
||||
}
|
||||
if ('keyword' in newParam) {
|
||||
setKeyword(newParam.keyword);
|
||||
}
|
||||
if ('source' in newParam) {
|
||||
setSource(newParam.source);
|
||||
}
|
||||
};
|
||||
|
||||
const queryKey = useMemo(
|
||||
() => ['workflow_product', JSON.stringify(initialPageParam)],
|
||||
[initialPageParam],
|
||||
);
|
||||
|
||||
const fetchProductList = async (params: GetProductListRequest) => {
|
||||
const resp = await ProductApi.PublicGetProductList(params);
|
||||
|
||||
return resp.data;
|
||||
};
|
||||
|
||||
const {
|
||||
data: pageData,
|
||||
error: queryError,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
status: loadingStatus,
|
||||
refetch,
|
||||
} = useInfiniteQuery({
|
||||
enabled,
|
||||
queryKey,
|
||||
queryFn: ({ pageParam }) => fetchProductList(pageParam),
|
||||
initialPageParam,
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam) => {
|
||||
if (!lastPage?.has_more) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...lastPageParam,
|
||||
page_num: (lastPageParam.page_num ?? 1) + 1,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const workflowProductList = useMemo(() => {
|
||||
const result: ProductInfo[] = [];
|
||||
const idMap: Record<string, boolean> = {};
|
||||
pageData?.pages.forEach(page => {
|
||||
page?.products?.forEach(product => {
|
||||
if (!product.meta_info.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!idMap[product.meta_info.id]) {
|
||||
result.push(product);
|
||||
}
|
||||
idMap[product.meta_info.id] = true;
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, [pageData]);
|
||||
|
||||
const copyProduct = async (item: ProductInfo, targetSpaceId: string) => {
|
||||
if (!item?.meta_info?.id) {
|
||||
throw new CustomError('normal_error', 'no productId');
|
||||
}
|
||||
const res = await ProductApi.PublicDuplicateProduct(
|
||||
{
|
||||
product_id: item?.meta_info?.id,
|
||||
space_id: targetSpaceId,
|
||||
entity_type: item.meta_info.entity_type as ProductEntityType,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workflowId = res.data?.new_entity_id;
|
||||
const pluginId = res.data?.new_plugin_id;
|
||||
|
||||
if (!workflowId || !pluginId) {
|
||||
throw new CustomError(
|
||||
'normal_error',
|
||||
'copyProduct fail, no new_entity_id',
|
||||
);
|
||||
}
|
||||
sendTeaEvent(EVENT_NAMES.template_action_front, {
|
||||
template_id: item.meta_info.id ?? '',
|
||||
template_name: item.meta_info.name ?? '',
|
||||
template_type:
|
||||
item.meta_info.entity_type === ProductEntityType.WorkflowTemplateV2
|
||||
? 'workflow'
|
||||
: 'imageflow',
|
||||
template_tag_professional: item.meta_info.is_professional
|
||||
? 'professional'
|
||||
: 'basic',
|
||||
entity_id: item.meta_info.entity_id ?? '',
|
||||
...(item?.meta_info?.is_free
|
||||
? ({
|
||||
template_tag_prize: 'free',
|
||||
} as const)
|
||||
: ({
|
||||
template_tag_prize: 'paid',
|
||||
template_prize_detail: Number(item?.meta_info?.price?.amount) || 0,
|
||||
} as const)),
|
||||
action: 'duplicate',
|
||||
after_id: workflowId,
|
||||
});
|
||||
return { workflowId, pluginId };
|
||||
};
|
||||
return {
|
||||
// 筛选条件
|
||||
updatePageParam,
|
||||
|
||||
//
|
||||
workflowProductList,
|
||||
queryError,
|
||||
loadingStatus,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
|
||||
// 操作
|
||||
copyProduct,
|
||||
} as const;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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, { useContext } from 'react';
|
||||
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { UISearch } from '@coze-studio/components';
|
||||
import { SortType } from '@coze-arch/idl/product_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import { DataSourceType, type WorkflowModalState } from '../type';
|
||||
|
||||
export function useWorkflowSearch() {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { run: debounceChangeSearch, cancel } = useDebounceFn(
|
||||
(search: string) => {
|
||||
/** 搜索最大字符数 */
|
||||
const maxCount = 100;
|
||||
if (search.length > maxCount) {
|
||||
updateSearchQuery(search.substring(0, maxCount));
|
||||
} else {
|
||||
updateSearchQuery(search);
|
||||
}
|
||||
},
|
||||
{ wait: 300 },
|
||||
);
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { dataSourceType, query, isSpaceWorkflow, sortType } =
|
||||
context.modalState;
|
||||
|
||||
const updateSearchQuery = (search?: string) => {
|
||||
const newState: Partial<WorkflowModalState> = { query: search ?? '' };
|
||||
if (dataSourceType === DataSourceType.Workflow) {
|
||||
// 搜索时如果有标签, 重置全部
|
||||
newState.workflowTag = isSpaceWorkflow ? 0 : 1;
|
||||
newState.sortType = undefined;
|
||||
}
|
||||
|
||||
if (dataSourceType === DataSourceType.Product) {
|
||||
if (!search && sortType === SortType.Relative) {
|
||||
newState.sortType = SortType.Heat;
|
||||
}
|
||||
if (search && !context.modalState.query) {
|
||||
newState.sortType = newState.sortType = SortType.Relative;
|
||||
}
|
||||
}
|
||||
|
||||
context.updateModalState(newState);
|
||||
};
|
||||
return (
|
||||
<UISearch
|
||||
tabIndex={-1}
|
||||
value={query}
|
||||
placeholder={I18n.t('workflow_add_search_placeholder')}
|
||||
data-testid="workflow.modal.search"
|
||||
onSearch={search => {
|
||||
if (!search) {
|
||||
// 如果search清空了,那么立即更新query
|
||||
cancel();
|
||||
updateSearchQuery('');
|
||||
} else {
|
||||
// 如果search有值,那么防抖更新
|
||||
debounceChangeSearch(search);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.workflow-modal {
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
@apply mb-24px coz-fg-plus font-medium text-20px leading-7;
|
||||
}
|
||||
|
||||
.title-for-avatar {
|
||||
@apply coz-fg-plus font-medium text-20px leading-7;
|
||||
}
|
||||
}
|
||||
|
||||
.douyin-workflow-modal {
|
||||
:global {
|
||||
.semi-modal {
|
||||
width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 React, { type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { BindBizType, WorkflowMode } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UICompositionModal } from '@coze-arch/bot-semi';
|
||||
|
||||
import {
|
||||
DataSourceType,
|
||||
MineActiveEnum,
|
||||
WorkFlowModalModeProps,
|
||||
WorkflowModalFrom,
|
||||
WorkflowModalState,
|
||||
type WorkflowModalProps,
|
||||
WORKFLOW_LIST_STATUS_ALL,
|
||||
BotPluginWorkFlowItem,
|
||||
} from './type';
|
||||
import { useWorkflowModalParts } from './hooks/use-workflow-modal-parts';
|
||||
import {
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP,
|
||||
ModalI18nKey,
|
||||
} from './hooks/use-i18n-text';
|
||||
|
||||
import styles from './index.module.less';
|
||||
export { ModalI18nKey };
|
||||
|
||||
const WorkflowModal: FC<WorkflowModalProps> = ({
|
||||
className,
|
||||
visible,
|
||||
onClose,
|
||||
...props
|
||||
}) => {
|
||||
const { sider, filter, content } = useWorkflowModalParts(props);
|
||||
const flowMode = props.flowMode ?? WorkflowMode.Workflow;
|
||||
const isDouyinBot = props.bindBizType === BindBizType.DouYinBot;
|
||||
|
||||
return (
|
||||
<UICompositionModal
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
siderWrapperClassName={props.hideSider || isDouyinBot ? 'hidden' : ''}
|
||||
header={I18n.t(
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP[flowMode]?.[ModalI18nKey.Title],
|
||||
)}
|
||||
className={classNames(
|
||||
styles['workflow-modal'],
|
||||
className,
|
||||
'new-workflow-modal',
|
||||
isDouyinBot ? styles['douyin-workflow-modal'] : '',
|
||||
)}
|
||||
sider={sider}
|
||||
filter={filter}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowModal;
|
||||
|
||||
export {
|
||||
useWorkflowModalParts,
|
||||
DataSourceType,
|
||||
MineActiveEnum,
|
||||
WorkflowModalFrom,
|
||||
WorkflowModalProps,
|
||||
WorkFlowModalModeProps,
|
||||
WorkflowModalState,
|
||||
WORKFLOW_LIST_STATUS_ALL,
|
||||
BotPluginWorkFlowItem,
|
||||
};
|
||||
|
||||
export { isSelectProjectCategory } from './utils';
|
||||
export { WorkflowCategory } from './type';
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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 FC, useContext, useState } from 'react';
|
||||
|
||||
import { WorkflowMode, BindBizType } from '@coze-arch/idl/workflow_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
IconCozWorkflow,
|
||||
IconCozChat,
|
||||
IconCozArrowDown,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Menu, Button } from '@coze-arch/coze-design';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import { WorkflowModalFrom, type WorkFlowModalModeProps } from '../type';
|
||||
import { useI18nText } from '../hooks/use-i18n-text';
|
||||
import { CreateWorkflowModal } from '../../workflow-edit';
|
||||
import { wait } from '../../utils';
|
||||
import { useOpenWorkflowDetail } from '../../hooks/use-open-workflow-detail';
|
||||
export const CreateWorkflowBtn: FC<
|
||||
Pick<
|
||||
WorkFlowModalModeProps,
|
||||
'onCreateSuccess' | 'nameValidators' | 'from'
|
||||
> & {
|
||||
className?: string;
|
||||
}
|
||||
> = ({ className, onCreateSuccess, nameValidators, from }) => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { i18nText, ModalI18nKey } = useI18nText();
|
||||
const openWorkflowDetailPage = useOpenWorkflowDetail();
|
||||
|
||||
const [createFlowMode, setCreateFlowMode] = useState(
|
||||
context?.flowMode ?? WorkflowMode.Workflow,
|
||||
);
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
const { createModalVisible, setCreateModalVisible, bindBizType } = context;
|
||||
|
||||
// 如果是抖音分身场景,此时只展示一个【创建工作流】按钮
|
||||
const showSingleButton =
|
||||
bindBizType === BindBizType.DouYinBot ||
|
||||
from === WorkflowModalFrom.WorkflowAgent;
|
||||
|
||||
/** 打开流程详情页 */
|
||||
const menuConfig = [
|
||||
{
|
||||
label: I18n.t('workflow_add_navigation_create'),
|
||||
handler: () => {
|
||||
setCreateFlowMode(WorkflowMode.Workflow);
|
||||
setCreateModalVisible(true);
|
||||
},
|
||||
icon: <IconCozWorkflow />,
|
||||
},
|
||||
{
|
||||
label: I18n.t('wf_chatflow_81'),
|
||||
handler: () => {
|
||||
setCreateFlowMode(WorkflowMode.ChatFlow);
|
||||
setCreateModalVisible(true);
|
||||
},
|
||||
icon: <IconCozChat />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* The community version does not currently support chatflow, for future expansion */}
|
||||
{showSingleButton || IS_OPEN_SOURCE ? (
|
||||
<Button
|
||||
className={className}
|
||||
color="hgltplus"
|
||||
onClick={() => {
|
||||
if (from === WorkflowModalFrom.WorkflowAgent) {
|
||||
setCreateFlowMode(WorkflowMode.ChatFlow);
|
||||
} else {
|
||||
setCreateFlowMode(WorkflowMode.Workflow);
|
||||
}
|
||||
setCreateModalVisible(true);
|
||||
}}
|
||||
>
|
||||
{from === WorkflowModalFrom.WorkflowAgent
|
||||
? I18n.t('wf_chatflow_81')
|
||||
: I18n.t('workflow_add_navigation_create')}
|
||||
</Button>
|
||||
) : (
|
||||
<Menu
|
||||
trigger="click"
|
||||
position="bottom"
|
||||
render={
|
||||
<Menu.SubMenu className={'w-[198px]'} mode="menu">
|
||||
{menuConfig.map(item => (
|
||||
<Menu.Item
|
||||
key={item.label}
|
||||
onClick={(value, event) => {
|
||||
event.stopPropagation();
|
||||
item.handler();
|
||||
}}
|
||||
icon={item.icon}
|
||||
>
|
||||
{item.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className={className}
|
||||
color="hgltplus"
|
||||
icon={<IconCozArrowDown />}
|
||||
iconPosition="right"
|
||||
>
|
||||
{context.projectId
|
||||
? I18n.t('wf_chatflow_03')
|
||||
: i18nText(ModalI18nKey.NavigationCreate)}
|
||||
</Button>
|
||||
</Menu>
|
||||
)}
|
||||
<CreateWorkflowModal
|
||||
initConfirmDisabled
|
||||
mode="add"
|
||||
flowMode={createFlowMode}
|
||||
bindBizType={context.bindBizType}
|
||||
bindBizId={context.bindBizId}
|
||||
projectId={context.projectId}
|
||||
visible={createModalVisible}
|
||||
onCancel={() => setCreateModalVisible(false)}
|
||||
onSuccess={async ({ workflowId, flowMode }) => {
|
||||
setCreateModalVisible(false);
|
||||
if (!workflowId) {
|
||||
throw new CustomError(
|
||||
'[Workflow] create failed',
|
||||
'create workflow failed, no workflow id',
|
||||
);
|
||||
}
|
||||
// 由于服务端创建 workflow 的主备数据同步有延迟,所以在创建完后如果直接跳转,有可能查不到 workflowId,所以前端延迟下,降低问题触发的概率
|
||||
await wait(500);
|
||||
|
||||
if (onCreateSuccess) {
|
||||
onCreateSuccess?.({
|
||||
spaceId: context.spaceId,
|
||||
workflowId,
|
||||
flowMode: flowMode || WorkflowMode.Workflow,
|
||||
});
|
||||
} else {
|
||||
openWorkflowDetailPage({
|
||||
workflowId,
|
||||
spaceId: context.spaceId ?? '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
nameValidators={nameValidators}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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, { forwardRef, useContext } from 'react';
|
||||
|
||||
import { UICompositionModalSider } from '@coze-arch/bot-semi';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import { type WorkFlowModalModeProps } from '../type';
|
||||
import { useWorkflowSearch } from '../hooks/use-workflow-search';
|
||||
import { WorkflowFilter, type WorkflowFilterRef } from './workflow-filter';
|
||||
import { CreateWorkflowBtn } from './create-workflow-btn';
|
||||
|
||||
export const WorkflowModalSider = forwardRef<
|
||||
WorkflowFilterRef,
|
||||
WorkFlowModalModeProps
|
||||
>((props, ref) => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { hiddenCreate, hiddenExplore, from } = props;
|
||||
|
||||
const searchNode = useWorkflowSearch();
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<UICompositionModalSider style={{ paddingTop: 16 }}>
|
||||
<UICompositionModalSider.Header>
|
||||
{searchNode}
|
||||
{!hiddenCreate && (
|
||||
<CreateWorkflowBtn
|
||||
className="!mt-6 w-full"
|
||||
onCreateSuccess={props.onCreateSuccess}
|
||||
nameValidators={props.nameValidators}
|
||||
from={from}
|
||||
/>
|
||||
)}
|
||||
</UICompositionModalSider.Header>
|
||||
<UICompositionModalSider.Content>
|
||||
<WorkflowFilter
|
||||
ref={ref}
|
||||
from={props.from}
|
||||
hiddenExplore={hiddenExplore}
|
||||
hiddenSpaceList={props.hiddenSpaceList}
|
||||
hiddenLibrary={props.hiddenLibrary}
|
||||
hiddenWorkflowCategories={props.hiddenWorkflowCategories}
|
||||
/>
|
||||
</UICompositionModalSider.Content>
|
||||
</UICompositionModalSider>
|
||||
);
|
||||
});
|
||||
|
||||
WorkflowModalSider.displayName = 'WorkflowModalSider';
|
||||
@@ -0,0 +1,83 @@
|
||||
@import '@coze-common/assets/style/common.less';
|
||||
@import '@coze-common/assets/style/mixins.less';
|
||||
|
||||
.tool-tag-list {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
|
||||
padding-top: 16px;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
&-label {
|
||||
height: 40px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 12px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 40px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 29, 35, 35%));
|
||||
}
|
||||
|
||||
&-cell {
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
height: 32px;
|
||||
margin-bottom: 4px;
|
||||
padding: 0 10px 0 12px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
color: #1d1c23;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
&-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-right: 8px;
|
||||
.common-svg-icon(20px, #1d1c23);
|
||||
|
||||
>img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
width: calc(100% - 24px);
|
||||
margin: 12px;
|
||||
background: rgba(28, 29, 35, 12%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--light-usage-text-color-text-0, #1c1f23);
|
||||
background: var(--light-usage-fill-color-fill-0, rgba(46, 50, 56, 5%));
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23);
|
||||
|
||||
background: var(--light-usage-fill-color-fill-0, rgba(46, 47, 56, 5%));
|
||||
border-radius: 8px;
|
||||
|
||||
.tool-tag-list-cell-icon {
|
||||
.common-svg-icon(20px, #4d53e8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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 {
|
||||
forwardRef,
|
||||
type ReactNode,
|
||||
useContext,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { WorkflowMode } from '@coze-workflow/base/api';
|
||||
import { workflowApi, isGeneralWorkflow } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { ProductEntityType } from '@coze-arch/bot-api/product_api';
|
||||
import { ProductApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
IconCozAllFill,
|
||||
IconCozFireFill,
|
||||
IconCozKnowledgeFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
|
||||
import WorkflowModalContext from '../workflow-modal-context';
|
||||
import {
|
||||
DataSourceType,
|
||||
WorkflowCategory,
|
||||
WorkflowModalFrom,
|
||||
type WorkFlowModalModeProps,
|
||||
} from '../type';
|
||||
|
||||
import s from './workflow-filter.module.less';
|
||||
|
||||
interface PluginTag {
|
||||
type?: string | number;
|
||||
name?: string;
|
||||
icon?: string | React.ReactElement;
|
||||
active_icon?: string | React.ReactElement;
|
||||
}
|
||||
|
||||
export interface WorkflowFilterRef {
|
||||
getCurrent: () => PluginTag | undefined;
|
||||
}
|
||||
|
||||
const WorkflowFilter = forwardRef<
|
||||
WorkflowFilterRef,
|
||||
Pick<
|
||||
WorkFlowModalModeProps,
|
||||
| 'from'
|
||||
| 'hiddenSpaceList'
|
||||
| 'hiddenExplore'
|
||||
| 'hiddenLibrary'
|
||||
| 'hiddenWorkflowCategories'
|
||||
>
|
||||
>(
|
||||
(
|
||||
{
|
||||
from,
|
||||
hiddenSpaceList,
|
||||
hiddenExplore,
|
||||
hiddenLibrary,
|
||||
hiddenWorkflowCategories = [],
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const [FLAGS] = useFlags();
|
||||
|
||||
const getWorkflowTags = async (): Promise<{
|
||||
type: 'WorkFlowTemplateTag' | 'PublicGetProductCategoryList';
|
||||
data: PluginTag[];
|
||||
}> => {
|
||||
if (hiddenExplore) {
|
||||
return {
|
||||
type: 'WorkFlowTemplateTag',
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
FLAGS['bot.community.store_imageflow'] ||
|
||||
isGeneralWorkflow(context?.flowMode || WorkflowMode.Workflow)
|
||||
) {
|
||||
// The community version does not currently support workflow template tags for future expansion
|
||||
if (IS_OPEN_SOURCE) {
|
||||
return {
|
||||
type: 'PublicGetProductCategoryList',
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
const resp = await ProductApi.PublicGetProductCategoryList({
|
||||
// 模版分类对于 工作流 / 图像流 通用
|
||||
entity_type: ProductEntityType.TemplateCommon,
|
||||
need_empty_category: false,
|
||||
});
|
||||
|
||||
const targetList: PluginTag[] = (resp.data?.categories ?? []).map(
|
||||
item => ({
|
||||
type: item.id,
|
||||
name: item.name ?? '',
|
||||
icon: item.icon_url,
|
||||
active_icon: item.active_icon_url,
|
||||
}),
|
||||
);
|
||||
|
||||
targetList.unshift({
|
||||
type: 'recommend',
|
||||
name: I18n.t('workflowstore_category1'),
|
||||
icon: <IconCozFireFill />,
|
||||
active_icon: <IconCozFireFill />,
|
||||
});
|
||||
|
||||
targetList.unshift({
|
||||
type: 'all',
|
||||
name: I18n.t('All'),
|
||||
icon: <IconCozAllFill />,
|
||||
active_icon: <IconCozAllFill />,
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'PublicGetProductCategoryList',
|
||||
data: targetList,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await workflowApi.WorkFlowTemplateTag({
|
||||
flow_mode: context?.flowMode,
|
||||
});
|
||||
return {
|
||||
type: 'WorkFlowTemplateTag',
|
||||
data: res.data?.tags ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
const currentValue = useMemo(() => {
|
||||
if (!context?.modalState) {
|
||||
return '';
|
||||
}
|
||||
return context.modalState.dataSourceType === DataSourceType.Product
|
||||
? context.modalState.productCategory
|
||||
: context.modalState.workflowTag;
|
||||
}, [context?.modalState]);
|
||||
|
||||
const queryKey = useMemo(() => {
|
||||
const result = ['workflow-modal-side'];
|
||||
result.push(`flowMode-${context?.flowMode}`);
|
||||
return result;
|
||||
}, [context]);
|
||||
|
||||
const { data: tags } = useQuery({
|
||||
enabled: Boolean(context),
|
||||
queryKey,
|
||||
queryFn: getWorkflowTags,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getCurrent: () => tags?.data.find(item => item.type === currentValue),
|
||||
}));
|
||||
/** 展示空间流程, 我的/团队的 */
|
||||
const clickSpaceContent = (category?: WorkflowCategory) => {
|
||||
context?.updateModalState({
|
||||
isSpaceWorkflow: category !== WorkflowCategory.Example,
|
||||
workflowCategory: category,
|
||||
workflowTag: 0,
|
||||
query: '',
|
||||
dataSourceType: DataSourceType.Workflow,
|
||||
productCategory: category,
|
||||
sortType: undefined,
|
||||
});
|
||||
};
|
||||
const nodeDataList = useMemo<
|
||||
Array<{
|
||||
title?: string;
|
||||
icon?: ReactNode;
|
||||
testId?: string;
|
||||
category?: WorkflowCategory;
|
||||
}>
|
||||
>(() => {
|
||||
const tempList = hiddenLibrary
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: I18n.t('project_resource_modal_library_resources', {
|
||||
resource: I18n.t('library_resource_type_workflow'),
|
||||
}),
|
||||
icon: (
|
||||
<IconCozKnowledgeFill
|
||||
className={s['tool-tag-list-cell-icon']}
|
||||
/>
|
||||
),
|
||||
testId: 'workflow.modal.search.option.library',
|
||||
category: WorkflowCategory.Library,
|
||||
},
|
||||
];
|
||||
if (from === WorkflowModalFrom.ProjectWorkflowAddNode) {
|
||||
tempList.push({
|
||||
title: I18n.t('project_resource_modal_project_resources', {
|
||||
resource: I18n.t('library_resource_type_workflow'),
|
||||
}),
|
||||
icon: (
|
||||
<IconCozKnowledgeFill className={s['tool-tag-list-cell-icon']} />
|
||||
),
|
||||
testId: 'workflow.modal.search.option.project',
|
||||
category: WorkflowCategory.Project,
|
||||
});
|
||||
}
|
||||
tempList.push({
|
||||
title: I18n.t('workflow_add_example'),
|
||||
icon: <IconCozKnowledgeFill className={s['tool-tag-list-cell-icon']} />,
|
||||
testId: 'workflow.modal.search.option.example',
|
||||
category: WorkflowCategory.Example,
|
||||
});
|
||||
return tempList.filter(
|
||||
item => !hiddenWorkflowCategories.includes(item.category),
|
||||
);
|
||||
}, [from, hiddenLibrary, hiddenWorkflowCategories, FLAGS]);
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`tool-tag-list ${s['tool-tag-list']}`}>
|
||||
{!hiddenSpaceList && (
|
||||
<div>
|
||||
{nodeDataList.map(nodeData => {
|
||||
const active =
|
||||
context?.modalState.workflowCategory === nodeData.category;
|
||||
return (
|
||||
<div
|
||||
key={nodeData.testId}
|
||||
data-testid={nodeData.testId}
|
||||
className={classNames(s['tool-tag-list-cell'], {
|
||||
[s.active]: active,
|
||||
})}
|
||||
onClick={() => clickSpaceContent(nodeData.category)}
|
||||
>
|
||||
{nodeData.icon}
|
||||
{nodeData.title}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
WorkflowFilter.displayName = 'WorkflowFilter';
|
||||
|
||||
export { WorkflowFilter };
|
||||
232
frontend/packages/workflow/components/src/workflow-modal/type.ts
Normal file
232
frontend/packages/workflow/components/src/workflow-modal/type.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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 BindBizType,
|
||||
type WorkFlowListStatus,
|
||||
type WorkflowMode,
|
||||
} from '@coze-workflow/base/api';
|
||||
import { type SortType, type public_api } from '@coze-arch/idl/product_api';
|
||||
import { type PluginParameter } from '@coze-arch/idl/developer_api';
|
||||
import { type WorkflowDetailData } from '@coze-arch/bot-api/workflow_api';
|
||||
|
||||
import { type RuleItem } from '../workflow-edit';
|
||||
import { type WorkflowInfo, WorkflowModalFrom } from '../types';
|
||||
import { type I18nKey, type ModalI18nKey } from './hooks/use-i18n-text';
|
||||
export { WorkflowModalFrom };
|
||||
|
||||
export interface BotPluginWorkFlowItem extends WorkflowDetailData {
|
||||
workflow_id: string;
|
||||
plugin_id: string;
|
||||
name: string;
|
||||
desc: string;
|
||||
parameters: Array<PluginParameter>;
|
||||
plugin_icon: string;
|
||||
flow_mode?: WorkflowMode;
|
||||
version_name?: string;
|
||||
}
|
||||
|
||||
export type GetProductListRequest = public_api.GetProductListRequest;
|
||||
/**
|
||||
* 商品类型
|
||||
*
|
||||
* 由于类型同名问题, 直接导出 ProductInfo 指向的是后台的类型不是目标类型,需要使用本方法转一下
|
||||
*/
|
||||
export type ProductInfo = public_api.ProductInfo;
|
||||
|
||||
export enum MineActiveEnum {
|
||||
All = '1',
|
||||
Mine = '2',
|
||||
}
|
||||
|
||||
/** 数据来源 */
|
||||
export enum DataSourceType {
|
||||
/** 流程 */
|
||||
Workflow = 'workflow',
|
||||
/** @deprecated 流程商店 */
|
||||
Product = 'product',
|
||||
}
|
||||
|
||||
export type WorkflowItemType =
|
||||
| { type: DataSourceType.Workflow; item: WorkflowInfo }
|
||||
| { type: DataSourceType.Product; item: ProductInfo };
|
||||
|
||||
export const WORKFLOW_LIST_STATUS_ALL = 'all';
|
||||
/**
|
||||
* 项目内的工作流添加子流程时的分类中,资源库/项目工作流分类
|
||||
*/
|
||||
export enum WorkflowCategory {
|
||||
/**
|
||||
* 项目工作流
|
||||
*/
|
||||
Project = 'project',
|
||||
/**
|
||||
* 资源库工作流
|
||||
*/
|
||||
Library = 'library',
|
||||
/**
|
||||
* 官方示例
|
||||
*/
|
||||
Example = 'example',
|
||||
}
|
||||
/** 流程弹窗状态 */
|
||||
export interface WorkflowModalState {
|
||||
/** 流程状态 */
|
||||
status: WorkFlowListStatus | typeof WORKFLOW_LIST_STATUS_ALL;
|
||||
/** @deprecated 数据类型, 当前请求的是流程数据还是商店数据 */
|
||||
dataSourceType: DataSourceType;
|
||||
/** 创建者 */
|
||||
creator: MineActiveEnum;
|
||||
/** @deprecated 工作流模板标签 */
|
||||
workflowTag: number;
|
||||
/** @deprecated 商品标签 */
|
||||
productCategory: string;
|
||||
/** 搜索关键字 */
|
||||
query: string;
|
||||
/** @deprecated 是否请求当前空间流程 */
|
||||
isSpaceWorkflow: boolean;
|
||||
/** 选中的 workflow 分类 */
|
||||
workflowCategory?: WorkflowCategory;
|
||||
/** @deprecated 商店产品下的排序方式 */
|
||||
sortType?: SortType;
|
||||
/** 弹窗内列表筛选的工作流类型,可以的值是 All、Workflow、Chatflow。用于列表里工作流类型筛选,此时 Imageflow 已经合并到 Workflow 类型中了 */
|
||||
listFlowMode: WorkflowMode;
|
||||
}
|
||||
|
||||
/** 流程弹窗 */
|
||||
export interface WorkFlowModalModeProps {
|
||||
/** 当前弹窗来源,默认不传 */
|
||||
from?: WorkflowModalFrom;
|
||||
/** 流程类型, 工作流还是图像流, 默认工作流 */
|
||||
flowMode?: WorkflowMode;
|
||||
/** 隐藏的流程 */
|
||||
excludedWorkflowIds?: string[];
|
||||
/**
|
||||
* filter 状态筛选组件是否显示全部状态选项,默认为 false
|
||||
*/
|
||||
filterOptionShowAll?: boolean;
|
||||
/**
|
||||
* 是否隐藏侧边栏,默认 false。用于场景详情页选择 workflow。
|
||||
*/
|
||||
hideSider?: boolean;
|
||||
/* 是否隐藏作者筛选菜单 */
|
||||
hideCreatorSelect?: boolean;
|
||||
/**
|
||||
* workflow item 是否显示删除按钮,默认 false,用于场景 workflow 以及抖音分身工作流
|
||||
*/
|
||||
itemShowDelete?: boolean;
|
||||
/** @deprecated 是否隐藏空间下 Workflow 列表模块 */
|
||||
hiddenSpaceList?: boolean;
|
||||
/**
|
||||
* @deprecated 使用 hiddenWorkflowCategories
|
||||
* 是否隐藏资源库模块
|
||||
*/
|
||||
hiddenLibrary?: boolean;
|
||||
/** 是否隐藏创建工作流入口 */
|
||||
hiddenCreate?: boolean;
|
||||
/**
|
||||
* @deprecated 探索分类已改为官方示例,使用 hiddenWorkflowCategories
|
||||
* 隐藏探索分类
|
||||
*/
|
||||
hiddenExplore?: boolean;
|
||||
/**
|
||||
* 隐藏的工作流分类,用法同 hiddenLibrary、hiddenExplore,
|
||||
*/
|
||||
hiddenWorkflowCategories?: WorkflowCategory[];
|
||||
/**
|
||||
* 隐藏工作流列表类型筛选
|
||||
*/
|
||||
hiddenListFlowModeFilter?: boolean;
|
||||
/** 复制按钮文案, 默认「复制并添加」 */
|
||||
dupText?: string;
|
||||
/** 初始状态, 配置各筛选项 */
|
||||
initState?: Partial<WorkflowModalState>;
|
||||
/** 已选流程列表 */
|
||||
workFlowList?: BotPluginWorkFlowItem[];
|
||||
/** 已选流程列表变化 */
|
||||
onWorkFlowListChange?: (newList: BotPluginWorkFlowItem[]) => void;
|
||||
/** 选择流程 */
|
||||
onAdd?: (
|
||||
item: BotPluginWorkFlowItem,
|
||||
config: {
|
||||
/** 是否来源于复制 */
|
||||
isDup: boolean;
|
||||
/** 目标空间 */
|
||||
spaceId: string;
|
||||
},
|
||||
) => void;
|
||||
/** 移除流程 */
|
||||
onRemove?: (item: BotPluginWorkFlowItem) => void;
|
||||
/**
|
||||
* 删除流程后的回调 hooks,同时会执行 removeWorkflow 移除和 bot/场景 的关联
|
||||
* @param item
|
||||
*/
|
||||
onDelete?: (item: BotPluginWorkFlowItem) => void;
|
||||
/**
|
||||
* 列表项点击
|
||||
*
|
||||
* 配置可覆盖默认行为: 新开页面打开详情页
|
||||
* @returns 返回 { handled: true } 或 undefined 不执行默认操作,否则执行内部默认的点击事件
|
||||
*/
|
||||
onItemClick?:
|
||||
| ((
|
||||
item: WorkflowItemType,
|
||||
/** 弹窗状态, 可用于初始化弹窗 */
|
||||
modalState: WorkflowModalState,
|
||||
) => { handled: boolean })
|
||||
| ((
|
||||
item: WorkflowItemType,
|
||||
/** 弹窗状态, 可用于初始化弹窗 */
|
||||
modalState: WorkflowModalState,
|
||||
) => void);
|
||||
/**
|
||||
* 创建流程成功
|
||||
*
|
||||
* 配置可覆盖默认行为: 新页面打开复制后的流程详情, 带参数 from=createSuccess
|
||||
*/
|
||||
onCreateSuccess?: (info: {
|
||||
spaceId: string;
|
||||
workflowId: string;
|
||||
flowMode: WorkflowMode;
|
||||
}) => void;
|
||||
/**
|
||||
* 复制流程成功
|
||||
*
|
||||
* 配置可覆盖默认行为: Toast 提示复制成功, 继续编辑
|
||||
*/
|
||||
onDupSuccess?: (item: BotPluginWorkFlowItem) => void;
|
||||
/** 项目内引入资源库文件触发的回调 */
|
||||
onImport?: (
|
||||
item: Pick<BotPluginWorkFlowItem, 'workflow_id' | 'name'>,
|
||||
) => void;
|
||||
bindBizId?: string;
|
||||
bindBizType?: BindBizType;
|
||||
projectId?: string;
|
||||
onClose?: () => void;
|
||||
/**
|
||||
* 创建 workflow 弹窗内命名校验
|
||||
*/
|
||||
nameValidators?: RuleItem[];
|
||||
/** 自定义 i18n 文案 */
|
||||
i18nMap?: Partial<Record<ModalI18nKey, I18nKey>>;
|
||||
}
|
||||
|
||||
export type WorkflowModalProps = {
|
||||
className?: string;
|
||||
visible?: boolean;
|
||||
} & WorkFlowModalModeProps;
|
||||
|
||||
export { WorkflowInfo };
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 WorkflowModalState, WorkflowCategory } from './type';
|
||||
|
||||
/**
|
||||
* workflow modal 当前是否选中了 project 工具流分类
|
||||
* @param modalState
|
||||
*/
|
||||
export const isSelectProjectCategory = (modalState?: WorkflowModalState) =>
|
||||
modalState?.workflowCategory === WorkflowCategory.Project;
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 {
|
||||
type BindBizType,
|
||||
type OrderBy,
|
||||
type WorkflowMode,
|
||||
} from '@coze-workflow/base/api';
|
||||
import { type SpaceType } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { type WorkflowModalState } from './type';
|
||||
import { type I18nKey, type ModalI18nKey } from './hooks/use-i18n-text';
|
||||
|
||||
export interface WorkflowModalContextValue {
|
||||
spaceId: string;
|
||||
spaceType: SpaceType;
|
||||
bindBizId?: string;
|
||||
bindBizType?: BindBizType;
|
||||
/** 当前项目 id,只在项目内的 workflow 有该字段 */
|
||||
projectId?: string;
|
||||
/** 工作流类型,此参数由 WorkflowModal 弹窗创建时由 props 传进来,可能的值是 Workflow、Imageflow。用于区分添加哪种工作流 */
|
||||
flowMode: WorkflowMode;
|
||||
modalState: WorkflowModalState;
|
||||
/** 更新弹窗状态, merge 的模式 */
|
||||
updateModalState: (newState: Partial<WorkflowModalState>) => void;
|
||||
orderBy: OrderBy;
|
||||
setOrderBy: React.Dispatch<React.SetStateAction<OrderBy>>;
|
||||
createModalVisible: boolean;
|
||||
setCreateModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
/** 获取当前弹窗状态, 可用于恢复弹窗状态 */
|
||||
getModalState: (ctx: WorkflowModalContextValue) => WorkflowModalState;
|
||||
|
||||
/** 自定义 i18n 文案 */
|
||||
i18nMap?: Partial<Record<ModalI18nKey, I18nKey>>;
|
||||
}
|
||||
|
||||
const WorkflowModalContext =
|
||||
React.createContext<WorkflowModalContextValue | null>(null);
|
||||
|
||||
export default WorkflowModalContext;
|
||||
Reference in New Issue
Block a user