Skip to content

Commit 0713580

Browse files
authored
improvement: custom tools modal, logs-details (#2283)
1 parent f421f27 commit 0713580

File tree

7 files changed

+540
-220
lines changed

7 files changed

+540
-220
lines changed

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx

Lines changed: 176 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import clsx from 'clsx'
88
import { Button, ChevronDown } from '@/components/emcn'
99
import type { TraceSpan } from '@/stores/logs/filters/types'
1010
import '@/components/emcn/components/code/code.css'
11+
import { WorkflowIcon } from '@/components/icons'
1112
import { LoopTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/loop/loop-config'
1213
import { ParallelTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/parallel/parallel-config'
1314
import { getBlock, getBlockByToolName } from '@/blocks'
@@ -120,6 +121,14 @@ function getBlockColor(type: string): string {
120121
return '#2FA1FF'
121122
case 'api':
122123
return '#2F55FF'
124+
case 'loop':
125+
case 'loop-iteration':
126+
return '#2FB3FF'
127+
case 'parallel':
128+
case 'parallel-iteration':
129+
return '#FEE12B'
130+
case 'workflow':
131+
return '#705335'
123132
default:
124133
return '#6b7280'
125134
}
@@ -134,12 +143,15 @@ function getBlockIconAndColor(type: string): {
134143
} {
135144
const lowerType = type.toLowerCase()
136145

137-
if (lowerType === 'loop') {
146+
if (lowerType === 'loop' || lowerType === 'loop-iteration') {
138147
return { icon: LoopTool.icon, bgColor: LoopTool.bgColor }
139148
}
140-
if (lowerType === 'parallel') {
149+
if (lowerType === 'parallel' || lowerType === 'parallel-iteration') {
141150
return { icon: ParallelTool.icon, bgColor: ParallelTool.bgColor }
142151
}
152+
if (lowerType === 'workflow') {
153+
return { icon: WorkflowIcon, bgColor: '#705335' }
154+
}
143155

144156
const blockType = lowerType === 'model' ? 'agent' : lowerType
145157
const blockConfig = getBlock(blockType)
@@ -289,15 +301,11 @@ function InputOutputSection({
289301
{isExpanded && (
290302
<div>
291303
{isError && typeof data === 'object' && data !== null && 'error' in data ? (
292-
<div
293-
className='rounded-[6px] px-[10px] py-[8px]'
294-
style={{
295-
backgroundColor: 'var(--terminal-status-error-bg)',
296-
color: 'var(--text-error)',
297-
}}
298-
>
299-
<div className='font-medium text-[12px]'>Error</div>
300-
<div className='mt-[4px] text-[12px]'>{(data as { error: string }).error}</div>
304+
<div className='rounded-[4px] border border-[rgba(234,67,53,0.24)] bg-[rgba(234,67,53,0.08)] px-[10px] py-[8px]'>
305+
<div className='font-medium text-[#EA4335] text-[12px]'>Error</div>
306+
<div className='mt-[4px] text-[#FF8076] text-[12px]'>
307+
{(data as { error: string }).error}
308+
</div>
301309
</div>
302310
) : (
303311
<div className='code-editor-theme overflow-hidden rounded-[6px] bg-[var(--surface-3)] px-[10px] py-[8px]'>
@@ -313,6 +321,116 @@ function InputOutputSection({
313321
)
314322
}
315323

324+
interface NestedBlockItemProps {
325+
span: TraceSpan
326+
parentId: string
327+
index: number
328+
expandedSections: Set<string>
329+
onToggle: (section: string) => void
330+
workflowStartTime: number
331+
totalDuration: number
332+
}
333+
334+
/**
335+
* Recursive component for rendering nested blocks at any depth
336+
*/
337+
function NestedBlockItem({
338+
span,
339+
parentId,
340+
index,
341+
expandedSections,
342+
onToggle,
343+
workflowStartTime,
344+
totalDuration,
345+
}: NestedBlockItemProps): React.ReactNode {
346+
const spanId = span.id || `${parentId}-nested-${index}`
347+
const isError = span.status === 'error'
348+
const toolBlock =
349+
span.type?.toLowerCase() === 'tool' && span.name ? getBlockByToolName(span.name) : null
350+
const { icon: SpanIcon, bgColor } = toolBlock
351+
? { icon: toolBlock.icon, bgColor: toolBlock.bgColor }
352+
: getBlockIconAndColor(span.type)
353+
354+
return (
355+
<div className='flex flex-col gap-[8px]'>
356+
<div className='flex items-center justify-between'>
357+
<div className='flex items-center gap-[8px]'>
358+
<div
359+
className='relative flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
360+
style={{ background: bgColor }}
361+
>
362+
{SpanIcon && <SpanIcon className={clsx('text-white', '!h-[9px] !w-[9px]')} />}
363+
</div>
364+
<span
365+
className='font-medium text-[12px]'
366+
style={{
367+
color: isError ? 'var(--text-error)' : 'var(--text-secondary)',
368+
}}
369+
>
370+
{span.name}
371+
</span>
372+
</div>
373+
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
374+
{formatDuration(span.duration || 0)}
375+
</span>
376+
</div>
377+
378+
<ProgressBar
379+
span={span}
380+
childSpans={span.children}
381+
workflowStartTime={workflowStartTime}
382+
totalDuration={totalDuration}
383+
/>
384+
385+
{span.input && (
386+
<InputOutputSection
387+
label='Input'
388+
data={span.input}
389+
isError={false}
390+
spanId={`${spanId}-input`}
391+
sectionType='input'
392+
expandedSections={expandedSections}
393+
onToggle={onToggle}
394+
/>
395+
)}
396+
397+
{span.input && span.output && (
398+
<div className='border-[var(--border)] border-t border-dashed' />
399+
)}
400+
401+
{span.output && (
402+
<InputOutputSection
403+
label={isError ? 'Error' : 'Output'}
404+
data={span.output}
405+
isError={isError}
406+
spanId={`${spanId}-output`}
407+
sectionType='output'
408+
expandedSections={expandedSections}
409+
onToggle={onToggle}
410+
/>
411+
)}
412+
413+
{/* Recursively render children */}
414+
{span.children && span.children.length > 0 && (
415+
<div className='mt-[8px] flex flex-col gap-[16px] border-[var(--border)] border-l-2 pl-[10px]'>
416+
{span.children.map((child, childIndex) => (
417+
<NestedBlockItem
418+
key={child.id || `${spanId}-child-${childIndex}`}
419+
span={child}
420+
parentId={spanId}
421+
index={childIndex}
422+
expandedSections={expandedSections}
423+
onToggle={onToggle}
424+
workflowStartTime={workflowStartTime}
425+
totalDuration={totalDuration}
426+
/>
427+
))}
428+
</div>
429+
)}
430+
</div>
431+
)
432+
}
433+
316434
interface TraceSpanItemProps {
317435
span: TraceSpan
318436
totalDuration: number
@@ -346,11 +464,22 @@ function TraceSpanItem({
346464
const hasOutput = Boolean(span.output)
347465
const isError = span.status === 'error'
348466

349-
const inlineChildTypes = new Set(['tool', 'model'])
350-
const inlineChildren =
351-
span.children?.filter((child) => inlineChildTypes.has(child.type?.toLowerCase() || '')) || []
352-
const otherChildren =
353-
span.children?.filter((child) => !inlineChildTypes.has(child.type?.toLowerCase() || '')) || []
467+
const inlineChildTypes = new Set([
468+
'tool',
469+
'model',
470+
'loop-iteration',
471+
'parallel-iteration',
472+
'workflow',
473+
])
474+
475+
// For workflow-in-workflow blocks, all children should be rendered inline/nested
476+
const isWorkflowBlock = span.type?.toLowerCase() === 'workflow'
477+
const inlineChildren = isWorkflowBlock
478+
? span.children || []
479+
: span.children?.filter((child) => inlineChildTypes.has(child.type?.toLowerCase() || '')) || []
480+
const otherChildren = isWorkflowBlock
481+
? []
482+
: span.children?.filter((child) => !inlineChildTypes.has(child.type?.toLowerCase() || '')) || []
354483

355484
const toolCallSpans = useMemo(() => {
356485
if (!hasToolCalls) return []
@@ -502,7 +631,14 @@ function TraceSpanItem({
502631

503632
<ProgressBar
504633
span={childSpan}
505-
childSpans={undefined}
634+
childSpans={
635+
childSpan.type?.toLowerCase() === 'loop-iteration' ||
636+
childSpan.type?.toLowerCase() === 'parallel-iteration' ||
637+
childSpan.type?.toLowerCase() === 'workflow' ||
638+
(isWorkflowBlock && childSpan.children && childSpan.children.length > 0)
639+
? childSpan.children
640+
: undefined
641+
}
506642
workflowStartTime={workflowStartTime}
507643
totalDuration={totalDuration}
508644
/>
@@ -534,6 +670,29 @@ function TraceSpanItem({
534670
onToggle={handleSectionToggle}
535671
/>
536672
)}
673+
674+
{/* Render nested blocks for loop/parallel iterations, nested workflows, and workflow block children */}
675+
{(childSpan.type?.toLowerCase() === 'loop-iteration' ||
676+
childSpan.type?.toLowerCase() === 'parallel-iteration' ||
677+
childSpan.type?.toLowerCase() === 'workflow' ||
678+
isWorkflowBlock) &&
679+
childSpan.children &&
680+
childSpan.children.length > 0 && (
681+
<div className='mt-[8px] flex flex-col gap-[16px] border-[var(--border)] border-l-2 pl-[10px]'>
682+
{childSpan.children.map((nestedChild, nestedIndex) => (
683+
<NestedBlockItem
684+
key={nestedChild.id || `${childId}-nested-${nestedIndex}`}
685+
span={nestedChild}
686+
parentId={childId}
687+
index={nestedIndex}
688+
expandedSections={expandedSections}
689+
onToggle={handleSectionToggle}
690+
workflowStartTime={workflowStartTime}
691+
totalDuration={totalDuration}
692+
/>
693+
))}
694+
</div>
695+
)}
537696
</div>
538697
</div>
539698
)

0 commit comments

Comments
 (0)