+ {/* Timestamp & Workflow Row */}
+
+ {/* Timestamp Card */}
+
- Workflow
+ Timestamp
-
-
+
- {log.workflow.name}
+ {formattedTimestamp?.compactDate || 'N/A'}
+
+
+ {formattedTimestamp?.compactTime || 'N/A'}
- )}
-
- {/* Execution ID */}
- {log.executionId && (
-
-
- Execution ID
-
-
- {log.executionId}
-
+ {/* Workflow Card */}
+ {log.workflow && (
+
+
+ Workflow
+
+
+
+
+ {log.workflow.name}
+
+
+
+ )}
- )}
- {/* Details Section */}
-
- {/* Level */}
-
- Level
-
-
+ {/* Execution ID */}
+ {log.executionId && (
+
+
+ Execution ID
+
+
+ {log.executionId}
+
+
+ )}
- {/* Trigger */}
-
-
- Trigger
-
- {log.trigger ? (
-
- ) : (
- —
- )}
-
+ {/* Details Section */}
+
+ {/* Level */}
+
+
+ Level
+
+
+
- {/* Duration */}
-
-
- Duration
-
-
- {log.duration || '—'}
-
-
-
+ {/* Trigger */}
+
+
+ Trigger
+
+ {log.trigger ? (
+
+ ) : (
+
+ —
+
+ )}
+
- {/* Workflow State */}
- {isWorkflowExecutionLog && log.executionId && (
-
-
- Workflow State
-
-
- )}
- {/* Workflow Execution - Trace Spans */}
- {isWorkflowExecutionLog && log.executionData?.traceSpans && (
-
- )}
+ {/* Workflow State */}
+ {isWorkflowExecutionLog && log.executionId && (
+
+
+ Workflow State
+
+
+
+ )}
- {/* Files */}
- {log.files && log.files.length > 0 &&
}
+ {/* Workflow Execution - Trace Spans */}
+ {isWorkflowExecutionLog && log.executionData?.traceSpans && (
+
+ )}
- {/* Cost Breakdown */}
- {hasCostInfo && (
-
-
- Cost Breakdown
-
+ {/* Files */}
+ {log.files && log.files.length > 0 && (
+
+ )}
-
-
-
-
- Base Execution:
-
-
- {formatCost(BASE_EXECUTION_CHARGE)}
-
-
-
-
- Model Input:
-
-
- {formatCost(log.cost?.input || 0)}
-
-
-
-
- Model Output:
-
-
- {formatCost(log.cost?.output || 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)}
+
+
-
-
+
-
-
-
- Total:
-
-
- {formatCost(log.cost?.total || 0)}
-
-
-
-
- Tokens:
-
-
- {log.cost?.tokens?.prompt || 0} in / {log.cost?.tokens?.completion || 0}{' '}
- out
-
+
+
+
+ Total:
+
+
+ {formatCost(log.cost?.total || 0)}
+
+
+
+
+ Tokens:
+
+
+ {log.cost?.tokens?.prompt || 0} in / {log.cost?.tokens?.completion || 0}{' '}
+ out
+
+
-
-
-
- Total cost includes a base execution charge of{' '}
- {formatCost(BASE_EXECUTION_CHARGE)} plus any model usage costs.
-
+
+
+ Total cost includes a base execution charge of{' '}
+ {formatCost(BASE_EXECUTION_CHARGE)} plus any model usage costs.
+
+
-
- )}
-
-
-
- )}
+ )}
+
+
+
+ )}
- {/* Frozen Canvas Modal */}
- {log?.executionId && (
-
setIsFrozenCanvasOpen(false)}
- />
- )}
-
+ {/* Frozen Canvas Modal */}
+ {log?.executionId && (
+
setIsFrozenCanvasOpen(false)}
+ />
+ )}
+
+ >
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/hooks/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/hooks/index.ts
new file mode 100644
index 00000000000..934fac5db7c
--- /dev/null
+++ b/apps/sim/app/workspace/[workspaceId]/logs/hooks/index.ts
@@ -0,0 +1,2 @@
+export { useLogDetailsResize } from './use-log-details-resize'
+export { useSearchState } from './use-search-state'
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-log-details-resize.ts b/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-log-details-resize.ts
new file mode 100644
index 00000000000..a05cd83c444
--- /dev/null
+++ b/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-log-details-resize.ts
@@ -0,0 +1,62 @@
+import { useCallback, useEffect, useState } from 'react'
+import {
+ MAX_LOG_DETAILS_WIDTH,
+ MIN_LOG_DETAILS_WIDTH,
+ useLogDetailsUIStore,
+} from '@/stores/logs/store'
+
+/**
+ * Hook for handling log details panel resize via mouse drag.
+ * @returns Resize state and mouse event handler.
+ */
+export function useLogDetailsResize() {
+ const setPanelWidth = useLogDetailsUIStore((state) => state.setPanelWidth)
+ const setIsResizing = useLogDetailsUIStore((state) => state.setIsResizing)
+ const [isResizing, setLocalIsResizing] = useState(false)
+
+ const handleMouseDown = useCallback(
+ (e: React.MouseEvent) => {
+ e.preventDefault()
+ setLocalIsResizing(true)
+ setIsResizing(true)
+ },
+ [setIsResizing]
+ )
+
+ useEffect(() => {
+ if (!isResizing) return
+
+ const handleMouseMove = (e: MouseEvent) => {
+ // Calculate new width from right edge of window
+ const newWidth = window.innerWidth - e.clientX
+ const clampedWidth = Math.max(
+ MIN_LOG_DETAILS_WIDTH,
+ Math.min(newWidth, MAX_LOG_DETAILS_WIDTH)
+ )
+
+ setPanelWidth(clampedWidth)
+ }
+
+ const handleMouseUp = () => {
+ setLocalIsResizing(false)
+ setIsResizing(false)
+ }
+
+ document.addEventListener('mousemove', handleMouseMove)
+ document.addEventListener('mouseup', handleMouseUp)
+ document.body.style.cursor = 'ew-resize'
+ document.body.style.userSelect = 'none'
+
+ return () => {
+ document.removeEventListener('mousemove', handleMouseMove)
+ document.removeEventListener('mouseup', handleMouseUp)
+ document.body.style.cursor = ''
+ document.body.style.userSelect = ''
+ }
+ }, [isResizing, setPanelWidth, setIsResizing])
+
+ return {
+ isResizing,
+ handleMouseDown,
+ }
+}
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
index 86ba275832f..6f8ffdea212 100644
--- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
@@ -107,6 +107,33 @@ export default function Logs() {
}
}, [debouncedSearchQuery, setStoreSearchQuery])
+ // Sync selected log with refreshed data from logs list
+ useEffect(() => {
+ if (!selectedLog?.id || logs.length === 0) return
+
+ const updatedLog = logs.find((l) => l.id === selectedLog.id)
+ if (updatedLog) {
+ // Update selectedLog with fresh data from the list
+ setSelectedLog(updatedLog)
+ // Update index in case position changed
+ const newIndex = logs.findIndex((l) => l.id === selectedLog.id)
+ if (newIndex !== selectedLogIndex) {
+ setSelectedLogIndex(newIndex)
+ }
+ }
+ }, [logs, selectedLog?.id, selectedLogIndex])
+
+ // Refetch log details during live mode
+ useEffect(() => {
+ if (!isLive || !selectedLog?.id) return
+
+ const interval = setInterval(() => {
+ logDetailQuery.refetch()
+ }, 5000)
+
+ return () => clearInterval(interval)
+ }, [isLive, selectedLog?.id, logDetailQuery])
+
const handleLogClick = (log: WorkflowLog) => {
// If clicking on the same log that's already selected and sidebar is open, close it
if (selectedLog?.id === log.id && isSidebarOpen) {
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx
index 382974d1988..5c5a660341d 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx
@@ -836,8 +836,7 @@ try {
<>
{
if (e.key === 'Escape' && (showEnvVars || showTags || showSchemaParams)) {
e.preventDefault()
diff --git a/apps/sim/stores/logs/store.ts b/apps/sim/stores/logs/store.ts
new file mode 100644
index 00000000000..3fa3514396b
--- /dev/null
+++ b/apps/sim/stores/logs/store.ts
@@ -0,0 +1,46 @@
+import { create } from 'zustand'
+import { persist } from 'zustand/middleware'
+
+/**
+ * Width constraints for the log details panel.
+ */
+export const MIN_LOG_DETAILS_WIDTH = 340
+export const MAX_LOG_DETAILS_WIDTH = 700
+export const DEFAULT_LOG_DETAILS_WIDTH = 340
+
+/**
+ * Log details UI state persisted across sessions.
+ */
+interface LogDetailsUIState {
+ panelWidth: number
+ setPanelWidth: (width: number) => void
+ isResizing: boolean
+ setIsResizing: (isResizing: boolean) => void
+}
+
+export const useLogDetailsUIStore = create()(
+ persist(
+ (set) => ({
+ panelWidth: DEFAULT_LOG_DETAILS_WIDTH,
+ /**
+ * Updates the log details panel width, enforcing min/max constraints.
+ * @param width - Desired width in pixels for the panel.
+ */
+ setPanelWidth: (width) => {
+ const clampedWidth = Math.max(MIN_LOG_DETAILS_WIDTH, Math.min(width, MAX_LOG_DETAILS_WIDTH))
+ set({ panelWidth: clampedWidth })
+ },
+ isResizing: false,
+ /**
+ * Updates the resize state flag.
+ * @param isResizing - True while the panel is being resized via mouse drag.
+ */
+ setIsResizing: (isResizing) => {
+ set({ isResizing })
+ },
+ }),
+ {
+ name: 'log-details-ui-state',
+ }
+ )
+)