chrome-devtools-frontend 1.0.1596535 → 1.0.1597448

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 (29) hide show
  1. package/agents/prompts/ui-widgets.md +7 -8
  2. package/docs/ui_engineering.md +10 -11
  3. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  4. package/front_end/core/host/UserMetrics.ts +12 -0
  5. package/front_end/core/root/Runtime.ts +5 -0
  6. package/front_end/core/sdk/CPUThrottlingManager.ts +9 -12
  7. package/front_end/core/sdk/PageResourceLoader.ts +22 -1
  8. package/front_end/devtools_compatibility.js +2 -1
  9. package/front_end/models/ai_assistance/AiConversation.ts +5 -0
  10. package/front_end/models/ai_assistance/agents/AiAgent.ts +4 -0
  11. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +24 -0
  12. package/front_end/models/ai_assistance/agents/StylingAgent.ts +289 -12
  13. package/front_end/models/greendev/Prototypes.ts +7 -1
  14. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +35 -33
  15. package/front_end/panels/ai_assistance/PatchWidget.ts +6 -6
  16. package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -28
  17. package/front_end/panels/ai_assistance/components/ChatView.ts +6 -11
  18. package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +200 -0
  19. package/front_end/panels/ai_assistance/components/chatMessage.css +0 -8
  20. package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
  21. package/front_end/panels/common/ExtensionServer.ts +15 -0
  22. package/front_end/panels/elements/ElementsTreeElement.ts +55 -47
  23. package/front_end/panels/elements/ElementsTreeOutline.ts +149 -28
  24. package/front_end/panels/lighthouse/LighthousePanel.ts +8 -0
  25. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  26. package/front_end/ui/legacy/UIUtils.ts +5 -5
  27. package/front_end/ui/legacy/Widget.ts +33 -2
  28. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  29. package/package.json +1 -1
@@ -7,8 +7,10 @@ import * as i18n from '../../../core/i18n/i18n.js';
7
7
  import * as Platform from '../../../core/platform/platform.js';
8
8
  import * as Root from '../../../core/root/root.js';
9
9
  import * as SDK from '../../../core/sdk/sdk.js';
10
- import type * as Protocol from '../../../generated/protocol.js';
10
+ import * as Protocol from '../../../generated/protocol.js';
11
+ import * as Greendev from '../../../models/greendev/greendev.js';
11
12
  import * as Annotations from '../../annotations/annotations.js';
13
+ import * as Emulation from '../../emulation/emulation.js';
12
14
  import {ChangeManager} from '../ChangeManager.js';
13
15
  import {debugLog} from '../debug.js';
14
16
  import {EvaluateAction, formatError, SideEffectError} from '../EvaluateAction.js';
@@ -24,6 +26,7 @@ import {
24
26
  type ConversationSuggestions,
25
27
  type FunctionCallHandlerResult,
26
28
  type FunctionHandlerOptions,
29
+ type MultimodalInput,
27
30
  MultimodalInputType,
28
31
  type RequestOptions,
29
32
  ResponseType
@@ -45,13 +48,14 @@ const UIStringsNotTranslate = {
45
48
 
46
49
  const lockedString = i18n.i18n.lockedString;
47
50
 
48
- /**
49
- * WARNING: preamble defined in code is only used when userTier is
50
- * TESTERS. Otherwise, a server-side preamble is used (see
51
- * chrome_preambles.gcl). Sync local changes with the server-side.
52
- */
53
- /* clang-format off */
54
- const preamble = `You are the most advanced CSS/DOM/HTML debugging assistant integrated into Chrome DevTools.
51
+ function getPreamble(): string {
52
+ /**
53
+ * WARNING: preamble defined in code is only used when userTier is
54
+ * TESTERS. Otherwise, a server-side preamble is used (see
55
+ * chrome_preambles.gcl). Sync local changes with the server-side.
56
+ */
57
+ /* clang-format off */
58
+ let preamble = `You are the most advanced CSS/DOM/HTML debugging assistant integrated into Chrome DevTools.
55
59
  You always suggest considering the best web development practices and the newest platform features such as view transitions.
56
60
  The user selected a DOM element in the browser's DevTools and sends a query about the page or the selected DOM element.
57
61
  First, examine the provided context, then use the functions to gather additional context and resolve the user request.
@@ -73,6 +77,47 @@ First, examine the provided context, then use the functions to gather additional
73
77
  * **CRITICAL** NEVER output text before a function call. Always do a function call first.
74
78
  * **CRITICAL** When answering questions about positioning or layout, ALWAYS inspect \`position\`, \`display\` and ALL related properties.
75
79
  * **CRITICAL** You are a CSS/DOM/HTML debugging assistant. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, religion, race, politics, sexuality, gender, or any other non web-development topics. Answer "Sorry, I can't answer that. I'm best at questions about debugging web pages." to such questions.`;
80
+
81
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
82
+ if (greenDevEmulationEnabled) {
83
+ preamble += `
84
+ # Emulation and Screenshots
85
+
86
+ * If asked to verify whether the page is visually broken or if there are display problems with specific devices, use the \`activateDeviceEmulation\` tool. This tool will activate emulation for a specified device and capture a screenshot.
87
+ * **DEVICE SELECTION**: You must choose the most closely related device match from the allowed list.
88
+ * If the user asks about a specific device (e.g., "iPhone 6"), choose the closest match (e.g., "iPhone 6/7/8").
89
+ * If the user specifies a generic category (e.g., "Android phone", "iPhone", "Samsung"), choose the device with the highest version number available in that category (e.g., "Pixel 7" or "Samsung Galaxy S20" for Android, "iPhone 14 Pro Max" for iPhone).
90
+ * **VISION DEFICIENCY**: If the user asks about checking for color blindness or vision issues, you can pass an optional \`visionDeficiency\` parameter to \`activateDeviceEmulation\`. Allowed values are: 'blurredVision', 'reducedContrast', 'achromatopsia', 'deuteranopia', 'protanopia', 'tritanopia'.
91
+ * **IMPORTANT**: This is a **TWO-STEP** process.
92
+ * **STEP 1**: Call \`activateDeviceEmulation\`. After calling this tool, YOU MUST STOP and tell the user that the screenshot has been captured and ask them whether they would like you to focus on specific sections of the screenshot or review it all for possible problems.
93
+ * **STEP 2**: The captured screenshot will be automatically attached to the user's **NEXT** query.
94
+ * **CRITICAL**: DO NOT try to investigate/analyze the page state or element visibility automatically. But, after the user has requested to analyze the page, you can prompt the user to select one of the problematic elements if they want to diagnose further.
95
+ * **CRITICAL**: The output of the analysis should only be in json form (no supplemental text) and the json should list the problems found on the device, with a short description of the problem. If identical problems are identified acress multiple devices, feel free to combine sections.
96
+ * **CRITICAL**: ALWAYS escape single and double quotes within the json output strings (\' and \").
97
+ *
98
+ * Example (with no duplication):
99
+
100
+ [
101
+ {
102
+ "Problem": "Element not resizing",
103
+ "Element": "Hero banner",
104
+ "NodeId": "23",
105
+ "Details": "The \"hero\" element is not resizing because... etc etc."
106
+ }
107
+ ]
108
+
109
+ # Additional notes:
110
+
111
+ When referring to an element for which you know the nodeId, annotate your output using markdown link syntax:
112
+ - For example, if nodeId is 23: ([link](#node-23))
113
+ - Always prefix the nodeId with the 'node-' prefix when using the markdown syntax.
114
+ - This link will reveal the element in the Elements panel
115
+ - Never mention node or nodeId when referring to the element, and especially not in the link text.`;
116
+ }
117
+
118
+ return preamble;
119
+ }
120
+
76
121
  /* clang-format on */
77
122
 
78
123
  const promptForScreenshot =
@@ -237,10 +282,11 @@ export class NodeContext extends ConversationContext<SDK.DOMModel.DOMNode> {
237
282
  * instance for a new conversation.
238
283
  */
239
284
  export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
240
- preamble = preamble;
285
+ preamble = getPreamble();
241
286
  readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_STYLING_AGENT;
242
287
  get userTier(): string|undefined {
243
- return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
288
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
289
+ return greenDevEmulationEnabled ? 'TESTERS' : Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
244
290
  }
245
291
  get executionMode(): Root.Runtime.HostConfigFreestylerExecutionMode {
246
292
  return Root.Runtime.hostConfig.devToolsFreestyler?.executionMode ??
@@ -269,6 +315,8 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
269
315
 
270
316
  #changes: ChangeManager;
271
317
  #createExtensionScope: CreateExtensionScopeFunction;
318
+ #greenDevEmulationScreenshot: string|null = null;
319
+ #greenDevEmulationAxTree: string|null = null;
272
320
 
273
321
  constructor(opts: AgentOptions) {
274
322
  super(opts);
@@ -447,6 +495,37 @@ const data = {
447
495
  },
448
496
  });
449
497
  }
498
+
499
+ this.declareFunction<{
500
+ deviceName: string,
501
+ visionDeficiency?: string,
502
+ }>('activateDeviceEmulation', {
503
+ description:
504
+ 'Sets emulation viewing mode for a specific device and optionally enables vision deficiency emulation.',
505
+ parameters: {
506
+ type: Host.AidaClient.ParametersTypes.OBJECT,
507
+ description: '',
508
+ nullable: false,
509
+ properties: {
510
+ deviceName: {
511
+ type: Host.AidaClient.ParametersTypes.STRING,
512
+ description:
513
+ 'The name of the device to emulate. Allowed values: Pixel 3 XL, Pixel 7, Samsung Galaxy S8+, Samsung Galaxy S20 Ultra, Surface Pro 7, Surface Duo, Galaxy Z Fold 5, Asus Zenbook Fold, Samsung Galaxy A51/71, Nest Hub Max, Nest Hub, iPhone 4, iPhone 5/SE, iPhone 6/7/8, iPhone SE, iPhone XR, iPhone 12 Pro, iPhone 14 Pro Max, iPad Mini, iPad Air, iPad Pro.',
514
+ nullable: false,
515
+ },
516
+ visionDeficiency: {
517
+ type: Host.AidaClient.ParametersTypes.STRING,
518
+ description:
519
+ 'Optional vision deficiency to emulate. Allowed values: blurredVision, reducedContrast, achromatopsia, deuteranopia, protanopia, tritanopia.',
520
+ nullable: true,
521
+ },
522
+ },
523
+ required: ['deviceName']
524
+ },
525
+ handler: async params => {
526
+ return await this.activateDeviceEmulation(params.deviceName, params.visionDeficiency);
527
+ },
528
+ });
450
529
  }
451
530
 
452
531
  async generateObservation(
@@ -761,6 +840,198 @@ const data = {
761
840
  };
762
841
  }
763
842
 
843
+ async #compressScreenshot(base64Data: string): Promise<string> {
844
+ return await new Promise((resolve, reject) => {
845
+ const img = new Image();
846
+ img.onload = () => {
847
+ // eslint-disable-next-line @devtools/no-imperative-dom-api
848
+ const canvas = document.createElement('canvas');
849
+ const maxDimension = 2000;
850
+ let scale = 1;
851
+ if (img.width > maxDimension || img.height > maxDimension) {
852
+ scale = maxDimension / Math.max(img.width, img.height);
853
+ }
854
+ canvas.width = img.width * scale;
855
+ canvas.height = img.height * scale;
856
+
857
+ const ctx = canvas.getContext('2d');
858
+ if (!ctx) {
859
+ reject(new Error('Could not get canvas context'));
860
+ return;
861
+ }
862
+ ctx.imageSmoothingEnabled = true;
863
+ ctx.imageSmoothingQuality = 'high';
864
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
865
+ const dataUrl = canvas.toDataURL('image/jpeg', 0.9);
866
+ resolve(dataUrl.split(',')[1]);
867
+ };
868
+ img.onerror = e => reject(new Error('Image load error: ' + e));
869
+ img.src = 'data:image/png;base64,' + base64Data;
870
+ });
871
+ }
872
+
873
+ async activateDeviceEmulation(deviceName: string, visionDeficiency?: string):
874
+ Promise<FunctionCallHandlerResult<unknown>> {
875
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
876
+ if (!greenDevEmulationEnabled) {
877
+ return {error: `GreenDev emulation capabilities not enabled`};
878
+ }
879
+
880
+ // eslint-disable-next-line no-console
881
+ console.log('activateDeviceEmulation called with device:', deviceName, 'visionDeficiency:', visionDeficiency);
882
+
883
+ this.#greenDevEmulationScreenshot = null;
884
+ this.#greenDevEmulationAxTree = null;
885
+
886
+ const emulatedDevicesList = Emulation.EmulatedDevices.EmulatedDevicesList.instance();
887
+ const device = emulatedDevicesList.standard().find(d => d.title === deviceName);
888
+
889
+ if (!device) {
890
+ return {
891
+ error: `Could not find device "${deviceName}" in the list of emulated devices.`,
892
+ };
893
+ }
894
+
895
+ const deviceModeModel = Emulation.DeviceModeModel.DeviceModeModel.instance();
896
+
897
+ const verticalMode = device.modesForOrientation(Emulation.EmulatedDevices.Vertical)[0];
898
+ if (!verticalMode) {
899
+ return {
900
+ error: `Could not find vertical mode for "${deviceName}".`,
901
+ };
902
+ }
903
+ deviceModeModel.emulate(Emulation.DeviceModeModel.Type.Device, device, verticalMode);
904
+
905
+ // Get the selected node early to use for both vision deficiency and wait mechanism.
906
+ const selectedNode = this.#getSelectedNode();
907
+
908
+ // Apply vision deficiency if provided (and turn it off when not provided).
909
+ try {
910
+ if (selectedNode) {
911
+ const target = selectedNode.domModel().target();
912
+ const emulationModel = target.model(SDK.EmulationModel.EmulationModel);
913
+ if (emulationModel) {
914
+ let type = Protocol.Emulation.SetEmulatedVisionDeficiencyRequestType.None;
915
+ if (visionDeficiency && visionDeficiency !== 'none') {
916
+ type = visionDeficiency as Protocol.Emulation.SetEmulatedVisionDeficiencyRequestType;
917
+ }
918
+ await target.emulationAgent().invoke_setEmulatedVisionDeficiency({type});
919
+ }
920
+ } else {
921
+ console.error('No selected node context to retrieve EmulationModel.');
922
+ }
923
+ } catch {
924
+ return {
925
+ error: `Unable to apply vision deficiency "${visionDeficiency}".`,
926
+ };
927
+ }
928
+
929
+ // Wait for the layout to settle after emulation changes.
930
+ // We use a double requestAnimationFrame to ensure at least one frame is rendered.
931
+ if (selectedNode) {
932
+ try {
933
+ const code = 'await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))';
934
+ // We use throwOnSideEffect: false because this is a benign wait, not a modification of the page state relevant to the user.
935
+ await this.#execJs(code, {throwOnSideEffect: false, contextNode: selectedNode});
936
+ } catch (e) {
937
+ console.error('Failed to wait for layout settle:', e);
938
+ }
939
+ }
940
+
941
+ const orientation = device.orientationByName(Emulation.EmulatedDevices.Vertical);
942
+ const width = orientation.width;
943
+
944
+ // TODO(finnur): Investigate better screen capture alternatives (that can do the whole page).
945
+ let documentHeight = 2000;
946
+ if (selectedNode) {
947
+ try {
948
+ const heightJs = 'document.body.scrollHeight';
949
+ const result = await this.#execJs(heightJs, {throwOnSideEffect: false, contextNode: selectedNode});
950
+ const parsedHeight = Number(result);
951
+ if (!isNaN(parsedHeight)) {
952
+ documentHeight = Math.min(parsedHeight, 2000);
953
+ }
954
+ } catch (e) {
955
+ console.error('Failed to get document height:', e);
956
+ }
957
+ }
958
+
959
+ // Specify a clip capping the height to the top 5000px.
960
+ const clip: Protocol.Page.Viewport = {
961
+ x: 0,
962
+ y: 0,
963
+ width,
964
+ height: documentHeight,
965
+ scale: 1,
966
+ };
967
+
968
+ // Capture using the clip. fullSize must be false when clip is used.
969
+ const screenshot = await deviceModeModel.captureScreenshot(false, clip);
970
+
971
+ if (!screenshot) {
972
+ return {
973
+ error: `Emulation for ${deviceName} activated, but failed to capture screenshot.`,
974
+ };
975
+ }
976
+
977
+ try {
978
+ this.#greenDevEmulationScreenshot = await this.#compressScreenshot(screenshot);
979
+ } catch (e) {
980
+ console.error('Screenshot compression failed, using original', e);
981
+ this.#greenDevEmulationScreenshot = screenshot;
982
+ }
983
+
984
+ try {
985
+ if (selectedNode) {
986
+ const accessibilityModel = selectedNode.domModel().target().model(SDK.AccessibilityModel.AccessibilityModel);
987
+ if (accessibilityModel) {
988
+ await accessibilityModel.resumeModel();
989
+ const axResponse = await accessibilityModel.agent.invoke_getFullAXTree({});
990
+ if (!axResponse.getError()) {
991
+ this.#greenDevEmulationAxTree = JSON.stringify(axResponse.nodes);
992
+ } else {
993
+ console.error('Failed to capture Accessibility Tree:', axResponse.getError());
994
+ }
995
+ }
996
+ }
997
+ } catch (e) {
998
+ console.error('Exception capturing Accessibility Tree:', e);
999
+ }
1000
+
1001
+ let resultMsg = `Emulation for ${deviceName} activated and screenshot has been captured.`;
1002
+ if (visionDeficiency) {
1003
+ resultMsg += ` Vision deficiency "${visionDeficiency}" was also applied.`;
1004
+ }
1005
+ resultMsg += ' Ready for analysis.';
1006
+
1007
+ return {
1008
+ result: resultMsg,
1009
+ };
1010
+ }
1011
+
1012
+ override popPendingMultimodalInput(): MultimodalInput|undefined {
1013
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
1014
+ if (!greenDevEmulationEnabled) {
1015
+ return undefined;
1016
+ }
1017
+
1018
+ if (this.#greenDevEmulationScreenshot) {
1019
+ const data = this.#greenDevEmulationScreenshot;
1020
+ this.#greenDevEmulationScreenshot = null;
1021
+ return {
1022
+ type: MultimodalInputType.SCREENSHOT,
1023
+ input: {
1024
+ inlineData: {
1025
+ data,
1026
+ mimeType: 'image/jpeg',
1027
+ },
1028
+ },
1029
+ id: crypto.randomUUID(),
1030
+ };
1031
+ }
1032
+ return undefined;
1033
+ }
1034
+
764
1035
  override async *
765
1036
  handleContextDetails(selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null):
766
1037
  AsyncGenerator<ContextResponse, void, void> {
@@ -780,12 +1051,18 @@ const data = {
780
1051
  override async enhanceQuery(
781
1052
  query: string, selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null,
782
1053
  multimodalInputType?: MultimodalInputType): Promise<string> {
1054
+ let multimodalInputEnhancementQuery =
1055
+ this.multimodalInputEnabled && multimodalInputType ? MULTIMODAL_ENHANCEMENT_PROMPTS[multimodalInputType] : '';
1056
+
1057
+ if (this.#greenDevEmulationAxTree) {
1058
+ multimodalInputEnhancementQuery += '\n# Accessibility Tree\n\n' + this.#greenDevEmulationAxTree;
1059
+ this.#greenDevEmulationAxTree = null;
1060
+ }
1061
+
783
1062
  const elementEnchancementQuery = selectedElement ?
784
1063
  `# Inspected element\n\n${
785
1064
  await StylingAgent.describeElement(selectedElement.getItem())}\n\n# User request\n\n` :
786
1065
  '';
787
- const multimodalInputEnhancementQuery =
788
- this.multimodalInputEnabled && multimodalInputType ? MULTIMODAL_ENHANCEMENT_PROMPTS[multimodalInputType] : '';
789
1066
  return `${multimodalInputEnhancementQuery}${elementEnchancementQuery}QUERY: ${query}`;
790
1067
  }
791
1068
  }
@@ -11,6 +11,7 @@ export interface GreenDevSettings {
11
11
  aiAnnotations: Common.Settings.Setting<boolean>;
12
12
  copyToGemini: Common.Settings.Setting<boolean>;
13
13
  breakpointDebuggerAgent: Common.Settings.Setting<boolean>;
14
+ emulationCapabilities: Common.Settings.Setting<boolean>;
14
15
  }
15
16
 
16
17
  export class Prototypes {
@@ -48,7 +49,12 @@ export class Prototypes {
48
49
  false,
49
50
  Common.Settings.SettingStorageType.LOCAL,
50
51
  );
52
+ const emulationCapabilities = settings.createSetting(
53
+ 'greendev-emulation-capabilities-enabled',
54
+ false,
55
+ Common.Settings.SettingStorageType.LOCAL,
56
+ );
51
57
 
52
- return {aiAnnotations, copyToGemini, breakpointDebuggerAgent};
58
+ return {aiAnnotations, copyToGemini, breakpointDebuggerAgent, emulationCapabilities};
53
59
  }
54
60
  }
@@ -43,6 +43,7 @@ import {DisabledWidget} from './components/DisabledWidget.js';
43
43
  import {ExploreWidget} from './components/ExploreWidget.js';
44
44
  import {MarkdownRendererWithCodeBlock} from './components/MarkdownRendererWithCodeBlock.js';
45
45
  import {PerformanceAgentMarkdownRenderer} from './components/PerformanceAgentMarkdownRenderer.js';
46
+ import {StylingAgentMarkdownRenderer} from './components/StylingAgentMarkdownRenderer.js';
46
47
  import {
47
48
  WalkthroughView,
48
49
  } from './components/WalkthroughView.js';
@@ -233,6 +234,10 @@ const lockedString = i18n.i18n.lockedString;
233
234
 
234
235
  function selectedElementFilter(maybeNode: SDK.DOMModel.DOMNode|null): SDK.DOMModel.DOMNode|null {
235
236
  if (maybeNode) {
237
+ if (Greendev.Prototypes.instance().isEnabled('emulationCapabilities')) {
238
+ return maybeNode;
239
+ }
240
+
236
241
  return maybeNode.nodeType() === Node.ELEMENT_NODE ? maybeNode : null;
237
242
  }
238
243
 
@@ -260,7 +265,12 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
260
265
  return [
261
266
  {title: 'What can you help me with?', jslogContext: 'styling-default'},
262
267
  {title: 'Why isn’t this element visible?', jslogContext: 'styling-default'},
263
- {title: 'How do I center this element?', jslogContext: 'styling-default'},
268
+ {
269
+ title: Greendev.Prototypes.instance().isEnabled('emulationCapabilities') ?
270
+ 'Are there display issues on this page for people using an Android phone?' :
271
+ 'How do I center this element?',
272
+ jslogContext: 'styling-default'
273
+ },
264
274
  ];
265
275
  case AiAssistanceModel.AiHistoryStorage.ConversationType.FILE:
266
276
  return [
@@ -313,6 +323,14 @@ function getMarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiC
313
323
  } else if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE) {
314
324
  // Handle historical conversations (can't linkify anything).
315
325
  return new PerformanceAgentMarkdownRenderer();
326
+ } else if (
327
+ Greendev.Prototypes.instance().isEnabled('emulationCapabilities') &&
328
+ conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING &&
329
+ SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel)) {
330
+ const domModel = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel);
331
+ const resourceTreeModel = domModel?.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
332
+ const mainFrameId = resourceTreeModel?.mainFrame?.id;
333
+ return new StylingAgentMarkdownRenderer(mainFrameId);
316
334
  }
317
335
 
318
336
  return new MarkdownRendererWithCodeBlock();
@@ -602,12 +620,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
602
620
  #isLoading = false;
603
621
  // Stores the availability status of the `AidaClient` and the reason for unavailability, if any.
604
622
  #aidaAvailability: Host.AidaClient.AidaAccessPreconditions;
605
- // Info of the currently logged in user.
606
- #userInfo: {
607
- accountImage?: string,
608
- accountFullName?: string,
609
- accountGivenName?: string,
610
- };
611
623
  #timelinePanelInstance: TimelinePanel.TimelinePanel.TimelinePanel|null = null;
612
624
  #runAbortController = new AbortController();
613
625
  #walkthrough: WalkthroughState = {
@@ -616,10 +628,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
616
628
  activeMessage: null,
617
629
  };
618
630
 
619
- constructor(private view: View = defaultView, {aidaClient, aidaAvailability, syncInfo}: {
631
+ constructor(private view: View = defaultView, {aidaClient, aidaAvailability}: {
620
632
  aidaClient: Host.AidaClient.AidaClient,
621
633
  aidaAvailability: Host.AidaClient.AidaAccessPreconditions,
622
- syncInfo: Host.InspectorFrontendHostAPI.SyncInformation,
623
634
  }) {
624
635
  super(AiAssistancePanel.panelName);
625
636
  this.registerRequiredCSS(aiAssistancePanelStyles);
@@ -627,11 +638,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
627
638
 
628
639
  this.#aidaClient = aidaClient;
629
640
  this.#aidaAvailability = aidaAvailability;
630
- this.#userInfo = {
631
- accountImage: syncInfo.accountImage,
632
- accountFullName: syncInfo.accountFullName,
633
- accountGivenName: syncInfo.accountGivenName,
634
- };
635
641
 
636
642
  if (UI.ActionRegistry.ActionRegistry.instance().hasAction('elements.toggle-element-search')) {
637
643
  this.#toggleSearchElementAction =
@@ -706,7 +712,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
706
712
  isReadOnly: this.#conversation.isReadOnly ?? false,
707
713
  changeSummary: this.#getChangeSummary(),
708
714
  inspectElementToggled: this.#toggleSearchElementAction?.toggled() ?? false,
709
- userInfo: this.#userInfo,
710
715
  canShowFeedbackForm: this.#serverSideLoggingEnabled,
711
716
  multimodalInputEnabled: isAiAssistanceMultimodalInputEnabled() &&
712
717
  this.#conversation.type === AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING,
@@ -792,11 +797,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
792
797
  const {forceNew} = opts;
793
798
  if (!panelInstance || forceNew) {
794
799
  const aidaClient = new Host.AidaClient.AidaClient();
795
- const syncInfoPromise = new Promise<Host.InspectorFrontendHostAPI.SyncInformation>(
796
- resolve => Host.InspectorFrontendHost.InspectorFrontendHostInstance.getSyncInformation(resolve));
797
- const [aidaAvailability, syncInfo] =
798
- await Promise.all([Host.AidaClient.AidaClient.checkAccessPreconditions(), syncInfoPromise]);
799
- panelInstance = new AiAssistancePanel(defaultView, {aidaClient, aidaAvailability, syncInfo});
800
+ const aidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions();
801
+ panelInstance = new AiAssistancePanel(defaultView, {aidaClient, aidaAvailability});
800
802
  }
801
803
 
802
804
  return panelInstance;
@@ -1059,13 +1061,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1059
1061
  const currentAidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions();
1060
1062
  if (currentAidaAvailability !== this.#aidaAvailability) {
1061
1063
  this.#aidaAvailability = currentAidaAvailability;
1062
- const syncInfo = await new Promise<Host.InspectorFrontendHostAPI.SyncInformation>(
1063
- resolve => Host.InspectorFrontendHost.InspectorFrontendHostInstance.getSyncInformation(resolve));
1064
- this.#userInfo = {
1065
- accountImage: syncInfo.accountImage,
1066
- accountFullName: syncInfo.accountFullName,
1067
- accountGivenName: syncInfo.accountGivenName,
1068
- };
1069
1064
  this.requestUpdate();
1070
1065
  }
1071
1066
  };
@@ -1635,12 +1630,19 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1635
1630
  if (this.#conversation.isEmpty) {
1636
1631
  Badges.UserBadges.instance().recordAction(Badges.BadgeAction.STARTED_AI_CONVERSATION);
1637
1632
  }
1638
- const multimodalInput = isAiAssistanceMultimodalInputEnabled() && imageInput && multimodalInputType ? {
1639
- input: imageInput,
1640
- id: crypto.randomUUID(),
1641
- type: multimodalInputType,
1642
- } :
1643
- undefined;
1633
+
1634
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
1635
+ let multimodalInput: AiAssistanceModel.AiAgent.MultimodalInput|undefined;
1636
+ const pendingInput = this.#conversation.getPendingMultimodalInput();
1637
+ if (greenDevEmulationEnabled && pendingInput) {
1638
+ multimodalInput = pendingInput;
1639
+ } else if (isAiAssistanceMultimodalInputEnabled() && imageInput && multimodalInputType) {
1640
+ multimodalInput = {
1641
+ input: imageInput,
1642
+ id: crypto.randomUUID(),
1643
+ type: multimodalInputType,
1644
+ };
1645
+ }
1644
1646
 
1645
1647
  void VisualLogging.logFunctionCall(`start-conversation-${this.#conversation.type}`, 'ui');
1646
1648
 
@@ -125,6 +125,7 @@ const UIStringsNotTranslate = {
125
125
  const lockedString = i18n.i18n.lockedString;
126
126
 
127
127
  const CODE_SNIPPET_WARNING_URL = 'https://support.google.com/legal/answer/13505487';
128
+ const {widget} = UI.Widget;
128
129
 
129
130
  export enum PatchSuggestionState {
130
131
  /**
@@ -251,12 +252,11 @@ const DEFAULT_VIEW: View = (input, output, target) => {
251
252
  }
252
253
 
253
254
  if (input.patchSuggestionState === PatchSuggestionState.SUCCESS) {
254
- return html`<devtools-widget .widgetConfig=${
255
- UI.Widget.widgetConfig(ChangesPanel.CombinedDiffView.CombinedDiffView, {
256
- workspaceDiff: input.workspaceDiff,
257
- // Ignore user creates inspector-stylesheets
258
- ignoredUrls: ['inspector://']
259
- })}></devtools-widget>`;
255
+ return html`${widget(ChangesPanel.CombinedDiffView.CombinedDiffView, {
256
+ workspaceDiff: input.workspaceDiff,
257
+ // Ignore user creates inspector-stylesheets
258
+ ignoredUrls: ['inspector://']
259
+ })}`;
260
260
  }
261
261
 
262
262
  return html`<devtools-code-block
@@ -34,6 +34,7 @@ import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
34
34
 
35
35
  const {html, Directives: {ref, ifDefined}} = Lit;
36
36
  const lockedString = i18n.i18n.lockedString;
37
+ const {widget} = UI.Widget;
37
38
 
38
39
  const REPORT_URL = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
39
40
  const SCROLL_ROUNDING_OFFSET = 1;
@@ -120,10 +121,6 @@ const UIStringsNotTranslate = {
120
121
  * @description Gemini (do not translate)
121
122
  */
122
123
  gemini: 'Gemini',
123
- /**
124
- * @description The fallback text when we can't find the user full name
125
- */
126
- you: 'You',
127
124
  /**
128
125
  * @description The fallback text when a step has no title yet
129
126
  */
@@ -156,10 +153,6 @@ const UIStringsNotTranslate = {
156
153
  * @description Alt text for the image input (displayed in the chat messages) that has been sent to the model.
157
154
  */
158
155
  imageInputSentToTheModel: 'Image input sent to the model',
159
- /**
160
- * @description Alt text for the account avatar.
161
- */
162
- accountAvatar: 'Account avatar',
163
156
  /**
164
157
  * @description Title for the link which wraps the image input rendered in chat messages.
165
158
  */
@@ -272,7 +265,6 @@ export interface MessageInput {
272
265
  isReadOnly: boolean;
273
266
  isLastMessage: boolean;
274
267
  canShowFeedbackForm: boolean;
275
- userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'|'accountGivenName'>;
276
268
  markdownRenderer: MarkdownLitRenderer;
277
269
  onSuggestionClick: (suggestion: string) => void;
278
270
  onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void;
@@ -289,14 +281,6 @@ export interface MessageInput {
289
281
  export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, target: HTMLElement): void => {
290
282
  const message = input.message;
291
283
  if (message.entity === ChatMessageEntity.USER) {
292
- const givenName = AiAssistanceModel.AiUtils.isGeminiBranding() ? input.userInfo.accountGivenName : '';
293
- const name = givenName || input.userInfo.accountFullName || lockedString(UIStringsNotTranslate.you);
294
- const image = input.userInfo.accountImage ?
295
- html`<img src="data:image/png;base64, ${input.userInfo.accountImage}" alt=${
296
- UIStringsNotTranslate.accountAvatar} />` :
297
- html`<devtools-icon
298
- name="profile"
299
- ></devtools-icon>`;
300
284
  const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
301
285
  renderImageChatMessage(message.imageInput.inlineData) :
302
286
  Lit.nothing;
@@ -308,12 +292,6 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
308
292
  class="chat-message query ${input.isLastMessage ? 'is-last-message' : ''}"
309
293
  jslog=${VisualLogging.section('question')}
310
294
  >
311
- <div class="message-info">
312
- ${image}
313
- <div class="message-name">
314
- <h2>${name}</h2>
315
- </div>
316
- </div>
317
295
  ${imageInput}
318
296
  <div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
319
297
  </section>
@@ -569,7 +547,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
569
547
  // clang-format off
570
548
  const walkthroughInline = input.walkthrough.isInlined ? html`
571
549
  <div class="walkthrough-container">
572
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
550
+ ${widget(WalkthroughView, {
573
551
  message: input.message as ModelChatMessage,
574
552
  isLoading: input.isLoading && input.isLastMessage,
575
553
  markdownRenderer: input.markdownRenderer,
@@ -577,7 +555,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
577
555
  isExpanded,
578
556
  onToggle: input.walkthrough.onToggle,
579
557
  onOpen: input.walkthrough.onOpen,
580
- })}></devtools-widget>
558
+ })}
581
559
  </div>
582
560
  ` : Lit.nothing;
583
561
 
@@ -1019,8 +997,6 @@ export class ChatMessage extends UI.Widget.Widget {
1019
997
  isReadOnly = false;
1020
998
  canShowFeedbackForm = false;
1021
999
  isLastMessage = false;
1022
- userInfo:
1023
- Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'|'accountGivenName'> = {};
1024
1000
  markdownRenderer!: MarkdownLitRenderer;
1025
1001
  onSuggestionClick: (suggestion: string) => void = () => {};
1026
1002
  onFeedbackSubmit:
@@ -1067,7 +1043,6 @@ export class ChatMessage extends UI.Widget.Widget {
1067
1043
  isLoading: this.isLoading,
1068
1044
  isReadOnly: this.isReadOnly,
1069
1045
  canShowFeedbackForm: this.canShowFeedbackForm,
1070
- userInfo: this.userInfo,
1071
1046
  markdownRenderer: this.markdownRenderer,
1072
1047
  isLastMessage: this.isLastMessage,
1073
1048
  onSuggestionClick: this.onSuggestionClick,