Skip to content

Commit 86653ca

Browse files
committed
feat(terminal): log view
1 parent 9eff58a commit 86653ca

File tree

22 files changed

+2086
-930
lines changed

22 files changed

+2086
-930
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,19 @@ const TraceSpanNode = memo(function TraceSpanNode({
573573
return children.sort((a, b) => parseTime(a.startTime) - parseTime(b.startTime))
574574
}, [span, spanId, spanStartTime])
575575

576-
const hasChildren = allChildren.length > 0
576+
// Hide empty model timing segments for agents without tool calls
577+
const filteredChildren = useMemo(() => {
578+
const isAgent = span.type?.toLowerCase() === 'agent'
579+
const hasToolCalls =
580+
(span.toolCalls?.length ?? 0) > 0 || allChildren.some((c) => c.type?.toLowerCase() === 'tool')
581+
582+
if (isAgent && !hasToolCalls) {
583+
return allChildren.filter((c) => c.type?.toLowerCase() !== 'model')
584+
}
585+
return allChildren
586+
}, [allChildren, span.type, span.toolCalls])
587+
588+
const hasChildren = filteredChildren.length > 0
577589
const isExpanded = isRootWorkflow || expandedNodes.has(spanId)
578590
const isToggleable = !isRootWorkflow
579591

@@ -685,7 +697,7 @@ const TraceSpanNode = memo(function TraceSpanNode({
685697
{/* Nested Children */}
686698
{hasChildren && (
687699
<div className='flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[10px]'>
688-
{allChildren.map((child, index) => (
700+
{filteredChildren.map((child, index) => (
689701
<div key={child.id || `${spanId}-child-${index}`} className='pl-[6px]'>
690702
<TraceSpanNode
691703
span={child}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/connection-blocks/connection-blocks.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import { useCallback, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import clsx from 'clsx'
6-
import { ChevronDown, RepeatIcon, SplitIcon } from 'lucide-react'
6+
import { RepeatIcon, SplitIcon } from 'lucide-react'
77
import { useShallow } from 'zustand/react/shallow'
8+
import { ChevronDown } from '@/components/emcn'
89
import {
910
FieldItem,
1011
type SchemaField,
@@ -115,9 +116,8 @@ function ConnectionItem({
115116
{hasFields && (
116117
<ChevronDown
117118
className={clsx(
118-
'h-3.5 w-3.5 flex-shrink-0 transition-transform duration-100',
119-
'text-[var(--text-secondary)] group-hover:text-[var(--text-primary)]',
120-
isExpanded && 'rotate-180'
119+
'h-[8px] w-[8px] flex-shrink-0 text-[var(--text-tertiary)] transition-transform duration-100 group-hover:text-[var(--text-primary)]',
120+
!isExpanded && '-rotate-90'
121121
)}
122122
/>
123123
)}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
'use client'
2+
3+
import { memo } from 'react'
4+
import clsx from 'clsx'
5+
import { Filter } from 'lucide-react'
6+
import {
7+
Button,
8+
Popover,
9+
PopoverContent,
10+
PopoverDivider,
11+
PopoverItem,
12+
PopoverScrollArea,
13+
PopoverSection,
14+
PopoverTrigger,
15+
} from '@/components/emcn'
16+
import type {
17+
BlockInfo,
18+
TerminalFilters,
19+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
20+
import {
21+
formatRunId,
22+
getBlockIcon,
23+
getRunIdColor,
24+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils'
25+
26+
/**
27+
* Props for the FilterPopover component
28+
*/
29+
export interface FilterPopoverProps {
30+
open: boolean
31+
onOpenChange: (open: boolean) => void
32+
filters: TerminalFilters
33+
toggleStatus: (status: 'error' | 'info') => void
34+
toggleBlock: (blockId: string) => void
35+
toggleRunId: (runId: string) => void
36+
uniqueBlocks: BlockInfo[]
37+
uniqueRunIds: string[]
38+
executionColorMap: Map<string, string>
39+
hasActiveFilters: boolean
40+
}
41+
42+
/**
43+
* Filter popover component used in terminal header and output panel
44+
*/
45+
export const FilterPopover = memo(function FilterPopover({
46+
open,
47+
onOpenChange,
48+
filters,
49+
toggleStatus,
50+
toggleBlock,
51+
toggleRunId,
52+
uniqueBlocks,
53+
uniqueRunIds,
54+
executionColorMap,
55+
hasActiveFilters,
56+
}: FilterPopoverProps) {
57+
return (
58+
<Popover open={open} onOpenChange={onOpenChange} size='sm'>
59+
<PopoverTrigger asChild>
60+
<Button
61+
variant='ghost'
62+
className='!p-1.5 -m-1.5'
63+
onClick={(e) => e.stopPropagation()}
64+
aria-label='Filters'
65+
>
66+
<Filter
67+
className={clsx('h-3 w-3', hasActiveFilters && 'text-[var(--brand-secondary)]')}
68+
/>
69+
</Button>
70+
</PopoverTrigger>
71+
<PopoverContent
72+
side='bottom'
73+
align='end'
74+
sideOffset={4}
75+
onClick={(e) => e.stopPropagation()}
76+
minWidth={160}
77+
maxWidth={220}
78+
maxHeight={300}
79+
>
80+
<PopoverSection>Status</PopoverSection>
81+
<PopoverItem
82+
active={filters.statuses.has('error')}
83+
showCheck={filters.statuses.has('error')}
84+
onClick={() => toggleStatus('error')}
85+
>
86+
<div
87+
className='h-[6px] w-[6px] rounded-[2px]'
88+
style={{ backgroundColor: 'var(--text-error)' }}
89+
/>
90+
<span className='flex-1'>Error</span>
91+
</PopoverItem>
92+
<PopoverItem
93+
active={filters.statuses.has('info')}
94+
showCheck={filters.statuses.has('info')}
95+
onClick={() => toggleStatus('info')}
96+
>
97+
<div
98+
className='h-[6px] w-[6px] rounded-[2px]'
99+
style={{ backgroundColor: 'var(--terminal-status-info-color)' }}
100+
/>
101+
<span className='flex-1'>Info</span>
102+
</PopoverItem>
103+
104+
{uniqueBlocks.length > 0 && (
105+
<>
106+
<PopoverDivider />
107+
<PopoverSection className='!mt-0'>Blocks</PopoverSection>
108+
<PopoverScrollArea className='max-h-[100px]'>
109+
{uniqueBlocks.map((block) => {
110+
const BlockIcon = getBlockIcon(block.blockType)
111+
const isSelected = filters.blockIds.has(block.blockId)
112+
113+
return (
114+
<PopoverItem
115+
key={block.blockId}
116+
active={isSelected}
117+
showCheck={isSelected}
118+
onClick={() => toggleBlock(block.blockId)}
119+
>
120+
{BlockIcon && <BlockIcon className='h-3 w-3' />}
121+
<span className='flex-1'>{block.blockName}</span>
122+
</PopoverItem>
123+
)
124+
})}
125+
</PopoverScrollArea>
126+
</>
127+
)}
128+
129+
{uniqueRunIds.length > 0 && (
130+
<>
131+
<PopoverDivider />
132+
<PopoverSection className='!mt-0'>Run ID</PopoverSection>
133+
<PopoverScrollArea className='max-h-[100px]'>
134+
{uniqueRunIds.map((runId) => {
135+
const isSelected = filters.runIds.has(runId)
136+
const runIdColor = getRunIdColor(runId, executionColorMap)
137+
138+
return (
139+
<PopoverItem
140+
key={runId}
141+
active={isSelected}
142+
showCheck={isSelected}
143+
onClick={() => toggleRunId(runId)}
144+
>
145+
<span
146+
className='flex-1 font-mono text-[11px]'
147+
style={{ color: runIdColor || '#D2D2D2' }}
148+
>
149+
{formatRunId(runId)}
150+
</span>
151+
</PopoverItem>
152+
)
153+
})}
154+
</PopoverScrollArea>
155+
</>
156+
)}
157+
</PopoverContent>
158+
</Popover>
159+
)
160+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { FilterPopover, type FilterPopoverProps } from './filter-popover'
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
export { LogRowContextMenu } from './log-row-context-menu'
2-
export { OutputPanel } from './output-panel'
1+
export { FilterPopover, type FilterPopoverProps } from './filter-popover'
2+
export { LogRowContextMenu, type LogRowContextMenuProps } from './log-row-context-menu'
3+
export { OutputPanel, type OutputPanelProps } from './output-panel'
4+
export { ToggleButton, type ToggleButtonProps } from './toggle-button'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { LogRowContextMenu, type LogRowContextMenuProps } from './log-row-context-menu'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/log-row-context-menu.tsx renamed to apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/log-row-context-menu/log-row-context-menu.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
11
'use client'
22

3-
import type { RefObject } from 'react'
3+
import { memo, type RefObject } from 'react'
44
import {
55
Popover,
66
PopoverAnchor,
77
PopoverContent,
88
PopoverDivider,
99
PopoverItem,
1010
} from '@/components/emcn'
11+
import type {
12+
ContextMenuPosition,
13+
TerminalFilters,
14+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
1115
import type { ConsoleEntry } from '@/stores/terminal'
1216

13-
interface ContextMenuPosition {
14-
x: number
15-
y: number
16-
}
17-
18-
interface TerminalFilters {
19-
blockIds: Set<string>
20-
statuses: Set<'error' | 'info'>
21-
runIds: Set<string>
22-
}
23-
24-
interface LogRowContextMenuProps {
17+
export interface LogRowContextMenuProps {
2518
isOpen: boolean
2619
position: ContextMenuPosition
2720
menuRef: RefObject<HTMLDivElement | null>
@@ -42,7 +35,7 @@ interface LogRowContextMenuProps {
4235
* Context menu for terminal log rows (left side).
4336
* Displays filtering options based on the selected row's properties.
4437
*/
45-
export function LogRowContextMenu({
38+
export const LogRowContextMenu = memo(function LogRowContextMenu({
4639
isOpen,
4740
position,
4841
menuRef,
@@ -173,4 +166,4 @@ export function LogRowContextMenu({
173166
</PopoverContent>
174167
</Popover>
175168
)
176-
}
169+
})

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/output-panel/components/output-context-menu.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
'use client'
22

3-
import type { RefObject } from 'react'
3+
import { memo, type RefObject } from 'react'
44
import {
55
Popover,
66
PopoverAnchor,
77
PopoverContent,
88
PopoverDivider,
99
PopoverItem,
1010
} from '@/components/emcn'
11+
import type { ContextMenuPosition } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
1112

12-
interface ContextMenuPosition {
13-
x: number
14-
y: number
15-
}
16-
17-
interface OutputContextMenuProps {
13+
export interface OutputContextMenuProps {
1814
isOpen: boolean
1915
position: ContextMenuPosition
2016
menuRef: RefObject<HTMLDivElement | null>
@@ -36,7 +32,7 @@ interface OutputContextMenuProps {
3632
* Context menu for terminal output panel (right side).
3733
* Displays copy, search, and display options for the code viewer.
3834
*/
39-
export function OutputContextMenu({
35+
export const OutputContextMenu = memo(function OutputContextMenu({
4036
isOpen,
4137
position,
4238
menuRef,
@@ -123,4 +119,4 @@ export function OutputContextMenu({
123119
</PopoverContent>
124120
</Popover>
125121
)
126-
}
122+
})

0 commit comments

Comments
 (0)