@@ -4,8 +4,9 @@ import { Badge } from '@comp/ui/badge';
44import { EvidenceAutomationRun , EvidenceAutomationRunStatus } from '@db' ;
55import { Stack , Text , Button } from '@trycompai/design-system' ;
66import { formatDistanceToNow } from 'date-fns' ;
7- import { ChevronDown } from 'lucide-react' ;
8- import { useMemo , useState } from 'react' ;
7+ import { toast } from 'sonner' ;
8+ import { CheckmarkFilled , ChevronDown , CopyToClipboard } from '@trycompai/design-system/icons' ;
9+ import { useCallback , useMemo , useState } from 'react' ;
910
1011type AutomationRunWithName = EvidenceAutomationRun & {
1112 evidenceAutomation : {
@@ -32,6 +33,39 @@ const getStatusStyles = (status: EvidenceAutomationRunStatus) => {
3233 }
3334} ;
3435
36+ function CopyableCodeBlock ( { label, content } : { label : string ; content : unknown } ) {
37+ const [ copied , setCopied ] = useState ( false ) ;
38+ const text = typeof content === 'string' ? content : JSON . stringify ( content , null , 2 ) ;
39+
40+ const handleCopy = useCallback ( ( ) => {
41+ navigator . clipboard . writeText ( text ) ;
42+ setCopied ( true ) ;
43+ toast . success ( 'Copied to clipboard' ) ;
44+ setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
45+ } , [ text ] ) ;
46+
47+ return (
48+ < div >
49+ < Text size = "xs" weight = "medium" variant = "muted" > { label } </ Text >
50+ < div className = "relative mt-1" >
51+ < div className = "absolute top-1.5 left-1.5 z-10" >
52+ < Button
53+ variant = "outline"
54+ size = "icon-xs"
55+ onClick = { handleCopy }
56+ title = "Copy to clipboard"
57+ >
58+ { copied ? < CheckmarkFilled className = "!size-3 text-primary" /> : < CopyToClipboard className = "!size-3" /> }
59+ </ Button >
60+ </ div >
61+ < pre className = "text-xs bg-muted text-foreground p-2 pl-9 rounded overflow-x-auto max-h-40 overflow-y-auto font-mono select-text cursor-text" >
62+ { text }
63+ </ pre >
64+ </ div >
65+ </ div >
66+ ) ;
67+ }
68+
3569export function AutomationRunsCard ( { runs } : AutomationRunsCardProps ) {
3670 const [ expandedId , setExpandedId ] = useState < string | null > ( null ) ;
3771 const [ showAll , setShowAll ] = useState ( false ) ;
@@ -81,11 +115,13 @@ export function AutomationRunsCard({ runs }: AutomationRunsCardProps) {
81115 < div
82116 key = { run . id }
83117 className = { `rounded-lg border border-border hover:border-border/80 transition-colors ${ styles . bg } ` }
84- onClick = { ( ) => hasDetails && setExpandedId ( isExpanded ? null : run . id ) }
85- role = { hasDetails ? 'button' : undefined }
86- style = { hasDetails ? { cursor : 'pointer' } : undefined }
87118 >
88- < div className = "flex items-center gap-3 px-4 py-2.5" >
119+ < div
120+ className = "flex items-center gap-3 px-4 py-2.5"
121+ onClick = { ( ) => hasDetails && setExpandedId ( isExpanded ? null : run . id ) }
122+ role = { hasDetails ? 'button' : undefined }
123+ style = { hasDetails ? { cursor : 'pointer' } : undefined }
124+ >
89125 < div className = { `h-2 w-2 rounded-full shrink-0 ${ styles . dot } ` } />
90126
91127 < div className = "flex-1 min-w-0" >
@@ -124,33 +160,23 @@ export function AutomationRunsCard({ runs }: AutomationRunsCardProps) {
124160 </ div >
125161
126162 { hasDetails && (
127- < ChevronDown className = { `h-4 w-4 text-muted-foreground transition-transform ${ isExpanded ? 'rotate-180' : '' } ` } />
163+ < ChevronDown size = { 16 } className = { `text-muted-foreground transition-transform ${ isExpanded ? 'rotate-180' : '' } ` } />
128164 ) }
129165 </ div >
130166
131167 { isExpanded && (
132- < div className = "px-4 pb-3 pt-2 border-t space-y-2" >
168+ < div className = "px-4 pb-3 pt-2 border-t space-y-2 select-text " >
133169 { run . evaluationReason && (
134170 < div >
135171 < Text size = "xs" weight = "medium" variant = "muted" > Evaluation</ Text >
136172 < Text size = "xs" as = "p" > { run . evaluationReason } </ Text >
137173 </ div >
138174 ) }
139175 { run . logs && (
140- < div >
141- < Text size = "xs" weight = "medium" variant = "muted" > Logs</ Text >
142- < pre className = "text-xs bg-muted text-foreground p-2 rounded overflow-x-auto max-h-40 overflow-y-auto font-mono mt-1" >
143- { typeof run . logs === 'string' ? run . logs : JSON . stringify ( run . logs , null , 2 ) }
144- </ pre >
145- </ div >
176+ < CopyableCodeBlock label = "Logs" content = { run . logs } />
146177 ) }
147178 { run . output && (
148- < div >
149- < Text size = "xs" weight = "medium" variant = "muted" > Output</ Text >
150- < pre className = "text-xs bg-muted text-foreground p-2 rounded overflow-x-auto max-h-40 overflow-y-auto font-mono mt-1" >
151- { typeof run . output === 'string' ? run . output : JSON . stringify ( run . output , null , 2 ) }
152- </ pre >
153- </ div >
179+ < CopyableCodeBlock label = "Output" content = { run . output } />
154180 ) }
155181 { run . status === 'failed' && run . error && (
156182 < div className = "px-2 py-1.5 rounded bg-destructive/10 border border-destructive/20" >
0 commit comments