Skip to content

Commit da09a2a

Browse files
authored
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
1 parent 38aa163 commit da09a2a

4 files changed

Lines changed: 379 additions & 619 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx

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

3-
import { lazy, memo, Suspense, useEffect, useMemo, useState } from 'react'
3+
import { lazy, memo, Suspense, useEffect, useMemo } from 'react'
44
import { createLogger } from '@sim/logger'
5-
import { formatDuration } from '@sim/utils/formatting'
65
import { Square } from 'lucide-react'
76
import { useRouter } from 'next/navigation'
8-
import { Button, Eye, PlayOutline, Skeleton, Tooltip } from '@/components/emcn'
7+
import { Button, PlayOutline, Skeleton, Tooltip } from '@/components/emcn'
98
import {
109
Download,
1110
FileX,
@@ -14,15 +13,12 @@ import {
1413
SquareArrowUpRight,
1514
WorkflowX,
1615
} from '@/components/emcn/icons'
17-
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
1816
import type { FilePreviewSession } from '@/lib/copilot/request/session'
1917
import {
2018
cancelRunToolExecution,
2119
markRunToolManuallyStopped,
2220
reportManualRunToolStop,
2321
} from '@/lib/copilot/tools/client/run-tool-execution'
24-
import { cn } from '@/lib/core/utils/cn'
25-
import { filterHiddenOutputKeys } from '@/lib/logs/execution/trace-spans/trace-spans'
2622
import {
2723
downloadWorkspaceFile,
2824
getFileExtension,
@@ -43,18 +39,7 @@ import type {
4339
MothershipResource,
4440
} from '@/app/workspace/[workspaceId]/home/types'
4541
import { KnowledgeBase } from '@/app/workspace/[workspaceId]/knowledge/[id]/base'
46-
import {
47-
ExecutionSnapshot,
48-
FileCards,
49-
TraceSpans,
50-
WorkflowOutputSection,
51-
} from '@/app/workspace/[workspaceId]/logs/components'
52-
import {
53-
formatDate,
54-
getDisplayStatus,
55-
StatusBadge,
56-
TriggerBadge,
57-
} from '@/app/workspace/[workspaceId]/logs/utils'
42+
import { LogDetailsContent } from '@/app/workspace/[workspaceId]/logs/components'
5843
import {
5944
useUserPermissionsContext,
6045
useWorkspacePermissionsContext,
@@ -68,7 +53,6 @@ import { downloadTableExport } from '@/hooks/queries/tables'
6853
import { useWorkflows } from '@/hooks/queries/workflows'
6954
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
7055
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
71-
import { formatCost } from '@/providers/utils'
7256
import { useExecutionStore } from '@/stores/execution/store'
7357
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
7458

@@ -639,26 +623,6 @@ interface EmbeddedLogProps {
639623

640624
function EmbeddedLog({ logId }: EmbeddedLogProps) {
641625
const { data: log, isLoading } = useLogDetail(logId)
642-
const [isSnapshotOpen, setIsSnapshotOpen] = useState(false)
643-
644-
const logStatus = getDisplayStatus(log?.status)
645-
646-
const workflowOutput = useMemo(() => {
647-
const executionData = log?.executionData as
648-
| { finalOutput?: Record<string, unknown> }
649-
| undefined
650-
if (!executionData?.finalOutput) return null
651-
return filterHiddenOutputKeys(executionData.finalOutput) as Record<string, unknown>
652-
}, [log?.executionData])
653-
654-
const isWorkflowExecutionLog =
655-
!!log &&
656-
((log.trigger === 'manual' && !!log.duration) ||
657-
!!(log.executionData?.enhanced && log.executionData?.traceSpans))
658-
659-
const hasCostInfo = isWorkflowExecutionLog && log?.cost
660-
661-
const formattedTimestamp = log ? formatDate(log.createdAt) : null
662626

663627
if (isLoading) return LOADING_SKELETON
664628

@@ -676,232 +640,9 @@ function EmbeddedLog({ logId }: EmbeddedLogProps) {
676640
)
677641
}
678642

679-
const workflowColor =
680-
log.trigger === 'mothership'
681-
? '#ec4899'
682-
: log.workflow?.color || (!log.workflowId ? 'var(--text-tertiary)' : undefined)
683-
684-
const totalToolCost = (() => {
685-
const models = (log.cost as Record<string, unknown>)?.models as
686-
| Record<string, { toolCost?: number }>
687-
| undefined
688-
return models ? Object.values(models).reduce((sum, m) => sum + (m?.toolCost || 0), 0) : 0
689-
})()
690-
691643
return (
692-
<div className='flex h-full flex-col overflow-y-auto'>
693-
<div className='flex flex-col gap-2.5 p-4 pb-6'>
694-
{/* Timestamp & Workflow Row */}
695-
<div className='flex min-w-0 items-center gap-4 px-[1px]'>
696-
<div className='flex w-[140px] flex-shrink-0 flex-col gap-2'>
697-
<div className='font-medium text-[var(--text-tertiary)] text-caption'>Timestamp</div>
698-
<div className='flex items-center gap-1.5'>
699-
<span className='font-medium text-[var(--text-secondary)] text-sm'>
700-
{formattedTimestamp?.compactDate || 'N/A'}
701-
</span>
702-
<span className='font-medium text-[var(--text-secondary)] text-sm'>
703-
{formattedTimestamp?.compactTime || 'N/A'}
704-
</span>
705-
</div>
706-
</div>
707-
<div className='flex w-0 min-w-0 flex-1 flex-col gap-2'>
708-
<div className='font-medium text-[var(--text-tertiary)] text-caption'>
709-
{log.trigger === 'mothership' ? 'Job' : 'Workflow'}
710-
</div>
711-
<div className='flex min-w-0 items-center gap-2'>
712-
<div
713-
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px] border-[1.5px]'
714-
style={{
715-
backgroundColor: workflowColor,
716-
borderColor: workflowColor ? workflowBorderColor(workflowColor) : undefined,
717-
backgroundClip: 'padding-box',
718-
}}
719-
/>
720-
<span className='min-w-0 flex-1 truncate font-medium text-[var(--text-secondary)] text-sm'>
721-
{log.trigger === 'mothership'
722-
? log.jobTitle || 'Untitled Job'
723-
: log.workflow?.name || (!log.workflowId ? 'Deleted Workflow' : 'Unknown')}
724-
</span>
725-
</div>
726-
</div>
727-
</div>
728-
729-
{/* Run ID */}
730-
{log.executionId && (
731-
<div className='flex flex-col gap-1.5 rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2.5 py-2'>
732-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>Run ID</span>
733-
<span className='truncate font-medium text-[var(--text-secondary)] text-sm'>
734-
{log.executionId}
735-
</span>
736-
</div>
737-
)}
738-
739-
{/* Details Section */}
740-
<div className='-my-1 flex min-w-0 flex-col overflow-hidden'>
741-
<div className='flex h-[48px] items-center justify-between border-[var(--border)] border-b p-2'>
742-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>Level</span>
743-
<StatusBadge status={logStatus} />
744-
</div>
745-
<div className='flex h-[48px] items-center justify-between border-[var(--border)] border-b p-2'>
746-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>Trigger</span>
747-
{log.trigger ? (
748-
<TriggerBadge trigger={log.trigger} />
749-
) : (
750-
<span className='font-medium text-[var(--text-secondary)] text-caption'></span>
751-
)}
752-
</div>
753-
<div
754-
className={cn(
755-
'flex h-[48px] items-center justify-between border-b p-2',
756-
log.deploymentVersion ? 'border-[var(--border)]' : 'border-transparent'
757-
)}
758-
>
759-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>Duration</span>
760-
<span className='font-medium text-[var(--text-secondary)] text-small'>
761-
{formatDuration(log.duration, { precision: 2 }) || '—'}
762-
</span>
763-
</div>
764-
{log.deploymentVersion && (
765-
<div className='flex h-[48px] items-center gap-2 p-2'>
766-
<span className='flex-shrink-0 font-medium text-[var(--text-tertiary)] text-caption'>
767-
Version
768-
</span>
769-
<div className='flex w-0 flex-1 justify-end'>
770-
<span className='max-w-full truncate rounded-md bg-[var(--badge-success-bg)] px-[9px] py-0.5 font-medium text-[var(--badge-success-text)] text-caption'>
771-
{log.deploymentVersionName || `v${log.deploymentVersion}`}
772-
</span>
773-
</div>
774-
</div>
775-
)}
776-
</div>
777-
778-
{/* Workflow State Snapshot */}
779-
{isWorkflowExecutionLog && log.executionId && log.trigger !== 'mothership' && (
780-
<div className='-mt-2 flex flex-col gap-1.5 rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2.5 py-2'>
781-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
782-
Workflow State
783-
</span>
784-
<Button
785-
variant='active'
786-
onClick={() => setIsSnapshotOpen(true)}
787-
className='flex w-full items-center justify-between px-2.5 py-1.5'
788-
>
789-
<span className='font-medium text-caption'>View Snapshot</span>
790-
<Eye className='h-[14px] w-[14px]' />
791-
</Button>
792-
</div>
793-
)}
794-
795-
{/* Workflow Output */}
796-
{isWorkflowExecutionLog && workflowOutput && (
797-
<div className='mt-1 flex flex-col gap-1.5 rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2.5 py-2 dark:bg-transparent'>
798-
<span
799-
className={cn(
800-
'font-medium text-caption',
801-
workflowOutput.error ? 'text-[var(--text-error)]' : 'text-[var(--text-tertiary)]'
802-
)}
803-
>
804-
Workflow Output
805-
</span>
806-
<WorkflowOutputSection output={workflowOutput} />
807-
</div>
808-
)}
809-
810-
{/* Trace Spans */}
811-
{isWorkflowExecutionLog && log.executionData?.traceSpans && (
812-
<div className='mt-1 flex flex-col gap-1.5 rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2.5 py-2 dark:bg-transparent'>
813-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>Trace Span</span>
814-
<TraceSpans traceSpans={log.executionData.traceSpans} />
815-
</div>
816-
)}
817-
818-
{/* Files */}
819-
{log.files && log.files.length > 0 && <FileCards files={log.files} isExecutionFile />}
820-
821-
{/* Cost Breakdown */}
822-
{hasCostInfo && (
823-
<div className='flex flex-col gap-2'>
824-
<span className='px-[1px] font-medium text-[var(--text-tertiary)] text-caption'>
825-
Cost Breakdown
826-
</span>
827-
<div className='flex flex-col gap-1 rounded-md border border-[var(--border)]'>
828-
<div className='flex flex-col gap-2.5 rounded-md p-2.5'>
829-
<div className='flex items-center justify-between'>
830-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
831-
Base Execution:
832-
</span>
833-
<span className='font-medium text-[var(--text-secondary)] text-caption'>
834-
{formatCost(BASE_EXECUTION_CHARGE)}
835-
</span>
836-
</div>
837-
<div className='flex items-center justify-between'>
838-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
839-
Model Input:
840-
</span>
841-
<span className='font-medium text-[var(--text-secondary)] text-caption'>
842-
{formatCost(log.cost?.input || 0)}
843-
</span>
844-
</div>
845-
<div className='flex items-center justify-between'>
846-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
847-
Model Output:
848-
</span>
849-
<span className='font-medium text-[var(--text-secondary)] text-caption'>
850-
{formatCost(log.cost?.output || 0)}
851-
</span>
852-
</div>
853-
{totalToolCost > 0 && (
854-
<div className='flex items-center justify-between'>
855-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
856-
Tool Usage:
857-
</span>
858-
<span className='font-medium text-[var(--text-secondary)] text-caption'>
859-
{formatCost(totalToolCost)}
860-
</span>
861-
</div>
862-
)}
863-
</div>
864-
<div className='border-[var(--border)] border-t' />
865-
<div className='flex flex-col gap-2.5 rounded-md p-2.5'>
866-
<div className='flex items-center justify-between'>
867-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
868-
Total:
869-
</span>
870-
<span className='font-medium text-[var(--text-secondary)] text-caption'>
871-
{formatCost(log.cost?.total || 0)}
872-
</span>
873-
</div>
874-
<div className='flex items-center justify-between'>
875-
<span className='font-medium text-[var(--text-tertiary)] text-caption'>
876-
Tokens:
877-
</span>
878-
<span className='font-medium text-[var(--text-secondary)] text-caption'>
879-
{log.cost?.tokens?.input || log.cost?.tokens?.prompt || 0} in /{' '}
880-
{log.cost?.tokens?.output || log.cost?.tokens?.completion || 0} out
881-
</span>
882-
</div>
883-
</div>
884-
</div>
885-
<div className='flex items-center justify-center rounded-md bg-[var(--surface-2)] p-2 text-center'>
886-
<p className='font-medium text-[var(--text-subtle)] text-xs'>
887-
Total cost includes a base execution charge of {formatCost(BASE_EXECUTION_CHARGE)}{' '}
888-
plus any model and tool usage costs.
889-
</p>
890-
</div>
891-
</div>
892-
)}
893-
</div>
894-
895-
{/* Frozen Canvas Modal */}
896-
{log.executionId && (
897-
<ExecutionSnapshot
898-
executionId={log.executionId}
899-
traceSpans={log.executionData?.traceSpans}
900-
isModal
901-
isOpen={isSnapshotOpen}
902-
onClose={() => setIsSnapshotOpen(false)}
903-
/>
904-
)}
644+
<div className='flex h-full flex-col overflow-hidden px-3.5 pt-3'>
645+
<LogDetailsContent log={log} />
905646
</div>
906647
)
907648
}

apps/sim/app/workspace/[workspaceId]/logs/components/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { Dashboard } from './dashboard'
2-
export { LogDetails, WorkflowOutputSection } from './log-details'
2+
export type { LogDetailsTab } from './log-details'
3+
export { LogDetails, LogDetailsContent, WorkflowOutputSection } from './log-details'
34
export { ExecutionSnapshot } from './log-details/components/execution-snapshot'
45
export { FileCards } from './log-details/components/file-download'
56
export { TraceSpans } from './log-details/components/trace-spans'
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { LogDetails, WorkflowOutputSection } from './log-details'
1+
export type { LogDetailsTab } from './log-details'
2+
export { LogDetails, LogDetailsContent, WorkflowOutputSection } from './log-details'

0 commit comments

Comments
 (0)