diff --git a/app/api/workflow/[id]/deploy/route.ts b/app/api/workflow/[id]/deploy/route.ts index 31790713a6..38bfed672c 100644 --- a/app/api/workflow/[id]/deploy/route.ts +++ b/app/api/workflow/[id]/deploy/route.ts @@ -21,6 +21,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ // Generate a new API key const apiKey = `wf_${uuidv4().replace(/-/g, '')}` + const deployedAt = new Date() // Update the workflow with the API key and deployment status await db @@ -28,11 +29,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ .set({ apiKey, isDeployed: true, - deployedAt: new Date(), + deployedAt, }) .where(eq(workflow.id, id)) - return createSuccessResponse({ apiKey }) + return createSuccessResponse({ apiKey, isDeployed: true, deployedAt }) } catch (error: any) { console.error('Error deploying workflow:', error) return createErrorResponse(error.message || 'Failed to deploy workflow', 500) diff --git a/app/w/components/control-bar/control-bar.tsx b/app/w/components/control-bar/control-bar.tsx index 21a1090d9d..6f87c1e2d1 100644 --- a/app/w/components/control-bar/control-bar.tsx +++ b/app/w/components/control-bar/control-bar.tsx @@ -32,49 +32,44 @@ import { useWorkflowExecution } from '../../hooks/use-workflow-execution' import { HistoryDropdownItem } from './components/history-dropdown-item' import { NotificationDropdownItem } from './components/notification-dropdown-item' +/** + * Control bar for managing workflows - handles editing, deletion, deployment, + * history, notifications and execution. + */ export function ControlBar() { - const { notifications, getWorkflowNotifications, addNotification } = useNotificationStore() - const { history, undo, redo, revertToHistoryState, lastSaved } = useWorkflowStore() - const [isEditing, setIsEditing] = useState(false) - const [editedName, setEditedName] = useState('') - const [historyOpen, setHistoryOpen] = useState(false) - const [notificationsOpen, setNotificationsOpen] = useState(false) - const { workflows, updateWorkflow, activeWorkflowId, removeWorkflow } = useWorkflowRegistry() - const [, forceUpdate] = useState({}) - const { isExecuting, handleRunWorkflow } = useWorkflowExecution() const router = useRouter() - // Use client-side only rendering for the timestamp + // Store hooks + const { notifications, getWorkflowNotifications, addNotification } = useNotificationStore() + const { history, revertToHistoryState, lastSaved, isDeployed, setDeploymentStatus } = + useWorkflowStore() + const { workflows, updateWorkflow, activeWorkflowId, removeWorkflow } = useWorkflowRegistry() + const { isExecuting, handleRunWorkflow } = useWorkflowExecution() + + // Local state const [mounted, setMounted] = useState(false) - useEffect(() => { - setMounted(true) - }, []) + const [, forceUpdate] = useState({}) + + // Workflow name editing state + const [isEditing, setIsEditing] = useState(false) + const [editedName, setEditedName] = useState('') + + // Dropdown states + const [historyOpen, setHistoryOpen] = useState(false) + const [notificationsOpen, setNotificationsOpen] = useState(false) + + // Deployment states + const [isDeploying, setIsDeploying] = useState(false) // Get notifications for current workflow const workflowNotifications = activeWorkflowId ? getWorkflowNotifications(activeWorkflowId) : notifications // Show all if no workflow is active - const handleDeleteWorkflow = () => { - if (!activeWorkflowId) return - - // Remove the workflow from the registry - const newWorkflows = { ...workflows } - delete newWorkflows[activeWorkflowId] - - // Get remaining workflow IDs - const remainingIds = Object.keys(newWorkflows) - - // Navigate before removing the workflow to avoid any state inconsistencies - if (remainingIds.length > 0) { - router.push(`/w/${remainingIds[0]}`) - } else { - router.push('/') - } - - // Remove the workflow from the registry - removeWorkflow(activeWorkflowId) - } + // Client-side only rendering for the timestamp + useEffect(() => { + setMounted(true) + }, []) // Update the time display every minute useEffect(() => { @@ -82,6 +77,30 @@ export function ControlBar() { return () => clearInterval(interval) }, []) + // Check deployment status on mount or when activeWorkflowId changes + useEffect(() => { + async function checkStatus() { + if (!activeWorkflowId) return + try { + const response = await fetch(`/api/workflow/${activeWorkflowId}/status`) + if (response.ok) { + const data = await response.json() + // Update the store with the deployment status from the API + setDeploymentStatus( + data.isDeployed, + data.deployedAt ? new Date(data.deployedAt) : undefined + ) + } + } catch (error) { + console.error('Failed to check deployment status:', error) + } + } + checkStatus() + }, [activeWorkflowId, setDeploymentStatus]) + + /** + * Workflow name handlers + */ const handleNameClick = () => { if (activeWorkflowId) { setEditedName(workflows[activeWorkflowId].name) @@ -107,28 +126,29 @@ export function ControlBar() { } } - // Add the deployment state and handlers - const [isDeploying, setIsDeploying] = useState(false) - const [isDeployed, setIsDeployed] = useState(false) + /** + * Workflow deletion handler + */ + const handleDeleteWorkflow = () => { + if (!activeWorkflowId) return - // Check deployment status on mount - useEffect(() => { - async function checkStatus() { - if (!activeWorkflowId) return - try { - const response = await fetch(`/api/workflow/${activeWorkflowId}/status`) - if (response.ok) { - const data = await response.json() - setIsDeployed(data.isDeployed) - } - } catch (error) { - console.error('Failed to check deployment status:', error) - } + // Get remaining workflow IDs + const remainingIds = Object.keys(workflows).filter((id) => id !== activeWorkflowId) + + // Navigate before removing the workflow to avoid any state inconsistencies + if (remainingIds.length > 0) { + router.push(`/w/${remainingIds[0]}`) + } else { + router.push('/') } - checkStatus() - }, [activeWorkflowId]) - // Deploy the workflow + // Remove the workflow from the registry + removeWorkflow(activeWorkflowId) + } + + /** + * Workflow deployment handler + */ const handleDeploy = async () => { if (!activeWorkflowId) return try { @@ -142,9 +162,12 @@ export function ControlBar() { if (!response.ok) throw new Error('Failed to deploy workflow') - const { apiKey } = await response.json() + const { apiKey, isDeployed: newDeployStatus, deployedAt } = await response.json() const endpoint = `${process.env.NEXT_PUBLIC_APP_URL}/api/workflow/${activeWorkflowId}/execute` + // Update the store with the deployment status + setDeploymentStatus(newDeployStatus, deployedAt ? new Date(deployedAt) : undefined) + addNotification('api', 'Workflow successfully deployed', activeWorkflowId, { isPersistent: true, sections: [ @@ -162,8 +185,6 @@ export function ControlBar() { }, ], }) - - setIsDeployed(true) } catch (error) { addNotification('error', 'Failed to deploy workflow. Please try again.', activeWorkflowId) } finally { @@ -171,201 +192,236 @@ export function ControlBar() { } } + /** + * Render workflow name section (editable/non-editable) + */ + const renderWorkflowName = () => ( +
+ {isEditing ? ( + setEditedName(e.target.value)} + onBlur={handleNameSubmit} + onKeyDown={handleNameKeyDown} + autoFocus + className="font-semibold text-sm bg-transparent border-none outline-none p-0 w-[200px]" + /> + ) : ( +

+ {activeWorkflowId ? workflows[activeWorkflowId]?.name : 'Workflow'} +

+ )} + {mounted && ( +

+ Saved{' '} + {formatDistanceToNow(lastSaved || Date.now(), { + addSuffix: true, + })} +

+ )} +
+ ) + + /** + * Render delete workflow button with confirmation dialog + */ + const renderDeleteButton = () => ( + + + + + + + + Delete Workflow + + + + + Delete Workflow + + Are you sure you want to delete this workflow? This action cannot be undone. + + + + Cancel + + Delete + + + + + ) + + /** + * Render deploy button with tooltip + */ + const renderDeployButton = () => ( + + + + + + {isDeploying ? 'Deploying...' : isDeployed ? 'Deployed' : 'Deploy as API Endpoint'} + + + ) + + /** + * Render history dropdown + */ + const renderHistoryDropdown = () => ( + + + + + + + + {!historyOpen && History} + + + {history.past.length === 0 && history.future.length === 0 ? ( + + + No history available + + + ) : ( + + <> + {[...history.future].reverse().map((entry, index) => ( + + revertToHistoryState( + history.past.length + 1 + (history.future.length - 1 - index) + ) + } + isFuture={true} + /> + ))} + {}} + /> + {[...history.past].reverse().map((entry, index) => ( + revertToHistoryState(history.past.length - 1 - index)} + /> + ))} + + + )} + + ) + + /** + * Render notifications dropdown + */ + const renderNotificationsDropdown = () => ( + + + + + + + + {!notificationsOpen && Notifications} + + + {workflowNotifications.length === 0 ? ( + + + No new notifications + + + ) : ( + + {[...workflowNotifications] + .sort((a, b) => b.timestamp - a.timestamp) + .map((notification) => ( + + ))} + + )} + + ) + + /** + * Render run workflow button + */ + const renderRunButton = () => ( + + ) + return (
{/* Left Section - Workflow Info */} -
- {isEditing ? ( - setEditedName(e.target.value)} - onBlur={handleNameSubmit} - onKeyDown={handleNameKeyDown} - autoFocus - className="font-semibold text-sm bg-transparent border-none outline-none p-0 w-[200px]" - /> - ) : ( -

- {activeWorkflowId ? workflows[activeWorkflowId].name : 'Workflow'} -

- )} - {mounted && ( -

- Saved{' '} - {formatDistanceToNow(lastSaved || Date.now(), { - addSuffix: true, - })} -

- )} -
+ {renderWorkflowName()} {/* Middle Section - Reserved for future use */}
{/* Right Section - Actions */}
- - - - - - - - Delete Workflow - - - - - Delete Workflow - - Are you sure you want to delete this workflow? This action cannot be undone. - - - - Cancel - - Delete - - - - - - - - - - - {isDeploying ? 'Deploying...' : isDeployed ? 'Deployed' : 'Deploy as API Endpoint'} - - - - - - - - - - - {!historyOpen && History} - - - {history.past.length === 0 && history.future.length === 0 ? ( - - - No history available - - - ) : ( - - <> - {[...history.future].reverse().map((entry, index) => ( - - revertToHistoryState( - history.past.length + 1 + (history.future.length - 1 - index) - ) - } - isFuture={true} - /> - ))} - {}} - /> - {[...history.past].reverse().map((entry, index) => ( - revertToHistoryState(history.past.length - 1 - index)} - /> - ))} - - - )} - - - - - - - - - - {!notificationsOpen && Notifications} - - - {workflowNotifications.length === 0 ? ( - - - No new notifications - - - ) : ( - - {[...workflowNotifications] - .sort((a, b) => b.timestamp - a.timestamp) - .map((notification) => ( - - ))} - - )} - - - + {renderDeleteButton()} + {renderDeployButton()} + {renderHistoryDropdown()} + {renderNotificationsDropdown()} + {renderRunButton()}
) diff --git a/stores/workflow/history-types.ts b/stores/workflow/history-types.ts index 290e96ed8a..60c6d405f6 100644 --- a/stores/workflow/history-types.ts +++ b/stores/workflow/history-types.ts @@ -4,6 +4,7 @@ export interface HistoryEntry { state: WorkflowState timestamp: number action: string + subblockValues: Record> } export interface WorkflowHistory { diff --git a/stores/workflow/middleware.ts b/stores/workflow/middleware.ts index e0dbe1cf9c..4669f4f1c3 100644 --- a/stores/workflow/middleware.ts +++ b/stores/workflow/middleware.ts @@ -1,6 +1,9 @@ import { StateCreator } from 'zustand' import { HistoryActions, HistoryEntry, WorkflowHistory } from './history-types' +import { useWorkflowRegistry } from './registry/store' +import { useSubBlockStore } from './subblock/store' import { WorkflowState, WorkflowStore } from './types' +import { mergeSubblockState } from './utils' // MAX for each individual workflow const MAX_HISTORY_LENGTH = 20 @@ -22,9 +25,12 @@ export const withHistory = ( blocks: initialState.blocks, edges: initialState.edges, loops: initialState.loops, + isDeployed: initialState.isDeployed || false, + deployedAt: initialState.deployedAt, }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, // Add storage for subblock values } return { @@ -49,6 +55,11 @@ export const withHistory = ( const previous = history.past[history.past.length - 1] const newPast = history.past.slice(0, history.past.length - 1) + // Get active workflow ID for subblock handling + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) return + + // Apply the state change set({ ...state, ...previous.state, @@ -58,6 +69,23 @@ export const withHistory = ( future: [history.present, ...history.future], }, }) + + // Restore subblock values from the previous state's snapshot + if (previous.subblockValues && activeWorkflowId) { + // Update the subblock store with the saved values + useSubBlockStore.setState({ + workflowValues: { + ...useSubBlockStore.getState().workflowValues, + [activeWorkflowId]: previous.subblockValues, + }, + }) + + // Also update localStorage for backup + localStorage.setItem( + `subblock-values-${activeWorkflowId}`, + JSON.stringify(previous.subblockValues) + ) + } }, // Restore next state from history @@ -68,6 +96,11 @@ export const withHistory = ( const next = history.future[0] const newFuture = history.future.slice(1) + // Get active workflow ID for subblock handling + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) return + + // Apply the state change set({ ...state, ...next.state, @@ -77,6 +110,23 @@ export const withHistory = ( future: newFuture, }, }) + + // Restore subblock values from the next state's snapshot + if (next.subblockValues && activeWorkflowId) { + // Update the subblock store with the saved values + useSubBlockStore.setState({ + workflowValues: { + ...useSubBlockStore.getState().workflowValues, + [activeWorkflowId]: next.subblockValues, + }, + }) + + // Also update localStorage for backup + localStorage.setItem( + `subblock-values-${activeWorkflowId}`, + JSON.stringify(next.subblockValues) + ) + } }, // Reset workflow to empty state @@ -88,9 +138,10 @@ export const withHistory = ( history: { past: [], present: { - state: { blocks: {}, edges: [], loops: {} }, + state: { blocks: {}, edges: [], loops: {}, isDeployed: false }, timestamp: Date.now(), action: 'Clear workflow', + subblockValues: {}, }, future: [], }, @@ -107,6 +158,10 @@ export const withHistory = ( if (!targetState) return + // Get active workflow ID for subblock handling + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) return + const newPast = allStates.slice(0, index) const newFuture = allStates.slice(index + 1) @@ -119,21 +174,60 @@ export const withHistory = ( future: newFuture, }, }) + + // Restore subblock values from the target state's snapshot + if (targetState.subblockValues && activeWorkflowId) { + // Update the subblock store with the saved values + useSubBlockStore.setState({ + workflowValues: { + ...useSubBlockStore.getState().workflowValues, + [activeWorkflowId]: targetState.subblockValues, + }, + }) + + // Also update localStorage for backup + localStorage.setItem( + `subblock-values-${activeWorkflowId}`, + JSON.stringify(targetState.subblockValues) + ) + } }, } } } // Create a new history entry with current state snapshot -export const createHistoryEntry = (state: WorkflowState, action: string): HistoryEntry => ({ - state: { +export const createHistoryEntry = (state: WorkflowState, action: string): HistoryEntry => { + // Get active workflow ID for subblock handling + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + + // Create a deep copy of the state + const stateCopy = { blocks: { ...state.blocks }, edges: [...state.edges], loops: { ...state.loops }, - }, - timestamp: Date.now(), - action, -}) + isDeployed: state.isDeployed !== undefined ? state.isDeployed : false, + deployedAt: state.deployedAt, + } + + // Capture the current subblock values for this workflow + let subblockValues = {} + + if (activeWorkflowId) { + // Get the current subblock values from the store + const currentValues = useSubBlockStore.getState().workflowValues[activeWorkflowId] || {} + + // Create a deep copy to ensure we don't have reference issues + subblockValues = JSON.parse(JSON.stringify(currentValues)) + } + + return { + state: stateCopy, + timestamp: Date.now(), + action, + subblockValues, + } +} // Add new entry to history and maintain history size limit export const pushHistory = ( diff --git a/stores/workflow/registry/store.ts b/stores/workflow/registry/store.ts index 1ec30c1b23..d9cb81ec68 100644 --- a/stores/workflow/registry/store.ts +++ b/stores/workflow/registry/store.ts @@ -34,6 +34,8 @@ export const useWorkflowRegistry = create()( edges: currentState.edges, loops: currentState.loops, history: currentState.history, + isDeployed: currentState.isDeployed, + deployedAt: currentState.deployedAt, }) ) } @@ -41,7 +43,8 @@ export const useWorkflowRegistry = create()( // Load workflow state const savedState = localStorage.getItem(`workflow-${id}`) if (savedState) { - const { blocks, edges, history, loops } = JSON.parse(savedState) + const parsedState = JSON.parse(savedState) + const { blocks, edges, history, loops } = parsedState // Initialize subblock store with workflow values useSubBlockStore.getState().initializeFromWorkflow(id, blocks) @@ -50,12 +53,21 @@ export const useWorkflowRegistry = create()( blocks, edges, loops, + isDeployed: parsedState.isDeployed !== undefined ? parsedState.isDeployed : false, + deployedAt: parsedState.deployedAt ? new Date(parsedState.deployedAt) : undefined, history: history || { past: [], present: { - state: { blocks, edges, loops: {} }, + state: { + blocks, + edges, + loops: {}, + isDeployed: parsedState.isDeployed !== undefined ? parsedState.isDeployed : false, + deployedAt: parsedState.deployedAt, + }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -65,12 +77,21 @@ export const useWorkflowRegistry = create()( blocks: {}, edges: [], loops: {}, + isDeployed: false, + deployedAt: undefined, history: { past: [], present: { - state: { blocks: {}, edges: [], loops: {} }, + state: { + blocks: {}, + edges: [], + loops: {}, + isDeployed: false, + deployedAt: undefined, + }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -197,6 +218,8 @@ export const useWorkflowRegistry = create()( }, edges: [], loops: {}, + isDeployed: false, + deployedAt: undefined, history: { past: [], present: { @@ -206,9 +229,12 @@ export const useWorkflowRegistry = create()( }, edges: [], loops: {}, + isDeployed: false, + deployedAt: undefined, }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -263,17 +289,21 @@ export const useWorkflowRegistry = create()( newActiveWorkflowId = remainingIds[0] const savedState = localStorage.getItem(`workflow-${newActiveWorkflowId}`) if (savedState) { - const { blocks, edges, history, loops } = JSON.parse(savedState) + const { blocks, edges, history, loops, isDeployed, deployedAt } = + JSON.parse(savedState) useWorkflowStore.setState({ blocks, edges, loops, + isDeployed: isDeployed || false, + deployedAt: deployedAt ? new Date(deployedAt) : undefined, history: history || { past: [], present: { - state: { blocks, edges, loops }, + state: { blocks, edges, loops, isDeployed: isDeployed || false, deployedAt }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -283,12 +313,21 @@ export const useWorkflowRegistry = create()( blocks: {}, edges: [], loops: {}, + isDeployed: false, + deployedAt: undefined, history: { past: [], present: { - state: { blocks: {}, edges: [], loops: {} }, + state: { + blocks: {}, + edges: [], + loops: {}, + isDeployed: false, + deployedAt: undefined, + }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -354,6 +393,8 @@ const initializeRegistry = () => { edges: currentState.edges, loops: currentState.loops, history: currentState.history, + isDeployed: currentState.isDeployed, + deployedAt: currentState.deployedAt, }) ) } diff --git a/stores/workflow/registry/utils.ts b/stores/workflow/registry/utils.ts index 0896c4c5ae..a9ccf4d699 100644 --- a/stores/workflow/registry/utils.ts +++ b/stores/workflow/registry/utils.ts @@ -3,6 +3,7 @@ import { WorkflowMetadata } from './types' // Available workflow colors export const WORKFLOW_COLORS = ['#3972F6', '#F639DD', '#F6B539', '#8139F6', '#F64439'] +// Generates a unique name for a new workflow export function generateUniqueName(existingWorkflows: Record): string { // Extract numbers from existing workflow names using regex const numbers = Object.values(existingWorkflows) @@ -21,11 +22,7 @@ export function generateUniqueName(existingWorkflows: Record): string { const workflowArray = Object.values(existingWorkflows) diff --git a/stores/workflow/store.ts b/stores/workflow/store.ts index 880dfe0195..75f6dbea15 100644 --- a/stores/workflow/store.ts +++ b/stores/workflow/store.ts @@ -15,12 +15,15 @@ const initialState = { edges: [], loops: {}, lastSaved: undefined, + isDeployed: false, + deployedAt: undefined, history: { past: [], present: { - state: { blocks: {}, edges: [], loops: {} }, + state: { blocks: {}, edges: [], loops: {}, isDeployed: false }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -70,6 +73,8 @@ export const useWorkflowStore = create()( }, edges: [...get().edges], loops: { ...get().loops }, + isDeployed: get().isDeployed, + deployedAt: get().deployedAt, } set(newState) @@ -100,6 +105,8 @@ export const useWorkflowStore = create()( blocks: { ...get().blocks }, edges: [...get().edges].filter((edge) => edge.source !== id && edge.target !== id), loops: { ...get().loops }, + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } // Clean up subblock values before removing the block @@ -192,6 +199,8 @@ export const useWorkflowStore = create()( blocks: { ...get().blocks }, edges: newEdges, loops: newLoops, + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } set(newState) @@ -229,6 +238,8 @@ export const useWorkflowStore = create()( blocks: { ...get().blocks }, edges: newEdges, loops: newLoops, + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } set(newState) @@ -241,12 +252,21 @@ export const useWorkflowStore = create()( blocks: {}, edges: [], loops: {}, + isDeployed: false, + deployedAt: undefined, history: { past: [], present: { - state: { blocks: {}, edges: [], loops: {} }, + state: { + blocks: {}, + edges: [], + loops: {}, + isDeployed: false, + deployedAt: undefined, + }, timestamp: Date.now(), action: 'Initial state', + subblockValues: {}, }, future: [], }, @@ -270,6 +290,8 @@ export const useWorkflowStore = create()( }, }, edges: [...get().edges], + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } set(newState) @@ -319,6 +341,8 @@ export const useWorkflowStore = create()( }, edges: [...get().edges], loops: { ...get().loops }, + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } // Update the subblock store with the duplicated values @@ -352,6 +376,8 @@ export const useWorkflowStore = create()( }, }, edges: [...get().edges], + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } set(newState) @@ -369,6 +395,8 @@ export const useWorkflowStore = create()( }, edges: [...get().edges], loops: { ...get().loops }, + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } set(newState) @@ -387,6 +415,8 @@ export const useWorkflowStore = create()( }, edges: [...state.edges], loops: { ...get().loops }, + isDeployed: state.isDeployed || false, + deployedAt: state.deployedAt, })) get().updateLastSaved() }, @@ -401,6 +431,8 @@ export const useWorkflowStore = create()( }, }, edges: [...state.edges], + isDeployed: state.isDeployed || false, + deployedAt: state.deployedAt, })) get().updateLastSaved() }, @@ -416,6 +448,8 @@ export const useWorkflowStore = create()( maxIterations: Math.max(1, Math.min(50, maxIterations)), // Clamp between 1-50 }, }, + isDeployed: get().isDeployed || false, + deployedAt: get().deployedAt, } set(newState) @@ -429,6 +463,17 @@ export const useWorkflowStore = create()( lastUpdate: Date.now(), })) }, + + setDeploymentStatus: (isDeployed: boolean, deployedAt?: Date) => { + const newState = { + ...get(), + isDeployed, + deployedAt: deployedAt || (isDeployed ? new Date() : undefined), + } + + set(newState) + get().updateLastSaved() + }, })), { name: 'workflow-store' } ) diff --git a/stores/workflow/types.ts b/stores/workflow/types.ts index 14ce32ac63..2c27862733 100644 --- a/stores/workflow/types.ts +++ b/stores/workflow/types.ts @@ -37,6 +37,8 @@ export interface WorkflowState { lastSaved?: number loops: Record lastUpdate?: number + isDeployed: boolean + deployedAt?: Date } export interface WorkflowActions { @@ -55,6 +57,7 @@ export interface WorkflowActions { updateBlockHeight: (id: string, height: number) => void updateLoopMaxIterations: (loopId: string, maxIterations: number) => void triggerUpdate: () => void + setDeploymentStatus: (isDeployed: boolean, deployedAt?: Date) => void } export type WorkflowStore = WorkflowState & WorkflowActions