Skip to content
Merged
2 changes: 1 addition & 1 deletion apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
--panel-width: 320px; /* PANEL_WIDTH.DEFAULT */
--toolbar-triggers-height: 300px; /* TOOLBAR_TRIGGERS_HEIGHT.DEFAULT */
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
--terminal-height: 155px; /* TERMINAL_HEIGHT.DEFAULT */
--terminal-height: 206px; /* TERMINAL_HEIGHT.DEFAULT */
}

.sidebar-container {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,19 @@ const TraceSpanNode = memo(function TraceSpanNode({
return children.sort((a, b) => parseTime(a.startTime) - parseTime(b.startTime))
}, [span, spanId, spanStartTime])

const hasChildren = allChildren.length > 0
// Hide empty model timing segments for agents without tool calls
const filteredChildren = useMemo(() => {
const isAgent = span.type?.toLowerCase() === 'agent'
const hasToolCalls =
(span.toolCalls?.length ?? 0) > 0 || allChildren.some((c) => c.type?.toLowerCase() === 'tool')

if (isAgent && !hasToolCalls) {
return allChildren.filter((c) => c.type?.toLowerCase() !== 'model')
}
return allChildren
}, [allChildren, span.type, span.toolCalls])

const hasChildren = filteredChildren.length > 0
const isExpanded = isRootWorkflow || expandedNodes.has(spanId)
const isToggleable = !isRootWorkflow

Expand Down Expand Up @@ -685,7 +697,7 @@ const TraceSpanNode = memo(function TraceSpanNode({
{/* Nested Children */}
{hasChildren && (
<div className='flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[10px]'>
{allChildren.map((child, index) => (
{filteredChildren.map((child, index) => (
<div key={child.id || `${spanId}-child-${index}`} className='pl-[6px]'>
<TraceSpanNode
span={child}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function Logs() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const [isLive, setIsLive] = useState(false)
const [isLive, setIsLive] = useState(true)
const [isVisuallyRefreshing, setIsVisuallyRefreshing] = useState(false)
const [isExporting, setIsExporting] = useState(false)
const isSearchOpenRef = useRef<boolean>(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import { useCallback, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import clsx from 'clsx'
import { ChevronDown, RepeatIcon, SplitIcon } from 'lucide-react'
import { RepeatIcon, SplitIcon } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import { ChevronDown } from '@/components/emcn'
import {
FieldItem,
type SchemaField,
Expand Down Expand Up @@ -115,9 +116,8 @@ function ConnectionItem({
{hasFields && (
<ChevronDown
className={clsx(
'h-3.5 w-3.5 flex-shrink-0 transition-transform duration-100',
'text-[var(--text-secondary)] group-hover:text-[var(--text-primary)]',
isExpanded && 'rotate-180'
'h-[8px] w-[8px] flex-shrink-0 text-[var(--text-tertiary)] transition-transform duration-100 group-hover:text-[var(--text-primary)]',
!isExpanded && '-rotate-90'
)}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use client'

import { memo } from 'react'
import clsx from 'clsx'
import { Filter } from 'lucide-react'
import {
Button,
Popover,
PopoverContent,
PopoverDivider,
PopoverItem,
PopoverScrollArea,
PopoverSection,
PopoverTrigger,
} from '@/components/emcn'
import type {
BlockInfo,
TerminalFilters,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
import { getBlockIcon } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils'

/**
* Props for the FilterPopover component
*/
export interface FilterPopoverProps {
open: boolean
onOpenChange: (open: boolean) => void
filters: TerminalFilters
toggleStatus: (status: 'error' | 'info') => void
toggleBlock: (blockId: string) => void
uniqueBlocks: BlockInfo[]
hasActiveFilters: boolean
}

/**
* Filter popover component used in terminal header and output panel
*/
export const FilterPopover = memo(function FilterPopover({
open,
onOpenChange,
filters,
toggleStatus,
toggleBlock,
uniqueBlocks,
hasActiveFilters,
}: FilterPopoverProps) {
return (
<Popover open={open} onOpenChange={onOpenChange} size='sm'>
<PopoverTrigger asChild>
<Button
variant='ghost'
className='!p-1.5 -m-1.5'
onClick={(e) => e.stopPropagation()}
aria-label='Filters'
>
<Filter
className={clsx('h-3 w-3', hasActiveFilters && 'text-[var(--brand-secondary)]')}
/>
</Button>
</PopoverTrigger>
<PopoverContent
side='top'
align='end'
sideOffset={4}
onClick={(e) => e.stopPropagation()}
minWidth={160}
maxWidth={220}
maxHeight={300}
>
<PopoverSection>Status</PopoverSection>
<PopoverItem
active={filters.statuses.has('error')}
showCheck={filters.statuses.has('error')}
onClick={() => toggleStatus('error')}
>
<div
className='h-[6px] w-[6px] rounded-[2px]'
style={{ backgroundColor: 'var(--text-error)' }}
/>
<span className='flex-1'>Error</span>
</PopoverItem>
<PopoverItem
active={filters.statuses.has('info')}
showCheck={filters.statuses.has('info')}
onClick={() => toggleStatus('info')}
>
<div
className='h-[6px] w-[6px] rounded-[2px]'
style={{ backgroundColor: 'var(--terminal-status-info-color)' }}
/>
<span className='flex-1'>Info</span>
</PopoverItem>

{uniqueBlocks.length > 0 && (
<>
<PopoverDivider className='my-[4px]' />
<PopoverSection className='!mt-0'>Blocks</PopoverSection>
<PopoverScrollArea className='max-h-[100px]'>
{uniqueBlocks.map((block) => {
const BlockIcon = getBlockIcon(block.blockType)
const isSelected = filters.blockIds.has(block.blockId)

return (
<PopoverItem
key={block.blockId}
active={isSelected}
showCheck={isSelected}
onClick={() => toggleBlock(block.blockId)}
>
{BlockIcon && <BlockIcon className='h-3 w-3' />}
<span className='flex-1'>{block.blockName}</span>
</PopoverItem>
)
})}
</PopoverScrollArea>
</>
)}
</PopoverContent>
</Popover>
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FilterPopover, type FilterPopoverProps } from './filter-popover'
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { LogRowContextMenu } from './log-row-context-menu'
export { OutputContextMenu } from './output-context-menu'
export { FilterPopover, type FilterPopoverProps } from './filter-popover'
export { LogRowContextMenu, type LogRowContextMenuProps } from './log-row-context-menu'
export { OutputPanel, type OutputPanelProps } from './output-panel'
export { RunningBadge, StatusDisplay, type StatusDisplayProps } from './status-display'
export { ToggleButton, type ToggleButtonProps } from './toggle-button'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LogRowContextMenu, type LogRowContextMenuProps } from './log-row-context-menu'
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
'use client'

import type { RefObject } from 'react'
import { memo, type RefObject } from 'react'
import {
Popover,
PopoverAnchor,
PopoverContent,
PopoverDivider,
PopoverItem,
} from '@/components/emcn'
import type {
ContextMenuPosition,
TerminalFilters,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
import type { ConsoleEntry } from '@/stores/terminal'

interface ContextMenuPosition {
x: number
y: number
}

interface TerminalFilters {
blockIds: Set<string>
statuses: Set<'error' | 'info'>
runIds: Set<string>
}

interface LogRowContextMenuProps {
export interface LogRowContextMenuProps {
isOpen: boolean
position: ContextMenuPosition
menuRef: RefObject<HTMLDivElement | null>
Expand All @@ -30,19 +23,16 @@ interface LogRowContextMenuProps {
filters: TerminalFilters
onFilterByBlock: (blockId: string) => void
onFilterByStatus: (status: 'error' | 'info') => void
onFilterByRunId: (runId: string) => void
onCopyRunId: (runId: string) => void
onClearFilters: () => void
onClearConsole: () => void
onFixInCopilot: (entry: ConsoleEntry) => void
hasActiveFilters: boolean
}

/**
* Context menu for terminal log rows (left side).
* Displays filtering options based on the selected row's properties.
*/
export function LogRowContextMenu({
export const LogRowContextMenu = memo(function LogRowContextMenu({
isOpen,
position,
menuRef,
Expand All @@ -51,19 +41,15 @@ export function LogRowContextMenu({
filters,
onFilterByBlock,
onFilterByStatus,
onFilterByRunId,
onCopyRunId,
onClearFilters,
onClearConsole,
onFixInCopilot,
hasActiveFilters,
}: LogRowContextMenuProps) {
const hasRunId = entry?.executionId != null

const isBlockFiltered = entry ? filters.blockIds.has(entry.blockId) : false
const entryStatus = entry?.success ? 'info' : 'error'
const isStatusFiltered = entry ? filters.statuses.has(entryStatus) : false
const isRunIdFiltered = entry?.executionId ? filters.runIds.has(entry.executionId) : false

return (
<Popover
Expand Down Expand Up @@ -134,34 +120,11 @@ export function LogRowContextMenu({
>
Filter by Status
</PopoverItem>
{hasRunId && (
<PopoverItem
showCheck={isRunIdFiltered}
onClick={() => {
onFilterByRunId(entry.executionId!)
onClose()
}}
>
Filter by Run ID
</PopoverItem>
)}
</>
)}

{/* Clear filters */}
{hasActiveFilters && (
<PopoverItem
onClick={() => {
onClearFilters()
onClose()
}}
>
Clear All Filters
</PopoverItem>
)}

{/* Destructive action */}
{(entry || hasActiveFilters) && <PopoverDivider />}
{entry && <PopoverDivider />}
<PopoverItem
onClick={() => {
onClearConsole()
Expand All @@ -173,4 +136,4 @@ export function LogRowContextMenu({
</PopoverContent>
</Popover>
)
}
})
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
'use client'

import type { RefObject } from 'react'
import { memo, type RefObject } from 'react'
import {
Popover,
PopoverAnchor,
PopoverContent,
PopoverDivider,
PopoverItem,
} from '@/components/emcn'
import type { ContextMenuPosition } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'

interface ContextMenuPosition {
x: number
y: number
}

interface OutputContextMenuProps {
export interface OutputContextMenuProps {
isOpen: boolean
position: ContextMenuPosition
menuRef: RefObject<HTMLDivElement | null>
onClose: () => void
onCopySelection: () => void
onCopyAll: () => void
onSearch: () => void
structuredView: boolean
onToggleStructuredView: () => void
wrapText: boolean
onToggleWrap: () => void
openOnRun: boolean
Expand All @@ -34,14 +32,16 @@ interface OutputContextMenuProps {
* Context menu for terminal output panel (right side).
* Displays copy, search, and display options for the code viewer.
*/
export function OutputContextMenu({
export const OutputContextMenu = memo(function OutputContextMenu({
isOpen,
position,
menuRef,
onClose,
onCopySelection,
onCopyAll,
onSearch,
structuredView,
onToggleStructuredView,
wrapText,
onToggleWrap,
openOnRun,
Expand Down Expand Up @@ -96,6 +96,9 @@ export function OutputContextMenu({

{/* Display settings - toggles don't close menu */}
<PopoverDivider />
<PopoverItem showCheck={structuredView} onClick={onToggleStructuredView}>
Structured View
</PopoverItem>
<PopoverItem showCheck={wrapText} onClick={onToggleWrap}>
Wrap Text
</PopoverItem>
Expand All @@ -116,4 +119,4 @@ export function OutputContextMenu({
</PopoverContent>
</Popover>
)
}
})
Loading