chrome-devtools-frontend 1.0.1598808 → 1.0.1601661

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 (161) hide show
  1. package/.agents/README.md +13 -0
  2. package/{agents/prompts/creating-a-model.md → .agents/skills/creating-a-model/SKILL.md} +7 -1
  3. package/{agents/prompts/devtools-imports.md → .agents/skills/devtools-imports/SKILL.md} +10 -5
  4. package/{agents/prompts/merging-devtools-module.md → .agents/skills/merging-devtools-module/SKILL.md} +5 -0
  5. package/{agents/prompts/ui-widgets.md → .agents/skills/ui-widgets/SKILL.md} +5 -0
  6. package/{agents/prompts/verification.md → .agents/skills/verification/SKILL.md} +5 -0
  7. package/front_end/core/common/Settings.ts +24 -1
  8. package/front_end/core/dom_extension/DOMExtension.ts +1 -0
  9. package/front_end/core/host/AidaClient.ts +5 -2
  10. package/front_end/core/host/AidaGcaTranslation.ts +377 -0
  11. package/front_end/core/host/GcaTypes.ts +520 -0
  12. package/front_end/core/host/UserMetrics.ts +0 -3
  13. package/front_end/core/host/host.ts +4 -0
  14. package/front_end/core/root/ExperimentNames.ts +0 -3
  15. package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
  16. package/front_end/core/sdk/ResourceTreeModel.ts +1 -1
  17. package/front_end/core/sdk/SourceMap.ts +4 -2
  18. package/front_end/entrypoints/main/MainImpl.ts +1 -12
  19. package/front_end/generated/Deprecation.ts +16 -0
  20. package/front_end/generated/InspectorBackendCommands.ts +4 -6
  21. package/front_end/generated/protocol.ts +46 -77
  22. package/front_end/models/ai_assistance/AiConversation.ts +49 -22
  23. package/front_end/models/ai_assistance/AiHistoryStorage.snapshot.txt +1 -1
  24. package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -0
  25. package/front_end/models/ai_assistance/ConversationHandler.ts +16 -11
  26. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +76 -36
  27. package/front_end/models/ai_assistance/agents/AiAgent.ts +26 -2
  28. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +0 -1
  29. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +11 -0
  30. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +39 -0
  31. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +53 -12
  32. package/front_end/models/ai_assistance/agents/FileAgent.ts +0 -15
  33. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +0 -5
  34. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +57 -0
  35. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +87 -15
  36. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +0 -10
  37. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -6
  38. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -5
  39. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  40. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.snapshot.txt +84 -0
  41. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +172 -0
  42. package/front_end/models/breakpoints/BreakpointManager.ts +20 -12
  43. package/front_end/models/emulation/DeviceModeModel.ts +5 -1
  44. package/front_end/models/emulation/EmulatedDevices.ts +3 -2
  45. package/front_end/models/javascript_metadata/NativeFunctions.js +60 -0
  46. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +104 -10
  47. package/front_end/models/lighthouse/RunTypes.ts +42 -0
  48. package/front_end/models/lighthouse/lighthouse.ts +2 -0
  49. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +59 -35
  50. package/front_end/models/stack_trace/StackTrace.ts +5 -0
  51. package/front_end/models/stack_trace/StackTraceImpl.ts +7 -1
  52. package/front_end/models/stack_trace/StackTraceModel.ts +2 -1
  53. package/front_end/models/stack_trace/stack_trace.ts +4 -0
  54. package/front_end/models/trace/ModelImpl.ts +4 -0
  55. package/front_end/models/trace/helpers/SamplesIntegrator.ts +1 -8
  56. package/front_end/models/trace/types/Configuration.ts +0 -5
  57. package/front_end/panels/accessibility/AccessibilityNodeView.ts +7 -3
  58. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +163 -74
  59. package/front_end/panels/ai_assistance/ExportConversation.ts +25 -0
  60. package/front_end/panels/ai_assistance/ai_assistance.ts +2 -0
  61. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -0
  62. package/front_end/panels/ai_assistance/components/ChatMessage.ts +120 -32
  63. package/front_end/panels/ai_assistance/components/ChatView.ts +44 -0
  64. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +240 -0
  65. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +4 -12
  66. package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +5 -27
  67. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +115 -17
  68. package/front_end/panels/ai_assistance/components/chatMessage.css +65 -1
  69. package/front_end/panels/ai_assistance/components/chatView.css +8 -0
  70. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +82 -0
  71. package/front_end/panels/ai_assistance/components/walkthroughView.css +9 -1
  72. package/front_end/panels/animation/AnimationTimeline.ts +5 -5
  73. package/front_end/panels/application/AppManifestView.ts +13 -7
  74. package/front_end/panels/application/FrameDetailsView.ts +4 -4
  75. package/front_end/panels/application/preloading/PreloadingView.ts +2 -4
  76. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +6 -3
  77. package/front_end/panels/application/preloading/components/PreloadingString.ts +15 -2
  78. package/front_end/panels/application/preloading/helper/PreloadingForward.ts +31 -2
  79. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +3 -2
  80. package/front_end/panels/common/DOMLinkifier.ts +10 -8
  81. package/front_end/panels/console/ConsoleView.ts +24 -2
  82. package/front_end/panels/console/ConsoleViewMessage.ts +22 -23
  83. package/front_end/panels/console/console.ts +0 -2
  84. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
  85. package/front_end/panels/elements/ElementsTreeElement.ts +2 -3
  86. package/front_end/panels/elements/PropertiesWidget.ts +5 -3
  87. package/front_end/panels/elements/StandaloneStylesContainer.ts +21 -9
  88. package/front_end/panels/elements/StylePropertiesSection.ts +98 -50
  89. package/front_end/panels/elements/StylePropertyTreeElement.ts +38 -4
  90. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +43 -35
  91. package/front_end/panels/elements/StylesSidebarPane.ts +154 -24
  92. package/front_end/panels/elements/components/ComputedStyleTrace.ts +3 -2
  93. package/front_end/panels/event_listeners/EventListenersView.ts +16 -4
  94. package/front_end/panels/explain/components/ConsoleInsight.ts +6 -9
  95. package/front_end/panels/explain/explain-meta.ts +5 -0
  96. package/front_end/panels/issues/AffectedResourcesView.ts +5 -9
  97. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +1 -1
  98. package/front_end/panels/lighthouse/LighthouseController.ts +154 -164
  99. package/front_end/panels/lighthouse/LighthousePanel.ts +5 -9
  100. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +1 -0
  101. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +9 -6
  102. package/front_end/panels/lighthouse/LighthouseStartView.ts +7 -6
  103. package/front_end/panels/lighthouse/LighthouseStatusView.ts +32 -8
  104. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +7 -3
  105. package/front_end/panels/network/NetworkDataGridNode.ts +34 -0
  106. package/front_end/panels/network/NetworkLogViewColumns.ts +10 -0
  107. package/front_end/panels/network/RequestPayloadView.ts +4 -1
  108. package/front_end/panels/network/RequestTimingView.ts +8 -2
  109. package/front_end/panels/performance_monitor/performanceMonitor.css +6 -4
  110. package/front_end/panels/recorder/RecorderController.ts +52 -14
  111. package/front_end/panels/recorder/components/RecordingView.ts +2 -1
  112. package/front_end/panels/recorder/models/RecordingStorage.ts +15 -20
  113. package/front_end/panels/sensors/SensorsView.ts +333 -157
  114. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +18 -4
  115. package/front_end/panels/sources/DebuggerPausedMessage.ts +6 -6
  116. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +2 -3
  117. package/front_end/panels/sources/NavigatorView.ts +11 -7
  118. package/front_end/panels/sources/ScopeChainSidebarPane.ts +1 -1
  119. package/front_end/panels/sources/SourcesPanel.ts +12 -34
  120. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +5 -1
  121. package/front_end/panels/sources/sources-meta.ts +6 -0
  122. package/front_end/panels/timeline/StatusDialog.ts +159 -83
  123. package/front_end/panels/timeline/ThirdPartyTreeView.ts +6 -2
  124. package/front_end/panels/timeline/TimelineController.ts +0 -4
  125. package/front_end/panels/timeline/TimelineDetailsView.ts +35 -3
  126. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +6 -1
  127. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +9 -4
  128. package/front_end/panels/timeline/TimelinePanel.ts +36 -11
  129. package/front_end/panels/timeline/TimelineTreeView.ts +14 -3
  130. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -57
  131. package/front_end/panels/timeline/components/InteractionBreakdown.ts +2 -1
  132. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  133. package/front_end/panels/timeline/components/NetworkRequestTooltip.ts +3 -7
  134. package/front_end/panels/timeline/components/Sidebar.ts +11 -19
  135. package/front_end/panels/timeline/components/SidebarInsightsTab.ts +2 -1
  136. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +60 -27
  137. package/front_end/panels/timeline/components/TimelineRangeSummaryView.ts +66 -0
  138. package/front_end/panels/timeline/components/TimelineSummary.ts +7 -19
  139. package/front_end/panels/timeline/components/components.ts +2 -0
  140. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +55 -28
  141. package/front_end/panels/timeline/components/insights/NodeLink.ts +3 -3
  142. package/front_end/panels/timeline/components/insights/baseInsightComponent.css +7 -0
  143. package/front_end/panels/timeline/components/insights/insights.ts +0 -2
  144. package/front_end/panels/timeline/components/timelineRangeSummaryView.css +20 -0
  145. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +7 -7
  146. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  147. package/front_end/panels/timeline/timelineDetailsView.css +0 -9
  148. package/front_end/third_party/chromium/README.chromium +1 -1
  149. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +11 -2
  150. package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +11 -2
  151. package/front_end/third_party/web-vitals/patches/0001-Add-onEachInteraction-to-onINP-options.patch +65 -4
  152. package/front_end/ui/kit/link/link.css +2 -2
  153. package/front_end/ui/legacy/Widget.ts +5 -3
  154. package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +7 -2
  155. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +57 -35
  156. package/front_end/ui/legacy/components/source_frame/PreviewFactory.ts +1 -0
  157. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -1
  158. package/package.json +1 -1
  159. package/agents/prompts/README.md +0 -18
  160. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +0 -93
  161. /package/front_end/{panels/console → models/stack_trace}/ErrorStackParser.ts +0 -0
@@ -8,13 +8,13 @@ import '../../../ui/kit/kit.js';
8
8
  import * as Common from '../../../core/common/common.js';
9
9
  import * as Host from '../../../core/host/host.js';
10
10
  import * as i18n from '../../../core/i18n/i18n.js';
11
- import type * as Platform from '../../../core/platform/platform.js';
11
+ import * as Platform from '../../../core/platform/platform.js';
12
12
  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, StylePropertiesAiWidget} from
17
- '../../../models/ai_assistance/agents/AiAgent.js';
16
+ AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LcpBreakdownAiWidget, PerformanceTraceAiWidget,
17
+ StylePropertiesAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
18
18
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
19
19
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
20
20
  import * as Marked from '../../../third_party/marked/marked.js';
@@ -28,7 +28,10 @@ import * as Lit from '../../../ui/lit/lit.js';
28
28
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
29
29
  import * as Elements from '../../elements/elements.js';
30
30
  import * as TimelineComponents from '../../timeline/components/components.js';
31
+ import * as TimelineInsights from '../../timeline/components/insights/insights.js';
32
+ import * as Timeline from '../../timeline/timeline.js';
31
33
  import * as TimelineUtils from '../../timeline/utils/utils.js';
34
+ import {PanelUtils} from '../../utils/utils.js';
32
35
 
33
36
  import chatMessageStyles from './chatMessage.css.js';
34
37
  import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
@@ -174,7 +177,11 @@ const UIStringsNotTranslate = {
174
177
  /**
175
178
  * @description Title for the button that takes the user into other DevTools panels to reveal items the AI references.
176
179
  */
177
- reveal: 'Reveal'
180
+ reveal: 'Reveal',
181
+ /**
182
+ * @description Title used for revealing the performance trace.
183
+ */
184
+ revealTrace: 'Reveal trace'
178
185
  } as const;
179
186
 
180
187
  export interface Step {
@@ -522,8 +529,8 @@ function renderWalkthroughSidebarButton(
522
529
  /**
523
530
  * Responsible for rendering the AI Walkthrough UI. This can take different
524
531
  * shapes and involve different parts depending on if the walkthrough is
525
- * inlined, expanded, or if we have side-effect steps that need the user to
526
- * view them.
532
+ * inlined, expanded, or if we have side-effect steps. In cases where the
533
+ * walkthrough is closed, side-effect steps are rendered inline in the chat.
527
534
  */
528
535
  function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.LitTemplate {
529
536
  const lastStep = steps.at(-1);
@@ -537,11 +544,17 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
537
544
  const openWalkThroughSidebarButton =
538
545
  !input.walkthrough.isInlined ? renderWalkthroughSidebarButton(input, steps) : Lit.nothing;
539
546
 
540
- // If there are side-effect steps, and the walkthrough is not open, we render
541
- // those inline so that the user can see them and approve them.
542
- // Note: this is a temporary approach and needs more UX discussion; b/487921578
547
+ // A message's walkthrough is considered expanded if the walkthrough is
548
+ // open and it is specifically targeting this message. This is necessary
549
+ // because the walkthrough state is shared across all messages in the chat.
550
+ const isExpanded = (input.walkthrough.isExpanded && input.walkthrough.activeMessage === input.message);
551
+
552
+ // When a side-effect step is present, it's shown in the main chat UI if the
553
+ // walkthrough is closed, allowing the user to approve it without opening
554
+ // the walkthrough. If the walkthrough is already open, the side-effect
555
+ // step is displayed within the walkthrough instead.
543
556
  // clang-format off
544
- const sideEffectStepsUI = !input.walkthrough.isInlined && !input.walkthrough.isExpanded && sideEffectSteps.length > 0 ? sideEffectSteps.map(step => html`
557
+ const sideEffectStepsUI = !isExpanded && sideEffectSteps.length > 0 ? sideEffectSteps.map(step => html`
545
558
  <div class="side-effect-container">
546
559
  ${renderStep({
547
560
  step,
@@ -552,11 +565,6 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
552
565
  </div> `) : Lit.nothing;
553
566
  // clang-format on
554
567
 
555
- // If the walkthrough is inlined (narrow width screens), render it here.
556
- // Note that we force it open if there is a side-effect.
557
-
558
- const isExpanded = (input.walkthrough.isExpanded && input.walkthrough.activeMessage === input.message) ||
559
- steps.some(s => s.requestApproval);
560
568
  // clang-format off
561
569
  const walkthroughInline = input.walkthrough.isInlined ? html`
562
570
  <div class="walkthrough-container">
@@ -574,8 +582,8 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
574
582
 
575
583
  return html`
576
584
  ${openWalkThroughSidebarButton}
577
- ${sideEffectStepsUI}
578
585
  ${walkthroughInline}
586
+ ${sideEffectStepsUI}
579
587
  `;
580
588
  // clang-format on
581
589
  }
@@ -644,8 +652,10 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
644
652
  }
645
653
 
646
654
  interface WidgetMakerResponse {
647
- renderedWidget: Lit.LitTemplate;
655
+ // Can be null if the widget is only used to add the Reveal CTA.
656
+ renderedWidget: Lit.LitTemplate|null;
648
657
  revealable: unknown;
658
+ customRevealTitle?: Platform.UIString.LocalizedString;
649
659
  }
650
660
 
651
661
  const nodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
@@ -720,6 +730,26 @@ async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): P
720
730
  return {renderedWidget, revealable: domNodeForId};
721
731
  }
722
732
 
733
+ async function makeLcpBreakdownWidget(widgetData: LcpBreakdownAiWidget): Promise<WidgetMakerResponse|null> {
734
+ const insight = widgetData.data.lcpData;
735
+ if (!insight) {
736
+ return null;
737
+ }
738
+
739
+ const widgetConfig = UI.Widget.widgetConfig(TimelineInsights.LCPBreakdown.LCPBreakdown, {
740
+ model: insight,
741
+ minimal: true,
742
+ });
743
+
744
+ // clang-format off
745
+ const renderedWidget = html`<devtools-widget
746
+ class="lcp-breakdown-widget"
747
+ .widgetConfig=${widgetConfig}></devtools-widget>`;
748
+ // clang-format on
749
+
750
+ return {renderedWidget, revealable: new TimelineUtils.Helpers.RevealableInsight(insight)};
751
+ }
752
+
723
753
  function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
724
754
  if (response === null) {
725
755
  return Lit.nothing;
@@ -732,24 +762,64 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
732
762
  void Common.Revealer.reveal(response?.revealable);
733
763
  }
734
764
 
765
+ const classes = Lit.Directives.classMap({
766
+ 'widget-and-revealer-container': true,
767
+ 'revealer-only': response.renderedWidget === null,
768
+ });
735
769
  // clang-format off
736
770
  return html`
737
- <div class="widget-and-revealer-container">
738
- <div class="widget-content-container">
739
- ${response.renderedWidget}
740
- </div>
771
+ <div class=${classes}>
772
+ ${response.renderedWidget ? html`
773
+ <div class="widget-content-container">
774
+ ${response.renderedWidget}
775
+ </div>` : Lit.nothing
776
+ }
741
777
  <div class="widget-reveal-container">
742
778
  <devtools-button class="widget-reveal"
743
779
  .iconName=${'tab-move'}
744
780
  .variant=${Buttons.Button.Variant.TEXT}
745
781
  @click=${onReveal}
746
- >${lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
782
+ >${response.customRevealTitle ?? lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
747
783
  </div>
748
784
  </div>
749
785
  `;
750
786
  // clang-format on
751
787
  }
752
788
 
789
+ async function makePerformanceTraceWidget(widgetData: PerformanceTraceAiWidget): Promise<WidgetMakerResponse|null> {
790
+ return {
791
+ renderedWidget: null,
792
+ revealable: new Timeline.TimelinePanel.ParsedTraceRevealable(widgetData.data.parsedTrace),
793
+ customRevealTitle: lockedString(UIStringsNotTranslate.revealTrace),
794
+ };
795
+ }
796
+
797
+ function renderNetworkRequestPreview(networkRequest: NonNullable<DomTreeAiWidget['data']['networkRequest']>):
798
+ Lit.TemplateResult {
799
+ const filename = networkRequest.url.split('/').pop() || networkRequest.url;
800
+ const size = i18n.ByteUtilities.bytesToString(networkRequest.size);
801
+ const resourceType = Common.ResourceType.resourceTypes[networkRequest.resourceType];
802
+ const {iconName, color} = PanelUtils.iconDataForResourceType(resourceType);
803
+
804
+ return html`
805
+ <div class="network-request-preview">
806
+ <div class="network-request-header">
807
+ <div class="network-request-icon">
808
+ ${
809
+ resourceType.isImage() ? html`<img src=${networkRequest.imageUrl ?? networkRequest.url} alt=${filename} />` :
810
+ html`<devtools-icon name=${iconName} style=${Lit.Directives.styleMap({
811
+ color: color ?? ''
812
+ })}></devtools-icon>`}
813
+ </div>
814
+ <div class="network-request-details">
815
+ <div class="network-request-name" title=${networkRequest.url}>${filename}</div>
816
+ <div class="network-request-size">${size}</div>
817
+ </div>
818
+ </div>
819
+ </div>
820
+ `;
821
+ }
822
+
753
823
  async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMakerResponse|null> {
754
824
  const root = widgetData.data.root;
755
825
  if (!(root instanceof SDK.DOMModel.DOMNodeSnapshot)) {
@@ -768,12 +838,17 @@ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMak
768
838
  wrap: true,
769
839
  });
770
840
 
841
+ const networkRequest = widgetData.data.networkRequest;
842
+
771
843
  // clang-format off
772
- const widget = html`<devtools-widget class="dom-tree-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
844
+ const renderedWidget = html`
845
+ ${networkRequest ? renderNetworkRequestPreview(networkRequest) : Lit.nothing}
846
+ <devtools-widget class="dom-tree-widget" .widgetConfig=${widgetConfig}></devtools-widget>
847
+ `;
773
848
  // clang-format on
774
849
 
775
850
  return {
776
- renderedWidget: widget,
851
+ renderedWidget,
777
852
  revealable: new SDK.DOMModel.DeferredDOMNode(root.domModel().target(), root.backendNodeId()),
778
853
  };
779
854
  }
@@ -802,14 +877,27 @@ async function renderWidgets(
802
877
  }
803
878
  const ui = await Promise.all(widgets.map(async widgetData => {
804
879
  let response: WidgetMakerResponse|null = null;
805
- if (widgetData.name === 'COMPUTED_STYLES') {
806
- response = await makeComputedStyleWidget(widgetData);
807
- } else if (widgetData.name === 'CORE_VITALS') {
808
- response = await makeCoreVitalsWidget(widgetData);
809
- } else if (widgetData.name === 'STYLE_PROPERTIES') {
810
- response = await makeStylePropertiesWidget(widgetData);
811
- } else if (widgetData.name === 'DOM_TREE') {
812
- response = await makeDomTreeWidget(widgetData);
880
+ switch (widgetData.name) {
881
+ case 'COMPUTED_STYLES':
882
+ response = await makeComputedStyleWidget(widgetData);
883
+ break;
884
+ case 'CORE_VITALS':
885
+ response = await makeCoreVitalsWidget(widgetData);
886
+ break;
887
+ case 'STYLE_PROPERTIES':
888
+ response = await makeStylePropertiesWidget(widgetData);
889
+ break;
890
+ case 'DOM_TREE':
891
+ response = await makeDomTreeWidget(widgetData);
892
+ break;
893
+ case 'PERFORMANCE_TRACE':
894
+ response = await makePerformanceTraceWidget(widgetData);
895
+ break;
896
+ case 'LCP_BREAKDOWN':
897
+ response = await makeLcpBreakdownWidget(widgetData);
898
+ break;
899
+ default:
900
+ Platform.assertNever(widgetData, 'Unknown AiWidget name');
813
901
  }
814
902
  return renderWidgetResponse(response);
815
903
  }));
@@ -7,6 +7,7 @@ import '../../../ui/components/spinners/spinners.js';
7
7
  import * as Host from '../../../core/host/host.js';
8
8
  import * as i18n from '../../../core/i18n/i18n.js';
9
9
  import type * as Platform from '../../../core/platform/platform.js';
10
+ import * as Root from '../../../core/root/root.js';
10
11
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
11
12
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
12
13
  import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
@@ -17,6 +18,7 @@ import {PatchWidget} from '../PatchWidget.js';
17
18
  import {ChatInput} from './ChatInput.js';
18
19
  import {ChatMessage, type Message, type ModelChatMessage} from './ChatMessage.js';
19
20
  import chatViewStyles from './chatView.css.js';
21
+ import {ExportForAgentsDialog} from './ExportForAgentsDialog.js';
20
22
 
21
23
  export {ChatInput, type ImageInputData} from './ChatInput.js';
22
24
 
@@ -63,6 +65,8 @@ export interface Props {
63
65
  onCopyResponseClick: (message: ModelChatMessage) => void;
64
66
  onContextRemoved: (() => void)|null;
65
67
  onContextAdd: (() => void)|null;
68
+ conversationMarkdown: string;
69
+ onExportConversation: (() => void)|null;
66
70
  changeManager: AiAssistanceModel.ChangeManager.ChangeManager;
67
71
  inspectElementToggled: boolean;
68
72
  messages: Message[];
@@ -81,6 +85,7 @@ export interface Props {
81
85
  disclaimerText: Platform.UIString.LocalizedString;
82
86
  uploadImageInputEnabled?: boolean;
83
87
  markdownRenderer: MarkdownLitRenderer;
88
+ generateConversationSummary: (markdown: string) => Promise<string>;
84
89
  walkthrough: {
85
90
  onOpen: (message: ModelChatMessage) => void,
86
91
  onToggle: (isOpen: boolean) => void,
@@ -94,6 +99,7 @@ interface ChatWidgetInput extends Props {
94
99
  handleScroll: (ev: Event) => void;
95
100
  handleSuggestionClick: (title: string) => void;
96
101
  handleMessageContainerRef: (el: Element|undefined) => void;
102
+ exportForAgentsClick: () => void;
97
103
  }
98
104
 
99
105
  const DEFAULT_VIEW: View = (input, output, target) => {
@@ -107,6 +113,9 @@ const DEFAULT_VIEW: View = (input, output, target) => {
107
113
  sticky: !input.isReadOnly,
108
114
  });
109
115
 
116
+ const hasAiV2Flag = Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
117
+ const shouldShowExportToAgent = !input.isLoading && input.messages.length > 0 && hasAiV2Flag;
118
+
110
119
  // clang-format off
111
120
  render(html`
112
121
  <style>${chatViewStyles}</style>
@@ -130,6 +139,15 @@ const DEFAULT_VIEW: View = (input, output, target) => {
130
139
  }
131
140
  })
132
141
  )}
142
+ ${shouldShowExportToAgent ? html`
143
+ <devtools-button
144
+ class="export-for-agents-button"
145
+ .jslogContext=${'ai-export-for-agents'}
146
+ .variant=${Buttons.Button.Variant.TEXT}
147
+ .iconName=${'copy'}
148
+ @click=${input.exportForAgentsClick}
149
+ >Export for agents</devtools-button>
150
+ ` : nothing}
133
151
  ${input.isLoading ? nothing : widget(PatchWidget, {
134
152
  changeSummary: input.changeSummary ?? '',
135
153
  changeManager: input.changeManager,
@@ -233,6 +251,7 @@ export class ChatView extends HTMLElement {
233
251
  */
234
252
  #isProgrammaticScroll = false;
235
253
  #view: View;
254
+ #cachedSummary: {markdown: string, summary: string}|null = null;
236
255
 
237
256
  constructor(props: Props, view = DEFAULT_VIEW) {
238
257
  super();
@@ -346,6 +365,30 @@ export class ChatView extends HTMLElement {
346
365
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceDynamicSuggestionClicked);
347
366
  };
348
367
 
368
+ async #getSummary(): Promise<string> {
369
+ if (this.#cachedSummary?.markdown === this.#props.conversationMarkdown) {
370
+ return this.#cachedSummary.summary;
371
+ }
372
+ try {
373
+ const summary = await this.#props.generateConversationSummary(this.#props.conversationMarkdown);
374
+ this.#cachedSummary = {markdown: this.#props.conversationMarkdown, summary};
375
+ return summary;
376
+ } catch (err) {
377
+ console.error(err);
378
+ return 'Failed to generate summary.';
379
+ }
380
+ }
381
+
382
+ async #exportForAgentsClick(): Promise<void> {
383
+ const summaryPromise = this.#getSummary();
384
+
385
+ void ExportForAgentsDialog.show({
386
+ promptText: summaryPromise,
387
+ markdownText: this.#props.conversationMarkdown,
388
+ onConversationSaveAs: this.#props.onExportConversation ?? (async () => {}),
389
+ });
390
+ }
391
+
349
392
  #render(): void {
350
393
  this.#view(
351
394
  {
@@ -353,6 +396,7 @@ export class ChatView extends HTMLElement {
353
396
  handleScroll: this.#handleScroll,
354
397
  handleSuggestionClick: this.#handleSuggestionClick,
355
398
  handleMessageContainerRef: this.#handleMessageContainerRef,
399
+ exportForAgentsClick: this.#exportForAgentsClick.bind(this),
356
400
  },
357
401
  this.#output, this.#shadow);
358
402
  }
@@ -0,0 +1,240 @@
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 Host from '../../../core/host/host.js';
6
+ import * as i18n from '../../../core/i18n/i18n.js';
7
+ import * as Buttons from '../../../ui/components/buttons/buttons.js';
8
+ import * as Snackbars from '../../../ui/components/snackbars/snackbars.js';
9
+ import * as UI from '../../../ui/legacy/legacy.js';
10
+ import * as Lit from '../../../ui/lit/lit.js';
11
+
12
+ import styles from './exportForAgentsDialog.css.js';
13
+
14
+ const {html, render} = Lit;
15
+
16
+ const UIStrings = {
17
+ /**
18
+ * @description Title for the export for agents dialog.
19
+ */
20
+ exportForAgents: 'Export for agents',
21
+ /**
22
+ * @description Button text for copying to clipboard.
23
+ */
24
+ copyToClipboard: 'Copy to clipboard',
25
+ /**
26
+ * @description Text displayed in a toast to indicate that the content was copied to the clipboard.
27
+ */
28
+ copiedToClipboard: 'Copied to clipboard',
29
+ /**
30
+ * @description Label for the 'as prompt' radio button in the export for agents dialog.
31
+ */
32
+ asPrompt: 'As prompt',
33
+ /**
34
+ * @description Label for the 'as markdown' radio button in the export for agents dialog.
35
+ */
36
+ asMarkdown: 'As markdown',
37
+ /**
38
+ * @description Button text for saving content as a markdown file.
39
+ */
40
+ saveAsMarkdown: 'Save as…',
41
+ /**
42
+ * @description Text displayed while the summary is being generated.
43
+ */
44
+ generatingSummary: 'Generating summary…',
45
+ /**
46
+ * @description Disclaimer text for the export for agents dialog.
47
+ */
48
+ disclaimer:
49
+ 'This is an experimental AI feature and won’t always get it right. Double check this text before pasting into another tool.',
50
+ } as const;
51
+
52
+ const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/ExportForAgentsDialog.ts', UIStrings);
53
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
54
+
55
+ export const enum StateType {
56
+ PROMPT = 'prompt',
57
+ CONVERSATION = 'conversation',
58
+ }
59
+
60
+ export interface State {
61
+ activeType: StateType;
62
+ promptText: string;
63
+ conversationText: string;
64
+ isPromptLoading: boolean;
65
+ }
66
+
67
+ interface ViewInput {
68
+ onButtonClick: (event: Event) => void;
69
+ state: State;
70
+ onStateChange: (stateType: StateType) => void;
71
+ jslogContext: string;
72
+ }
73
+
74
+ type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
75
+
76
+ export const DEFAULT_VIEW: View = (input, _output, target): void => {
77
+ const isPrompt = input.state.activeType === StateType.PROMPT;
78
+ const buttonText = isPrompt ? i18nString(UIStrings.copyToClipboard) : i18nString(UIStrings.saveAsMarkdown);
79
+ const exportText = isPrompt && input.state.isPromptLoading ?
80
+ i18nString(UIStrings.generatingSummary) :
81
+ (isPrompt ? input.state.promptText : input.state.conversationText);
82
+ // clang-format off
83
+ render(html`
84
+ <style>${styles}</style>
85
+ <div class="export-for-agents-dialog">
86
+ <header>
87
+ <h2 tabindex="-1">
88
+ ${i18nString(UIStrings.exportForAgents)}
89
+ </h2>
90
+ </header>
91
+ <div class="state-selection">
92
+ <label>
93
+ <input
94
+ type="radio"
95
+ value="prompt"
96
+ name="export-state"
97
+ .checked=${isPrompt}
98
+ @change=${() => input.onStateChange(StateType.PROMPT)}
99
+ >
100
+ ${i18nString(UIStrings.asPrompt)}
101
+ </label>
102
+ <label>
103
+ <input
104
+ type="radio"
105
+ value="conversation"
106
+ name="export-state"
107
+ .checked=${!isPrompt}
108
+ @change=${() => input.onStateChange(StateType.CONVERSATION)}
109
+ >
110
+ ${i18nString(UIStrings.asMarkdown)}
111
+ </label>
112
+ </div>
113
+ <main>
114
+ <textarea readonly .value=${exportText}></textarea>
115
+ </main>
116
+ ${isPrompt ? html`<div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>` : Lit.nothing}
117
+ <footer>
118
+ <div class="right-buttons">
119
+ <devtools-button
120
+ @click=${input.onButtonClick}
121
+ .jslogContext=${input.jslogContext}
122
+ .variant=${Buttons.Button.Variant.PRIMARY}
123
+ .disabled=${isPrompt && input.state.isPromptLoading}
124
+ >
125
+ ${buttonText}
126
+ </devtools-button>
127
+ </div>
128
+ </footer>
129
+ </div>
130
+ `, target);
131
+ // clang-format on
132
+ };
133
+
134
+ export class ExportForAgentsDialog extends UI.Widget.VBox {
135
+ readonly #view: View;
136
+ readonly #dialog: UI.Dialog.Dialog;
137
+ #state: State;
138
+ #onConversationSaveAs: () => void;
139
+
140
+ constructor(
141
+ options: {
142
+ dialog: UI.Dialog.Dialog,
143
+ promptText: string|Promise<string>,
144
+ markdownText: string,
145
+ onConversationSaveAs: () => void,
146
+ },
147
+ view: View = DEFAULT_VIEW) {
148
+ super();
149
+ this.#dialog = options.dialog;
150
+ this.#state = {
151
+ activeType: StateType.PROMPT,
152
+ promptText: typeof options.promptText === 'string' ? options.promptText : '',
153
+ conversationText: options.markdownText,
154
+ isPromptLoading: typeof options.promptText !== 'string',
155
+ };
156
+ this.#onConversationSaveAs = options.onConversationSaveAs;
157
+ this.#view = view;
158
+
159
+ if (options.promptText instanceof Promise) {
160
+ void options.promptText.then(promptText => {
161
+ this.#state.promptText = promptText;
162
+ this.#state.isPromptLoading = false;
163
+ this.requestUpdate();
164
+ });
165
+ }
166
+
167
+ this.requestUpdate();
168
+ }
169
+
170
+ #onStateChange = (newState: StateType): void => {
171
+ this.#state.activeType = newState;
172
+ this.requestUpdate();
173
+ };
174
+
175
+ override performUpdate(): void {
176
+ let onButtonClick: (event: Event) => void;
177
+ let jslogContext = '';
178
+
179
+ switch (this.#state.activeType) {
180
+ case StateType.PROMPT:
181
+ jslogContext = 'ai-export-for-agents.copy-to-clipboard';
182
+ onButtonClick = (event: Event): void => {
183
+ event.preventDefault();
184
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(this.#state.promptText);
185
+ Snackbars.Snackbar.Snackbar.show({
186
+ message: i18nString(UIStrings.copiedToClipboard),
187
+ });
188
+ this.#dialog.hide();
189
+ };
190
+ break;
191
+ case StateType.CONVERSATION:
192
+ jslogContext = 'ai-export-for-agents.save-as-markdown';
193
+ onButtonClick = (): void => {
194
+ this.#dialog.hide();
195
+ this.#onConversationSaveAs();
196
+ };
197
+ break;
198
+ }
199
+
200
+ const viewInput = {
201
+ onButtonClick,
202
+ state: this.#state,
203
+ onStateChange: this.#onStateChange,
204
+ jslogContext,
205
+ };
206
+
207
+ this.#view(viewInput, undefined, this.contentElement);
208
+ }
209
+
210
+ static show({
211
+ promptText,
212
+ markdownText,
213
+ onConversationSaveAs,
214
+ }: {
215
+ promptText: string|Promise<string>,
216
+ markdownText: string,
217
+ onConversationSaveAs: () => void,
218
+ }): void {
219
+ const dialog = new UI.Dialog.Dialog();
220
+ dialog.setAriaLabel(i18nString(UIStrings.exportForAgents));
221
+
222
+ dialog.setOutsideClickCallback(ev => {
223
+ ev.consume(true);
224
+ dialog.hide();
225
+ });
226
+
227
+ dialog.addCloseButton();
228
+ dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.MEASURE_CONTENT);
229
+ dialog.setDimmed(true);
230
+
231
+ const exportDialog = new ExportForAgentsDialog({dialog, promptText, markdownText, onConversationSaveAs});
232
+ exportDialog.show(dialog.contentElement);
233
+ // Because the Dialog sets its height based off its content, we need
234
+ // the Export Dialog widget to have rendered fully before we show
235
+ // the dialog with its contents.
236
+ void exportDialog.updateComplete.then(() => {
237
+ dialog.show();
238
+ });
239
+ }
240
+ }
@@ -13,7 +13,7 @@ import * as PanelsCommon from '../../common/common.js';
13
13
  import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
14
14
 
15
15
  const {html} = Lit.StaticHtml;
16
- const {ref, createRef} = Lit.Directives;
16
+ const {until} = Lit.Directives;
17
17
 
18
18
  export class PerformanceAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
19
19
  constructor(
@@ -27,16 +27,8 @@ export class PerformanceAgentMarkdownRenderer extends MarkdownRendererWithCodeBl
27
27
  if (token.href.startsWith('#node-')) {
28
28
  const nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
29
29
 
30
- const templateRef = createRef();
31
- void this.#linkifyNode(nodeId, token.text).then(node => {
32
- if (!templateRef.value || !node) {
33
- return;
34
- }
35
-
36
- templateRef.value.textContent = '';
37
- templateRef.value.append(node);
38
- });
39
- return html`<span ${ref(templateRef)}>${token.text}</span>`;
30
+ return html`<span>${
31
+ until(this.#linkifyNode(nodeId, token.text).then(node => node || token.text), token.text)}</span>`;
40
32
  }
41
33
 
42
34
  const event = this.lookupEvent(token.href.slice(1) as Trace.Types.File.SerializableKey);
@@ -65,7 +57,7 @@ export class PerformanceAgentMarkdownRenderer extends MarkdownRendererWithCodeBl
65
57
  // Taken from front_end/panels/timeline/components/insights/NodeLink.ts
66
58
  // Would be nice to move the above component to somewhere that allows the AI
67
59
  // Assistance panel to also use it.
68
- async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Node|undefined> {
60
+ async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Lit.LitTemplate|undefined> {
69
61
  if (backendNodeId === undefined) {
70
62
  return;
71
63
  }