1
+ import Box , { BoxProps } from "@mui/material/Box"
2
+ import Popover from "@mui/material/Popover"
3
+ import Skeleton from "@mui/material/Skeleton"
4
+ import Tooltip from "@mui/material/Tooltip"
1
5
import makeStyles from "@mui/styles/makeStyles"
2
6
import { watchAgentMetadata } from "api/api"
3
7
import { WorkspaceAgent , WorkspaceAgentMetadata } from "api/typesGenerated"
4
8
import { Stack } from "components/Stack/Stack"
5
9
import dayjs from "dayjs"
6
10
import {
7
- createContext ,
8
11
FC ,
12
+ createContext ,
9
13
useContext ,
10
14
useEffect ,
11
15
useRef ,
12
16
useState ,
13
17
} from "react"
14
- import Skeleton from "@mui/material/Skeleton "
18
+ import { colors } from "theme/colors "
15
19
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
16
20
import { combineClasses } from "utils/combineClasses"
17
- import Tooltip from "@mui/material/Tooltip"
18
- import Box , { BoxProps } from "@mui/material/Box"
21
+ import * as XTerm from "xterm"
22
+ import { FitAddon } from "xterm-addon-fit"
23
+ import "xterm/css/xterm.css"
19
24
20
25
type ItemStatus = "stale" | "valid" | "loading"
21
26
22
27
export const WatchAgentMetadataContext = createContext ( watchAgentMetadata )
23
28
29
+ const MetadataTerminalPopover : FC < {
30
+ id : string
31
+ value : string
32
+ } > = ( { id, value : value } ) => {
33
+ const styles = useStyles ( )
34
+
35
+ const viewTermRef = useRef < HTMLDivElement > ( null )
36
+ const [ open , setOpen ] = useState ( false )
37
+
38
+ const [ xtermRef , setXtermRef ] = useState < HTMLDivElement | null > ( null )
39
+ const [ terminal , setTerminal ] = useState < XTerm . Terminal | null > ( null )
40
+ const [ fitAddon , setFitAddon ] = useState < FitAddon | null > ( null )
41
+
42
+ // Create the terminal.
43
+ // Largely taken from TerminalPage.
44
+ useEffect ( ( ) => {
45
+ if ( ! xtermRef ) {
46
+ return
47
+ }
48
+ const terminal = new XTerm . Terminal ( {
49
+ allowTransparency : true ,
50
+ disableStdin : true ,
51
+ fontFamily : MONOSPACE_FONT_FAMILY ,
52
+ fontSize : 16 ,
53
+ theme : {
54
+ background : colors . gray [ 16 ] ,
55
+ } ,
56
+ } )
57
+ console . log ( "created terminal" , terminal )
58
+
59
+ const fitAddon = new FitAddon ( )
60
+ setTerminal ( terminal )
61
+ setFitAddon ( fitAddon )
62
+ fitAddon . fit ( )
63
+ terminal . open ( xtermRef )
64
+ terminal . write ( value )
65
+
66
+ return ( ) => {
67
+ terminal . dispose ( )
68
+ }
69
+ } , [ xtermRef , open ] )
70
+
71
+ useEffect ( ( ) => {
72
+ if ( open && terminal && fitAddon ) {
73
+ fitAddon . fit ( )
74
+ }
75
+ } , [ open , terminal , fitAddon ] )
76
+
77
+ useEffect ( ( ) => {
78
+ if ( open && terminal && fitAddon ) {
79
+ setTimeout ( ( ) => fitAddon . fit ( ) , 0 )
80
+ }
81
+ } , [ open , terminal , fitAddon ] )
82
+
83
+ return (
84
+ < >
85
+ < div
86
+ className = { styles . viewTerminal }
87
+ ref = { viewTermRef }
88
+ onMouseOver = { ( ) => {
89
+ setOpen ( true )
90
+ } }
91
+ >
92
+ View Terminal
93
+ </ div >
94
+
95
+ < Popover
96
+ id = { id }
97
+ open = { open }
98
+ onClose = { ( ) => setOpen ( false ) }
99
+ anchorEl = { viewTermRef . current }
100
+ anchorOrigin = { {
101
+ vertical : "bottom" ,
102
+ horizontal : "left" ,
103
+ } }
104
+ >
105
+ < Box p = { 1 } >
106
+ < div
107
+ className = { styles . terminal }
108
+ ref = { ( el ) => {
109
+ setXtermRef ( el )
110
+ } }
111
+ data-testid = "terminal"
112
+ />
113
+ </ Box >
114
+ </ Popover >
115
+ </ >
116
+ )
117
+ }
118
+
24
119
const MetadataItem : FC < { item : WorkspaceAgentMetadata } > = ( { item } ) => {
25
120
const styles = useStyles ( )
26
121
@@ -31,6 +126,13 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
31
126
throw new Error ( "Metadata item description is undefined" )
32
127
}
33
128
129
+ const terminalPrefix = "terminal:"
130
+ const isTerminal = item . description . display_name . startsWith ( terminalPrefix )
131
+
132
+ const displayName = isTerminal
133
+ ? item . description . display_name . slice ( terminalPrefix . length )
134
+ : item . description . display_name
135
+
34
136
const staleThreshold = Math . max (
35
137
item . description . interval + item . description . timeout * 2 ,
36
138
// In case there is intense backpressure, we give a little bit of slack.
@@ -88,10 +190,15 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
88
190
89
191
return (
90
192
< div className = { styles . metadata } >
91
- < div className = { styles . metadataLabel } >
92
- { item . description . display_name }
93
- </ div >
94
- < Box > { value } </ Box >
193
+ < div className = { styles . metadataLabel } > { displayName } </ div >
194
+ { isTerminal ? (
195
+ < MetadataTerminalPopover
196
+ id = { `metadata-terminal-${ item . description . key } ` }
197
+ value = { item . result . value }
198
+ />
199
+ ) : (
200
+ < Box > { value } </ Box >
201
+ ) }
95
202
</ div >
96
203
)
97
204
}
@@ -105,6 +212,7 @@ export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
105
212
if ( metadata . length === 0 ) {
106
213
return < > </ >
107
214
}
215
+
108
216
return (
109
217
< div className = { styles . root } >
110
218
< Stack alignItems = "baseline" direction = "row" spacing = { 6 } >
@@ -228,6 +336,53 @@ const useStyles = makeStyles((theme) => ({
228
336
scrollPadding : theme . spacing ( 0 , 4 ) ,
229
337
} ,
230
338
339
+ viewTerminal : {
340
+ fontFamily : MONOSPACE_FONT_FAMILY ,
341
+ display : "inline-block" ,
342
+ textDecoration : "underline" ,
343
+ fontWeight : 600 ,
344
+ margin : 0 ,
345
+ fontSize : 14 ,
346
+ borderRadius : 4 ,
347
+ color : theme . palette . text . primary ,
348
+ } ,
349
+
350
+ terminal : {
351
+ width : "100vw" ,
352
+ height : "30vh" ,
353
+ overflow : "hidden" ,
354
+ backgroundColor : theme . palette . background . paper ,
355
+ flex : 1 ,
356
+ // These styles attempt to mimic the VS Code scrollbar.
357
+ "& .xterm" : {
358
+ padding : 4 ,
359
+ width : "100vw" ,
360
+ height : "100vh" ,
361
+ } ,
362
+ "& .xterm-viewport" : {
363
+ // This is required to force full-width on the terminal.
364
+ // Otherwise there's a small white bar to the right of the scrollbar.
365
+ width : "auto !important" ,
366
+ } ,
367
+ "& .xterm-viewport::-webkit-scrollbar" : {
368
+ width : "10px" ,
369
+ } ,
370
+ "& .xterm-viewport::-webkit-scrollbar-track" : {
371
+ backgroundColor : "inherit" ,
372
+ } ,
373
+ "& .xterm-viewport::-webkit-scrollbar-thumb" : {
374
+ minHeight : 20 ,
375
+ backgroundColor : "rgba(255, 255, 255, 0.18)" ,
376
+ } ,
377
+ } ,
378
+
379
+ popover : {
380
+ padding : 0 ,
381
+ width : theme . spacing ( 38 ) ,
382
+ color : theme . palette . text . secondary ,
383
+ marginTop : theme . spacing ( 0.5 ) ,
384
+ } ,
385
+
231
386
metadata : {
232
387
fontSize : 12 ,
233
388
lineHeight : "normal" ,
0 commit comments