11'use client'
22
3- import { useEffect , useState } from 'react'
4- import { createLogger } from '@sim/logger'
3+ import { useEffect , useMemo , useState } from 'react'
54import { AlertCircle , Loader2 } from 'lucide-react'
65import { Modal , ModalBody , ModalContent , ModalHeader } from '@/components/emcn'
76import { redactApiKeys } from '@/lib/core/security/redaction'
@@ -10,10 +9,9 @@ import {
109 BlockDetailsSidebar ,
1110 WorkflowPreview ,
1211} from '@/app/workspace/[workspaceId]/w/components/preview'
12+ import { useExecutionSnapshot } from '@/hooks/queries/logs'
1313import type { WorkflowState } from '@/stores/workflows/workflow/types'
1414
15- const logger = createLogger ( 'ExecutionSnapshot' )
16-
1715interface TraceSpan {
1816 blockId ?: string
1917 input ?: unknown
@@ -45,25 +43,6 @@ function isMigratedWorkflowState(state: WorkflowState): state is MigratedWorkflo
4543 return ( state as MigratedWorkflowState ) . _migrated === true
4644}
4745
48- interface ExecutionSnapshotData {
49- executionId : string
50- workflowId : string
51- workflowState : WorkflowState | MigratedWorkflowState
52- executionMetadata : {
53- trigger : string
54- startedAt : string
55- endedAt ?: string
56- totalDurationMs ?: number
57-
58- cost : {
59- total : number | null
60- input : number | null
61- output : number | null
62- }
63- totalTokens : number | null
64- }
65- }
66-
6746interface ExecutionSnapshotProps {
6847 executionId : string
6948 traceSpans ?: TraceSpan [ ]
@@ -85,83 +64,55 @@ export function ExecutionSnapshot({
8564 isOpen = false ,
8665 onClose = ( ) => { } ,
8766} : ExecutionSnapshotProps ) {
88- const [ data , setData ] = useState < ExecutionSnapshotData | null > ( null )
89- const [ blockExecutions , setBlockExecutions ] = useState < Record < string , BlockExecutionData > > ( { } )
90- const [ loading , setLoading ] = useState ( true )
91- const [ error , setError ] = useState < string | null > ( null )
67+ const { data, isLoading, error } = useExecutionSnapshot ( executionId )
9268 const [ pinnedBlockId , setPinnedBlockId ] = useState < string | null > ( null )
9369
94- useEffect ( ( ) => {
95- if ( traceSpans && Array . isArray ( traceSpans ) ) {
96- const blockExecutionMap : Record < string , BlockExecutionData > = { }
97-
98- const collectBlockSpans = ( spans : TraceSpan [ ] ) : TraceSpan [ ] => {
99- const blockSpans : TraceSpan [ ] = [ ]
100-
101- for ( const span of spans ) {
102- if ( span . blockId ) {
103- blockSpans . push ( span )
104- }
105- if ( span . children && Array . isArray ( span . children ) ) {
106- blockSpans . push ( ...collectBlockSpans ( span . children ) )
107- }
108- }
70+ // Process traceSpans to create blockExecutions map
71+ const blockExecutions = useMemo ( ( ) => {
72+ if ( ! traceSpans || ! Array . isArray ( traceSpans ) ) return { }
10973
110- return blockSpans
111- }
74+ const blockExecutionMap : Record < string , BlockExecutionData > = { }
11275
113- const allBlockSpans = collectBlockSpans ( traceSpans )
76+ const collectBlockSpans = ( spans : TraceSpan [ ] ) : TraceSpan [ ] => {
77+ const blockSpans : TraceSpan [ ] = [ ]
11478
115- for ( const span of allBlockSpans ) {
116- if ( span . blockId && ! blockExecutionMap [ span . blockId ] ) {
117- blockExecutionMap [ span . blockId ] = {
118- input : redactApiKeys ( span . input || { } ) ,
119- output : redactApiKeys ( span . output || { } ) ,
120- status : span . status || 'unknown' ,
121- durationMs : span . duration || 0 ,
122- }
79+ for ( const span of spans ) {
80+ if ( span . blockId ) {
81+ blockSpans . push ( span )
82+ }
83+ if ( span . children && Array . isArray ( span . children ) ) {
84+ blockSpans . push ( ...collectBlockSpans ( span . children ) )
12385 }
12486 }
12587
126- setBlockExecutions ( blockExecutionMap )
88+ return blockSpans
12789 }
128- } , [ traceSpans ] )
129-
130- useEffect ( ( ) => {
131- const abortController = new AbortController ( )
13290
133- const fetchData = async ( ) => {
134- try {
135- setLoading ( true )
136- setError ( null )
91+ const allBlockSpans = collectBlockSpans ( traceSpans )
13792
138- const response = await fetch ( `/api/logs/execution/${ executionId } ` , {
139- signal : abortController . signal ,
140- } )
141- if ( ! response . ok ) {
142- throw new Error ( `Failed to fetch execution snapshot data: ${ response . statusText } ` )
93+ for ( const span of allBlockSpans ) {
94+ if ( span . blockId && ! blockExecutionMap [ span . blockId ] ) {
95+ blockExecutionMap [ span . blockId ] = {
96+ input : redactApiKeys ( span . input || { } ) ,
97+ output : redactApiKeys ( span . output || { } ) ,
98+ status : span . status || 'unknown' ,
99+ durationMs : span . duration || 0 ,
143100 }
144-
145- const result = await response . json ( )
146- setData ( result )
147- logger . debug ( `Loaded execution snapshot data for execution: ${ executionId } ` )
148- } catch ( err ) {
149- if ( err instanceof Error && err . name === 'AbortError' ) return
150- const errorMessage = err instanceof Error ? err . message : 'Unknown error'
151- logger . error ( 'Failed to fetch execution snapshot data:' , err )
152- setError ( errorMessage )
153- } finally {
154- setLoading ( false )
155101 }
156102 }
157103
158- fetchData ( )
104+ return blockExecutionMap
105+ } , [ traceSpans ] )
159106
160- return ( ) => abortController . abort ( )
107+ // Reset pinned block when executionId changes
108+ useEffect ( ( ) => {
109+ setPinnedBlockId ( null )
161110 } , [ executionId ] )
162111
112+ const workflowState = data ?. workflowState as WorkflowState | undefined
113+
163114 const renderContent = ( ) => {
164- if ( loading ) {
115+ if ( isLoading ) {
165116 return (
166117 < div
167118 className = { cn ( 'flex items-center justify-center' , className ) }
@@ -183,24 +134,27 @@ export function ExecutionSnapshot({
183134 >
184135 < div className = 'flex items-center gap-[8px] text-[var(--text-error)]' >
185136 < AlertCircle className = 'h-[16px] w-[16px]' />
186- < span className = 'text-[13px]' > Failed to load execution snapshot: { error } </ span >
137+ < span className = 'text-[13px]' > Failed to load execution snapshot: { error . message } </ span >
187138 </ div >
188139 </ div >
189140 )
190141 }
191142
192- if ( ! data ) {
143+ if ( ! data || ! workflowState ) {
193144 return (
194145 < div
195146 className = { cn ( 'flex items-center justify-center' , className ) }
196147 style = { { height, width } }
197148 >
198- < div className = 'text-[13px] text-[var(--text-secondary)]' > No data available</ div >
149+ < div className = 'flex items-center gap-[8px] text-[var(--text-secondary)]' >
150+ < Loader2 className = 'h-[16px] w-[16px] animate-spin' />
151+ < span className = 'text-[13px]' > Loading execution snapshot...</ span >
152+ </ div >
199153 </ div >
200154 )
201155 }
202156
203- if ( isMigratedWorkflowState ( data . workflowState ) ) {
157+ if ( isMigratedWorkflowState ( workflowState ) ) {
204158 return (
205159 < div
206160 className = { cn ( 'flex flex-col items-center justify-center gap-[16px] p-[32px]' , className ) }
@@ -214,9 +168,7 @@ export function ExecutionSnapshot({
214168 This log was migrated from the old logging system. The workflow state at execution time
215169 is not available.
216170 </ div >
217- < div className = 'text-[12px] text-[var(--text-tertiary)]' >
218- Note: { data . workflowState . _note }
219- </ div >
171+ < div className = 'text-[12px] text-[var(--text-tertiary)]' > Note: { workflowState . _note } </ div >
220172 </ div >
221173 )
222174 }
@@ -231,7 +183,7 @@ export function ExecutionSnapshot({
231183 >
232184 < div className = 'h-full flex-1' >
233185 < WorkflowPreview
234- workflowState = { data . workflowState }
186+ workflowState = { workflowState }
235187 showSubBlocks = { true }
236188 isPannable = { true }
237189 defaultPosition = { { x : 0 , y : 0 } }
@@ -244,12 +196,12 @@ export function ExecutionSnapshot({
244196 executedBlocks = { blockExecutions }
245197 />
246198 </ div >
247- { pinnedBlockId && data . workflowState . blocks [ pinnedBlockId ] && (
199+ { pinnedBlockId && workflowState . blocks [ pinnedBlockId ] && (
248200 < BlockDetailsSidebar
249- block = { data . workflowState . blocks [ pinnedBlockId ] }
201+ block = { workflowState . blocks [ pinnedBlockId ] }
250202 executionData = { blockExecutions [ pinnedBlockId ] }
251203 allBlockExecutions = { blockExecutions }
252- workflowBlocks = { data . workflowState . blocks }
204+ workflowBlocks = { workflowState . blocks }
253205 isExecutionMode
254206 />
255207 ) }
0 commit comments