@@ -148,16 +148,81 @@ function getCurrentIterationData(blockExecutionData: any) {
148148 }
149149}
150150
151- function PinnedLogs ( { executionData, onClose } : { executionData : any ; onClose : ( ) => void } ) {
151+ function PinnedLogs ( {
152+ executionData,
153+ blockId,
154+ workflowState,
155+ onClose
156+ } : {
157+ executionData : any | null ;
158+ blockId : string ;
159+ workflowState : any ;
160+ onClose : ( ) => void ;
161+ } ) {
162+ // ALL HOOKS MUST BE CALLED BEFORE ANY CONDITIONAL RETURNS
152163 const [ currentIterationIndex , setCurrentIterationIndex ] = useState ( 0 )
153164
165+ // Reset iteration index when execution data changes
166+ useEffect ( ( ) => {
167+ setCurrentIterationIndex ( 0 )
168+ } , [ executionData ] )
169+
170+ // Handle case where block has no execution data (e.g., failed workflow)
171+ if ( ! executionData ) {
172+ const blockInfo = workflowState ?. blocks ?. [ blockId ]
173+ const formatted = {
174+ blockName : blockInfo ?. name || 'Unknown Block' ,
175+ blockType : blockInfo ?. type || 'unknown' ,
176+ status : 'not_executed' ,
177+ duration : 'N/A' ,
178+ input : null ,
179+ output : null ,
180+ errorMessage : null ,
181+ errorStackTrace : null ,
182+ cost : null ,
183+ tokens : null ,
184+ }
185+
186+ return (
187+ < Card className = 'fixed top-4 right-4 z-[100] max-h-[calc(100vh-8rem)] w-96 overflow-y-auto border-border bg-background shadow-lg' >
188+ < CardHeader className = 'pb-3' >
189+ < div className = 'flex items-center justify-between' >
190+ < CardTitle className = 'flex items-center gap-2 text-foreground text-lg' >
191+ < Zap className = 'h-5 w-5' />
192+ { formatted . blockName }
193+ </ CardTitle >
194+ < button onClick = { onClose } className = 'rounded-sm p-1 text-foreground hover:bg-muted' >
195+ < X className = 'h-4 w-4' />
196+ </ button >
197+ </ div >
198+ < div className = 'flex items-center justify-between' >
199+ < div className = 'flex items-center gap-2' >
200+ < Badge variant = 'secondary' >
201+ { formatted . blockType }
202+ </ Badge >
203+ < Badge variant = 'outline' > not executed</ Badge >
204+ </ div >
205+ </ div >
206+ </ CardHeader >
207+
208+ < CardContent className = 'space-y-4' >
209+ < div className = 'rounded-md bg-muted/50 p-4 text-center' >
210+ < div className = 'text-muted-foreground text-sm' >
211+ This block was not executed because the workflow failed before reaching it.
212+ </ div >
213+ </ div >
214+ </ CardContent >
215+ </ Card >
216+ )
217+ }
218+
219+ // Now we can safely use the execution data
154220 const iterationInfo = getCurrentIterationData ( {
155221 ...executionData ,
156222 currentIteration : currentIterationIndex ,
157223 } )
158224
159225 const formatted = formatExecutionData ( iterationInfo . executionData )
160-
161226 const totalIterations = executionData . iterations ?. length || 1
162227
163228 const goToPreviousIteration = ( ) => {
@@ -172,10 +237,6 @@ function PinnedLogs({ executionData, onClose }: { executionData: any; onClose: (
172237 }
173238 }
174239
175- useEffect ( ( ) => {
176- setCurrentIterationIndex ( 0 )
177- } , [ executionData ] )
178-
179240 return (
180241 < Card className = 'fixed top-4 right-4 z-[100] max-h-[calc(100vh-8rem)] w-96 overflow-y-auto border-border bg-background shadow-lg' >
181242 < CardHeader className = 'pb-3' >
@@ -337,17 +398,42 @@ export function FrozenCanvas({
337398 if ( traceSpans && Array . isArray ( traceSpans ) ) {
338399 const blockExecutionMap : Record < string , any > = { }
339400
340- const workflowSpan = traceSpans [ 0 ]
341- if ( workflowSpan ?. children && Array . isArray ( workflowSpan . children ) ) {
342- const traceSpansByBlockId = workflowSpan . children . reduce ( ( acc : any , span : any ) => {
401+ logger . debug ( 'Processing trace spans for frozen canvas:' , { traceSpans } )
402+
403+ // Recursively collect all spans with blockId from the trace spans tree
404+ const collectBlockSpans = ( spans : any [ ] ) : any [ ] => {
405+ const blockSpans : any [ ] = [ ]
406+
407+ for ( const span of spans ) {
408+ // If this span has a blockId, it's a block execution
343409 if ( span . blockId ) {
344- if ( ! acc [ span . blockId ] ) {
345- acc [ span . blockId ] = [ ]
346- }
347- acc [ span . blockId ] . push ( span )
410+ blockSpans . push ( span )
348411 }
349- return acc
350- } , { } )
412+
413+ // Recursively check children
414+ if ( span . children && Array . isArray ( span . children ) ) {
415+ blockSpans . push ( ...collectBlockSpans ( span . children ) )
416+ }
417+ }
418+
419+ return blockSpans
420+ }
421+
422+ const allBlockSpans = collectBlockSpans ( traceSpans )
423+ logger . debug ( 'Collected all block spans:' , allBlockSpans )
424+
425+ // Group spans by blockId
426+ const traceSpansByBlockId = allBlockSpans . reduce ( ( acc : any , span : any ) => {
427+ if ( span . blockId ) {
428+ if ( ! acc [ span . blockId ] ) {
429+ acc [ span . blockId ] = [ ]
430+ }
431+ acc [ span . blockId ] . push ( span )
432+ }
433+ return acc
434+ } , { } )
435+
436+ logger . debug ( 'Grouped trace spans by blockId:' , traceSpansByBlockId )
351437
352438 for ( const [ blockId , spans ] of Object . entries ( traceSpansByBlockId ) ) {
353439 const spanArray = spans as any [ ]
@@ -407,10 +493,9 @@ export function FrozenCanvas({
407493 totalIterations : iterations . length ,
408494 }
409495 }
410- }
411496
412- setBlockExecutions ( blockExecutionMap )
413- }
497+ setBlockExecutions ( blockExecutionMap )
498+ }
414499 } , [ traceSpans ] )
415500
416501 useEffect ( ( ) => {
@@ -439,8 +524,6 @@ export function FrozenCanvas({
439524 fetchData ( )
440525 } , [ executionId ] )
441526
442- // No need to create a temporary workflow - just use the workflowState directly
443-
444527 if ( loading ) {
445528 return (
446529 < div className = { cn ( 'flex items-center justify-center' , className ) } style = { { height, width } } >
@@ -502,16 +585,18 @@ export function FrozenCanvas({
502585 showSubBlocks = { true }
503586 isPannable = { true }
504587 onNodeClick = { ( blockId ) => {
505- if ( blockExecutions [ blockId ] ) {
506- setPinnedBlockId ( blockId )
507- }
588+ // Always allow clicking blocks, even if they don't have execution data
589+ // This is important for failed workflows where some blocks never executed
590+ setPinnedBlockId ( blockId )
508591 } }
509592 />
510593 </ div >
511594
512- { pinnedBlockId && blockExecutions [ pinnedBlockId ] && (
595+ { pinnedBlockId && (
513596 < PinnedLogs
514- executionData = { blockExecutions [ pinnedBlockId ] }
597+ executionData = { blockExecutions [ pinnedBlockId ] || null }
598+ blockId = { pinnedBlockId }
599+ workflowState = { data . workflowState }
515600 onClose = { ( ) => setPinnedBlockId ( null ) }
516601 />
517602 ) }
0 commit comments