add ENABLE_COLLABORATION_MODE

This commit is contained in:
hjlarry
2025-10-21 11:46:28 +08:00
parent a23bf53d2b
commit 753234fdfe
11 changed files with 80 additions and 25 deletions

View File

@ -45,26 +45,38 @@ const WorkflowMain = ({
const reactFlow = useReactFlow()
const store = useStoreApi()
const { startCursorTracking, stopCursorTracking, onlineUsers, cursors, isConnected } = useCollaboration(appId || '', store)
const {
startCursorTracking,
stopCursorTracking,
onlineUsers,
cursors,
isConnected,
isEnabled: isCollaborationEnabled,
} = useCollaboration(appId || '', store)
const [myUserId, setMyUserId] = useState<string | null>(null)
useEffect(() => {
if (isConnected)
if (isCollaborationEnabled && isConnected)
setMyUserId('current-user')
}, [isConnected])
else
setMyUserId(null)
}, [isCollaborationEnabled, isConnected])
const filteredCursors = Object.fromEntries(
Object.entries(cursors).filter(([userId]) => userId !== myUserId),
)
useEffect(() => {
if (!isCollaborationEnabled)
return
if (containerRef.current)
startCursorTracking(containerRef as React.RefObject<HTMLElement>, reactFlow)
return () => {
stopCursorTracking()
}
}, [startCursorTracking, stopCursorTracking, reactFlow])
}, [startCursorTracking, stopCursorTracking, reactFlow, isCollaborationEnabled])
const handleWorkflowDataUpdate = useCallback((payload: any) => {
const {
@ -128,7 +140,7 @@ const WorkflowMain = ({
} = useWorkflowRun()
useEffect(() => {
if (!appId) return
if (!appId || !isCollaborationEnabled) return
const unsubscribe = collaborationManager.onVarsAndFeaturesUpdate(async (update: any) => {
try {
@ -141,11 +153,11 @@ const WorkflowMain = ({
})
return unsubscribe
}, [appId, handleWorkflowDataUpdate])
}, [appId, handleWorkflowDataUpdate, isCollaborationEnabled])
// Listen for workflow updates from other users
useEffect(() => {
if (!appId) return
if (!appId || !isCollaborationEnabled) return
const unsubscribe = collaborationManager.onWorkflowUpdate(async () => {
console.log('Received workflow update from collaborator, fetching latest workflow data')
@ -170,11 +182,11 @@ const WorkflowMain = ({
})
return unsubscribe
}, [appId, handleWorkflowDataUpdate, handleUpdateWorkflowCanvas])
}, [appId, handleWorkflowDataUpdate, handleUpdateWorkflowCanvas, isCollaborationEnabled])
// Listen for sync requests from other users (only processed by leader)
useEffect(() => {
if (!appId) return
if (!appId || !isCollaborationEnabled) return
const unsubscribe = collaborationManager.onSyncRequest(() => {
console.log('Leader received sync request, performing sync')
@ -182,7 +194,7 @@ const WorkflowMain = ({
})
return unsubscribe
}, [appId, doSyncWorkflowDraft])
}, [appId, doSyncWorkflowDraft, isCollaborationEnabled])
const {
handleStartWorkflowRun,
handleWorkflowStartRunInChatflow,

View File

@ -14,6 +14,7 @@ import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { API_PREFIX } from '@/config'
import { useWorkflowRefreshDraft } from '.'
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
import { useGlobalPublicStore } from '@/context/global-public-context'
export const useNodesSyncDraft = () => {
const store = useStoreApi()
@ -22,6 +23,7 @@ export const useNodesSyncDraft = () => {
const { getNodesReadOnly } = useNodesReadOnly()
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
const params = useParams()
const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode)
const getPostParams = useCallback(() => {
const {
@ -86,21 +88,21 @@ export const useNodesSyncDraft = () => {
environment_variables: environmentVariables,
conversation_variables: conversationVariables,
hash: syncWorkflowDraftHash,
_is_collaborative: true,
_is_collaborative: isCollaborationEnabled,
},
}
}
}, [store, featuresStore, workflowStore])
}, [store, featuresStore, workflowStore, isCollaborationEnabled])
const syncWorkflowDraftWhenPageClose = useCallback(() => {
if (getNodesReadOnly())
return
// Check leader status at sync time
const currentIsLeader = collaborationManager.getIsLeader()
const currentIsLeader = isCollaborationEnabled ? collaborationManager.getIsLeader() : true
// Only allow leader to sync data
if (!currentIsLeader) {
if (isCollaborationEnabled && !currentIsLeader) {
console.log('Not leader, skipping sync on page close')
return
}
@ -114,7 +116,7 @@ export const useNodesSyncDraft = () => {
JSON.stringify(postParams.params),
)
}
}, [getPostParams, params.appId, getNodesReadOnly])
}, [getPostParams, params.appId, getNodesReadOnly, isCollaborationEnabled])
const doSyncWorkflowDraft = useCallback(async (
notRefreshWhenSyncError?: boolean,
@ -129,12 +131,13 @@ export const useNodesSyncDraft = () => {
return
// Check leader status at sync time
const currentIsLeader = collaborationManager.getIsLeader()
const currentIsLeader = isCollaborationEnabled ? collaborationManager.getIsLeader() : true
// If not leader and not forcing upload, request the leader to sync
if (!currentIsLeader && !forceUpload) {
if (isCollaborationEnabled && !currentIsLeader && !forceUpload) {
console.log('Not leader, requesting leader to sync workflow draft')
collaborationManager.emitSyncRequest()
if (isCollaborationEnabled)
collaborationManager.emitSyncRequest()
callback?.onSettled?.()
return
}
@ -181,7 +184,7 @@ export const useNodesSyncDraft = () => {
callback?.onSettled?.()
}
}
}, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft])
}, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft, isCollaborationEnabled])
return {
doSyncWorkflowDraft,

View File

@ -3,6 +3,7 @@ import type { ReactFlowInstance } from 'reactflow'
import { collaborationManager } from '../core/collaboration-manager'
import { CursorService } from '../services/cursor-service'
import type { CollaborationState } from '../types/collaboration'
import { useGlobalPublicStore } from '@/context/global-public-context'
export function useCollaboration(appId: string, reactFlowStore?: any) {
const [state, setState] = useState<Partial<CollaborationState & { isLeader: boolean }>>({
@ -14,9 +15,19 @@ export function useCollaboration(appId: string, reactFlowStore?: any) {
})
const cursorServiceRef = useRef<CursorService | null>(null)
const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode)
useEffect(() => {
if (!appId) return
if (!appId || !isCollaborationEnabled) {
setState({
isConnected: false,
onlineUsers: [],
cursors: {},
nodePanelPresence: {},
isLeader: false,
})
return
}
let connectionId: string | null = null
let isUnmounted = false
@ -75,9 +86,12 @@ export function useCollaboration(appId: string, reactFlowStore?: any) {
if (connectionId)
collaborationManager.disconnect(connectionId)
}
}, [appId, reactFlowStore])
}, [appId, reactFlowStore, isCollaborationEnabled])
const startCursorTracking = (containerRef: React.RefObject<HTMLElement>, reactFlowInstance?: ReactFlowInstance) => {
if (!isCollaborationEnabled || !cursorServiceRef.current)
return
if (cursorServiceRef.current) {
cursorServiceRef.current.startTracking(containerRef, (position) => {
collaborationManager.emitCursorMove(position)
@ -96,6 +110,7 @@ export function useCollaboration(appId: string, reactFlowStore?: any) {
nodePanelPresence: state.nodePanelPresence || {},
isLeader: state.isLeader || false,
leaderId: collaborationManager.getLeaderId(),
isEnabled: isCollaborationEnabled,
startCursorTracking,
stopCursorTracking,
}

View File

@ -50,7 +50,7 @@ const useAvatarUrls = (users: any[]) => {
const OnlineUsers = () => {
const appId = useStore(s => s.appId)
const { onlineUsers, cursors } = useCollaboration(appId as string)
const { onlineUsers, cursors, isEnabled: isCollaborationEnabled } = useCollaboration(appId as string)
const { userProfile } = useAppContext()
const reactFlow = useReactFlow()
const [dropdownOpen, setDropdownOpen] = useState(false)
@ -67,7 +67,7 @@ const OnlineUsers = () => {
reactFlow.setCenter(cursor.x, cursor.y, { zoom: 1, duration: 800 })
}
if (!onlineUsers || onlineUsers.length === 0)
if (!isCollaborationEnabled || !onlineUsers || onlineUsers.length === 0)
return null
// Display logic:

View File

@ -39,6 +39,7 @@ export type SystemFeatures = {
enable_email_code_login: boolean
enable_email_password_login: boolean
enable_social_oauth_login: boolean
enable_collaboration_mode: boolean
is_allow_create_workspace: boolean
is_allow_register: boolean
is_email_setup: boolean
@ -75,6 +76,7 @@ export const defaultSystemFeatures: SystemFeatures = {
enable_email_code_login: false,
enable_email_password_login: false,
enable_social_oauth_login: false,
enable_collaboration_mode: false,
is_allow_create_workspace: false,
is_allow_register: false,
is_email_setup: false,