@@ -5,6 +5,68 @@ import { MetadataSection } from "./MetadataSection";
55import StatusIndicator from "./StatusIndicator" ;
66import { state } from "../App" ;
77import { TableCell , TableRowInteractive } from "./TableContainer" ;
8+ import { useState } from "react" ;
9+
10+ // Copy button component
11+ const CopyButton = observer ( ( { text } : { text : string } ) => {
12+ const [ copied , setCopied ] = useState ( false ) ;
13+
14+ const handleClick = async ( e : React . MouseEvent ) => {
15+ e . stopPropagation ( ) ; // Prevent row expansion
16+ try {
17+ await navigator . clipboard . writeText ( text ) ;
18+ setCopied ( true ) ;
19+ // Reset to "Copy" state after 2 seconds
20+ setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
21+ } catch ( err ) {
22+ console . error ( "Failed to copy text: " , err ) ;
23+ }
24+ } ;
25+
26+ return (
27+ < button
28+ className = "p-1 text-gray-400 hover:text-gray-600 transition-colors relative group cursor-pointer"
29+ onClick = { handleClick }
30+ title = "Copy to clipboard"
31+ >
32+ { /* Tooltip */ }
33+ < div className = "absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 text-xs text-white bg-gray-800 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap" >
34+ { copied ? "Copied!" : "Copy" }
35+ </ div >
36+
37+ { /* Icon */ }
38+ { copied ? (
39+ < svg
40+ className = "w-3 h-3 text-green-600"
41+ fill = "none"
42+ stroke = "currentColor"
43+ viewBox = "0 0 24 24"
44+ >
45+ < path
46+ strokeLinecap = "round"
47+ strokeLinejoin = "round"
48+ strokeWidth = { 2 }
49+ d = "M5 13l4 4L19 7"
50+ />
51+ </ svg >
52+ ) : (
53+ < svg
54+ className = "w-3 h-3"
55+ fill = "none"
56+ stroke = "currentColor"
57+ viewBox = "0 0 24 24"
58+ >
59+ < path
60+ strokeLinecap = "round"
61+ strokeLinejoin = "round"
62+ strokeWidth = { 2 }
63+ d = "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
64+ />
65+ </ svg >
66+ ) }
67+ </ button >
68+ ) ;
69+ } ) ;
870
971// Small, focused components following "dereference values late" principle
1072const ExpandIcon = observer ( ( { rolloutId } : { rolloutId ?: string } ) => {
@@ -59,11 +121,24 @@ const RolloutId = observer(
59121 return (
60122 < span className = "font-mono text-gray-900 whitespace-nowrap" >
61123 { rolloutId }
124+ < CopyButton text = { rolloutId } />
62125 </ span >
63126 ) ;
64127 }
65128) ;
66129
130+ const InvocationId = observer ( ( { invocationId } : { invocationId ?: string } ) => {
131+ if ( ! invocationId ) {
132+ return null ;
133+ }
134+ return (
135+ < span className = "font-mono text-gray-900 whitespace-nowrap" >
136+ { invocationId }
137+ < CopyButton text = { invocationId } />
138+ </ span >
139+ ) ;
140+ } ) ;
141+
67142const RowModel = observer ( ( { model } : { model : string | undefined } ) => (
68143 < span className = "text-gray-900 truncate block" > { model || "N/A" } </ span >
69144) ) ;
@@ -224,6 +299,13 @@ export const EvaluationRow = observer(
224299 />
225300 </ TableCell >
226301
302+ { /* Invocation ID */ }
303+ < TableCell className = "py-3 text-xs" >
304+ < InvocationId
305+ invocationId = { row . execution_metadata ?. invocation_id }
306+ />
307+ </ TableCell >
308+
227309 { /* Rollout ID */ }
228310 < TableCell className = "py-3 text-xs" >
229311 < RolloutId rolloutId = { row . execution_metadata ?. rollout_id } />
@@ -248,7 +330,7 @@ export const EvaluationRow = observer(
248330 { /* Expanded Content Row */ }
249331 { isExpanded && (
250332 < tr >
251- < td colSpan = { 8 } className = "p-0" >
333+ < td colSpan = { 9 } className = "p-0" >
252334 < ExpandedContent
253335 row = { row }
254336 messages = { row . messages }
0 commit comments