feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
Emitter,
|
||||
EntityManager,
|
||||
FlowDocument,
|
||||
type IPoint,
|
||||
type PositionSchema,
|
||||
type FlowNodeEntity,
|
||||
} from '@flowgram-adapter/fixed-layout-editor';
|
||||
|
||||
import {
|
||||
getLineId,
|
||||
getNodeIdFromTreeId,
|
||||
getTreeIdFromNodeId,
|
||||
calcDistance,
|
||||
} from '../utils';
|
||||
import { type CustomLine, type EdgeItem } from '../typings';
|
||||
import { CustomRenderStateConfigEntity } from '../entities';
|
||||
import { LINE_HOVER_DISTANCE } from '../constants';
|
||||
import { TreeService } from './tree-service';
|
||||
import { CustomLinesManager } from './custom-lines-manager';
|
||||
|
||||
@injectable()
|
||||
export class CustomHoverService {
|
||||
@inject(CustomLinesManager) declare linesManager: CustomLinesManager;
|
||||
|
||||
@inject(FlowDocument) declare document: FlowDocument;
|
||||
|
||||
@inject(TreeService) declare treeService: TreeService;
|
||||
|
||||
@inject(EntityManager) declare entityManager: EntityManager;
|
||||
|
||||
@inject(CustomRenderStateConfigEntity)
|
||||
declare renderStateEntity: CustomRenderStateConfigEntity;
|
||||
|
||||
get edges(): EdgeItem[] {
|
||||
return this.treeService.getUnCollapsedEdges();
|
||||
}
|
||||
|
||||
private onBackgroundClickEmitter = new Emitter<void>();
|
||||
|
||||
onBackgroundClick = this.onBackgroundClickEmitter.event;
|
||||
|
||||
private onHoverCollapseEmitter = new Emitter<FlowNodeEntity | undefined>();
|
||||
|
||||
onHoverCollapse = this.onHoverCollapseEmitter.event;
|
||||
|
||||
private onHoverLineEmitter = new Emitter<CustomLine | undefined>();
|
||||
|
||||
onHoverLine = this.onHoverLineEmitter.event;
|
||||
|
||||
private onSelectNodeEmitter = new Emitter<void>();
|
||||
|
||||
onSelectNode = this.onSelectNodeEmitter.event;
|
||||
|
||||
private _hoveredLine: CustomLine | undefined;
|
||||
|
||||
get hoveredLine() {
|
||||
return this._hoveredLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据鼠标位置计算和线条的间距
|
||||
*/
|
||||
getCloseInLineFromMousePos(
|
||||
mousePos: IPoint,
|
||||
minDistance: number = LINE_HOVER_DISTANCE,
|
||||
): CustomLine | undefined {
|
||||
let targetLine: CustomLine | undefined, targetLineDist: number | undefined;
|
||||
(this.linesManager?.lines || []).forEach(line => {
|
||||
const dist = calcDistance(mousePos, line);
|
||||
|
||||
if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
|
||||
targetLineDist = dist;
|
||||
targetLine = line;
|
||||
}
|
||||
});
|
||||
return targetLine;
|
||||
}
|
||||
|
||||
updateHoverLine(pos: PositionSchema, checkTarget: boolean) {
|
||||
if (!checkTarget) {
|
||||
this.onHoverLineEmitter.fire(undefined);
|
||||
}
|
||||
const hoverLine = this.getCloseInLineFromMousePos(pos);
|
||||
if (hoverLine) {
|
||||
this.onHoverLineEmitter.fire(hoverLine);
|
||||
this._hoveredLine = hoverLine;
|
||||
} else {
|
||||
this.onHoverLineEmitter.fire(undefined);
|
||||
this._hoveredLine = undefined;
|
||||
}
|
||||
return hoverLine;
|
||||
}
|
||||
|
||||
backgroundClick(updateLines = true) {
|
||||
this.onBackgroundClickEmitter.fire();
|
||||
this.renderStateEntity.setSelectNodes([]);
|
||||
this.renderStateEntity.setActivatedNode(undefined);
|
||||
if (this.hoveredLine) {
|
||||
const lineId = getLineId(this._hoveredLine);
|
||||
this.renderStateEntity.activeLines = [lineId!];
|
||||
} else if (updateLines) {
|
||||
this.renderStateEntity.activeLines = [];
|
||||
} else {
|
||||
this.renderStateEntity.activeLines = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠策略:访问所有连线的元素,同时访问其父元素,执行 collapse。
|
||||
hoverCollapse(from?: FlowNodeEntity) {
|
||||
this.onHoverCollapseEmitter.fire(from);
|
||||
}
|
||||
|
||||
getParent(node?: FlowNodeEntity): FlowNodeEntity | undefined {
|
||||
if (!node) {
|
||||
return node;
|
||||
}
|
||||
if (node.flowNodeType !== 'blockIcon') {
|
||||
return this.getParent(node.parent);
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
selectNode(node: FlowNodeEntity) {
|
||||
const selectNodes = this.getRelatedNodes(node);
|
||||
this.renderStateEntity.setSelectNodes(selectNodes);
|
||||
this.renderStateEntity.setActivatedNode(node);
|
||||
// 高亮所有相关的线条
|
||||
const activeLines: string[] = [];
|
||||
this.linesManager.lines.map(line => {
|
||||
const fromInclude = selectNodes.includes(line.from.id);
|
||||
const toInclude = selectNodes.includes(line.to.id);
|
||||
if (fromInclude && toInclude) {
|
||||
activeLines.push(getLineId(line)!);
|
||||
}
|
||||
});
|
||||
this.renderStateEntity.activeLines = activeLines;
|
||||
this.linesManager.renderLines();
|
||||
}
|
||||
|
||||
traverseAncestors = (node: FlowNodeEntity): string[] => {
|
||||
let ancArr: string[] = [];
|
||||
if (node.parent) {
|
||||
const arr = this.traverseAncestors(node.parent);
|
||||
ancArr = ancArr.concat(arr);
|
||||
}
|
||||
this.edges.forEach(edge => {
|
||||
if (edge.to === getTreeIdFromNodeId(node.id)) {
|
||||
// push 额外线条
|
||||
ancArr.push(edge.from);
|
||||
// 遍历之前的节点信息
|
||||
const fromNode = this.document.getNode(getNodeIdFromTreeId(edge.from));
|
||||
if (fromNode) {
|
||||
const arr = this.traverseAncestors(fromNode);
|
||||
ancArr.concat(arr);
|
||||
}
|
||||
}
|
||||
});
|
||||
ancArr.push(node.id);
|
||||
return ancArr;
|
||||
};
|
||||
|
||||
traverseDescendants = (node: FlowNodeEntity): string[] => {
|
||||
let ancArr: string[] = [];
|
||||
if (node.children?.length) {
|
||||
node.children?.forEach(child => {
|
||||
ancArr.push(child.id);
|
||||
const childArr = this.traverseDescendants(child);
|
||||
ancArr = ancArr.concat(childArr);
|
||||
});
|
||||
}
|
||||
if (node.next) {
|
||||
const childArr = this.traverseDescendants(node.next);
|
||||
ancArr = ancArr.concat(childArr);
|
||||
}
|
||||
return ancArr;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据当前树结构,遍历节点并且选中
|
||||
*/
|
||||
getRelatedNodes = (node: FlowNodeEntity) => {
|
||||
const parentRelated = this.traverseAncestors(node);
|
||||
const childRelated = this.traverseDescendants(node);
|
||||
return [...parentRelated, ...childRelated];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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,
|
||||
FlowDocument,
|
||||
type FlowNodeEntity,
|
||||
FlowNodeTransformData,
|
||||
type IPoint,
|
||||
} from '@flowgram-adapter/fixed-layout-editor';
|
||||
|
||||
import type { CustomLine, EdgeItem } from '../typings';
|
||||
import { CustomRenderStateEntity } from '../entities';
|
||||
import { NODE_HEIGHT } from '../constants';
|
||||
import { TreeService } from './tree-service';
|
||||
|
||||
const FILTER_NODE_TYPE = ['block', 'blockIcon', 'root', 'inlineBlocks'];
|
||||
|
||||
@injectable()
|
||||
export class CustomLinesManager {
|
||||
@inject(FlowDocument) declare document: FlowDocument;
|
||||
|
||||
@inject(EntityManager) declare entityManager: EntityManager;
|
||||
|
||||
// 额外的连线
|
||||
@inject(TreeService) declare treeService: TreeService;
|
||||
|
||||
get edges(): EdgeItem[] {
|
||||
return this.treeService.edges;
|
||||
}
|
||||
|
||||
get treeNodes() {
|
||||
return this.filterTreeNode(this.document.getAllNodes());
|
||||
}
|
||||
|
||||
private _lines: CustomLine[] = [];
|
||||
|
||||
get lines() {
|
||||
return this._lines;
|
||||
}
|
||||
|
||||
set lines(lines: CustomLine[]) {
|
||||
this._lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤其他的非渲染节点
|
||||
*/
|
||||
filterTreeNode(nodes: FlowNodeEntity[]) {
|
||||
return nodes.filter(
|
||||
node => !FILTER_NODE_TYPE.includes(node.flowNodeType as string),
|
||||
);
|
||||
}
|
||||
|
||||
getOutputFromInput(inputPoint: IPoint) {
|
||||
return {
|
||||
x: inputPoint.x,
|
||||
y: inputPoint.y + NODE_HEIGHT,
|
||||
};
|
||||
}
|
||||
|
||||
bfsAddLine(root: FlowNodeEntity): CustomLine[] {
|
||||
const queue: FlowNodeEntity[] = [root];
|
||||
const result: CustomLine[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
if (node.next) {
|
||||
result.push({
|
||||
from: node,
|
||||
to: node.next,
|
||||
fromPoint: this.getOutputFromInput(
|
||||
node.getData(FlowNodeTransformData)?.inputPoint,
|
||||
),
|
||||
toPoint: node.next.getData(FlowNodeTransformData)?.inputPoint,
|
||||
});
|
||||
queue.push(node.next);
|
||||
}
|
||||
if (node.children?.length) {
|
||||
if (node.flowNodeType === 'root') {
|
||||
queue.push(node.children[0]);
|
||||
} else if (node.flowNodeType === 'split') {
|
||||
// 分支逻辑特殊处理
|
||||
const inlineBlocksChildren = node.children[1]?.children || [];
|
||||
const branchChildren =
|
||||
inlineBlocksChildren
|
||||
?.map(c => c?.children?.[0]?.children?.[0])
|
||||
?.filter(Boolean) || [];
|
||||
branchChildren.forEach(child => {
|
||||
result.push({
|
||||
from: node,
|
||||
to: child,
|
||||
fromPoint: this.getOutputFromInput(
|
||||
node.getData(FlowNodeTransformData)?.inputPoint,
|
||||
),
|
||||
toPoint: child.getData(FlowNodeTransformData)?.inputPoint,
|
||||
});
|
||||
});
|
||||
queue.push(...branchChildren);
|
||||
} else {
|
||||
node.children.forEach(child => {
|
||||
result.push({
|
||||
from: node,
|
||||
to: child,
|
||||
fromPoint: this.getOutputFromInput(
|
||||
node.getData(FlowNodeTransformData)?.inputPoint,
|
||||
),
|
||||
toPoint: child.getData(FlowNodeTransformData)?.inputPoint,
|
||||
});
|
||||
});
|
||||
queue.push(...node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
initLines() {
|
||||
if (this._lines?.length) {
|
||||
return;
|
||||
}
|
||||
this.renderLines();
|
||||
}
|
||||
|
||||
renderLines() {
|
||||
// 下一帧渲染,保证线条数据最新
|
||||
requestAnimationFrame(() => {
|
||||
const lines = this.bfsAddLine(this.document.originTree.root);
|
||||
const extraLines: CustomLine[] = (this.edges || [])
|
||||
.filter(edge => {
|
||||
const from = this.entityManager.getEntityById<FlowNodeEntity>(
|
||||
edge.from,
|
||||
)!;
|
||||
const to = this.entityManager.getEntityById<FlowNodeEntity>(edge.to)!;
|
||||
return from && to && !edge.collapsed;
|
||||
})
|
||||
.map(edge => {
|
||||
const from = this.entityManager.getEntityById<FlowNodeEntity>(
|
||||
edge.from,
|
||||
)!;
|
||||
const to = this.entityManager.getEntityById<FlowNodeEntity>(edge.to)!;
|
||||
return {
|
||||
from,
|
||||
to,
|
||||
fromPoint: this.getOutputFromInput(
|
||||
from.getData(FlowNodeTransformData)?.inputPoint,
|
||||
),
|
||||
toPoint: to.getData(FlowNodeTransformData)?.inputPoint,
|
||||
};
|
||||
});
|
||||
this._lines = [...lines, ...extraLines];
|
||||
const renderState = this.entityManager.getEntity<CustomRenderStateEntity>(
|
||||
CustomRenderStateEntity,
|
||||
);
|
||||
renderState?.updateVersion();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { CustomLinesManager } from './custom-lines-manager';
|
||||
export { CustomHoverService } from './custom-hover-service';
|
||||
export { TreeService } from './tree-service';
|
||||
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
/* eslint-disable max-params */
|
||||
/* eslint-disable complexity */
|
||||
import { nanoid } from 'nanoid';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
FlowDocument,
|
||||
type FlowNodeJSON,
|
||||
} from '@flowgram-adapter/fixed-layout-editor';
|
||||
import type {
|
||||
DependencyTree,
|
||||
DependencyTreeNode,
|
||||
KnowledgeInfo,
|
||||
PluginVersionInfo,
|
||||
TableInfo,
|
||||
} from '@coze-arch/bot-api/workflow_api';
|
||||
|
||||
import {
|
||||
transformKnowledge,
|
||||
transformPlugin,
|
||||
transformTable,
|
||||
isLoop,
|
||||
} from '../utils';
|
||||
import {
|
||||
DependencyOrigin,
|
||||
type EdgeItem,
|
||||
NodeType,
|
||||
type TreeNode,
|
||||
} from '../typings';
|
||||
|
||||
@injectable()
|
||||
export class TreeService {
|
||||
@inject(FlowDocument) declare document: FlowDocument;
|
||||
|
||||
declare root: TreeNode;
|
||||
|
||||
edges: EdgeItem[] = [];
|
||||
|
||||
treeHistory: {
|
||||
// 唯一标识符
|
||||
id: string;
|
||||
// 判断是否已经存在的两个条件
|
||||
resourceId: string;
|
||||
version?: string;
|
||||
depth: number;
|
||||
}[] = [];
|
||||
|
||||
declare globalJson: DependencyTree;
|
||||
|
||||
declare addChildrenArr: {
|
||||
parentId: string;
|
||||
json: FlowNodeJSON;
|
||||
}[];
|
||||
|
||||
getUnCollapsedEdges() {
|
||||
return this.edges.filter(e => !e.collapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于检查是否有重复项
|
||||
*/
|
||||
getNodeDuplicateFromTree = (id: string, version?: string) => {
|
||||
let duplicate = false;
|
||||
const depth: number[] = [];
|
||||
const dupId: string[] = [];
|
||||
const matchArr = this.treeHistory.filter(
|
||||
history =>
|
||||
`${history.resourceId}${history.version || ''}` ===
|
||||
`${id}${version || ''}`,
|
||||
);
|
||||
|
||||
if (matchArr?.length) {
|
||||
duplicate = true;
|
||||
matchArr.forEach(item => {
|
||||
depth.push(item.depth);
|
||||
dupId.push(item.id);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
depth,
|
||||
duplicate,
|
||||
dupId,
|
||||
};
|
||||
};
|
||||
|
||||
traverseDFS(node: TreeNode, id: string): TreeNode | undefined {
|
||||
if (node.id === id) {
|
||||
return node;
|
||||
} else {
|
||||
if (!node.children?.length) {
|
||||
return undefined;
|
||||
}
|
||||
for (const _node of node.children) {
|
||||
const findNode = this.traverseDFS(_node, id);
|
||||
if (findNode) {
|
||||
return findNode;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getNodeByIdFromTree(id: string): TreeNode | undefined {
|
||||
return this.traverseDFS(this.root, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 knowledge、plugin、table 的逻辑
|
||||
*/
|
||||
transformDuplicateInfo = (
|
||||
id: string,
|
||||
info: KnowledgeInfo | PluginVersionInfo | TableInfo,
|
||||
type: 'knowledge' | 'plugin' | 'table',
|
||||
depth: number,
|
||||
fromId: string,
|
||||
) => {
|
||||
const {
|
||||
duplicate,
|
||||
depth: dupDepth,
|
||||
dupId,
|
||||
} = this.getNodeDuplicateFromTree(id);
|
||||
let newSchema: FlowNodeJSON;
|
||||
if (type === 'knowledge') {
|
||||
newSchema = transformKnowledge(depth + 1, info);
|
||||
} else if (type === 'plugin') {
|
||||
newSchema = transformPlugin(depth + 1, info);
|
||||
} else {
|
||||
newSchema = transformTable(depth + 1, info);
|
||||
}
|
||||
// 如果重复,判断添加线还是节点
|
||||
if (duplicate) {
|
||||
for (const [i, d] of dupDepth.entries()) {
|
||||
// 只要有一个匹配,就加线。
|
||||
// 兜底走加节点的逻辑
|
||||
if (depth + 1 === d) {
|
||||
// 存到线条中
|
||||
this.edges.push({
|
||||
from: fromId,
|
||||
to: dupId[i],
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
return newSchema;
|
||||
}
|
||||
// 否则,正常往 blocks 里添加数据
|
||||
return newSchema;
|
||||
};
|
||||
|
||||
dfsTransformNodeToSchema = (
|
||||
depth: number,
|
||||
node: DependencyTreeNode,
|
||||
type?: NodeType,
|
||||
parentId?: string,
|
||||
): TreeNode | undefined => {
|
||||
if (!node?.commit_id) {
|
||||
return undefined;
|
||||
}
|
||||
let from = DependencyOrigin.APP;
|
||||
if (node.is_library) {
|
||||
from = DependencyOrigin.LIBRARY;
|
||||
}
|
||||
if (node.is_product) {
|
||||
from = DependencyOrigin.SHOP;
|
||||
}
|
||||
const dependencies = node.dependency;
|
||||
const children: TreeNode[] = [];
|
||||
const nodeId = `${node.commit_id}_${nanoid(5)}`;
|
||||
const {
|
||||
duplicate,
|
||||
depth: dupDepth,
|
||||
dupId,
|
||||
} = this.getNodeDuplicateFromTree(
|
||||
node!.id as string,
|
||||
node.workflow_version,
|
||||
);
|
||||
if (duplicate) {
|
||||
for (const [i, d] of dupDepth.entries()) {
|
||||
if (depth === d) {
|
||||
this.edges.push({
|
||||
from: parentId || '',
|
||||
to: dupId[i],
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const loop = node.id && isLoop(node.id, this.globalJson);
|
||||
// 重复节点,停止继续往下
|
||||
const endData = {
|
||||
id: nodeId,
|
||||
type: 'custom',
|
||||
data: {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
type:
|
||||
type || (node.is_chatflow ? NodeType.CHAT_FLOW : NodeType.WORKFLOW),
|
||||
from,
|
||||
collapsed: false,
|
||||
version: node.workflow_version,
|
||||
depth,
|
||||
loop,
|
||||
},
|
||||
parent: [],
|
||||
meta: {
|
||||
isNodeEnd: true,
|
||||
},
|
||||
blocks: [],
|
||||
};
|
||||
return endData;
|
||||
}
|
||||
this.treeHistory.push({
|
||||
id: nodeId,
|
||||
resourceId: node.id as string,
|
||||
version: node.workflow_version,
|
||||
depth,
|
||||
});
|
||||
|
||||
(dependencies?.knowledge_list || []).map(k => {
|
||||
const newS = this.transformDuplicateInfo(
|
||||
k.id as string,
|
||||
k,
|
||||
'knowledge',
|
||||
depth,
|
||||
nodeId,
|
||||
);
|
||||
if (newS) {
|
||||
this.treeHistory.push({
|
||||
id: newS.id,
|
||||
resourceId: newS.data?.id,
|
||||
version: newS.data?.version,
|
||||
depth: newS.data?.depth,
|
||||
});
|
||||
children.push({
|
||||
...newS,
|
||||
data: newS.data || {},
|
||||
type: newS.type as string,
|
||||
parent: [],
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
(dependencies?.table_list || []).map(t => {
|
||||
const newS = this.transformDuplicateInfo(
|
||||
t.id as string,
|
||||
t,
|
||||
'table',
|
||||
depth,
|
||||
nodeId,
|
||||
);
|
||||
if (newS) {
|
||||
this.treeHistory.push({
|
||||
id: newS.id,
|
||||
resourceId: newS.data?.id,
|
||||
version: newS.data?.version,
|
||||
depth: newS.data?.depth,
|
||||
});
|
||||
children.push({
|
||||
...newS,
|
||||
data: newS.data || {},
|
||||
type: newS.type as string,
|
||||
parent: [],
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
(dependencies?.plugin_version || []).map(p => {
|
||||
const newS = this.transformDuplicateInfo(
|
||||
p.id as string,
|
||||
p,
|
||||
'plugin',
|
||||
depth,
|
||||
nodeId,
|
||||
);
|
||||
if (newS) {
|
||||
this.treeHistory.push({
|
||||
id: newS.id,
|
||||
resourceId: newS.data?.id,
|
||||
version: newS.data?.version,
|
||||
depth: newS.data?.depth,
|
||||
});
|
||||
children.push({
|
||||
...newS,
|
||||
data: newS.data || {},
|
||||
type: newS.type as string,
|
||||
parent: [],
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
(dependencies?.workflow_version?.filter(v => v.id !== node.id) || []).map(
|
||||
w => {
|
||||
const workflowInfo = this.globalJson.node_list?.find(
|
||||
_node => _node.id === w.id && _node.workflow_version === w.version,
|
||||
);
|
||||
const subWorkflowSchema = this.dfsTransformNodeToSchema(
|
||||
depth + 1,
|
||||
workflowInfo!,
|
||||
undefined,
|
||||
nodeId,
|
||||
);
|
||||
// 检测 workflow 的重复
|
||||
if (subWorkflowSchema) {
|
||||
this.treeHistory.push({
|
||||
id: subWorkflowSchema.id,
|
||||
resourceId: subWorkflowSchema.data?.id || '',
|
||||
version: subWorkflowSchema.data?.version,
|
||||
depth: subWorkflowSchema?.data?.depth,
|
||||
});
|
||||
children.push(subWorkflowSchema);
|
||||
}
|
||||
},
|
||||
);
|
||||
const isNodeEnd = !children.length;
|
||||
return {
|
||||
id: nodeId,
|
||||
type: isNodeEnd ? 'custom' : 'split',
|
||||
parent: [],
|
||||
data: {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
type:
|
||||
type || (node.is_chatflow ? NodeType.CHAT_FLOW : NodeType.WORKFLOW),
|
||||
from,
|
||||
collapsed: false,
|
||||
version: node.workflow_version,
|
||||
depth,
|
||||
},
|
||||
...(isNodeEnd
|
||||
? {
|
||||
meta: {
|
||||
isNodeEnd: true,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
children,
|
||||
};
|
||||
};
|
||||
|
||||
// 展开所有的子元素
|
||||
dfsCloneCollapsedOpen(node: TreeNode): TreeNode {
|
||||
const children = node.children?.map(c => this.dfsCloneCollapsedOpen(c));
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
collapsed: false,
|
||||
},
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
// 根据 edges,移动节点到另一个 TreeNode 的 children 下。
|
||||
// 需要将内部的 collapsed 全部变成 open
|
||||
cloneNode(node: TreeNode) {
|
||||
return this.dfsCloneCollapsedOpen(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定父元素 parent
|
||||
*/
|
||||
bindTreeParent(node: TreeNode, parent?: TreeNode) {
|
||||
if (parent) {
|
||||
node.parent.push(parent);
|
||||
}
|
||||
this.edges.forEach(edge => {
|
||||
if (edge.to === node.id) {
|
||||
const fromNode = this.getNodeByIdFromTree(edge.from);
|
||||
if (fromNode) {
|
||||
node.parent.push(fromNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
node.children?.forEach(item => {
|
||||
this.bindTreeParent(item, node);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据,将数据从后端数据 json 变成 tree 结构
|
||||
*/
|
||||
transformSchema(json: DependencyTree) {
|
||||
this.globalJson = json;
|
||||
const { node_list = [] } = json;
|
||||
const rootWorkflow = node_list.find(node => node.is_root);
|
||||
if (!rootWorkflow) {
|
||||
return undefined;
|
||||
}
|
||||
const root = this.dfsTransformNodeToSchema(0, rootWorkflow);
|
||||
if (root) {
|
||||
this.root = root;
|
||||
// this.bindTreeParent(root);
|
||||
}
|
||||
}
|
||||
|
||||
dfsTreeJson(node: TreeNode): FlowNodeJSON {
|
||||
let blocks: FlowNodeJSON[] = [];
|
||||
const lines = this.getUnCollapsedEdges();
|
||||
node?.children?.forEach(c => {
|
||||
const connectLines = lines.filter(l => l.to === c.id);
|
||||
if (c.data?.collapsed && connectLines?.length) {
|
||||
const cloneNode = this.cloneNode(c);
|
||||
connectLines.forEach(l => {
|
||||
this.addChildrenArr.push({
|
||||
parentId: l.from,
|
||||
json: this.dfsTreeJson(cloneNode),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
if (node?.children?.length) {
|
||||
blocks = node.children
|
||||
.filter(c => !c?.data?.collapsed)
|
||||
.map(c => ({
|
||||
id: `${c.id}_block`,
|
||||
type: 'block',
|
||||
blocks: [this.dfsTreeJson(c)],
|
||||
}));
|
||||
}
|
||||
return {
|
||||
id: node?.id,
|
||||
type: node?.type,
|
||||
data: node?.data,
|
||||
meta: node?.meta,
|
||||
blocks,
|
||||
};
|
||||
}
|
||||
|
||||
addNode(
|
||||
json: FlowNodeJSON,
|
||||
addItem: {
|
||||
parentId: string;
|
||||
json: FlowNodeJSON;
|
||||
},
|
||||
) {
|
||||
if (json.id === addItem.parentId) {
|
||||
const blockItem = {
|
||||
id: `${addItem.json.id}_block`,
|
||||
type: 'block',
|
||||
blocks: [addItem.json],
|
||||
};
|
||||
if (json.blocks) {
|
||||
json.blocks.push(blockItem);
|
||||
} else {
|
||||
json.blocks = [blockItem];
|
||||
}
|
||||
} else {
|
||||
json.blocks?.forEach(block => {
|
||||
this.addNode(block, addItem);
|
||||
});
|
||||
}
|
||||
// 可能因此原本设置为结束的节点现在有 child 了
|
||||
if (json.blocks?.length) {
|
||||
if (json.type === 'custom') {
|
||||
json.type = 'split';
|
||||
}
|
||||
if (json.meta) {
|
||||
json.meta.isNodeEnd = false;
|
||||
} else {
|
||||
json.meta = {
|
||||
isNodeEnd: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
treeToFlowNodeJson() {
|
||||
this.addChildrenArr = [];
|
||||
const rootNodeJson = this.dfsTreeJson(this.root);
|
||||
if (!rootNodeJson || !rootNodeJson?.id) {
|
||||
const json = {
|
||||
nodes: [],
|
||||
};
|
||||
this.document.fromJSON(json);
|
||||
return json;
|
||||
}
|
||||
|
||||
const json2 = {
|
||||
nodes: [rootNodeJson],
|
||||
};
|
||||
|
||||
this.addChildrenArr.forEach(item => {
|
||||
this.addNode(rootNodeJson, item);
|
||||
});
|
||||
|
||||
this.document.fromJSON(json2);
|
||||
return json2;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user