@@ -9,6 +9,16 @@ const DEFAULT_WIDGET_STATE = {
99 lastPlayed : null ,
1010} ;
1111
12+ type MockOpenAiApi = Partial < OpenAiGlobals > & {
13+ setWidgetState : ( state : unknown ) => Promise < void > ;
14+ callTool : ( name : string , args : Record < string , unknown > ) => Promise < { result : string } > ;
15+ sendFollowUpMessage : ( args : { prompt : string } ) => Promise < void > ;
16+ openExternal : ( payload : { href : string } ) => void ;
17+ requestDisplayMode : ( args : { mode : DisplayMode } ) => Promise < { mode : DisplayMode } > ;
18+ } ;
19+
20+ type OpenAiHostWindow = Window & { openai ?: MockOpenAiApi } ;
21+
1222export function GradioWidgetDevShim ( ) {
1323 const iframeRef = useRef < HTMLIFrameElement > ( null ) ;
1424 const [ toolOutputJson , setToolOutputJson ] = useState (
@@ -56,13 +66,14 @@ export function GradioWidgetDevShim() {
5666 const iframe = iframeRef . current ;
5767 if ( ! iframe ?. contentWindow ) return ;
5868
59- const iframeWindow = iframe . contentWindow as any ;
60- if ( ! iframeWindow . openai ) return ;
69+ const iframeWindow = iframe . contentWindow as OpenAiHostWindow ;
70+ const openaiApi = iframeWindow . openai ;
71+ if ( ! openaiApi ) return ;
6172
6273 // Update the globals
63- iframeWindow . openai . displayMode = displayMode ;
64- iframeWindow . openai . maxHeight = maxHeight ;
65- iframeWindow . openai . theme = theme ;
74+ openaiApi . displayMode = displayMode ;
75+ openaiApi . maxHeight = maxHeight ;
76+ openaiApi . theme = theme ;
6677
6778 // Dispatch event to trigger hooks
6879 const event = new CustomEvent ( 'openai:set_globals' , {
@@ -74,40 +85,35 @@ export function GradioWidgetDevShim() {
7485 } ,
7586 } ,
7687 } ) ;
77- iframeWindow . dispatchEvent ( event ) ;
88+ iframeWindow . dispatchEvent ( event ) ;
7889
79- console . log ( '[Shim] Auto-updated displayMode, maxHeight, theme' ) ;
80- } , [ displayMode , maxHeight , theme ] ) ;
90+ console . log ( '[Shim] Auto-updated displayMode, maxHeight, theme' ) ;
91+ } , [ displayMode , maxHeight , theme ] ) ;
8192
8293 // Initialize window.openai in iframe when it loads
83- useEffect ( ( ) => {
84- const iframe = iframeRef . current ;
85- if ( ! iframe ) return ;
86-
87- const handleLoad = ( ) => {
88- const iframeWindow = iframe . contentWindow ;
89- if ( ! iframeWindow ) return ;
90-
91- // Helper to dispatch custom events in iframe
92- const dispatchGlobalsEvent = ( globals : Partial < OpenAiGlobals > ) => {
93- const event = new CustomEvent ( 'openai:set_globals' , {
94- detail : { globals } ,
95- } ) ;
96- iframeWindow . dispatchEvent ( event ) ;
97- } ;
94+ useEffect ( ( ) => {
95+ const iframe = iframeRef . current ;
96+ if ( ! iframe ) return ;
97+
98+ const handleLoad = ( ) => {
99+ const iframeWindow = iframe . contentWindow ;
100+ if ( ! iframeWindow ) return ;
101+
102+ // Helper to dispatch custom events in iframe
103+ const dispatchGlobalsEvent = ( globals : Partial < OpenAiGlobals > ) => {
104+ const event = new CustomEvent ( 'openai:set_globals' , {
105+ detail : { globals } ,
106+ } ) ;
107+ iframeWindow . dispatchEvent ( event ) ;
108+ } ;
98109
99- // Mock the window.openai API
100- const mockOpenAi : Partial < OpenAiGlobals > & {
101- callTool : ( name : string , args : Record < string , unknown > ) => Promise < { result : string } > ;
102- sendFollowUpMessage : ( args : { prompt : string } ) => Promise < void > ;
103- openExternal : ( payload : { href : string } ) => void ;
104- requestDisplayMode : ( args : { mode : DisplayMode } ) => Promise < { mode : DisplayMode } > ;
105- } = {
106- theme,
107- locale : 'en-US' ,
108- displayMode,
109- maxHeight,
110- toolInput : { } ,
110+ // Mock the window.openai API
111+ const mockOpenAi : MockOpenAiApi = {
112+ theme,
113+ locale : 'en-US' ,
114+ displayMode,
115+ maxHeight,
116+ toolInput : { } ,
111117 toolOutput : null ,
112118 toolResponseMetadata : null ,
113119 widgetState : null ,
@@ -116,11 +122,11 @@ export function GradioWidgetDevShim() {
116122 capabilities : { hover : true , touch : false } ,
117123 } ,
118124 safeArea : {
119- insets : { top : 0 , bottom : 0 , left : 0 , right : 0 } ,
120- } ,
121- setWidgetState : async ( state : unknown ) => {
122- console . log ( '[Shim] setWidgetState called:' , state ) ;
123- setWidgetStateJson ( JSON . stringify ( state , null , 2 ) ) ;
125+ insets : { top : 0 , bottom : 0 , left : 0 , right : 0 } ,
126+ } ,
127+ setWidgetState : async ( state : unknown ) => {
128+ console . log ( '[Shim] setWidgetState called:' , state ) ;
129+ setWidgetStateJson ( JSON . stringify ( state , null , 2 ) ) ;
124130 mockOpenAi . widgetState = state ;
125131 dispatchGlobalsEvent ( { widgetState : state } ) ;
126132 } ,
@@ -139,13 +145,14 @@ export function GradioWidgetDevShim() {
139145 console . log ( '[Shim] requestDisplayMode called:' , args ) ;
140146 setDisplayMode ( args . mode ) ;
141147 return { mode : args . mode } ;
142- } ,
143- } ;
148+ } ,
149+ } ;
144150
145- // Inject into iframe's window BEFORE the widget loads
146- ( iframeWindow as any ) . openai = mockOpenAi ;
151+ // Inject into iframe's window BEFORE the widget loads
152+ const hostWindow = iframeWindow as OpenAiHostWindow ;
153+ hostWindow . openai = mockOpenAi ;
147154
148- console . log ( '[Shim] window.openai initialized in iframe' ) ;
155+ console . log ( '[Shim] window.openai initialized in iframe' ) ;
149156
150157 // Auto-send initial data after a short delay to ensure React is ready
151158 setTimeout ( ( ) => {
@@ -155,13 +162,13 @@ export function GradioWidgetDevShim() {
155162 ? JSON . parse ( widgetStateJson )
156163 : null ;
157164
158- mockOpenAi . toolOutput = toolOutput ;
159- mockOpenAi . widgetState = widgetState ;
165+ mockOpenAi . toolOutput = toolOutput ;
166+ mockOpenAi . widgetState = widgetState ;
160167
161- dispatchGlobalsEvent ( {
162- toolOutput,
163- widgetState,
164- displayMode,
168+ dispatchGlobalsEvent ( {
169+ toolOutput,
170+ widgetState,
171+ displayMode,
165172 maxHeight,
166173 theme,
167174 } ) ;
@@ -171,38 +178,40 @@ export function GradioWidgetDevShim() {
171178 console . error ( '[Shim] Failed to send initial data:' , e ) ;
172179 }
173180 } , 100 ) ;
174- } ;
181+ } ;
175182
176- iframe . addEventListener ( 'load' , handleLoad ) ;
177- return ( ) => iframe . removeEventListener ( 'load' , handleLoad ) ;
178- } , [ ] ) ; // Only run on mount
183+ iframe . addEventListener ( 'load' , handleLoad ) ;
184+ return ( ) => iframe . removeEventListener ( 'load' , handleLoad ) ;
185+ // eslint-disable-next-line react-hooks/exhaustive-deps
186+ } , [ ] ) ;
179187
180188 const sendUpdate = ( ) => {
181189 setError ( null ) ;
182- const iframe = iframeRef . current ;
183- if ( ! iframe ?. contentWindow ) {
184- setError ( 'Iframe not loaded' ) ;
185- return ;
186- }
190+ const iframe = iframeRef . current ;
191+ if ( ! iframe ?. contentWindow ) {
192+ setError ( 'Iframe not loaded' ) ;
193+ return ;
194+ }
187195
188- try {
196+ try {
189197 const toolOutput = JSON . parse ( toolOutputJson ) ;
190198 const widgetState = widgetStateJson . trim ( )
191199 ? JSON . parse ( widgetStateJson )
192200 : null ;
193201
194- const iframeWindow = iframe . contentWindow as any ;
195- if ( ! iframeWindow . openai ) {
196- setError ( 'window.openai not initialized' ) ;
197- return ;
198- }
202+ const iframeWindow = iframe . contentWindow as OpenAiHostWindow ;
203+ const openaiApi = iframeWindow . openai ;
204+ if ( ! openaiApi ) {
205+ setError ( 'window.openai not initialized' ) ;
206+ return ;
207+ }
199208
200209 // Update the globals
201- iframeWindow . openai . toolOutput = toolOutput ;
202- iframeWindow . openai . widgetState = widgetState ;
203- iframeWindow . openai . displayMode = displayMode ;
204- iframeWindow . openai . maxHeight = maxHeight ;
205- iframeWindow . openai . theme = theme ;
210+ openaiApi . toolOutput = toolOutput ;
211+ openaiApi . widgetState = widgetState ;
212+ openaiApi . displayMode = displayMode ;
213+ openaiApi . maxHeight = maxHeight ;
214+ openaiApi . theme = theme ;
206215
207216 // Dispatch event to trigger hooks
208217 const event = new CustomEvent ( 'openai:set_globals' , {
@@ -215,13 +224,13 @@ export function GradioWidgetDevShim() {
215224 theme,
216225 } ,
217226 } ,
218- } ) ;
219- iframeWindow . dispatchEvent ( event ) ;
227+ } ) ;
228+ iframeWindow . dispatchEvent ( event ) ;
220229
221- console . log ( '[Shim] Update sent to widget' ) ;
222- } catch ( e ) {
223- setError ( `Invalid JSON: ${ e instanceof Error ? e . message : String ( e ) } ` ) ;
224- }
230+ console . log ( '[Shim] Update sent to widget' ) ;
231+ } catch ( e ) {
232+ setError ( `Invalid JSON: ${ e instanceof Error ? e . message : String ( e ) } ` ) ;
233+ }
225234 } ;
226235
227236 return (
@@ -409,29 +418,30 @@ export function GradioWidgetDevShim() {
409418 < button
410419 onClick = { ( ) => {
411420 setToolOutputJson ( '' ) ;
412- const iframe = iframeRef . current ;
413- if ( iframe ?. contentWindow ) {
414- const iframeWindow = iframe . contentWindow as any ;
415- if ( iframeWindow . openai ) {
416- iframeWindow . openai . toolOutput = null ;
417- const event = new CustomEvent ( 'openai:set_globals' , {
418- detail : {
419- globals : {
420- toolOutput : null ,
421- widgetState : widgetStateJson . trim ( )
421+ const iframe = iframeRef . current ;
422+ if ( iframe ?. contentWindow ) {
423+ const iframeWindow = iframe . contentWindow as OpenAiHostWindow ;
424+ const openaiApi = iframeWindow . openai ;
425+ if ( openaiApi ) {
426+ openaiApi . toolOutput = null ;
427+ const event = new CustomEvent ( 'openai:set_globals' , {
428+ detail : {
429+ globals : {
430+ toolOutput : null ,
431+ widgetState : widgetStateJson . trim ( )
422432 ? JSON . parse ( widgetStateJson )
423433 : null ,
424434 displayMode,
425435 maxHeight,
426436 theme,
437+ } ,
427438 } ,
428- } ,
429- } ) ;
430- iframeWindow . dispatchEvent ( event ) ;
431- console . log ( '[Shim] Loading state activated' ) ;
439+ } ) ;
440+ iframeWindow . dispatchEvent ( event ) ;
441+ console . log ( '[Shim] Loading state activated' ) ;
442+ }
432443 }
433- }
434- } }
444+ } }
435445 className = "w-full px-3 py-2 text-sm bg-purple-100 hover:bg-purple-200 rounded text-left"
436446 >
437447 Show Loading
0 commit comments