22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5- import * as Host from '../../core/host/host.js' ;
65import * as i18n from '../../core/i18n/i18n.js' ;
76import { ReactDevToolsViewBase } from '../react_devtools/ReactDevToolsViewBase.js' ;
87
@@ -129,62 +128,25 @@ export class LivematePanel extends ReactDevToolsViewBase {
129128 bottomRow . setAttribute ( 'style' , 'display: flex; align-items: center; gap: 8px;' ) ;
130129
131130 // AI query text box
132- const queryInput = document . createElement ( 'textarea' ) ;
131+ const queryInput : HTMLTextAreaElement = document . createElement ( 'textarea' ) ;
133132 queryInput . setAttribute ( 'placeholder' , 'Query to modify component...' ) ;
134133 queryInput . setAttribute ( 'style' , 'flex: 1; padding: 12px 16px; border: 1px solid var(--sys-color-divider); border-radius: 4px; background: var(--sys-color-cdt-base-container); color: var(--sys-color-on-surface); font-size: 14px; min-height: 100px; resize: vertical; font-family: inherit;' ) ;
135134
136- // Function to send query to Devmate
137- const sendQueryToDevmate = ( ) : void => {
138- const query = queryInput . value ;
139- if ( query . trim ( ) ) {
140- // Build the prompt with focused component and hierarchy information
141- let prompt = query ;
142- if ( currentHierarchy . length > 0 ) {
143- // The focused component is the last item in the hierarchy (leaf node)
144- const focusedComponent = currentHierarchy [ currentHierarchy . length - 1 ] . name ;
145- const hierarchyStr = currentHierarchy . map ( c => c . name ) . join ( ' > ' ) ;
146- prompt = `Focused component: ${ focusedComponent } \nComponent hierarchy: ${ hierarchyStr } \n\nQuery: ${ query } ` ;
147- }
148- (
149- Host . InspectorFrontendHost . InspectorFrontendHostInstance as unknown as {
150- sendToDevmate : ( prompt : string ) => void ,
151- }
152- ) . sendToDevmate ( prompt ) ;
153-
154- // Disable input and button with grayed out appearance
155- queryInput . disabled = true ;
156- queryInput . style . opacity = '0.5' ;
157- queryInput . style . cursor = 'not-allowed' ;
158- sendButton . disabled = true ;
159- sendButton . style . opacity = '0.5' ;
160- sendButton . style . cursor = 'not-allowed' ;
161-
162- // Re-enable and clear after 3 seconds
163- setTimeout ( ( ) => {
164- queryInput . value = '' ;
165- queryInput . disabled = false ;
166- queryInput . style . opacity = '1' ;
167- queryInput . style . cursor = 'text' ;
168- sendButton . disabled = false ;
169- sendButton . style . opacity = '1' ;
170- sendButton . style . cursor = 'pointer' ;
171- } , 3000 ) ;
172- }
173- } ;
174-
175135 // Handle Enter key to send prompt (Shift+Enter for newline)
176- queryInput . addEventListener ( 'keydown' , ( event : KeyboardEvent ) => {
136+ queryInput . addEventListener ( 'keydown' , async ( event : KeyboardEvent ) => {
177137 if ( event . key === 'Enter' && ! event . shiftKey ) {
178138 event . preventDefault ( ) ;
179- sendQueryToDevmate ( ) ;
139+ await sendCommandToMetro ( queryInput , currentHierarchy ) ;
180140 }
181141 } ) ;
182142
183143 // Send to devmate button
184144 const sendButton = document . createElement ( 'button' ) ;
185145 sendButton . textContent = 'Send to Devmate' ;
186146 sendButton . setAttribute ( 'style' , 'padding: 4px 12px; cursor: pointer; align-self: flex-end;' ) ;
187- sendButton . addEventListener ( 'click' , sendQueryToDevmate ) ;
147+ sendButton . addEventListener ( 'click' , async ( ) => {
148+ await sendCommandToMetro ( queryInput , currentHierarchy ) ;
149+ } ) ;
188150
189151 bottomRow . appendChild ( queryInput ) ;
190152 bottomRow . appendChild ( sendButton ) ;
@@ -194,8 +156,66 @@ export class LivematePanel extends ReactDevToolsViewBase {
194156
195157 outerWrapper . appendChild ( toolbarContainer ) ;
196158 this . contentElement . appendChild ( outerWrapper ) ;
159+ }
160+ }
161+
162+ async function sendCommandToMetro (
163+ input : HTMLTextAreaElement ,
164+ currentHierarchy : Array < { name : string } > = [ ] ,
165+ timeoutMs = 5000
166+ ) : Promise < { success : boolean ; output ?: string ; error ?: string } > {
167+ const query = input . value ;
168+ let prompt ;
169+ if ( query . trim ( ) ) {
170+ prompt = query ;
171+ if ( currentHierarchy . length > 0 ) {
172+ const focusedComponent = currentHierarchy [ currentHierarchy . length - 1 ] . name ;
173+ const hierarchyStr = currentHierarchy . map ( c => c . name ) . join ( ' > ' ) ;
174+ prompt = `Focused component: ${ focusedComponent } \nComponent hierarchy: ${ hierarchyStr } \n\nQuery: ${ query } ` ;
175+ }
176+ }
177+
178+ const controller = new AbortController ( ) ;
179+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , timeoutMs ) ;
180+
181+ try {
182+ const response = await fetch ( 'http://localhost:8081/livemate' , {
183+ method : 'POST' ,
184+ headers : {
185+ 'Content-Type' : 'application/json' ,
186+ } ,
187+ body : JSON . stringify ( { prompt} ) ,
188+ signal : controller . signal ,
189+ } ) ;
197190
191+ clearTimeout ( timeoutId ) ;
198192
193+ if ( ! response . ok ) {
194+ const errorText = await response . text ( ) ;
195+ input . value = '' ;
196+ return {
197+ success : false ,
198+ error : `HTTP ${ response . status } : ${ errorText } ` ,
199+ } ;
200+ }
201+
202+ const result = await response . json ( ) ;
203+ return result ;
204+ } catch ( e ) {
205+ clearTimeout ( timeoutId ) ;
206+
207+ if ( e instanceof Error && e . name === 'AbortError' ) {
208+ input . value = '' ;
209+ return {
210+ success : false ,
211+ error : 'Request timeout' ,
212+ } ;
213+ }
199214
215+ input . value = '' ;
216+ return {
217+ success : false ,
218+ error : e instanceof Error ? e . message : 'Unknown error' ,
219+ } ;
200220 }
201221}
0 commit comments