11'use client'
22
3- import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
3+ import { useCallback , useRef , useState } from 'react'
44import { AlertCircle , Loader2 } from 'lucide-react'
55import { createPortal } from 'react-dom'
66import {
@@ -13,13 +13,8 @@ import {
1313 PopoverContent ,
1414 PopoverItem ,
1515} from '@/components/emcn'
16- import { redactApiKeys } from '@/lib/core/security/redaction'
1716import { cn } from '@/lib/core/utils/cn'
18- import {
19- getLeftmostBlockId ,
20- PreviewEditor ,
21- WorkflowPreview ,
22- } from '@/app/workspace/[workspaceId]/w/components/preview'
17+ import { Preview } from '@/app/workspace/[workspaceId]/w/components/preview'
2318import { useExecutionSnapshot } from '@/hooks/queries/logs'
2419import type { WorkflowState } from '@/stores/workflows/workflow/types'
2520
@@ -32,13 +27,6 @@ interface TraceSpan {
3227 children ?: TraceSpan [ ]
3328}
3429
35- interface BlockExecutionData {
36- input : unknown
37- output : unknown
38- status : string
39- durationMs : number
40- }
41-
4230interface MigratedWorkflowState extends WorkflowState {
4331 _migrated : true
4432 _note ?: string
@@ -70,98 +58,35 @@ export function ExecutionSnapshot({
7058 onClose = ( ) => { } ,
7159} : ExecutionSnapshotProps ) {
7260 const { data, isLoading, error } = useExecutionSnapshot ( executionId )
73- const [ pinnedBlockId , setPinnedBlockId ] = useState < string | null > ( null )
74- const autoSelectedForExecutionRef = useRef < string | null > ( null )
61+ const lastExecutionIdRef = useRef < string | null > ( null )
7562
7663 const [ isMenuOpen , setIsMenuOpen ] = useState ( false )
7764 const [ menuPosition , setMenuPosition ] = useState ( { x : 0 , y : 0 } )
78- const [ contextMenuBlockId , setContextMenuBlockId ] = useState < string | null > ( null )
7965 const menuRef = useRef < HTMLDivElement > ( null )
8066
8167 const closeMenu = useCallback ( ( ) => {
8268 setIsMenuOpen ( false )
83- setContextMenuBlockId ( null )
8469 } , [ ] )
8570
8671 const handleCanvasContextMenu = useCallback ( ( e : React . MouseEvent ) => {
8772 e . preventDefault ( )
8873 e . stopPropagation ( )
89- setContextMenuBlockId ( null )
9074 setMenuPosition ( { x : e . clientX , y : e . clientY } )
9175 setIsMenuOpen ( true )
9276 } , [ ] )
9377
94- const handleNodeContextMenu = useCallback (
95- ( blockId : string , mousePosition : { x : number ; y : number } ) => {
96- setContextMenuBlockId ( blockId )
97- setMenuPosition ( mousePosition )
98- setIsMenuOpen ( true )
99- } ,
100- [ ]
101- )
102-
10378 const handleCopyExecutionId = useCallback ( ( ) => {
10479 navigator . clipboard . writeText ( executionId )
10580 closeMenu ( )
10681 } , [ executionId , closeMenu ] )
10782
108- const handleOpenDetails = useCallback ( ( ) => {
109- if ( contextMenuBlockId ) {
110- setPinnedBlockId ( contextMenuBlockId )
111- }
112- closeMenu ( )
113- } , [ contextMenuBlockId , closeMenu ] )
114-
115- const blockExecutions = useMemo ( ( ) => {
116- if ( ! traceSpans || ! Array . isArray ( traceSpans ) ) return { }
117-
118- const blockExecutionMap : Record < string , BlockExecutionData > = { }
119-
120- const collectBlockSpans = ( spans : TraceSpan [ ] ) : TraceSpan [ ] => {
121- const blockSpans : TraceSpan [ ] = [ ]
122-
123- for ( const span of spans ) {
124- if ( span . blockId ) {
125- blockSpans . push ( span )
126- }
127- if ( span . children && Array . isArray ( span . children ) ) {
128- blockSpans . push ( ...collectBlockSpans ( span . children ) )
129- }
130- }
131-
132- return blockSpans
133- }
134-
135- const allBlockSpans = collectBlockSpans ( traceSpans )
136-
137- for ( const span of allBlockSpans ) {
138- if ( span . blockId && ! blockExecutionMap [ span . blockId ] ) {
139- blockExecutionMap [ span . blockId ] = {
140- input : redactApiKeys ( span . input || { } ) ,
141- output : redactApiKeys ( span . output || { } ) ,
142- status : span . status || 'unknown' ,
143- durationMs : span . duration || 0 ,
144- }
145- }
146- }
147-
148- return blockExecutionMap
149- } , [ traceSpans ] )
150-
15183 const workflowState = data ?. workflowState as WorkflowState | undefined
15284
153- // Auto-select the leftmost block once when data loads for a new executionId
154- useEffect ( ( ) => {
155- if (
156- workflowState &&
157- ! isMigratedWorkflowState ( workflowState ) &&
158- autoSelectedForExecutionRef . current !== executionId
159- ) {
160- autoSelectedForExecutionRef . current = executionId
161- const leftmostId = getLeftmostBlockId ( workflowState )
162- setPinnedBlockId ( leftmostId )
163- }
164- } , [ executionId , workflowState ] )
85+ // Track execution ID changes for key reset
86+ const executionKey = executionId !== lastExecutionIdRef . current ? executionId : undefined
87+ if ( executionId !== lastExecutionIdRef . current ) {
88+ lastExecutionIdRef . current = executionId
89+ }
16590
16691 const renderContent = ( ) => {
16792 if ( isLoading ) {
@@ -226,44 +151,17 @@ export function ExecutionSnapshot({
226151 }
227152
228153 return (
229- < div
230- style = { { height, width } }
231- className = { cn (
232- 'flex overflow-hidden' ,
233- ! isModal && 'rounded-[4px] border border-[var(--border)]' ,
234- className
235- ) }
236- >
237- < div className = 'h-full flex-1' onContextMenu = { handleCanvasContextMenu } >
238- < WorkflowPreview
239- workflowState = { workflowState }
240- isPannable = { true }
241- defaultPosition = { { x : 0 , y : 0 } }
242- defaultZoom = { 0.8 }
243- onNodeClick = { ( blockId ) => {
244- setPinnedBlockId ( blockId )
245- } }
246- onNodeContextMenu = { handleNodeContextMenu }
247- onPaneClick = { ( ) => setPinnedBlockId ( null ) }
248- cursorStyle = 'pointer'
249- executedBlocks = { blockExecutions }
250- selectedBlockId = { pinnedBlockId }
251- />
252- </ div >
253- { pinnedBlockId && workflowState . blocks [ pinnedBlockId ] && (
254- < PreviewEditor
255- block = { workflowState . blocks [ pinnedBlockId ] }
256- executionData = { blockExecutions [ pinnedBlockId ] }
257- allBlockExecutions = { blockExecutions }
258- workflowBlocks = { workflowState . blocks }
259- workflowVariables = { workflowState . variables }
260- loops = { workflowState . loops }
261- parallels = { workflowState . parallels }
262- isExecutionMode
263- onClose = { ( ) => setPinnedBlockId ( null ) }
264- />
265- ) }
266- </ div >
154+ < Preview
155+ key = { executionKey }
156+ workflowState = { workflowState }
157+ traceSpans = { traceSpans }
158+ className = { className }
159+ height = { height }
160+ width = { width }
161+ onCanvasContextMenu = { handleCanvasContextMenu }
162+ showBorder = { ! isModal }
163+ autoSelectLeftmost
164+ />
267165 )
268166 }
269167
@@ -287,9 +185,6 @@ export function ExecutionSnapshot({
287185 } }
288186 />
289187 < PopoverContent ref = { menuRef } align = 'start' side = 'bottom' sideOffset = { 4 } >
290- { contextMenuBlockId && (
291- < PopoverItem onClick = { handleOpenDetails } > Open Details</ PopoverItem >
292- ) }
293188 < PopoverItem onClick = { handleCopyExecutionId } > Copy Execution ID</ PopoverItem >
294189 </ PopoverContent >
295190 </ Popover > ,
@@ -304,7 +199,6 @@ export function ExecutionSnapshot({
304199 open = { isOpen }
305200 onOpenChange = { ( open ) => {
306201 if ( ! open ) {
307- setPinnedBlockId ( null )
308202 onClose ( )
309203 }
310204 } }
0 commit comments