diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts index d54ccbfdf..861708926 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts @@ -1,5 +1,5 @@ import type React from 'react' -import { RepeatIcon, SplitIcon } from 'lucide-react' +import { AlertTriangleIcon, BanIcon, RepeatIcon, SplitIcon, XCircleIcon } from 'lucide-react' import { getBlock } from '@/blocks' import { TERMINAL_BLOCK_COLUMN_WIDTH } from '@/stores/constants' import type { ConsoleEntry } from '@/stores/terminal' @@ -12,6 +12,15 @@ const SUBFLOW_COLORS = { parallel: '#FEE12B', } as const +/** + * Special block type colors for errors and system messages + */ +const SPECIAL_BLOCK_COLORS = { + error: '#ef4444', + validation: '#f59e0b', + cancelled: '#6b7280', +} as const + /** * Retrieves the icon component for a given block type */ @@ -32,6 +41,18 @@ export function getBlockIcon( return SplitIcon } + if (blockType === 'error') { + return XCircleIcon + } + + if (blockType === 'validation') { + return AlertTriangleIcon + } + + if (blockType === 'cancelled') { + return BanIcon + } + return null } @@ -50,6 +71,16 @@ export function getBlockColor(blockType: string): string { if (blockType === 'parallel') { return SUBFLOW_COLORS.parallel } + // Special block types for errors and system messages + if (blockType === 'error') { + return SPECIAL_BLOCK_COLORS.error + } + if (blockType === 'validation') { + return SPECIAL_BLOCK_COLORS.validation + } + if (blockType === 'cancelled') { + return SPECIAL_BLOCK_COLORS.cancelled + } return '#6b7280' } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 747425247..495c89b69 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -1161,9 +1161,14 @@ export function useWorkflowExecution() { cancelRunningEntries(activeWorkflowId) } - if (accumulatedBlockLogs.length === 0) { - // No blocks executed yet - this is a pre-execution error - // Use 0 for executionOrder so validation errors appear first + const isPreExecutionError = accumulatedBlockLogs.length === 0 + // Check if any block already has this error - don't duplicate block errors + const blockAlreadyHasError = accumulatedBlockLogs.some((log) => log.error) + + // Only add workflow-level error entry for: + // 1. Pre-execution errors (validation) - no blocks ran + // 2. Timeout errors - no block has the error + if (isPreExecutionError || !blockAlreadyHasError) { addConsole({ input: {}, output: {}, @@ -1171,21 +1176,38 @@ export function useWorkflowExecution() { error: data.error, durationMs: data.duration || 0, startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(), - executionOrder: 0, + executionOrder: isPreExecutionError ? 0 : Number.MAX_SAFE_INTEGER, endedAt: new Date().toISOString(), workflowId: activeWorkflowId, - blockId: 'validation', + blockId: isPreExecutionError ? 'validation' : 'timeout-error', executionId, - blockName: 'Workflow Validation', - blockType: 'validation', + blockName: isPreExecutionError ? 'Workflow Validation' : 'Timeout Error', + blockType: isPreExecutionError ? 'validation' : 'error', }) } }, - onExecutionCancelled: () => { + onExecutionCancelled: (data) => { if (activeWorkflowId) { cancelRunningEntries(activeWorkflowId) } + + // Add console entry for cancellation + addConsole({ + input: {}, + output: {}, + success: false, + error: 'Execution was cancelled', + durationMs: data?.duration || 0, + startedAt: new Date(Date.now() - (data?.duration || 0)).toISOString(), + executionOrder: Number.MAX_SAFE_INTEGER, + endedAt: new Date().toISOString(), + workflowId: activeWorkflowId, + blockId: 'cancelled', + executionId, + blockName: 'Execution Cancelled', + blockType: 'cancelled', + }) }, }, }) @@ -1736,21 +1758,27 @@ export function useWorkflowExecution() { cancelRunningEntries(workflowId) - addConsole({ - input: {}, - output: {}, - success: false, - error: data.error, - durationMs: data.duration || 0, - startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(), - executionOrder: Number.MAX_SAFE_INTEGER, - endedAt: new Date().toISOString(), - workflowId, - blockId: 'workflow-error', - executionId, - blockName: 'Workflow Error', - blockType: 'error', - }) + // Check if any block already has an error - don't duplicate block errors + const blockAlreadyHasError = accumulatedBlockLogs.some((log) => log.error) + + // Only add timeout error entry if no block has the error + if (!blockAlreadyHasError) { + addConsole({ + input: {}, + output: {}, + success: false, + error: data.error, + durationMs: data.duration || 0, + startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(), + executionOrder: Number.MAX_SAFE_INTEGER, + endedAt: new Date().toISOString(), + workflowId, + blockId: 'timeout-error', + executionId, + blockName: 'Timeout Error', + blockType: 'error', + }) + } }, onExecutionCancelled: () => {