mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 04:26:30 +08:00
try a lot for yjs, but update data still not work...
This commit is contained in:
parent
41372168b6
commit
4cc01c8aa8
@ -138,3 +138,22 @@ def handle_collaboration_event(sid, data):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {"msg": "event_broadcasted"}
|
return {"msg": "event_broadcasted"}
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("yjs_update")
|
||||||
|
def handle_yjs_update(sid, data):
|
||||||
|
"""
|
||||||
|
Handle Y.js document updates - simple broadcast relay.
|
||||||
|
"""
|
||||||
|
mapping = redis_client.get(f"ws_sid_map:{sid}")
|
||||||
|
|
||||||
|
if not mapping:
|
||||||
|
return {"msg": "unauthorized"}, 401
|
||||||
|
|
||||||
|
mapping_data = json.loads(mapping)
|
||||||
|
workflow_id = mapping_data["workflow_id"]
|
||||||
|
user_id = mapping_data["user_id"]
|
||||||
|
|
||||||
|
sio.emit("yjs_update", data, room=workflow_id, skip_sid=sid)
|
||||||
|
|
||||||
|
return {"msg": "yjs_update_broadcasted"}
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import {
|
|||||||
} from '@/service/workflow'
|
} from '@/service/workflow'
|
||||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||||
|
import { useCollaborationStore } from '@/app/components/workflow/store/collaboration-store'
|
||||||
|
|
||||||
export const useWorkflowInit = () => {
|
export const useWorkflowInit = () => {
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const {
|
const {
|
||||||
@ -38,9 +40,45 @@ export const useWorkflowInit = () => {
|
|||||||
}, [workflowStore])
|
}, [workflowStore])
|
||||||
useWorkflowConfig(appDetail.id, handleUpdateWorkflowConfig)
|
useWorkflowConfig(appDetail.id, handleUpdateWorkflowConfig)
|
||||||
|
|
||||||
|
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)
|
||||||
|
resolve()
|
||||||
|
else
|
||||||
|
setTimeout(checkInitialized, 50)
|
||||||
|
}
|
||||||
|
checkInitialized()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 handleGetInitialWorkflowData = useCallback(async () => {
|
const handleGetInitialWorkflowData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`)
|
const [res] = await Promise.all([
|
||||||
|
fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`),
|
||||||
|
initializeCollaboration(appDetail.id),
|
||||||
|
])
|
||||||
setData(res)
|
setData(res)
|
||||||
workflowStore.setState({
|
workflowStore.setState({
|
||||||
envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
|
envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
|
||||||
@ -50,6 +88,7 @@ export const useWorkflowInit = () => {
|
|||||||
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
|
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
|
||||||
conversationVariables: res.conversation_variables || [],
|
conversationVariables: res.conversation_variables || [],
|
||||||
})
|
})
|
||||||
|
await populateYjsWithServerData(res)
|
||||||
setSyncWorkflowDraftHash(res.hash)
|
setSyncWorkflowDraftHash(res.hash)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,7 @@ import {
|
|||||||
} from './use-workflow'
|
} from './use-workflow'
|
||||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||||
import useInspectVarsCrud from './use-inspect-vars-crud'
|
import useInspectVarsCrud from './use-inspect-vars-crud'
|
||||||
|
import { useCollaborationStore } from '@/app/components/workflow/store/collaboration-store'
|
||||||
|
|
||||||
export const useNodesInteractions = () => {
|
export const useNodesInteractions = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -1496,6 +1497,21 @@ export const useNodesInteractions = () => {
|
|||||||
return draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
|
return draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
|
||||||
}, [store, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory])
|
}, [store, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
|||||||
@ -83,6 +83,7 @@ import Confirm from '@/app/components/base/confirm'
|
|||||||
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
||||||
import { HooksStoreContextProvider } from './hooks-store'
|
import { HooksStoreContextProvider } from './hooks-store'
|
||||||
import type { Shape as HooksStoreShape } from './hooks-store'
|
import type { Shape as HooksStoreShape } from './hooks-store'
|
||||||
|
import { useCollaborationStore } from '@/app/components/workflow/store/collaboration-store'
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
[CUSTOM_NODE]: CustomNode,
|
[CUSTOM_NODE]: CustomNode,
|
||||||
@ -127,6 +128,22 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
return workflowCanvasHeight - bottomPanelHeight
|
return workflowCanvasHeight - bottomPanelHeight
|
||||||
}, [workflowCanvasHeight, bottomPanelHeight])
|
}, [workflowCanvasHeight, bottomPanelHeight])
|
||||||
|
|
||||||
|
const collaborationNodes = useCollaborationStore((state) => {
|
||||||
|
return state.nodes
|
||||||
|
})
|
||||||
|
const collaborationEdges = useCollaborationStore((state) => {
|
||||||
|
return state.edges
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNodes(collaborationNodes)
|
||||||
|
}, [collaborationNodes, setNodes])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('collaborationEdges changed:', collaborationEdges, 122112)
|
||||||
|
setEdges(collaborationEdges)
|
||||||
|
}, [collaborationEdges, setEdges])
|
||||||
|
|
||||||
// update workflow Canvas width and height
|
// update workflow Canvas width and height
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workflowContainerRef.current) {
|
if (workflowContainerRef.current) {
|
||||||
|
|||||||
132
web/app/components/workflow/store/collaboration-store.ts
Normal file
132
web/app/components/workflow/store/collaboration-store.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { create } from 'zustand'
|
||||||
|
import * as Y from 'yjs'
|
||||||
|
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
|
||||||
|
private socket: any
|
||||||
|
private isDestroyed = false
|
||||||
|
private onRemoteUpdate?: () => void
|
||||||
|
|
||||||
|
constructor(socket: any, doc: Y.Doc, onRemoteUpdate?: () => void) {
|
||||||
|
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.socket.on('yjs_update', (updateData: Uint8Array) => {
|
||||||
|
Y.applyUpdate(this.doc, new Uint8Array(updateData), 'remote')
|
||||||
|
|
||||||
|
if (this.onRemoteUpdate)
|
||||||
|
this.onRemoteUpdate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.isDestroyed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
nodes: Node[]
|
||||||
|
edges: Edge[]
|
||||||
|
|
||||||
|
initCollaboration: (appId: string) => void
|
||||||
|
destroyCollaboration: () => void
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCollaborationStore = create<CollaborationStore>((set, get) => ({
|
||||||
|
ydoc: null,
|
||||||
|
provider: null,
|
||||||
|
yNodesMap: null,
|
||||||
|
yEdgesMap: null,
|
||||||
|
yTestMap: null,
|
||||||
|
yTestArray: 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 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 provider = new YjsSocketIOProvider(socket, globalYDoc, updateReactState)
|
||||||
|
|
||||||
|
yNodesMap.observe((event) => {
|
||||||
|
console.log('yNodesMap changed:', event)
|
||||||
|
updateReactState()
|
||||||
|
})
|
||||||
|
yEdgesMap.observe((event) => {
|
||||||
|
console.log('yEdgesMap changed:', event)
|
||||||
|
updateReactState()
|
||||||
|
})
|
||||||
|
|
||||||
|
updateReactState()
|
||||||
|
|
||||||
|
set({
|
||||||
|
ydoc,
|
||||||
|
provider,
|
||||||
|
yNodesMap,
|
||||||
|
yEdgesMap,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyCollaboration: () => {
|
||||||
|
const { provider } = get()
|
||||||
|
provider?.destroy()
|
||||||
|
set({
|
||||||
|
ydoc: null,
|
||||||
|
provider: null,
|
||||||
|
yNodesMap: null,
|
||||||
|
yEdgesMap: null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}))
|
||||||
@ -148,6 +148,7 @@
|
|||||||
"tldts": "^7.0.9",
|
"tldts": "^7.0.9",
|
||||||
"use-context-selector": "^2.0.0",
|
"use-context-selector": "^2.0.0",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
|
"yjs": "^13.6.27",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zundo": "^2.1.0",
|
"zundo": "^2.1.0",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
|
|||||||
19
web/pnpm-lock.yaml
generated
19
web/pnpm-lock.yaml
generated
@ -56,7 +56,7 @@ importers:
|
|||||||
version: 0.30.0
|
version: 0.30.0
|
||||||
'@lexical/react':
|
'@lexical/react':
|
||||||
specifier: ^0.30.0
|
specifier: ^0.30.0
|
||||||
version: 0.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.24)
|
version: 0.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27)
|
||||||
'@lexical/selection':
|
'@lexical/selection':
|
||||||
specifier: ^0.30.0
|
specifier: ^0.30.0
|
||||||
version: 0.30.0
|
version: 0.30.0
|
||||||
@ -345,6 +345,9 @@ importers:
|
|||||||
uuid:
|
uuid:
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
|
yjs:
|
||||||
|
specifier: ^13.6.27
|
||||||
|
version: 13.6.27
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.23.8
|
specifier: ^3.23.8
|
||||||
version: 3.24.2
|
version: 3.24.2
|
||||||
@ -8895,8 +8898,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
yjs@13.6.24:
|
yjs@13.6.27:
|
||||||
resolution: {integrity: sha512-xn/pYLTZa3uD1uDG8lpxfLRo5SR/rp0frdASOl2a71aYNvUXdWcLtVL91s2y7j+Q8ppmjZ9H3jsGVgoFMbT2VA==}
|
resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==}
|
||||||
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
||||||
|
|
||||||
yn@3.1.1:
|
yn@3.1.1:
|
||||||
@ -10780,7 +10783,7 @@ snapshots:
|
|||||||
'@lexical/utils': 0.30.0
|
'@lexical/utils': 0.30.0
|
||||||
lexical: 0.30.0
|
lexical: 0.30.0
|
||||||
|
|
||||||
'@lexical/react@0.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.24)':
|
'@lexical/react@0.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lexical/devtools-core': 0.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@lexical/devtools-core': 0.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@lexical/dragon': 0.30.0
|
'@lexical/dragon': 0.30.0
|
||||||
@ -10796,7 +10799,7 @@ snapshots:
|
|||||||
'@lexical/table': 0.30.0
|
'@lexical/table': 0.30.0
|
||||||
'@lexical/text': 0.30.0
|
'@lexical/text': 0.30.0
|
||||||
'@lexical/utils': 0.30.0
|
'@lexical/utils': 0.30.0
|
||||||
'@lexical/yjs': 0.30.0(yjs@13.6.24)
|
'@lexical/yjs': 0.30.0(yjs@13.6.27)
|
||||||
lexical: 0.30.0
|
lexical: 0.30.0
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
@ -10832,12 +10835,12 @@ snapshots:
|
|||||||
'@lexical/table': 0.30.0
|
'@lexical/table': 0.30.0
|
||||||
lexical: 0.30.0
|
lexical: 0.30.0
|
||||||
|
|
||||||
'@lexical/yjs@0.30.0(yjs@13.6.24)':
|
'@lexical/yjs@0.30.0(yjs@13.6.27)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lexical/offset': 0.30.0
|
'@lexical/offset': 0.30.0
|
||||||
'@lexical/selection': 0.30.0
|
'@lexical/selection': 0.30.0
|
||||||
lexical: 0.30.0
|
lexical: 0.30.0
|
||||||
yjs: 13.6.24
|
yjs: 13.6.27
|
||||||
|
|
||||||
'@mapbox/node-pre-gyp@1.0.11':
|
'@mapbox/node-pre-gyp@1.0.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -19239,7 +19242,7 @@ snapshots:
|
|||||||
y18n: 5.0.8
|
y18n: 5.0.8
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
|
|
||||||
yjs@13.6.24:
|
yjs@13.6.27:
|
||||||
dependencies:
|
dependencies:
|
||||||
lib0: 0.2.102
|
lib0: 0.2.102
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user