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.
- package/agents/prompts/ui-widgets.md +7 -8
- package/docs/ui_engineering.md +10 -11
- package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
- package/front_end/core/host/UserMetrics.ts +12 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/core/sdk/CPUThrottlingManager.ts +9 -12
- package/front_end/core/sdk/PageResourceLoader.ts +22 -1
- package/front_end/devtools_compatibility.js +2 -1
- package/front_end/models/ai_assistance/AiConversation.ts +5 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +4 -0
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +24 -0
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +289 -12
- package/front_end/models/greendev/Prototypes.ts +7 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +35 -33
- package/front_end/panels/ai_assistance/PatchWidget.ts +6 -6
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -28
- package/front_end/panels/ai_assistance/components/ChatView.ts +6 -11
- package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +200 -0
- package/front_end/panels/ai_assistance/components/chatMessage.css +0 -8
- package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
- package/front_end/panels/common/ExtensionServer.ts +15 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +55 -47
- package/front_end/panels/elements/ElementsTreeOutline.ts +149 -28
- package/front_end/panels/lighthouse/LighthousePanel.ts +8 -0
- package/front_end/panels/settings/SettingsScreen.ts +3 -2
- package/front_end/ui/legacy/UIUtils.ts +5 -5
- package/front_end/ui/legacy/Widget.ts +33 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- 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
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 =
|
|
285
|
+
preamble = getPreamble();
|
|
241
286
|
readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_STYLING_AGENT;
|
|
242
287
|
get userTier(): string|undefined {
|
|
243
|
-
|
|
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
|
-
{
|
|
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
|
|
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
|
|
796
|
-
|
|
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
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
})}
|
|
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,
|