diff --git a/api/controllers/console/app/online_user.py b/api/controllers/console/app/online_user.py index 34423f40da..4a95e777d9 100644 --- a/api/controllers/console/app/online_user.py +++ b/api/controllers/console/app/online_user.py @@ -207,6 +207,8 @@ def handle_collaboration_event(sid, data): Handle general collaboration events, include: 1. mouseMove 2. varsAndFeaturesUpdate + 3. syncRequest(ask leader to update graph) + 4. appStateUpdate """ mapping = redis_client.get(f"ws_sid_map:{sid}") diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 3d572b926a..2463490fc1 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AppCard from '@/app/components/app/overview/appCard' @@ -19,6 +19,8 @@ import { asyncRunSafe } from '@/utils' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import type { IAppCardProps } from '@/app/components/app/overview/appCard' import { useStore as useAppStore } from '@/app/components/app/store' +import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager' +import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager' export type ICardViewProps = { appId: string @@ -47,15 +49,44 @@ const CardView: FC = ({ appId, isInPanel, className }) => { message ||= (type === 'success' ? 'modifiedSuccessfully' : 'modifiedUnsuccessfully') - if (type === 'success') + if (type === 'success') { updateAppDetail() + // Emit collaboration event to notify other clients of app state changes + const socket = webSocketClient.getSocket(appId) + if (socket) { + socket.emit('collaboration_event', { + type: 'appStateUpdate', + data: { timestamp: Date.now() }, + timestamp: Date.now(), + }) + } + } + notify({ type, message: t(`common.actionMsg.${message}`), }) } + // Listen for collaborative app state updates from other clients + useEffect(() => { + if (!appId) return + + const unsubscribe = collaborationManager.onAppStateUpdate(async (update: any) => { + try { + console.log('Received app state update from collaboration:', update) + // Update app detail when other clients modify app state + await updateAppDetail() + } + catch (error) { + console.error('app state update failed:', error) + } + }) + + return unsubscribe + }, [appId]) + const onChangeSiteStatus = async (value: boolean) => { const [err] = await asyncRunSafe( updateAppSiteStatus({ diff --git a/web/app/components/workflow/collaboration/core/collaboration-manager.ts b/web/app/components/workflow/collaboration/core/collaboration-manager.ts index 38e47bba97..98fb76c03b 100644 --- a/web/app/components/workflow/collaboration/core/collaboration-manager.ts +++ b/web/app/components/workflow/collaboration/core/collaboration-manager.ts @@ -246,6 +246,10 @@ export class CollaborationManager { return this.eventEmitter.on('varsAndFeaturesUpdate', callback) } + onAppStateUpdate(callback: (update: any) => void): () => void { + return this.eventEmitter.on('appStateUpdate', callback) + } + onLeaderChange(callback: (isLeader: boolean) => void): () => void { return this.eventEmitter.on('leaderChange', callback) } @@ -575,6 +579,10 @@ export class CollaborationManager { console.log('Processing varsAndFeaturesUpdate event:', update) this.eventEmitter.emit('varsAndFeaturesUpdate', update) } + else if (update.type === 'appStateUpdate') { + console.log('Processing appStateUpdate event:', update) + this.eventEmitter.emit('appStateUpdate', update) + } else if (update.type === 'syncRequest') { console.log('Received sync request from another user') // Only process if we are the leader