TraceStudio-dev/web/src/core/services/GraphService.ts

111 lines
5.0 KiB
TypeScript
Raw Normal View History

2026-01-12 03:32:51 +08:00
import useRuntimeStore from '../store/runtimeStore'
import { runtime } from '../model/StudioRuntime'
2026-01-12 11:13:01 +08:00
import { PortModel } from '../model/PortModel';
2026-01-12 03:32:51 +08:00
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)
2026-01-12 11:13:01 +08:00
const srcTypeInfo = PortModel.getTypeInfo(srcOutput?.type || '')
const tgtTypeInfo = PortModel.getTypeInfo(tgtInput?.type || '')
2026-01-12 03:32:51 +08:00
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