Skip to content

Commit 34aaed6

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
RPP: add auto-annotation label generation
Fixed: 405061899 Change-Id: Idf06927221664dfa45a1ba3215fb5f2a6ad99084 Also-by: [email protected] Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6381853 Reviewed-by: Ergün Erdoğmuş <[email protected]> Commit-Queue: Jack Franklin <[email protected]> Auto-Submit: Jack Franklin <[email protected]>
1 parent ec072de commit 34aaed6

File tree

11 files changed

+157
-21
lines changed

11 files changed

+157
-21
lines changed

front_end/models/ai_assistance/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ devtools_entrypoint("bundle") {
5656
":*",
5757
"../../entrypoints/main/*",
5858
"../../panels/ai_assistance/*",
59+
"../../panels/timeline/*",
5960
]
6061

6162
visibility += devtools_models_visibility

front_end/models/ai_assistance/agents/PerformanceAgent.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,22 @@ self: 3
226226
assert.isFalse(enhancedQuery3.includes(mockAiCallTree.serialize()));
227227
});
228228
});
229+
230+
describe('generating an AI entry label', () => {
231+
it('generates a label from the final answer and trims newlines', async function() {
232+
const agent = new PerformanceAgent({
233+
aidaClient: mockAidaClient([[{
234+
explanation: 'hello world\n',
235+
}]]),
236+
});
237+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
238+
const evalScriptEvent = parsedTrace.Renderer.allTraceEntries.find(
239+
event => event.name === Trace.Types.Events.Name.EVALUATE_SCRIPT && event.ts === 122411195649);
240+
assert.exists(evalScriptEvent);
241+
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(evalScriptEvent, parsedTrace);
242+
assert.isOk(aiCallTree);
243+
const label = await agent.generateAIEntryLabel(aiCallTree);
244+
assert.strictEqual(label, 'hello world');
245+
});
246+
});
229247
});

front_end/models/ai_assistance/agents/PerformanceAgent.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,22 @@ export class PerformanceAgent extends AiAgent<TimelineUtils.AICallTree.AICallTre
245245
const perfEnhancementQuery = treeStr ? `${treeStr}\n\n# User request\n\n` : '';
246246
return `${perfEnhancementQuery}${query}`;
247247
}
248+
249+
/**
250+
* Used in the Performance panel to automatically generate a label for a selected entry.
251+
*/
252+
async generateAIEntryLabel(callTree: TimelineUtils.AICallTree.AICallTree): Promise<string> {
253+
const context = new CallTreeContext(callTree);
254+
const response = await Array.fromAsync(this.run(AI_LABEL_GENERATION_PROMPT, {selected: context}));
255+
const lastResponse = response.at(-1);
256+
if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
257+
return lastResponse.text.trim();
258+
}
259+
throw new Error('Failed to generate AI entry label');
260+
}
248261
}
262+
263+
const AI_LABEL_GENERATION_PROMPT =
264+
`Generate a very short label for the selected callframe of only a few words describing what the callframe is broadly doing, but provide the most important information for debugging performance.
265+
266+
Important: Describe selected callframe in just 1 sentence under 80 characters without line breaks. We will use your response for this callframe annotation so start with the sentence directly with what the callframe is doing and do not return any other text.`;

front_end/panels/settings/AISettingsTab.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ export class AISettingsTab extends LegacyWrapper.LegacyWrapper.WrappableComponen
342342
text: noLogging ? i18nString(UIStrings.generatedAiAnnotationsSendDataNoLogging) :
343343
i18nString(UIStrings.generatedAiAnnotationsSendData)
344344
}],
345-
// TODO: Add a relevant link
345+
// TODO(b/405316456): Add a relevant link here once we have written the documentation.
346346
learnMoreLink: {url: '', linkJSLogContext: 'learn-more.ai-annotations'},
347347
settingExpandState: {
348348
isSettingExpanded: false,

front_end/panels/timeline/TimelineFlameChartView.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
273273
networkProvider: this.networkDataProvider,
274274
},
275275
entryQueries: {
276+
parsedTrace: () => {
277+
return this.#parsedTrace;
278+
},
276279
isEntryCollapsedByUser: (entry: Trace.Types.Events.Event): boolean => {
277280
return ModificationsManager.activeManager()?.getEntriesFilter().entryIsInvisible(entry) ?? false;
278281
},

front_end/panels/timeline/overlays/BUILD.gn

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ devtools_module("overlays") {
1010
sources = [ "OverlaysImpl.ts" ]
1111

1212
deps = [
13+
"../../../core/common:bundle",
14+
"../../../core/i18n:bundle",
1315
"../../../core/platform:bundle",
1416
"../../../models/trace:bundle",
1517
"../../../services/trace_bounds:bundle",
1618
"../../../ui/legacy/components/perf_ui:bundle",
17-
"../../../ui/lit:bundle",
18-
"../../timeline/utils:bundle",
19+
"../../../ui/visual_logging:bundle",
20+
"../utils:bundle",
1921
"./components:bundle",
2022
]
2123
}

front_end/panels/timeline/overlays/OverlaysImpl.test.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// found in the LICENSE file.
44

55
import * as Common from '../../../core/common/common.js';
6+
import * as AiAssistanceModels from '../../../models/ai_assistance/ai_assistance.js';
67
import * as Trace from '../../../models/trace/trace.js';
8+
import {mockAidaClient} from '../../../testing/AiAssistanceHelpers.js';
79
import {cleanTextContent, dispatchClickEvent} from '../../../testing/DOMHelpers.js';
810
import {describeWithEnvironment, updateHostConfig} from '../../../testing/EnvironmentHelpers.js';
911
import {
@@ -22,6 +24,9 @@ import * as Components from './components/components.js';
2224
import * as Overlays from './overlays.js';
2325

2426
const FAKE_OVERLAY_ENTRY_QUERIES: Overlays.Overlays.OverlayEntryQueries = {
27+
parsedTrace() {
28+
return null;
29+
},
2530
isEntryCollapsedByUser() {
2631
return false;
2732
},
@@ -277,7 +282,12 @@ describeWithEnvironment('Overlays', () => {
277282
network: networkFlameChartsContainer,
278283
},
279284
charts,
280-
entryQueries: FAKE_OVERLAY_ENTRY_QUERIES,
285+
entryQueries: {
286+
...FAKE_OVERLAY_ENTRY_QUERIES,
287+
parsedTrace() {
288+
return parsedTrace;
289+
},
290+
},
281291
});
282292
const currManager = Timeline.ModificationsManager.ModificationsManager.activeManager();
283293
// The Annotations Overlays are added through the ModificationsManager listener
@@ -322,6 +332,7 @@ describeWithEnvironment('Overlays', () => {
322332
inputField: HTMLElement,
323333
overlays: Overlays.Overlays.Overlays,
324334
event: Trace.Types.Events.Event,
335+
component: Components.EntryLabelOverlay.EntryLabelOverlay,
325336
}> {
326337
updateHostConfig({
327338
devToolsAiGeneratedTimelineLabels: {
@@ -341,6 +352,7 @@ describeWithEnvironment('Overlays', () => {
341352
label: label ?? '',
342353
});
343354
await overlays.update();
355+
await RenderCoordinator.done();
344356

345357
// Ensure that the overlay was created.
346358
const overlayDOM = container.querySelector<HTMLElement>('.overlay-type-ENTRY_LABEL');
@@ -353,7 +365,7 @@ describeWithEnvironment('Overlays', () => {
353365
const inputField = elementsWrapper.querySelector<HTMLElement>('.input-field');
354366
assert.isOk(inputField);
355367

356-
return {elementsWrapper, inputField, overlays, event};
368+
return {elementsWrapper, inputField, overlays, event, component};
357369
}
358370

359371
it('can render an entry selected overlay', async function() {
@@ -483,6 +495,7 @@ describeWithEnvironment('Overlays', () => {
483495

484496
// Double click on the label box to make it editable and focus on it
485497
inputField.dispatchEvent(new FocusEvent('dblclick', {bubbles: true}));
498+
await RenderCoordinator.done();
486499

487500
const aiLabelButtonWrapper =
488501
elementsWrapper.querySelector<HTMLElement>('.ai-label-button-wrapper') as HTMLSpanElement;
@@ -493,6 +506,7 @@ describeWithEnvironment('Overlays', () => {
493506
// This dialog should not be visible unless the `generate annotation` button is clicked
494507
assert.isFalse(showFreDialogStub.called, 'Expected FreDialog to be not shown but it\'s shown');
495508
aiButton.dispatchEvent(new FocusEvent('click', {bubbles: true}));
509+
await RenderCoordinator.done();
496510

497511
// This dialog should be visible
498512
assert.isTrue(showFreDialogStub.called, 'Expected FreDialog to be shown but it\'s not shown');
@@ -558,6 +572,35 @@ describeWithEnvironment('Overlays', () => {
558572
assert.strictEqual(inputField?.innerText, 'entry label');
559573
});
560574

575+
it('generates a label when the user clicks "Generate" if the setting is enabled', async function() {
576+
const {elementsWrapper, inputField, component} = await createAnnotationsLabelElement(this, 'web-dev.json.gz', 50);
577+
Common.Settings.moduleSetting('ai-annotations-enabled').set(true);
578+
579+
const generateButton = elementsWrapper.querySelector<HTMLElement>('.ai-label-button');
580+
assert.isOk(generateButton, 'could not find "Generate label" button');
581+
assert.isTrue(generateButton.classList.contains('enabled'));
582+
const agent = new AiAssistanceModels.PerformanceAgent({
583+
aidaClient: mockAidaClient([[{
584+
explanation: 'This is an interesting entry',
585+
metadata: {
586+
rpcGlobalId: 123,
587+
}
588+
}]])
589+
});
590+
component.overrideAIAgentForTest(agent);
591+
592+
// The Agent call is async, so wait for the change event on the label to ensure the UI is updated.
593+
const changeEvent = new Promise<void>(resolve => {
594+
component.addEventListener(
595+
Components.EntryLabelOverlay.EntryLabelChangeEvent.eventName, () => resolve(), {once: true});
596+
});
597+
dispatchClickEvent(generateButton);
598+
await RenderCoordinator.done();
599+
await changeEvent;
600+
601+
assert.strictEqual(inputField.innerHTML, 'This is an interesting entry');
602+
});
603+
561604
it('Correct security tooltip on the `generate ai label` info icon hover for the users with logging enabled',
562605
async function() {
563606
const {elementsWrapper, inputField} =

front_end/panels/timeline/overlays/OverlaysImpl.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as Platform from '../../../core/platform/platform.js';
77
import * as Trace from '../../../models/trace/trace.js';
88
import type * as PerfUI from '../../../ui/legacy/components/perf_ui/perf_ui.js';
99
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
10-
import {EntryStyles} from '../../timeline/utils/utils.js';
10+
import * as Utils from '../utils/utils.js';
1111

1212
import * as Components from './components/components.js';
1313

@@ -386,6 +386,7 @@ export interface TimelineCharts {
386386
}
387387

388388
export interface OverlayEntryQueries {
389+
parsedTrace: () => Trace.Handlers.Types.ParsedTrace | null;
389390
isEntryCollapsedByUser: (entry: Trace.Types.Events.Event) => boolean;
390391
firstVisibleParentForEntry: (entry: Trace.Types.Events.Event) => Trace.Types.Events.Event | null;
391392
}
@@ -1528,6 +1529,11 @@ export class Overlays extends EventTarget {
15281529
case 'ENTRY_LABEL': {
15291530
const shouldDrawLabelBelowEntry = Trace.Types.Events.isLegacyTimelineFrame(overlay.entry);
15301531
const component = new Components.EntryLabelOverlay.EntryLabelOverlay(overlay.label, shouldDrawLabelBelowEntry);
1532+
// Generate the AI Call Tree for the AI Auto-Annotation feature.
1533+
const parsedTrace = this.#queries.parsedTrace();
1534+
const callTree = parsedTrace ? Utils.AICallTree.AICallTree.fromEvent(overlay.entry, parsedTrace) : null;
1535+
component.callTree = callTree;
1536+
15311537
component.addEventListener(Components.EntryLabelOverlay.EmptyEntryLabelRemoveEvent.eventName, () => {
15321538
this.dispatchEvent(new AnnotationOverlayActionEvent(overlay, 'Remove'));
15331539
});
@@ -1602,7 +1608,7 @@ export class Overlays extends EventTarget {
16021608
return overlayElement;
16031609
}
16041610
case 'TIMINGS_MARKER': {
1605-
const {color} = EntryStyles.markerDetailsForEvent(overlay.entries[0]);
1611+
const {color} = Utils.EntryStyles.markerDetailsForEvent(overlay.entries[0]);
16061612
const markersComponent = this.#createTimingsMarkerElement(overlay);
16071613
overlayElement.appendChild(markersComponent);
16081614
overlayElement.style.backgroundColor = color;
@@ -1671,7 +1677,7 @@ export class Overlays extends EventTarget {
16711677
const markers = document.createElement('div');
16721678
markers.classList.add('markers');
16731679
for (const entry of overlay.entries) {
1674-
const {color, title} = EntryStyles.markerDetailsForEvent(entry);
1680+
const {color, title} = Utils.EntryStyles.markerDetailsForEvent(entry);
16751681
const marker = document.createElement('div');
16761682
marker.classList.add('marker-title');
16771683
marker.textContent = title;

front_end/panels/timeline/overlays/components/BUILD.gn

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@ devtools_module("components") {
2525
]
2626

2727
deps = [
28+
"../../../../core/host:bundle",
2829
"../../../../core/i18n:bundle",
30+
"../../../../core/platform:bundle",
31+
"../../../../core/root:bundle",
32+
"../../../../models/ai_assistance:bundle",
2933
"../../../../models/trace:bundle",
3034
"../../../../panels/common:bundle",
3135
"../../../../ui/components/helpers:bundle",
3236
"../../../../ui/components/icon_button:bundle",
3337
"../../../../ui/components/tooltips:bundle",
38+
"../../../../ui/legacy/theme_support:bundle",
3439
"../../../../ui/lit:bundle",
3540
"../../../../ui/visual_logging:bundle",
41+
"../../../common:bundle",
42+
"../../utils:bundle",
3643
]
3744
}
3845

0 commit comments

Comments
 (0)