mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
Broken checkpoint
This commit is contained in:
@@ -16,6 +16,7 @@ import { TraceSpansDisplay } from '@/app/workspace/[workspaceId]/logs/components
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils/format-date'
|
||||
import { formatCost } from '@/providers/utils'
|
||||
import type { WorkflowLog } from '@/stores/logs/filters/types'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
interface LogSidebarProps {
|
||||
log: WorkflowLog | null
|
||||
@@ -529,15 +530,32 @@ export function Sidebar({
|
||||
<h3 className='mb-1 font-medium text-muted-foreground text-xs'>
|
||||
Workflow State
|
||||
</h3>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => setIsFrozenCanvasOpen(true)}
|
||||
className='w-full justify-start gap-2'
|
||||
>
|
||||
<Eye className='h-4 w-4' />
|
||||
View Snapshot
|
||||
</Button>
|
||||
<div className='flex w-full gap-2'>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => setIsFrozenCanvasOpen(true)}
|
||||
className='flex-1 justify-start gap-2'
|
||||
>
|
||||
<Eye className='h-4 w-4' />
|
||||
View Snapshot
|
||||
</Button>
|
||||
<Button
|
||||
variant='secondary'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
try {
|
||||
const router = useRouter()
|
||||
const href = `/workspace/${encodeURIComponent(String(log.workflowId || ''))}/w/${encodeURIComponent(String(log.workflowId || ''))}`
|
||||
router.push(href)
|
||||
} catch {}
|
||||
}}
|
||||
className='flex-1 justify-start gap-2'
|
||||
>
|
||||
<Eye className='h-4 w-4' />
|
||||
Open Live Debug
|
||||
</Button>
|
||||
</div>
|
||||
<p className='mt-1 text-muted-foreground text-xs'>
|
||||
See the exact workflow state and block inputs/outputs at execution time
|
||||
</p>
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
useKeyboardShortcuts,
|
||||
} from '@/app/workspace/[workspaceId]/w/hooks/use-keyboard-shortcuts'
|
||||
import { useExecutionStore } from '@/stores/execution/store'
|
||||
import { useDebugCanvasStore } from '@/stores/execution/debug-canvas/store'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
import { usePanelStore } from '@/stores/panel/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
@@ -817,6 +818,7 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
|
||||
|
||||
if (isDebugging) {
|
||||
// Stop debugging
|
||||
try { useDebugCanvasStore.getState().clear() } catch {}
|
||||
handleCancelDebug()
|
||||
} else {
|
||||
// Check if there are executable blocks before starting debug mode
|
||||
@@ -851,6 +853,8 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
|
||||
if (starterId) {
|
||||
execStore.setActiveBlocks(new Set([starterId]))
|
||||
}
|
||||
// Ensure debug canvas starts in a clean state
|
||||
try { useDebugCanvasStore.getState().clear() } catch {}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
||||
@@ -38,6 +38,8 @@ import { getTool } from '@/tools/utils'
|
||||
import { getTrigger, getTriggersByProvider } from '@/triggers'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { useDebugCanvasStore } from '@/stores/execution/debug-canvas/store'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
// Token render cache (LRU-style)
|
||||
const TOKEN_CACHE_MAX = 500
|
||||
@@ -1789,6 +1791,33 @@ export function DebugPanel() {
|
||||
}
|
||||
}, [workspaceId, workflowId])
|
||||
|
||||
// Load selected execution's workflow into debug canvas
|
||||
useEffect(() => {
|
||||
if (!selectedExecutionKey) return
|
||||
const selected = executions.find((e) => e.id === selectedExecutionKey)
|
||||
const execId = selected?.executionId
|
||||
if (!execId) return
|
||||
|
||||
let canceled = false
|
||||
const load = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/logs/${encodeURIComponent(execId)}/frozen-canvas`)
|
||||
const json = await response.json()
|
||||
if (canceled) return
|
||||
const state = json?.data?.workflowState as WorkflowState | undefined
|
||||
if (state) {
|
||||
useDebugCanvasStore.getState().activate(state)
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently ignore load errors
|
||||
}
|
||||
}
|
||||
load()
|
||||
return () => {
|
||||
canceled = true
|
||||
}
|
||||
}, [selectedExecutionKey, executions])
|
||||
|
||||
if (!isDebugging) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center px-6'>
|
||||
|
||||
@@ -4,36 +4,38 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
|
||||
import type { DeploymentStatus } from '@/stores/workflows/registry/types'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { BlockState, Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
import { useDebugCanvasStore } from '@/stores/execution/debug-canvas/store'
|
||||
|
||||
/**
|
||||
* Interface for the current workflow abstraction
|
||||
*/
|
||||
export interface CurrentWorkflow {
|
||||
// Current workflow state properties
|
||||
blocks: Record<string, BlockState>
|
||||
edges: Edge[]
|
||||
loops: Record<string, Loop>
|
||||
parallels: Record<string, Parallel>
|
||||
lastSaved?: number
|
||||
isDeployed?: boolean
|
||||
deployedAt?: Date
|
||||
deploymentStatuses?: Record<string, DeploymentStatus>
|
||||
needsRedeployment?: boolean
|
||||
hasActiveWebhook?: boolean
|
||||
// Current workflow state properties
|
||||
blocks: Record<string, BlockState>
|
||||
edges: Edge[]
|
||||
loops: Record<string, Loop>
|
||||
parallels: Record<string, Parallel>
|
||||
lastSaved?: number
|
||||
isDeployed?: boolean
|
||||
deployedAt?: Date
|
||||
deploymentStatuses?: Record<string, DeploymentStatus>
|
||||
needsRedeployment?: boolean
|
||||
hasActiveWebhook?: boolean
|
||||
|
||||
// Mode information
|
||||
isDiffMode: boolean
|
||||
isNormalMode: boolean
|
||||
// Mode information
|
||||
isDiffMode: boolean
|
||||
isNormalMode: boolean
|
||||
isDebugCanvasMode?: boolean
|
||||
|
||||
// Full workflow state (for cases that need the complete object)
|
||||
workflowState: WorkflowState
|
||||
// Full workflow state (for cases that need the complete object)
|
||||
workflowState: WorkflowState
|
||||
|
||||
// Helper methods
|
||||
getBlockById: (blockId: string) => BlockState | undefined
|
||||
getBlockCount: () => number
|
||||
getEdgeCount: () => number
|
||||
hasBlocks: () => boolean
|
||||
hasEdges: () => boolean
|
||||
// Helper methods
|
||||
getBlockById: (blockId: string) => BlockState | undefined
|
||||
getBlockCount: () => number
|
||||
getEdgeCount: () => number
|
||||
hasBlocks: () => boolean
|
||||
hasEdges: () => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,48 +43,78 @@ export interface CurrentWorkflow {
|
||||
* Automatically handles diff vs normal mode without exposing the complexity to consumers.
|
||||
*/
|
||||
export function useCurrentWorkflow(): CurrentWorkflow {
|
||||
// Get normal workflow state
|
||||
const normalWorkflow = useWorkflowStore((state) => state.getWorkflowState())
|
||||
// Get normal workflow state
|
||||
const normalWorkflow = useWorkflowStore((state) => state.getWorkflowState())
|
||||
|
||||
// Get diff state - now including isDiffReady
|
||||
const { isShowingDiff, isDiffReady, diffWorkflow } = useWorkflowDiffStore()
|
||||
// Get diff state - now including isDiffReady
|
||||
const { isShowingDiff, isDiffReady, diffWorkflow } = useWorkflowDiffStore()
|
||||
|
||||
// Create the abstracted interface
|
||||
const currentWorkflow = useMemo((): CurrentWorkflow => {
|
||||
// Determine which workflow to use - only use diff if it's ready
|
||||
const hasDiffBlocks =
|
||||
!!diffWorkflow && Object.keys((diffWorkflow as any).blocks || {}).length > 0
|
||||
const shouldUseDiff = isShowingDiff && isDiffReady && hasDiffBlocks
|
||||
const activeWorkflow = shouldUseDiff ? diffWorkflow : normalWorkflow
|
||||
// Get debug canvas override
|
||||
const debugCanvas = useDebugCanvasStore((s) => ({ isActive: s.isActive, workflowState: s.workflowState }))
|
||||
|
||||
return {
|
||||
// Current workflow state
|
||||
blocks: activeWorkflow.blocks,
|
||||
edges: activeWorkflow.edges,
|
||||
loops: activeWorkflow.loops || {},
|
||||
parallels: activeWorkflow.parallels || {},
|
||||
lastSaved: activeWorkflow.lastSaved,
|
||||
isDeployed: activeWorkflow.isDeployed,
|
||||
deployedAt: activeWorkflow.deployedAt,
|
||||
deploymentStatuses: activeWorkflow.deploymentStatuses,
|
||||
needsRedeployment: activeWorkflow.needsRedeployment,
|
||||
hasActiveWebhook: activeWorkflow.hasActiveWebhook,
|
||||
// Create the abstracted interface
|
||||
const currentWorkflow = useMemo((): CurrentWorkflow => {
|
||||
// Prefer debug canvas if active
|
||||
const hasDebugCanvas = !!debugCanvas.isActive && !!debugCanvas.workflowState
|
||||
if (hasDebugCanvas) {
|
||||
const activeWorkflow = debugCanvas.workflowState as WorkflowState
|
||||
return {
|
||||
blocks: activeWorkflow.blocks,
|
||||
edges: activeWorkflow.edges,
|
||||
loops: activeWorkflow.loops || {},
|
||||
parallels: activeWorkflow.parallels || {},
|
||||
lastSaved: activeWorkflow.lastSaved,
|
||||
isDeployed: activeWorkflow.isDeployed,
|
||||
deployedAt: activeWorkflow.deployedAt,
|
||||
deploymentStatuses: activeWorkflow.deploymentStatuses,
|
||||
needsRedeployment: activeWorkflow.needsRedeployment,
|
||||
hasActiveWebhook: activeWorkflow.hasActiveWebhook,
|
||||
isDiffMode: false,
|
||||
isNormalMode: false,
|
||||
isDebugCanvasMode: true,
|
||||
workflowState: activeWorkflow,
|
||||
getBlockById: (blockId: string) => activeWorkflow.blocks[blockId],
|
||||
getBlockCount: () => Object.keys(activeWorkflow.blocks).length,
|
||||
getEdgeCount: () => activeWorkflow.edges.length,
|
||||
hasBlocks: () => Object.keys(activeWorkflow.blocks).length > 0,
|
||||
hasEdges: () => activeWorkflow.edges.length > 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Mode information - update to reflect ready state
|
||||
isDiffMode: shouldUseDiff,
|
||||
isNormalMode: !shouldUseDiff,
|
||||
// Determine which workflow to use - only use diff if it's ready
|
||||
const hasDiffBlocks = !!diffWorkflow && Object.keys((diffWorkflow as any).blocks || {}).length > 0
|
||||
const shouldUseDiff = isShowingDiff && isDiffReady && hasDiffBlocks
|
||||
const activeWorkflow = shouldUseDiff ? diffWorkflow : normalWorkflow
|
||||
|
||||
// Full workflow state (for cases that need the complete object)
|
||||
workflowState: activeWorkflow,
|
||||
return {
|
||||
// Current workflow state
|
||||
blocks: activeWorkflow.blocks,
|
||||
edges: activeWorkflow.edges,
|
||||
loops: activeWorkflow.loops || {},
|
||||
parallels: activeWorkflow.parallels || {},
|
||||
lastSaved: activeWorkflow.lastSaved,
|
||||
isDeployed: activeWorkflow.isDeployed,
|
||||
deployedAt: activeWorkflow.deployedAt,
|
||||
deploymentStatuses: activeWorkflow.deploymentStatuses,
|
||||
needsRedeployment: activeWorkflow.needsRedeployment,
|
||||
hasActiveWebhook: activeWorkflow.hasActiveWebhook,
|
||||
|
||||
// Helper methods
|
||||
getBlockById: (blockId: string) => activeWorkflow.blocks[blockId],
|
||||
getBlockCount: () => Object.keys(activeWorkflow.blocks).length,
|
||||
getEdgeCount: () => activeWorkflow.edges.length,
|
||||
hasBlocks: () => Object.keys(activeWorkflow.blocks).length > 0,
|
||||
hasEdges: () => activeWorkflow.edges.length > 0,
|
||||
}
|
||||
}, [normalWorkflow, isShowingDiff, isDiffReady, diffWorkflow])
|
||||
// Mode information - update to reflect ready state
|
||||
isDiffMode: shouldUseDiff,
|
||||
isNormalMode: !shouldUseDiff,
|
||||
isDebugCanvasMode: false,
|
||||
|
||||
return currentWorkflow
|
||||
// Full workflow state (for cases that need the complete object)
|
||||
workflowState: activeWorkflow,
|
||||
|
||||
// Helper methods
|
||||
getBlockById: (blockId: string) => activeWorkflow.blocks[blockId],
|
||||
getBlockCount: () => Object.keys(activeWorkflow.blocks).length,
|
||||
getEdgeCount: () => activeWorkflow.edges.length,
|
||||
hasBlocks: () => Object.keys(activeWorkflow.blocks).length > 0,
|
||||
hasEdges: () => activeWorkflow.edges.length > 0,
|
||||
}
|
||||
}, [normalWorkflow, isShowingDiff, isDiffReady, diffWorkflow, debugCanvas.isActive, debugCanvas.workflowState])
|
||||
|
||||
return currentWorkflow
|
||||
}
|
||||
|
||||
@@ -1635,6 +1635,8 @@ const WorkflowContent = React.memo(() => {
|
||||
)
|
||||
}
|
||||
|
||||
const isReadOnly = currentWorkflow.isDebugCanvasMode === true ? true : !effectivePermissions.canEdit
|
||||
|
||||
return (
|
||||
<div className='flex h-screen w-full flex-col overflow-hidden'>
|
||||
<div className='relative h-full w-full flex-1 transition-all duration-200'>
|
||||
@@ -1650,11 +1652,11 @@ const WorkflowContent = React.memo(() => {
|
||||
edges={edgesWithSelection}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={effectivePermissions.canEdit ? onConnect : undefined}
|
||||
onConnect={isReadOnly ? undefined : onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onDrop={effectivePermissions.canEdit ? onDrop : undefined}
|
||||
onDragOver={effectivePermissions.canEdit ? onDragOver : undefined}
|
||||
onDrop={isReadOnly ? undefined : onDrop}
|
||||
onDragOver={isReadOnly ? undefined : onDragOver}
|
||||
fitView
|
||||
minZoom={0.1}
|
||||
maxZoom={1.3}
|
||||
@@ -1674,22 +1676,22 @@ const WorkflowContent = React.memo(() => {
|
||||
onEdgeClick={onEdgeClick}
|
||||
elementsSelectable={true}
|
||||
selectNodesOnDrag={false}
|
||||
nodesConnectable={effectivePermissions.canEdit}
|
||||
nodesDraggable={effectivePermissions.canEdit}
|
||||
nodesConnectable={!isReadOnly}
|
||||
nodesDraggable={!isReadOnly}
|
||||
draggable={false}
|
||||
noWheelClassName='allow-scroll'
|
||||
edgesFocusable={true}
|
||||
edgesUpdatable={effectivePermissions.canEdit}
|
||||
edgesUpdatable={!isReadOnly}
|
||||
className='workflow-container h-full'
|
||||
onNodeDrag={effectivePermissions.canEdit ? onNodeDrag : undefined}
|
||||
onNodeDragStop={effectivePermissions.canEdit ? onNodeDragStop : undefined}
|
||||
onNodeDragStart={effectivePermissions.canEdit ? onNodeDragStart : undefined}
|
||||
onNodeDrag={isReadOnly ? undefined : onNodeDrag}
|
||||
onNodeDragStop={isReadOnly ? undefined : onNodeDragStop}
|
||||
onNodeDragStart={isReadOnly ? undefined : onNodeDragStart}
|
||||
snapToGrid={false}
|
||||
snapGrid={[20, 20]}
|
||||
elevateEdgesOnSelect={true}
|
||||
elevateNodesOnSelect={true}
|
||||
autoPanOnConnect={effectivePermissions.canEdit}
|
||||
autoPanOnNodeDrag={effectivePermissions.canEdit}
|
||||
autoPanOnConnect={!isReadOnly}
|
||||
autoPanOnNodeDrag={!isReadOnly}
|
||||
>
|
||||
<Background
|
||||
color='hsl(var(--workflow-dots))'
|
||||
|
||||
24
apps/sim/stores/execution/debug-canvas/store.ts
Normal file
24
apps/sim/stores/execution/debug-canvas/store.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { create } from 'zustand'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
interface DebugCanvasState {
|
||||
isActive: boolean
|
||||
workflowState: WorkflowState | null
|
||||
}
|
||||
|
||||
interface DebugCanvasActions {
|
||||
activate: (workflowState: WorkflowState) => void
|
||||
deactivate: () => void
|
||||
setWorkflowState: (workflowState: WorkflowState | null) => void
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
export const useDebugCanvasStore = create<DebugCanvasState & DebugCanvasActions>()((set) => ({
|
||||
isActive: false,
|
||||
workflowState: null,
|
||||
|
||||
activate: (workflowState) => set({ isActive: true, workflowState }),
|
||||
deactivate: () => set({ isActive: false, workflowState: null }),
|
||||
setWorkflowState: (workflowState) => set({ workflowState }),
|
||||
clear: () => set({ isActive: false, workflowState: null }),
|
||||
}))
|
||||
Reference in New Issue
Block a user