chrome-devtools-frontend 1.0.1592362 → 1.0.1593518

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 (109) hide show
  1. package/front_end/Images/src/tab-move.svg +1 -0
  2. package/front_end/application_tokens.css +4 -4
  3. package/front_end/core/protocol_client/InspectorBackend.ts +0 -4
  4. package/front_end/core/sdk/EmulationModel.ts +1 -41
  5. package/front_end/generated/InspectorBackendCommands.ts +2 -1
  6. package/front_end/generated/SupportedCSSProperties.js +2 -2
  7. package/front_end/generated/protocol-mapping.d.ts +7 -0
  8. package/front_end/generated/protocol-proxy-api.d.ts +5 -0
  9. package/front_end/generated/protocol.ts +59 -0
  10. package/front_end/models/ai_assistance/agents/AiAgent.ts +24 -1
  11. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +26 -86
  12. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +14 -4
  13. package/front_end/models/ai_assistance/agents/StylingAgent.ts +16 -2
  14. package/front_end/models/emulation/DeviceModeModel.ts +0 -47
  15. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -6
  16. package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +5 -3
  17. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +29 -6
  18. package/front_end/panels/ai_assistance/README.md +61 -0
  19. package/front_end/panels/ai_assistance/components/ChatMessage.ts +141 -27
  20. package/front_end/panels/ai_assistance/components/ChatView.ts +5 -1
  21. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +19 -7
  22. package/front_end/panels/ai_assistance/components/chatMessage.css +30 -0
  23. package/front_end/panels/ai_assistance/components/walkthroughView.css +12 -7
  24. package/front_end/panels/animation/AnimationGroupPreviewUI.ts +1 -1
  25. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +1 -0
  26. package/front_end/panels/application/components/BackForwardCacheView.ts +4 -2
  27. package/front_end/panels/autofill/AutofillView.ts +1 -1
  28. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +161 -154
  29. package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +1 -0
  30. package/front_end/panels/browser_debugger/domBreakpointsSidebarPane.css +58 -36
  31. package/front_end/panels/console/ConsoleViewMessage.ts +1 -0
  32. package/front_end/panels/console/consoleView.css +7 -2
  33. package/front_end/panels/elements/ComputedStyleWidget.ts +5 -1
  34. package/front_end/panels/elements/ElementsTreeElement.ts +1 -0
  35. package/front_end/panels/elements/LayoutPane.ts +8 -7
  36. package/front_end/panels/elements/StandaloneStylesContainer.ts +254 -0
  37. package/front_end/panels/elements/components/ComputedStyleTrace.ts +4 -0
  38. package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +1 -1
  39. package/front_end/panels/elements/components/StylePropertyEditor.ts +2 -1
  40. package/front_end/panels/elements/components/computedStyleProperty.css +1 -1
  41. package/front_end/panels/elements/elements.ts +2 -0
  42. package/front_end/panels/emulation/DeviceModeToolbar.ts +0 -25
  43. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
  44. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +1 -1
  45. package/front_end/panels/linear_memory_inspector/components/ValueInterpreterDisplay.ts +1 -1
  46. package/front_end/panels/media/PlayerListView.ts +1 -1
  47. package/front_end/panels/network/NetworkLogViewColumns.ts +2 -1
  48. package/front_end/panels/network/components/RequestHeaderSection.ts +1 -1
  49. package/front_end/panels/protocol_monitor/JSONEditor.ts +1 -1
  50. package/front_end/panels/recorder/components/RecordingListView.ts +1 -1
  51. package/front_end/panels/recorder/components/StepEditor.ts +3 -3
  52. package/front_end/panels/settings/KeybindsSettingsTab.ts +2 -1
  53. package/front_end/panels/sources/components/HeadersView.ts +2 -2
  54. package/front_end/panels/timeline/components/BreadcrumbsUI.ts +1 -1
  55. package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -1
  56. package/front_end/third_party/chromium/README.chromium +1 -1
  57. package/front_end/third_party/puppeteer/README.chromium +2 -2
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js +1 -0
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js.map +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js.map +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  66. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  67. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  68. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +6 -6
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
  77. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js +1 -0
  78. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js.map +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js.map +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  85. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  87. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  88. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  91. package/front_end/third_party/puppeteer/package/package.json +4 -4
  92. package/front_end/third_party/puppeteer/package/src/bidi/core/Navigation.ts +1 -0
  93. package/front_end/third_party/puppeteer/package/src/cdp/BrowserContext.ts +1 -1
  94. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +1 -1
  95. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  96. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  97. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  98. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +7 -12
  99. package/front_end/ui/components/tree_outline/TreeOutline.ts +6 -2
  100. package/front_end/ui/components/tree_outline/treeOutline.css +5 -0
  101. package/front_end/ui/legacy/ListControl.ts +5 -3
  102. package/front_end/ui/legacy/ListWidget.ts +1 -1
  103. package/front_end/ui/legacy/SoftContextMenu.ts +2 -1
  104. package/front_end/ui/legacy/Treeoutline.ts +1 -0
  105. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +4 -2
  106. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
  107. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +31 -0
  108. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  109. package/package.json +1 -1
@@ -113,7 +113,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
113
113
  #emulationModel: SDK.EmulationModel.EmulationModel|null;
114
114
  #onModelAvailable: (() => void)|null;
115
115
  #outlineRect?: Rect;
116
- #screenOrientationLocked: boolean;
117
116
 
118
117
  private constructor() {
119
118
  super();
@@ -173,7 +172,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
173
172
 
174
173
  this.#emulationModel = null;
175
174
  this.#onModelAvailable = null;
176
- this.#screenOrientationLocked = false;
177
175
  SDK.TargetManager.TargetManager.instance().observeModels(SDK.EmulationModel.EmulationModel, this);
178
176
  }
179
177
 
@@ -444,9 +442,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
444
442
  this.#onModelAvailable = null;
445
443
  callback();
446
444
  }
447
- emulationModel.addEventListener(
448
- SDK.EmulationModel.EmulationModelEvents.SCREEN_ORIENTATION_LOCK_CHANGED, this.onScreenOrientationLockChanged,
449
- this);
450
445
  const resourceTreeModel = emulationModel.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
451
446
  if (resourceTreeModel) {
452
447
  resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameResized, this.onFrameChange, this);
@@ -459,12 +454,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
459
454
 
460
455
  modelRemoved(emulationModel: SDK.EmulationModel.EmulationModel): void {
461
456
  if (this.#emulationModel === emulationModel) {
462
- emulationModel.removeEventListener(
463
- SDK.EmulationModel.EmulationModelEvents.SCREEN_ORIENTATION_LOCK_CHANGED, this.onScreenOrientationLockChanged,
464
- this);
465
457
  this.#emulationModel = null;
466
- this.#screenOrientationLocked = false;
467
- this.dispatchEventToListeners(Events.UPDATED);
468
458
  }
469
459
  }
470
460
 
@@ -481,43 +471,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
481
471
  this.showHingeIfApplicable(overlayModel);
482
472
  }
483
473
 
484
- private onScreenOrientationLockChanged(
485
- event: Common.EventTarget.EventTargetEvent<SDK.EmulationModel.ScreenOrientationLockChangedEvent>): void {
486
- this.#screenOrientationLocked = event.data.locked;
487
- if (event.data.locked && event.data.orientation) {
488
- this.applyOrientationLock(event.data.orientation);
489
- }
490
- this.dispatchEventToListeners(Events.UPDATED);
491
- }
492
-
493
- private applyOrientationLock(orientation: Protocol.Emulation.ScreenOrientation): void {
494
- const wantsLandscape = orientation.type === Protocol.Emulation.ScreenOrientationType.LandscapePrimary ||
495
- orientation.type === Protocol.Emulation.ScreenOrientationType.LandscapeSecondary;
496
-
497
- if (this.#type === Type.Device && this.#device && this.#mode) {
498
- // For device emulation, switch to the matching orientation mode.
499
- const isCurrentlyLandscape =
500
- this.#mode.orientation === Horizontal || this.#mode.orientation === HorizontalSpanned;
501
- if (wantsLandscape !== isCurrentlyLandscape) {
502
- const rotationPartner = this.#device.getRotationPartner(this.#mode);
503
- if (rotationPartner) {
504
- this.emulate(this.#type, this.#device, rotationPartner);
505
- }
506
- }
507
- } else if (this.#type === Type.Responsive) {
508
- // For responsive mode, swap width/height if orientation doesn't match.
509
- const appliedSize = this.appliedDeviceSize();
510
- const isCurrentlyLandscape = appliedSize.width > appliedSize.height;
511
- if (wantsLandscape !== isCurrentlyLandscape) {
512
- this.setSizeAndScaleToFit(appliedSize.height, appliedSize.width);
513
- }
514
- }
515
- }
516
-
517
- isScreenOrientationLocked(): boolean {
518
- return this.#screenOrientationLocked;
519
- }
520
-
521
474
  private scaleSettingChanged(): void {
522
475
  this.calculateAndEmulate(false);
523
476
  }
@@ -7322,16 +7322,16 @@ export const NativeFunctions = [
7322
7322
  signatures: [["type","eventInitDict"]]
7323
7323
  },
7324
7324
  {
7325
- name: "registerTool",
7326
- signatures: [["params"]]
7325
+ name: "provideContext",
7326
+ signatures: [["?options"]]
7327
7327
  },
7328
7328
  {
7329
- name: "unregisterTool",
7330
- signatures: [["tool_name"]]
7329
+ name: "registerTool",
7330
+ signatures: [["tool"]]
7331
7331
  },
7332
7332
  {
7333
- name: "provideContext",
7334
- signatures: [["params"]]
7333
+ name: "unregisterTool",
7334
+ signatures: [["name"]]
7335
7335
  },
7336
7336
  {
7337
7337
  name: "SnapEvent",
@@ -452,9 +452,11 @@ export class AXBreadcrumb {
452
452
 
453
453
  this.#element = document.createElement('div');
454
454
  this.#element.classList.add('ax-breadcrumb');
455
- this.#element.setAttribute(
456
- 'jslog',
457
- `${VisualLogging.treeItem().track({click: true, keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter'})}`);
455
+ this.#element.setAttribute('jslog', `${VisualLogging.treeItem().track({
456
+ click: true,
457
+ resize: true,
458
+ keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter'
459
+ })}`);
458
460
  elementsToAXBreadcrumb.set(this.#element, this);
459
461
 
460
462
  this.#nodeElement = document.createElement('div');
@@ -460,10 +460,23 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
460
460
  }
461
461
  }
462
462
 
463
- const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
464
-
465
463
  if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled ||
466
464
  Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent')) {
465
+
466
+ const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
467
+ /**
468
+ * We want to mark the walkthrough as loading only if it's showing the last
469
+ * message. Otherwise, a previous walkthrough will show as loading if we
470
+ * rely only on the isLoading flag.
471
+ */
472
+ let walkthroughIsForLastMessage = false;
473
+ if(input.state === ViewState.CHAT_VIEW) {
474
+ const lastMessage = input.props.messages.at(-1);
475
+ if(lastMessage && input.props.walkthrough.activeMessage === lastMessage) {
476
+ walkthroughIsForLastMessage = true;
477
+ }
478
+ }
479
+
467
480
  Lit.render(html`
468
481
  ${toolbarView(input)}
469
482
  <div class="ai-assistance-view-container">
@@ -481,7 +494,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
481
494
  ${shouldShowWalkthrough ? html`
482
495
  <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
483
496
  message: input.props.walkthrough.activeMessage,
484
- isLoading: input.props.isLoading,
497
+ isLoading: input.props.isLoading && walkthroughIsForLastMessage,
485
498
  markdownRenderer: input.props.markdownRenderer,
486
499
  onToggle: input.props.walkthrough.onToggle,
487
500
  })}></devtools-widget>` : Lit.nothing}
@@ -1439,11 +1452,19 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1439
1452
  this.#updateConversationState();
1440
1453
  }
1441
1454
 
1455
+ #clearWalkthrough(): void {
1456
+ this.#walkthrough.isExpanded = false;
1457
+ this.#walkthrough.activeMessage = null;
1458
+ }
1459
+
1442
1460
  #onDeleteClicked(): void {
1443
1461
  if (!this.#conversation) {
1444
1462
  return;
1445
1463
  }
1446
1464
 
1465
+ // Ensure we clear the walkthrough so it doesn't hold onto a chat that is about to be deleted.
1466
+ this.#clearWalkthrough();
1467
+
1447
1468
  void AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().deleteHistoryEntry(this.#conversation.id);
1448
1469
  this.#updateConversationState();
1449
1470
  UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.chatDeleted));
@@ -1650,7 +1671,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1650
1671
  let announcedAnswerLoading = false;
1651
1672
  let announcedAnswerReady = false;
1652
1673
  for await (const data of items) {
1653
- step.sideEffect = undefined;
1674
+ step.requestApproval = undefined;
1654
1675
  switch (data.type) {
1655
1676
  case AiAssistanceModel.AiAgent.ResponseType.USER_QUERY: {
1656
1677
  this.#messages.push({
@@ -1711,10 +1732,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1711
1732
  case AiAssistanceModel.AiAgent.ResponseType.SIDE_EFFECT: {
1712
1733
  step.isLoading = false;
1713
1734
  step.code ??= data.code;
1714
- step.sideEffect = {
1735
+ step.requestApproval = {
1736
+ description: data.description,
1715
1737
  onAnswer: (result: boolean) => {
1716
1738
  data.confirm(result);
1717
- step.sideEffect = undefined;
1739
+ step.requestApproval = undefined;
1718
1740
  this.requestUpdate();
1719
1741
  },
1720
1742
  };
@@ -1726,6 +1748,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1726
1748
  step.code ??= data.code;
1727
1749
  step.output ??= data.output;
1728
1750
  step.canceled = data.canceled;
1751
+ step.widgets ??= data.widgets;
1729
1752
  commitStep();
1730
1753
  break;
1731
1754
  }
@@ -83,3 +83,64 @@ The spinner displayed next to a step in the UI is intentionally tied to the **co
83
83
  * **Providing Continuous Feedback**: Using the conversation's `isLoading` ensures a persistent visual indicator that the AI is actively working on the query, even if individual steps are quickly parsed. This prevents the UI from appearing unresponsive.
84
84
  * **Avoiding a "Stuck" Feeling**: If the spinner were tied to `step.isLoading`, it would flicker on and off rapidly, potentially making the user feel that the AI has stopped processing or is stuck.
85
85
  * **Clear Progress Visualization**: The spinner is dynamically moved to the *last* active step in the list as long as the overall conversation is loading. Once a step is completed, it receives a checkmark, and the spinner moves to the next active step.
86
+
87
+ ## AI-Defined UI Widgets
88
+
89
+ To provide a richer user experience, AI functions can now return data that represents UI widgets. These widgets are then rendered directly within the AI assistance panel, allowing for more interactive and contextual responses.
90
+
91
+ ### Data Structure for Widgets
92
+
93
+ This is achieved through a new type of `ModelMessagePart`: the `WidgetPart`.
94
+
95
+ - **`WidgetPart`**: Represents a UI widget to be rendered. It contains a `widget` object with the following properties:
96
+ - `name`: A string identifier for the widget to be rendered (e.g., `'freestyler'`). This name is used by the frontend to select the correct widget component.
97
+ - `data`: An object containing the data required by the widget. The structure of this data is specific to each widget.
98
+
99
+ A `ModelChatMessage` can contain one or more `WidgetPart`s, usually as part of the final answer.
100
+
101
+ **Example `ModelChatMessage` with a `WidgetPart`:**
102
+
103
+ ```json
104
+ {
105
+ "entity": "MODEL",
106
+ "parts": [
107
+ {
108
+ "type": "answer",
109
+ "text": "Here is a widget to help you with CSS."
110
+ },
111
+ {
112
+ "type": "widget",
113
+ "widget": {
114
+ "name": "freestyler",
115
+ "data": {
116
+ "css": "color: red;"
117
+ }
118
+ }
119
+ }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ ### From AI Function to UI
125
+
126
+ The process of rendering an AI-defined UI widget begins within the AI function handler itself. AI functions, typically defined in `front_end/models/ai_assistance/agents/AiAgent.js`, can return an `AiWidget` type as part of their response.
127
+
128
+ 1. **AI Function Output**: An AI function constructs an `AiWidget` object, specifying its `name` (e.g., `'freestyler'` or `'COMPUTED_STYLES'`) and a `data` payload that contains all the necessary information for the widget to render.
129
+
130
+ 2. **`ModelChatMessage` Integration**: This `AiWidget` is then encapsulated within a `WidgetPart`, which is added to the `parts` array of a `ModelChatMessage`. This `ModelChatMessage` is what the `ChatMessage` UI component receives.
131
+
132
+ 3. **`ChatMessage` Processing**: In `front_end/panels/ai_assistance/components/ChatMessage.ts`, the `ChatMessage` component is responsible for iterating through the `parts` of a `ModelChatMessage`. When it encounters a `WidgetPart`, it delegates the rendering to the `renderStepWidgets` function.
133
+
134
+ 4. **`renderStepWidgets` Mapping**: The `renderStepWidgets` function acts as a registry and renderer for different widget types. It reads the `widget.name` from the `WidgetPart` and, based on this name, calls a specific `make...Widget` function (e.g., `makeComputedStyleWidget` for `'COMPUTED_STYLES'` widgets).
135
+
136
+ 5. **Widget Instantiation and Rendering**: Each `make...Widget` function is responsible for taking the `widget.data` and converting it into a `UI.Widget.widgetConfig` object. This configuration is then used with the `<devtools-widget>` Lit component, which dynamically instantiates the corresponding `UI.Widget` subclass and renders it into the DOM.
137
+
138
+ This modular approach ensures that new UI widgets can be introduced and managed by AI functions without requiring significant changes to the core messaging or rendering infrastructure.
139
+
140
+ The `ChatMessage` component is responsible for handling `WidgetPart`s. When it encounters a `WidgetPart`, it will:
141
+
142
+ 1. Look up the widget name (`widget.name`) in a registry of available widget components.
143
+ 2. Instantiate the corresponding widget component.
144
+ 3. Pass the `widget.data` to the component as properties.
145
+
146
+ This allows for a flexible system where new widgets can be added to the frontend and then invoked by the AI without requiring changes to the core message handling logic.
@@ -10,7 +10,10 @@ import * as Host from '../../../core/host/host.js';
10
10
  import * as i18n from '../../../core/i18n/i18n.js';
11
11
  import type * as Platform from '../../../core/platform/platform.js';
12
12
  import * as Root from '../../../core/root/root.js';
13
+ import * as SDK from '../../../core/sdk/sdk.js';
14
+ import type {AiWidget, ComputedStyleAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
13
15
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
16
+ import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
14
17
  import * as Marked from '../../../third_party/marked/marked.js';
15
18
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
16
19
  import * as Input from '../../../ui/components/input/input.js';
@@ -20,9 +23,10 @@ import * as UIHelpers from '../../../ui/helpers/helpers.js';
20
23
  import * as UI from '../../../ui/legacy/legacy.js';
21
24
  import * as Lit from '../../../ui/lit/lit.js';
22
25
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
26
+ import * as Elements from '../../elements/elements.js';
23
27
 
24
28
  import chatMessageStyles from './chatMessage.css.js';
25
- import {WalkthroughView} from './WalkthroughView.js';
29
+ import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
26
30
 
27
31
  const {html, Directives: {ref, ifDefined}} = Lit;
28
32
  const lockedString = i18n.i18n.lockedString;
@@ -96,18 +100,14 @@ const UIStringsNotTranslate = {
96
100
  * @description Displayed when the user stop the response
97
101
  */
98
102
  stoppedResponse: 'You stopped this response',
99
- /**
100
- * @description Prompt for user to confirm code execution that may affect the page.
101
- */
102
- sideEffectConfirmationDescription: 'This code may modify page content. Continue?',
103
103
  /**
104
104
  * @description Button text that confirm code execution that may affect the page.
105
105
  */
106
- positiveSideEffectConfirmation: 'Continue',
106
+ confirmActionRequestApproval: 'Continue',
107
107
  /**
108
108
  * @description Button text that cancels code execution that may affect the page.
109
109
  */
110
- negativeSideEffectConfirmation: 'Cancel',
110
+ declineActionRequestApproval: 'Cancel',
111
111
  /**
112
112
  * @description The generic name of the AI agent (do not translate)
113
113
  */
@@ -168,6 +168,11 @@ const UIStringsNotTranslate = {
168
168
  * @description Title for the button that shows the thinking process (walkthrough).
169
169
  */
170
170
  showThinking: 'Show thinking',
171
+
172
+ /**
173
+ * @description Title for the button that takes the user into other DevTools panels to reveal items the AI references.
174
+ */
175
+ reveal: 'Reveal'
171
176
  } as const;
172
177
 
173
178
  export interface Step {
@@ -176,12 +181,14 @@ export interface Step {
176
181
  title?: string;
177
182
  code?: string;
178
183
  output?: string;
184
+ widgets?: AiWidget[];
179
185
  canceled?: boolean;
180
- sideEffect?: ConfirmSideEffectDialog;
186
+ requestApproval?: ConfirmSideEffectDialog;
181
187
  contextDetails?: [AiAssistanceModel.AiAgent.ContextDetail, ...AiAssistanceModel.AiAgent.ContextDetail[]];
182
188
  }
183
189
 
184
190
  export interface ConfirmSideEffectDialog {
191
+ description: string|null;
185
192
  onAnswer: (result: boolean) => void;
186
193
  }
187
194
 
@@ -386,13 +393,14 @@ function renderTextAsMarkdown(text: string, markdownRenderer: MarkdownLitRendere
386
393
  // clang-format on
387
394
  }
388
395
 
389
- function titleForStep(step: Step): string {
396
+ export function titleForStep(step: Step): string {
390
397
  return step.title ?? `${lockedString(UIStringsNotTranslate.investigating)}…`;
391
398
  }
392
399
 
393
400
  function renderTitle(step: Step): Lit.LitTemplate {
394
- const paused =
395
- step.sideEffect ? html`<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` : Lit.nothing;
401
+ const paused = step.requestApproval ?
402
+ html`<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` :
403
+ Lit.nothing;
396
404
 
397
405
  return html`<span class="title">${paused}${titleForStep(step)}</span>`;
398
406
  }
@@ -444,7 +452,7 @@ function renderStepDetails({
444
452
  markdownRenderer: MarkdownLitRenderer,
445
453
  isLast: boolean,
446
454
  }): Lit.LitTemplate {
447
- const sideEffects = isLast && step.sideEffect ? renderSideEffectConfirmationUi(step) : Lit.nothing;
455
+ const sideEffects = isLast && step.requestApproval ? renderSideEffectConfirmationUi(step) : Lit.nothing;
448
456
  const thought = step.thought ? html`<p>${renderTextAsMarkdown(step.thought, markdownRenderer)}</p>` : Lit.nothing;
449
457
 
450
458
  // clang-format off
@@ -481,7 +489,10 @@ function renderWalkthroughSidebarButton(
481
489
  if (walkthrough.isInlined) {
482
490
  return Lit.nothing;
483
491
  }
484
- const title = input.isLoading ? titleForStep(lastStep) : lockedString(UIStringsNotTranslate.showThinking);
492
+ const title = walkthroughTitle({
493
+ isLoading: input.isLoading,
494
+ lastStep,
495
+ });
485
496
 
486
497
  // clang-format off
487
498
  return html`
@@ -520,7 +531,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
520
531
  // No steps = no walkthrough UI in the chat view.
521
532
  return Lit.nothing;
522
533
  }
523
- const sideEffectSteps = steps.filter(s => s.sideEffect);
534
+ const sideEffectSteps = steps.filter(s => s.requestApproval);
524
535
  // If the walkthrough is in the sidebar, we render a button into the
525
536
  // ChatView to open it.
526
537
  const openWalkThroughSidebarButton =
@@ -552,7 +563,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
552
563
  markdownRenderer: input.markdownRenderer,
553
564
  isInlined: true,
554
565
  isExpanded: input.isLastMessage &&
555
- (input.walkthrough.isExpanded || steps.some(step => Boolean(step.sideEffect))),
566
+ (input.walkthrough.isExpanded || steps.some(step => Boolean(step.requestApproval))),
556
567
  onToggle: input.walkthrough.onToggle,
557
568
  })}></devtools-widget>
558
569
  </div>
@@ -571,14 +582,14 @@ function renderStepBadge({step, isLoading, isLast}: {
571
582
  isLoading: boolean,
572
583
  isLast: boolean,
573
584
  }): Lit.LitTemplate {
574
- if (isLoading && isLast && !step.sideEffect) {
585
+ if (isLoading && isLast && !step.requestApproval) {
575
586
  return html`<devtools-spinner></devtools-spinner>`;
576
587
  }
577
588
 
578
589
  let iconName = 'checkmark';
579
590
  let ariaLabel: string|undefined = lockedString(UIStringsNotTranslate.completed);
580
591
  let role: 'button'|undefined = 'button';
581
- if (isLast && step.sideEffect) {
592
+ if (isLast && step.requestApproval) {
582
593
  role = undefined;
583
594
  ariaLabel = undefined;
584
595
  iconName = 'pause-circle';
@@ -601,17 +612,18 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
601
612
  markdownRenderer: MarkdownLitRenderer,
602
613
  isLast: boolean,
603
614
  }): Lit.LitTemplate {
615
+ const shouldRenderWidgets = Boolean(step.widgets?.length && Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
604
616
  const stepClasses = Lit.Directives.classMap({
605
617
  step: true,
606
- empty: !step.thought && !step.code && !step.contextDetails && !step.sideEffect,
607
- paused: Boolean(step.sideEffect),
618
+ empty: !step.thought && !step.code && !step.contextDetails && !step.requestApproval,
619
+ paused: Boolean(step.requestApproval),
608
620
  canceled: Boolean(step.canceled),
609
621
  });
610
622
  // clang-format off
611
623
  return html`
612
624
  <details class=${stepClasses}
613
625
  jslog=${VisualLogging.section('step')}
614
- .open=${Boolean(step.sideEffect)}>
626
+ .open=${Boolean(step.requestApproval)}>
615
627
  <summary>
616
628
  <div class="summary">
617
629
  ${renderStepBadge({ step, isLoading, isLast })}
@@ -623,12 +635,114 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
623
635
  </div>
624
636
  </summary>
625
637
  ${renderStepDetails({step, markdownRenderer, isLast})}
626
- </details>`;
638
+ </details>
639
+ ${shouldRenderWidgets ? html`
640
+ <div class="step-widgets-wrapper">
641
+ ${Lit.Directives.until(renderStepWidgets(step))}
642
+ </div>` : Lit.nothing
643
+ }`;
644
+ // clang-format on
645
+ }
646
+
647
+ interface WidgetMakerResponse {
648
+ renderedWidget: Lit.LitTemplate;
649
+ revealable: unknown;
650
+ }
651
+
652
+ async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promise<WidgetMakerResponse|null> {
653
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
654
+ if (!target) {
655
+ return null;
656
+ }
657
+ const node = new SDK.DOMModel.DeferredDOMNode(
658
+ target,
659
+ widgetData.data.backendNodeId,
660
+ );
661
+ const resolved = await node.resolvePromise();
662
+ if (!resolved) {
663
+ return null;
664
+ }
665
+ const model = new ComputedStyle.ComputedStyleModel.ComputedStyleModel(resolved);
666
+ const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(resolved, widgetData.data.computedStyles);
667
+
668
+ const widgetConfig = UI.Widget.widgetConfig(Elements.ComputedStyleWidget.ComputedStyleWidget, {
669
+ nodeStyle: styles,
670
+ matchedStyles: widgetData.data.matchedCascade,
671
+ // This disables showing the nested traces and detailed information in the widget.
672
+ propertyTraces: null,
673
+ computedStyleModel: model,
674
+ allowUserControl: false,
675
+ filterText: new RegExp(widgetData.data.properties.join('|'), 'i')
676
+ });
677
+
678
+ // clang-format off
679
+ const widget = html`<devtools-widget class="computed-styles-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
627
680
  // clang-format on
681
+
682
+ return {renderedWidget: widget, revealable: new Elements.ElementsPanel.NodeComputedStyles(resolved)};
683
+ }
684
+
685
+ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
686
+ if (response === null) {
687
+ return Lit.nothing;
688
+ }
689
+
690
+ function onReveal(): void {
691
+ if (response === null) {
692
+ return;
693
+ }
694
+ void Common.Revealer.reveal(response?.revealable);
695
+ }
696
+
697
+ // clang-format off
698
+ return html`
699
+ <div class="widget-content-container">
700
+ ${response.renderedWidget}
701
+ </div>
702
+ <div class="widget-reveal-container">
703
+ <devtools-button class="widget-reveal"
704
+ .iconName=${'tab-move'}
705
+ .variant=${Buttons.Button.Variant.TEXT}
706
+ @click=${onReveal}
707
+ >${lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
708
+ </div>
709
+ `;
710
+ // clang-format on
711
+ }
712
+
713
+ /**
714
+ * Renders AI-defined UI widgets within a step.
715
+ * When a ModelChatMessage contains a WidgetPart, the ChatMessage component
716
+ * iterates through the `step.widgets` array. For each widget, it determines
717
+ * the appropriate rendering logic based on the `widgetData.name`.
718
+ *
719
+ * Currently, only 'COMPUTED_STYLES' widgets are supported. For these, the
720
+ * `makeComputedStyleWidget` function is called to construct the necessary
721
+ * data and configuration for the `Elements.ComputedStyleWidget.ComputedStyleWidget`
722
+ * component. The widget is then rendered using the `<devtools-widget>`
723
+ * custom element, which dynamically instantiates and displays the specified
724
+ * UI.Widget subclass with the provided configuration.
725
+ *
726
+ * This allows for a flexible and extensible system where new widget types
727
+ * can be added to the AI responses and rendered in DevTools by adding
728
+ * corresponding `make...Widget` functions and handling them here.
729
+ */
730
+ async function renderStepWidgets(step: Step): Promise<Lit.LitTemplate> {
731
+ if (!step.widgets || step.widgets.length === 0) {
732
+ return Lit.nothing;
733
+ }
734
+ const ui = await Promise.all(step.widgets.map(async widgetData => {
735
+ if (widgetData.name === 'COMPUTED_STYLES') {
736
+ const response = await makeComputedStyleWidget(widgetData);
737
+ return renderWidgetResponse(response);
738
+ }
739
+ return Lit.nothing;
740
+ }));
741
+ return html`${ui}`;
628
742
  }
629
743
 
630
744
  function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
631
- if (!step.sideEffect) {
745
+ if (!step.requestApproval) {
632
746
  return Lit.nothing;
633
747
  }
634
748
 
@@ -637,7 +751,7 @@ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
637
751
  class="side-effect-confirmation"
638
752
  jslog=${VisualLogging.section('side-effect-confirmation')}
639
753
  >
640
- <p>${lockedString(UIStringsNotTranslate.sideEffectConfirmationDescription)}</p>
754
+ ${step.requestApproval.description ? html`<p>${step.requestApproval.description}</p>` : Lit.nothing}
641
755
  <div class="side-effect-buttons-container">
642
756
  <devtools-button
643
757
  .data=${
@@ -646,9 +760,9 @@ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
646
760
  jslogContext: 'decline-execute-code',
647
761
  } as Buttons.Button.ButtonData
648
762
  }
649
- @click=${() => step.sideEffect?.onAnswer(false)}
763
+ @click=${() => step.requestApproval?.onAnswer(false)}
650
764
  >${lockedString(
651
- UIStringsNotTranslate.negativeSideEffectConfirmation,
765
+ UIStringsNotTranslate.declineActionRequestApproval,
652
766
  )}</devtools-button>
653
767
  <devtools-button
654
768
  .data=${
@@ -658,9 +772,9 @@ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
658
772
  iconName: 'play',
659
773
  } as Buttons.Button.ButtonData
660
774
  }
661
- @click=${() => step.sideEffect?.onAnswer(true)}
775
+ @click=${() => step.requestApproval?.onAnswer(true)}
662
776
  >${
663
- lockedString(UIStringsNotTranslate.positiveSideEffectConfirmation)
777
+ lockedString(UIStringsNotTranslate.confirmActionRequestApproval)
664
778
  }</devtools-button>
665
779
  </div>
666
780
  </div>`;
@@ -20,7 +20,11 @@ import chatViewStyles from './chatView.css.js';
20
20
 
21
21
  export {ChatInput, type ImageInputData} from './ChatInput.js';
22
22
 
23
- const {ref, repeat, classMap} = Directives;
23
+ const {
24
+ ref,
25
+ repeat,
26
+ classMap,
27
+ } = Directives;
24
28
 
25
29
  /*
26
30
  * Strings that don't need to be translated at this time.
@@ -10,9 +10,11 @@ import * as UI from '../../../ui/legacy/legacy.js';
10
10
  import * as Lit from '../../../ui/lit/lit.js';
11
11
 
12
12
  import chatMessageStyles from './chatMessage.css.js';
13
- import {type ModelChatMessage, renderStep} from './ChatMessage.js';
13
+ import {type ModelChatMessage, renderStep, type Step, titleForStep} from './ChatMessage.js';
14
14
  import walkthroughViewStyles from './walkthroughView.css.js';
15
15
 
16
+ const lockedString = i18n.i18n.lockedString;
17
+
16
18
  const {html, render} = Lit;
17
19
 
18
20
  const UIStrings = {
@@ -25,9 +27,9 @@ const UIStrings = {
25
27
  */
26
28
  title: 'Investigation steps',
27
29
  /**
28
- * @description Title for the inline walkthrough view.
30
+ * @description Title for the button that shows the thinking process (walkthrough).
29
31
  */
30
- inlineTitle: 'Show thinking',
32
+ showThinking: 'Show thinking',
31
33
  } as const;
32
34
  const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/WalkthroughView.ts', UIStrings);
33
35
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -41,8 +43,17 @@ export interface ViewInput {
41
43
  onToggle: (isOpen: boolean) => void;
42
44
  }
43
45
 
44
- function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate): Lit.LitTemplate {
45
- if (!input.isInlined) {
46
+ export function walkthroughTitle(input: {
47
+ isLoading: boolean,
48
+ lastStep: Step,
49
+ }): string {
50
+ const title = input.isLoading ? titleForStep(input.lastStep) : lockedString(UIStrings.showThinking);
51
+ return title;
52
+ }
53
+
54
+ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate, steps: Step[]): Lit.LitTemplate {
55
+ const lastStep = steps.at(-1);
56
+ if (!input.isInlined || !lastStep) {
46
57
  return Lit.nothing;
47
58
  }
48
59
 
@@ -54,7 +65,8 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate)
54
65
  return html`
55
66
  <details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle}>
56
67
  <summary>
57
- ${i18nString(UIStrings.inlineTitle)}
68
+ ${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
69
+ ${walkthroughTitle({isLoading: input.isLoading, lastStep,})}
58
70
  <devtools-icon name="chevron-down"></devtools-icon>
59
71
  </summary>
60
72
  ${stepsOutput}
@@ -123,7 +135,7 @@ export const DEFAULT_VIEW = (
123
135
  ${chatMessageStyles}
124
136
  ${walkthroughViewStyles}
125
137
  </style>
126
- ${input.isInlined ? renderInlineWalkthrough(input, stepsOutput)
138
+ ${input.isInlined ? renderInlineWalkthrough(input, stepsOutput, steps)
127
139
  : renderSidebarWalkthrough(input, stepsOutput, steps.length)
128
140
  }`, target);
129
141
  // clang-format on