chrome-devtools-frontend 1.0.1611390 → 1.0.1613465

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 (53) hide show
  1. package/front_end/Images/src/flowsheet.svg +1 -0
  2. package/front_end/core/common/Color.ts +3 -2
  3. package/front_end/core/host/AidaClientTypes.ts +5 -0
  4. package/front_end/core/host/AidaGcaTranslation.ts +1 -0
  5. package/front_end/core/platform/StringUtilities.ts +5 -0
  6. package/front_end/core/sdk/NetworkManager.ts +7 -0
  7. package/front_end/core/sdk/ResourceTreeModel.ts +2 -1
  8. package/front_end/core/sdk/Target.ts +1 -3
  9. package/front_end/generated/InspectorBackendCommands.ts +3 -3
  10. package/front_end/generated/SupportedCSSProperties.js +4 -0
  11. package/front_end/generated/protocol.ts +12 -10
  12. package/front_end/models/greendev/Prototypes.ts +7 -4
  13. package/front_end/models/har/HARFormat.ts +30 -0
  14. package/front_end/models/har/Importer.ts +12 -1
  15. package/front_end/models/har/Log.ts +28 -0
  16. package/front_end/models/web_mcp/WebMCPModel.ts +73 -18
  17. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +64 -25
  18. package/front_end/panels/ai_assistance/PatchWidget.ts +7 -23
  19. package/front_end/panels/ai_assistance/SelectWorkspaceDialog.ts +3 -3
  20. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +7 -0
  21. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  22. package/front_end/panels/ai_assistance/components/ChatMessage.ts +21 -11
  23. package/front_end/panels/ai_assistance/components/ChatView.ts +4 -0
  24. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +20 -10
  25. package/front_end/panels/ai_assistance/components/OptInChangeDialog.ts +179 -0
  26. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +6 -2
  27. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +1 -1
  28. package/front_end/panels/ai_assistance/components/optInChangeDialog.css +95 -0
  29. package/front_end/panels/ai_assistance/components/walkthroughView.css +19 -18
  30. package/front_end/panels/application/CookieItemsView.ts +3 -2
  31. package/front_end/panels/application/WebMCPView.ts +97 -6
  32. package/front_end/panels/application/webMCPView.css +7 -1
  33. package/front_end/panels/changes/ChangesView.ts +0 -12
  34. package/front_end/panels/common/common.ts +0 -1
  35. package/front_end/panels/console/ConsolePrompt.ts +2 -2
  36. package/front_end/panels/elements/StylePropertyTreeElement.ts +2 -1
  37. package/front_end/panels/elements/StylesSidebarPane.ts +3 -1
  38. package/front_end/panels/elements/elements-meta.ts +14 -0
  39. package/front_end/panels/network/NetworkLogView.ts +58 -28
  40. package/front_end/panels/recorder/RecorderController.ts +4 -4
  41. package/front_end/panels/settings/AISettingsTab.ts +39 -3
  42. package/front_end/panels/settings/SettingsScreen.ts +2 -2
  43. package/front_end/panels/sources/BreakpointEditDialog.ts +6 -0
  44. package/front_end/third_party/chromium/README.chromium +1 -1
  45. package/front_end/ui/components/markdown_view/CodeBlock.ts +6 -0
  46. package/front_end/ui/legacy/SplitWidget.ts +9 -6
  47. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +3 -3
  48. package/front_end/ui/legacy/components/source_frame/FontView.ts +101 -88
  49. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +4 -4
  50. package/front_end/ui/visual_logging/KnownContextValues.ts +10 -0
  51. package/mcp/mcp.ts +1 -1
  52. package/package.json +1 -1
  53. package/front_end/panels/common/CopyChangesToPrompt.ts +0 -233
@@ -44,6 +44,7 @@ import {
44
44
  import {DisabledWidget} from './components/DisabledWidget.js';
45
45
  import {ExploreWidget} from './components/ExploreWidget.js';
46
46
  import {MarkdownRendererWithCodeBlock} from './components/MarkdownRendererWithCodeBlock.js';
47
+ import {OptInChangeDialog} from './components/OptInChangeDialog.js';
47
48
  import {PerformanceAgentMarkdownRenderer} from './components/PerformanceAgentMarkdownRenderer.js';
48
49
  import {StylingAgentMarkdownRenderer} from './components/StylingAgentMarkdownRenderer.js';
49
50
  import {
@@ -252,6 +253,16 @@ const UIStringsNotTranslate = {
252
253
  */
253
254
  inputDisclaimerForAccessibilityEnterpriseNoLogging:
254
255
  'Chat messages and the selected Lighthouse report are sent to Google. The content you submit and that is generated by this feature will not be used to improve Google’s AI models. This is an experimental AI feature and won’t always get it right.',
256
+ /**
257
+ * @description Disclaimer text right after the chat input when V2 is enabled.
258
+ */
259
+ inputDisclaimerV2:
260
+ 'Chat messages, data accessible for this site via DevTools panels and Web APIs, and items you select such as network requests, files, and performance traces are sent to Google and may be seen by human reviewers to improve this feature. This is an experimental AI feature and won’t always get it right.',
261
+ /**
262
+ * @description Disclaimer text right after the chat input when V2 is enabled and enterprise logging is off.
263
+ */
264
+ inputDisclaimerEnterpriseNoLoggingV2:
265
+ 'Chat messages, data accessible for this site via DevTools panels and Web APIs, and items you select such as network requests, files, and performance traces are sent to Google. The content submitted to and generated by this feature will not be used to improve Google’s AI models. This is an experimental AI feature and won’t always get it right.',
255
266
  } as const;
256
267
 
257
268
  const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/AiAssistancePanel.ts', UIStrings);
@@ -306,9 +317,8 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
306
317
  ];
307
318
  case AiAssistanceModel.AiHistoryStorage.ConversationType.ACCESSIBILITY:
308
319
  return [
309
- {title: 'What are the accessibility issues on this page?', jslogContext: 'accessibility-default'},
310
- {title: 'How can I fix these accessibility issues?', jslogContext: 'accessibility-default'},
311
- {title: 'What does this Lighthouse report say about accessibility?', jslogContext: 'accessibility-default'},
320
+ {title: 'How can I fix accessibility issues on my page?', jslogContext: 'accessibility-default'},
321
+ {title: 'What accessibility issues exist on my page?', jslogContext: 'accessibility-default'},
312
322
  ];
313
323
  case AiAssistanceModel.AiHistoryStorage.ConversationType.NETWORK:
314
324
 
@@ -780,11 +790,32 @@ export class AiAssistancePanel extends UI.Panel.Panel {
780
790
  return await this.#conversationSummaryAgent.summarizeConversation(markdown);
781
791
  },
782
792
  onTextSubmit: async (
783
-
784
793
  text: string, imageInput?: Host.AidaClient.Part,
785
794
  multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType) => {
786
- Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
787
- await this.#startConversation(text, imageInput, multimodalInputType);
795
+ const submit = (): void => {
796
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
797
+ void this.#startConversation(text, imageInput, multimodalInputType);
798
+ };
799
+
800
+ const isAIV2Enabled = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
801
+ const seenSetting =
802
+ Common.Settings.Settings.instance().moduleSetting('ai-assistance-v2-opt-in-change-dialog-seen');
803
+ if (isAIV2Enabled && !seenSetting.get()) {
804
+ OptInChangeDialog.show({
805
+ onGotIt: () => {
806
+ seenSetting.set(true);
807
+ submit();
808
+ },
809
+ onManageSettings: () => {
810
+ seenSetting.set(true);
811
+ this.#viewOutput.chatView?.setInputValue(text);
812
+ void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
813
+ },
814
+ });
815
+ return;
816
+ }
817
+
818
+ submit();
788
819
  },
789
820
  onInspectElementClick: this.#handleSelectElementClick.bind(this),
790
821
  onFeedbackSubmit: this.#handleFeedbackSubmit.bind(this),
@@ -1397,46 +1428,54 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1397
1428
  return i18nString(UIStrings.inputDisclaimerForEmptyState);
1398
1429
  }
1399
1430
 
1400
- const noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue ===
1431
+ const loggingEnabled = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue !==
1401
1432
  Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
1433
+
1434
+ if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
1435
+ if (loggingEnabled) {
1436
+ return lockedString(UIStringsNotTranslate.inputDisclaimerV2);
1437
+ }
1438
+ return lockedString(UIStringsNotTranslate.inputDisclaimerEnterpriseNoLoggingV2);
1439
+ }
1440
+
1402
1441
  switch (this.#conversation.type) {
1403
1442
  case AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING:
1404
- if (noLogging) {
1405
- return lockedString(UIStringsNotTranslate.inputDisclaimerForStylingEnterpriseNoLogging);
1443
+ if (loggingEnabled) {
1444
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForStyling);
1406
1445
  }
1407
- return lockedString(UIStringsNotTranslate.inputDisclaimerForStyling);
1446
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForStylingEnterpriseNoLogging);
1408
1447
  case AiAssistanceModel.AiHistoryStorage.ConversationType.FILE:
1409
- if (noLogging) {
1410
- return lockedString(UIStringsNotTranslate.inputDisclaimerForFileEnterpriseNoLogging);
1448
+ if (loggingEnabled) {
1449
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForFile);
1411
1450
  }
1412
- return lockedString(UIStringsNotTranslate.inputDisclaimerForFile);
1451
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForFileEnterpriseNoLogging);
1413
1452
  case AiAssistanceModel.AiHistoryStorage.ConversationType.NETWORK:
1414
- if (noLogging) {
1415
- return lockedString(UIStringsNotTranslate.inputDisclaimerForNetworkEnterpriseNoLogging);
1453
+ if (loggingEnabled) {
1454
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForNetwork);
1416
1455
  }
1417
- return lockedString(UIStringsNotTranslate.inputDisclaimerForNetwork);
1456
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForNetworkEnterpriseNoLogging);
1418
1457
 
1419
1458
  // It is deliberate that both Performance agents use the same disclaimer
1420
1459
  // text and this has been approved by Privacy.
1421
1460
  case AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE:
1422
- if (noLogging) {
1423
- return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformanceEnterpriseNoLogging);
1461
+ if (loggingEnabled) {
1462
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformance);
1424
1463
  }
1425
- return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformance);
1464
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformanceEnterpriseNoLogging);
1426
1465
 
1427
1466
  case AiAssistanceModel.AiHistoryStorage.ConversationType.ACCESSIBILITY:
1428
- if (noLogging) {
1429
- return lockedString(UIStringsNotTranslate.inputDisclaimerForAccessibilityEnterpriseNoLogging);
1467
+ if (loggingEnabled) {
1468
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForAccessibility);
1430
1469
  }
1431
- return lockedString(UIStringsNotTranslate.inputDisclaimerForAccessibility);
1470
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForAccessibilityEnterpriseNoLogging);
1432
1471
 
1433
1472
  case AiAssistanceModel.AiHistoryStorage.ConversationType.BREAKPOINT:
1434
1473
 
1435
1474
  case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
1436
- if (noLogging) {
1437
- return lockedString(UIStringsNotTranslate.inputDisclaimerForNoContextEnterpriseNoLogging);
1475
+ if (loggingEnabled) {
1476
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForNoContext);
1438
1477
  }
1439
- return lockedString(UIStringsNotTranslate.inputDisclaimerForNoContext);
1478
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForNoContextEnterpriseNoLogging);
1440
1479
  }
1441
1480
  }
1442
1481
 
@@ -13,7 +13,6 @@ import * as i18n from '../../core/i18n/i18n.js';
13
13
  import * as Platform from '../../core/platform/platform.js';
14
14
  import * as Root from '../../core/root/root.js';
15
15
  import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
16
- import * as GreenDev from '../../models/greendev/greendev.js';
17
16
  import * as Persistence from '../../models/persistence/persistence.js';
18
17
  import * as Workspace from '../../models/workspace/workspace.js';
19
18
  import * as WorkspaceDiff from '../../models/workspace_diff/workspace_diff.js';
@@ -273,20 +272,6 @@ const DEFAULT_VIEW: View = (input, output, target) => {
273
272
  nothing}`;
274
273
  }
275
274
 
276
- function renderCopyPrompt(changedCode?: string): LitTemplate {
277
- if (!GreenDev.Prototypes.instance().isEnabled('copyToGemini') || !changedCode) {
278
- return nothing;
279
- }
280
-
281
- // clang-format off
282
- return html`<devtools-widget class="copy-to-prompt"
283
- ${widget(PanelCommon.CopyChangesToPrompt, {
284
- workspaceDiff: input.workspaceDiff,
285
- patchAgentCSSChange: changedCode,
286
- })}></devtools-widget>`;
287
- // clang-format on
288
- }
289
-
290
275
  function renderFooter(): LitTemplate {
291
276
  if (input.savedToDisk) {
292
277
  return nothing;
@@ -357,7 +342,6 @@ const DEFAULT_VIEW: View = (input, output, target) => {
357
342
  </div>
358
343
  ` :
359
344
  html`
360
- ${renderCopyPrompt(input.changeSummary)}
361
345
  <devtools-button
362
346
  @click=${input.onApplyToWorkspace}
363
347
  .jslogContext=${'patch-widget.apply-to-workspace'}
@@ -435,7 +419,7 @@ export class PatchWidget extends UI.Widget.Widget {
435
419
  #project?: Workspace.Workspace.Project;
436
420
  #patchSources?: string;
437
421
  #savedToDisk?: boolean;
438
- #noLogging: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not.
422
+ #loggingEnabled: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not.
439
423
  #patchSuggestionState = PatchSuggestionState.INITIAL;
440
424
  #workspaceDiff = WorkspaceDiff.WorkspaceDiff.workspaceDiff();
441
425
  #workspace = Workspace.Workspace.WorkspaceImpl.instance();
@@ -450,7 +434,7 @@ export class PatchWidget extends UI.Widget.Widget {
450
434
  }) {
451
435
  super(element);
452
436
  this.#aidaClient = opts?.aidaClient ?? new Host.AidaClient.AidaClient();
453
- this.#noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue ===
437
+ this.#loggingEnabled = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue !==
454
438
  Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
455
439
  this.#view = view;
456
440
 
@@ -514,9 +498,9 @@ export class PatchWidget extends UI.Widget.Widget {
514
498
  projectPath,
515
499
  projectType: this.#getSelectedProjectType(projectPath),
516
500
  savedToDisk: this.#savedToDisk,
517
- applyToWorkspaceTooltipText: this.#noLogging ?
518
- lockedString(UIStringsNotTranslate.applyToWorkspaceTooltipNoLogging) :
519
- lockedString(UIStringsNotTranslate.applyToWorkspaceTooltip),
501
+ applyToWorkspaceTooltipText: this.#loggingEnabled ?
502
+ lockedString(UIStringsNotTranslate.applyToWorkspaceTooltip) :
503
+ lockedString(UIStringsNotTranslate.applyToWorkspaceTooltipNoLogging),
520
504
  onLearnMoreTooltipClick: this.#onLearnMoreTooltipClick.bind(this),
521
505
  onApplyToWorkspace: this.#onApplyToWorkspace.bind(this),
522
506
  onCancel: () => {
@@ -566,8 +550,8 @@ export class PatchWidget extends UI.Widget.Widget {
566
550
  },
567
551
  {
568
552
  iconName: 'google',
569
- content: this.#noLogging ? lockedString(UIStringsNotTranslate.freDisclaimerTextPrivacyNoLogging) :
570
- lockedString(UIStringsNotTranslate.freDisclaimerTextPrivacy),
553
+ content: this.#loggingEnabled ? lockedString(UIStringsNotTranslate.freDisclaimerTextPrivacy) :
554
+ lockedString(UIStringsNotTranslate.freDisclaimerTextPrivacyNoLogging),
571
555
  },
572
556
  {
573
557
  iconName: 'warning',
@@ -233,14 +233,14 @@ export class SelectWorkspaceDialog extends UI.Widget.VBox {
233
233
  }
234
234
 
235
235
  override performUpdate(): void {
236
- const noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue ===
236
+ const loggingEnabled = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue !==
237
237
  Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
238
238
 
239
239
  const viewInput = {
240
240
  folders: this.#folders,
241
241
  selectedIndex: this.#selectedIndex,
242
- selectProjectRootText: noLogging ? lockedString(UIStringsNotTranslate.selectProjectRootNoLogging) :
243
- lockedString(UIStringsNotTranslate.selectProjectRoot),
242
+ selectProjectRootText: loggingEnabled ? lockedString(UIStringsNotTranslate.selectProjectRoot) :
243
+ lockedString(UIStringsNotTranslate.selectProjectRootNoLogging),
244
244
  showAutomaticWorkspaceNudge: this.#automaticFileSystemManager.automaticFileSystem === null &&
245
245
  this.#automaticFileSystemManager.availability === 'available',
246
246
  onProjectSelected: (index: number) => {
@@ -161,6 +161,13 @@ Common.Settings.registerSettingExtension({
161
161
  },
162
162
  });
163
163
 
164
+ Common.Settings.registerSettingExtension({
165
+ category: Common.Settings.SettingCategory.AI,
166
+ settingName: 'ai-assistance-v2-opt-in-change-dialog-seen',
167
+ settingType: Common.Settings.SettingType.BOOLEAN,
168
+ defaultValue: false,
169
+ });
170
+
164
171
  UI.ActionRegistration.registerActionExtension({
165
172
  actionId: 'freestyler.main-menu',
166
173
  contextTypes(): [] {
@@ -11,6 +11,7 @@ export * from './SelectWorkspaceDialog.js';
11
11
  export * as ChatMessage from './components/ChatMessage.js';
12
12
  export * as ExploreWidget from './components/ExploreWidget.js';
13
13
  export * as ExportForAgentsDialog from './components/ExportForAgentsDialog.js';
14
+ export * as OptInChangeDialog from './components/OptInChangeDialog.js';
14
15
  export * as PatchWidget from './PatchWidget.js';
15
16
  export * as DisabledWidget from './components/DisabledWidget.js';
16
17
  export * as WalkthroughView from './components/WalkthroughView.js';
@@ -157,9 +157,13 @@ const UIStringsNotTranslate = {
157
157
  */
158
158
  completed: 'Completed',
159
159
  /**
160
- * @description Aria label for the cancel icon to be read by screen reader
160
+ * @description Aria label for the spinner to be read by screen reader when a step is in progress.
161
161
  */
162
- canceled: 'Canceled',
162
+ inProgress: 'In progress',
163
+ /**
164
+ * @description Aria label for the aborted icon to be read by screen reader
165
+ */
166
+ aborted: 'Aborted',
163
167
  /**
164
168
  * @description Alt text for the image input (displayed in the chat messages) that has been sent to the model.
165
169
  */
@@ -208,6 +212,10 @@ const UIStringsNotTranslate = {
208
212
  * @description Accessilility label for the button that shows the walkthrough when there are no widgets in the walkthrough.
209
213
  */
210
214
  showThinking: 'Show thinking',
215
+ /**
216
+ * @description Accessilility label for the button that hides the walkthrough when there are no widgets in the walkthrough.
217
+ */
218
+ hideThinking: 'Hide thinking',
211
219
  } as const;
212
220
 
213
221
  export interface Step {
@@ -467,7 +475,7 @@ function renderTitle(step: Step): Lit.LitTemplate {
467
475
  html`<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` :
468
476
  Lit.nothing;
469
477
 
470
- return html`<span class="title">${paused}${titleForStep(step)}</span>`;
478
+ return html`<span class="title" aria-label=${titleForStep(step)}>${paused}${titleForStep(step)}</span>`;
471
479
  }
472
480
 
473
481
  function renderStepCode(step: Step): Lit.LitTemplate {
@@ -577,10 +585,10 @@ function renderWalkthroughSidebarButton(
577
585
  });
578
586
 
579
587
  let accessibleLabel = title;
580
- if (!isExpanded) {
581
- if (input.isLoading || lastStep.requestApproval) {
582
- accessibleLabel = `${titleForStep(lastStep)} ${i18n.i18n.lockedString(UIStringsNotTranslate.showThinking)}`;
583
- }
588
+ // If the agent is still thinking we want the accessibility label to include the current step title followed by Show/Hide thinking.
589
+ if (input.isLoading) {
590
+ const suffix = isExpanded ? UIStringsNotTranslate.hideThinking : UIStringsNotTranslate.showThinking;
591
+ accessibleLabel = `${titleForStep(lastStep)} ${i18n.i18n.lockedString(suffix)}`;
584
592
  }
585
593
 
586
594
  // clang-format off
@@ -683,7 +691,7 @@ function renderStepBadge({step, isLoading, isLast}: {
683
691
  isLast: boolean,
684
692
  }): Lit.LitTemplate {
685
693
  if (isLoading && isLast && !step.requestApproval) {
686
- return html`<devtools-spinner></devtools-spinner>`;
694
+ return html`<devtools-spinner aria-label=${lockedString(UIStringsNotTranslate.inProgress)}></devtools-spinner>`;
687
695
  }
688
696
 
689
697
  let iconName = 'checkmark';
@@ -691,10 +699,10 @@ function renderStepBadge({step, isLoading, isLast}: {
691
699
  let role: 'button'|undefined = 'button';
692
700
  if (isLast && step.requestApproval) {
693
701
  role = undefined;
694
- ariaLabel = undefined;
702
+ ariaLabel = lockedString(UIStringsNotTranslate.paused);
695
703
  iconName = 'pause-circle';
696
704
  } else if (step.canceled) {
697
- ariaLabel = lockedString(UIStringsNotTranslate.canceled);
705
+ ariaLabel = lockedString(UIStringsNotTranslate.aborted);
698
706
  iconName = 'cross';
699
707
  }
700
708
 
@@ -938,6 +946,7 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
938
946
  const revealButton = html`
939
947
  <devtools-button class="widget-reveal-button"
940
948
  .variant=${Buttons.Button.Variant.TEXT}
949
+ .accessibleLabel=${lockedString(UIStringsNotTranslate.reveal)}
941
950
  @click=${onReveal}
942
951
  >
943
952
  ${response.customRevealTitle ?? lockedString(UIStringsNotTranslate.reveal)}
@@ -950,7 +959,7 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
950
959
  <div class=${classes}>
951
960
  ${response.title ? html`
952
961
  <div class="widget-header">
953
- <div class="widget-name">${response.title}</div>
962
+ <h3 class="widget-name">${response.title}</h3>
954
963
  <div class="widget-reveal-container">
955
964
  ${revealButton}
956
965
  </div>
@@ -1255,6 +1264,7 @@ function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.Lit
1255
1264
  .jslogContext=${'ai-export-for-agents'}
1256
1265
  .variant=${Buttons.Button.Variant.OUTLINED}
1257
1266
  .iconName=${'copy'}
1267
+ aria-label=${lockedString(UIStringsNotTranslate.exportForAgents)}
1258
1268
  @click=${input.onExportClick}
1259
1269
  >${lockedString(UIStringsNotTranslate.exportForAgents)}</devtools-button>
1260
1270
  ${input.suggestions ? html`<div class="vertical-separator"></div>` : Lit.nothing}
@@ -282,6 +282,10 @@ export class ChatView extends HTMLElement {
282
282
  textArea.focus();
283
283
  }
284
284
 
285
+ setInputValue(text: string): void {
286
+ this.#output.input?.getWidget()?.setInputValue(text);
287
+ }
288
+
285
289
  restoreScrollPosition(): void {
286
290
  if (this.#scrollTop === undefined) {
287
291
  return;
@@ -59,6 +59,8 @@ export const enum StateType {
59
59
  CONVERSATION = 'conversation',
60
60
  }
61
61
 
62
+ const DEFAULT_STATE_TYPE = StateType.PROMPT;
63
+
62
64
  export interface State {
63
65
  activeType: StateType;
64
66
  promptText: string;
@@ -78,26 +80,25 @@ type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
78
80
  export const DEFAULT_VIEW: View = (input, _output, target): void => {
79
81
  const isPrompt = input.state.activeType === StateType.PROMPT;
80
82
  const buttonText = isPrompt ? i18nString(UIStrings.copyToClipboard) : i18nString(UIStrings.saveAsMarkdown);
81
- const exportText = isPrompt && input.state.isPromptLoading ?
82
- i18nString(UIStrings.generatingSummary) :
83
- (isPrompt ? input.state.promptText : input.state.conversationText);
83
+ const exportText = isPrompt ? input.state.promptText : input.state.conversationText;
84
84
  // clang-format off
85
85
 
86
86
  render(html`
87
87
  <style>${styles}</style>
88
88
  <div class="export-for-agents-dialog">
89
89
  <header>
90
- <h2 tabindex="-1">
90
+ <h1 id="export-for-agents-dialog-title" tabindex="-1">
91
91
  ${i18nString(UIStrings.exportForAgents)}
92
- </h2>
92
+ </h1>
93
93
  </header>
94
- <div class="state-selection">
94
+ <div class="state-selection" role="radiogroup" aria-labelledby="export-for-agents-dialog-title">
95
95
  <label>
96
96
  <input
97
97
  type="radio"
98
98
  value="prompt"
99
99
  name="export-state"
100
100
  .checked=${isPrompt}
101
+ aria-label=${i18nString(UIStrings.asPrompt)}
101
102
  @change=${() => input.onStateChange(StateType.PROMPT)}
102
103
  >
103
104
  ${i18nString(UIStrings.asPrompt)}
@@ -108,19 +109,20 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
108
109
  value="conversation"
109
110
  name="export-state"
110
111
  .checked=${!isPrompt}
112
+ aria-label=${i18nString(UIStrings.asMarkdown)}
111
113
  @change=${() => input.onStateChange(StateType.CONVERSATION)}
112
114
  >
113
115
  ${i18nString(UIStrings.asMarkdown)}
114
116
  </label>
115
117
  </div>
116
118
  <main>
117
- ${input.state.isPromptLoading ? html`
119
+ ${isPrompt && input.state.isPromptLoading ? html`
118
120
  <span class="prompt-loading">
119
121
  <devtools-spinner></devtools-spinner>
120
122
  ${i18nString(UIStrings.generatingSummary)}
121
123
  </span>
122
124
  ` : Lit.nothing}
123
- <textarea readonly .value=${input.state.isPromptLoading ? '' : exportText}></textarea>
125
+ <textarea readonly .value=${isPrompt && input.state.isPromptLoading ? '' : exportText}></textarea>
124
126
  </main>
125
127
  <div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>
126
128
  <footer>
@@ -130,6 +132,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
130
132
  .jslogContext=${input.jslogContext}
131
133
  .variant=${Buttons.Button.Variant.PRIMARY}
132
134
  .disabled=${isPrompt && input.state.isPromptLoading}
135
+ .accessibleLabel=${buttonText}
133
136
  >
134
137
  ${buttonText}
135
138
  </devtools-button>
@@ -141,6 +144,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
141
144
  };
142
145
 
143
146
  export class ExportForAgentsDialog extends UI.Widget.VBox {
147
+ static #lastSelectedType: StateType = DEFAULT_STATE_TYPE;
144
148
  readonly #view: View;
145
149
  readonly #dialog: UI.Dialog.Dialog;
146
150
  #state: State;
@@ -157,7 +161,7 @@ export class ExportForAgentsDialog extends UI.Widget.VBox {
157
161
  super();
158
162
  this.#dialog = options.dialog;
159
163
  this.#state = {
160
- activeType: StateType.PROMPT,
164
+ activeType: ExportForAgentsDialog.#lastSelectedType,
161
165
  promptText: typeof options.promptText === 'string' ? options.promptText : '',
162
166
  conversationText: options.markdownText,
163
167
  isPromptLoading: typeof options.promptText !== 'string',
@@ -176,8 +180,13 @@ export class ExportForAgentsDialog extends UI.Widget.VBox {
176
180
  this.requestUpdate();
177
181
  }
178
182
 
183
+ static clearPersistedViewState(): void {
184
+ ExportForAgentsDialog.#lastSelectedType = DEFAULT_STATE_TYPE;
185
+ }
186
+
179
187
  #onStateChange = (newState: StateType): void => {
180
188
  this.#state.activeType = newState;
189
+ ExportForAgentsDialog.#lastSelectedType = newState;
181
190
  this.requestUpdate();
182
191
  };
183
192
 
@@ -191,9 +200,10 @@ export class ExportForAgentsDialog extends UI.Widget.VBox {
191
200
  onButtonClick = (event: Event): void => {
192
201
  event.preventDefault();
193
202
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(this.#state.promptText);
194
- Snackbars.Snackbar.Snackbar.show({
203
+ const snackbar = Snackbars.Snackbar.Snackbar.show({
195
204
  message: i18nString(UIStrings.copiedToClipboard),
196
205
  });
206
+ snackbar.setAttribute('aria-label', i18nString(UIStrings.copiedToClipboard));
197
207
  this.#dialog.hide();
198
208
  };
199
209
  break;
@@ -0,0 +1,179 @@
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 i18n from '../../../core/i18n/i18n.js';
6
+ import * as Root from '../../../core/root/root.js';
7
+ import * as Buttons from '../../../ui/components/buttons/buttons.js';
8
+ import * as UI from '../../../ui/legacy/legacy.js';
9
+ import * as Lit from '../../../ui/lit/lit.js';
10
+ import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
11
+
12
+ import styles from './optInChangeDialog.css.js';
13
+
14
+ const {html, render} = Lit;
15
+
16
+ const UIStrings = {
17
+ /**
18
+ * @description Title for the opt-in change dialog.
19
+ */
20
+ title: 'AI assistance just got better',
21
+ /**
22
+ * @description First point in the opt-in change dialog, describing the new integration.
23
+ */
24
+ integrationPoint:
25
+ 'AI assistance is now integrated with Application and Lighthouse panels, and pulls context from data sources simultaneously',
26
+ /**
27
+ * @description Second point in the opt-in change dialog, describing the new widgets.
28
+ */
29
+ widgetPoint: 'Use widgets to verify results or jump to source data for select debugging cases',
30
+ /**
31
+ * @description Third point in the opt-in change dialog (disclaimer) for regular users.
32
+ */
33
+ privacyDisclaimer:
34
+ 'Chat messages, data accessible for this site via DevTools panels and Web APIs, and items you select such as network requests, files, and performance traces are sent to Google and may be seen by human reviewers to improve this feature. This is an experimental AI feature and won’t always get it right.',
35
+ /**
36
+ * @description Third point in the opt-in change dialog (disclaimer) for enterprise users with logging disabled.
37
+ */
38
+ privacyDisclaimerEnterpriseNoLogging:
39
+ 'Chat messages, data accessible for this site via DevTools panels and Web APIs, and items you select such as network requests, files, and performance traces are sent to Google. The content submitted to and generated by this feature will not be used to improve Google’s AI models. This is an experimental AI feature and won’t always get it right.',
40
+ /**
41
+ * @description Button text for managing settings.
42
+ */
43
+ manageSettings: 'Manage in settings',
44
+ /**
45
+ * @description Button text for acknowledging the changes.
46
+ */
47
+ gotIt: 'Got it',
48
+ } as const;
49
+
50
+ const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/OptInChangeDialog.ts', UIStrings);
51
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
52
+
53
+ interface ViewInput {
54
+ onGotIt: () => void;
55
+ onManageSettings: () => void;
56
+ loggingEnabled: boolean;
57
+ }
58
+
59
+ type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
60
+
61
+ export const DEFAULT_VIEW: View = (input, _output, target): void => {
62
+ const disclaimer = input.loggingEnabled ? i18nString(UIStrings.privacyDisclaimer) :
63
+ i18nString(UIStrings.privacyDisclaimerEnterpriseNoLogging);
64
+ // clang-format off
65
+ render(html`
66
+ <style>${styles}</style>
67
+ <div class="opt-in-change-dialog" jslog=${VisualLogging.dialog('ai-v2-opt-in-change-dialog')}>
68
+ <header>
69
+ <div class="header-icon-container">
70
+ <devtools-icon name="smart-assistant" role="presentation"></devtools-icon>
71
+ </div>
72
+ <h1 tabindex="-1">
73
+ ${i18nString(UIStrings.title)}
74
+ </h1>
75
+ </header>
76
+ <main>
77
+ <div class="item">
78
+ <devtools-icon name="lightbulb-spark" role="presentation"></devtools-icon>
79
+ <div class="text">${i18nString(UIStrings.integrationPoint)}</div>
80
+ </div>
81
+ <div class="item">
82
+ <devtools-icon name="flowsheet" role="presentation"></devtools-icon>
83
+ <div class="text">${i18nString(UIStrings.widgetPoint)}</div>
84
+ </div>
85
+ <div class="item">
86
+ <devtools-icon name="google" role="presentation"></devtools-icon>
87
+ <div class="text">${disclaimer}</div>
88
+ </div>
89
+ </main>
90
+ <footer>
91
+ <div class="right-buttons">
92
+ <devtools-button
93
+ @click=${input.onManageSettings}
94
+ .jslogContext=${'ai-assistance-v2-opt-in.manage-settings'}
95
+ .variant=${Buttons.Button.Variant.OUTLINED}
96
+ .accessibleLabel=${i18nString(UIStrings.manageSettings)}
97
+ >
98
+ ${i18nString(UIStrings.manageSettings)}
99
+ </devtools-button>
100
+ <devtools-button
101
+ @click=${input.onGotIt}
102
+ .jslogContext=${'ai-assistance-v2-opt-in.got-it'}
103
+ .variant=${Buttons.Button.Variant.PRIMARY}
104
+ .accessibleLabel=${i18nString(UIStrings.gotIt)}
105
+ >
106
+ ${i18nString(UIStrings.gotIt)}
107
+ </devtools-button>
108
+ </div>
109
+ </footer>
110
+ </div>
111
+ `, target);
112
+ // clang-format on
113
+ };
114
+
115
+ export class OptInChangeDialog extends UI.Widget.VBox {
116
+ readonly #view: View;
117
+ readonly #onGotIt: () => void;
118
+ readonly #onManageSettings: () => void;
119
+
120
+ constructor(
121
+ options: {
122
+ onGotIt: () => void,
123
+ onManageSettings: () => void,
124
+ },
125
+ view: View = DEFAULT_VIEW) {
126
+ super();
127
+ this.#onGotIt = options.onGotIt;
128
+ this.#onManageSettings = options.onManageSettings;
129
+ this.#view = view;
130
+
131
+ this.requestUpdate();
132
+ }
133
+
134
+ override performUpdate(): void {
135
+ const loggingEnabled = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue !==
136
+ Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
137
+
138
+ const viewInput = {
139
+ onGotIt: this.#onGotIt,
140
+ onManageSettings: this.#onManageSettings,
141
+ loggingEnabled,
142
+ };
143
+
144
+ this.#view(viewInput, undefined, this.contentElement);
145
+ }
146
+
147
+ focusTitle(): void {
148
+ this.contentElement.querySelector('h1')?.focus();
149
+ }
150
+
151
+ static show(options: {
152
+ onGotIt: () => void,
153
+ onManageSettings: () => void,
154
+ }): void {
155
+ const dialog = new UI.Dialog.Dialog();
156
+ dialog.setAriaLabel(i18nString(UIStrings.title));
157
+ dialog.setOutsideClickCallback(event => event.consume(true));
158
+ dialog.setCloseOnEscape(false);
159
+ dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.MEASURE_CONTENT);
160
+ dialog.setDimmed(true);
161
+
162
+ const optInChangeDialog = new OptInChangeDialog({
163
+ onGotIt: () => {
164
+ dialog.hide();
165
+ options.onGotIt();
166
+ },
167
+ onManageSettings: () => {
168
+ dialog.hide();
169
+ options.onManageSettings();
170
+ },
171
+ });
172
+ optInChangeDialog.show(dialog.contentElement);
173
+
174
+ void optInChangeDialog.updateComplete.then(() => {
175
+ dialog.show();
176
+ optInChangeDialog.focusTitle();
177
+ });
178
+ }
179
+ }