feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
69
frontend/packages/workflow/playground/src/hooks/index.ts
Normal file
69
frontend/packages/workflow/playground/src/hooks/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useGlobalState } from './use-global-state';
|
||||
export { useExecStateEntity } from './use-exec-state-entity';
|
||||
|
||||
export { useSpaceId } from './use-space-id';
|
||||
export { useLatestWorkflowJson } from './use-latest-workflow-json';
|
||||
|
||||
export { useWorkflowOperation } from './use-workflow-operation';
|
||||
export { useLineService } from './use-line-service';
|
||||
export { useWorkflowRunService } from './use-workflow-run-service';
|
||||
export { useTestRunReporterService } from './use-test-run-reporter-service';
|
||||
|
||||
export { useScrollToNode } from './use-scroll-to-node';
|
||||
export { useScrollToLine } from './use-scroll-to-line';
|
||||
export { useHaveCollaborators } from './use-have-collaborators';
|
||||
export { useNodeRenderData } from './use-node-render-data';
|
||||
export { useRedoUndo } from './use-redo-undo';
|
||||
export { useInputVariables } from './use-input-variables';
|
||||
export { useGetWorkflowMode } from './use-get-workflow-mode';
|
||||
|
||||
export { useRoleService, useRoleServiceStore } from './use-role-service';
|
||||
export { useUpload } from './use-upload';
|
||||
export { useVariableService } from './use-variable-service';
|
||||
export { useNodeRenderScene } from './use-node-render-scene';
|
||||
export { useTestFormState } from './use-test-form-state';
|
||||
export { useUpdateSortedPortLines } from './use-update-sorted-port-lines';
|
||||
export { useAddNode } from './use-add-node';
|
||||
export {
|
||||
useFloatLayoutService,
|
||||
useFloatLayoutSize,
|
||||
} from './use-float-layout-service';
|
||||
export { useOpenTraceListPanel } from './use-open-trace-list-panel';
|
||||
export { useTestRun } from './use-test-run';
|
||||
export { useDataSetInfos } from './use-dataset-info';
|
||||
export { useNodeVersionService } from './node-version';
|
||||
export { useSaveService } from './use-save-service';
|
||||
export { useDatabaseNodeService } from './use-database-node-service';
|
||||
export {
|
||||
usePluginNodeStore,
|
||||
usePluginNodeService,
|
||||
} from './use-plugin-node-service';
|
||||
|
||||
export { useNewDatabaseQuery } from './use-new-database-query';
|
||||
export { useCurrentDatabaseQuery } from './use-current-database-query';
|
||||
export { useCurrentDatabaseID } from './use-current-database-id';
|
||||
|
||||
export { useRelatedBotService } from './use-related-bot-service';
|
||||
|
||||
export { useWorkflowPreset } from './use-workflow-preset';
|
||||
export { useWorkflowModels } from './use-workflow-models';
|
||||
export {
|
||||
useDependencyService,
|
||||
useDependencyEntity,
|
||||
} from './use-dependency-service';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useNodeVersionService } from './use-node-version-service';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { NodeVersionService } from '@/services';
|
||||
|
||||
export const useNodeVersionService = () =>
|
||||
useService<NodeVersionService>(NodeVersionService);
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 semver from 'semver';
|
||||
import { type BotPluginWorkFlowItem } from '@coze-workflow/components';
|
||||
import { type ApiNodeDataDTO } from '@coze-workflow/nodes';
|
||||
import { BlockInput } from '@coze-workflow/base';
|
||||
|
||||
interface PluginApi {
|
||||
name: string;
|
||||
plugin_name: string;
|
||||
api_id: string;
|
||||
plugin_id: string;
|
||||
plugin_icon: string;
|
||||
desc: string;
|
||||
plugin_product_status: number;
|
||||
version_name?: string;
|
||||
version_ts?: string;
|
||||
}
|
||||
|
||||
export const createApiNodeInfo = (
|
||||
apiParams: Partial<PluginApi> | undefined,
|
||||
templateIcon?: string,
|
||||
): ApiNodeDataDTO => {
|
||||
const { name, plugin_name, api_id, plugin_id, desc, version_ts } =
|
||||
apiParams || {};
|
||||
|
||||
return {
|
||||
data: {
|
||||
nodeMeta: {
|
||||
title: name,
|
||||
icon: templateIcon,
|
||||
subtitle: `${plugin_name}:${name}`,
|
||||
description: desc,
|
||||
},
|
||||
inputs: {
|
||||
apiParam: [
|
||||
BlockInput.create('apiID', api_id),
|
||||
BlockInput.create('apiName', name),
|
||||
BlockInput.create('pluginID', plugin_id),
|
||||
BlockInput.create('pluginName', plugin_name),
|
||||
BlockInput.create('pluginVersion', version_ts || ''),
|
||||
BlockInput.create('tips', ''),
|
||||
BlockInput.create('outDocLink', ''),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createSubWorkflowNodeInfo = ({
|
||||
workflowItem,
|
||||
spaceId,
|
||||
templateIcon,
|
||||
isImageflow,
|
||||
}: {
|
||||
workflowItem: BotPluginWorkFlowItem | undefined;
|
||||
spaceId: string;
|
||||
isImageflow: boolean;
|
||||
templateIcon?: string;
|
||||
}) => {
|
||||
const { name, workflow_id, desc, version_name } = workflowItem || {};
|
||||
|
||||
const nodeJson = {
|
||||
data: {
|
||||
nodeMeta: {
|
||||
title: name,
|
||||
description: desc,
|
||||
icon: templateIcon,
|
||||
isImageflow,
|
||||
},
|
||||
inputs: {
|
||||
workflowId: workflow_id,
|
||||
spaceId,
|
||||
workflowVersion: semver.valid(version_name) ? version_name : '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return nodeJson;
|
||||
};
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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 { useRef } from 'react';
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { WorkflowNodesService } from '@coze-workflow/nodes';
|
||||
import {
|
||||
isSelectProjectCategory,
|
||||
useOpenWorkflowDetail,
|
||||
useWorkflowModal,
|
||||
WorkflowModalFrom,
|
||||
type WorkFlowModalModeProps,
|
||||
} from '@coze-workflow/components';
|
||||
import { type Workflow } from '@coze-workflow/base/api';
|
||||
import {
|
||||
StandardNodeType,
|
||||
WorkflowMode,
|
||||
type WorkflowNodeJSON,
|
||||
} from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, Space, Toast, Typography } from '@coze-arch/coze-design';
|
||||
import { From } from '@coze-agent-ide/plugin-shared';
|
||||
import { usePluginApisModal } from '@coze-agent-ide/bot-plugin/components/plugin-apis/use-plugin-apis-modal';
|
||||
|
||||
import { WorkflowPlaygroundContext } from '@/workflow-playground-context';
|
||||
import { WorkflowEditService } from '@/services';
|
||||
import { useSpaceId } from '@/hooks/use-space-id';
|
||||
import { useGlobalState } from '@/hooks/use-global-state';
|
||||
import { useNodeVersionService } from '@/hooks';
|
||||
|
||||
import { createApiNodeInfo, createSubWorkflowNodeInfo } from './helper';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
/**
|
||||
* 子流程、插件节点关闭时的结果
|
||||
*/
|
||||
export enum AddNodeModalCloseResult {
|
||||
/**
|
||||
* 成功添加节点
|
||||
*/
|
||||
NodeAdded = 'node-added',
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
Cancel = 'cancel',
|
||||
/**
|
||||
* 打开 project 的新 tab
|
||||
*/
|
||||
OpenNewTab = 'new-tab',
|
||||
}
|
||||
|
||||
export type AddNodeCallback = (params: {
|
||||
nodeType: StandardNodeType;
|
||||
nodeJSON: Partial<WorkflowNodeJSON>;
|
||||
}) => void;
|
||||
export const useAddNodeModal = (prevAddNodeRef: {
|
||||
current: { x: number; y: number; isDrag: boolean };
|
||||
}) => {
|
||||
const spaceId = useSpaceId();
|
||||
const globalState = useGlobalState();
|
||||
const playgroundContext = useService<WorkflowPlaygroundContext>(
|
||||
WorkflowPlaygroundContext,
|
||||
);
|
||||
|
||||
const nodeVersionService = useNodeVersionService();
|
||||
|
||||
const projectApi = globalState.getProjectApi();
|
||||
|
||||
const addNodeCallbackRef = useRef<AddNodeCallback>();
|
||||
const onCloseRef = useRef<(result?: AddNodeModalCloseResult) => void>();
|
||||
const addNodeModalCloseResultRef = useRef<AddNodeModalCloseResult>();
|
||||
const editService = useService<WorkflowEditService>(WorkflowEditService);
|
||||
const nodesService = useService<WorkflowNodesService>(WorkflowNodesService);
|
||||
const openWorkflowDetail = useOpenWorkflowDetail();
|
||||
const createOpenWorkflowModalCallback =
|
||||
(isImageflow): WorkFlowModalModeProps['onAdd'] =>
|
||||
async (val, config) => {
|
||||
if (!val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await nodeVersionService.addSubWorkflowCheck(
|
||||
val.workflow_id,
|
||||
val.version_name,
|
||||
))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { name } = val;
|
||||
|
||||
const templateIcon = playgroundContext.getTemplateList([
|
||||
isImageflow ? StandardNodeType.Imageflow : StandardNodeType.SubWorkflow,
|
||||
])?.[0]?.icon_url;
|
||||
|
||||
const nodeJSON = createSubWorkflowNodeInfo({
|
||||
workflowItem: val,
|
||||
spaceId,
|
||||
templateIcon,
|
||||
isImageflow,
|
||||
});
|
||||
|
||||
const position = {
|
||||
clientX: prevAddNodeRef.current.x,
|
||||
clientY: prevAddNodeRef.current.y,
|
||||
};
|
||||
const { isDrag } = prevAddNodeRef.current;
|
||||
|
||||
if (addNodeCallbackRef.current) {
|
||||
addNodeCallbackRef.current({
|
||||
nodeType: StandardNodeType.SubWorkflow,
|
||||
nodeJSON,
|
||||
});
|
||||
} else {
|
||||
// 这里可能会失败,底层调用 released_workflows 接口
|
||||
editService.addNode(
|
||||
StandardNodeType.SubWorkflow,
|
||||
nodeJSON,
|
||||
position,
|
||||
isDrag,
|
||||
);
|
||||
}
|
||||
Toast.success({
|
||||
content: (
|
||||
<Space spacing={6}>
|
||||
<Text>
|
||||
{isImageflow
|
||||
? I18n.t('workflow_add_imageflow_toast_success', { name })
|
||||
: I18n.t('wf_node_add_wf_modal_toast_wf_added', {
|
||||
workflowName: name,
|
||||
})}
|
||||
</Text>
|
||||
{config.isDup ? (
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${spaceId}&workflow_id=${val.workflow_id}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('workflowstore_continue_editing')}
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const openWorkflowModalCallback = createOpenWorkflowModalCallback(false);
|
||||
const openImageflowModalCallback = createOpenWorkflowModalCallback(true);
|
||||
const onCloseModal = () => {
|
||||
onCloseRef.current?.(addNodeModalCloseResultRef.current);
|
||||
addNodeModalCloseResultRef.current = undefined;
|
||||
};
|
||||
// workflow 添加弹窗
|
||||
const workflowModalFrom = globalState.projectId
|
||||
? WorkflowModalFrom.ProjectWorkflowAddNode
|
||||
: WorkflowModalFrom.WorkflowAddNode;
|
||||
const {
|
||||
node: workflowModal,
|
||||
open: openWorkflow,
|
||||
close: closeWorkflow,
|
||||
} = useWorkflowModal({
|
||||
from: workflowModalFrom,
|
||||
flowMode: WorkflowMode.Workflow,
|
||||
onAdd: openWorkflowModalCallback,
|
||||
bindBizId: globalState.config?.bindBizID,
|
||||
bindBizType: globalState.config?.bindBizType,
|
||||
excludedWorkflowIds: [globalState.workflowId],
|
||||
projectId: globalState.projectId,
|
||||
onDupSuccess: () => null,
|
||||
onClose: onCloseModal,
|
||||
onCreateSuccess: val => {
|
||||
addNodeModalCloseResultRef.current = AddNodeModalCloseResult.OpenNewTab;
|
||||
closeWorkflow();
|
||||
if (workflowModalFrom === WorkflowModalFrom.ProjectWorkflowAddNode) {
|
||||
globalState.playgroundProps.refetchProjectResourceList?.();
|
||||
}
|
||||
openWorkflowDetail({
|
||||
workflowId: val.workflowId,
|
||||
spaceId: val.spaceId,
|
||||
projectId: globalState.projectId,
|
||||
ideNavigate: projectApi?.navigate,
|
||||
});
|
||||
},
|
||||
onItemClick: ({ item }, modalState) => {
|
||||
if (isSelectProjectCategory(modalState)) {
|
||||
addNodeModalCloseResultRef.current = AddNodeModalCloseResult.OpenNewTab;
|
||||
closeWorkflow();
|
||||
projectApi?.navigate?.(`/workflow/${(item as Workflow).workflow_id}`);
|
||||
return { handled: true };
|
||||
}
|
||||
return { handled: false };
|
||||
},
|
||||
});
|
||||
|
||||
// 图像流弹窗
|
||||
const {
|
||||
node: imageFlowModal,
|
||||
open: openImageflow,
|
||||
close: closeImageflow,
|
||||
} = useWorkflowModal({
|
||||
from: WorkflowModalFrom.WorkflowAddNode,
|
||||
flowMode: WorkflowMode.Imageflow,
|
||||
onAdd: openImageflowModalCallback,
|
||||
excludedWorkflowIds: [globalState.workflowId],
|
||||
onDupSuccess: () => null,
|
||||
onClose: onCloseModal,
|
||||
});
|
||||
|
||||
// plugin 添加弹窗
|
||||
const pluginModalFrom = globalState.projectId
|
||||
? From.ProjectWorkflow
|
||||
: From.WorkflowAddNode;
|
||||
const {
|
||||
node: pluginModal,
|
||||
open: openPlugin,
|
||||
close: closePlugin,
|
||||
} = usePluginApisModal({
|
||||
from: pluginModalFrom,
|
||||
projectId: globalState.projectId,
|
||||
closeCallback: onCloseModal,
|
||||
clickProjectPluginCallback: pluginInfo => {
|
||||
addNodeModalCloseResultRef.current = AddNodeModalCloseResult.OpenNewTab;
|
||||
closePlugin();
|
||||
projectApi?.navigate(`/plugin/${pluginInfo?.id}`);
|
||||
},
|
||||
openModeCallback: async val => {
|
||||
if (!val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await nodeVersionService.addApiCheck(val.plugin_id, val.version_ts))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const templateIcon = playgroundContext.getNodeTemplateInfoByType(
|
||||
StandardNodeType.Api,
|
||||
)?.icon;
|
||||
const nodeJSON = createApiNodeInfo(val, templateIcon);
|
||||
const position = {
|
||||
clientX: prevAddNodeRef.current.x,
|
||||
clientY: prevAddNodeRef.current.y,
|
||||
};
|
||||
const { isDrag } = prevAddNodeRef.current;
|
||||
|
||||
// 插件面板弹窗-点击添加插件,预先请求 api-detail 接口,获取 plugin 详情,调用 panel.tsx 的 handleSelectNode 方法
|
||||
if (addNodeCallbackRef.current) {
|
||||
addNodeCallbackRef.current({
|
||||
nodeType: StandardNodeType.Api,
|
||||
nodeJSON,
|
||||
});
|
||||
} else {
|
||||
// 拖拽「插件」,或者「子流程」节点自身,会走这里的逻辑,此时 isDrag 为 true
|
||||
editService.addNode(StandardNodeType.Api, nodeJSON, position, isDrag);
|
||||
}
|
||||
Toast.success(
|
||||
I18n.t('bot_edit_tool_added_toast', {
|
||||
api_name: val?.name,
|
||||
}) as string,
|
||||
);
|
||||
},
|
||||
onCreateSuccess:
|
||||
pluginModalFrom === From.ProjectWorkflow
|
||||
? val => {
|
||||
addNodeModalCloseResultRef.current =
|
||||
AddNodeModalCloseResult.OpenNewTab;
|
||||
closePlugin();
|
||||
if (val?.pluginId && pluginModalFrom === From.ProjectWorkflow) {
|
||||
globalState.playgroundProps.refetchProjectResourceList?.();
|
||||
projectApi?.navigate(`/plugin/${val.pluginId}`);
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
const wrapOpenFunc = function <T>(
|
||||
openFunc: (modalProps?: T) => void,
|
||||
closeFunc?: () => void,
|
||||
) {
|
||||
return ({
|
||||
onAdd,
|
||||
closeOnAdd,
|
||||
onClose,
|
||||
modalProps,
|
||||
}: {
|
||||
onAdd?: AddNodeCallback;
|
||||
onClose?: (closeResult?: AddNodeModalCloseResult) => void;
|
||||
closeOnAdd?: boolean;
|
||||
modalProps?: T;
|
||||
} = {}) => {
|
||||
if (onAdd) {
|
||||
addNodeCallbackRef.current = (...args) => {
|
||||
const nodeJSON = args?.[0]?.nodeJSON as WorkflowNodeJSON<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Record<string, any>
|
||||
>;
|
||||
if (nodeJSON?.data?.nodeMeta?.title) {
|
||||
nodeJSON.data.nodeMeta.title = nodesService.createUniqTitle(
|
||||
nodeJSON.data.nodeMeta.title,
|
||||
);
|
||||
}
|
||||
addNodeModalCloseResultRef.current =
|
||||
AddNodeModalCloseResult.NodeAdded;
|
||||
onAdd?.(...args);
|
||||
closeOnAdd ? closeFunc?.() : null;
|
||||
};
|
||||
} else {
|
||||
addNodeCallbackRef.current = undefined;
|
||||
}
|
||||
onCloseRef.current = onClose;
|
||||
openFunc?.(modalProps);
|
||||
};
|
||||
};
|
||||
return {
|
||||
workflowModal,
|
||||
openWorkflow: wrapOpenFunc(openWorkflow, closeWorkflow),
|
||||
|
||||
imageFlowModal,
|
||||
openImageflow: wrapOpenFunc(openImageflow, closeImageflow),
|
||||
|
||||
pluginModal,
|
||||
openPlugin: wrapOpenFunc(openPlugin, closePlugin),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 左侧添加节点面板的显示隐藏状态,需要被别的地方消费,所以抽象成一个全局 state
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface AddNodeVisibleStore {
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export const useAddNodeVisibleStore = create<AddNodeVisibleStore>(set => ({
|
||||
visible: true,
|
||||
setVisible: visible => set({ visible }),
|
||||
}));
|
||||
162
frontend/packages/workflow/playground/src/hooks/use-add-node.ts
Normal file
162
frontend/packages/workflow/playground/src/hooks/use-add-node.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 { useRef } from 'react';
|
||||
|
||||
import { set } from 'lodash-es';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { StandardNodeType } from '@coze-workflow/base';
|
||||
|
||||
import { getWorkflowVersionByPluginId } from '@/utils';
|
||||
import { type HandleAddNode } from '@/typing';
|
||||
import { WorkflowEditService } from '@/services';
|
||||
import { useNodeVersionService, useGlobalState } from '@/hooks';
|
||||
|
||||
import { useAddNodeModal } from './use-add-node-modal';
|
||||
|
||||
export interface AddNodeProps {
|
||||
x: number;
|
||||
y: number;
|
||||
isDrag: boolean;
|
||||
}
|
||||
export const useAddNode = () => {
|
||||
const prevAddNodeRef = useRef<{ x: number; y: number; isDrag: boolean }>({
|
||||
x: 0,
|
||||
y: 0,
|
||||
isDrag: false,
|
||||
});
|
||||
|
||||
const updateAddNodePosition = (props: AddNodeProps) => {
|
||||
prevAddNodeRef.current = props;
|
||||
};
|
||||
|
||||
const {
|
||||
openPlugin,
|
||||
openWorkflow,
|
||||
openImageflow,
|
||||
pluginModal,
|
||||
workflowModal,
|
||||
imageFlowModal,
|
||||
} = useAddNodeModal(prevAddNodeRef);
|
||||
|
||||
const editService = useService<WorkflowEditService>(WorkflowEditService);
|
||||
const nodeVersionService = useNodeVersionService();
|
||||
const { spaceId } = useGlobalState();
|
||||
|
||||
const handleAddSubWorkflow: HandleAddNode = async (
|
||||
item,
|
||||
coord = { x: 0, y: 0 },
|
||||
isDrag = false,
|
||||
) => {
|
||||
const { nodeType, nodeJson, nodeVersionInfo } = item;
|
||||
if (nodeJson) {
|
||||
const { workflowId, pluginId } = nodeVersionInfo;
|
||||
const versionName = await getWorkflowVersionByPluginId({
|
||||
spaceId,
|
||||
pluginId,
|
||||
});
|
||||
versionName && set(nodeJson, 'data.inputs.workflowVersion', versionName);
|
||||
|
||||
if (
|
||||
!(await nodeVersionService.addSubWorkflowCheck(workflowId, versionName))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
editService.addNode(
|
||||
nodeType,
|
||||
nodeJson,
|
||||
{ clientX: coord?.x || 0, clientY: coord?.y || 0 },
|
||||
isDrag,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 记录历史位置,打开子流程弹窗
|
||||
prevAddNodeRef.current = {
|
||||
x: coord.x,
|
||||
y: coord.y,
|
||||
isDrag,
|
||||
};
|
||||
openWorkflow();
|
||||
return;
|
||||
};
|
||||
const handleAddPlugin: HandleAddNode = async (
|
||||
item,
|
||||
coord = { x: 0, y: 0 },
|
||||
isDrag = false,
|
||||
) => {
|
||||
const { nodeType, nodeJson, modalProps, nodeVersionInfo } = item;
|
||||
|
||||
if (nodeJson) {
|
||||
const { pluginId, version } = nodeVersionInfo;
|
||||
if (!(await nodeVersionService.addApiCheck(pluginId, version))) {
|
||||
return;
|
||||
}
|
||||
// 节点添加面板,拖拽添加具体插件节点逻辑
|
||||
editService.addNode(
|
||||
nodeType,
|
||||
nodeJson,
|
||||
{ clientX: coord?.x || 0, clientY: coord?.y || 0 },
|
||||
isDrag,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 记录历史位置,打开插件弹窗
|
||||
prevAddNodeRef.current = {
|
||||
x: coord.x,
|
||||
y: coord.y,
|
||||
isDrag,
|
||||
};
|
||||
|
||||
// 打开插件弹窗添加节点
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
openPlugin({ modalProps: modalProps as any });
|
||||
};
|
||||
|
||||
const handleAddNode: HandleAddNode = (
|
||||
item,
|
||||
coord = { x: 0, y: 0 },
|
||||
isDrag = false,
|
||||
) => {
|
||||
const { nodeType } = item;
|
||||
|
||||
if (nodeType === StandardNodeType.Api) {
|
||||
// 节点添加面板,拖拽添加具体子插件
|
||||
return handleAddPlugin(item, coord, isDrag);
|
||||
}
|
||||
|
||||
if (nodeType === StandardNodeType.SubWorkflow) {
|
||||
// 节点添加面板,拖拽添加具体子流程
|
||||
return handleAddSubWorkflow(item, coord, isDrag);
|
||||
}
|
||||
|
||||
// 节点添加面板,拖拽添加普通节点
|
||||
editService.addNode(
|
||||
item.nodeType,
|
||||
item.nodeJson,
|
||||
{ clientX: coord?.x || 0, clientY: coord?.y || 0 },
|
||||
isDrag,
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
handleAddNode,
|
||||
openPlugin,
|
||||
openWorkflow,
|
||||
openImageflow,
|
||||
updateAddNodePosition,
|
||||
modals: [workflowModal, pluginModal, imageFlowModal],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.warningIcon {
|
||||
> :global(svg) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 全局hook,管理 Biz IDE 的状态,与 React 组件交互
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Modal } from '@coze-arch/bot-semi';
|
||||
import { IconWarningInfo } from '@coze-arch/bot-icons';
|
||||
|
||||
import { useSingletonInnerSideSheet } from '../components/workflow-inner-side-sheet';
|
||||
|
||||
// TODO: 改成 UIModal
|
||||
|
||||
import styles from './use-biz-ide-state.module.less';
|
||||
|
||||
interface BizIDEState {
|
||||
/**
|
||||
* 当前开启的 BizIDE 唯一标识
|
||||
*/
|
||||
uniqueId: string | null;
|
||||
/**
|
||||
* 当前是否有 BizIDE 开启
|
||||
*/
|
||||
isBizIDEOpen: boolean;
|
||||
/**
|
||||
* 当前开启的 BizIDE 是否在 test 运行中
|
||||
*/
|
||||
isBizIDETesting: boolean;
|
||||
}
|
||||
|
||||
interface BizIDEStateStore extends BizIDEState {
|
||||
setUniqueId: (uniqueId: string) => void;
|
||||
setIsBizIDEOpen: (isBizIDEOpen: boolean) => void;
|
||||
setIsBizIDETesting: (isBizIDETesting: boolean) => void;
|
||||
setData: (data: Partial<BizIDEState>) => void;
|
||||
}
|
||||
|
||||
const useBizIDEStateStore = create<BizIDEStateStore>(set => ({
|
||||
uniqueId: null,
|
||||
setUniqueId: uniqueId => set({ uniqueId }),
|
||||
isBizIDEOpen: false,
|
||||
setIsBizIDEOpen: isBizIDEOpen => set({ isBizIDEOpen }),
|
||||
isBizIDETesting: false,
|
||||
setIsBizIDETesting: isBizIDETesting => set({ isBizIDETesting }),
|
||||
setData: (data: Partial<BizIDEState>) => set(data),
|
||||
}));
|
||||
|
||||
export const useBizIDEState = () => {
|
||||
const {
|
||||
uniqueId,
|
||||
isBizIDEOpen,
|
||||
isBizIDETesting,
|
||||
setUniqueId,
|
||||
setIsBizIDEOpen,
|
||||
setIsBizIDETesting,
|
||||
setData,
|
||||
} = useBizIDEStateStore(state => state);
|
||||
|
||||
const {
|
||||
handleOpen: openSideSheet,
|
||||
handleClose: closeSideSheet,
|
||||
visible,
|
||||
forceClose,
|
||||
} = useSingletonInnerSideSheet(uniqueId || '');
|
||||
|
||||
const openBizIDE = async id => {
|
||||
const opened = await openSideSheet(id);
|
||||
if (opened) {
|
||||
setUniqueId(id);
|
||||
setIsBizIDEOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const closeBizIDE = async () => {
|
||||
const closed = await closeSideSheet();
|
||||
if (closed) {
|
||||
setData({
|
||||
uniqueId: null,
|
||||
isBizIDEOpen: false,
|
||||
isBizIDETesting: false,
|
||||
});
|
||||
}
|
||||
return closed;
|
||||
};
|
||||
|
||||
const closeConfirm = async (id?: string): Promise<boolean> => {
|
||||
// 当传入id时,表示关闭指定id的弹窗。当id和当前nodeId不一致时,说明已经关闭了
|
||||
if (id && id !== uniqueId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isBizIDEOpen || !isBizIDETesting) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
Modal.warning({
|
||||
icon: (
|
||||
<IconWarningInfo
|
||||
className={styles.warningIcon}
|
||||
style={{
|
||||
width: 22,
|
||||
height: 22,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
title: I18n.t('workflow_detail_code_is_running'),
|
||||
content: I18n.t('workflow_detail_code_is_terminate_execution'),
|
||||
okType: 'warning',
|
||||
okText: I18n.t('Confirm'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
onOk: () => {
|
||||
setData({
|
||||
uniqueId: null,
|
||||
isBizIDEOpen: false,
|
||||
isBizIDETesting: false,
|
||||
});
|
||||
resolve(true);
|
||||
},
|
||||
onCancel: () => {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const forceCloseBizIDE = () => {
|
||||
if (visible) {
|
||||
forceClose();
|
||||
}
|
||||
|
||||
setData({
|
||||
uniqueId: null,
|
||||
isBizIDEOpen: false,
|
||||
isBizIDETesting: false,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
uniqueId,
|
||||
isBizIDEOpen,
|
||||
isBizIDETesting,
|
||||
setBizIDEUniqueId: setUniqueId,
|
||||
setIsBizIDEOpen,
|
||||
setIsBizIDETesting,
|
||||
/**
|
||||
* 关闭 Biz IDE
|
||||
* 检测是否正在运行中,包括 confirm 对话框的出现也封装在这里
|
||||
* 外部只需要调用这个 hook 即可
|
||||
* 在三种情况下会 resolve true,并关闭 BizIDE
|
||||
* 1. BizIDE 没有被打开
|
||||
2. BizIDE 被打开了,但是不在运行中
|
||||
3. BizIDE 在运行中,但是用户点击了 confirm 确认
|
||||
*/
|
||||
closeBizIDE,
|
||||
/**
|
||||
* 强制关闭 Biz IDE,不管是否在运行中
|
||||
*/
|
||||
openBizIDE,
|
||||
forceCloseBizIDE,
|
||||
closeConfirm,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { useWorkflowNode } from '@coze-workflow/base';
|
||||
|
||||
/**
|
||||
* 获取数据库节点选中的数据库ID
|
||||
* @returns 返回当前数据库ID
|
||||
*/
|
||||
export function useCurrentDatabaseID() {
|
||||
const { data } = useWorkflowNode();
|
||||
const databaseList = data?.databaseInfoList ?? data?.inputs?.databaseInfoList;
|
||||
return databaseList?.[0]?.databaseInfoID;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { MessageBizType } from '@coze-arch/idl/workflow_api';
|
||||
import type { Disposable } from '@flowgram-adapter/common';
|
||||
|
||||
import { useNewDatabaseQuery } from './use-new-database-query';
|
||||
import { useDependencyService } from './use-dependency-service';
|
||||
import { useDatabaseNodeService } from './use-database-node-service';
|
||||
import { useCurrentDatabaseID } from './use-current-database-id';
|
||||
|
||||
/**
|
||||
* 获取当前数据库的查询
|
||||
* @returns 返回数据库查询结果
|
||||
* - data: 查询成功时返回数据库对象,无数据时返回undefined
|
||||
* - isLoading: 加载状态
|
||||
* - error: 查询失败时的错误对象
|
||||
*/
|
||||
export function useCurrentDatabaseQuery() {
|
||||
const currentDatabaseID = useCurrentDatabaseID();
|
||||
const { data, isLoading, error } = useNewDatabaseQuery(currentDatabaseID);
|
||||
const disposeRef: React.MutableRefObject<Disposable | null> =
|
||||
useRef<Disposable>(null);
|
||||
const databaseNodeService = useDatabaseNodeService();
|
||||
const dependencyService = useDependencyService();
|
||||
|
||||
useEffect(() => {
|
||||
databaseNodeService.load(currentDatabaseID);
|
||||
if (!disposeRef.current) {
|
||||
disposeRef.current = dependencyService.onDependencyChange(source => {
|
||||
if (source?.bizType === MessageBizType.Database) {
|
||||
// 数据库资源更新时,重新请求接口
|
||||
databaseNodeService.load(currentDatabaseID);
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
disposeRef?.current?.dispose?.();
|
||||
disposeRef.current = null;
|
||||
};
|
||||
}, [currentDatabaseID]);
|
||||
|
||||
return { data, isLoading, error };
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { type DatabseNodeStore } from '@/services/database-node-service-impl';
|
||||
import { DatabaseNodeService } from '@/services/database-node-service';
|
||||
|
||||
export function useDatabaseNodeService() {
|
||||
const databaseNodeService =
|
||||
useService<DatabaseNodeService>(DatabaseNodeService);
|
||||
|
||||
return databaseNodeService;
|
||||
}
|
||||
|
||||
export const useDatabaseServiceStore = <T>(
|
||||
selector: (s: DatabseNodeStore) => T,
|
||||
) => {
|
||||
const databaseNodeService = useDatabaseNodeService();
|
||||
|
||||
return databaseNodeService.store(selector);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
import { MessageBizType } from '@coze-arch/idl/workflow_api';
|
||||
import { type Dataset } from '@coze-arch/bot-api/knowledge';
|
||||
import { type Disposable } from '@flowgram-adapter/common';
|
||||
|
||||
import { useGlobalState } from './use-global-state';
|
||||
import { useDependencyService } from './use-dependency-service';
|
||||
|
||||
export const useDataSetInfos = ({ ids }: { ids: string[] }) => {
|
||||
const [dataSets, setDataSets] = useState<Dataset[]>([]);
|
||||
const [isReady, setReady] = useState(false);
|
||||
const { spaceId, sharedDataSetStore } = useGlobalState();
|
||||
const dependencyService = useDependencyService();
|
||||
|
||||
const disposeRef: React.MutableRefObject<Disposable | null> =
|
||||
useRef<Disposable>(null);
|
||||
|
||||
const getDataSetInfos = useCallback(
|
||||
async (_ids: string[]) => {
|
||||
try {
|
||||
const _dataSets = await sharedDataSetStore.getDataSetInfosByIds(
|
||||
_ids,
|
||||
spaceId,
|
||||
);
|
||||
setDataSets(_dataSets);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setReady(true);
|
||||
}
|
||||
},
|
||||
[spaceId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getDataSetInfos(ids);
|
||||
if (!disposeRef.current) {
|
||||
disposeRef.current = dependencyService.onDependencyChange(source => {
|
||||
if (source?.bizType === MessageBizType.Dataset) {
|
||||
getDataSetInfos(ids);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
disposeRef?.current?.dispose?.();
|
||||
disposeRef.current = null;
|
||||
};
|
||||
}, [ids.join('')]);
|
||||
|
||||
return {
|
||||
dataSets,
|
||||
isReady,
|
||||
cacheDataSetInfo: sharedDataSetStore.addDataSetInfo,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useConfigEntity, useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowDependencyService } from '@/services/workflow-dependency-service';
|
||||
import { WorkflowDependencyStateEntity } from '@/entities';
|
||||
|
||||
export const useDependencyService = () =>
|
||||
useService<WorkflowDependencyService>(WorkflowDependencyService);
|
||||
|
||||
export const useDependencyEntity = () => {
|
||||
const entity = useConfigEntity<WorkflowDependencyStateEntity>(
|
||||
WorkflowDependencyStateEntity,
|
||||
true,
|
||||
);
|
||||
return entity;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export enum EditorTheme {
|
||||
Light = 'light',
|
||||
Dark = 'dark',
|
||||
}
|
||||
|
||||
interface EditorThemeState {
|
||||
editorTheme: EditorTheme;
|
||||
setEditorTheme: (next: EditorTheme) => void;
|
||||
isDarkTheme: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const EditorThemeContext = createContext<EditorThemeState>({
|
||||
editorTheme: EditorTheme.Light,
|
||||
setEditorTheme: _next => {
|
||||
console.log(_next);
|
||||
},
|
||||
isDarkTheme: false,
|
||||
});
|
||||
|
||||
export const useEditorThemeState = () => useContext(EditorThemeContext);
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { useEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowExecStateEntity } from '../entities/workflow-exec-state-entity';
|
||||
|
||||
export const useExecStateEntity = () => {
|
||||
const entity = useEntity<WorkflowExecStateEntity>(WorkflowExecStateEntity);
|
||||
|
||||
return entity;
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowFloatLayoutService } from '@/services/workflow-float-layout-service';
|
||||
|
||||
export const useFloatLayoutService = () => {
|
||||
const floatLayoutService = useService(WorkflowFloatLayoutService);
|
||||
return floatLayoutService;
|
||||
};
|
||||
|
||||
export const useFloatLayoutSize = () => {
|
||||
const floatLayoutService = useFloatLayoutService();
|
||||
const [size, setSize] = useState(floatLayoutService.size);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = floatLayoutService.onSizeChange(setSize);
|
||||
return () => disposable.dispose();
|
||||
}, [floatLayoutService, setSize]);
|
||||
|
||||
return size;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 { throttle, once } from 'lodash-es';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
type GetMetaRoleListResponse,
|
||||
RoleType,
|
||||
} from '@coze-arch/idl/social_api';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import { SocialApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { useGlobalState } from './use-global-state';
|
||||
import { useGetWorkflowMode } from './use-get-workflow-mode';
|
||||
|
||||
const warn = once(
|
||||
throttle(() => Toast.warning('当前工作流未关联场景'), 10 * 1000),
|
||||
);
|
||||
|
||||
const useQuerySceneFlowMetaRole = () => {
|
||||
const globalState = useGlobalState();
|
||||
const { isSceneFlow } = useGetWorkflowMode();
|
||||
const { bindBizID } = globalState;
|
||||
if (isSceneFlow && !bindBizID) {
|
||||
warn();
|
||||
}
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['scene_flow_role_list'],
|
||||
staleTime: 10 * 1000,
|
||||
queryFn: () =>
|
||||
SocialApi.GetMetaRoleList({
|
||||
meta_id: bindBizID as string,
|
||||
}),
|
||||
placeholderData: {
|
||||
role_list: [],
|
||||
} as unknown as GetMetaRoleListResponse,
|
||||
enabled: isSceneFlow && !!bindBizID,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetSceneFlowRoleList = () => {
|
||||
const { data: res, isLoading } = useQuerySceneFlowMetaRole();
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
data: res?.role_list.map(item => ({
|
||||
biz_role_id: item.biz_role_id as string,
|
||||
role: item.name,
|
||||
nickname: item.nickname,
|
||||
role_type: item.role_type,
|
||||
description: item.description,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
export const useGetSceneFlowBot = () => {
|
||||
const { data: res, isLoading } = useQuerySceneFlowMetaRole();
|
||||
if (isLoading) {
|
||||
return null;
|
||||
} else {
|
||||
const host = res?.role_list?.find(item => item.role_type === RoleType.Host);
|
||||
return {
|
||||
name: host?.name,
|
||||
participantId: host?.participant_id,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 这个 hooks 用来快速判断 workflow 的类型
|
||||
*/
|
||||
|
||||
import { useEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import { WorkflowMode } from '@coze-workflow/base/api';
|
||||
|
||||
import { WorkflowGlobalStateEntity } from '../typing';
|
||||
|
||||
export const useGetWorkflowMode = () => {
|
||||
const globalState = useEntity<WorkflowGlobalStateEntity>(
|
||||
WorkflowGlobalStateEntity,
|
||||
);
|
||||
|
||||
const isImageFlow = globalState.flowMode === WorkflowMode.Imageflow;
|
||||
const isSceneFlow = globalState.flowMode === WorkflowMode.SceneFlow;
|
||||
const isChatflow = globalState.flowMode === WorkflowMode.ChatFlow;
|
||||
// const isSceneFlow = true;
|
||||
|
||||
return {
|
||||
isImageFlow,
|
||||
isSceneFlow,
|
||||
isChatflow,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { useConfigEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowGlobalStateEntity } from '../entities';
|
||||
|
||||
/** 获取全局状态 */
|
||||
export const useGlobalState = (
|
||||
listenChange = true,
|
||||
): WorkflowGlobalStateEntity => {
|
||||
const globalState = useConfigEntity<WorkflowGlobalStateEntity>(
|
||||
WorkflowGlobalStateEntity,
|
||||
listenChange,
|
||||
);
|
||||
return globalState;
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { PUBLIC_SPACE_ID } from '@coze-workflow/base/constants';
|
||||
import { workflowApi } from '@coze-workflow/base';
|
||||
|
||||
import { useGlobalState } from './use-global-state';
|
||||
|
||||
// 判断当前是否有协作者
|
||||
export function useHaveCollaborators() {
|
||||
const { spaceId, workflowId } = useGlobalState();
|
||||
const [haveCollaborators, setHaveCollaborators] = useState<
|
||||
boolean | undefined
|
||||
>();
|
||||
|
||||
useEffect(() => {
|
||||
if (spaceId === PUBLIC_SPACE_ID) {
|
||||
setHaveCollaborators(false);
|
||||
return;
|
||||
}
|
||||
|
||||
workflowApi
|
||||
.ListCollaborators(
|
||||
{
|
||||
workflow_id: workflowId,
|
||||
space_id: spaceId,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
)
|
||||
.then(({ data }) => {
|
||||
setHaveCollaborators(data.length > 1);
|
||||
});
|
||||
});
|
||||
|
||||
return haveCollaborators;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useThrottleEffect } from 'ahooks';
|
||||
import { ExpressionEditorTreeHelper } from '@coze-workflow/components';
|
||||
import {
|
||||
type InputVariable,
|
||||
useWorkflowNode,
|
||||
type ViewVariableType,
|
||||
} from '@coze-workflow/base';
|
||||
|
||||
import { useNodeAvailableVariablesWithNode } from '../form-extensions/hooks';
|
||||
|
||||
const useInputs = (): {
|
||||
name: string;
|
||||
id?: string;
|
||||
keyPath?: string[];
|
||||
}[] => {
|
||||
const workflowNode = useWorkflowNode();
|
||||
const inputs = (
|
||||
(workflowNode?.inputParameters || []) as {
|
||||
name: string;
|
||||
input: {
|
||||
content: {
|
||||
keyPath: string[];
|
||||
};
|
||||
};
|
||||
}[]
|
||||
).map(i => ({
|
||||
...i,
|
||||
keyPath: [...(i.input?.content?.keyPath || [])], // 深拷贝一份
|
||||
}));
|
||||
return inputs;
|
||||
};
|
||||
|
||||
export const useInputVariables = (props?: {
|
||||
needNullName?: boolean;
|
||||
needNullType?: boolean;
|
||||
}) => {
|
||||
const { needNullName = true, needNullType = false } = props ?? {};
|
||||
const availableVariables = useNodeAvailableVariablesWithNode();
|
||||
const inputs = useInputs();
|
||||
const inputsWithVariables = ExpressionEditorTreeHelper.findAvailableVariables(
|
||||
{
|
||||
variables: availableVariables,
|
||||
inputs,
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const _variables = inputsWithVariables.map((v, i) => ({
|
||||
name: v.name,
|
||||
id: inputs[i].id,
|
||||
type: v.variable?.type as ViewVariableType,
|
||||
index: i,
|
||||
}));
|
||||
|
||||
const [variables, setVariables] = useState<InputVariable[]>();
|
||||
|
||||
useThrottleEffect(
|
||||
() => {
|
||||
setVariables(
|
||||
_variables.filter(
|
||||
v =>
|
||||
(needNullName ? true : !!v.name) &&
|
||||
(needNullType ? true : !!v.type),
|
||||
),
|
||||
);
|
||||
},
|
||||
[
|
||||
_variables.map(d => `${d.name}${d.type}`).join(''),
|
||||
needNullName,
|
||||
needNullType,
|
||||
],
|
||||
{
|
||||
wait: 300,
|
||||
},
|
||||
);
|
||||
|
||||
return variables;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
WorkflowDocument,
|
||||
type WorkflowJSON,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowSaveService } from '../services';
|
||||
|
||||
export const useLatestWorkflowJson = () => {
|
||||
const workflowDocument = useService<WorkflowDocument>(WorkflowDocument);
|
||||
|
||||
const saveService = useService<WorkflowSaveService>(WorkflowSaveService);
|
||||
|
||||
const getLatestWorkflowJson = async (): Promise<WorkflowJSON> => {
|
||||
await saveService.waitSaving();
|
||||
|
||||
return workflowDocument.toJSON() as WorkflowJSON;
|
||||
};
|
||||
|
||||
return { getLatestWorkflowJson };
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowLinesService } from '../services/workflow-line-service';
|
||||
|
||||
export const useLineService = () => {
|
||||
const lineService = useService<WorkflowLinesService>(WorkflowLinesService);
|
||||
|
||||
return lineService;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { type WorkflowDatabase, ViewVariableType } from '@coze-workflow/base';
|
||||
import { FieldItemType, type DatabaseInfo } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { useDatabaseServiceStore } from './use-database-node-service';
|
||||
|
||||
/**
|
||||
* 查询数据库信息的Hook
|
||||
* @param id 数据库ID
|
||||
* @returns 返回对象包含:
|
||||
* - data: 查询成功时返回数据库信息,无数据时返回undefined
|
||||
* - isLoading: 加载状态
|
||||
* - error: 查询失败时的错误对象
|
||||
*/
|
||||
export function useNewDatabaseQuery(id?: string) {
|
||||
const { getDatabaseDetail, isLoading, getError } = useDatabaseServiceStore(
|
||||
useShallow(state => ({
|
||||
getDatabaseDetail: state.getData,
|
||||
isLoading: state.loading,
|
||||
getError: state.getError,
|
||||
})),
|
||||
);
|
||||
const rawData = getDatabaseDetail(id);
|
||||
const data = transformRawDatabaseToDatabase(rawData?.database_info);
|
||||
|
||||
return { data, isLoading, error: getError(id) };
|
||||
}
|
||||
|
||||
function transformRawDatabaseToDatabase(
|
||||
rawDatabase?: DatabaseInfo,
|
||||
): WorkflowDatabase | undefined {
|
||||
if (!rawDatabase) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: rawDatabase.id as string,
|
||||
fields: rawDatabase.field_list?.map(field => ({
|
||||
id: field.alterId as number,
|
||||
name: field.name,
|
||||
type: fieldItemTypeToViewVariableType(field.type),
|
||||
required: field.must_required,
|
||||
description: field.desc,
|
||||
isSystemField: field.name
|
||||
? ['id', 'uuid', 'bstudio_create_time'].includes(field.name)
|
||||
: false,
|
||||
})),
|
||||
iconUrl: rawDatabase.icon_url,
|
||||
tableName: rawDatabase.table_name,
|
||||
};
|
||||
}
|
||||
|
||||
function fieldItemTypeToViewVariableType(type?: FieldItemType) {
|
||||
const typeMap = {
|
||||
[FieldItemType.Text]: ViewVariableType.String,
|
||||
[FieldItemType.Number]: ViewVariableType.Integer,
|
||||
[FieldItemType.Float]: ViewVariableType.Number,
|
||||
[FieldItemType.Boolean]: ViewVariableType.Boolean,
|
||||
[FieldItemType.Date]: ViewVariableType.Time,
|
||||
};
|
||||
|
||||
if (!type) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return typeMap[type] || undefined;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { pick } from 'lodash-es';
|
||||
import { FlowNodeRenderData } from '@flowgram-adapter/free-layout-editor';
|
||||
import { useCurrentEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
type SimpleNodeRenderData = Pick<FlowNodeRenderData, 'expanded' | 'node'>;
|
||||
|
||||
const pickSimpleNodeRenderData = (data: FlowNodeRenderData) =>
|
||||
pick(data, 'expanded', 'node');
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 获取当前节点的渲染数据,包括expanded等渲染相关的状态
|
||||
*/
|
||||
export const useNodeRenderData = () => {
|
||||
const node = useCurrentEntity();
|
||||
const initialRenderData =
|
||||
node.getData<FlowNodeRenderData>(FlowNodeRenderData);
|
||||
const [nodeRenderData, setNodeRenderData] = useState<SimpleNodeRenderData>(
|
||||
pickSimpleNodeRenderData(initialRenderData),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = initialRenderData.onDataChange(data => {
|
||||
setNodeRenderData(pickSimpleNodeRenderData(data as FlowNodeRenderData));
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable?.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...nodeRenderData,
|
||||
expanded: true, // Coze V2 没有节点折叠
|
||||
toggleNodeExpand: initialRenderData.toggleExpand.bind(initialRenderData),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { NodeRenderSceneContext } from '@/contexts/node-render-context';
|
||||
|
||||
export function useNodeRenderScene() {
|
||||
const scene = useContext(NodeRenderSceneContext);
|
||||
|
||||
return {
|
||||
isNewNodeRender: scene === 'new-node-render',
|
||||
isOldNodeRender: scene === 'old-node-render',
|
||||
isNodeSideSheet: scene === 'node-side-sheet',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 { createContext, useContext, type ReactNode } from 'react';
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface NodeSideSheetStore {
|
||||
leftPanelVisible?: boolean;
|
||||
leftPanelWidth?: number;
|
||||
leftPanelContent?: ReactNode;
|
||||
leftPanelContentId?: string;
|
||||
openLeftPanel: (options: { content: ReactNode; contentId?: string }) => void;
|
||||
updateLeftPanel: (options: { content: ReactNode }) => void;
|
||||
closeLeftPanel: () => void;
|
||||
rightPanelVisible?: boolean;
|
||||
rightPanelWidth: number;
|
||||
rightPanelContent?: ReactNode;
|
||||
openRightPanel: (options: { content: ReactNode }) => void;
|
||||
closeRightPanel: () => void;
|
||||
isNodeSideSheetVisible: boolean;
|
||||
openNodeSideSheet: (options?: { width?: number }) => void;
|
||||
mainPanelWidth?: number;
|
||||
setMainPanelWidth: (width: number) => void;
|
||||
closeNodeSideSheet: () => void;
|
||||
closeAllExtraSheets: () => void;
|
||||
}
|
||||
|
||||
export const useNodeSideSheetStore = create<NodeSideSheetStore>(set => ({
|
||||
isNodeSideSheetVisible: false,
|
||||
leftPanelVisible: false,
|
||||
mainPanelWidth: 360,
|
||||
setMainPanelWidth: width => set({ mainPanelWidth: width }),
|
||||
openNodeSideSheet: options =>
|
||||
set(state => ({
|
||||
isNodeSideSheetVisible: true,
|
||||
width: options?.width || state.mainPanelWidth,
|
||||
})),
|
||||
closeNodeSideSheet: () => {
|
||||
set({
|
||||
isNodeSideSheetVisible: false,
|
||||
leftPanelVisible: false,
|
||||
leftPanelContent: undefined,
|
||||
rightPanelVisible: false,
|
||||
rightPanelContent: undefined,
|
||||
});
|
||||
},
|
||||
openLeftPanel: options =>
|
||||
set({
|
||||
leftPanelVisible: true,
|
||||
leftPanelContent: options.content,
|
||||
leftPanelContentId: options.contentId,
|
||||
}),
|
||||
|
||||
updateLeftPanel: options =>
|
||||
set({
|
||||
leftPanelContent: options.content,
|
||||
}),
|
||||
|
||||
closeLeftPanel: () =>
|
||||
set({
|
||||
leftPanelVisible: false,
|
||||
leftPanelContent: undefined,
|
||||
leftPanelContentId: '',
|
||||
}),
|
||||
rightPanelWidth: 360,
|
||||
openRightPanel: options =>
|
||||
set({
|
||||
rightPanelVisible: true,
|
||||
rightPanelContent: options.content,
|
||||
}),
|
||||
closeRightPanel: () =>
|
||||
set({
|
||||
rightPanelVisible: false,
|
||||
rightPanelContent: undefined,
|
||||
}),
|
||||
closeAllExtraSheets: () => {
|
||||
set({
|
||||
leftPanelVisible: false,
|
||||
leftPanelContent: undefined,
|
||||
rightPanelVisible: false,
|
||||
rightPanelContent: undefined,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
interface NodeFormPanelState {
|
||||
fullscreenPanel: React.ReactNode;
|
||||
setFullscreenPanel: (next: React.ReactNode) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const NodeFormPanelContext = createContext(
|
||||
{} as unknown as NodeFormPanelState,
|
||||
);
|
||||
|
||||
export const useNodeFormPanelState = () => useContext(NodeFormPanelContext);
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { LayoutPanelKey } from '@/constants';
|
||||
|
||||
import { useFloatLayoutService } from './use-float-layout-service';
|
||||
|
||||
interface OpenOptions {
|
||||
defaultTab?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const useOpenTraceListPanel = () => {
|
||||
const floatLayoutService = useFloatLayoutService();
|
||||
|
||||
const open = useCallback(
|
||||
(options?: OpenOptions) => {
|
||||
floatLayoutService.open(LayoutPanelKey.TraceList, 'bottom', options);
|
||||
},
|
||||
[floatLayoutService],
|
||||
);
|
||||
|
||||
return { open };
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import {
|
||||
PluginNodeService,
|
||||
type PluginNodeStore,
|
||||
} from '@/services/plugin-node-service';
|
||||
|
||||
export const usePluginNodeService = () =>
|
||||
useService<PluginNodeService>(PluginNodeService);
|
||||
|
||||
export const usePluginNodeStore = <T>(selector: (s: PluginNodeStore) => T) => {
|
||||
const pluginService = usePluginNodeService();
|
||||
return pluginService.store(selector);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { HistoryService, WorkflowHistoryConfig } from '@coze-workflow/history';
|
||||
|
||||
export const useRedoUndo = () => {
|
||||
const historyService = useService<HistoryService>(HistoryService);
|
||||
const config = useService<WorkflowHistoryConfig>(WorkflowHistoryConfig);
|
||||
|
||||
return {
|
||||
start: () => {
|
||||
historyService.start();
|
||||
config.disabled = false;
|
||||
},
|
||||
stop: () => {
|
||||
historyService.stop();
|
||||
config.disabled = true;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useRefInput } from './use-ref-input';
|
||||
export { useRefInputProps } from './use-ref-input-props';
|
||||
export { useRefInputNode } from './use-ref-input-node';
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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,
|
||||
type ReactNode,
|
||||
type CSSProperties,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
ValueExpression,
|
||||
ValueExpressionType,
|
||||
type RefExpression,
|
||||
type ViewVariableType,
|
||||
} from '@coze-workflow/base';
|
||||
import { type TreeNodeData } from '@coze-arch/bot-semi/Tree';
|
||||
import { IconCozApply } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, type TreeSelectProps } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
RefValueDisplay,
|
||||
type RefTagColor,
|
||||
type RefValueDisplayProps,
|
||||
} from '@/form-extensions/components/value-expression-input/ref-value-display';
|
||||
import {
|
||||
type CustomFilterVar,
|
||||
type VariableTreeDataNode,
|
||||
type RenderDisplayVarName,
|
||||
} from '@/form-extensions/components/tree-variable-selector/types';
|
||||
import {
|
||||
VariableSelector,
|
||||
type VariableSelectorProps,
|
||||
} from '@/form-extensions/components/tree-variable-selector';
|
||||
export const useRefInputNode = ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
disabled,
|
||||
variablesDataSource,
|
||||
validateStatus,
|
||||
readonly,
|
||||
invalidContent,
|
||||
renderDisplayVarName,
|
||||
testId,
|
||||
disabledTypes,
|
||||
showClear = false,
|
||||
customFilterVar,
|
||||
setFocused,
|
||||
style,
|
||||
refTagColor,
|
||||
hideDeleteIcon,
|
||||
variableTagStyle,
|
||||
optionFilter,
|
||||
renderExtraOption,
|
||||
enableSelectNode,
|
||||
popoverStyle,
|
||||
handleDataSource,
|
||||
variableTypeConstraints,
|
||||
}: {
|
||||
value?: ValueExpression;
|
||||
onChange: (v: ValueExpression | undefined) => void;
|
||||
onBlur?: () => void;
|
||||
disabled?: boolean;
|
||||
variablesDataSource?: VariableTreeDataNode[];
|
||||
validateStatus?: TreeSelectProps['validateStatus'];
|
||||
readonly?: boolean;
|
||||
testId?: string;
|
||||
disabledTypes?: ViewVariableType[];
|
||||
showClear?: boolean;
|
||||
invalidContent?: string;
|
||||
renderDisplayVarName?: RenderDisplayVarName;
|
||||
customFilterVar?: CustomFilterVar;
|
||||
setFocused?: (focused: boolean) => void;
|
||||
style?: CSSProperties;
|
||||
refTagColor?: RefTagColor;
|
||||
hideDeleteIcon?: boolean;
|
||||
variableTagStyle?: CSSProperties;
|
||||
optionFilter?: VariableSelectorProps['optionFilter'];
|
||||
handleDataSource?: VariableSelectorProps['handleDataSource'];
|
||||
renderExtraOption?: (
|
||||
data?: TreeNodeData[],
|
||||
action?: {
|
||||
hiddenPopover: () => void;
|
||||
},
|
||||
) => ReactNode;
|
||||
enableSelectNode?: boolean;
|
||||
popoverStyle?: CSSProperties;
|
||||
/* 类型限制,引用类型不满足限制时,显示警告信息 */
|
||||
variableTypeConstraints?: RefValueDisplayProps['variableTypeConstraints'];
|
||||
}) => {
|
||||
const onRefChange = useCallback(
|
||||
(v: string[] | undefined): void => {
|
||||
if (v === undefined) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange({
|
||||
type: ValueExpressionType.REF,
|
||||
content: { keyPath: v as string[] },
|
||||
});
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleRefRemove = () => {
|
||||
onChange?.(undefined);
|
||||
onBlur?.();
|
||||
};
|
||||
|
||||
const [focused, setStateFocused] = useState(false);
|
||||
const _setFocused = useCallback(
|
||||
(v: boolean) => {
|
||||
setStateFocused(v);
|
||||
setFocused?.(v);
|
||||
},
|
||||
[setFocused],
|
||||
);
|
||||
|
||||
const renderVariableSelect = (trigger: ReactNode) =>
|
||||
readonly && trigger ? (
|
||||
trigger
|
||||
) : (
|
||||
<VariableSelector
|
||||
testId={testId}
|
||||
disabled={disabled}
|
||||
disabledTypes={disabledTypes}
|
||||
dataSource={variablesDataSource}
|
||||
value={
|
||||
value && ValueExpression.isRef(value)
|
||||
? (value.content?.keyPath as VariableSelectorProps['value'])
|
||||
: undefined
|
||||
}
|
||||
onChange={onRefChange}
|
||||
onBlur={onBlur}
|
||||
validateStatus={validateStatus}
|
||||
readonly={readonly}
|
||||
showClear={showClear}
|
||||
customFilterVar={customFilterVar}
|
||||
onPopoverVisibleChange={_setFocused}
|
||||
trigger={trigger}
|
||||
style={style}
|
||||
invalidContent={invalidContent}
|
||||
renderDisplayVarName={renderDisplayVarName}
|
||||
optionFilter={optionFilter}
|
||||
renderExtraOption={renderExtraOption}
|
||||
enableSelectNode={enableSelectNode}
|
||||
popoverStyle={popoverStyle}
|
||||
handleDataSource={handleDataSource}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderVariableDisplay = (props?: { needWrapper?: boolean }) =>
|
||||
props?.needWrapper ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'w-full max-w-[100%] h-[24px] pr-[4px]',
|
||||
'flex flex-row items-center justify-between',
|
||||
'bg-transparent hover:bg-background-5 active:bg-background-6',
|
||||
'rounded-lg border border-solid coz-stroke-plus hover:coz-mg-secondary-hovered active:coz-mg-secondary-pressed',
|
||||
{
|
||||
'semi-input-wrapper-error': validateStatus === 'error',
|
||||
'coz-stroke-primary': validateStatus !== 'error',
|
||||
'!coz-stroke-hglt': focused,
|
||||
'pointer-events-none': readonly,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{renderVariableDisplay()}
|
||||
<div>
|
||||
{renderVariableSelect(
|
||||
<IconButton
|
||||
size="mini"
|
||||
color="secondary"
|
||||
icon={<IconCozApply className="text-[16px]" />}
|
||||
/>,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
renderVariableSelect(
|
||||
<div
|
||||
className={classNames(
|
||||
'cursor-pointer w-full overflow-hidden flex items-center pl-0.5',
|
||||
)}
|
||||
>
|
||||
<RefValueDisplay
|
||||
value={value as RefExpression}
|
||||
onClose={handleRefRemove}
|
||||
tagColor={refTagColor}
|
||||
closable={!hideDeleteIcon}
|
||||
style={variableTagStyle}
|
||||
variableTypeConstraints={variableTypeConstraints}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</div>,
|
||||
)
|
||||
);
|
||||
return {
|
||||
renderVariableSelect,
|
||||
renderVariableDisplay,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { get } from 'lodash-es';
|
||||
import { type FeedbackStatus } from '@flowgram-adapter/free-layout-editor';
|
||||
import { type FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
WorkflowVariableService,
|
||||
useVariableTypeChange,
|
||||
} from '@coze-workflow/variable';
|
||||
import {
|
||||
ValueExpressionType,
|
||||
type ValueExpression,
|
||||
type ViewVariableType,
|
||||
} from '@coze-workflow/base';
|
||||
|
||||
import { useNodeAvailableVariablesWithNode } from '@/form-extensions/hooks';
|
||||
import { feedbackStatus2ValidateStatus } from '@/form-extensions/components/utils';
|
||||
import { formatWithNodeVariables } from '@/form-extensions/components/tree-variable-selector/utils';
|
||||
|
||||
export const useRefInputProps = ({
|
||||
disabledTypes,
|
||||
value,
|
||||
onChange,
|
||||
node,
|
||||
feedbackStatus,
|
||||
}: {
|
||||
disabledTypes?: ViewVariableType[];
|
||||
value?: ValueExpression;
|
||||
onChange: (v: ValueExpression) => void;
|
||||
node: FlowNodeEntity;
|
||||
feedbackStatus?: FeedbackStatus;
|
||||
}) => {
|
||||
const availableVariables = useNodeAvailableVariablesWithNode();
|
||||
|
||||
const variableService: WorkflowVariableService = useService(
|
||||
WorkflowVariableService,
|
||||
);
|
||||
const variablesDataSource = formatWithNodeVariables(
|
||||
availableVariables,
|
||||
disabledTypes || [],
|
||||
);
|
||||
|
||||
const keyPath = get(value, 'content.keyPath') as unknown as string[];
|
||||
|
||||
// 监听联动变量变化,从而重新触发 effect
|
||||
useEffect(() => {
|
||||
const hasDisabledTypes =
|
||||
Array.isArray(disabledTypes) && disabledTypes.length > 0;
|
||||
|
||||
if (!keyPath || !hasDisabledTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = variableService.onListenVariableTypeChange(
|
||||
keyPath,
|
||||
v => {
|
||||
// 如果变量类型变化后,位于 disabledTypes 中,那么需要清空
|
||||
if (v && (disabledTypes || []).includes(v.type)) {
|
||||
onChange({
|
||||
type: ValueExpressionType.REF,
|
||||
});
|
||||
}
|
||||
},
|
||||
{ node },
|
||||
);
|
||||
|
||||
return () => {
|
||||
listener?.dispose();
|
||||
};
|
||||
}, [keyPath, disabledTypes]);
|
||||
|
||||
useVariableTypeChange({
|
||||
keyPath,
|
||||
onTypeChange: ({ variableMeta: v }) => {
|
||||
const hasDisabledTypes =
|
||||
Array.isArray(disabledTypes) && disabledTypes.length > 0;
|
||||
if (!hasDisabledTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (v && (disabledTypes || []).includes(v.type)) {
|
||||
onChange({
|
||||
type: ValueExpressionType.REF,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
variablesDataSource,
|
||||
validateStatus: feedbackStatus2ValidateStatus(feedbackStatus),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 CSSProperties } from 'react';
|
||||
|
||||
import { type FeedbackStatus } from '@flowgram-adapter/free-layout-editor';
|
||||
import { type FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
type ValueExpression,
|
||||
type ViewVariableType,
|
||||
} from '@coze-workflow/base/types';
|
||||
import { type TreeSelectProps } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
type CustomFilterVar,
|
||||
type RenderDisplayVarName,
|
||||
} from '@/form-extensions/components/tree-variable-selector/types';
|
||||
|
||||
import { useRefInputProps } from './use-ref-input-props';
|
||||
import { useRefInputNode } from './use-ref-input-node';
|
||||
|
||||
export const useRefInput = ({
|
||||
node,
|
||||
feedbackStatus,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
disabled,
|
||||
readonly,
|
||||
testId,
|
||||
disabledTypes,
|
||||
showClear = false,
|
||||
customFilterVar,
|
||||
setFocused,
|
||||
style,
|
||||
invalidContent,
|
||||
renderDisplayVarName,
|
||||
}: {
|
||||
node: FlowNodeEntity;
|
||||
feedbackStatus?: FeedbackStatus;
|
||||
value?: ValueExpression;
|
||||
onChange: (v: ValueExpression | undefined) => void;
|
||||
onBlur?: () => void;
|
||||
disabled?: boolean;
|
||||
readonly?: boolean;
|
||||
invalidContent?: string;
|
||||
renderDisplayVarName?: RenderDisplayVarName;
|
||||
testId?: string;
|
||||
disabledTypes?: ViewVariableType[];
|
||||
showClear?: boolean;
|
||||
customFilterVar?: CustomFilterVar;
|
||||
style?: CSSProperties;
|
||||
setFocused?: (focused: boolean) => void;
|
||||
}) => {
|
||||
const { variablesDataSource, validateStatus } = useRefInputProps({
|
||||
disabledTypes,
|
||||
value,
|
||||
onChange,
|
||||
node,
|
||||
feedbackStatus,
|
||||
});
|
||||
|
||||
const { renderVariableSelect, renderVariableDisplay } = useRefInputNode({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
disabled,
|
||||
variablesDataSource,
|
||||
validateStatus: validateStatus as TreeSelectProps['validateStatus'],
|
||||
readonly,
|
||||
testId,
|
||||
disabledTypes,
|
||||
invalidContent,
|
||||
renderDisplayVarName,
|
||||
showClear,
|
||||
customFilterVar,
|
||||
setFocused,
|
||||
style,
|
||||
});
|
||||
return {
|
||||
renderVariableSelect,
|
||||
renderVariableDisplay,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { RelatedCaseDataService } from '@/services';
|
||||
|
||||
export const useRelatedBotService = () =>
|
||||
useService<RelatedCaseDataService>(RelatedCaseDataService);
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { persist, devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface ResizableSidePanelStoreState {
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface ResizableSidePanelStoreActions {
|
||||
setWidth: (width: number) => void;
|
||||
}
|
||||
|
||||
type ResizableSidePanelStore = ResizableSidePanelStoreState &
|
||||
ResizableSidePanelStoreActions;
|
||||
|
||||
const NAME = 'workflow-resizable-side-panel';
|
||||
|
||||
/**
|
||||
* 可调节宽度的侧拉窗状态,需要持久化
|
||||
*/
|
||||
export const useResizableSidePanelStore = create<ResizableSidePanelStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
set => ({
|
||||
width: 0,
|
||||
setWidth: width => set({ width }),
|
||||
}),
|
||||
{
|
||||
name: NAME,
|
||||
},
|
||||
),
|
||||
{
|
||||
enabled: IS_DEV_MODE,
|
||||
name: NAME,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { RoleService, type RoleServiceState } from '@/services/role-service';
|
||||
|
||||
export const useRoleService = () => useService<RoleService>(RoleService);
|
||||
|
||||
export const useRoleServiceStore = <T>(
|
||||
selector: (s: RoleServiceState) => T,
|
||||
) => {
|
||||
const roleService = useRoleService();
|
||||
|
||||
return roleService.store(selector);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowSaveService } from '@/services';
|
||||
|
||||
export const useSaveService = () =>
|
||||
useService<WorkflowSaveService>(WorkflowSaveService);
|
||||
@@ -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 {
|
||||
usePlayground,
|
||||
SelectionService,
|
||||
useService,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type PlaygroundConfigRevealOpts } from '@flowgram-adapter/free-layout-editor';
|
||||
import { Rectangle, SizeSchema } from '@flowgram-adapter/common';
|
||||
|
||||
import { useLineService } from './use-line-service';
|
||||
|
||||
export const useScrollToLine = () => {
|
||||
const lineService = useLineService();
|
||||
const playground = usePlayground();
|
||||
|
||||
const selectionService = useService<SelectionService>(SelectionService);
|
||||
|
||||
const scrollToLine = async (fromId: string, toId: string) => {
|
||||
const line = lineService.getLine(fromId, toId);
|
||||
let success = false;
|
||||
if (line) {
|
||||
const bounds = Rectangle.enlarge([line.bounds]).pad(30, 30);
|
||||
|
||||
const viewport = playground.config.getViewport(false);
|
||||
const zoom = SizeSchema.fixSize(bounds, viewport);
|
||||
|
||||
const scrollConfig: PlaygroundConfigRevealOpts = {
|
||||
bounds,
|
||||
zoom,
|
||||
scrollToCenter: true,
|
||||
easing: true,
|
||||
};
|
||||
|
||||
selectionService.selection = [line];
|
||||
|
||||
await playground.config.scrollToView(scrollConfig);
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
return scrollToLine;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import { useService, usePlayground } from '@flowgram-adapter/free-layout-editor';
|
||||
import { WorkflowSelectService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
export const useScrollToNode = () => {
|
||||
const selectServices = useService<WorkflowSelectService>(
|
||||
WorkflowSelectService,
|
||||
);
|
||||
|
||||
const playground = usePlayground();
|
||||
|
||||
const scrollToNode = async (nodeId: string) => {
|
||||
let success = false;
|
||||
const node = playground.entityManager.getEntityById<FlowNodeEntity>(nodeId);
|
||||
|
||||
if (node) {
|
||||
await selectServices.selectNodeAndScrollToView(node, true);
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
return scrollToNode;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useGlobalState } from './use-global-state';
|
||||
|
||||
export const useSpaceId = (): string => {
|
||||
const globalState = useGlobalState();
|
||||
return globalState.spaceId;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 { useConfigEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowTemplateStateEntity } from '@/entities';
|
||||
|
||||
export const useTemplateService = () => {
|
||||
const entity = useConfigEntity<WorkflowTemplateStateEntity>(
|
||||
WorkflowTemplateStateEntity,
|
||||
true,
|
||||
);
|
||||
|
||||
return entity;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 { useConfigEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowTestFormStateEntity } from '../entities';
|
||||
|
||||
export const useTestFormState = () => {
|
||||
const entity = useConfigEntity<WorkflowTestFormStateEntity>(
|
||||
WorkflowTestFormStateEntity,
|
||||
true,
|
||||
);
|
||||
|
||||
return entity;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { TestRunReporterService } from '@/services';
|
||||
|
||||
export const useTestRunReporterService = () =>
|
||||
useService<TestRunReporterService>(TestRunReporterService);
|
||||
161
frontend/packages/workflow/playground/src/hooks/use-test-run.ts
Normal file
161
frontend/packages/workflow/playground/src/hooks/use-test-run.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { type GetWorkFlowProcessData } from '@coze-arch/idl/workflow_api';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { TestRunState } from '../services/workflow-run-service';
|
||||
import { WorkflowRunService } from '../services';
|
||||
import { useTestRunFlow } from '../components/test-run/hooks/use-test-run-flow';
|
||||
import { useExecStateEntity } from './use-exec-state-entity';
|
||||
|
||||
export interface TestRunInstanceAction {
|
||||
/* 触发全流程 testrun */
|
||||
handleTestRun: () => Promise<void>;
|
||||
cancelTestRun: () => Promise<void>;
|
||||
pauseTestRun: () => void;
|
||||
continueTestRun: () => void;
|
||||
|
||||
getTestRunHistory: (config: {
|
||||
/** 是否展示节点结果 */
|
||||
showNodeResults?: boolean;
|
||||
/** 指定执行 ID, 若不填, 则展示最近一次运行结果 */
|
||||
executeId?: string;
|
||||
}) => Promise<GetWorkFlowProcessData>;
|
||||
}
|
||||
|
||||
export interface TestRunInstanceState {
|
||||
/* 是否执行中 */
|
||||
isExecuting: boolean;
|
||||
/* 是否执行成功 */
|
||||
isSucceed: boolean;
|
||||
/* 是否执行失败 */
|
||||
isFailed: boolean;
|
||||
/* 是否取消执行 */
|
||||
isCanceled: boolean;
|
||||
/* 是否暂停 */
|
||||
isPaused: boolean;
|
||||
/* 是否运行结束 */
|
||||
isEnd: boolean;
|
||||
}
|
||||
|
||||
export interface TestRunInstanceCallback {
|
||||
/** testRun 执行前回调 */
|
||||
onBeforeTestRun?: () => void;
|
||||
/** testRun 开始执行回调 */
|
||||
onTestRunStart?: (executeId: string, isSingleMode?: boolean) => void;
|
||||
/** testRun 取消回调 */
|
||||
onTestRunCanceled?: (executeId: string) => void;
|
||||
/** testRun 失败回调 */
|
||||
onTestRunFailed?: (executeId: string) => void;
|
||||
/** testRun 成功回调 */
|
||||
onTestRunSucceed?: (executeId: string) => void;
|
||||
/** testRun 结束回调 */
|
||||
onTestRunEnd?: (testRunState: TestRunState, executeId: string) => void;
|
||||
}
|
||||
|
||||
export type TestRunInstance = TestRunInstanceAction & TestRunInstanceState;
|
||||
|
||||
export const useTestRun = (props?: {
|
||||
callbacks?: TestRunInstanceCallback;
|
||||
}): TestRunInstance => {
|
||||
const { callbacks = {} } = props || {};
|
||||
const {
|
||||
onBeforeTestRun,
|
||||
onTestRunStart,
|
||||
onTestRunEnd,
|
||||
onTestRunFailed,
|
||||
onTestRunCanceled,
|
||||
onTestRunSucceed,
|
||||
} = callbacks;
|
||||
|
||||
const runService = useService<WorkflowRunService>(WorkflowRunService);
|
||||
|
||||
const {
|
||||
config: { executeId, isSingleMode },
|
||||
} = useExecStateEntity();
|
||||
|
||||
const isSingleModeRef = useRef(isSingleMode);
|
||||
isSingleModeRef.current = isSingleMode;
|
||||
|
||||
const { onTestRunStateChange, testRunState } = runService;
|
||||
|
||||
const { testRunFlow } = useTestRunFlow();
|
||||
|
||||
const testRunInstance: TestRunInstance = {
|
||||
handleTestRun: async () => {
|
||||
onBeforeTestRun?.();
|
||||
await testRunFlow();
|
||||
},
|
||||
cancelTestRun: runService.cancelTestRun,
|
||||
pauseTestRun: () => runService.pauseTestRun(),
|
||||
continueTestRun: () => runService.continueTestRun(),
|
||||
getTestRunHistory: config => runService.getProcessResult(config),
|
||||
|
||||
isExecuting: testRunState === TestRunState.Executing,
|
||||
isSucceed: testRunState === TestRunState.Succeed,
|
||||
isFailed: testRunState === TestRunState.Failed,
|
||||
isCanceled: testRunState === TestRunState.Canceled,
|
||||
isPaused: testRunState === TestRunState.Paused,
|
||||
isEnd: [
|
||||
TestRunState.Succeed,
|
||||
TestRunState.Failed,
|
||||
TestRunState.Canceled,
|
||||
].includes(testRunState),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 处理testRun回调
|
||||
const dispose = onTestRunStateChange(({ prevState, curState }) => {
|
||||
if (
|
||||
[
|
||||
TestRunState.Succeed,
|
||||
TestRunState.Failed,
|
||||
TestRunState.Canceled,
|
||||
].includes(curState)
|
||||
) {
|
||||
onTestRunEnd?.(curState, executeId);
|
||||
|
||||
switch (curState) {
|
||||
case TestRunState.Failed:
|
||||
onTestRunFailed?.(executeId);
|
||||
break;
|
||||
case TestRunState.Succeed:
|
||||
onTestRunSucceed?.(executeId);
|
||||
break;
|
||||
case TestRunState.Canceled:
|
||||
onTestRunCanceled?.(executeId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
prevState === TestRunState.Idle &&
|
||||
curState === TestRunState.Executing
|
||||
) {
|
||||
onTestRunStart?.(executeId, isSingleModeRef.current);
|
||||
}
|
||||
});
|
||||
|
||||
return () => dispose.dispose();
|
||||
}, [callbacks, executeId]);
|
||||
|
||||
return testRunInstance;
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
WorkflowNodePortsData,
|
||||
useCurrentEntity,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { useLineService } from './use-line-service';
|
||||
|
||||
/**
|
||||
* 端口排序更新后,需要重新连线。
|
||||
*
|
||||
* 多端口的情况下,使用了index作为portId, 而不是uid。
|
||||
*
|
||||
* 当port排序变更后,index并不会改变,如果希望连线和数据保持一致,只能重新连线。
|
||||
*
|
||||
* */
|
||||
export const useUpdateSortedPortLines = (
|
||||
calcPortId: (index: number) => string,
|
||||
) => {
|
||||
const node = useCurrentEntity();
|
||||
|
||||
const lineService = useLineService();
|
||||
|
||||
/**
|
||||
* 将startIndex 到 endIndex 中间所有的连线全部重新连接
|
||||
* 两两交互,例:
|
||||
*
|
||||
* 0 1 2
|
||||
*
|
||||
* 1 0 2
|
||||
*
|
||||
* 1 2 0
|
||||
* */
|
||||
const updateSortedPortLines = (startIndex: number, endIndex: number) => {
|
||||
if (startIndex === endIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const step = startIndex < endIndex ? 1 : -1;
|
||||
|
||||
for (let i = startIndex; i !== endIndex; i += step) {
|
||||
const oldPortInfo = {
|
||||
from: node.id,
|
||||
fromPort: calcPortId(i),
|
||||
};
|
||||
const newPortInfo = {
|
||||
from: node.id,
|
||||
fromPort: calcPortId(i + step),
|
||||
};
|
||||
lineService.replaceLineByPort(oldPortInfo, newPortInfo);
|
||||
}
|
||||
|
||||
// 拖拽结束后端口dom对应的portId变更,需要更新一下
|
||||
node
|
||||
.getData<WorkflowNodePortsData>(WorkflowNodePortsData)
|
||||
.updateDynamicPorts();
|
||||
};
|
||||
|
||||
return updateSortedPortLines;
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
// 支持预览的图片类型
|
||||
export const PREVIEW_IMAGE_TYPE = ['jpg', 'jpeg', 'png', 'webp', 'svg'];
|
||||
|
||||
export const MAX_IMAGE_SIZE = 1024 * 1024 * 5;
|
||||
/**
|
||||
* 文件扩展至 500MB
|
||||
*/
|
||||
export const MAX_FILE_SIZE = 1024 * 1024 * 500;
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useUpload, UploadConfig } from './use-upload';
|
||||
export {
|
||||
getAccept,
|
||||
getFileExtension,
|
||||
getBase64,
|
||||
getImageSize,
|
||||
formatBytes,
|
||||
} from './utils';
|
||||
export { PREVIEW_IMAGE_TYPE } from './constant';
|
||||
|
||||
export { FileItem, FileItemStatus } from './types';
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum FileItemStatus {
|
||||
Success = 'success',
|
||||
UploadFail = 'uploadFail',
|
||||
ValidateFail = 'validateFail',
|
||||
Validating = 'validating',
|
||||
Uploading = 'uploading',
|
||||
Wait = 'wait',
|
||||
}
|
||||
|
||||
export interface FileItem extends File {
|
||||
// 唯一标识
|
||||
uid?: string;
|
||||
// 文件地址
|
||||
url?: string;
|
||||
// 上传进度
|
||||
percent?: number;
|
||||
// 校验信息
|
||||
validateMessage?: string;
|
||||
status?: FileItemStatus;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import { useState } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { workflowApi } from '@coze-workflow/base/api';
|
||||
import { type ViewVariableType } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { upLoadFile } from '@coze-arch/bot-utils';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { validate } from './validate';
|
||||
import { FileItemStatus, type FileItem } from './types';
|
||||
import { MAX_IMAGE_SIZE, MAX_FILE_SIZE } from './constant';
|
||||
|
||||
export interface UploadConfig {
|
||||
initialValue?: FileItem[];
|
||||
customValidate?: (file: FileItem) => Promise<string | undefined>;
|
||||
timeout?: number;
|
||||
fileType?: 'object' | 'image';
|
||||
multiple?: boolean;
|
||||
maxSize?: number;
|
||||
inputType?: ViewVariableType;
|
||||
accept?: string;
|
||||
maxFileCount?: number;
|
||||
}
|
||||
|
||||
export const useUpload = (props?: UploadConfig) => {
|
||||
const {
|
||||
initialValue = [],
|
||||
customValidate,
|
||||
timeout,
|
||||
fileType,
|
||||
multiple = true,
|
||||
maxSize,
|
||||
accept,
|
||||
maxFileCount = 20,
|
||||
} = props || {};
|
||||
const [fileList, setFileList] = useState(initialValue);
|
||||
const isUploading = fileList.some(
|
||||
file => file.status === FileItemStatus.Uploading,
|
||||
);
|
||||
|
||||
const updateFileItemProps = (uid, fileItemProps) => {
|
||||
setFileList(prevList => {
|
||||
const newList = [...prevList];
|
||||
const index = newList.findIndex(item => item.uid === uid);
|
||||
if (index !== -1) {
|
||||
Object.keys(fileItemProps).forEach(key => {
|
||||
newList[index][key] = fileItemProps[key];
|
||||
});
|
||||
}
|
||||
|
||||
return newList;
|
||||
});
|
||||
};
|
||||
|
||||
const uploadFileWithProgress = async file => {
|
||||
let progressTimer;
|
||||
|
||||
try {
|
||||
const doUpload = async () =>
|
||||
await upLoadFile({
|
||||
biz: 'workflow',
|
||||
fileType,
|
||||
file,
|
||||
getProgress: percent => {
|
||||
updateFileItemProps(file.uid, {
|
||||
percent,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (timeout) {
|
||||
progressTimer = setTimeout(() => {
|
||||
throw new Error('Upload timed out');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
const uri = await doUpload();
|
||||
|
||||
if (!uri) {
|
||||
throw new CustomError('normal_error', 'no uri');
|
||||
}
|
||||
|
||||
// 上传完成,清空超时计时器
|
||||
clearTimeout(progressTimer);
|
||||
|
||||
// 加签uri,获得url
|
||||
const { url } = await workflowApi.SignImageURL(
|
||||
{
|
||||
uri,
|
||||
},
|
||||
{
|
||||
__disableErrorToast: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!url) {
|
||||
throw new Error(I18n.t('imageflow_upload_error'));
|
||||
}
|
||||
|
||||
updateFileItemProps(file.uid, {
|
||||
url,
|
||||
status: FileItemStatus.Success,
|
||||
});
|
||||
|
||||
return url;
|
||||
} catch (error) {
|
||||
updateFileItemProps(file.uid, {
|
||||
validateMessage: error.message || 'upload failed',
|
||||
status: FileItemStatus.ValidateFail,
|
||||
});
|
||||
clearTimeout(progressTimer);
|
||||
}
|
||||
};
|
||||
|
||||
const validateFile = async (file: FileItem): Promise<string | undefined> => {
|
||||
const validateMsg = await validate(file, {
|
||||
customValidate,
|
||||
maxSize:
|
||||
(maxSize ?? fileType === 'image') ? MAX_IMAGE_SIZE : MAX_FILE_SIZE,
|
||||
accept,
|
||||
});
|
||||
if (validateMsg) {
|
||||
return validateMsg;
|
||||
}
|
||||
};
|
||||
|
||||
const upload = async (file: FileItem) => {
|
||||
file.status = FileItemStatus.Uploading;
|
||||
if (!file.uid) {
|
||||
file.uid = nanoid();
|
||||
}
|
||||
|
||||
const errorInfo = await validateFile(file);
|
||||
|
||||
if (errorInfo) {
|
||||
Toast.error(errorInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!multiple && fileList[0]) {
|
||||
setFileList([]);
|
||||
}
|
||||
|
||||
let canUpload = true;
|
||||
|
||||
setFileList(prevList => {
|
||||
if (prevList.length >= maxFileCount) {
|
||||
Toast.warning(I18n.t('plugin_file_max'));
|
||||
canUpload = false;
|
||||
return prevList;
|
||||
}
|
||||
return [...prevList, file];
|
||||
});
|
||||
|
||||
if (canUpload) {
|
||||
await uploadFileWithProgress(file);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteFile = (uid?: string) => {
|
||||
const index = fileList.findIndex(item => uid === item.uid);
|
||||
|
||||
if (index !== -1 && uid) {
|
||||
setFileList(prevList => {
|
||||
const newList = [...prevList];
|
||||
newList.splice(index, 1);
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
fileList,
|
||||
upload,
|
||||
isUploading,
|
||||
deleteFile,
|
||||
setFileList: _fileList => setFileList(_fileList),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param bytes 文件大小
|
||||
* @param decimals 小数位数, 默认 2 位
|
||||
* @example
|
||||
* formatBytes(1024); // 1KB
|
||||
* formatBytes('1024'); // 1KB
|
||||
* formatBytes(1234); // 1.21KB
|
||||
* formatBytes(1234, 3); // 1.205KB
|
||||
*/
|
||||
export function formatBytes(bytes: number, decimals = 2) {
|
||||
if (bytes === 0) {
|
||||
return '0 Bytes';
|
||||
}
|
||||
const k = 1024,
|
||||
dm = decimals,
|
||||
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
|
||||
i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ViewVariableType } from '@coze-workflow/base';
|
||||
|
||||
export const ACCEPT_MAP = {
|
||||
// [ViewVariableType.File]: ['*'],
|
||||
|
||||
[ViewVariableType.Image]: ['image/*'],
|
||||
|
||||
[ViewVariableType.Doc]: ['.docx', '.doc', '.pdf'],
|
||||
|
||||
[ViewVariableType.Audio]: [
|
||||
'.mp3',
|
||||
'.wav',
|
||||
'.aac',
|
||||
'.flac',
|
||||
'.ogg',
|
||||
'.wma',
|
||||
'.alac',
|
||||
'.mid',
|
||||
'.midi',
|
||||
'.ac3',
|
||||
'.dsd',
|
||||
],
|
||||
|
||||
[ViewVariableType.Excel]: ['.xls', '.xlsx', '.csv'],
|
||||
|
||||
[ViewVariableType.Video]: ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv'],
|
||||
|
||||
[ViewVariableType.Zip]: ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'],
|
||||
|
||||
[ViewVariableType.Code]: ['.py', '.java', '.c', '.cpp', '.js', '.css'],
|
||||
|
||||
[ViewVariableType.Txt]: ['.txt'],
|
||||
|
||||
[ViewVariableType.Ppt]: ['.ppt', '.pptx'],
|
||||
|
||||
[ViewVariableType.Svg]: ['.svg'],
|
||||
};
|
||||
|
||||
export const getAccept = (
|
||||
inputType: ViewVariableType,
|
||||
availableFileTypes?: ViewVariableType[],
|
||||
) => {
|
||||
let accept: string;
|
||||
const itemType = ViewVariableType.isArrayType(inputType)
|
||||
? ViewVariableType.getArraySubType(inputType)
|
||||
: inputType;
|
||||
|
||||
if (itemType === ViewVariableType.File) {
|
||||
if (availableFileTypes?.length) {
|
||||
accept = availableFileTypes
|
||||
.map(type => ACCEPT_MAP[type]?.join(','))
|
||||
.join(',');
|
||||
} else {
|
||||
accept = Object.values(ACCEPT_MAP)
|
||||
.map(items => items.join(','))
|
||||
.join(',');
|
||||
}
|
||||
} else {
|
||||
accept = (ACCEPT_MAP[itemType] || []).join(',');
|
||||
}
|
||||
|
||||
return accept;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
|
||||
export function getBase64(file: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = event => {
|
||||
const result = event.target?.result;
|
||||
if (!result || typeof result !== 'string') {
|
||||
reject(
|
||||
new CustomError(REPORT_EVENTS.parmasValidation, 'file read fail'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve(result.replace(/^.*?,/, ''));
|
||||
};
|
||||
fileReader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/** 获取文件名后缀 */
|
||||
export function getFileExtension(name?: string) {
|
||||
if (!name) {
|
||||
return '';
|
||||
}
|
||||
const index = name.lastIndexOf('.');
|
||||
return name.slice(index + 1).toLowerCase();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FileItem } from '../types';
|
||||
|
||||
/**
|
||||
* 获取图片的宽高
|
||||
*/
|
||||
export async function getImageSize(
|
||||
file: FileItem,
|
||||
): Promise<{ width: number; height: number }> {
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
resolve({
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
});
|
||||
};
|
||||
img.onerror = e => {
|
||||
reject(e);
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { formatBytes } from './format-bytes';
|
||||
export { getImageSize } from './get-image-size';
|
||||
export { getBase64 } from './get-base-64';
|
||||
export { getAccept, ACCEPT_MAP } from './get-accept';
|
||||
export { getFileExtension } from './get-file-extension';
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 mime from 'mime-types';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { getFileExtension } from '../utils';
|
||||
|
||||
export const acceptValidate = (fileName: string, accept?: string) => {
|
||||
if (!accept) {
|
||||
return;
|
||||
}
|
||||
const acceptList = accept.split(',');
|
||||
|
||||
const fileExtension = getFileExtension(fileName);
|
||||
const mimeType = mime.lookup(fileExtension);
|
||||
|
||||
// image/* 匹配所有的图片类型
|
||||
if (acceptList.includes('image/*') && mimeType?.startsWith?.('image/')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!acceptList.includes(`.${fileExtension}`)) {
|
||||
return I18n.t('imageflow_upload_error_type', {
|
||||
type: `${acceptList.filter(Boolean).join('/')}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 { isNil } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { getImageSize } from '../utils/get-image-size';
|
||||
import { type FileItem } from '../types';
|
||||
|
||||
export interface ImageSizeRule {
|
||||
maxWidth?: number;
|
||||
minWidth?: number;
|
||||
maxHeight?: number;
|
||||
minHeight?: number;
|
||||
aspectRatio?: number;
|
||||
}
|
||||
|
||||
/** 图像宽高校验 */
|
||||
// eslint-disable-next-line complexity
|
||||
export const imageSizeValidate = async (
|
||||
file: FileItem,
|
||||
rule?: ImageSizeRule,
|
||||
): Promise<string | undefined> => {
|
||||
const { maxWidth, minWidth, maxHeight, minHeight, aspectRatio } = rule || {};
|
||||
|
||||
// 未定义时不校验
|
||||
if (isNil(maxWidth || minWidth || maxHeight || minHeight || aspectRatio)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = await getImageSize(file);
|
||||
|
||||
if (maxWidth && width > maxWidth) {
|
||||
return I18n.t('imageflow_upload_error5', {
|
||||
value: `${maxWidth}px`,
|
||||
});
|
||||
}
|
||||
|
||||
if (minWidth && width < minWidth) {
|
||||
return I18n.t('imageflow_upload_error3', {
|
||||
value: `${minWidth}px`,
|
||||
});
|
||||
}
|
||||
|
||||
if (maxHeight && height > maxHeight) {
|
||||
return I18n.t('imageflow_upload_error4', {
|
||||
value: `${maxHeight}px`,
|
||||
});
|
||||
}
|
||||
|
||||
if (minHeight && height < minHeight) {
|
||||
return I18n.t('imageflow_upload_error2', {
|
||||
value: `${minHeight}px`,
|
||||
});
|
||||
}
|
||||
if (aspectRatio && width / height > aspectRatio) {
|
||||
return I18n.t('imageflow_upload_error1');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FileItem } from '../types';
|
||||
import { sizeValidate } from './size-validate';
|
||||
import { imageSizeValidate, type ImageSizeRule } from './image-size-validate';
|
||||
import { acceptValidate } from './accept-validate';
|
||||
|
||||
interface UploadValidateRule {
|
||||
maxSize?: number;
|
||||
imageSize?: ImageSizeRule;
|
||||
accept?: string;
|
||||
customValidate?: (file: FileItem) => Promise<string | undefined>;
|
||||
}
|
||||
|
||||
export const validate = async (file: FileItem, rules?: UploadValidateRule) => {
|
||||
const { size, name } = file;
|
||||
|
||||
const { maxSize, imageSize, accept, customValidate } = rules || {};
|
||||
|
||||
const validators = [
|
||||
async () => await customValidate?.(file),
|
||||
() => sizeValidate(size, maxSize),
|
||||
async () => await imageSizeValidate(file, imageSize),
|
||||
() => acceptValidate(name, accept),
|
||||
];
|
||||
|
||||
for await (const validator of validators) {
|
||||
const errorMsg = await validator();
|
||||
if (errorMsg) {
|
||||
return errorMsg;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { formatBytes } from '../utils/format-bytes';
|
||||
|
||||
const DEFAULT_MAX_SIZE = 1024 * 1024 * 20;
|
||||
|
||||
/** 文件大小校验 */
|
||||
export const sizeValidate = (
|
||||
size: number,
|
||||
maxSize: number = DEFAULT_MAX_SIZE,
|
||||
): string | undefined => {
|
||||
if (maxSize && size > maxSize) {
|
||||
return I18n.t('imageflow_upload_exceed', {
|
||||
size: formatBytes(maxSize),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { WorkflowDocument } from '@flowgram-adapter/free-layout-editor';
|
||||
import { DisposableCollection } from '@flowgram-adapter/common';
|
||||
import { GlobalVariableService } from '@coze-workflow/variable';
|
||||
import { useValidationService } from '@coze-workflow/base/services';
|
||||
|
||||
import { useLineService, useGlobalState } from '@/hooks';
|
||||
|
||||
export const useValidateWorkflow = () => {
|
||||
const lineService = useLineService();
|
||||
const validationService = useValidationService();
|
||||
const globalState = useGlobalState();
|
||||
|
||||
const feValidate = useCallback(async () => {
|
||||
const { hasError, nodeErrorMap: feErrorMap } =
|
||||
await validationService.validateWorkflow();
|
||||
if (hasError && feErrorMap) {
|
||||
validationService.setErrorsV2({
|
||||
[globalState.workflowId]: {
|
||||
workflowId: globalState.workflowId,
|
||||
errors: feErrorMap,
|
||||
},
|
||||
});
|
||||
}
|
||||
return hasError;
|
||||
}, [validationService, globalState]);
|
||||
|
||||
const beValidate = useCallback(async () => {
|
||||
const { hasError, errors } = await validationService.validateSchemaV2();
|
||||
if (hasError) {
|
||||
validationService.setErrorsV2(errors);
|
||||
}
|
||||
|
||||
return hasError;
|
||||
}, [validationService]);
|
||||
|
||||
const validate = useCallback(async () => {
|
||||
validationService.validating = true;
|
||||
try {
|
||||
const feHasError = await feValidate();
|
||||
if (feHasError) {
|
||||
return feHasError;
|
||||
}
|
||||
const beHasError = await beValidate();
|
||||
if (!feHasError && !beHasError) {
|
||||
validationService.clearErrors();
|
||||
}
|
||||
lineService.validateAllLine();
|
||||
return beHasError;
|
||||
} finally {
|
||||
validationService.validating = false;
|
||||
}
|
||||
}, [feValidate, beValidate, validationService, lineService]);
|
||||
|
||||
return { validate };
|
||||
};
|
||||
|
||||
/**
|
||||
* 校验的触发频率
|
||||
*/
|
||||
const DEBOUNCE_TIME = 2000;
|
||||
|
||||
export const useWatchValidateWorkflow = () => {
|
||||
const { isInIDE } = useGlobalState();
|
||||
|
||||
const { validate } = useValidateWorkflow();
|
||||
const workflowDocument = useService<WorkflowDocument>(WorkflowDocument);
|
||||
|
||||
const debounceValidate = useMemoizedFn(debounce(validate, DEBOUNCE_TIME));
|
||||
|
||||
const globalVariableService = useService<GlobalVariableService>(
|
||||
GlobalVariableService,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const globalVariableDispose = new DisposableCollection();
|
||||
|
||||
globalVariableDispose.push(
|
||||
globalVariableService.onLoaded(() => {
|
||||
if (!isInIDE) {
|
||||
debounceValidate();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const contentChangeDispose = workflowDocument.onContentChange(() => {
|
||||
debounceValidate();
|
||||
});
|
||||
|
||||
return () => {
|
||||
contentChangeDispose.dispose();
|
||||
globalVariableDispose.dispose();
|
||||
};
|
||||
}, [workflowDocument, isInIDE]);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { ValueExpressionService } from '@/services/value-expression-service';
|
||||
|
||||
export const useValueExpressionService = () => {
|
||||
const valueExpressionService = useService<ValueExpressionService>(
|
||||
ValueExpressionService,
|
||||
);
|
||||
return valueExpressionService;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { WorkflowVariableService } from '@coze-workflow/variable';
|
||||
|
||||
export function useVariableService() {
|
||||
const variableService = useService<WorkflowVariableService>(
|
||||
WorkflowVariableService,
|
||||
);
|
||||
|
||||
return variableService;
|
||||
}
|
||||
@@ -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 { useState, useEffect } from 'react';
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { type MessageBizType } from '@coze-workflow/base';
|
||||
import type { Model } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { bizTypeToDependencyTypeMap } from '@/services/workflow-dependency-service';
|
||||
import { DependencySourceType } from '@/constants';
|
||||
|
||||
import { WorkflowModelsService } from '../services';
|
||||
import { useDependencyService } from './use-dependency-service';
|
||||
|
||||
/**
|
||||
* 统一获取模型数据入口,监听到模型资源变化时,更新模型数据
|
||||
*/
|
||||
export const useWorkflowModels = () => {
|
||||
const modelsService = useService<WorkflowModelsService>(
|
||||
WorkflowModelsService,
|
||||
);
|
||||
const dependencyService = useDependencyService();
|
||||
const [models, setModels] = useState<Model[]>(
|
||||
modelsService?.getModels() ?? [],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = dependencyService.onDependencyChange(source => {
|
||||
if (
|
||||
bizTypeToDependencyTypeMap[source?.bizType as MessageBizType] ===
|
||||
DependencySourceType.LLM
|
||||
) {
|
||||
const curModels = modelsService?.getModels() ?? [];
|
||||
setModels(curModels);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable?.dispose?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { models };
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowOperationService } from '../services';
|
||||
|
||||
export const useWorkflowOperation = () => {
|
||||
const operation = useService<WorkflowOperationService>(
|
||||
WorkflowOperationService,
|
||||
);
|
||||
|
||||
return operation;
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { createMinimapPlugin } from '@flowgram-adapter/free-layout-editor';
|
||||
import { createHistoryNodePlugin } from '@flowgram-adapter/free-layout-editor';
|
||||
import { createFreeSnapPlugin } from '@flowgram-adapter/free-layout-editor';
|
||||
import { createFreeNodePanelPlugin } from '@flowgram-adapter/free-layout-editor';
|
||||
import { createFreeLinesPlugin } from '@flowgram-adapter/free-layout-editor';
|
||||
import { createContainerNodePlugin } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
EntityManager,
|
||||
type PluginContext,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { createWorkflowVariablePlugins } from '@coze-workflow/variable';
|
||||
import { createTestRunPlugin } from '@coze-workflow/test-run';
|
||||
import {
|
||||
createFreeHistoryPlugin,
|
||||
createOperationReportPlugin,
|
||||
} from '@coze-workflow/history';
|
||||
import { createWorkflowEncapsulatePlugin } from '@coze-workflow/feature-encapsulate';
|
||||
import { type StandardNodeType } from '@coze-workflow/base';
|
||||
|
||||
import { WorkflowPlaygroundContext } from '@/workflow-playground-context';
|
||||
import { type WorkflowPlaygroundProps } from '@/typing';
|
||||
import { RelatedCaseDataService } from '@/services';
|
||||
import { WorkflowGlobalStateEntity } from '@/entities';
|
||||
|
||||
import { NodePanel } from '../components/node-panel';
|
||||
import { LineAddButton } from '../components/line-add-button';
|
||||
|
||||
const createEncapsulatePlugin = (props?: WorkflowPlaygroundProps) =>
|
||||
createWorkflowEncapsulatePlugin({
|
||||
getGlobalState: (ctx: PluginContext) =>
|
||||
ctx
|
||||
.get<EntityManager>(EntityManager)
|
||||
.getEntity<WorkflowGlobalStateEntity>(
|
||||
WorkflowGlobalStateEntity,
|
||||
) as WorkflowGlobalStateEntity,
|
||||
getNodeTemplate: (ctx: PluginContext) => (type: StandardNodeType) =>
|
||||
ctx
|
||||
.get<WorkflowPlaygroundContext>(WorkflowPlaygroundContext)
|
||||
.getNodeTemplateInfoByType(type),
|
||||
onEncapsulate: async (res, ctx) => {
|
||||
if (!res.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.projectId) {
|
||||
// project中刷新流程列表并rename到新建的流程
|
||||
await props?.refetchProjectResourceList?.();
|
||||
await props?.renameProjectResource?.(res.workflowId);
|
||||
} else {
|
||||
// 资源库中更新生成流程绑定的Bot信息
|
||||
const relatedCaseDataService = ctx.get<RelatedCaseDataService>(
|
||||
RelatedCaseDataService,
|
||||
);
|
||||
relatedCaseDataService.updateRelatedBot(
|
||||
relatedCaseDataService.getRelatedBotValue(),
|
||||
res.workflowId,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const useWorkflowPreset = (props?: WorkflowPlaygroundProps) => {
|
||||
const preset = useCallback(
|
||||
() => [
|
||||
createFreeLinesPlugin({
|
||||
renderInsideLine: LineAddButton,
|
||||
}),
|
||||
...createWorkflowVariablePlugins(),
|
||||
createFreeHistoryPlugin({
|
||||
enable: true,
|
||||
limit: 50,
|
||||
}),
|
||||
createOperationReportPlugin({}),
|
||||
createMinimapPlugin({
|
||||
disableLayer: true,
|
||||
canvasStyle: {
|
||||
canvasWidth: 182,
|
||||
canvasHeight: 102,
|
||||
canvasPadding: 50,
|
||||
canvasBackground: 'rgba(245, 245, 245, 1)',
|
||||
canvasBorderRadius: 10,
|
||||
viewportBackground: 'rgba(235, 235, 235, 1)',
|
||||
viewportBorderRadius: 4,
|
||||
viewportBorderColor: 'rgba(201, 201, 201, 1)',
|
||||
viewportBorderWidth: 1,
|
||||
viewportBorderDashLength: 2,
|
||||
nodeColor: 'rgba(255, 255, 255, 1)',
|
||||
nodeBorderRadius: 2,
|
||||
nodeBorderWidth: 0.145,
|
||||
nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
|
||||
overlayColor: 'rgba(255, 255, 255, 0)',
|
||||
},
|
||||
inactiveDebounceTime: 1,
|
||||
}),
|
||||
createFreeSnapPlugin({
|
||||
edgeColor: '#00B2B2',
|
||||
alignColor: '#00B2B2',
|
||||
edgeLineWidth: 1,
|
||||
alignLineWidth: 1,
|
||||
alignCrossWidth: 8,
|
||||
}),
|
||||
createFreeNodePanelPlugin({
|
||||
renderer: NodePanel,
|
||||
}),
|
||||
createHistoryNodePlugin({}),
|
||||
createContainerNodePlugin({}),
|
||||
createTestRunPlugin({}),
|
||||
createEncapsulatePlugin(props),
|
||||
],
|
||||
[],
|
||||
);
|
||||
return preset;
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取当前 workflow 被别的哪些workflow或bots 引用了
|
||||
* 目前bots没有数据,只有workflow的
|
||||
*/
|
||||
import {
|
||||
useQuery,
|
||||
type RefetchOptions,
|
||||
type QueryObserverResult,
|
||||
} from '@tanstack/react-query';
|
||||
import { type Workflow } from '@coze-workflow/base/api';
|
||||
|
||||
import { useWorkflowOperation } from './use-workflow-operation';
|
||||
import { useGlobalState } from './use-global-state';
|
||||
|
||||
interface WorkflowReferences {
|
||||
workflowList: Workflow[];
|
||||
}
|
||||
|
||||
export const useWorkflowReferences = (): {
|
||||
references: WorkflowReferences | undefined;
|
||||
refetchReferences: (options?: RefetchOptions | undefined) => Promise<
|
||||
QueryObserverResult<
|
||||
{
|
||||
workflowList: Workflow[];
|
||||
},
|
||||
Error
|
||||
>
|
||||
>;
|
||||
} => {
|
||||
const { spaceId, workflowId } = useGlobalState();
|
||||
|
||||
const operation = useWorkflowOperation();
|
||||
|
||||
const getWorkflowReferences = async () => {
|
||||
const workflowList = await operation.getReference();
|
||||
|
||||
return { workflowList };
|
||||
};
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['workflow_references', spaceId, workflowId],
|
||||
queryFn: getWorkflowReferences,
|
||||
});
|
||||
|
||||
return {
|
||||
references: data,
|
||||
refetchReferences: refetch,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowRunService } from '../services';
|
||||
|
||||
export const useWorkflowRunService = () =>
|
||||
useService<WorkflowRunService>(WorkflowRunService);
|
||||
Reference in New Issue
Block a user