Skip to content

Commit da76b6d

Browse files
ktranDevtools-frontend LUCI CQ
authored andcommitted
Enable new badges, and gate 'Debug with AI' also behind a feature
This CL adds the new badges for the native context menus and rate limits them based on their usage count. We also gate the 'Debug with AI' behind a new feature. Bug: 427371633 Change-Id: I0d852038962977f0d7b14cec898f4a99b51c1982 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6827161 Reviewed-by: Alex Rudenko <[email protected]> Reviewed-by: Kateryna Prokopenko <[email protected]> Commit-Queue: Kim-Anh Tran <[email protected]>
1 parent 358bff7 commit da76b6d

File tree

13 files changed

+139
-37
lines changed

13 files changed

+139
-37
lines changed

front_end/core/host/InspectorFrontendHost.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
280280
recordUserMetricsAction(_umaName: string): void {
281281
}
282282

283+
recordNewBadgeUsage(_featureName: string): void {
284+
}
285+
283286
connectAutomaticFileSystem(
284287
_fileSystemPath: Platform.DevToolsPath.RawPathString,
285288
_fileSystemUUID: string,

front_end/core/host/InspectorFrontendHostAPI.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ export interface InspectorFrontendHostAPI {
355355

356356
recordUserMetricsAction(umaName: string): void;
357357

358+
recordNewBadgeUsage(featureName: string): void;
359+
358360
sendMessageToBackend(message: string): void;
359361

360362
setDevicesDiscoveryConfig(config: Adb.Config): void;

front_end/core/root/Runtime.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,12 +452,18 @@ interface AllowPopoverForcing {
452452

453453
interface AiSubmenuPrompts {
454454
enabled: boolean;
455+
featureName?: string;
455456
}
456457

457458
interface IpProtectionInDevTools {
458459
enabled: boolean;
459460
}
460461

462+
interface AiDebugWithAi {
463+
enabled: boolean;
464+
featureName?: string;
465+
}
466+
461467
/**
462468
* The host configuration that we expect from the DevTools back-end.
463469
*
@@ -478,6 +484,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
478484
devToolsDeepLinksViaExtensibilityApi: HostConfigDeepLinksViaExtensibilityApi,
479485
devToolsFreestyler: HostConfigFreestyler,
480486
devToolsAiAssistanceNetworkAgent: HostConfigAiAssistanceNetworkAgent,
487+
devToolsAiDebugWithAi: AiDebugWithAi,
481488
devToolsAiAssistanceFileAgent: HostConfigAiAssistanceFileAgent,
482489
devToolsAiAssistancePerformanceAgent: HostConfigAiAssistancePerformanceAgent,
483490
devToolsAiCodeCompletion: HostConfigAiCodeCompletion,

front_end/panels/ai_assistance/ai_assistance-meta.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import * as Common from '../../core/common/common.js';
66
import * as i18n from '../../core/i18n/i18n.js';
77
import type * as Platform from '../../core/platform/platform.js';
8-
import type * as Root from '../../core/root/root.js';
8+
import * as Root from '../../core/root/root.js';
99
import * as UI from '../../ui/legacy/legacy.js';
1010

1111
import type * as AiAssistance from './ai_assistance.js';
@@ -108,13 +108,22 @@ function isAnyFeatureAvailable(config?: Root.Runtime.HostConfig): boolean {
108108
isPerformanceAgentFeatureAvailable(config) || isFileAgentFeatureAvailable(config);
109109
}
110110

111+
function titleForAiAssistanceActions(): Platform.UIString.LocalizedString {
112+
if (Root.Runtime.hostConfig.devToolsAiDebugWithAi?.enabled ||
113+
Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.enabled) {
114+
return i18nLazyString(UIStrings.debugWithAi)();
115+
}
116+
return i18nLazyString(UIStrings.askAi)();
117+
}
118+
111119
UI.ViewManager.registerViewExtension({
112120
location: UI.ViewManager.ViewLocationValues.DRAWER_VIEW,
113121
id: 'freestyler',
114122
commandPrompt: i18nLazyString(UIStrings.showAiAssistance),
115123
title: i18nLazyString(UIStrings.aiAssistance),
116124
order: 10,
117125
isPreviewFeature: true,
126+
featurePromotionId: 'ai-assistance',
118127
persistence: UI.ViewManager.ViewPersistence.CLOSEABLE,
119128
hasToolbar: false,
120129
condition: config => isAnyFeatureAvailable(config) && !isPolicyRestricted(config),
@@ -156,7 +165,7 @@ UI.ActionRegistration.registerActionExtension({
156165
return [];
157166
},
158167
category: UI.ActionRegistration.ActionCategory.GLOBAL,
159-
title: i18nLazyString(UIStrings.askAi),
168+
title: () => titleForAiAssistanceActions(),
160169
async loadActionDelegate() {
161170
const AiAssistance = await loadAiAssistanceModule();
162171
return new AiAssistance.ActionDelegate();
@@ -171,7 +180,7 @@ UI.ActionRegistration.registerActionExtension({
171180
return [];
172181
},
173182
category: UI.ActionRegistration.ActionCategory.GLOBAL,
174-
title: i18nLazyString(UIStrings.askAi),
183+
title: () => titleForAiAssistanceActions(),
175184
async loadActionDelegate() {
176185
const AiAssistance = await loadAiAssistanceModule();
177186
return new AiAssistance.ActionDelegate();
@@ -186,7 +195,7 @@ UI.ActionRegistration.registerActionExtension({
186195
return [];
187196
},
188197
category: UI.ActionRegistration.ActionCategory.GLOBAL,
189-
title: i18nLazyString(UIStrings.askAi),
198+
title: () => titleForAiAssistanceActions(),
190199
async loadActionDelegate() {
191200
const AiAssistance = await loadAiAssistanceModule();
192201
return new AiAssistance.ActionDelegate();
@@ -201,7 +210,7 @@ UI.ActionRegistration.registerActionExtension({
201210
return [];
202211
},
203212
category: UI.ActionRegistration.ActionCategory.GLOBAL,
204-
title: i18nLazyString(UIStrings.debugWithAi),
213+
title: () => titleForAiAssistanceActions(),
205214
async loadActionDelegate() {
206215
const AiAssistance = await loadAiAssistanceModule();
207216
return new AiAssistance.ActionDelegate();
@@ -216,7 +225,7 @@ UI.ActionRegistration.registerActionExtension({
216225
return [];
217226
},
218227
category: UI.ActionRegistration.ActionCategory.GLOBAL,
219-
title: i18nLazyString(UIStrings.debugWithAi),
228+
title: () => titleForAiAssistanceActions(),
220229
async loadActionDelegate() {
221230
const AiAssistance = await loadAiAssistanceModule();
222231
return new AiAssistance.ActionDelegate();
@@ -231,7 +240,7 @@ UI.ActionRegistration.registerActionExtension({
231240
return [];
232241
},
233242
category: UI.ActionRegistration.ActionCategory.GLOBAL,
234-
title: i18nLazyString(UIStrings.askAi),
243+
title: () => titleForAiAssistanceActions(),
235244
async loadActionDelegate() {
236245
const AiAssistance = await loadAiAssistanceModule();
237246
return new AiAssistance.ActionDelegate();
@@ -248,7 +257,7 @@ UI.ActionRegistration.registerActionExtension({
248257
return [];
249258
},
250259
category: UI.ActionRegistration.ActionCategory.GLOBAL,
251-
title: i18nLazyString(UIStrings.askAi),
260+
title: () => titleForAiAssistanceActions(),
252261
async loadActionDelegate() {
253262
const AiAssistance = await loadAiAssistanceModule();
254263
return new AiAssistance.ActionDelegate();
@@ -262,7 +271,7 @@ UI.ActionRegistration.registerActionExtension({
262271
return [];
263272
},
264273
category: UI.ActionRegistration.ActionCategory.GLOBAL,
265-
title: i18nLazyString(UIStrings.debugWithAi),
274+
title: () => titleForAiAssistanceActions(),
266275
async loadActionDelegate() {
267276
const AiAssistance = await loadAiAssistanceModule();
268277
return new AiAssistance.ActionDelegate();

front_end/panels/elements/ElementsTreeElement.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,6 @@ const UIStrings = {
232232
* @description Text of the tooltip for scroll adorner.
233233
*/
234234
elementHasScrollableOverflow: 'This element has a scrollable overflow',
235-
/**
236-
* @description Context menu text in Elements Panel to that opens a submenu with AI prompts.
237-
*/
238-
debugWithAi: 'Debug with AI',
239235
/**
240236
* @description Text of a context menu item to redirect to the AI assistance panel and to start a chat.
241237
*/
@@ -918,8 +914,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
918914
UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, this.nodeInternal);
919915
if (Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.enabled) {
920916
const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
921-
const submenu =
922-
contextMenu.footerSection().appendSubMenuItem(i18nString(UIStrings.debugWithAi), false, openAiAssistanceId);
917+
// Register new badge under the `devToolsAiSubmenuPrompts` feature, as the freestyler one is already used in ViewManager.
918+
const submenu = contextMenu.footerSection().appendSubMenuItem(
919+
action.title(), false, Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.featureName);
923920
submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
924921

925922
const submenuConfigs = [
@@ -1050,6 +1047,11 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
10501047
submenu, action, item.label, item.prompt, openAiAssistanceId + item.jslogContextSuffix);
10511048
}
10521049
}
1050+
} else if (Root.Runtime.hostConfig.devToolsAiDebugWithAi?.enabled) {
1051+
// Register new badge under the `devToolsAiDebugWithAi` feature, as the freestyler one is already used in ViewManager.
1052+
contextMenu.footerSection().appendAction(
1053+
openAiAssistanceId, undefined, false, undefined,
1054+
Root.Runtime.hostConfig.devToolsAiDebugWithAi?.featureName);
10531055
} else {
10541056
contextMenu.footerSection().appendAction(openAiAssistanceId);
10551057
}

front_end/panels/network/NetworkLogView.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,6 @@ const UIStrings = {
474474
* @description Text for the Show only/Hide requests dropdown button of the filterbar
475475
*/
476476
moreFilters: 'More filters',
477-
/**
478-
* @description Context menu text in Network Panel to that opens a submenu with AI prompts.
479-
*/
480-
debugWithAi: 'Debug with AI',
481477
/**
482478
* @description Text of a context menu item to redirect to the AI assistance panel and to start a chat.
483479
*/
@@ -1717,14 +1713,15 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
17171713
submenu: UI.ContextMenu.SubMenu, action: UI.ActionRegistration.Action,
17181714
label: Common.UIString.LocalizedString, prompt: string, jslogContext: string): void {
17191715
submenu.defaultSection().appendItem(
1720-
label, async () => await action.execute({prompt}), {disabled: !action.enabled(), jslogContext});
1716+
label, () => action.execute({prompt}), {disabled: !action.enabled(), jslogContext});
17211717
}
17221718

17231719
UI.Context.Context.instance().setFlavor(SDK.NetworkRequest.NetworkRequest, request);
17241720
if (Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.enabled) {
17251721
const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
17261722
const submenu = contextMenu.footerSection().appendSubMenuItem(
1727-
i18nString(UIStrings.debugWithAi), false, openAiAssistanceId);
1723+
action.title(), false, openAiAssistanceId,
1724+
Root.Runtime.hostConfig.devToolsAiAssistanceNetworkAgent?.featureName);
17281725
submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
17291726
appendSubmenuPromptAction(
17301727
submenu, action, i18nString(UIStrings.explainPurpose), 'What is the purpose of this request?',
@@ -1738,6 +1735,10 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
17381735
appendSubmenuPromptAction(
17391736
submenu, action, i18nString(UIStrings.assessSecurityHeaders), 'Are there any security headers present?',
17401737
openAiAssistanceId + '.security');
1738+
} else if (Root.Runtime.hostConfig.devToolsAiDebugWithAi?.enabled) {
1739+
contextMenu.footerSection().appendAction(
1740+
openAiAssistanceId, undefined, false, undefined,
1741+
Root.Runtime.hostConfig.devToolsAiAssistanceNetworkAgent?.featureName);
17411742
} else {
17421743
contextMenu.footerSection().appendAction(openAiAssistanceId);
17431744
}

front_end/panels/sources/SourcesPanel.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,6 @@ const UIStrings = {
167167
* @description Text in Sources Panel of the Sources panel
168168
*/
169169
openInSourcesPanel: 'Open in Sources panel',
170-
/**
171-
* @description Context menu text in Sources Panel to that opens a submenu with AI prompts.
172-
*/
173-
debugWithAi: 'Debug with AI',
174170
/**
175171
* @description Text of a context menu item to redirect to the AI assistance panel and to start a chat.
176172
*/
@@ -974,7 +970,8 @@ export class SourcesPanel extends UI.Panel.Panel implements
974970
if (Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.enabled) {
975971
const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
976972
const submenu = contextMenu.footerSection().appendSubMenuItem(
977-
i18nString(UIStrings.debugWithAi), false, openAiAssistanceId);
973+
action.title(), false, openAiAssistanceId,
974+
Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.featureName);
978975
submenu.defaultSection().appendAction('drjones.sources-panel-context', i18nString(UIStrings.startAChat));
979976
appendSubmenuPromptAction(
980977
submenu, action, i18nString(UIStrings.assessPerformance), 'Is this script optimized for performance?',
@@ -985,6 +982,10 @@ export class SourcesPanel extends UI.Panel.Panel implements
985982
appendSubmenuPromptAction(
986983
submenu, action, i18nString(UIStrings.explainInputHandling), 'Does the script handle user input safely',
987984
openAiAssistanceId + '.input');
985+
} else if (Root.Runtime.hostConfig.devToolsAiDebugWithAi?.enabled) {
986+
contextMenu.footerSection().appendAction(
987+
openAiAssistanceId, undefined, false, undefined,
988+
Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.featureName);
988989
} else {
989990
contextMenu.footerSection().appendAction(openAiAssistanceId);
990991
}

front_end/panels/timeline/TimelineFlameChartDataProvider.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ const UIStrings = {
102102
* @description Text for an action that shows all of the hidden entries of the Flame Chart
103103
*/
104104
resetTrace: 'Reset trace',
105-
/**
106-
* @description Context menu text in Performance Panel to that opens a submenu with AI prompts.
107-
*/
108-
debugWithAi: 'Debug with AI',
109105
/**
110106
* @description Text of a context menu item to redirect to the AI assistance panel and to start a chat.
111107
*/
@@ -291,11 +287,12 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
291287
submenu: UI.ContextMenu.SubMenu, action: UI.ActionRegistration.Action,
292288
label: Common.UIString.LocalizedString, prompt: string, jslogContext: string): void {
293289
submenu.defaultSection().appendItem(
294-
label, async () => await action.execute({prompt}), {disabled: !action.enabled(), jslogContext});
290+
label, () => action.execute({prompt}), {disabled: !action.enabled(), jslogContext});
295291
}
296292

297293
const submenu = contextMenu.footerSection().appendSubMenuItem(
298-
i18nString(UIStrings.debugWithAi), false, PERF_AI_ACTION_ID);
294+
action.title(), false, PERF_AI_ACTION_ID,
295+
Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.featureName);
299296
submenu.defaultSection().appendAction(PERF_AI_ACTION_ID, i18nString(UIStrings.startAChat));
300297
submenu.defaultSection().appendItem(i18nString(UIStrings.labelEntry), () => {
301298
this.dispatchEventToListeners(
@@ -312,10 +309,13 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
312309
appendSubmenuPromptAction(
313310
submenu, action, i18nString(UIStrings.findImprovements), 'How can I reduce the time of this call tree?',
314311
PERF_AI_ACTION_ID + '.improvements');
312+
} else if (Root.Runtime.hostConfig.devToolsAiDebugWithAi?.enabled) {
313+
contextMenu.footerSection().appendAction(
314+
PERF_AI_ACTION_ID, undefined, false, undefined,
315+
Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.featureName);
315316
} else {
316317
contextMenu.footerSection().appendAction(PERF_AI_ACTION_ID);
317318
}
318-
UI.Context.Context.instance().setFlavor(Utils.AIContext.AgentFocus, context);
319319
}
320320
}
321321

front_end/panels/timeline/components/insights/BaseInsightComponent.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,11 @@ export abstract class BaseInsightComponent<T extends InsightModel> extends HTMLE
380380
return Lit.nothing;
381381
}
382382

383-
const ariaLabel = `Ask AI about ${insightModel.title} insight`;
383+
const aiLabel = Root.Runtime.hostConfig.devToolsAiDebugWithAi?.enabled ||
384+
Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.enabled ?
385+
'Debug with AI' :
386+
'Ask AI';
387+
const ariaLabel = `${aiLabel} about ${insightModel.title} insight`;
384388
// Only render the insight body content if it is selected.
385389
// To avoid re-rendering triggered from elsewhere.
386390
const content = this.renderContent();
@@ -398,7 +402,7 @@ export abstract class BaseInsightComponent<T extends InsightModel> extends HTMLE
398402
jslog=${VisualLogging.action(`timeline.insight-ask-ai.${this.internalName}`).track({click: true})}
399403
@click=${this.#askAIButtonClick}
400404
aria-label=${ariaLabel}
401-
>Ask AI</devtools-button>
405+
>${aiLabel}</devtools-button>
402406
</div>
403407
`: Lit.nothing}
404408
</div>`;

front_end/ui/legacy/ContextMenu.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,29 @@ describeWithEnvironment('ContextMenu', () => {
9494
sinon.assert.called(showContextMenuAtPoint);
9595
});
9696

97+
it('records new badge usage', async () => {
98+
sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'isHostedMode').returns(false);
99+
sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'showContextMenuAtPoint');
100+
101+
const event = new Event('contextmenu');
102+
sinon.stub(event, 'target').value(document);
103+
const contextMenu = new UI.ContextMenu.ContextMenu(event);
104+
const submenu1 = contextMenu.defaultSection().appendSubMenuItem('submenu', false, undefined, 'feature1');
105+
submenu1.defaultSection().appendItem('item', () => {}, {featureName: 'feature2'});
106+
submenu1.defaultSection().appendItem('item', () => {});
107+
108+
const submenu2 = contextMenu.defaultSection().appendSubMenuItem('submenu2', false, undefined, 'feature3');
109+
submenu2.defaultSection().appendItem('item', () => {}, {featureName: 'feature4'});
110+
const item = submenu2.defaultSection().appendItem('item', () => {}, {featureName: 'feature5'});
111+
112+
await contextMenu.show();
113+
const newBadgeUsageStub =
114+
sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordNewBadgeUsage');
115+
Host.InspectorFrontendHost.InspectorFrontendHostInstance.events.dispatchEventToListeners(
116+
Host.InspectorFrontendHostAPI.Events.ContextMenuItemSelected, item.id());
117+
assert.deepEqual(newBadgeUsageStub.args, [['feature5'], ['feature3']]);
118+
});
119+
97120
it('logs impressions and clicks for hosted menu', async () => {
98121
const throttler = new Common.Throttler.Throttler(1000000000);
99122
await VisualLogging.startLogging({processingThrottler: throttler});

0 commit comments

Comments
 (0)