Skip to content

Commit c757598

Browse files
AlinaVarkkiDevtools-frontend LUCI CQ
authored and
Devtools-frontend LUCI CQ
committed
[RPP][AI] Refactor 'generate label' button states
Also place cursor at the end of the generated label. Bug: 407763455 Change-Id: I39eae71c20b3e82c30bcb749c5803654ca8ea215 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6426048 Commit-Queue: Jack Franklin <jacktfranklin@chromium.org> Auto-Submit: Alina Varkki <alinavarkki@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
1 parent 5fd9211 commit c757598

File tree

1 file changed

+49
-44
lines changed

1 file changed

+49
-44
lines changed

front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
120128
const str_ = i18n.i18n.registerUIStrings('panels/timeline/overlays/components/EntryLabelOverlay.ts', UIStrings);
121129
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
122130
const 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

Comments
 (0)