Checkpoint

This commit is contained in:
Siddharth Ganesan
2025-08-28 17:52:03 -07:00
parent 728f5812ac
commit 692b385ece
2 changed files with 155 additions and 14 deletions

View File

@@ -22,6 +22,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp
import { cn } from '@/lib/utils'
import { useVariablesStore } from '@/stores/panel/variables/store'
import { useEnvironmentStore } from '@/stores/settings/environment/store'
import { useConsoleStore } from '@/stores/panel/console/store'
import {
Play,
FastForward,
@@ -461,6 +462,28 @@ export function DebugPanel() {
}
return false
})() : false
const isFocusedErrored = debugContext ? (() => {
const id = focusedBlockId || ''
if (!id) return false
// Check direct block state for error
const directState = debugContext.blockStates.get(id)
if (directState?.output && typeof directState.output === 'object' && 'error' in directState.output) {
return true
}
// Check virtual executions for errors
for (const [key, state] of debugContext.blockStates.entries()) {
if (isVirtualForBlock(String(key), id) && state?.output && typeof state.output === 'object' && 'error' in state.output) {
return true
}
}
// Also check block logs for this block
const hasErrorLog = debugContext.blockLogs?.some(log =>
(log.blockId === id || resolveOriginalBlockId(log.blockId) === id) && !log.success
)
return hasErrorLog || false
})() : false
const isStarterFocused = focusedBlock?.metadata?.id === 'starter' || focusedBlock?.type === 'starter'
const isFocusedCurrent = useMemo(() => {
const id = focusedBlockId || ''
@@ -1231,12 +1254,14 @@ export function DebugPanel() {
}
const getStatusIcon = () => {
if (isFocusedErrored) return <Circle className='h-2 w-2 fill-red-500 text-red-500' />
if (isFocusedCurrent) return <Circle className='h-2 w-2 fill-emerald-500 text-emerald-500' />
if (isFocusedExecuted) return <Circle className='h-2 w-2 fill-blue-500 text-blue-500' />
return <Circle className='h-2 w-2 fill-muted-foreground/40 text-muted-foreground/40' />
}
const getStatusText = () => {
if (isFocusedErrored) return 'Error'
if (isFocusedCurrent) return 'Current'
if (isFocusedExecuted) return 'Executed'
return 'Pending'
@@ -1341,9 +1366,15 @@ export function DebugPanel() {
</div>
{/* Header Section - Single Line */}
<div className='flex items-center justify-between border-b border-border/50 px-3 py-2.5'>
<div className={cn(
'flex items-center justify-between border-b border-border/50 px-3 py-2.5',
isFocusedErrored && 'bg-red-50 dark:bg-red-900/10 border-red-500'
)}>
<div className='flex items-center gap-2'>
<span className='font-semibold text-sm truncate'>
<span className={cn(
'font-semibold text-sm truncate',
isFocusedErrored && 'text-red-600 dark:text-red-400'
)}>
{focusedBlock ? getDisplayName(focusedBlock) : 'Debug Panel'}
</span>
{focusedBlockId && (
@@ -1486,7 +1517,79 @@ export function DebugPanel() {
</TabsContent>
<TabsContent value='output' className='flex-1 overflow-auto p-3 m-0'>
{resolvedOutputKVs && Object.keys(resolvedOutputKVs).length > 0 ? (
{isFocusedErrored ? (
<div className='rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 p-4'>
<div className='flex items-start gap-3'>
<AlertCircle className='h-5 w-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5' />
<div className='flex-1 space-y-2'>
<p className='font-medium text-sm text-red-900 dark:text-red-200'>
Execution Error
</p>
<div className='text-sm text-red-800 dark:text-red-300'>
{(() => {
// Get error message from block state or logs
const id = focusedBlockId || ''
// First check console entries for the most recent error
const consoleEntries = useConsoleStore.getState().entries
const consoleError = consoleEntries
.filter(entry => {
// Match by block ID (handle virtual IDs too)
if (entry.blockId === id) return true
const resolved = resolveOriginalBlockId(entry.blockId)
return resolved === id
})
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
.find(entry => entry.error)
if (consoleError?.error) {
return (
<pre className='font-mono text-xs whitespace-pre-wrap break-words'>
{consoleError.error}
</pre>
)
}
// Then check block state
const directState = debugContext?.blockStates.get(id)
if (directState?.output && typeof directState.output === 'object' && 'error' in directState.output) {
return (
<pre className='font-mono text-xs whitespace-pre-wrap break-words'>
{String(directState.output.error)}
</pre>
)
}
// Check virtual executions
for (const [key, state] of (debugContext?.blockStates?.entries() || [])) {
if (isVirtualForBlock(String(key), id) && state?.output && typeof state.output === 'object' && 'error' in state.output) {
return (
<pre className='font-mono text-xs whitespace-pre-wrap break-words'>
{String(state.output.error)}
</pre>
)
}
}
// Check logs
const errorLog = debugContext?.blockLogs?.find(log =>
(log.blockId === id || resolveOriginalBlockId(log.blockId) === id) && !log.success
)
if (errorLog?.error) {
return (
<pre className='font-mono text-xs whitespace-pre-wrap break-words'>
{String(errorLog.error)}
</pre>
)
}
return <span className='text-xs'>Unknown error occurred</span>
})()}
</div>
</div>
</div>
</div>
) : resolvedOutputKVs && Object.keys(resolvedOutputKVs).length > 0 ? (
<div className='h-full overflow-y-scroll overflow-x-hidden'>
<table className='w-full table-fixed'>
<colgroup>
@@ -1519,7 +1622,7 @@ export function DebugPanel() {
{isExpanded ? (
<pre className='text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-words overflow-x-auto'>
{JSON.stringify(value, null, 2)}
</pre>
</pre>
) : (
<span className='text-[11px] font-mono text-muted-foreground hover:text-foreground block truncate'>
{JSON.stringify(value).slice(0, 100)}...

View File

@@ -591,6 +591,7 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
const setActiveBlocks = useExecutionStore((s) => s.setActiveBlocks)
const setActiveTab = usePanelStore((s) => s.setActiveTab)
const breakpointId = useExecutionStore((s) => s.breakpointId)
const debugContext = useExecutionStore((s) => s.debugContext)
const handleDebugOpen = (e: React.MouseEvent) => {
if (!isDebugModeEnabled) return
@@ -606,6 +607,34 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
const isExecutingNow = isDebugModeEnabled ? executingBlockIds.has(id) : activeBlockIds.has(id)
const isCurrentBlock = isDebugModeEnabled && isPending
const isPanelFocused = isDebugModeEnabled && panelFocusedBlockId === id
// Check if block has errored during debug execution
const hasError = isDebugModeEnabled && debugContext ? (() => {
// Check direct block state for error
const directState = debugContext.blockStates?.get(id)
if (directState?.output && typeof directState.output === 'object' && 'error' in directState.output) {
return true
}
// Check virtual executions for errors (for blocks inside parallels)
for (const [key, state] of (debugContext.blockStates?.entries() || [])) {
// Check if this is a virtual ID for our block
if (typeof key === 'string' && key.startsWith(`${id}_parallel_`)) {
if (state?.output && typeof state.output === 'object' && 'error' in state.output) {
return true
}
}
}
// Also check block logs for this block
const hasErrorLog = debugContext.blockLogs?.some((log: any) => {
if (log.blockId === id && !log.success) return true
// Check if log is for a virtual version of this block
if (typeof log.blockId === 'string' && log.blockId.startsWith(`${id}_parallel_`) && !log.success) {
return true
}
return false
})
return hasErrorLog || false
})() : false
return (
<div className='group relative'>
@@ -616,23 +645,32 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
'transition-block-bg transition-ring',
displayIsWide ? 'w-[480px]' : 'w-[320px]',
!isEnabled && 'shadow-sm',
// Panel-focused block highlight
isPanelFocused && 'bg-blue-50/60 dark:bg-blue-900/5',
// Error state - highest priority
hasError && 'bg-red-50 dark:bg-red-900/10 ring-2 ring-red-500',
// Panel-focused block highlight (unless errored)
!hasError && isPanelFocused && 'bg-blue-50/60 dark:bg-blue-900/5',
// Executing blocks match staging: pulsing blue ring
isExecutingNow && 'animate-pulse-ring ring-2 ring-blue-500',
// Pending blocks show green border when not executing
!isExecutingNow && isCurrentBlock && 'ring-2 ring-blue-500',
// Diff highlighting
diffStatus === 'new' && 'bg-green-50/50 ring-2 ring-green-500 dark:bg-green-900/10',
diffStatus === 'edited' && 'bg-orange-50/50 ring-2 ring-orange-500 dark:bg-orange-900/10',
!hasError && isExecutingNow && 'animate-pulse-ring ring-2 ring-blue-500',
// Pending blocks show blue border when not executing
!hasError && !isExecutingNow && isCurrentBlock && 'ring-2 ring-blue-500',
// Diff highlighting (only if not in debug error state)
!hasError && diffStatus === 'new' && 'bg-green-50/50 ring-2 ring-green-500 dark:bg-green-900/10',
!hasError && diffStatus === 'edited' && 'bg-orange-50/50 ring-2 ring-orange-500 dark:bg-orange-900/10',
// Deleted block highlighting (in original workflow)
isDeletedBlock && 'bg-red-50/50 ring-2 ring-red-500 dark:bg-red-900/10',
'z-[20]'
)}
onClick={handleDebugOpen}
>
{/* Show debug indicator for current blocks in debug mode (pending or executing) */}
{isDebugModeEnabled && (isPending || executingBlockIds.has(id)) && (
{/* Show error indicator for errored blocks */}
{hasError && (
<div className='-top-6 -translate-x-1/2 absolute left-1/2 z-10 transform rounded-t-md bg-red-500 px-2 py-0.5 text-white text-xs'>
Error
</div>
)}
{/* Show debug indicator for current blocks in debug mode (pending or executing) - but not if errored */}
{!hasError && isDebugModeEnabled && (isPending || executingBlockIds.has(id)) && (
<div className='-top-6 -translate-x-1/2 absolute left-1/2 z-10 transform rounded-t-md bg-blue-500 px-2 py-0.5 text-white text-xs'>
Current
</div>