use loro for crdt data

This commit is contained in:
hjlarry
2025-07-31 14:02:53 +08:00
parent 4cc01c8aa8
commit 965b65db6e
9 changed files with 113 additions and 125 deletions

View File

@ -40,14 +40,14 @@ export const useWorkflowInit = () => {
}, [workflowStore])
useWorkflowConfig(appDetail.id, handleUpdateWorkflowConfig)
const initializeCollaboration = async (appId: string) => {
const initializeCollaboration = async (appId: string) => {
const { initCollaboration } = useCollaborationStore.getState()
initCollaboration(appId)
return new Promise<void>((resolve) => {
const checkInitialized = () => {
const { yNodesMap, yEdgesMap } = useCollaborationStore.getState()
if (yNodesMap && yEdgesMap)
const { nodesMap, edgesMap } = useCollaborationStore.getState()
if (nodesMap && edgesMap)
resolve()
else
setTimeout(checkInitialized, 50)
@ -56,21 +56,18 @@ export const useWorkflowInit = () => {
})
}
const populateYjsWithServerData = async (serverData: any) => {
const { yNodesMap, yEdgesMap } = useCollaborationStore.getState()
if (yNodesMap && yEdgesMap && serverData.graph) {
const { ydoc } = useCollaborationStore.getState()
ydoc?.transact(() => {
serverData.graph.nodes?.forEach((node: any) => {
yNodesMap.set(node.id, node)
})
serverData.graph.edges?.forEach((edge: any) => {
yEdgesMap.set(edge.id, edge)
})
const populateCollaborationWithServerData = async (serverData: any) => {
const { nodesMap, edgesMap, loroDoc } = useCollaborationStore.getState()
serverData.graph.nodes?.forEach((node: any) => {
console.log('Setting node:', node.id, node)
nodesMap.set(node.id, node)
})
}
serverData.graph.edges?.forEach((edge: any) => {
console.log('Setting edge:', edge.id, edge)
edgesMap.set(edge.id, edge)
})
loroDoc.commit()
}
const handleGetInitialWorkflowData = useCallback(async () => {
@ -88,7 +85,7 @@ export const useWorkflowInit = () => {
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
conversationVariables: res.conversation_variables || [],
})
await populateYjsWithServerData(res)
await populateCollaborationWithServerData(res)
setSyncWorkflowDraftHash(res.hash)
setIsLoading(false)
}

View File

@ -1498,18 +1498,15 @@ export const useNodesInteractions = () => {
})
setEdges(newEdges)
const { yNodesMap, yEdgesMap, ydoc } = useCollaborationStore.getState()
if (yNodesMap && yEdgesMap && ydoc) {
ydoc.transact(() => {
newNodes.forEach((node) => {
yNodesMap.set(node.id, node)
})
console.log('Before edge delete, yEdgesMap size:', yEdgesMap?.size)
connectedEdges.forEach((edge) => {
yEdgesMap.delete(edge.id)
})
console.log('After edge delete, yEdgesMap size:', yEdgesMap?.size)
const { nodesMap, edgesMap, loroDoc } = useCollaborationStore.getState()
if (nodesMap && edgesMap && loroDoc) {
// newNodes.forEach((node) => {
// nodesMap.set(node.id, node)
// })
connectedEdges.forEach((edge) => {
edgesMap.delete(edge.id)
})
loroDoc.commit()
}
handleSyncWorkflowDraft()

View File

@ -140,7 +140,6 @@ export const Workflow: FC<WorkflowProps> = memo(({
}, [collaborationNodes, setNodes])
useEffect(() => {
console.log('collaborationEdges changed:', collaborationEdges, 122112)
setEdges(collaborationEdges)
}, [collaborationEdges, setEdges])

View File

@ -1,132 +1,118 @@
import { create } from 'zustand'
import * as Y from 'yjs'
import { LoroDoc } from 'loro-crdt'
import type { Edge, Node } from '../types'
import { useWebSocketStore } from './websocket-store'
let globalYDoc: Y.Doc | null = null
let globalYNodesMap: Y.Map<any> | null = null
let globalYEdgesMap: Y.Map<any> | null = null
class YjsSocketIOProvider {
private doc: Y.Doc
class LoroSocketIOProvider {
private doc: any
private socket: any
private isDestroyed = false
private onRemoteUpdate?: () => void
constructor(socket: any, doc: Y.Doc, onRemoteUpdate?: () => void) {
constructor(socket: any, doc: any) {
this.socket = socket
this.doc = doc
this.onRemoteUpdate = onRemoteUpdate
this.setupEventListeners()
}
private setupEventListeners() {
this.doc.on('update', (update: Uint8Array, origin: any) => {
if (origin !== 'remote')
this.socket.emit('yjs_update', update)
this.doc.subscribe((event: any) => {
if (event.origin !== 'remote') {
const update = this.doc.export({ mode: 'update' })
this.socket.emit('graph_update', update)
}
})
this.socket.on('yjs_update', (updateData: Uint8Array) => {
Y.applyUpdate(this.doc, new Uint8Array(updateData), 'remote')
if (this.onRemoteUpdate)
this.onRemoteUpdate()
this.socket.on('graph_update', (updateData: Uint8Array) => {
try {
const data = new Uint8Array(updateData)
this.doc.import(data)
}
catch (error) {
console.error('Error importing graph update:', error)
}
})
}
destroy() {
this.isDestroyed = true
this.socket.off('graph_update')
}
}
type CollaborationStore = {
ydoc: Y.Doc | null
provider: YjsSocketIOProvider | null
yNodesMap: Y.Map<any> | null
yEdgesMap: Y.Map<any> | null
yTestMap: Y.Map<any> | null
yTestArray: Y.Array<any> | null
loroDoc: any | null
provider: LoroSocketIOProvider | null
nodesMap: any | null
edgesMap: any | null
nodes: Node[]
edges: Edge[]
updateNodes?: () => void
updateEdges?: () => void
initCollaboration: (appId: string) => void
destroyCollaboration: () => void
}
export const useCollaborationStore = create<CollaborationStore>((set, get) => ({
ydoc: null,
loroDoc: null,
provider: null,
yNodesMap: null,
yEdgesMap: null,
yTestMap: null,
yTestArray: null,
nodesMap: null,
edgesMap: null,
nodes: [],
edges: [],
initCollaboration: (appId: string) => {
if (!globalYDoc) {
console.log('Creating new global Y.Doc instance')
globalYDoc = new Y.Doc()
globalYNodesMap = globalYDoc.getMap<any>('nodes')
globalYEdgesMap = globalYDoc.getMap<any>('edges')
}
else {
console.log('Reusing existing global Y.Doc instance')
}
const ydoc = globalYDoc
const yNodesMap = globalYNodesMap!
const yEdgesMap = globalYEdgesMap!
const { getSocket } = useWebSocketStore.getState()
const socket = getSocket(appId)
const doc = new LoroDoc()
const nodesMap = doc.getMap('nodes')
const edgesMap = doc.getMap('edges')
const updateReactState = () => {
console.log('updateReactState called')
const nodes = Array.from(yNodesMap.values())
const edges = Array.from(yEdgesMap.values())
console.log('Y.js data - nodes:', nodes.length, 'edges:', edges.length)
set({
nodes: [...nodes] as Node[],
edges: [...edges] as Edge[],
})
const updateNodes = () => {
const nodes = Array.from(nodesMap.values())
set({ nodes })
}
const provider = new YjsSocketIOProvider(socket, globalYDoc, updateReactState)
const updateEdges = () => {
const edges = Array.from(edgesMap.values())
set({ edges })
}
yNodesMap.observe((event) => {
console.log('yNodesMap changed:', event)
updateReactState()
})
yEdgesMap.observe((event) => {
console.log('yEdgesMap changed:', event)
updateReactState()
})
updateReactState()
const provider = new LoroSocketIOProvider(socket, doc)
set({
ydoc,
loroDoc: doc,
provider,
yNodesMap,
yEdgesMap,
nodesMap,
edgesMap,
nodes: [],
edges: [],
updateNodes,
updateEdges,
})
nodesMap.subscribe((event: any) => {
console.log('NodesMap changed:', event)
updateNodes()
})
edgesMap.subscribe((event: any) => {
console.log('EdgesMap changed:', event)
updateEdges()
})
},
destroyCollaboration: () => {
const { provider } = get()
provider?.destroy()
set({
ydoc: null,
provider: null,
yNodesMap: null,
yEdgesMap: null,
})
if (provider) {
provider.destroy()
set({
loroDoc: null,
provider: null,
nodesMap: null,
edgesMap: null,
nodes: [],
edges: [],
})
}
},
}))