chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import { create } from 'zustand'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import {
|
||||
type BlockRunStatus,
|
||||
defaultWorkflowExecutionState,
|
||||
deriveExecutionFlags,
|
||||
type EdgeRunStatus,
|
||||
type ExecutionActions,
|
||||
type ExecutionState,
|
||||
type ExecutionStatus,
|
||||
initialState,
|
||||
type WorkflowExecutionState,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* Returns the execution state for a workflow, creating a fresh default if absent.
|
||||
*
|
||||
* @remarks
|
||||
* When the workflow has no entry in the map, fresh `Set` and `Map` instances
|
||||
* are created so that callers never share mutable collections with
|
||||
* {@link defaultWorkflowExecutionState}.
|
||||
*/
|
||||
function getOrCreate(
|
||||
map: Map<string, WorkflowExecutionState>,
|
||||
workflowId: string
|
||||
): WorkflowExecutionState {
|
||||
return (
|
||||
map.get(workflowId) ?? {
|
||||
...defaultWorkflowExecutionState,
|
||||
activeBlockIds: new Set<string>(),
|
||||
lastRunPath: new Map<string, BlockRunStatus>(),
|
||||
lastRunEdges: new Map<string, EdgeRunStatus>(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutably updates a single workflow's execution state within the map.
|
||||
*
|
||||
* Creates a shallow copy of the outer map, merges the patch into the
|
||||
* target workflow's entry, and returns the new map. This ensures Zustand
|
||||
* detects the top-level reference change and notifies subscribers.
|
||||
*/
|
||||
function updatedMap(
|
||||
map: Map<string, WorkflowExecutionState>,
|
||||
workflowId: string,
|
||||
patch: Partial<WorkflowExecutionState>
|
||||
): Map<string, WorkflowExecutionState> {
|
||||
const next = new Map(map)
|
||||
const current = getOrCreate(map, workflowId)
|
||||
next.set(workflowId, { ...current, ...patch })
|
||||
return next
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Zustand store for per-workflow execution state.
|
||||
*
|
||||
* All execution state (running, debugging, block/edge highlights) is keyed
|
||||
* by workflow ID so users can run multiple workflows concurrently, each
|
||||
* with independent visual feedback.
|
||||
*/
|
||||
export const useExecutionStore = create<ExecutionState & ExecutionActions>()((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
getWorkflowExecution: (workflowId) => {
|
||||
return getOrCreate(get().workflowExecutions, workflowId)
|
||||
},
|
||||
|
||||
setActiveBlocks: (workflowId, blockIds) => {
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, {
|
||||
activeBlockIds: new Set(blockIds),
|
||||
}),
|
||||
})
|
||||
},
|
||||
|
||||
setPendingBlocks: (workflowId, pendingBlocks) => {
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, { pendingBlocks }),
|
||||
})
|
||||
},
|
||||
|
||||
setStatus: (workflowId, status, options) => {
|
||||
const patch: Partial<WorkflowExecutionState> = {
|
||||
status,
|
||||
...deriveExecutionFlags(status),
|
||||
}
|
||||
if (options?.clearRunPath) {
|
||||
patch.lastRunPath = new Map()
|
||||
patch.lastRunEdges = new Map()
|
||||
}
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, patch),
|
||||
})
|
||||
},
|
||||
|
||||
setIsExecuting: (workflowId, isExecuting) => {
|
||||
const current = getOrCreate(get().workflowExecutions, workflowId)
|
||||
const nextStatus: ExecutionStatus = isExecuting
|
||||
? current.status === 'debugging'
|
||||
? 'debugging'
|
||||
: 'running'
|
||||
: 'idle'
|
||||
get().setStatus(workflowId, nextStatus, { clearRunPath: isExecuting })
|
||||
},
|
||||
|
||||
setIsDebugging: (workflowId, isDebugging) => {
|
||||
const current = getOrCreate(get().workflowExecutions, workflowId)
|
||||
const nextStatus: ExecutionStatus = isDebugging
|
||||
? 'debugging'
|
||||
: current.status === 'debugging'
|
||||
? 'running'
|
||||
: current.status
|
||||
get().setStatus(workflowId, nextStatus)
|
||||
},
|
||||
|
||||
setExecutor: (workflowId, executor) => {
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, { executor }),
|
||||
})
|
||||
},
|
||||
|
||||
setDebugContext: (workflowId, debugContext) => {
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, { debugContext }),
|
||||
})
|
||||
},
|
||||
|
||||
setBlockRunStatus: (workflowId, blockId, status) => {
|
||||
const current = getOrCreate(get().workflowExecutions, workflowId)
|
||||
const newRunPath = new Map(current.lastRunPath)
|
||||
newRunPath.set(blockId, status)
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, {
|
||||
lastRunPath: newRunPath,
|
||||
}),
|
||||
})
|
||||
},
|
||||
|
||||
setEdgeRunStatus: (workflowId, edgeId, status) => {
|
||||
const current = getOrCreate(get().workflowExecutions, workflowId)
|
||||
const newRunEdges = new Map(current.lastRunEdges)
|
||||
newRunEdges.set(edgeId, status)
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, {
|
||||
lastRunEdges: newRunEdges,
|
||||
}),
|
||||
})
|
||||
},
|
||||
|
||||
setCurrentExecutionId: (workflowId, executionId) => {
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, {
|
||||
currentExecutionId: executionId,
|
||||
}),
|
||||
})
|
||||
},
|
||||
|
||||
getCurrentExecutionId: (workflowId) => {
|
||||
return getOrCreate(get().workflowExecutions, workflowId).currentExecutionId
|
||||
},
|
||||
|
||||
clearRunPath: (workflowId) => {
|
||||
set({
|
||||
workflowExecutions: updatedMap(get().workflowExecutions, workflowId, {
|
||||
lastRunPath: new Map(),
|
||||
lastRunEdges: new Map(),
|
||||
}),
|
||||
})
|
||||
},
|
||||
|
||||
reset: () => set(initialState),
|
||||
|
||||
setLastExecutionSnapshot: (workflowId, snapshot) => {
|
||||
const newSnapshots = new Map(get().lastExecutionSnapshots)
|
||||
newSnapshots.set(workflowId, snapshot)
|
||||
set({ lastExecutionSnapshots: newSnapshots })
|
||||
},
|
||||
|
||||
getLastExecutionSnapshot: (workflowId) => {
|
||||
return get().lastExecutionSnapshots.get(workflowId)
|
||||
},
|
||||
|
||||
clearLastExecutionSnapshot: (workflowId) => {
|
||||
const newSnapshots = new Map(get().lastExecutionSnapshots)
|
||||
newSnapshots.delete(workflowId)
|
||||
set({ lastExecutionSnapshots: newSnapshots })
|
||||
},
|
||||
}))
|
||||
|
||||
/**
|
||||
* Convenience hook that returns the execution state for the currently active workflow.
|
||||
*/
|
||||
export function useCurrentWorkflowExecution(): WorkflowExecutionState {
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
return useExecutionStore((state) => {
|
||||
if (!activeWorkflowId) return defaultWorkflowExecutionState
|
||||
return state.workflowExecutions.get(activeWorkflowId) ?? defaultWorkflowExecutionState
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the active workflow is currently executing.
|
||||
* More granular than useCurrentWorkflowExecution — only re-renders when
|
||||
* the isExecuting boolean changes, not on every iteration update during
|
||||
* parallel-loop runs.
|
||||
*/
|
||||
export function useIsCurrentWorkflowExecuting(): boolean {
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
return useExecutionStore((state) => {
|
||||
if (!activeWorkflowId) return false
|
||||
return state.workflowExecutions.get(activeWorkflowId)?.isExecuting ?? false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a specific block is currently active (executing) in the current workflow.
|
||||
* More granular than useCurrentWorkflowExecution — only re-renders when
|
||||
* the boolean result changes for this specific block.
|
||||
*/
|
||||
export function useIsBlockActive(blockId: string): boolean {
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
return useExecutionStore((state) => {
|
||||
if (!activeWorkflowId) return false
|
||||
return state.workflowExecutions.get(activeWorkflowId)?.activeBlockIds.has(blockId) ?? false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last run path (block statuses) for the current workflow.
|
||||
* More granular than useCurrentWorkflowExecution — only re-renders when
|
||||
* the lastRunPath map reference changes.
|
||||
*/
|
||||
export function useLastRunPath(): Map<string, BlockRunStatus> {
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
return useExecutionStore((state) => {
|
||||
if (!activeWorkflowId) return defaultWorkflowExecutionState.lastRunPath
|
||||
return (
|
||||
state.workflowExecutions.get(activeWorkflowId)?.lastRunPath ??
|
||||
defaultWorkflowExecutionState.lastRunPath
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last run edges (edge statuses) for the current workflow.
|
||||
* More granular than useCurrentWorkflowExecution — only re-renders when
|
||||
* the lastRunEdges map reference changes.
|
||||
*/
|
||||
export function useLastRunEdges(): Map<string, EdgeRunStatus> {
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
return useExecutionStore((state) => {
|
||||
if (!activeWorkflowId) return defaultWorkflowExecutionState.lastRunEdges
|
||||
return (
|
||||
state.workflowExecutions.get(activeWorkflowId)?.lastRunEdges ??
|
||||
defaultWorkflowExecutionState.lastRunEdges
|
||||
)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user