Skip to content

Commit 239a05c

Browse files
committed
refactor: improve flow run details rendering with parallel steps and status styles
- Group specific steps (summary, sentiment, tags) as parallel steps - Enhance step status display with color indicators and retry info - Rearrange steps to show website, parallel, then saveToDb and others - Add styles for parallel step containers for better layout and clarity
1 parent 4d96fa9 commit 239a05c

File tree

1 file changed

+195
-124
lines changed

1 file changed

+195
-124
lines changed

examples/playground/components/flow-run-details.tsx

Lines changed: 195 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export default function FlowRunDetails({
189189
Click steps to view details
190190
</span>
191191
</h3>
192-
<div className="grid grid-cols-1 md:grid-cols-1 gap-2">
192+
<div>
193193
{runData.step_states &&
194194
(() => {
195195
// Sort step_states directly by step.step_index
@@ -201,7 +201,24 @@ export default function FlowRunDetails({
201201
},
202202
);
203203

204-
return sortedStepStates.map((step, index) => {
204+
// Group parallel steps based on flow definition
205+
// We're specifically looking for summary, sentiment, and tags steps that run in parallel
206+
const parallelStepSlugs = ['summary', 'sentiment', 'tags'];
207+
const parallelSteps = sortedStepStates.filter((step) =>
208+
parallelStepSlugs.includes(step.step_slug),
209+
);
210+
211+
// Other steps will be displayed normally
212+
const regularSteps = sortedStepStates.filter(
213+
(step) => !parallelStepSlugs.includes(step.step_slug),
214+
);
215+
216+
// Function to render a step
217+
const renderStep = (
218+
step: any,
219+
index: number,
220+
isParallel: boolean = false,
221+
) => {
205222
// Find the corresponding step tasks for this step
206223
const stepTasks = runData.step_tasks
207224
?.filter((task) => task.step_slug === step.step_slug)
@@ -214,40 +231,42 @@ export default function FlowRunDetails({
214231
(task) => task.status === 'completed',
215232
);
216233

234+
// Get the pre-sorted step tasks
235+
const latestTask =
236+
stepTasks && stepTasks.length > 0
237+
? stepTasks.sort(
238+
(a, b) =>
239+
(b.attempts_count || 0) - (a.attempts_count || 0),
240+
)[0]
241+
: null;
242+
243+
// Check if this is a retry (attempts_count > 1)
244+
const isRetrying =
245+
latestTask &&
246+
latestTask.attempts_count > 1 &&
247+
step.status === 'started';
248+
249+
// Define status-based styles
250+
let statusStyle = '';
251+
if (step.status === 'completed') {
252+
statusStyle = 'bg-green-500/5 border-green-500/30';
253+
} else if (isRetrying) {
254+
statusStyle =
255+
'bg-red-500/5 border-red-500/30 animate-pulse';
256+
} else if (step.status === 'started') {
257+
statusStyle = 'bg-yellow-500/5 border-yellow-500/30';
258+
} else if (step.status === 'failed') {
259+
statusStyle = 'bg-red-500/5 border-red-500/30';
260+
} else if (step.status === 'created') {
261+
statusStyle = 'bg-blue-500/5 border-blue-500/30';
262+
} else {
263+
statusStyle = 'bg-gray-500/5 border-gray-500/30';
264+
}
265+
217266
return (
218267
<Collapsible
219268
key={index}
220-
className={`mb-1 rounded-lg border ${(() => {
221-
// Get the pre-sorted step tasks from above
222-
const latestTask =
223-
stepTasks && stepTasks.length > 0
224-
? stepTasks.sort(
225-
(a, b) =>
226-
(b.attempts_count || 0) -
227-
(a.attempts_count || 0),
228-
)[0]
229-
: null;
230-
231-
// Check if this is a retry (attempts_count > 1)
232-
const isRetrying =
233-
latestTask &&
234-
latestTask.attempts_count > 1 &&
235-
step.status === 'started';
236-
237-
if (step.status === 'completed') {
238-
return 'bg-green-500/5 border-green-500/30';
239-
} else if (isRetrying) {
240-
return 'bg-red-500/5 border-red-500/30 animate-pulse';
241-
} else if (step.status === 'started') {
242-
return 'bg-yellow-500/5 border-yellow-500/30';
243-
} else if (step.status === 'failed') {
244-
return 'bg-red-500/5 border-red-500/30';
245-
} else if (step.status === 'created') {
246-
return 'bg-blue-500/5 border-blue-500/30';
247-
} else {
248-
return 'bg-gray-500/5 border-gray-500/30';
249-
}
250-
})()}`}
269+
className={`rounded-lg border ${statusStyle}`}
251270
>
252271
<CollapsibleTrigger className="flex items-center justify-between w-full p-2 text-left">
253272
<div>
@@ -256,100 +275,68 @@ export default function FlowRunDetails({
256275
</h4>
257276
</div>
258277
<div className="flex items-center">
259-
{step.status === 'started' && step.started_at && (
260-
<span className="text-xs text-yellow-600/80 mr-2">
261-
{formatRelativeTime(
262-
step.started_at,
263-
currentTime,
264-
)}
265-
</span>
266-
)}
267-
{step.status === 'completed' &&
268-
step.started_at &&
269-
step.completed_at && (
270-
<span className="text-xs text-green-600/80 mr-2">
271-
{formatTimeDifference(
272-
step.started_at,
273-
step.completed_at,
278+
{!isParallel && (
279+
<>
280+
{step.status === 'started' &&
281+
step.started_at && (
282+
<span className="text-xs text-yellow-600/80 mr-2">
283+
{formatRelativeTime(
284+
step.started_at,
285+
currentTime,
286+
)}
287+
</span>
274288
)}
275-
</span>
276-
)}
277-
{step.status === 'failed' &&
278-
step.started_at &&
279-
step.failed_at && (
280-
<span className="text-xs text-red-600/80 mr-2">
281-
Failed after{' '}
282-
{formatTimeDifference(
283-
step.started_at,
284-
step.failed_at,
289+
{step.status === 'completed' &&
290+
step.started_at &&
291+
step.completed_at && (
292+
<span className="text-xs text-green-600/80 mr-2">
293+
{formatTimeDifference(
294+
step.started_at,
295+
step.completed_at,
296+
)}
297+
</span>
285298
)}
286-
</span>
287-
)}
288-
{(() => {
289-
// Use the pre-sorted step tasks from above
290-
const latestTask =
291-
stepTasks && stepTasks.length > 0
292-
? stepTasks.sort(
293-
(a, b) =>
294-
(b.attempts_count || 0) -
295-
(a.attempts_count || 0),
296-
)[0]
297-
: null;
298-
299-
// Check if this is a retry (attempts_count > 1)
300-
const isRetrying =
301-
latestTask &&
302-
latestTask.attempts_count > 1 &&
303-
step.status === 'started';
304-
305-
return (
306-
<span
307-
className={`inline-block w-2 h-2 rounded-full mr-1 ${
308-
step.status === 'completed'
309-
? 'bg-green-500'
310-
: isRetrying
311-
? 'bg-red-500 breathing'
312-
: step.status === 'started'
313-
? 'bg-yellow-500 breathing'
314-
: step.status === 'failed'
315-
? 'bg-red-500'
316-
: step.status === 'created'
317-
? 'bg-blue-500'
318-
: 'bg-gray-500'
319-
}`}
320-
></span>
321-
);
322-
})()}
323-
<span className="capitalize text-xs">
324-
{(() => {
325-
// Use the pre-sorted step tasks from above
326-
const latestTask =
327-
stepTasks && stepTasks.length > 0
328-
? stepTasks.sort(
329-
(a, b) =>
330-
(b.attempts_count || 0) -
331-
(a.attempts_count || 0),
332-
)[0]
333-
: null;
334-
335-
// Check if this is a retry (attempts_count > 1)
336-
const isRetrying =
337-
latestTask &&
338-
latestTask.attempts_count > 1 &&
339-
step.status === 'started';
340-
341-
if (isRetrying) {
342-
return `retrying (retry ${latestTask.attempts_count - 1})`;
343-
} else if (step.status === 'created') {
344-
return 'waiting';
345-
} else {
346-
return step.status;
347-
}
348-
})()}
349-
</span>
299+
{step.status === 'failed' &&
300+
step.started_at &&
301+
step.failed_at && (
302+
<span className="text-xs text-red-600/80 mr-2">
303+
Failed after{' '}
304+
{formatTimeDifference(
305+
step.started_at,
306+
step.failed_at,
307+
)}
308+
</span>
309+
)}
310+
</>
311+
)}
312+
313+
<span
314+
className={`inline-block w-2 h-2 rounded-full ${!isParallel ? 'mr-1' : ''} ${
315+
step.status === 'completed'
316+
? 'bg-green-500'
317+
: isRetrying
318+
? 'bg-red-500 breathing'
319+
: step.status === 'started'
320+
? 'bg-yellow-500 breathing'
321+
: step.status === 'failed'
322+
? 'bg-red-500'
323+
: step.status === 'created'
324+
? 'bg-blue-500'
325+
: 'bg-gray-500'
326+
}`}
327+
></span>
328+
{!isParallel && (
329+
<span className="capitalize text-xs">
330+
{isRetrying
331+
? `retrying (retry ${latestTask.attempts_count - 1})`
332+
: step.status === 'created'
333+
? 'waiting'
334+
: step.status}
335+
</span>
336+
)}
350337
</div>
351338
</CollapsibleTrigger>
352-
<CollapsibleContent className="px-2 pb-2">
339+
<CollapsibleContent className="px-2 pb-2 w-full bg-background/70 backdrop-blur-sm border-t border-foreground/10">
353340
{step.status === 'completed' && stepTask?.output && (
354341
<div className="mt-1 overflow-auto">
355342
<div className="max-h-32 overflow-hidden border border-gray-500/30 rounded-md">
@@ -384,7 +371,91 @@ export default function FlowRunDetails({
384371
</CollapsibleContent>
385372
</Collapsible>
386373
);
387-
});
374+
};
375+
376+
// Separate website and saveToDb steps to place parallel steps between them
377+
const websiteStep = regularSteps.find(
378+
(step) => step.step_slug === 'website',
379+
);
380+
const saveToDbStep = regularSteps.find(
381+
(step) => step.step_slug === 'saveToDb',
382+
);
383+
const otherRegularSteps = regularSteps.filter(
384+
(step) =>
385+
step.step_slug !== 'website' &&
386+
step.step_slug !== 'saveToDb',
387+
);
388+
389+
return (
390+
<div className="space-y-3">
391+
{/* Website step (first step) */}
392+
{websiteStep && (
393+
<div className="grid grid-cols-1 gap-2 mb-6">
394+
{renderStep(websiteStep, 0)}
395+
</div>
396+
)}
397+
398+
{/* Parallel steps with note */}
399+
{parallelSteps.length > 0 && (
400+
<div className="mb-0">
401+
<div className="flex justify-between items-center mb-1">
402+
<span className="text-xs text-muted-foreground italic">
403+
Following steps run in parallel
404+
</span>
405+
<span className="text-xs flex items-center">
406+
<svg
407+
xmlns="http://www.w3.org/2000/svg"
408+
width="12"
409+
height="12"
410+
viewBox="0 0 24 24"
411+
fill="none"
412+
stroke="currentColor"
413+
strokeWidth="2"
414+
strokeLinecap="round"
415+
strokeLinejoin="round"
416+
className="mr-1"
417+
>
418+
<path d="M12 22v-6M9 8V2M15 8V2M6 8a3 3 0 0 1 3 3v1M18 8a3 3 0 0 0-3 3v1M12 19a3 3 0 0 1-3-3v-1M12 19a3 3 0 0 0 3-3v-1"></path>
419+
</svg>
420+
Parallel Processing
421+
</span>
422+
</div>
423+
<div className="grid grid-cols-3 gap-2 relative step-container">
424+
{parallelSteps.map((step, index) =>
425+
renderStep(step, index, true),
426+
)}
427+
</div>
428+
<style jsx>{`
429+
.step-container {
430+
margin-bottom: 2rem;
431+
}
432+
.step-container > :global(*) {
433+
height: 41px; /* Match the height of regular steps (label + padding) */
434+
}
435+
.step-container > :global(*[data-state="open"]) {
436+
height: auto;
437+
}
438+
`}</style>
439+
</div>
440+
)}
441+
442+
{/* SaveToDb step (last step) */}
443+
{saveToDbStep && (
444+
<div className="grid grid-cols-1 gap-2">
445+
{renderStep(saveToDbStep, 1)}
446+
</div>
447+
)}
448+
449+
{/* Any other regular steps */}
450+
{otherRegularSteps.length > 0 && (
451+
<div className="grid grid-cols-1 gap-2 mt-3">
452+
{otherRegularSteps.map((step, index) =>
453+
renderStep(step, index + 2),
454+
)}
455+
</div>
456+
)}
457+
</div>
458+
);
388459
})()}
389460
</div>
390461
</div>

0 commit comments

Comments
 (0)