From da09a2a19c2cb988eaaf8f5fe09f9663e8c5fa62 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 1 May 2026 16:52:15 -0700 Subject: [PATCH] improvement(mothership): reuse logs detail panel in resource view (#4389) * improvement(mothership): reuse logs detail panel in resource view * improvement(logs): drop unnecessary memo and export tab type * fix(logs): notify embedder on every resolved-tab change --- .../resource-content/resource-content.tsx | 269 +------ .../[workspaceId]/logs/components/index.ts | 3 +- .../logs/components/log-details/index.ts | 3 +- .../components/log-details/log-details.tsx | 737 +++++++++--------- 4 files changed, 386 insertions(+), 626 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index 299dddb10c..75317b99ef 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -1,11 +1,10 @@ 'use client' -import { lazy, memo, Suspense, useEffect, useMemo, useState } from 'react' +import { lazy, memo, Suspense, useEffect, useMemo } from 'react' import { createLogger } from '@sim/logger' -import { formatDuration } from '@sim/utils/formatting' import { Square } from 'lucide-react' import { useRouter } from 'next/navigation' -import { Button, Eye, PlayOutline, Skeleton, Tooltip } from '@/components/emcn' +import { Button, PlayOutline, Skeleton, Tooltip } from '@/components/emcn' import { Download, FileX, @@ -14,15 +13,12 @@ import { SquareArrowUpRight, WorkflowX, } from '@/components/emcn/icons' -import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants' import type { FilePreviewSession } from '@/lib/copilot/request/session' import { cancelRunToolExecution, markRunToolManuallyStopped, reportManualRunToolStop, } from '@/lib/copilot/tools/client/run-tool-execution' -import { cn } from '@/lib/core/utils/cn' -import { filterHiddenOutputKeys } from '@/lib/logs/execution/trace-spans/trace-spans' import { downloadWorkspaceFile, getFileExtension, @@ -43,18 +39,7 @@ import type { MothershipResource, } from '@/app/workspace/[workspaceId]/home/types' import { KnowledgeBase } from '@/app/workspace/[workspaceId]/knowledge/[id]/base' -import { - ExecutionSnapshot, - FileCards, - TraceSpans, - WorkflowOutputSection, -} from '@/app/workspace/[workspaceId]/logs/components' -import { - formatDate, - getDisplayStatus, - StatusBadge, - TriggerBadge, -} from '@/app/workspace/[workspaceId]/logs/utils' +import { LogDetailsContent } from '@/app/workspace/[workspaceId]/logs/components' import { useUserPermissionsContext, useWorkspacePermissionsContext, @@ -68,7 +53,6 @@ import { downloadTableExport } from '@/hooks/queries/tables' import { useWorkflows } from '@/hooks/queries/workflows' import { useWorkspaceFiles } from '@/hooks/queries/workspace-files' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' -import { formatCost } from '@/providers/utils' import { useExecutionStore } from '@/stores/execution/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -639,26 +623,6 @@ interface EmbeddedLogProps { function EmbeddedLog({ logId }: EmbeddedLogProps) { const { data: log, isLoading } = useLogDetail(logId) - const [isSnapshotOpen, setIsSnapshotOpen] = useState(false) - - const logStatus = getDisplayStatus(log?.status) - - const workflowOutput = useMemo(() => { - const executionData = log?.executionData as - | { finalOutput?: Record } - | undefined - if (!executionData?.finalOutput) return null - return filterHiddenOutputKeys(executionData.finalOutput) as Record - }, [log?.executionData]) - - const isWorkflowExecutionLog = - !!log && - ((log.trigger === 'manual' && !!log.duration) || - !!(log.executionData?.enhanced && log.executionData?.traceSpans)) - - const hasCostInfo = isWorkflowExecutionLog && log?.cost - - const formattedTimestamp = log ? formatDate(log.createdAt) : null if (isLoading) return LOADING_SKELETON @@ -676,232 +640,9 @@ function EmbeddedLog({ logId }: EmbeddedLogProps) { ) } - const workflowColor = - log.trigger === 'mothership' - ? '#ec4899' - : log.workflow?.color || (!log.workflowId ? 'var(--text-tertiary)' : undefined) - - const totalToolCost = (() => { - const models = (log.cost as Record)?.models as - | Record - | undefined - return models ? Object.values(models).reduce((sum, m) => sum + (m?.toolCost || 0), 0) : 0 - })() - return ( -
-
- {/* Timestamp & Workflow Row */} -
-
-
Timestamp
-
- - {formattedTimestamp?.compactDate || 'N/A'} - - - {formattedTimestamp?.compactTime || 'N/A'} - -
-
-
-
- {log.trigger === 'mothership' ? 'Job' : 'Workflow'} -
-
-
- - {log.trigger === 'mothership' - ? log.jobTitle || 'Untitled Job' - : log.workflow?.name || (!log.workflowId ? 'Deleted Workflow' : 'Unknown')} - -
-
-
- - {/* Run ID */} - {log.executionId && ( -
- Run ID - - {log.executionId} - -
- )} - - {/* Details Section */} -
-
- Level - -
-
- Trigger - {log.trigger ? ( - - ) : ( - — - )} -
-
- Duration - - {formatDuration(log.duration, { precision: 2 }) || '—'} - -
- {log.deploymentVersion && ( -
- - Version - -
- - {log.deploymentVersionName || `v${log.deploymentVersion}`} - -
-
- )} -
- - {/* Workflow State Snapshot */} - {isWorkflowExecutionLog && log.executionId && log.trigger !== 'mothership' && ( -
- - Workflow State - - -
- )} - - {/* Workflow Output */} - {isWorkflowExecutionLog && workflowOutput && ( -
- - Workflow Output - - -
- )} - - {/* Trace Spans */} - {isWorkflowExecutionLog && log.executionData?.traceSpans && ( -
- Trace Span - -
- )} - - {/* Files */} - {log.files && log.files.length > 0 && } - - {/* Cost Breakdown */} - {hasCostInfo && ( -
- - Cost Breakdown - -
-
-
- - Base Execution: - - - {formatCost(BASE_EXECUTION_CHARGE)} - -
-
- - Model Input: - - - {formatCost(log.cost?.input || 0)} - -
-
- - Model Output: - - - {formatCost(log.cost?.output || 0)} - -
- {totalToolCost > 0 && ( -
- - Tool Usage: - - - {formatCost(totalToolCost)} - -
- )} -
-
-
-
- - Total: - - - {formatCost(log.cost?.total || 0)} - -
-
- - Tokens: - - - {log.cost?.tokens?.input || log.cost?.tokens?.prompt || 0} in /{' '} - {log.cost?.tokens?.output || log.cost?.tokens?.completion || 0} out - -
-
-
-
-

- Total cost includes a base execution charge of {formatCost(BASE_EXECUTION_CHARGE)}{' '} - plus any model and tool usage costs. -

-
-
- )} -
- - {/* Frozen Canvas Modal */} - {log.executionId && ( - setIsSnapshotOpen(false)} - /> - )} +
+
) } diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/index.ts index 59310eeb4e..acdfd88cfe 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/index.ts @@ -1,5 +1,6 @@ export { Dashboard } from './dashboard' -export { LogDetails, WorkflowOutputSection } from './log-details' +export type { LogDetailsTab } from './log-details' +export { LogDetails, LogDetailsContent, WorkflowOutputSection } from './log-details' export { ExecutionSnapshot } from './log-details/components/execution-snapshot' export { FileCards } from './log-details/components/file-download' export { TraceSpans } from './log-details/components/trace-spans' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/index.ts index 47078f505c..cc8d670e1e 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/index.ts @@ -1 +1,2 @@ -export { LogDetails, WorkflowOutputSection } from './log-details' +export type { LogDetailsTab } from './log-details' +export { LogDetails, LogDetailsContent, WorkflowOutputSection } from './log-details' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx index 2be31bc5bc..ee33779f6d 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx @@ -254,6 +254,370 @@ export const WorkflowOutputSection = memo( (prev, next) => prev.output === next.output ) +export type LogDetailsTab = 'overview' | 'trace' + +interface LogDetailsContentProps { + /** The log to display */ + log: WorkflowLog + /** Fires when the active tab changes, so embedders can gate their own keyboard handlers */ + onActiveTabChange?: (tab: LogDetailsTab) => void +} + +/** + * Tabbed body for a single log: overview details and trace spans, plus the + * execution snapshot modal. Used as the body of the `LogDetails` sidebar and + * embedded directly inside the Mothership resource panel — keep the two in + * sync by editing this component, not by re-implementing it elsewhere. + */ +export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentProps) { + const [isExecutionSnapshotOpen, setIsExecutionSnapshotOpen] = useState(false) + const [activeTab, setActiveTab] = useState('overview') + const [prevLogId, setPrevLogId] = useState(log.id) + const [copiedRunId, setCopiedRunId] = useState(false) + + if (prevLogId !== log.id) { + setPrevLogId(log.id) + setActiveTab('overview') + } + + const copiedRunIdTimerRef = useRef(null) + const scrollAreaRef = useRef(null) + + useEffect(() => { + return () => { + if (copiedRunIdTimerRef.current !== null) window.clearTimeout(copiedRunIdTimerRef.current) + } + }, []) + + const { config: permissionConfig } = usePermissionConfig() + + useEffect(() => { + if (scrollAreaRef.current) { + scrollAreaRef.current.scrollTop = 0 + } + }, [log.id]) + + const isWorkflowExecutionLog = + (log.trigger === 'manual' && !!log.duration) || + !!(log.executionData?.enhanced && log.executionData?.traceSpans) + + const hasCostInfo = !!(isWorkflowExecutionLog && log.cost) + const showWorkflowState = + isWorkflowExecutionLog && + !!log.executionId && + log.trigger !== 'mothership' && + !permissionConfig.hideTraceSpans + const showTraceTab = + isWorkflowExecutionLog && !!log.executionData?.traceSpans && !permissionConfig.hideTraceSpans + + const resolvedTab: LogDetailsTab = activeTab === 'trace' && !showTraceTab ? 'overview' : activeTab + + const prevResolvedTabRef = useRef(resolvedTab) + if (prevResolvedTabRef.current !== resolvedTab) { + prevResolvedTabRef.current = resolvedTab + onActiveTabChange?.(resolvedTab) + } + + const workflowOutput = useMemo(() => { + const executionData = log.executionData as { finalOutput?: Record } | undefined + if (!executionData?.finalOutput) return null + return filterHiddenOutputKeys(executionData.finalOutput) as Record + }, [log.executionData]) + + const workflowInput = useMemo(() => { + const executionData = log.executionData as { workflowInput?: unknown } | undefined + const raw = executionData?.workflowInput + if (raw === undefined || raw === null) return null + if (typeof raw === 'object' && !Array.isArray(raw)) { + return raw as Record + } + return { input: raw } as Record + }, [log.executionData]) + + const formattedTimestamp = formatDate(log.createdAt) + const logStatus = getDisplayStatus(log.status) + + return ( + <> + { + const tab = v as LogDetailsTab + setActiveTab(tab) + onActiveTabChange?.(tab) + }} + className='mt-4 flex min-h-0 flex-1 flex-col' + > + + Overview + {showTraceTab && Trace} + + + {/* Overview Tab */} + +
+ {/* Timestamp + Workflow header */} +
+
+ + Timestamp + + + {formattedTimestamp + ? `${formattedTimestamp.compactDate} ${formattedTimestamp.compactTime}` + : '—'} + +
+
+ + {log.trigger === 'mothership' ? 'Job' : 'Workflow'} + +
+ {(() => { + const c = + log.trigger === 'mothership' + ? '#ec4899' + : log.workflow?.color || + (!log.workflowId ? DELETED_WORKFLOW_COLOR : undefined) + return ( +
+ ) + })()} + + {log.trigger === 'mothership' + ? log.jobTitle || 'Untitled Job' + : log.workflow?.name || + (!log.workflowId ? DELETED_WORKFLOW_LABEL : 'Unknown')} + +
+
+
+ + {/* Details Section */} +
+ {/* Run ID — click to copy */} + {log.executionId && ( +
{ + navigator.clipboard.writeText(log.executionId!) + if (copiedRunIdTimerRef.current) clearTimeout(copiedRunIdTimerRef.current) + setCopiedRunId(true) + copiedRunIdTimerRef.current = window.setTimeout( + () => setCopiedRunId(false), + 1500 + ) + }} + > + + Run ID + + + {copiedRunId ? 'Copied!' : log.executionId} + +
+ )} + + {/* Level */} +
+ Level + +
+ + {/* Trigger */} +
+ + Trigger + + {log.trigger ? ( + + ) : ( + — + )} +
+ + {/* Duration */} +
+ + Duration + + + {formatDuration(log.duration, { precision: 2 }) || '—'} + +
+ + {/* Version */} + {log.deploymentVersion && ( +
+ + Version + +
+ + {log.deploymentVersionName || `v${log.deploymentVersion}`} + +
+
+ )} + + {/* Snapshot */} + {showWorkflowState && ( +
+ + Snapshot + + +
+ )} +
+ + {/* Workflow Input */} + {isWorkflowExecutionLog && workflowInput && !permissionConfig.hideTraceSpans && ( +
+ + Workflow Input + + +
+ )} + + {/* Workflow Output */} + {isWorkflowExecutionLog && workflowOutput && !permissionConfig.hideTraceSpans && ( +
+ + Workflow Output + + +
+ )} + + {/* Files */} + {log.files && log.files.length > 0 && } + + {/* Cost Breakdown */} + {hasCostInfo && ( +
+
+ + Base Run + + + {formatCost(BASE_EXECUTION_CHARGE)} + +
+
+ + Model Input + + + {formatCost(log.cost?.input || 0)} + +
+
+ + Model Output + + + {formatCost(log.cost?.output || 0)} + +
+ {(() => { + const models = (log.cost as Record)?.models as + | Record + | undefined + const totalToolCost = models + ? Object.values(models).reduce((sum, m) => sum + (m?.toolCost || 0), 0) + : 0 + return totalToolCost > 0 ? ( +
+ + Tool Usage + + + {formatCost(totalToolCost)} + +
+ ) : null + })()} +
+ + Total + + + {formatCost(log.cost?.total || 0)} + +
+
+ + Tokens + + + {log.cost?.tokens?.input || log.cost?.tokens?.prompt || 0} in ·{' '} + {log.cost?.tokens?.output || log.cost?.tokens?.completion || 0} out + +
+
+

+ Total includes a {formatCost(BASE_EXECUTION_CHARGE)} base charge plus model and + tool usage. +

+
+
+ )} +
+ + + {/* Trace Tab */} + {showTraceTab && log.executionData?.traceSpans && ( + + + + )} + + + {/* Frozen Canvas Modal */} + {log.executionId && ( + setIsExecutionSnapshotOpen(false)} + /> + )} + + ) +} + interface LogDetailsProps { /** The log to display details for */ log: WorkflowLog | null @@ -279,12 +643,9 @@ interface LogDetailsProps { /** * Sidebar panel displaying detailed information about a selected log. - * Supports navigation between logs and expandable sections. - * @param props - Component props - * @returns Log details sidebar component + * Wraps `LogDetailsContent` with sidebar chrome — resize handle, header, and + * keyboard navigation between logs. */ -type LogDetailsTab = 'overview' | 'trace' - export const LogDetails = memo(function LogDetails({ log, isOpen, @@ -297,76 +658,14 @@ export const LogDetails = memo(function LogDetails({ isRetryPending = false, onActiveTabChange, }: LogDetailsProps) { - const [isExecutionSnapshotOpen, setIsExecutionSnapshotOpen] = useState(false) - const [activeTab, setActiveTab] = useState('overview') - const [prevLogId, setPrevLogId] = useState(log?.id) - const [copiedRunId, setCopiedRunId] = useState(false) + const activeTabRef = useRef('overview') - if (prevLogId !== log?.id) { - setPrevLogId(log?.id) - setActiveTab('overview') - } - const copiedRunIdTimerRef = useRef(null) - const scrollAreaRef = useRef(null) - - useEffect(() => { - return () => { - if (copiedRunIdTimerRef.current !== null) window.clearTimeout(copiedRunIdTimerRef.current) - } - }, []) const panelWidth = useLogDetailsUIStore((state) => state.panelWidth) const { handleMouseDown } = useLogDetailsResize() - const { config: permissionConfig } = usePermissionConfig() const maxVw = `${MAX_LOG_DETAILS_WIDTH_RATIO * 100}vw` const effectiveWidth = `clamp(${MIN_LOG_DETAILS_WIDTH}px, ${panelWidth}px, ${maxVw})` - useEffect(() => { - if (scrollAreaRef.current) { - scrollAreaRef.current.scrollTop = 0 - } - }, [log?.id]) - - const isWorkflowExecutionLog = - !!log && - ((log.trigger === 'manual' && !!log.duration) || - !!(log.executionData?.enhanced && log.executionData?.traceSpans)) - - const hasCostInfo = !!(isWorkflowExecutionLog && log?.cost) - const showWorkflowState = - isWorkflowExecutionLog && - !!log?.executionId && - log?.trigger !== 'mothership' && - !permissionConfig.hideTraceSpans - const showTraceTab = - isWorkflowExecutionLog && !!log?.executionData?.traceSpans && !permissionConfig.hideTraceSpans - - const resolvedTab: LogDetailsTab = activeTab === 'trace' && !showTraceTab ? 'overview' : activeTab - - const prevResolvedTabRef = useRef(resolvedTab) - if (prevResolvedTabRef.current !== resolvedTab) { - prevResolvedTabRef.current = resolvedTab - if (resolvedTab !== activeTab) onActiveTabChange?.(resolvedTab) - } - - const workflowOutput = useMemo(() => { - const executionData = log?.executionData as - | { finalOutput?: Record } - | undefined - if (!executionData?.finalOutput) return null - return filterHiddenOutputKeys(executionData.finalOutput) as Record - }, [log?.executionData]) - - const workflowInput = useMemo(() => { - const executionData = log?.executionData as { workflowInput?: unknown } | undefined - const raw = executionData?.workflowInput - if (raw === undefined || raw === null) return null - if (typeof raw === 'object' && !Array.isArray(raw)) { - return raw as Record - } - return { input: raw } as Record - }, [log?.executionData]) - useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen) { @@ -377,7 +676,7 @@ export const LogDetails = memo(function LogDetails({ // When the Trace tab is active, arrow keys belong to TraceView's own // span-navigation handler. Log-to-log navigation should not hijack them. - if (resolvedTab === 'trace') return + if (activeTabRef.current === 'trace') return if (e.key === 'ArrowUp' && hasPrev && onNavigatePrev) { e.preventDefault() @@ -392,11 +691,7 @@ export const LogDetails = memo(function LogDetails({ window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) - }, [isOpen, onClose, hasPrev, hasNext, onNavigatePrev, onNavigateNext, resolvedTab]) - - const formattedTimestamp = log ? formatDate(log.createdAt) : null - - const logStatus = getDisplayStatus(log?.status) + }, [isOpen, onClose, hasPrev, hasNext, onNavigatePrev, onNavigateNext]) return ( <> @@ -426,9 +721,9 @@ export const LogDetails = memo(function LogDetails({

Log Details

- {log?.status === 'failed' && - (log?.workflow?.id || log?.workflowId) && - log?.trigger !== 'mothership' && ( + {log.status === 'failed' && + (log.workflow?.id || log.workflowId) && + log.trigger !== 'mothership' && (
- {/* Tabs */} - { - const tab = v as LogDetailsTab - setActiveTab(tab) + { + activeTabRef.current = tab onActiveTabChange?.(tab) }} - className='mt-4 flex min-h-0 flex-1 flex-col' - > - - Overview - {showTraceTab && Trace} - - - {/* Overview Tab */} - -
- {/* Timestamp + Workflow header */} -
-
- - Timestamp - - - {formattedTimestamp - ? `${formattedTimestamp.compactDate} ${formattedTimestamp.compactTime}` - : '—'} - -
-
- - {log.trigger === 'mothership' ? 'Job' : 'Workflow'} - -
- {(() => { - const c = - log.trigger === 'mothership' - ? '#ec4899' - : log.workflow?.color || - (!log.workflowId ? DELETED_WORKFLOW_COLOR : undefined) - return ( -
- ) - })()} - - {log.trigger === 'mothership' - ? log.jobTitle || 'Untitled Job' - : log.workflow?.name || - (!log.workflowId ? DELETED_WORKFLOW_LABEL : 'Unknown')} - -
-
-
- - {/* Details Section */} -
- {/* Run ID — click to copy */} - {log.executionId && ( -
{ - navigator.clipboard.writeText(log.executionId!) - if (copiedRunIdTimerRef.current) clearTimeout(copiedRunIdTimerRef.current) - setCopiedRunId(true) - copiedRunIdTimerRef.current = window.setTimeout( - () => setCopiedRunId(false), - 1500 - ) - }} - > - - Run ID - - - {copiedRunId ? 'Copied!' : log.executionId} - -
- )} - - {/* Level */} -
- - Level - - -
- - {/* Trigger */} -
- - Trigger - - {log.trigger ? ( - - ) : ( - - — - - )} -
- - {/* Duration */} -
- - Duration - - - {formatDuration(log.duration, { precision: 2 }) || '—'} - -
- - {/* Version */} - {log.deploymentVersion && ( -
- - Version - -
- - {log.deploymentVersionName || `v${log.deploymentVersion}`} - -
-
- )} - - {/* Snapshot */} - {showWorkflowState && ( -
- - Snapshot - - -
- )} -
- - {/* Workflow Input */} - {isWorkflowExecutionLog && workflowInput && !permissionConfig.hideTraceSpans && ( -
- - Workflow Input - - -
- )} - - {/* Workflow Output */} - {isWorkflowExecutionLog && workflowOutput && !permissionConfig.hideTraceSpans && ( -
- - Workflow Output - - -
- )} - - {/* Files */} - {log.files && log.files.length > 0 && ( - - )} - - {/* Cost Breakdown */} - {hasCostInfo && ( -
-
- - Base Run - - - {formatCost(BASE_EXECUTION_CHARGE)} - -
-
- - Model Input - - - {formatCost(log.cost?.input || 0)} - -
-
- - Model Output - - - {formatCost(log.cost?.output || 0)} - -
- {(() => { - const models = (log.cost as Record)?.models as - | Record - | undefined - const totalToolCost = models - ? Object.values(models).reduce((sum, m) => sum + (m?.toolCost || 0), 0) - : 0 - return totalToolCost > 0 ? ( -
- - Tool Usage - - - {formatCost(totalToolCost)} - -
- ) : null - })()} -
- - Total - - - {formatCost(log.cost?.total || 0)} - -
-
- - Tokens - - - {log.cost?.tokens?.input || log.cost?.tokens?.prompt || 0} in ·{' '} - {log.cost?.tokens?.output || log.cost?.tokens?.completion || 0} out - -
-
-

- Total includes a {formatCost(BASE_EXECUTION_CHARGE)} base charge plus - model and tool usage. -

-
-
- )} -
- - - {/* Trace Tab */} - {showTraceTab && log.executionData?.traceSpans && ( - - - - )} - + />
)} - - {/* Frozen Canvas Modal */} - {log?.executionId && ( - setIsExecutionSnapshotOpen(false)} - /> - )}
)