Files
coze-studio/frontend/packages/workflow/playground/src/workflow-playground-context.ts
fanlv 890153324f feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
2025-07-20 17:36:12 +08:00

348 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 { inject, injectable } from 'inversify';
import { EntityManager } from '@flowgram-adapter/free-layout-editor';
import {
WorkflowDocumentProvider,
type WorkflowDocument,
type WorkflowNodeRegistry,
} from '@flowgram-adapter/free-layout-editor';
import {
WorkflowBatchService,
WorkflowVariableService,
WorkflowVariableValidationService,
} from '@coze-workflow/variable';
import {
type NodeTemplateInfo,
WorkflowNodesService,
type PlaygroundContext,
} from '@coze-workflow/nodes';
import { NODE_ORDER, StandardNodeType } from '@coze-workflow/base/types';
import {
workflowApi,
WorkflowMode,
type NodeTemplateListResponse,
} from '@coze-workflow/base/api';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { CustomError } from '@coze-arch/bot-error';
import {
type NodeCategory as ServerNodeCategory,
type PluginCategory,
type PluginAPINode,
} from '@coze-arch/bot-api/workflow_api';
import {
ProductEntityType,
SortType,
type GetUserFavoriteListData,
} from '@coze-arch/bot-api/product_api';
import { type Model } from '@coze-arch/bot-api/developer_api';
import { ProductApi } from '@coze-arch/bot-api';
import {
type NodeTemplate,
type PluginApiNodeTemplate,
type PluginCategoryNodeTemplate,
type NodeCategory,
} from './typing';
import { createApiNodeInfo } from './hooks/use-add-node-modal/helper';
import { WorkflowGlobalStateEntity } from './entities';
import { PAGE_SIZE } from './components/node-panel/constant';
export interface ImageflowNode {
title: string;
desc: string;
icon: string;
apiName: string;
apiID: string;
pluginID: string;
pluginName: string;
}
export interface ImageflowNodesGroup {
category: string;
tools: ImageflowNode[];
}
@injectable()
export class WorkflowPlaygroundContext implements PlaygroundContext {
protected nodeTemplateMap = new Map<StandardNodeType, NodeTemplate>();
protected pluginApiMap: Record<string, PluginAPINode> = {};
protected pluginCategoryMap: Record<string, PluginCategory> = {};
public favoritePlugins: GetUserFavoriteListData | undefined;
protected nodeCategoryList: ServerNodeCategory[] = [];
public imageflowNodes: ImageflowNode[];
@inject(WorkflowDocumentProvider)
protected documentProvider: WorkflowDocumentProvider;
@inject(WorkflowVariableService)
readonly variableService: WorkflowVariableService;
@inject(WorkflowBatchService) readonly batchService: WorkflowBatchService;
@inject(WorkflowVariableValidationService)
readonly variableValidationService: WorkflowVariableValidationService;
@inject(WorkflowNodesService) readonly nodesService: WorkflowNodesService;
@inject(EntityManager) public entityManager: EntityManager;
protected modelList: Model[];
get models() {
return this.modelList;
}
set models(models: Model[]) {
this.modelList = models;
}
/**
* 获取 document
*/
get document(): WorkflowDocument {
return this.documentProvider();
}
/**
* 获取 工作流节点模板
*/
async loadNodeInfos(locale: string): Promise<void> {
const nodeIds: StandardNodeType[] = Object.values(StandardNodeType);
let resp: NodeTemplateListResponse | undefined;
let favoritePlugins: GetUserFavoriteListData | undefined;
const response = await Promise.allSettled([
workflowApi.NodeTemplateList(
{
node_types: nodeIds,
},
{
headers: {
'x-locale': locale, // zh-CN, en-US
},
},
),
this.fetchFavoritePlugins({ pageNum: 1 }),
]);
response[0].status === 'fulfilled' && (resp = response[0].value);
response[1].status === 'fulfilled' && (favoritePlugins = response[1].value);
// 对服务端返回的 template 数据做一次转换,把其中的 type: 1 转换为 type: '1', 与标准的 StandardNodeType
const typeKey = 'node_type';
this.favoritePlugins = favoritePlugins;
this.nodeCategoryList = resp?.data?.cate_list ?? [];
this.pluginApiMap = (resp?.data?.plugin_api_list ?? []).reduce<
Record<string, PluginAPINode>
>((acc, curr) => {
curr.api_id && (acc[curr.api_id] = curr);
return acc;
}, {});
this.pluginCategoryMap = (resp?.data?.plugin_category_list ?? []).reduce<
Record<string, PluginCategory>
>((acc, curr) => {
curr.plugin_category_id && (acc[curr.plugin_category_id] = curr);
return acc;
}, {});
resp?.data?.template_list?.forEach(temp => {
if (temp[typeKey]) {
this.nodeTemplateMap.set(`${temp[typeKey]}` as StandardNodeType, {
...temp,
type: `${temp[typeKey]}` as StandardNodeType,
});
}
});
}
getImageFlowNode(pluginId: string, apiName: string) {
return this.imageflowNodes.find(
tool => tool.pluginID === pluginId && tool.apiName === apiName,
);
}
getNodeTemplateInfoByType = (
type: StandardNodeType,
): NodeTemplateInfo | undefined => {
const registry = this.document.getNodeRegister<WorkflowNodeRegistry>(type);
if (
!registry ||
!registry.meta ||
registry.meta.nodeDTOType === undefined
) {
throw new CustomError(
REPORT_EVENTS.parmasValidation,
`Unknown NodeMeta by type ${type}`,
);
}
const info = this.nodeTemplateMap.get(
registry.meta.nodeDTOType as StandardNodeType,
);
if (!info) {
return;
}
return {
title: info.name as string,
icon: info.icon_url as string,
description: info.desc as string,
mainColor: info.color || '',
subTitle: (type !== StandardNodeType.Start &&
type !== StandardNodeType.End
? info.name
: '') as string,
};
};
/**
* 这个会禁止圈选和删除等操作
*/
get disabled(): boolean {
return !!this.globalState?.readonly;
}
/**
* 这种通过 context 透传的方式不太好,后面需要重新改造下
*/
get spaceId(): string | undefined {
return this.globalState?.spaceId;
}
get flowMode(): WorkflowMode {
return this.globalState?.flowMode ?? WorkflowMode.Workflow;
}
getTemplateList(types: StandardNodeType[] = []): NodeTemplate[] {
// HACK: 传入的 type 的类型是 string然后后端返回的模板实际是 number
return (
types
.sort(
(prev, next) => Number(NODE_ORDER[prev]) - Number(NODE_ORDER[next]),
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map(type => this.nodeTemplateMap.get(type)!)
.filter(Boolean)
);
}
async fetchFavoritePlugins({
pageNum,
pageSize = PAGE_SIZE,
}: {
pageNum: number;
pageSize?: number;
}): Promise<GetUserFavoriteListData | undefined> {
// The community version does not currently support plugin favorite function, for future expansion
if (IS_OPEN_SOURCE) {
return {
favorite_products: [],
has_more: false,
};
}
const resp = await ProductApi.PublicGetUserFavoriteList({
entity_type: ProductEntityType.Plugin,
sort_type: SortType.Newest,
page_num: pageNum,
page_size: pageSize,
});
return resp.data;
}
getTemplateCategoryList(
enabledNodeTypes: StandardNodeType[] = [],
isSupportImageflowNodes = false,
): NodeCategory[] {
const isBindDouyin = Boolean(this.globalState?.isBindDouyin);
const nodeCategoryList =
this.nodeCategoryList.length !== 0
? this.nodeCategoryList
: // fallback when server data load failed
[
{
name: '',
node_type_list: enabledNodeTypes,
},
];
return nodeCategoryList
.map(category => {
const nodeList =
category.node_type_list
?.filter(item =>
enabledNodeTypes.includes(item as StandardNodeType),
)
?.map(item => this.nodeTemplateMap.get(item as StandardNodeType))
?.filter<NodeTemplate>((item): item is NodeTemplate =>
Boolean(item),
) ?? [];
const pluginApiList: PluginApiNodeTemplate[] = (
category.plugin_api_id_list ?? []
)?.map(apiId => {
const pluginInfo = this.pluginApiMap[apiId];
const nodeJSON = createApiNodeInfo(
{
name: pluginInfo.api_name,
plugin_name: pluginInfo.name,
api_id: pluginInfo.api_id,
plugin_id: pluginInfo.plugin_id,
desc: pluginInfo.desc,
},
pluginInfo.icon_url,
);
return {
type: StandardNodeType.Api,
...pluginInfo,
nodeJSON,
};
});
const pluginCategoryList: PluginCategoryNodeTemplate[] = (
category.plugin_category_id_list ?? []
).map(categoryId => {
const pluginCategory = this.pluginCategoryMap[categoryId];
return {
type: StandardNodeType.Api,
...pluginCategory,
categoryInfo: {
categoryId,
onlyOfficial: pluginCategory.only_official,
},
};
});
return {
categoryName: category.name,
nodeList: isSupportImageflowNodes
? [
...nodeList,
...(isBindDouyin ? [] : pluginApiList),
...(isBindDouyin ? [] : pluginCategoryList),
]
: nodeList,
};
})
.filter(category => category.nodeList.length > 0);
}
get globalState() {
return this.entityManager.getEntity<WorkflowGlobalStateEntity>(
WorkflowGlobalStateEntity,
);
}
get schemaGray(): {
isBatchV2: boolean;
} {
// const { schemaGray } = this.globalState?.config ?? {};
return {
isBatchV2: false,
};
}
}