110 lines
4.9 KiB
TypeScript
110 lines
4.9 KiB
TypeScript
|
|
import useRuntimeStore from '../store/runtimeStore'
|
||
|
|
import { runtime } from '../model/StudioRuntime'
|
||
|
|
|
||
|
|
const GraphService = {
|
||
|
|
// Graph mutations
|
||
|
|
createEdge(edge: { id?: string; source: string; sourceHandle?: string; target: string; targetHandle?: string, style?: any, animated?: boolean }){
|
||
|
|
const srcHandle = edge.sourceHandle || ''
|
||
|
|
const tgtHandle = edge.targetHandle || ''
|
||
|
|
const strip = (h: string) => h.replace(/^output-/, '').replace(/^input-/, '')
|
||
|
|
const srcPort = strip(srcHandle) || 'output'
|
||
|
|
const tgtPort = strip(tgtHandle) || 'input'
|
||
|
|
|
||
|
|
const srcModel = runtime.getNodeModel(edge.source)
|
||
|
|
const tgtModel = runtime.getNodeModel(edge.target)
|
||
|
|
if(!srcModel || !tgtModel){ console.warn('GraphService.createEdge: missing source/target model', edge); return null }
|
||
|
|
const srcOutput = srcModel.outputs.find((o: any) => o.name === srcPort)
|
||
|
|
const tgtInput = tgtModel.inputs.find((i: any) => i.name === tgtPort)
|
||
|
|
|
||
|
|
const srcTypeInfo = srcOutput?.getTypeInfo() || { type: '', dim: 0 }
|
||
|
|
const tgtTypeInfo = tgtInput?.getTypeInfo() || { type: '', dim: 0 }
|
||
|
|
|
||
|
|
if(srcTypeInfo.type != tgtTypeInfo.type){ console.warn('GraphService.createEdge: type mismatch'); return null }
|
||
|
|
const srcDim = srcTypeInfo.dim + srcModel.upDimension
|
||
|
|
if(srcDim != tgtTypeInfo.dim && srcDim != tgtTypeInfo.dim + 1 && srcDim + 1 != tgtTypeInfo.dim){ console.warn('GraphService.createEdge: dimension mismatch'); return null }
|
||
|
|
|
||
|
|
// remove existing edges for single-input ports
|
||
|
|
const existing = runtime.getAllEdgesForView().filter(e => e.target === edge.target && (e.targetHandle || 'input') === (edge.targetHandle || 'input'))
|
||
|
|
if(existing.length){ runtime.removeEdgesByTarget(edge.target, tgtPort) }
|
||
|
|
|
||
|
|
const runtimeEdge = { ...(edge as any), source_port: srcPort, target_port: tgtPort, dimension_mode: 'none', style: edge.style || runtime.graph?.getEdgeStyle(srcTypeInfo.type, srcDim, tgtTypeInfo.dim), animated: !!edge.animated }
|
||
|
|
const createdId = runtime.createEdge(runtimeEdge)
|
||
|
|
return createdId || runtimeEdge.id || null
|
||
|
|
},
|
||
|
|
|
||
|
|
removeEdge(edgeId: string){ return runtime.removeEdge(edgeId) },
|
||
|
|
|
||
|
|
updateEdgeStyle(edgeId: string, style: Record<string, any>, animated?: boolean){
|
||
|
|
const all = runtime.getAllEdgesForView()
|
||
|
|
const found = all.find((e:any) => e.id === edgeId)
|
||
|
|
if(!found) return false
|
||
|
|
const orig = found.data
|
||
|
|
orig.style = style || {}
|
||
|
|
orig.animated = !!animated
|
||
|
|
return true
|
||
|
|
},
|
||
|
|
|
||
|
|
deleteEdgesByHandle(edgeId: string){
|
||
|
|
const all = runtime.getAllEdgesForView()
|
||
|
|
const target = all.find((e:any) => e.id === edgeId)
|
||
|
|
if(!target) return false
|
||
|
|
const toRemove = all.filter((e:any) => e.target === target.target && (e.targetHandle || e.target_port) === (target.targetHandle || target.target_port))
|
||
|
|
for(const r of toRemove){ runtime.removeEdge(r.id) }
|
||
|
|
return true
|
||
|
|
},
|
||
|
|
|
||
|
|
deleteNode(nodeId: string){
|
||
|
|
if(runtime.graph && typeof runtime.graph.removeNode === 'function') runtime.graph.removeNode(nodeId)
|
||
|
|
const all = runtime.getAllEdgesForView()
|
||
|
|
for(const e of all){ if(e.source === nodeId || e.target === nodeId){ runtime.removeEdge(e.id) } }
|
||
|
|
return true
|
||
|
|
},
|
||
|
|
|
||
|
|
duplicateNode(nodeId: string){
|
||
|
|
const node = runtime.getNodeModel(nodeId)
|
||
|
|
if(!node) return null
|
||
|
|
const newPos = { x: (node.position?.x || 0) + 50, y: (node.position?.y || 0) + 50 }
|
||
|
|
const newId = runtime.createNode(node.schemaName, newPos, { ...(node.params || {}) }, node.meta)
|
||
|
|
return newId
|
||
|
|
},
|
||
|
|
|
||
|
|
createNode(schemaName: string, position: {x:number,y:number}, params: Record<string, any> = {}, meta?: any){
|
||
|
|
const node = runtime.createNode(schemaName, position, params, meta)
|
||
|
|
return node.id
|
||
|
|
},
|
||
|
|
|
||
|
|
updateNodePosition(nodeId: string, position: {x:number,y:number}){
|
||
|
|
return runtime.updateNodePosition(nodeId, position)
|
||
|
|
},
|
||
|
|
|
||
|
|
updateNodeParams(nodeId: string, params: Record<string, any>){
|
||
|
|
try{
|
||
|
|
const node = runtime.getNodeModel(nodeId)
|
||
|
|
if(!node) return false
|
||
|
|
node.params = { ...(node.params || {}), ...(params || {}) }
|
||
|
|
return true
|
||
|
|
}catch(e){ console.error('GraphService.updateNodeParams failed', e); return false }
|
||
|
|
},
|
||
|
|
|
||
|
|
disconnectNode(nodeId: string){
|
||
|
|
const all = runtime.getAllEdgesForView()
|
||
|
|
for(const e of all){ if(e.source === nodeId || e.target === nodeId){ runtime.removeEdge(e.id) } }
|
||
|
|
return true
|
||
|
|
},
|
||
|
|
|
||
|
|
exposeParam(nodeId: string, paramName: string, paramType?: string){ return runtime.exposeParamAsInput(nodeId, paramName, paramType) },
|
||
|
|
unexposeParam(nodeId: string, paramName: string){ return runtime.removeExposedParam(nodeId, paramName) },
|
||
|
|
|
||
|
|
// View helpers
|
||
|
|
getNodeModel(id: string) { return runtime.getNodeModel(id) },
|
||
|
|
getAllNodesForView() { return runtime.getAllNodesForView() },
|
||
|
|
getAllEdgesForView() { return runtime.getAllEdgesForView ? runtime.getAllEdgesForView() : [] },
|
||
|
|
|
||
|
|
subscribeGraphVersion(listener: (v: number) => void){
|
||
|
|
const unsub = useRuntimeStore.subscribe((s) => s.graphVersion, (v) => listener(v))
|
||
|
|
return unsub
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default GraphService
|