chrome-devtools-frontend 1.0.1605219 → 1.0.1606789

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/front_end/Images/src/dots-circle.svg +10 -0
  2. package/front_end/core/host/UserMetrics.ts +0 -1
  3. package/front_end/core/platform/api/HostRuntime.ts +9 -6
  4. package/front_end/core/platform/browser/HostRuntime.ts +2 -2
  5. package/front_end/core/platform/node/HostRuntime.ts +7 -7
  6. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  7. package/front_end/core/root/ExperimentNames.ts +0 -1
  8. package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
  9. package/front_end/core/sdk/sdk.ts +2 -2
  10. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
  11. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
  12. package/front_end/entrypoints/main/MainImpl.ts +0 -101
  13. package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
  14. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
  15. package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -25
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  17. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
  18. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +10 -9
  19. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +313 -0
  20. package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
  21. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +19 -8
  23. package/front_end/models/ai_assistance/agents/StylingAgent.ts +35 -278
  24. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
  26. package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
  27. package/front_end/models/web_mcp/web_mcp.ts +9 -0
  28. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
  29. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  30. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
  31. package/front_end/panels/ai_assistance/components/ChatMessage.ts +149 -49
  32. package/front_end/panels/ai_assistance/components/ChatView.ts +11 -15
  33. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +13 -4
  34. package/front_end/panels/ai_assistance/components/chatMessage.css +53 -1
  35. package/front_end/panels/ai_assistance/components/chatView.css +0 -10
  36. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
  37. package/front_end/panels/application/WebMCPView.ts +471 -25
  38. package/front_end/panels/application/webMCPView.css +53 -2
  39. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  40. package/front_end/panels/elements/ElementsTreeOutline.ts +87 -0
  41. package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
  42. package/front_end/panels/elements/elements.ts +3 -0
  43. package/front_end/panels/elements/elementsTreeOutline.css +21 -0
  44. package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
  45. package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
  46. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
  47. package/front_end/panels/profiler/HeapDetachedElementsView.ts +0 -4
  48. package/front_end/panels/profiler/HeapProfileView.ts +0 -4
  49. package/front_end/panels/profiler/HeapProfilerPanel.ts +4 -13
  50. package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
  51. package/front_end/panels/profiler/HeapSnapshotView.ts +19 -32
  52. package/front_end/panels/profiler/ProfileHeader.ts +1 -15
  53. package/front_end/panels/profiler/ProfileTypeRegistry.ts +4 -12
  54. package/front_end/panels/profiler/ProfileView.ts +1 -6
  55. package/front_end/panels/profiler/ProfilesPanel.ts +60 -7
  56. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
  57. package/front_end/panels/timeline/EventsTimelineTreeView.ts +1 -1
  58. package/front_end/panels/timeline/ThirdPartyTreeView.ts +2 -2
  59. package/front_end/panels/timeline/TimelinePanel.ts +11 -153
  60. package/front_end/panels/timeline/TimelineTreeView.ts +147 -48
  61. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
  62. package/front_end/panels/timeline/components/CWVMetrics.ts +18 -2
  63. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  64. package/front_end/panels/timeline/utils/Helpers.ts +5 -0
  65. package/front_end/third_party/chromium/README.chromium +1 -1
  66. package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
  67. package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
  68. package/front_end/ui/legacy/FilterBar.ts +2 -0
  69. package/front_end/ui/legacy/SplitWidget.ts +33 -27
  70. package/front_end/ui/legacy/TabbedPane.ts +123 -3
  71. package/front_end/ui/legacy/Widget.ts +95 -48
  72. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
  73. package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
  74. package/front_end/ui/legacy/infobar.css +1 -0
  75. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -1
  76. package/package.json +1 -1
  77. package/front_end/core/sdk/WebMCPModel.ts +0 -104
  78. package/front_end/models/ai_assistance/ConversationHandler.ts +0 -325
@@ -0,0 +1,88 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as SDK from '../../../core/sdk/sdk.js';
6
+ import type * as Protocol from '../../../generated/protocol.js';
7
+ import type * as Marked from '../../../third_party/marked/marked.js';
8
+ import * as Lit from '../../../ui/lit/lit.js';
9
+ import * as PanelsCommon from '../../common/common.js';
10
+
11
+ import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
12
+
13
+ const {html} = Lit.StaticHtml;
14
+ const {until} = Lit.Directives;
15
+
16
+ export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
17
+ constructor(
18
+ private mainFrameId = '',
19
+ ) {
20
+ super();
21
+ }
22
+
23
+ override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
24
+ if (token.type === 'link' && token.href.startsWith('#')) {
25
+ if (token.href.startsWith('#path-')) {
26
+ const path = token.href.replace('#path-', '');
27
+ return html`<span>${
28
+ until(this.#linkifyPath(path, token.text).then(node => node || token.text), token.text)}</span>`;
29
+ }
30
+
31
+ let nodeId = undefined;
32
+ if (token.href.startsWith('#node-')) {
33
+ nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
34
+ } else if (token.href.startsWith('#')) {
35
+ nodeId = Number(token.href.replace('#', '')) as Protocol.DOM.BackendNodeId;
36
+ }
37
+
38
+ if (nodeId) {
39
+ return html`<span>${
40
+ until(this.#linkifyNode(nodeId, token.text).then(node => node || token.text), token.text)}</span>`;
41
+ }
42
+ }
43
+
44
+ return super.templateForToken(token);
45
+ }
46
+
47
+ async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Lit.LitTemplate|undefined> {
48
+ if (backendNodeId === undefined) {
49
+ return;
50
+ }
51
+
52
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
53
+ const domModel = target?.model(SDK.DOMModel.DOMModel);
54
+ if (!domModel) {
55
+ return undefined;
56
+ }
57
+ const domNodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
58
+ const node = domNodesMap?.get(backendNodeId);
59
+ if (!node) {
60
+ return;
61
+ }
62
+
63
+ if (node.frameId() !== this.mainFrameId) {
64
+ return;
65
+ }
66
+
67
+ const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
68
+ return linkedNode;
69
+ }
70
+
71
+ async #linkifyPath(path: string, label: string): Promise<Lit.LitTemplate|undefined> {
72
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
73
+ const domModel = target?.model(SDK.DOMModel.DOMModel);
74
+ if (!domModel) {
75
+ return undefined;
76
+ }
77
+ const nodeId = await domModel.pushNodeByPathToFrontend(path);
78
+ if (!nodeId) {
79
+ return;
80
+ }
81
+ const node = domModel.nodeForId(nodeId);
82
+ if (!node) {
83
+ return;
84
+ }
85
+ const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
86
+ return linkedNode;
87
+ }
88
+ }
@@ -13,8 +13,9 @@ import * as Root from '../../../core/root/root.js';
13
13
  import * as SDK from '../../../core/sdk/sdk.js';
14
14
  import type * as Protocol from '../../../generated/protocol.js';
15
15
  import type {
16
- AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LcpBreakdownAiWidget, PerformanceTraceAiWidget,
17
- StylePropertiesAiWidget, TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
16
+ AiWidget, BottomUpTreeAiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LcpBreakdownAiWidget,
17
+ PerformanceTraceAiWidget, StylePropertiesAiWidget,
18
+ TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
18
19
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
19
20
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
20
21
  import * as Trace from '../../../models/trace/trace.js';
@@ -44,6 +45,7 @@ const {widget} = UI.Widget;
44
45
 
45
46
  const REPORT_URL = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
46
47
  const SCROLL_ROUNDING_OFFSET = 1;
48
+ const MAX_NUM_LINES_IN_CODEBLOCK = 11;
47
49
 
48
50
  /*
49
51
  * Strings that don't need to be translated at this time.
@@ -193,7 +195,15 @@ const UIStringsNotTranslate = {
193
195
  /**
194
196
  * @description Title for the performance summary widget.
195
197
  */
196
- performanceSummary: 'Performance summary'
198
+ performanceSummary: 'Performance summary',
199
+ /**
200
+ * @description The title of the button that allows exporting the conversation for agents.
201
+ */
202
+ exportForAgents: 'Copy for your coding agent',
203
+ /**
204
+ * @description Title for the bottom up thread activity widget.
205
+ */
206
+ bottomUpTree: 'Bottom-up thread activity',
197
207
  } as const;
198
208
 
199
209
  export interface Step {
@@ -260,6 +270,7 @@ export interface RatingViewInput {
260
270
  export interface ActionViewInput {
261
271
  onReportClick: () => void;
262
272
  onCopyResponseClick: () => void;
273
+ onExportClick?: () => void;
263
274
  showActions: boolean;
264
275
  }
265
276
 
@@ -293,11 +304,14 @@ export interface MessageInput {
293
304
  isLoading: boolean;
294
305
  isReadOnly: boolean;
295
306
  isLastMessage: boolean;
307
+ isFirstMessage: boolean;
296
308
  canShowFeedbackForm: boolean;
297
309
  markdownRenderer: MarkdownLitRenderer;
298
310
  onSuggestionClick: (suggestion: string) => void;
299
311
  onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void;
300
312
  onCopyResponseClick: (message: ModelChatMessage) => void;
313
+ onExportClick?: () => void;
314
+ changeSummary?: string;
301
315
  walkthrough: {
302
316
  onOpen: (message: ModelChatMessage) => void,
303
317
  isExpanded: boolean,
@@ -309,22 +323,37 @@ export interface MessageInput {
309
323
  }
310
324
 
311
325
  export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, target: HTMLElement): void => {
326
+ const hasAiV2 = Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
312
327
  const message = input.message;
328
+
313
329
  if (message.entity === ChatMessageEntity.USER) {
314
330
  const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
315
331
  renderImageChatMessage(message.imageInput.inlineData) :
316
332
  Lit.nothing;
333
+
334
+ const messageClasses = Lit.Directives.classMap({
335
+ 'chat-message': true,
336
+ query: true,
337
+ 'is-last-message': input.isLastMessage,
338
+ 'is-first-message': input.isFirstMessage,
339
+ 'ai-v2': hasAiV2,
340
+ });
341
+
342
+ const userQueryWrapperClasses = Lit.Directives.classMap({
343
+ // Don't need to style at all unless we are on the V2 flag.
344
+ // Once we ship this can be removed entirely.
345
+ 'user-query-wrapper': hasAiV2
346
+ });
317
347
  // clang-format off
318
348
  Lit.render(html`
319
349
  <style>${Input.textInputStyles}</style>
320
350
  <style>${chatMessageStyles}</style>
321
- <section
322
- class="chat-message query ${input.isLastMessage ? 'is-last-message' : ''}"
323
- jslog=${VisualLogging.section('question')}
324
- >
325
- ${imageInput}
326
- <div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
327
- </section>
351
+ <div class=${userQueryWrapperClasses}>
352
+ <section class=${messageClasses} jslog=${VisualLogging.section('question')}>
353
+ ${imageInput}
354
+ <div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
355
+ </section>
356
+ </div>
328
357
  `, target);
329
358
  // clang-format on
330
359
  return;
@@ -332,48 +361,62 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
332
361
 
333
362
  const steps = message.parts.filter(part => part.type === 'step').map(part => part.step);
334
363
  const icon = AiAssistanceModel.AiUtils.getIconName();
335
- const aiAssistanceV2 = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
336
364
 
365
+ const messageClasses = Lit.Directives.classMap({
366
+ 'chat-message': true,
367
+ answer: true,
368
+ 'is-last-message': input.isLastMessage,
369
+ 'is-first-message': input.isFirstMessage,
370
+ 'ai-v2': hasAiV2,
371
+ });
337
372
  // clang-format off
338
373
  Lit.render(html`
339
374
  <style>${Input.textInputStyles}</style>
340
375
  <style>${chatMessageStyles}</style>
341
- <section
342
- class="chat-message answer ${input.isLastMessage ? 'is-last-message' : ''}"
343
- jslog=${VisualLogging.section('answer')}
344
- >
345
- ${aiAssistanceV2 ? Lit.nothing : html`
376
+ <section class=${messageClasses} jslog=${VisualLogging.section('answer')}>
377
+ ${hasAiV2 ? Lit.nothing : html`
346
378
  <div class="message-info">
347
379
  <devtools-icon name=${icon}></devtools-icon>
348
380
  <div class="message-name">
349
381
  <h2>${AiAssistanceModel.AiUtils.isGeminiBranding() ? lockedString(UIStringsNotTranslate.gemini) : lockedString(UIStringsNotTranslate.ai)}</h2>
350
382
  </div>
351
383
  </div>`}
352
- ${aiAssistanceV2 ? renderWalkthroughUI(input, steps) : Lit.nothing}
353
- ${Lit.Directives.repeat(
354
- message.parts,
355
- (_, index) => index,
356
- (part, index) => {
357
- const isLastPart = index === message.parts.length - 1;
358
- if (part.type === 'answer') {
359
- return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
360
- }
361
- if (part.type === 'widget') {
362
- return html`${Lit.Directives.until(renderWidgets(part.widgets, {wrapperClass: 'main-widgets-wrapper'}))}`;
363
- }
364
- if (!aiAssistanceV2 && part.type === 'step') {
365
- return renderStep({
366
- step: part.step,
367
- isLoading: input.isLoading,
368
- markdownRenderer: input.markdownRenderer,
369
- isLast: isLastPart,
370
- });
371
- }
372
- return Lit.nothing;
373
- },
374
- )}
375
- ${renderError(message)}
376
- ${input.showActions ? renderActions(input, output) : Lit.nothing}
384
+ ${hasAiV2 ? renderWalkthroughUI(input, steps) : Lit.nothing}
385
+ <div class="answer-body-wrapper">
386
+ ${Lit.Directives.repeat(
387
+ message.parts,
388
+ (_, index) => index,
389
+ (part, index) => {
390
+ const isLastPart = index === message.parts.length - 1;
391
+ if (part.type === 'answer') {
392
+ return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
393
+ }
394
+ if (part.type === 'widget') {
395
+ return html`${Lit.Directives.until(renderWidgets(part.widgets, {wrapperClass: 'main-widgets-wrapper'}))}`;
396
+ }
397
+ if (!hasAiV2 && part.type === 'step') {
398
+ return renderStep({
399
+ step: part.step,
400
+ isLoading: input.isLoading,
401
+ markdownRenderer: input.markdownRenderer,
402
+ isLast: isLastPart,
403
+ });
404
+ }
405
+ return Lit.nothing;
406
+ },
407
+ )}
408
+ ${renderError(message)}
409
+ ${input.isLastMessage && hasAiV2 && !input.isLoading && input.changeSummary ? html`
410
+ <devtools-code-block
411
+ .code=${input.changeSummary}
412
+ .codeLang=${'css'}
413
+ .displayLimit=${MAX_NUM_LINES_IN_CODEBLOCK}
414
+ .displayNotice=${true}
415
+ class="ai-css-change"
416
+ ></devtools-code-block>
417
+ ` : Lit.nothing}
418
+ ${input.showActions ? renderActions(input, output) : Lit.nothing}
419
+ </div>
377
420
  </section>
378
421
  `, target);
379
422
  // clang-format on
@@ -439,6 +482,7 @@ function renderStepCode(step: Step): Lit.LitTemplate {
439
482
  <devtools-code-block
440
483
  .code=${step.code.trim()}
441
484
  .codeLang=${'js'}
485
+ .displayLimit=${MAX_NUM_LINES_IN_CODEBLOCK}
442
486
  .displayNotice=${!Boolean(step.output)}
443
487
  .header=${codeHeadingText}
444
488
  .showCopyButton=${true}
@@ -449,6 +493,7 @@ function renderStepCode(step: Step): Lit.LitTemplate {
449
493
  <devtools-code-block
450
494
  .code=${step.output}
451
495
  .codeLang=${'js'}
496
+ .displayLimit=${MAX_NUM_LINES_IN_CODEBLOCK}
452
497
  .displayNotice=${true}
453
498
  .header=${lockedString(UIStringsNotTranslate.dataReturned)}
454
499
  .showCopyButton=${false}
@@ -481,6 +526,7 @@ function renderStepDetails({
481
526
  <devtools-code-block
482
527
  .code=${contextDetail.text}
483
528
  .codeLang=${contextDetail.codeLang || ''}
529
+ .displayLimit=${MAX_NUM_LINES_IN_CODEBLOCK}
484
530
  .displayNotice=${false}
485
531
  .header=${contextDetail.title}
486
532
  .showCopyButton=${true}
@@ -739,10 +785,9 @@ async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promi
739
785
  };
740
786
  }
741
787
 
742
- async function makeCoreVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<WidgetMakerResponse|null> {
788
+ async function makeCoreWebVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<WidgetMakerResponse|null> {
743
789
  // clang-format off
744
- const renderedWidget = html`<devtools-widget
745
- class="core-vitals-widget" ${widget(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data})}>
790
+ const renderedWidget = html`<devtools-widget class="core-vitals-widget" ${widget(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data, skipBottomBorder: true})}>
746
791
  </devtools-widget>`;
747
792
  // clang-format on
748
793
 
@@ -802,6 +847,34 @@ async function makeLcpBreakdownWidget(widgetData: LcpBreakdownAiWidget): Promise
802
847
  };
803
848
  }
804
849
 
850
+ async function makeBottomUpTimelineTreeWidget(widgetData: BottomUpTreeAiWidget): Promise<WidgetMakerResponse|null> {
851
+ const bottomUpRootNode = AiAssistanceModel.AIQueries.AIQueries.mainThreadActivityBottomUp(
852
+ widgetData.data.bounds, widgetData.data.parsedTrace);
853
+ if (!bottomUpRootNode) {
854
+ return null;
855
+ }
856
+ const events = bottomUpRootNode.events;
857
+ const startTime = Trace.Helpers.Timing.microToMilli(widgetData.data.bounds.min);
858
+ const endTime = Trace.Helpers.Timing.microToMilli(widgetData.data.bounds.max);
859
+
860
+ const renderedWidget = html`<devtools-widget
861
+ class="bottom-up-timeline-tree-widget"
862
+ ${widget(Timeline.TimelineTreeView.BottomUpTimelineTreeView, {
863
+ selectedEvents: events,
864
+ parsedTrace: widgetData.data.parsedTrace,
865
+ startTime,
866
+ endTime,
867
+ compactMode: true,
868
+ maxLinkLength: 15,
869
+ })}></devtools-widget>`;
870
+
871
+ return {
872
+ renderedWidget,
873
+ revealable: new TimelineUtils.Helpers.RevealableBottomUpProfile(widgetData.data.bounds),
874
+ title: lockedString(UIStringsNotTranslate.bottomUpTree)
875
+ };
876
+ }
877
+
805
878
  function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
806
879
  if (response === null) {
807
880
  return Lit.nothing;
@@ -911,6 +984,7 @@ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMak
911
984
  rootDOMNode: root,
912
985
  visibleWidth: 400,
913
986
  wrap: true,
987
+ maxRows: 10,
914
988
  })}></devtools-widget>
915
989
  `;
916
990
  // clang-format on
@@ -951,7 +1025,7 @@ async function renderWidgets(
951
1025
  response = await makeComputedStyleWidget(widgetData);
952
1026
  break;
953
1027
  case 'CORE_VITALS':
954
- response = await makeCoreVitalsWidget(widgetData);
1028
+ response = await makeCoreWebVitalsWidget(widgetData);
955
1029
  break;
956
1030
  case 'STYLE_PROPERTIES':
957
1031
  response = await makeStylePropertiesWidget(widgetData);
@@ -968,6 +1042,9 @@ async function renderWidgets(
968
1042
  case 'TIMELINE_RANGE_SUMMARY':
969
1043
  response = await makeTimelineRangeSummaryWidget(widgetData);
970
1044
  break;
1045
+ case 'BOTTOM_UP_TREE':
1046
+ response = await makeBottomUpTimelineTreeWidget(widgetData);
1047
+ break;
971
1048
  default:
972
1049
  Platform.assertNever(widgetData, 'Unknown AiWidget name');
973
1050
  }
@@ -1066,9 +1143,14 @@ function renderImageChatMessage(inlineData: Host.AidaClient.MediaBlob): Lit.LitT
1066
1143
  }
1067
1144
 
1068
1145
  function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.LitTemplate {
1146
+ const aiAssistanceV2 = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
1147
+ const rowClasses = Lit.Directives.classMap({
1148
+ 'ai-assistance-feedback-row': true,
1149
+ 'not-v2': !aiAssistanceV2,
1150
+ });
1069
1151
  // clang-format off
1070
1152
  return html`
1071
- <div class="ai-assistance-feedback-row">
1153
+ <div class=${rowClasses}>
1072
1154
  <div class="action-buttons">
1073
1155
  ${input.showRateButtons ? html`
1074
1156
  <devtools-button
@@ -1097,7 +1179,7 @@ function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.Lit
1097
1179
  } as Buttons.Button.ButtonData}
1098
1180
  @click=${() => input.onRatingClick(Host.AidaClient.Rating.NEGATIVE)}
1099
1181
  ></devtools-button>
1100
- <div class="vertical-separator"></div>
1182
+ ${aiAssistanceV2 ? Lit.nothing : html`<div class="vertical-separator"></div>`}
1101
1183
  `: Lit.nothing}
1102
1184
  <devtools-button
1103
1185
  .data=${
@@ -1111,7 +1193,8 @@ function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.Lit
1111
1193
  }
1112
1194
  @click=${input.onReportClick}
1113
1195
  ></devtools-button>
1114
- <div class="vertical-separator"></div>
1196
+ ${aiAssistanceV2 ? Lit.nothing : html`
1197
+ <div class="vertical-separator"></div>
1115
1198
  <devtools-button
1116
1199
  .data=${{
1117
1200
  variant: Buttons.Button.Variant.ICON,
@@ -1122,6 +1205,17 @@ function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.Lit
1122
1205
  } as Buttons.Button.ButtonData}
1123
1206
  aria-label=${lockedString(UIStringsNotTranslate.copyResponse)}
1124
1207
  @click=${input.onCopyResponseClick}></devtools-button>
1208
+ `}
1209
+ ${input.onExportClick && aiAssistanceV2 && input.isLastMessage ? html`
1210
+ <devtools-button
1211
+ class="export-for-agents-button"
1212
+ .jslogContext=${'ai-export-for-agents'}
1213
+ .variant=${Buttons.Button.Variant.OUTLINED}
1214
+ .iconName=${'copy'}
1215
+ @click=${input.onExportClick}
1216
+ >${lockedString(UIStringsNotTranslate.exportForAgents)}</devtools-button>
1217
+ ${input.suggestions ? html`<div class="vertical-separator"></div>` : Lit.nothing}
1218
+ ` : Lit.nothing}
1125
1219
  </div>
1126
1220
  ${input.suggestions ? html`<div class="suggestions-container">
1127
1221
  <div class="scroll-button-container left hidden" ${ref(element => { output.suggestionsLeftScrollButtonContainer = element; } )}>
@@ -1225,11 +1319,14 @@ export class ChatMessage extends UI.Widget.Widget {
1225
1319
  isReadOnly = false;
1226
1320
  canShowFeedbackForm = false;
1227
1321
  isLastMessage = false;
1322
+ isFirstMessage = false;
1228
1323
  markdownRenderer!: MarkdownLitRenderer;
1229
1324
  onSuggestionClick: (suggestion: string) => void = () => {};
1230
1325
  onFeedbackSubmit:
1231
1326
  (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void = () => {};
1232
1327
  onCopyResponseClick: (message: ModelChatMessage) => void = () => {};
1328
+ onExportClick: () => void = () => {};
1329
+ changeSummary?: string;
1233
1330
  walkthrough: MessageInput['walkthrough'] = {
1234
1331
  onOpen: () => {},
1235
1332
  onToggle: () => {},
@@ -1272,6 +1369,7 @@ export class ChatMessage extends UI.Widget.Widget {
1272
1369
  canShowFeedbackForm: this.canShowFeedbackForm,
1273
1370
  markdownRenderer: this.markdownRenderer,
1274
1371
  isLastMessage: this.isLastMessage,
1372
+ isFirstMessage: this.isFirstMessage,
1275
1373
  onSuggestionClick: this.onSuggestionClick,
1276
1374
  onRatingClick: this.#handleRateClick.bind(this),
1277
1375
  onReportClick: () => UIHelpers.openInNewTab(REPORT_URL),
@@ -1280,6 +1378,7 @@ export class ChatMessage extends UI.Widget.Widget {
1280
1378
  this.onCopyResponseClick(this.message);
1281
1379
  }
1282
1380
  },
1381
+ onExportClick: this.onExportClick,
1283
1382
  scrollSuggestionsScrollContainer: this.#scrollSuggestionsScrollContainer.bind(this),
1284
1383
  onSuggestionsScrollOrResize: this.#handleSuggestionsScrollOrResize.bind(this),
1285
1384
  onSubmit: this.#handleSubmit.bind(this),
@@ -1296,6 +1395,7 @@ export class ChatMessage extends UI.Widget.Widget {
1296
1395
  currentRating: this.#currentRating,
1297
1396
  isShowingFeedbackForm: this.#isShowingFeedbackForm,
1298
1397
  onFeedbackSubmit: this.onFeedbackSubmit,
1398
+ changeSummary: this.changeSummary,
1299
1399
  walkthrough: this.walkthrough,
1300
1400
  },
1301
1401
  this.#viewOutput, this.contentElement);
@@ -1436,7 +1536,7 @@ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAi
1436
1536
  widget(Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget, {
1437
1537
  maxRows: 10,
1438
1538
  model: {
1439
- selectedEvents: thirdPartyTree.selectedEvents() ?? null,
1539
+ selectedEvents: thirdPartyTree.selectedEvents ?? null,
1440
1540
  parsedTrace,
1441
1541
  entityMapper: thirdPartyTree.entityMapper(),
1442
1542
  },
@@ -104,9 +104,12 @@ interface ChatWidgetInput extends Props {
104
104
  }
105
105
 
106
106
  const DEFAULT_VIEW: View = (input, output, target) => {
107
+ const hasAiV2 = Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
108
+
107
109
  const chatUiClasses = classMap({
108
110
  'chat-ui': true,
109
111
  gemini: AiAssistanceModel.AiUtils.isGeminiBranding(),
112
+ 'ai-v2': hasAiV2,
110
113
  });
111
114
 
112
115
  const inputWidgetClasses = classMap({
@@ -114,8 +117,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
114
117
  sticky: !input.isReadOnly,
115
118
  });
116
119
 
117
- const hasAiV2Flag = Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
118
- const shouldShowExportToAgent = !input.isLoading && input.messages.length > 0 && hasAiV2Flag;
120
+ const shouldShowPatchWidget = !hasAiV2 && !input.isLoading;
119
121
 
120
122
  // clang-format off
121
123
  render(html`
@@ -132,27 +134,21 @@ const DEFAULT_VIEW: View = (input, output, target) => {
132
134
  canShowFeedbackForm: input.canShowFeedbackForm,
133
135
  markdownRenderer: input.markdownRenderer,
134
136
  isLastMessage: input.messages.at(-1) === message,
137
+ isFirstMessage: input.messages.at(0) === message,
135
138
  onSuggestionClick: input.handleSuggestionClick,
136
139
  onFeedbackSubmit: input.onFeedbackSubmit,
137
140
  onCopyResponseClick: input.onCopyResponseClick,
141
+ onExportClick: input.exportForAgentsClick,
142
+ changeSummary: input.changeSummary,
138
143
  walkthrough: {
139
144
  ...input.walkthrough,
140
145
  }
141
146
  })
142
147
  )}
143
- ${shouldShowExportToAgent ? html`
144
- <devtools-button
145
- class="export-for-agents-button"
146
- .jslogContext=${'ai-export-for-agents'}
147
- .variant=${Buttons.Button.Variant.TEXT}
148
- .iconName=${'copy'}
149
- @click=${input.exportForAgentsClick}
150
- >Export for agents</devtools-button>
151
- ` : nothing}
152
- ${input.isLoading ? nothing : widget(PatchWidget, {
153
- changeSummary: input.changeSummary ?? '',
154
- changeManager: input.changeManager,
155
- })}
148
+ ${shouldShowPatchWidget ? widget(PatchWidget, {
149
+ changeSummary: input.changeSummary ?? '',
150
+ changeManager: input.changeManager,
151
+ }) : nothing}
156
152
  </div>
157
153
  ` : html`
158
154
  <div class="empty-state-container">
@@ -2,6 +2,8 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import '../../../ui/components/spinners/spinners.js';
6
+
5
7
  import * as Host from '../../../core/host/host.js';
6
8
  import * as i18n from '../../../core/i18n/i18n.js';
7
9
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
@@ -17,7 +19,7 @@ const UIStrings = {
17
19
  /**
18
20
  * @description Title for the export for agents dialog.
19
21
  */
20
- exportForAgents: 'Export for agents',
22
+ exportForAgents: 'Copy for your coding agent',
21
23
  /**
22
24
  * @description Button text for copying to clipboard.
23
25
  */
@@ -80,6 +82,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
80
82
  i18nString(UIStrings.generatingSummary) :
81
83
  (isPrompt ? input.state.promptText : input.state.conversationText);
82
84
  // clang-format off
85
+
83
86
  render(html`
84
87
  <style>${styles}</style>
85
88
  <div class="export-for-agents-dialog">
@@ -111,9 +114,15 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
111
114
  </label>
112
115
  </div>
113
116
  <main>
114
- <textarea readonly .value=${exportText}></textarea>
117
+ ${input.state.isPromptLoading ? html`
118
+ <span class="prompt-loading">
119
+ <devtools-spinner></devtools-spinner>
120
+ ${i18nString(UIStrings.generatingSummary)}
121
+ </span>
122
+ ` : Lit.nothing}
123
+ <textarea readonly .value=${input.state.isPromptLoading ? '' : exportText}></textarea>
115
124
  </main>
116
- ${isPrompt ? html`<div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>` : Lit.nothing}
125
+ <div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>
117
126
  <footer>
118
127
  <div class="right-buttons">
119
128
  <devtools-button
@@ -156,7 +165,7 @@ export class ExportForAgentsDialog extends UI.Widget.VBox {
156
165
  this.#onConversationSaveAs = options.onConversationSaveAs;
157
166
  this.#view = view;
158
167
 
159
- if (options.promptText instanceof Promise) {
168
+ if (typeof options.promptText !== 'string') {
160
169
  void options.promptText.then(promptText => {
161
170
  this.#state.promptText = promptText;
162
171
  this.#state.isPromptLoading = false;
@@ -9,10 +9,15 @@
9
9
  font-family: var(--default-font-family);
10
10
  width: 100%;
11
11
  display: flex;
12
- gap: var(--sys-size-8);
13
12
  justify-content: space-between;
14
13
  align-items: center;
15
14
  margin-block: calc(-1 * var(--sys-size-3));
15
+ margin-top: var(--sys-size-5);
16
+
17
+ &.not-v2 {
18
+ /* Can be removed when AIv2 ships */
19
+ gap: var(--sys-size-8);
20
+ }
16
21
 
17
22
  .action-buttons {
18
23
  display: flex;
@@ -119,6 +124,13 @@
119
124
  }
120
125
  }
121
126
 
127
+ .user-query-wrapper {
128
+ display: flex;
129
+ justify-content: flex-end;
130
+ padding: 0 var(--sys-size-5);
131
+ align-items: center;
132
+ }
133
+
122
134
  .chat-message {
123
135
  user-select: text;
124
136
  cursor: initial;
@@ -132,6 +144,42 @@
132
144
  overflow-wrap: anywhere;
133
145
  border-bottom: var(--sys-size-1) solid var(--sys-color-divider);
134
146
 
147
+
148
+ &.query.ai-v2 {
149
+ width: fit-content;
150
+ max-width: 80%;
151
+ text-align: left;
152
+ padding: var(--sys-size-4) var(--sys-size-6);
153
+ font: var(--sys-typescale-body4-regular);
154
+ /* top left - top right - bottom right - bottom left */
155
+ border-radius: var(--sys-shape-corner-medium) var(--sys-shape-corner-extra-small) var(--sys-shape-corner-medium) var(--sys-shape-corner-medium);
156
+ background-color: var(--sys-color-surface5);
157
+ color: var(--sys-color-on-surface);
158
+
159
+ &.is-first-message {
160
+ /* So the first message doesn't bump right against the top
161
+ * toolbar */
162
+ margin-top: var(--sys-size-6);
163
+ }
164
+ }
165
+
166
+ &.ai-v2 {
167
+ border-bottom: none;
168
+ }
169
+
170
+ .ai-css-change {
171
+ margin: var(--sys-size-6) 0;
172
+ }
173
+
174
+ &.ai-v2 .answer-body-wrapper {
175
+ @container(min-width: 700px) {
176
+ /* Purposefully not using design system variables, this is a
177
+ * specific size to indent the content in and align it with the
178
+ * walkthrough CTA. */
179
+ padding-left: 35px;
180
+ }
181
+ }
182
+
135
183
  &.is-last-message {
136
184
  border-bottom: 0;
137
185
  }
@@ -395,6 +443,10 @@
395
443
 
396
444
  .widget-name {
397
445
  font: var(--sys-typescale-body4-regular);
446
+ max-width: 80%;
447
+ overflow: hidden;
448
+ text-overflow: ellipsis;
449
+ white-space: nowrap; /* stop the titles going onto multiple lines */
398
450
  }
399
451
 
400
452
  .widget-reveal-container {