mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Checkpoint
This commit is contained in:
@@ -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)}...
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user