@@ -34,6 +34,86 @@ function getSandboxUrl() {
3434 return sessionMetadata ?. sandboxUrl ;
3535}
3636
37+ const Placeholder : React . FC < { renderBrowserShell : boolean ; className ?: string } > = React . memo (
38+ ( { renderBrowserShell, className } ) => {
39+ const placeholderClassName = renderBrowserShell
40+ ? 'flex items-center justify-center bg-gray-50 dark:bg-gray-900 min-h-[400px]'
41+ : 'flex items-center justify-center bg-gray-50 dark:bg-gray-900 min-h-[400px] rounded-xl' ;
42+
43+ return (
44+ < div className = { `${ placeholderClassName } ${ className || '' } ` } >
45+ < div className = "text-center" >
46+ < div className = "text-gray-400 dark:text-gray-500 text-sm" >
47+ GUI Agent Environment Not Started
48+ </ div >
49+ </ div >
50+ </ div >
51+ ) ;
52+ } ,
53+ ) ;
54+
55+ const VncViewer : React . FC < { renderBrowserShell : boolean } > = React . memo (
56+ ( { renderBrowserShell } ) => {
57+ const vncContainerRef = useRef < HTMLDivElement > ( null ) ;
58+ const [ scale , setScale ] = useState ( 0.5 ) ;
59+
60+ useEffect ( ( ) => {
61+ const container = vncContainerRef . current ;
62+ if ( ! container ) return ;
63+
64+ const updateScale = ( ) => {
65+ const containerWidth = container . offsetWidth ;
66+ if ( containerWidth && VNC_WIDTH ) {
67+ const newScale = containerWidth / VNC_WIDTH ;
68+ setScale ( newScale > 1 ? 1 : newScale ) ;
69+ }
70+ } ;
71+ updateScale ( ) ;
72+
73+ const resizeObserver = new window . ResizeObserver ( ( ) => {
74+ updateScale ( ) ;
75+ } ) ;
76+ resizeObserver . observe ( container ) ;
77+
78+ return ( ) => {
79+ resizeObserver . disconnect ( ) ;
80+ } ;
81+ } , [ ] ) ;
82+
83+ const scaledWidth = VNC_WIDTH * scale ;
84+ const scaledHeight = VNC_HEIGHT * scale ;
85+
86+ const sandboxBaseUrl = getSandboxUrl ( ) ;
87+
88+ if ( ! sandboxBaseUrl ) {
89+ return < Placeholder renderBrowserShell = { renderBrowserShell } /> ;
90+ }
91+
92+ return (
93+ < div className = "space-y-4" ref = { vncContainerRef } >
94+ < div
95+ style = { {
96+ width : `${ scaledWidth } px` ,
97+ height : `${ scaledHeight } px` ,
98+ } }
99+ >
100+ < iframe
101+ className = "w-full"
102+ src = { `${ sandboxBaseUrl + '/vnc/index.html?autoconnect=true' } ` }
103+ frameBorder = "0"
104+ style = { {
105+ width : `${ VNC_WIDTH } px` ,
106+ height : `${ VNC_HEIGHT } px` ,
107+ transform : `scale(${ scale } )` ,
108+ transformOrigin : 'top left' ,
109+ } }
110+ > </ iframe >
111+ </ div >
112+ </ div >
113+ ) ;
114+ } ,
115+ ) ;
116+
37117export const ScreenshotDisplay : React . FC < ScreenshotDisplayProps > = ( {
38118 strategy,
39119 relatedImage,
@@ -49,32 +129,6 @@ export const ScreenshotDisplay: React.FC<ScreenshotDisplayProps> = ({
49129 renderBrowserShell = true ,
50130} ) => {
51131 const imageRef = useRef < HTMLImageElement > ( null ) ;
52- const vncContainerRef = useRef < HTMLDivElement > ( null ) ;
53- const [ scale , setScale ] = useState ( 0.5 ) ;
54-
55- useEffect ( ( ) => {
56- if ( strategy !== 'vnc' ) return ;
57- const container = vncContainerRef . current ;
58- if ( ! container ) return ;
59-
60- const updateScale = ( ) => {
61- const containerWidth = container . offsetWidth ;
62- if ( containerWidth && VNC_WIDTH ) {
63- const newScale = containerWidth / VNC_WIDTH ;
64- setScale ( newScale > 1 ? 1 : newScale ) ;
65- }
66- } ;
67- updateScale ( ) ;
68-
69- const resizeObserver = new window . ResizeObserver ( ( ) => {
70- updateScale ( ) ;
71- } ) ;
72- resizeObserver . observe ( container ) ;
73-
74- return ( ) => {
75- resizeObserver . disconnect ( ) ;
76- } ;
77- } , [ strategy ] ) ;
78132
79133 const shouldShowMouseCursor = ( imageType : 'before' | 'after' | 'single' ) => {
80134 if ( ! mousePosition || ! showCoordinates ) return false ;
@@ -93,22 +147,6 @@ export const ScreenshotDisplay: React.FC<ScreenshotDisplayProps> = ({
93147 return content ;
94148 } ;
95149
96- const renderPlaceholder = ( ) => {
97- const placeholderClassName = renderBrowserShell
98- ? 'flex items-center justify-center bg-gray-50 dark:bg-gray-900 min-h-[400px]'
99- : 'flex items-center justify-center bg-gray-50 dark:bg-gray-900 min-h-[400px] rounded-xl' ;
100-
101- return (
102- < div className = { placeholderClassName } >
103- < div className = "text-center" >
104- < div className = "text-gray-400 dark:text-gray-500 text-sm" >
105- GUI Agent Environment Not Started
106- </ div >
107- </ div >
108- </ div >
109- ) ;
110- } ;
111-
112150 const renderImageContent = (
113151 image : string | null ,
114152 alt : string ,
@@ -156,7 +194,7 @@ export const ScreenshotDisplay: React.FC<ScreenshotDisplayProps> = ({
156194 ) ;
157195 }
158196
159- return renderPlaceholder ( ) ;
197+ return < Placeholder renderBrowserShell = { renderBrowserShell } /> ;
160198 } ;
161199
162200 if ( strategy === 'both' ) {
@@ -201,37 +239,7 @@ export const ScreenshotDisplay: React.FC<ScreenshotDisplayProps> = ({
201239 }
202240
203241 if ( strategy === 'vnc' ) {
204- const scaledWidth = VNC_WIDTH * scale ;
205- const scaledHeight = VNC_HEIGHT * scale ;
206-
207- const sandboxBaseUrl = getSandboxUrl ( ) ;
208-
209- if ( ! sandboxBaseUrl ) {
210- return renderPlaceholder ( ) ;
211- }
212-
213- return (
214- < div className = "space-y-4" ref = { vncContainerRef } >
215- < div
216- style = { {
217- width : `${ scaledWidth } px` ,
218- height : `${ scaledHeight } px` ,
219- } }
220- >
221- < iframe
222- className = "w-full"
223- src = { `${ sandboxBaseUrl + '/vnc/index.html?autoconnect=true' } ` }
224- frameBorder = "0"
225- style = { {
226- width : `${ VNC_WIDTH } px` ,
227- height : `${ VNC_HEIGHT } px` ,
228- transform : `scale(${ scale } )` ,
229- transformOrigin : 'top left' ,
230- } }
231- > </ iframe >
232- </ div >
233- </ div >
234- ) ;
242+ return < VncViewer renderBrowserShell = { renderBrowserShell } /> ;
235243 }
236244
237245 return wrapWithBrowserShell (
0 commit comments