jbxl-workflow
Version:
流程图
253 lines (247 loc) • 8.44 kB
JavaScript
import {h, nextTick, render} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {generateRandomId} from "@dang_8899/xl-ui";
// components
import Toolbar from '@/components/Toolbar.vue'
// class
import {LLMNode, ParameterNode, PluginNode} from '@/Node.class'
class WorkflowToolbar {
constructorFuncMap = new Map([
['llm-node-shape', LLMNode],
['plugin-node-shape', PluginNode],
['parameter-node-shape', ParameterNode]
])
constructor(opts) {
this.scaleValue = 0.8 // 缩放比例
this.container = opts.container
this.clipboard = [] // 剪贴板
this.init()
}
// 初始化
init() {
this.render()
this.initEvent()
}
// 渲染工具栏到容器中
render() {
const toolbarVNode = h(Toolbar, {
scaleValue: this.scaleValue,
onUndo: () => {
this.undo()
},
onRedo: () => {
this.redo()
},
onExpand: () => {
this.toggleExpandAllAndRetractAll(true)
},
onRetract: () => {
this.toggleExpandAllAndRetractAll(false)
},
onStartDrag: () => {
this.startDrag()
},
onStartSelect: () => {
this.startSelect()
},
onZoomOut: () => {
globalThis._graph?.zoom(-0.2)
},
onZoomIn: () => {
globalThis._graph?.zoom(0.2)
},
onChangeScaleValue: (factor) => {
if (factor === 'auto') {
globalThis._graph?.zoomToFit()
} else {
globalThis._graph?.zoomTo(factor)
}
}
})
render(toolbarVNode, this.container)
}
// 初始化事件监听
initEvent() {
globalThis._graph?.on('scale', ({ sx }) => {
this.scaleValue = sx
this.render()
})
globalThis._graph?.zoomTo(this.scaleValue)
}
// 撤销
undo() {
if (globalThis._graph?.canUndo()) {
globalThis._graph?.undo()
}
}
// 重做
redo() {
if (globalThis._graph?.canRedo()) {
globalThis._graph?.redo()
}
}
// 全部展开/全部收起
toggleExpandAllAndRetractAll(isExpand) {
const nodes = globalThis._graph?.getNodes()
if (nodes.length) {
nodes.forEach(node => {
const data = node.getData()
const { vueInstance } = data?.self || {}
const { toggleExpandAllAndRetractAll } = vueInstance?.exposed || {}
toggleExpandAllAndRetractAll?.(isExpand)
})
}
}
// 拖动画布
startDrag() {
globalThis._graph?.enablePanning()
globalThis._graph?.disableSelection()
}
// 选中
startSelect() {
globalThis._graph?.enableSelection()
globalThis._graph?.disablePanning()
}
// 删除节点
removeNode() {
const cells = globalThis._graph?.getSelectedCells()
const nodes = cells.filter(cell => cell.isNode()) // 获取所有被选中的节点
if (nodes.some(item => ['start-node-shape', 'end-node-shape'].includes(item.shape))) {
// return ElMessage.error('开始节点和结束节点不能删除')
return ElMessage({
type: 'error',
message: `开始节点和结束节点不能删除`,
customClass: 'top-full-width-message'
});
}
if (globalThis.ISRUNNING) {
ElMessage.closeAll()
return ElMessage.warning('工作流正在运行中,无法进行删除操作')
}
ElMessageBox.confirm(
'删除节点',
'删除节点后将无法恢复,是否确认?',
{
customClass: 'delete-confirm'
}
).then(() => {
const nodePool = globalThis._graph.nodePool
if (nodes.length) {
nodes.forEach(node => {
const data = node.getData()
const prevNodeList = nodePool.getDirectUpstreamNodes(data?.self.id)
const nextNodeList = nodePool.getDirectDownstreamNodes(data?.self.id)
prevNodeList.forEach(prevNode => { // 断开所有直接上游节点与被删除节点之间连线
nodePool.disconnect(prevNode.id, data?.self.id)
prevNode.removeNextNode(data?.self.id)
})
nextNodeList.forEach(nextNode => { // 断开被删除节点与所有直接下游节点之间连线
nodePool.disconnect(data?.self.id, nextNode.id)
})
nodePool.removeNode(data?.self)
})
globalThis._graph?.removeCells(nodes)
}
})
}
// 复制节点
copyNode() {
// 获取当前焦点元素
const activeElement = document.activeElement
// 检查当前焦点元素是否可编辑,用来判断是否复制文本,如果不是,则执行节点复制
const isEditable = activeElement?.getAttribute('contenteditable') === 'true' || ['INPUT', 'TEXTAREA'].includes(activeElement?.tagName)
const cells = globalThis._graph?.getSelectedCells()
const nodes = cells.filter(cell => cell.isNode()) // 获取所有被选中的节点
// 如果当前焦点在可编辑区域,则不执行节点复制
if (isEditable) {
this.clipboard = []
return true
}
if (nodes.length) {
console.log('nodes', nodes)
this.clipboard = nodes.map(node => {
const data = node.getData()
return data?.self || {}
})
}
}
// 粘贴节点
async pasteNode() {
if (this.clipboard.length > 0) {
if (this.clipboard.some(item => ['start-node-shape', 'end-node-shape'].includes(item.shapeType))) {
// return ElMessage.error('开始节点和结束节点不能复制')
return ElMessage({
type: 'error',
message: `开始节点和结束节点不能复制`,
customClass: 'top-full-width-message'
});
}
console.log('this.clipboard', this.clipboard)
const newNodesIdList = []
let lastNodeId = null
this.clipboard.forEach((self, index) => {
const NodeInstance = this.constructorFuncMap.get(self.shapeType)
const newNode = new NodeInstance(this.getConstructorOpts(self))
const params = newNode.getGraphPureParams()
// params.x += 30
// params.y += 30
const [newNodeSelf, X6Node] = globalThis._graph?.addNode(params) // 生成节点
globalThis._graph?.nodePool?.addNode(newNodeSelf) // 添加到节点池
console.log('X6Node', X6Node)
newNodesIdList.push(X6Node.id)
if (index === this.clipboard.length - 1) {
lastNodeId = X6Node.id
}
})
const lastNode = globalThis._graph?.getCellById(lastNodeId)
if (lastNode) {
lastNode.setZIndex(9999)
}
await globalThis._graph?.cleanSelection()
console.log('newNodesIdList', newNodesIdList)
await globalThis._graph?.select(newNodesIdList)
}
}
getConstructorOpts(self) {
let title = self.title || ''
if (title.indexOf('_') > -1) {
const [nodeTitle, nodeNumber] = title.split('_')
title = `${nodeTitle}_${!isNaN(nodeNumber * 1) ? nodeNumber * 1 + 1 : 1}`
} else {
title = `${self.title}_1`
}
return {
id: `${generateRandomId()}${self.shapeType}`,
title,
description: self.description,
x: self.x + 30,
y: self.y + 30,
paramsDataList: self.paramsData.list.map(item => ({ ...item, id: '' })),
outputParamsDataList: self.outputParamsData.list.map(item => ({ ...item, id: '' })),
pluginId: self.pluginId,
currentModel: self.currentModel,
temperatureValue: self.temperatureValue,
maxTokensValue: self.maxTokensValue,
modelType: self.modelType,
systemPrompt: self.systemPrompt,
currRoadInstruction: self.currRoadInstruction,
systemPromptRichText: self.systemPromptRichText,
currRoadInstructionRichText: self.currRoadInstructionRichText,
}
}
// 删除两节点间的连线
removeEdge(selectedEdgeSourceNode, selectedEdgeTargetNode) {
if (globalThis.ISRUNNING) {
ElMessage.closeAll()
return ElMessage.warning('工作流正在运行中,无法进行删除连线操作')
}
if (selectedEdgeSourceNode && selectedEdgeTargetNode) {
const sourceNode = selectedEdgeSourceNode.data?.self
const targetNode = selectedEdgeTargetNode.data?.self
globalThis._graph?.nodePool?.portConnectionCache.delete(`${selectedEdgeSourceNode.id}-${selectedEdgeTargetNode.id}`)
globalThis._graph?.nodePool?.disconnect(sourceNode?.id, targetNode?.id)
sourceNode.removeNextNode(targetNode?.id)
}
}
}
export default WorkflowToolbar