@@ -117,6 +117,14 @@ const UIStringsNotTranslate = {
117117 learnMoreButton : 'Learn more about auto annotations' ,
118118} as const ;
119119
120+ const enum AIButtonState {
121+ ENABLED = 'enabled' ,
122+ DISABLED = 'disabled' ,
123+ HIDDEN = 'hidden' ,
124+ GENERATION_FAILED = 'generation_failed' ,
125+ GENERATING_LABEL = 'generating_label' ,
126+ }
127+
120128const str_ = i18n . i18n . registerUIStrings ( 'panels/timeline/overlays/components/EntryLabelOverlay.ts' , UIStrings ) ;
121129const i18nString = i18n . i18n . getLocalizedString . bind ( undefined , str_ ) ;
122130const lockedString = i18n . i18n . lockedString ;
@@ -201,10 +209,8 @@ export class EntryLabelOverlay extends HTMLElement {
201209 * consented, hopefully!
202210 */
203211 #inAIConsentDialogFlow = false ;
204- // Keep track of the AI label loading state to render a loading component if the label is being generated
205- #isAILabelLoading = false ;
206- // Set to true when the generation of an AI label failed so we know when to display the 'generation failed' disclaimer
207- #isAILabelGenerationFailed = false ;
212+ #currAIButtonState: AIButtonState = AIButtonState . HIDDEN ;
213+
208214 /**
209215 * The entry label overlay consists of 3 parts - the label part with the label string inside,
210216 * the line connecting the label to the entry, and a black box around an entry to highlight the entry with a label.
@@ -507,6 +513,8 @@ export class EntryLabelOverlay extends HTMLElement {
507513
508514 set callTree ( callTree : Utils . AICallTree . AICallTree | null ) {
509515 this . #callTree = callTree ;
516+ // If the entry has a calltree, we need to check if we need to show the 'generate label' button.
517+ this . #setAIButtonRenderState( ) ;
510518 }
511519
512520 // Generate the AI label suggestion if:
@@ -523,7 +531,7 @@ export class EntryLabelOverlay extends HTMLElement {
523531 }
524532 try {
525533 // Trigger a re-render to display the loading component in the place of the button when the label is being generated.
526- this . #isAILabelLoading = true ;
534+ this . #currAIButtonState = AIButtonState . GENERATING_LABEL ;
527535 UI . ARIAUtils . alert ( UIStringsNotTranslate . generatingLabel ) ;
528536 // Trigger a re-render to put focus back on the input box, otherwise
529537 // when the button changes to a loading spinner, it loses focus and the
@@ -535,17 +543,16 @@ export class EntryLabelOverlay extends HTMLElement {
535543 this . #label = await this . #agent. generateAIEntryLabel ( this . #callTree) ;
536544 this . dispatchEvent ( new EntryLabelChangeEvent ( this . #label) ) ;
537545 this . #inputField. innerText = this . #label;
538-
539- this . #isAILabelLoading = false ;
546+ this . #placeCursorAtInputEnd( ) ;
547+ // Reset the button state because we want to hide it if the label is not empty.
548+ this . #setAIButtonRenderState( ) ;
540549 // Trigger a re-render to hide the AI Button and display the generated label.
541550 this . #render( ) ;
542551 } catch {
543- this . #isAILabelLoading = false ;
544- this . #isAILabelGenerationFailed = true ;
552+ this . #currAIButtonState = AIButtonState . GENERATION_FAILED ;
545553 void ComponentHelpers . ScheduledRender . scheduleRender ( this , this . #boundRender) ;
546554 }
547555 } else {
548- this . #isAILabelLoading = false ;
549556 this . #inAIConsentDialogFlow = true ;
550557 this . #render( ) ;
551558 const hasConsented = await this . #showUserAiFirstRunDialog( ) ;
@@ -610,7 +617,7 @@ export class EntryLabelOverlay extends HTMLElement {
610617 return this . #aiAnnotationsEnabledSetting. get ( ) ;
611618 }
612619
613- #shouldRenderAIButton ( ) : 'enabled' | 'disabled' | 'hidden' {
620+ #setAIButtonRenderState ( ) : void {
614621 const hasAiExperiment = Boolean ( Root . Runtime . hostConfig . devToolsAiGeneratedTimelineLabels ?. enabled ) ;
615622 const aiDisabledByEnterprisePolicy = Root . Runtime . hostConfig . aidaAvailability ?. enterprisePolicyValue ===
616623 Root . Runtime . GenAiEnterprisePolicyValue . DISABLE ;
@@ -625,24 +632,19 @@ export class EntryLabelOverlay extends HTMLElement {
625632 const labelIsEmpty = this . #label?. length <= 0 ;
626633
627634 if ( ! hasAiExperiment || aiDisabledByEnterprisePolicy || ! dataToGenerateLabelAvailable || ! labelIsEmpty ) {
628- return 'hidden' ;
629- }
630-
631- // To verify whether AI can be used, check if the user is logged in, over 18, in a supported
632- // location and offline. If the user is not logged in, `blockedByAge` will return true.
633- const aiAvailable = ! Root . Runtime . hostConfig . aidaAvailability ?. blockedByAge &&
634- ! Root . Runtime . hostConfig . aidaAvailability ?. blockedByGeo && ! navigator . onLine === false ;
635- if ( aiAvailable ) {
636- return 'enabled' ;
637- }
638-
639- // If AI features are not available, we show a disabled button.
640- if ( ! aiAvailable ) {
641- return 'disabled' ;
635+ this . #currAIButtonState = AIButtonState . HIDDEN ;
636+ } else {
637+ // To verify whether AI can be used, check if the user is logged in, over 18, in a supported
638+ // location and offline. If the user is not logged in, `blockedByAge` will return true.
639+ const aiAvailable = ! Root . Runtime . hostConfig . aidaAvailability ?. blockedByAge &&
640+ ! Root . Runtime . hostConfig . aidaAvailability ?. blockedByGeo && ! navigator . onLine === false ;
641+ if ( aiAvailable ) {
642+ this . #currAIButtonState = AIButtonState . ENABLED ;
643+ } else {
644+ // If AI features are not available, we show a disabled button.
645+ this . #currAIButtonState = AIButtonState . DISABLED ;
646+ }
642647 }
643-
644- console . error ( '\'Generate label\' button is hidden for an unknown reason' ) ;
645- return 'hidden' ;
646648 }
647649
648650 #renderAITooltip( opts : { textContent : string , includeSettingsButton : boolean } ) : Lit . TemplateResult {
@@ -668,26 +670,24 @@ export class EntryLabelOverlay extends HTMLElement {
668670 </ devtools-tooltip > ` ;
669671 // clang-format on
670672 }
671-
672- #renderAiButton( ) : Lit . LitTemplate {
673- const noLogging = Root . Runtime . hostConfig . aidaAvailability ?. enterprisePolicyValue ===
674- Root . Runtime . GenAiEnterprisePolicyValue . ALLOW_WITHOUT_LOGGING ;
675-
676- if ( this . #isAILabelLoading) {
677- // clang-format off
678- return html `
673+ #renderGeneratingLabelAiButton( ) : Lit . LitTemplate {
674+ // clang-format off
675+ return html `
679676 < span
680677 class ="ai-label-loading ">
681678 < devtools-spinner > </ devtools-spinner >
682679 < span class ="generate-label-text "> ${ lockedString ( UIStringsNotTranslate . generatingLabel ) } </ span >
683680 </ span >
684681 ` ;
685- // clang-format on
686- }
682+ // clang-format on
683+ }
687684
688- if ( this . #isAILabelGenerationFailed) {
685+ #renderAiButton( ) : Lit . LitTemplate {
686+ const noLogging = Root . Runtime . hostConfig . aidaAvailability ?. enterprisePolicyValue ===
687+ Root . Runtime . GenAiEnterprisePolicyValue . ALLOW_WITHOUT_LOGGING ;
688+
689+ if ( this . #currAIButtonState === AIButtonState . GENERATION_FAILED ) {
689690 // Only show the error message on the first component render render after the failure.
690- this . #isAILabelGenerationFailed = false ;
691691 // clang-format off
692692 return html `
693693 < span
@@ -830,6 +830,7 @@ export class EntryLabelOverlay extends HTMLElement {
830830 @paste=${ this . #handleLabelInputPaste}
831831 @keyup=${ ( ) => {
832832 this . #handleLabelInputKeyUp( ) ;
833+ this . #setAIButtonRenderState( ) ;
833834 // Rerender the label component when the label text changes because we need to
834835 // make sure the 'auto annotation' button is only shown when the label is empty.
835836 this . #render( ) ;
@@ -839,12 +840,16 @@ export class EntryLabelOverlay extends HTMLElement {
839840 tabindex="0"
840841 > </ span >
841842 ${ ( ( ) => {
842- switch ( this . #shouldRenderAIButton ( ) ) {
843- case 'hidden' :
843+ switch ( this . #currAIButtonState ) {
844+ case AIButtonState . HIDDEN :
844845 return Lit . nothing ;
845- case 'enabled' :
846+ case AIButtonState . ENABLED :
847+ return this . #renderAiButton( ) ;
848+ case AIButtonState . GENERATING_LABEL :
849+ return this . #renderGeneratingLabelAiButton( ) ;
850+ case AIButtonState . GENERATION_FAILED :
846851 return this . #renderAiButton( ) ;
847- case 'disabled' :
852+ case AIButtonState . DISABLED :
848853 return this . #renderDisabledAiButton( ) ;
849854 }
850855 } ) ( ) }
0 commit comments