@@ -217,6 +217,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
217217const lockedString = i18n . i18n . lockedString ;
218218
219219const SCROLL_ROUNDING_OFFSET = 1 ;
220+ const JPEG_MIME_TYPE = 'image/jpeg' ;
220221
221222export interface Step {
222223 isLoading : boolean ;
@@ -238,6 +239,13 @@ export const enum ChatMessageEntity {
238239 USER = 'user' ,
239240}
240241
242+ export type ImageInputData = {
243+ isLoading : true ,
244+ } | {
245+ isLoading : false ,
246+ data : string ,
247+ } ;
248+
241249export interface UserChatMessage {
242250 entity : ChatMessageEntity . USER ;
243251 text : string ;
@@ -285,7 +293,7 @@ export interface Props {
285293 patchSuggestionLoading ?: boolean ;
286294 projectName ?: string ;
287295 multimodalInputEnabled ?: boolean ;
288- imageInput ?: string ;
296+ imageInput ?: ImageInputData ;
289297 onApplyToWorkspace ?: ( ) => void ;
290298 isTextInputDisabled : boolean ;
291299 emptyStateSuggestions : string [ ] ;
@@ -413,12 +421,17 @@ export class ChatView extends HTMLElement {
413421
414422 #handleSubmit = ( ev : SubmitEvent ) : void => {
415423 ev . preventDefault ( ) ;
424+ if ( this . #props. imageInput ?. isLoading ) {
425+ return ;
426+ }
427+
416428 const textArea = this . #shadow. querySelector ( '.chat-input' ) as HTMLTextAreaElement ;
417429 if ( ! textArea ?. value ) {
418430 return ;
419431 }
420- const imageInput =
421- this . #props. imageInput ? { inlineData : { data : this . #props. imageInput , mimeType : 'image/jpeg' } } : undefined ;
432+ const imageInput = ! this . #props. imageInput ?. isLoading && this . #props. imageInput ?. data ?
433+ { inlineData : { data : this . #props. imageInput . data , mimeType : JPEG_MIME_TYPE } } :
434+ undefined ;
422435 void this . #props. onTextSubmit ( textArea . value , imageInput ) ;
423436 textArea . value = '' ;
424437 } ;
@@ -431,11 +444,12 @@ export class ChatView extends HTMLElement {
431444 // Go to a new line only when Shift + Enter is pressed.
432445 if ( ev . key === 'Enter' && ! ev . shiftKey ) {
433446 ev . preventDefault ( ) ;
434- if ( ! ev . target ?. value ) {
447+ if ( ! ev . target ?. value || this . #props . imageInput ?. isLoading ) {
435448 return ;
436449 }
437- const imageInput =
438- this . #props. imageInput ? { inlineData : { data : this . #props. imageInput , mimeType : 'image/jpeg' } } : undefined ;
450+ const imageInput = ! this . #props. imageInput ?. isLoading && this . #props. imageInput ?. data ?
451+ { inlineData : { data : this . #props. imageInput . data , mimeType : JPEG_MIME_TYPE } } :
452+ undefined ;
439453 void this . #props. onTextSubmit ( ev . target . value , imageInput ) ;
440454 ev . target . value = '' ;
441455 }
@@ -1053,13 +1067,12 @@ function renderReadOnlySection({onNewConversation, conversationType}: {
10531067}
10541068
10551069function renderChatInputButtons (
1056- { isLoading, blockedByCrossOrigin, isTextInputDisabled, isTextInputEmpty, onCancel, onNewConversation} : {
1070+ { isLoading, blockedByCrossOrigin, isTextInputDisabled, isTextInputEmpty, imageInput , onCancel, onNewConversation} : {
10571071 isLoading : boolean ,
10581072 blockedByCrossOrigin : boolean ,
10591073 isTextInputDisabled : boolean ,
10601074 isTextInputEmpty : boolean ,
1061- onCancel : ( ev : SubmitEvent ) => void ,
1062- onNewConversation : ( ) => void ,
1075+ imageInput ?: ImageInputData , onCancel : ( ev : SubmitEvent ) => void , onNewConversation : ( ) => void ,
10631076 } ) : Lit . TemplateResult {
10641077 if ( isLoading ) {
10651078 // clang-format off
@@ -1107,7 +1120,7 @@ function renderChatInputButtons(
11071120 type : 'submit' ,
11081121 variant : Buttons . Button . Variant . ICON ,
11091122 size : Buttons . Button . Size . REGULAR ,
1110- disabled : isTextInputDisabled || isTextInputEmpty ,
1123+ disabled : isTextInputDisabled || isTextInputEmpty || imageInput ?. isLoading ,
11111124 iconName : 'send' ,
11121125 title : lockedString ( UIStringsNotTranslate . sendButtonTitle ) ,
11131126 jslogContext : 'send' ,
@@ -1120,11 +1133,13 @@ function renderTakeScreenshotButton({
11201133 multimodalInputEnabled,
11211134 blockedByCrossOrigin,
11221135 isTextInputDisabled,
1136+ imageInput,
11231137 onTakeScreenshot,
11241138} : {
11251139 isTextInputDisabled : boolean ,
11261140 blockedByCrossOrigin : boolean ,
11271141 multimodalInputEnabled ?: boolean ,
1142+ imageInput ?: ImageInputData ,
11281143 onTakeScreenshot ?: ( ) => void ,
11291144} ) : Lit . LitTemplate {
11301145 if ( ! multimodalInputEnabled || blockedByCrossOrigin ) {
@@ -1138,7 +1153,7 @@ function renderTakeScreenshotButton({
11381153 {
11391154 variant : Buttons . Button . Variant . ICON ,
11401155 size : Buttons . Button . Size . REGULAR ,
1141- disabled : isTextInputDisabled ,
1156+ disabled : isTextInputDisabled || imageInput ?. isLoading ,
11421157 iconName : 'photo-camera' ,
11431158 title : lockedString ( UIStringsNotTranslate . takeScreenshotButtonTitle ) ,
11441159 jslogContext : 'take-screenshot' ,
@@ -1153,15 +1168,14 @@ function renderImageInput({
11531168 onRemoveImageInput,
11541169} : {
11551170 multimodalInputEnabled ?: boolean ,
1156- imageInput ?: string ,
1171+ imageInput ?: ImageInputData ,
11571172 onRemoveImageInput ?: ( ) => void ,
11581173} ) : Lit . LitTemplate {
1159- if ( ! multimodalInputEnabled || ! imageInput || imageInput === '' ) {
1174+ if ( ! multimodalInputEnabled || ! imageInput ) {
11601175 return Lit . nothing ;
11611176 }
1162- return html `
1163- < div class ="image-input-container ">
1164- < devtools-button
1177+
1178+ const crossButton = html `< devtools-button
11651179 aria-label =${ lockedString ( UIStringsNotTranslate . removeImageInputButtonTitle ) }
11661180 @click =${ onRemoveImageInput }
11671181 .data=${
@@ -1172,8 +1186,20 @@ function renderImageInput({
11721186 title : lockedString ( UIStringsNotTranslate . removeImageInputButtonTitle ) ,
11731187 } as Buttons . Button . ButtonData
11741188 }
1175- > </ devtools-button >
1176- < img src ="data:image/jpeg;base64, ${ imageInput } " alt ="Screenshot input " />
1189+ > </ devtools-button > ` ;
1190+
1191+ if ( imageInput . isLoading ) {
1192+ return html `< div class ="image-input-container ">
1193+ ${ crossButton }
1194+ < div class ="loading ">
1195+ < devtools-spinner > </ devtools-spinner >
1196+ </ div >
1197+ </ div > ` ;
1198+ }
1199+ return html `
1200+ < div class ="image-input-container ">
1201+ ${ crossButton }
1202+ < img src ="data:image/jpeg;base64, ${ imageInput . data } " alt ="Screenshot input " />
11771203 </ div > ` ;
11781204 }
11791205
@@ -1208,7 +1234,7 @@ function renderChatInput({
12081234 inspectElementToggled : boolean ,
12091235 multimodalInputEnabled ?: boolean ,
12101236 conversationType ?: ConversationType ,
1211- imageInput ?: string ,
1237+ imageInput ?: ImageInputData ,
12121238 isTextInputEmpty : boolean ,
12131239 onContextClick : ( ) => void ,
12141240 onInspectElementClick : ( ) => void ,
@@ -1230,10 +1256,10 @@ function renderChatInput({
12301256 'screenshot-button' : Boolean ( multimodalInputEnabled ) && ! blockedByCrossOrigin ,
12311257 } ) ;
12321258
1233- const chatInputContainerCls = Lit . Directives . classMap ( {
1234- 'chat-input-container' : true ,
1235- disabled : isTextInputDisabled ,
1236- } ) ;
1259+ const chatInputContainerCls = Lit . Directives . classMap ( {
1260+ 'chat-input-container' : true ,
1261+ disabled : isTextInputDisabled ,
1262+ } ) ;
12371263
12381264 // clang-format off
12391265 return html `
@@ -1269,13 +1295,14 @@ function renderChatInput({
12691295 > </ textarea >
12701296 < div class ="chat-input-buttons ">
12711297 ${ renderTakeScreenshotButton ( {
1272- multimodalInputEnabled, blockedByCrossOrigin, isTextInputDisabled, onTakeScreenshot
1298+ multimodalInputEnabled, blockedByCrossOrigin, isTextInputDisabled, imageInput, onTakeScreenshot
1299+ } ) }
1300+ ${ renderChatInputButtons ( {
1301+ isLoading, blockedByCrossOrigin, isTextInputDisabled, isTextInputEmpty, imageInput, onCancel, onNewConversation
12731302 } ) }
1274- ${ renderChatInputButtons ( { isLoading, blockedByCrossOrigin, isTextInputDisabled, isTextInputEmpty, onCancel, onNewConversation } ) }
12751303 </ div >
12761304 </ div >
1277- </ form >
1278- ` ;
1305+ </ form > ` ;
12791306 // clang-format on
12801307}
12811308
0 commit comments