diff --git a/api/controllers/console/app/online_user.py b/api/controllers/console/app/online_user.py index afb77e1ac6..da50d1508d 100644 --- a/api/controllers/console/app/online_user.py +++ b/api/controllers/console/app/online_user.py @@ -1,4 +1,5 @@ import json +import time from flask import request from flask_restful import Resource, marshal_with, reqparse @@ -106,3 +107,43 @@ class OnlineUserApi(Resource): return {"data": results} api.add_resource(OnlineUserApi, "/online-users") + + +@ext_socketio.on("collaboration_event") +def handle_collaboration_event(data): + """ + Handle general collaboration events, include: + 1. mouseMove + 2. openPanel + + """ + sid = request.sid + 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"] + + event_type = data.get("type") + event_data = data.get("data") + timestamp = data.get("timestamp", int(time.time())) + + if not event_type or not event_data: + return {"msg": "invalid event data"}, 400 + + ext_socketio.emit( + "collaboration_update", + { + "type": event_type, + "userId": user_id, + "data": event_data, + "timestamp": timestamp + }, + room=workflow_id, + skip_sid=sid + ) + + return {"msg": "event_broadcasted"} diff --git a/web/app/components/workflow-app/hooks/use-workflow-websocket.ts b/web/app/components/workflow-app/hooks/use-workflow-websocket.ts index c7864b37ab..1980e92e8a 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-websocket.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-websocket.ts @@ -24,16 +24,35 @@ export function useCollaborativeCursors(appId: string) { const socket = connectOnlineUserWebSocket(appId) socketRef.current = socket - // Listen for other users' cursor updates - socket.on('users_mouse_positions', (positions: Record) => { - setCursors(positions) + // Listen for collaboration updates from other users + socket.on('collaboration_update', (update: { + type: string + userId: string + data: any + timestamp: number + }) => { + if (update.type === 'mouseMove') { + setCursors(prev => ({ + ...prev, + [update.userId]: { + x: update.data.x, + y: update.data.y, + userId: update.userId, + }, + })) + } + // if (update.type === 'openPanel') { ... } }) - // Mouse move handler with throttle (e.g. 30ms) + // Mouse move handler with throttle 300ms const handleMouseMove = (e: MouseEvent) => { const now = Date.now() - if (now - lastSent.current > 30) { - socket.emit('mouse_move', { x: e.clientX, y: e.clientY }) + if (now - lastSent.current > 300) { + socket.emit('collaboration_event', { + type: 'mouseMove', + data: { x: e.clientX, y: e.clientY }, + timestamp: now, + }) lastSent.current = now } } @@ -41,7 +60,7 @@ export function useCollaborativeCursors(appId: string) { return () => { window.removeEventListener('mousemove', handleMouseMove) - socket.off('users_mouse_positions') + socket.off('collaboration_update') disconnectOnlineUserWebSocket() } }, [appId])