mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
283 lines
8.4 KiB
TypeScript
283 lines
8.4 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { useChatStore } from './chat/store'
|
|
import { useConsoleStore } from './console/store'
|
|
import { useCustomToolsStore } from './custom-tools/store'
|
|
import { useExecutionStore } from './execution/store'
|
|
import { useNotificationStore } from './notifications/store'
|
|
import { useEnvironmentStore } from './settings/environment/store'
|
|
import { getSyncManagers, initializeSyncManagers, resetSyncManagers } from './sync-registry'
|
|
import {
|
|
loadRegistry,
|
|
loadSubblockValues,
|
|
loadWorkflowState,
|
|
saveSubblockValues,
|
|
saveWorkflowState,
|
|
} from './workflows/persistence'
|
|
import { useWorkflowRegistry } from './workflows/registry/store'
|
|
import { useSubBlockStore } from './workflows/subblock/store'
|
|
import { useWorkflowStore } from './workflows/workflow/store'
|
|
|
|
// Track initialization state
|
|
let isInitializing = false
|
|
|
|
/**
|
|
* Initialize the application state and sync system
|
|
*
|
|
* Note: Workflow scheduling is handled automatically by the workflowSync manager
|
|
* when workflows are synced to the database. The scheduling logic checks if a
|
|
* workflow has scheduling enabled in its starter block and updates the schedule
|
|
* accordingly.
|
|
*/
|
|
async function initializeApplication(): Promise<void> {
|
|
if (typeof window === 'undefined' || isInitializing) return
|
|
|
|
isInitializing = true
|
|
|
|
try {
|
|
// Load environment variables directly from DB
|
|
await useEnvironmentStore.getState().loadEnvironmentVariables()
|
|
|
|
// Initialize sync system for other stores
|
|
await initializeSyncManagers()
|
|
|
|
// After DB sync, check if we need to load from localStorage
|
|
// This is a fallback in case DB sync failed or there's no data in DB
|
|
const registryState = useWorkflowRegistry.getState()
|
|
if (Object.keys(registryState.workflows).length === 0) {
|
|
// No workflows loaded from DB, try localStorage as fallback
|
|
const workflows = loadRegistry()
|
|
if (workflows && Object.keys(workflows).length > 0) {
|
|
console.log('Loading workflows from localStorage as fallback')
|
|
useWorkflowRegistry.setState({ workflows })
|
|
|
|
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
|
if (activeWorkflowId) {
|
|
initializeWorkflowState(activeWorkflowId)
|
|
}
|
|
}
|
|
} else {
|
|
console.log('Using workflows loaded from DB, ignoring localStorage')
|
|
}
|
|
|
|
// 2. Register cleanup
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
} catch (error) {
|
|
console.error('Error during application initialization:', error)
|
|
} finally {
|
|
isInitializing = false
|
|
}
|
|
}
|
|
|
|
function initializeWorkflowState(workflowId: string): void {
|
|
// Load the specific workflow state from localStorage
|
|
const workflowState = loadWorkflowState(workflowId)
|
|
if (!workflowState) {
|
|
console.warn(`No saved state found for workflow ${workflowId}`)
|
|
return
|
|
}
|
|
|
|
// Set the workflow store state with the loaded state
|
|
useWorkflowStore.setState(workflowState)
|
|
|
|
// Initialize subblock values for this workflow
|
|
const subblockValues = loadSubblockValues(workflowId)
|
|
if (subblockValues) {
|
|
// Update the subblock store with the loaded values
|
|
useSubBlockStore.setState((state) => ({
|
|
workflowValues: {
|
|
...state.workflowValues,
|
|
[workflowId]: subblockValues,
|
|
},
|
|
}))
|
|
} else if (workflowState.blocks) {
|
|
// If no saved subblock values, initialize from blocks
|
|
useSubBlockStore.getState().initializeFromWorkflow(workflowId, workflowState.blocks)
|
|
}
|
|
|
|
console.log(`Initialized workflow state for ${workflowId}`)
|
|
}
|
|
|
|
/**
|
|
* Handle application cleanup before unload
|
|
*/
|
|
function handleBeforeUnload(event: BeforeUnloadEvent): void {
|
|
// 1. Persist current state
|
|
const currentId = useWorkflowRegistry.getState().activeWorkflowId
|
|
if (currentId) {
|
|
const currentState = useWorkflowStore.getState()
|
|
|
|
// Save the current workflow state with its ID
|
|
saveWorkflowState(currentId, {
|
|
blocks: currentState.blocks,
|
|
edges: currentState.edges,
|
|
loops: currentState.loops,
|
|
isDeployed: currentState.isDeployed,
|
|
deployedAt: currentState.deployedAt,
|
|
lastSaved: Date.now(),
|
|
// Include history for undo/redo functionality
|
|
history: currentState.history,
|
|
})
|
|
|
|
// Save subblock values for the current workflow
|
|
const subblockValues = useSubBlockStore.getState().workflowValues[currentId]
|
|
if (subblockValues) {
|
|
saveSubblockValues(currentId, subblockValues)
|
|
}
|
|
}
|
|
|
|
// 2. Final sync for managers that need it
|
|
getSyncManagers()
|
|
.filter((manager) => manager.config.syncOnExit)
|
|
.forEach((manager) => {
|
|
manager.sync()
|
|
})
|
|
|
|
// 3. Cleanup managers
|
|
getSyncManagers().forEach((manager) => manager.dispose())
|
|
|
|
// Standard beforeunload pattern
|
|
event.preventDefault()
|
|
event.returnValue = ''
|
|
}
|
|
|
|
/**
|
|
* Clean up sync system
|
|
*/
|
|
function cleanupApplication(): void {
|
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
getSyncManagers().forEach((manager) => manager.dispose())
|
|
}
|
|
|
|
/**
|
|
* Clear all user data when signing out
|
|
* This ensures data from one account doesn't persist to another
|
|
*/
|
|
export async function clearUserData(): Promise<void> {
|
|
if (typeof window === 'undefined') return
|
|
|
|
try {
|
|
// 1. Reset all sync managers to prevent any pending syncs
|
|
resetSyncManagers()
|
|
|
|
// 2. Reset all stores to their initial state
|
|
resetAllStores()
|
|
|
|
// 3. Clear localStorage except for essential app settings
|
|
const keysToKeep = ['next-favicon', 'theme']
|
|
const keysToRemove = Object.keys(localStorage).filter((key) => !keysToKeep.includes(key))
|
|
keysToRemove.forEach((key) => localStorage.removeItem(key))
|
|
|
|
console.log('User data cleared successfully')
|
|
} catch (error) {
|
|
console.error('Error clearing user data:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to manage application lifecycle
|
|
*/
|
|
export function useAppInitialization() {
|
|
useEffect(() => {
|
|
// Use Promise to handle async initialization
|
|
initializeApplication()
|
|
|
|
return () => {
|
|
cleanupApplication()
|
|
}
|
|
}, [])
|
|
}
|
|
|
|
/**
|
|
* Hook to reinitialize the application after successful login
|
|
* Use this in the login success handler or post-login page
|
|
*/
|
|
export function useLoginInitialization() {
|
|
useEffect(() => {
|
|
reinitializeAfterLogin()
|
|
}, [])
|
|
}
|
|
|
|
// Initialize immediately when imported on client
|
|
if (typeof window !== 'undefined') {
|
|
initializeApplication()
|
|
}
|
|
|
|
// Export all stores
|
|
export {
|
|
useWorkflowStore,
|
|
useWorkflowRegistry,
|
|
useNotificationStore,
|
|
useEnvironmentStore,
|
|
useExecutionStore,
|
|
useConsoleStore,
|
|
useChatStore,
|
|
useCustomToolsStore,
|
|
}
|
|
|
|
// Helper function to reset all stores
|
|
export const resetAllStores = () => {
|
|
// Reset all stores to initial state
|
|
useWorkflowRegistry.setState({
|
|
workflows: {},
|
|
activeWorkflowId: null,
|
|
isLoading: false,
|
|
error: null,
|
|
})
|
|
useWorkflowStore.getState().clear()
|
|
useSubBlockStore.getState().clear()
|
|
useNotificationStore.setState({ notifications: [] })
|
|
useEnvironmentStore.setState({ variables: {}, isLoading: false, error: null })
|
|
useExecutionStore.getState().reset()
|
|
useConsoleStore.setState({ entries: [], isOpen: false })
|
|
useChatStore.setState({ messages: [], isProcessing: false, error: null })
|
|
useCustomToolsStore.setState({ tools: {} })
|
|
}
|
|
|
|
// Helper function to log all store states
|
|
export const logAllStores = () => {
|
|
const state = {
|
|
workflow: useWorkflowStore.getState(),
|
|
workflowRegistry: useWorkflowRegistry.getState(),
|
|
notifications: useNotificationStore.getState(),
|
|
environment: useEnvironmentStore.getState(),
|
|
execution: useExecutionStore.getState(),
|
|
console: useConsoleStore.getState(),
|
|
chat: useChatStore.getState(),
|
|
customTools: useCustomToolsStore.getState(),
|
|
subBlock: useSubBlockStore.getState(),
|
|
}
|
|
|
|
console.group('Application State')
|
|
Object.entries(state).forEach(([storeName, storeState]) => {
|
|
console.group(storeName)
|
|
console.log(storeState)
|
|
console.groupEnd()
|
|
})
|
|
console.groupEnd()
|
|
|
|
return state
|
|
}
|
|
|
|
// Re-export sync managers
|
|
export { workflowSync } from './sync-registry'
|
|
|
|
/**
|
|
* Reinitialize the application after login
|
|
* This ensures we load fresh data from the database for the new user
|
|
*/
|
|
export async function reinitializeAfterLogin(): Promise<void> {
|
|
if (typeof window === 'undefined') return
|
|
|
|
try {
|
|
// Reset initialization flags to force a fresh load
|
|
isInitializing = false
|
|
|
|
// Reinitialize the application
|
|
await initializeApplication()
|
|
|
|
console.log('Application reinitialized after login')
|
|
} catch (error) {
|
|
console.error('Error reinitializing application:', error)
|
|
}
|
|
}
|