chrome-devtools-frontend 1.0.1642845 → 1.0.1642899
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/SECURITY.md +1 -0
- package/front_end/core/host/UserMetrics.ts +2 -1
- package/front_end/core/sdk/CSSMatchedStyles.ts +55 -26
- package/front_end/core/sdk/CSSRule.ts +1 -0
- package/front_end/core/sdk/DebuggerModel.ts +5 -0
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +4 -3
- package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -3
- package/front_end/models/ai_assistance/AiAgent2.ts +80 -16
- package/front_end/models/ai_assistance/AiConversation.ts +3 -2
- package/front_end/models/ai_assistance/README.md +8 -0
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +50 -35
- package/front_end/models/ai_assistance/agents/AiAgent.ts +16 -0
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +195 -147
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -25
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -305
- package/front_end/models/ai_assistance/ai_assistance.ts +8 -0
- package/front_end/models/ai_assistance/contexts/DOMNodeContext.snapshot.txt +51 -0
- package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +200 -0
- package/front_end/models/ai_assistance/skills/styling.md +36 -2
- package/front_end/models/ai_assistance/tools/GetStyles.ts +137 -0
- package/front_end/models/ai_assistance/tools/Tool.ts +55 -0
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +34 -0
- package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -0
- package/front_end/models/live-metrics/LiveMetrics.ts +24 -13
- package/front_end/models/stack_trace/DetailedErrorStackParser.ts +2 -2
- package/front_end/models/stack_trace/StackTrace.ts +4 -1
- package/front_end/models/stack_trace/StackTraceImpl.ts +9 -2
- package/front_end/models/stack_trace/StackTraceModel.ts +17 -4
- package/front_end/models/stack_trace/Trie.ts +1 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +19 -15
- package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
- package/front_end/panels/application/DOMStorageItemsView.ts +4 -0
- package/front_end/panels/application/KeyValueStorageItemsView.ts +39 -7
- package/front_end/panels/common/ExtensionServer.ts +26 -15
- package/front_end/panels/elements/StandaloneStylesContainer.ts +1 -1
- package/front_end/panels/elements/StylePropertiesSection.ts +8 -0
- package/front_end/panels/elements/StylePropertyHighlighter.ts +4 -2
- package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -5
- package/front_end/panels/elements/StylesContainer.ts +1 -1
- package/front_end/panels/elements/StylesSidebarPane.ts +4 -4
- package/front_end/panels/layer_viewer/PaintProfilerView.ts +106 -132
- package/front_end/panels/lighthouse/LighthousePanel.ts +4 -3
- package/front_end/panels/network/NetworkLogView.ts +3 -0
- package/front_end/panels/network/networkLogView.css +0 -15
- package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +36 -3
- package/front_end/ui/legacy/components/data_grid/dataGridAiButton.css +20 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +19 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Host from '../../../core/host/host.js';
|
|
6
|
-
import * as i18n from '../../../core/i18n/i18n.js';
|
|
7
6
|
import * as Root from '../../../core/root/root.js';
|
|
8
7
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
9
8
|
import * as Protocol from '../../../generated/protocol.js';
|
|
@@ -11,16 +10,15 @@ import * as Greendev from '../../../models/greendev/greendev.js';
|
|
|
11
10
|
import * as Annotations from '../../annotations/annotations.js';
|
|
12
11
|
import * as Emulation from '../../emulation/emulation.js';
|
|
13
12
|
import {ChangeManager} from '../ChangeManager.js';
|
|
14
|
-
import {debugLog} from '../debug.js';
|
|
15
13
|
import {ExtensionScope} from '../ExtensionScope.js';
|
|
16
14
|
import {AI_ASSISTANCE_CSS_CLASS_NAME} from '../injected.js';
|
|
15
|
+
import {ToolName} from '../tools/Tool.js';
|
|
16
|
+
import {ToolRegistry} from '../tools/ToolRegistry.js';
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
AiAgent,
|
|
20
|
-
type ComputedStyleAiWidget,
|
|
21
20
|
type ContextResponse,
|
|
22
|
-
ConversationContext,
|
|
23
|
-
type ConversationSuggestions,
|
|
21
|
+
type ConversationContext,
|
|
24
22
|
type FunctionCallHandlerResult,
|
|
25
23
|
type MultimodalInput,
|
|
26
24
|
MultimodalInputType,
|
|
@@ -35,18 +33,6 @@ import {
|
|
|
35
33
|
JavascriptExecutor
|
|
36
34
|
} from './ExecuteJavascript.js';
|
|
37
35
|
|
|
38
|
-
/*
|
|
39
|
-
* Strings that don't need to be translated at this time.
|
|
40
|
-
*/
|
|
41
|
-
const UIStringsNotTranslate = {
|
|
42
|
-
/**
|
|
43
|
-
* @description Heading text for context details of Freestyler agent.
|
|
44
|
-
*/
|
|
45
|
-
dataUsed: 'Data used',
|
|
46
|
-
} as const;
|
|
47
|
-
|
|
48
|
-
const lockedString = i18n.i18n.lockedString;
|
|
49
|
-
|
|
50
36
|
const preamble = `You are the most advanced CSS/DOM/HTML debugging assistant integrated into Chrome DevTools.
|
|
51
37
|
You always suggest considering the best web development practices and the newest platform features such as view transitions.
|
|
52
38
|
The user selected a DOM element in the browser's DevTools and sends a query about the page or the selected DOM element.
|
|
@@ -152,78 +138,6 @@ const MULTIMODAL_ENHANCEMENT_PROMPTS: Record<MultimodalInputType, string> = {
|
|
|
152
138
|
|
|
153
139
|
export const AI_ASSISTANCE_FILTER_REGEX = `\\.${AI_ASSISTANCE_CSS_CLASS_NAME}-.*&`;
|
|
154
140
|
|
|
155
|
-
export class NodeContext extends ConversationContext<SDK.DOMModel.DOMNode> {
|
|
156
|
-
#node: SDK.DOMModel.DOMNode;
|
|
157
|
-
|
|
158
|
-
constructor(node: SDK.DOMModel.DOMNode) {
|
|
159
|
-
super();
|
|
160
|
-
this.#node = node;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
override getURL(): string {
|
|
164
|
-
const ownerDocument = this.#node.ownerDocument;
|
|
165
|
-
if (!ownerDocument) {
|
|
166
|
-
// The node is detached from a document.
|
|
167
|
-
return 'detached';
|
|
168
|
-
}
|
|
169
|
-
return ownerDocument.documentURL;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
getItem(): SDK.DOMModel.DOMNode {
|
|
173
|
-
return this.#node;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
override getTitle(): string {
|
|
177
|
-
throw new Error('Not implemented');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
override async getSuggestions(): Promise<ConversationSuggestions|undefined> {
|
|
181
|
-
const layoutProps = await this.#node.domModel().cssModel().getLayoutPropertiesFromComputedStyle(this.#node.id);
|
|
182
|
-
|
|
183
|
-
if (!layoutProps) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (layoutProps.isFlex) {
|
|
188
|
-
return [
|
|
189
|
-
{title: 'How can I make flex items wrap?', jslogContext: 'flex-wrap'},
|
|
190
|
-
{title: 'How do I distribute flex items evenly?', jslogContext: 'flex-distribute'},
|
|
191
|
-
{title: 'What is flexbox?', jslogContext: 'flex-what'},
|
|
192
|
-
];
|
|
193
|
-
}
|
|
194
|
-
if (layoutProps.isSubgrid) {
|
|
195
|
-
return [
|
|
196
|
-
{title: 'Where is this grid defined?', jslogContext: 'subgrid-where'},
|
|
197
|
-
{title: 'How to overwrite parent grid properties?', jslogContext: 'subgrid-override'},
|
|
198
|
-
{title: 'How do subgrids work? ', jslogContext: 'subgrid-how'},
|
|
199
|
-
];
|
|
200
|
-
}
|
|
201
|
-
if (layoutProps.isGrid) {
|
|
202
|
-
return [
|
|
203
|
-
{title: 'How do I align items in a grid?', jslogContext: 'grid-align'},
|
|
204
|
-
{title: 'How to add spacing between grid items?', jslogContext: 'grid-gap'},
|
|
205
|
-
{title: 'How does grid layout work?', jslogContext: 'grid-how'},
|
|
206
|
-
];
|
|
207
|
-
}
|
|
208
|
-
if (layoutProps.hasScroll) {
|
|
209
|
-
return [
|
|
210
|
-
{title: 'How do I remove scrollbars for this element?', jslogContext: 'scroll-remove'},
|
|
211
|
-
{title: 'How can I style a scrollbar?', jslogContext: 'scroll-style'},
|
|
212
|
-
{title: 'Why does this element scroll?', jslogContext: 'scroll-why'},
|
|
213
|
-
];
|
|
214
|
-
}
|
|
215
|
-
if (layoutProps.containerType) {
|
|
216
|
-
return [
|
|
217
|
-
{title: 'What are container queries?', jslogContext: 'container-what'},
|
|
218
|
-
{title: 'How do I use container-type?', jslogContext: 'container-how'},
|
|
219
|
-
{title: 'What\'s the container context for this element?', jslogContext: 'container-context'},
|
|
220
|
-
];
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
141
|
/**
|
|
228
142
|
* One agent instance handles one conversation. Create a new agent
|
|
229
143
|
* instance for a new conversation.
|
|
@@ -282,57 +196,17 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
282
196
|
},
|
|
283
197
|
this.#execJs);
|
|
284
198
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
description:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
**CRITICAL** You MUST provide a specific list of CSS property names. Do not use generic values like "all" or "*".`,
|
|
297
|
-
parameters: {
|
|
298
|
-
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
299
|
-
description: '',
|
|
300
|
-
nullable: false,
|
|
301
|
-
properties: {
|
|
302
|
-
explanation: {
|
|
303
|
-
type: Host.AidaClient.ParametersTypes.STRING,
|
|
304
|
-
description: 'Explain why you want to get styles',
|
|
305
|
-
nullable: false,
|
|
306
|
-
},
|
|
307
|
-
elements: {
|
|
308
|
-
type: Host.AidaClient.ParametersTypes.ARRAY,
|
|
309
|
-
description: 'A list of element uids to get data for. These are numbers, not selectors.',
|
|
310
|
-
items: {type: Host.AidaClient.ParametersTypes.INTEGER, description: `An element uid.`},
|
|
311
|
-
nullable: false,
|
|
312
|
-
},
|
|
313
|
-
styleProperties: {
|
|
314
|
-
type: Host.AidaClient.ParametersTypes.ARRAY,
|
|
315
|
-
description:
|
|
316
|
-
'One or more specific CSS style property names to fetch. Generic values like "all" or "*" are not supported.',
|
|
317
|
-
nullable: false,
|
|
318
|
-
items: {
|
|
319
|
-
type: Host.AidaClient.ParametersTypes.STRING,
|
|
320
|
-
description: 'A CSS style property name to retrieve. For example, \'background-color\'.'
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
required: ['explanation', 'elements', 'styleProperties']
|
|
325
|
-
},
|
|
326
|
-
displayInfoFromArgs: params => {
|
|
327
|
-
return {
|
|
328
|
-
title: 'Reading computed and source styles',
|
|
329
|
-
thought: params.explanation,
|
|
330
|
-
action: `getStyles(${JSON.stringify(params.elements)}, ${JSON.stringify(params.styleProperties)})`,
|
|
331
|
-
};
|
|
332
|
-
},
|
|
333
|
-
handler: async params => {
|
|
334
|
-
return await this.#getStyles(params.elements, params.styleProperties);
|
|
335
|
-
},
|
|
199
|
+
const getStylesTool = ToolRegistry.get(ToolName.GET_STYLES);
|
|
200
|
+
if (!getStylesTool) {
|
|
201
|
+
throw new Error('Required tool "getStyles" not found');
|
|
202
|
+
}
|
|
203
|
+
this.declareFunction(ToolName.GET_STYLES, {
|
|
204
|
+
description: getStylesTool.description,
|
|
205
|
+
parameters: getStylesTool.parameters,
|
|
206
|
+
displayInfoFromArgs: getStylesTool.displayInfoFromArgs,
|
|
207
|
+
handler: args => getStylesTool.handler(args, {
|
|
208
|
+
conversationContext: this.context ?? null,
|
|
209
|
+
}),
|
|
336
210
|
});
|
|
337
211
|
|
|
338
212
|
this.declareFunction('executeJavaScript', executeJavaScriptFunction(this.#javascriptExecutor));
|
|
@@ -401,162 +275,10 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
401
275
|
});
|
|
402
276
|
}
|
|
403
277
|
|
|
404
|
-
static async describeElement(element: SDK.DOMModel.DOMNode): Promise<string> {
|
|
405
|
-
let output = `* Element's uid is ${element.backendNodeId()}.
|
|
406
|
-
* Its selector is \`${element.simpleSelector()}\``;
|
|
407
|
-
const childNodes = await element.getChildNodesPromise();
|
|
408
|
-
if (childNodes) {
|
|
409
|
-
const textChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.TEXT_NODE);
|
|
410
|
-
const elementChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.ELEMENT_NODE);
|
|
411
|
-
switch (elementChildNodes.length) {
|
|
412
|
-
case 0:
|
|
413
|
-
output += '\n* It doesn\'t have any child element nodes';
|
|
414
|
-
break;
|
|
415
|
-
case 1:
|
|
416
|
-
output += `\n* It only has 1 child element node: \`${elementChildNodes[0].simpleSelector()}\``;
|
|
417
|
-
break;
|
|
418
|
-
default:
|
|
419
|
-
output += `\n* It has ${elementChildNodes.length} child element nodes: ${
|
|
420
|
-
elementChildNodes.map(node => `\`${node.simpleSelector()}\` (uid=${node.backendNodeId()})`).join(', ')}`;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
switch (textChildNodes.length) {
|
|
424
|
-
case 0:
|
|
425
|
-
output += '\n* It doesn\'t have any child text nodes';
|
|
426
|
-
break;
|
|
427
|
-
case 1:
|
|
428
|
-
output += '\n* It only has 1 child text node';
|
|
429
|
-
break;
|
|
430
|
-
default:
|
|
431
|
-
output += `\n* It has ${textChildNodes.length} child text nodes`;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (element.nextSibling) {
|
|
436
|
-
const elementOrNodeElementNodeText = element.nextSibling.nodeType() === Node.ELEMENT_NODE ?
|
|
437
|
-
`an element (uid=${element.nextSibling.backendNodeId()})` :
|
|
438
|
-
'a non element';
|
|
439
|
-
output += `\n* It has a next sibling and it is ${elementOrNodeElementNodeText} node`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (element.previousSibling) {
|
|
443
|
-
const elementOrNodeElementNodeText = element.previousSibling.nodeType() === Node.ELEMENT_NODE ?
|
|
444
|
-
`an element (uid=${element.previousSibling.backendNodeId()})` :
|
|
445
|
-
'a non element';
|
|
446
|
-
output += `\n* It has a previous sibling and it is ${elementOrNodeElementNodeText} node`;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (element.isInShadowTree()) {
|
|
450
|
-
output += '\n* It is in a shadow DOM tree.';
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const parentNode = element.parentNode;
|
|
454
|
-
if (parentNode) {
|
|
455
|
-
const parentChildrenNodes = await parentNode.getChildNodesPromise();
|
|
456
|
-
output += `\n* Its parent's selector is \`${parentNode.simpleSelector()}\` (uid=${parentNode.backendNodeId()})`;
|
|
457
|
-
const elementOrNodeElementNodeText = parentNode.nodeType() === Node.ELEMENT_NODE ? 'an element' : 'a non element';
|
|
458
|
-
output += `\n* Its parent is ${elementOrNodeElementNodeText} node`;
|
|
459
|
-
if (parentNode.isShadowRoot()) {
|
|
460
|
-
output += '\n* Its parent is a shadow root.';
|
|
461
|
-
}
|
|
462
|
-
if (parentChildrenNodes) {
|
|
463
|
-
const childElementNodes =
|
|
464
|
-
parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.ELEMENT_NODE);
|
|
465
|
-
switch (childElementNodes.length) {
|
|
466
|
-
case 0:
|
|
467
|
-
break;
|
|
468
|
-
case 1:
|
|
469
|
-
output += '\n* Its parent has only 1 child element node';
|
|
470
|
-
break;
|
|
471
|
-
default:
|
|
472
|
-
output += `\n* Its parent has ${childElementNodes.length} child element nodes: ${
|
|
473
|
-
childElementNodes.map(node => `\`${node.simpleSelector()}\` (uid=${node.backendNodeId()})`)
|
|
474
|
-
.join(', ')}`;
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const siblingTextNodes = parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.TEXT_NODE);
|
|
479
|
-
switch (siblingTextNodes.length) {
|
|
480
|
-
case 0:
|
|
481
|
-
break;
|
|
482
|
-
case 1:
|
|
483
|
-
output += '\n* Its parent has only 1 child text node';
|
|
484
|
-
break;
|
|
485
|
-
default:
|
|
486
|
-
output += `\n* Its parent has ${siblingTextNodes.length} child text nodes: ${
|
|
487
|
-
siblingTextNodes.map(node => `\`${node.simpleSelector()}\``).join(', ')}`;
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return output.trim();
|
|
494
|
-
}
|
|
495
|
-
|
|
496
278
|
#getSelectedNode(): SDK.DOMModel.DOMNode|null {
|
|
497
279
|
return this.context?.getItem() ?? null;
|
|
498
280
|
}
|
|
499
281
|
|
|
500
|
-
async #getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
|
|
501
|
-
const widgets: ComputedStyleAiWidget[] = [];
|
|
502
|
-
|
|
503
|
-
const result:
|
|
504
|
-
Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
|
|
505
|
-
for (const uid of elements) {
|
|
506
|
-
result[uid] = {computed: {}, authored: {}};
|
|
507
|
-
debugLog(`Action to execute: uid=${uid}`);
|
|
508
|
-
const selectedNode = this.#getSelectedNode();
|
|
509
|
-
if (!selectedNode) {
|
|
510
|
-
return {error: 'Error: Could not find the currently selected element.'};
|
|
511
|
-
}
|
|
512
|
-
const node = new SDK.DOMModel.DeferredDOMNode(
|
|
513
|
-
selectedNode.domModel().target(), Number(uid) as unknown as Protocol.DOM.BackendNodeId);
|
|
514
|
-
const resolved = await node.resolvePromise();
|
|
515
|
-
if (!resolved) {
|
|
516
|
-
return {error: 'Error: Could not find the element with uid=' + uid};
|
|
517
|
-
}
|
|
518
|
-
const newContext = new NodeContext(resolved);
|
|
519
|
-
if (this.context?.getOrigin() !== newContext.getOrigin()) {
|
|
520
|
-
return {error: 'Error: Node does not belong to the current origin.'};
|
|
521
|
-
}
|
|
522
|
-
const styles = await resolved.domModel().cssModel().getComputedStyle(resolved.id);
|
|
523
|
-
if (!styles) {
|
|
524
|
-
return {error: 'Error: Could not get computed styles.'};
|
|
525
|
-
}
|
|
526
|
-
const matchedStyles = await resolved.domModel().cssModel().getMatchedStyles(resolved.id);
|
|
527
|
-
if (!matchedStyles) {
|
|
528
|
-
return {error: 'Error: Could not get authored styles.'};
|
|
529
|
-
}
|
|
530
|
-
widgets.push({
|
|
531
|
-
name: 'COMPUTED_STYLES',
|
|
532
|
-
data: {
|
|
533
|
-
computedStyles: styles,
|
|
534
|
-
backendNodeId: node.backendNodeId(),
|
|
535
|
-
matchedCascade: matchedStyles,
|
|
536
|
-
properties,
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
for (const prop of properties) {
|
|
540
|
-
result[uid].computed[prop] = styles.get(prop);
|
|
541
|
-
}
|
|
542
|
-
for (const style of matchedStyles.nodeStyles()) {
|
|
543
|
-
for (const property of style.allProperties()) {
|
|
544
|
-
if (!properties.includes(property.name)) {
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
const state = matchedStyles.propertyState(property);
|
|
548
|
-
if (state === SDK.CSSMatchedStyles.PropertyState.ACTIVE) {
|
|
549
|
-
result[uid].authored[property.name] = property.value;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
return {
|
|
555
|
-
result: JSON.stringify(result, null, 2),
|
|
556
|
-
widgets,
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
|
|
560
282
|
async addElementAnnotation(elementId: string, annotationMessage: string):
|
|
561
283
|
Promise<FunctionCallHandlerResult<unknown>> {
|
|
562
284
|
if (!Annotations.AnnotationRepository.annotationsEnabled()) {
|
|
@@ -782,16 +504,15 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
782
504
|
override async *
|
|
783
505
|
handleContextDetails(selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null):
|
|
784
506
|
AsyncGenerator<ContextResponse, void, void> {
|
|
785
|
-
if (
|
|
786
|
-
|
|
507
|
+
if (selectedElement) {
|
|
508
|
+
const details = await selectedElement.getUserFacingDetails();
|
|
509
|
+
if (details) {
|
|
510
|
+
yield {
|
|
511
|
+
type: ResponseType.CONTEXT,
|
|
512
|
+
details,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
787
515
|
}
|
|
788
|
-
yield {
|
|
789
|
-
type: ResponseType.CONTEXT,
|
|
790
|
-
details: [{
|
|
791
|
-
title: lockedString(UIStringsNotTranslate.dataUsed),
|
|
792
|
-
text: await StylingAgent.describeElement(selectedElement.getItem()),
|
|
793
|
-
}],
|
|
794
|
-
};
|
|
795
516
|
}
|
|
796
517
|
|
|
797
518
|
protected override async preRun(): Promise<void> {
|
|
@@ -814,10 +535,8 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
814
535
|
this.#hasAddedEmulationInstructions = true;
|
|
815
536
|
}
|
|
816
537
|
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
await StylingAgent.describeElement(selectedElement.getItem())}\n\n# User request\n\n` :
|
|
820
|
-
'';
|
|
538
|
+
const promptDetails = selectedElement ? await selectedElement.getPromptDetails() : null;
|
|
539
|
+
const elementEnchancementQuery = promptDetails ? `${promptDetails}\n\n# User request\n\n` : '';
|
|
821
540
|
return `${multimodalInputEnhancementQuery}${elementEnchancementQuery}QUERY: ${query}`;
|
|
822
541
|
}
|
|
823
542
|
}
|
|
@@ -24,6 +24,7 @@ import * as AiOrigins from './AiOrigins.js';
|
|
|
24
24
|
import * as AiUtils from './AiUtils.js';
|
|
25
25
|
import * as BuiltInAi from './BuiltInAi.js';
|
|
26
26
|
import * as ChangeManager from './ChangeManager.js';
|
|
27
|
+
import * as DOMNodeContext from './contexts/DOMNodeContext.js';
|
|
27
28
|
import * as FileFormatter from './data_formatters/FileFormatter.js';
|
|
28
29
|
import * as LighthouseFormatter from './data_formatters/LighthouseFormatter.js';
|
|
29
30
|
import * as NetworkRequestFormatter from './data_formatters/NetworkRequestFormatter.js';
|
|
@@ -38,6 +39,9 @@ import * as AICallTree from './performance/AICallTree.js';
|
|
|
38
39
|
import * as AIContext from './performance/AIContext.js';
|
|
39
40
|
import * as AIQueries from './performance/AIQueries.js';
|
|
40
41
|
import * as StorageItem from './StorageItem.js';
|
|
42
|
+
import * as GetStyles from './tools/GetStyles.js';
|
|
43
|
+
import * as Tool from './tools/Tool.js';
|
|
44
|
+
import * as ToolRegistry from './tools/ToolRegistry.js';
|
|
41
45
|
|
|
42
46
|
export {
|
|
43
47
|
AccessibilityAgent,
|
|
@@ -56,10 +60,12 @@ export {
|
|
|
56
60
|
ContextSelectionAgent,
|
|
57
61
|
ConversationSummaryAgent,
|
|
58
62
|
Debug,
|
|
63
|
+
DOMNodeContext,
|
|
59
64
|
EvaluateAction,
|
|
60
65
|
ExtensionScope,
|
|
61
66
|
FileAgent,
|
|
62
67
|
FileFormatter,
|
|
68
|
+
GetStyles,
|
|
63
69
|
GreenDevAgent,
|
|
64
70
|
GreenDevAgentAntigravityCliSocketClient,
|
|
65
71
|
GreenDevAgentGeminiCliSocketClient,
|
|
@@ -75,5 +81,7 @@ export {
|
|
|
75
81
|
StorageAgent,
|
|
76
82
|
StorageItem,
|
|
77
83
|
StylingAgent,
|
|
84
|
+
Tool,
|
|
85
|
+
ToolRegistry,
|
|
78
86
|
UnitFormatters,
|
|
79
87
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Title: DOMNodeContext getPromptDetails describes the node correctly
|
|
2
|
+
Content:
|
|
3
|
+
# Inspected element
|
|
4
|
+
|
|
5
|
+
* Element's uid is 99.
|
|
6
|
+
* Its selector is `div#myElement`
|
|
7
|
+
=== end content
|
|
8
|
+
|
|
9
|
+
Title: DOMNodeContext getUserFacingDetails returns details with Data Used title
|
|
10
|
+
Content:
|
|
11
|
+
[
|
|
12
|
+
{
|
|
13
|
+
"title": "Data used",
|
|
14
|
+
"text": "* Element's uid is 99.\n* Its selector is `div#myElement`"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
=== end content
|
|
18
|
+
|
|
19
|
+
Title: DOMNodeContext describes an element with child nodes not loaded
|
|
20
|
+
Content:
|
|
21
|
+
* Element's uid is 99.
|
|
22
|
+
* Its selector is `div#myElement`
|
|
23
|
+
=== end content
|
|
24
|
+
|
|
25
|
+
Title: DOMNodeContext describes an element with no children, siblings, or parent
|
|
26
|
+
Content:
|
|
27
|
+
* Element's uid is 99.
|
|
28
|
+
* Its selector is `div#myElement`
|
|
29
|
+
* It doesn't have any child element nodes
|
|
30
|
+
* It doesn't have any child text nodes
|
|
31
|
+
=== end content
|
|
32
|
+
|
|
33
|
+
Title: DOMNodeContext describes an element with child element and text nodes
|
|
34
|
+
Content:
|
|
35
|
+
* Element's uid is 99.
|
|
36
|
+
* Its selector is `div#parentElement`
|
|
37
|
+
* It has 2 child element nodes: `span.child1` (uid=undefined), `span.child2` (uid=undefined)
|
|
38
|
+
* It only has 1 child text node
|
|
39
|
+
=== end content
|
|
40
|
+
|
|
41
|
+
Title: DOMNodeContext describes an element with siblings and a parent
|
|
42
|
+
Content:
|
|
43
|
+
* Element's uid is 99.
|
|
44
|
+
* Its selector is `div#parentElement`
|
|
45
|
+
* It has a next sibling and it is an element (uid=undefined) node
|
|
46
|
+
* It has a previous sibling and it is a non element node
|
|
47
|
+
* Its parent's selector is `div#grandparentElement` (uid=undefined)
|
|
48
|
+
* Its parent is a non element node
|
|
49
|
+
* Its parent has only 1 child element node
|
|
50
|
+
* Its parent has only 1 child text node
|
|
51
|
+
=== end content
|
|
@@ -0,0 +1,200 @@
|
|
|
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 type * as SDK from '../../../core/sdk/sdk.js';
|
|
7
|
+
import {
|
|
8
|
+
type ContextDetail,
|
|
9
|
+
ConversationContext,
|
|
10
|
+
type ConversationSuggestions,
|
|
11
|
+
} from '../agents/AiAgent.js';
|
|
12
|
+
|
|
13
|
+
const UIStringsNotTranslate = {
|
|
14
|
+
/**
|
|
15
|
+
* @description Heading text for context details of DevTools AI Agent.
|
|
16
|
+
*/
|
|
17
|
+
dataUsed: 'Data used',
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const lockedString = i18n.i18n.lockedString;
|
|
21
|
+
|
|
22
|
+
export class DOMNodeContext extends ConversationContext<SDK.DOMModel.DOMNode> {
|
|
23
|
+
#node: SDK.DOMModel.DOMNode;
|
|
24
|
+
|
|
25
|
+
constructor(node: SDK.DOMModel.DOMNode) {
|
|
26
|
+
super();
|
|
27
|
+
this.#node = node;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override getURL(): string {
|
|
31
|
+
const ownerDocument = this.#node.ownerDocument;
|
|
32
|
+
if (!ownerDocument) {
|
|
33
|
+
// The node is detached from a document.
|
|
34
|
+
return 'detached';
|
|
35
|
+
}
|
|
36
|
+
return ownerDocument.documentURL;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getItem(): SDK.DOMModel.DOMNode {
|
|
40
|
+
return this.#node;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override getTitle(): string {
|
|
44
|
+
throw new Error('Not implemented');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override async getSuggestions(): Promise<ConversationSuggestions|undefined> {
|
|
48
|
+
const layoutProps = await this.#node.domModel().cssModel().getLayoutPropertiesFromComputedStyle(this.#node.id);
|
|
49
|
+
|
|
50
|
+
if (!layoutProps) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (layoutProps.isFlex) {
|
|
55
|
+
return [
|
|
56
|
+
{title: 'How can I make flex items wrap?', jslogContext: 'flex-wrap'},
|
|
57
|
+
{title: 'How do I distribute flex items evenly?', jslogContext: 'flex-distribute'},
|
|
58
|
+
{title: 'What is flexbox?', jslogContext: 'flex-what'},
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
if (layoutProps.isSubgrid) {
|
|
62
|
+
return [
|
|
63
|
+
{title: 'Where is this grid defined?', jslogContext: 'subgrid-where'},
|
|
64
|
+
{title: 'How to overwrite parent grid properties?', jslogContext: 'subgrid-override'},
|
|
65
|
+
{title: 'How do subgrids work? ', jslogContext: 'subgrid-how'},
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
if (layoutProps.isGrid) {
|
|
69
|
+
return [
|
|
70
|
+
{title: 'How do I align items in a grid?', jslogContext: 'grid-align'},
|
|
71
|
+
{title: 'How to add spacing between grid items?', jslogContext: 'grid-gap'},
|
|
72
|
+
{title: 'How does grid layout work?', jslogContext: 'grid-how'},
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
if (layoutProps.hasScroll) {
|
|
76
|
+
return [
|
|
77
|
+
{title: 'How do I remove scrollbars for this element?', jslogContext: 'scroll-remove'},
|
|
78
|
+
{title: 'How can I style a scrollbar?', jslogContext: 'scroll-style'},
|
|
79
|
+
{title: 'Why does this element scroll?', jslogContext: 'scroll-why'},
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
if (layoutProps.containerType) {
|
|
83
|
+
return [
|
|
84
|
+
{title: 'What are container queries?', jslogContext: 'container-what'},
|
|
85
|
+
{title: 'How do I use container-type?', jslogContext: 'container-how'},
|
|
86
|
+
{title: 'What\'s the container context for this element?', jslogContext: 'container-context'},
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override async getPromptDetails(): Promise<string|null> {
|
|
94
|
+
return `# Inspected element
|
|
95
|
+
|
|
96
|
+
${await this.describe()}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override async getUserFacingDetails(): Promise<[ContextDetail, ...ContextDetail[]]|null> {
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
title: lockedString(UIStringsNotTranslate.dataUsed),
|
|
103
|
+
text: await this.describe(),
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async describe(): Promise<string> {
|
|
109
|
+
const element = this.#node;
|
|
110
|
+
let output = `* Element's uid is ${element.backendNodeId()}.
|
|
111
|
+
* Its selector is \`${element.simpleSelector()}\``;
|
|
112
|
+
const childNodes = await element.getChildNodesPromise();
|
|
113
|
+
if (childNodes) {
|
|
114
|
+
const textChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.TEXT_NODE);
|
|
115
|
+
const elementChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.ELEMENT_NODE);
|
|
116
|
+
switch (elementChildNodes.length) {
|
|
117
|
+
case 0:
|
|
118
|
+
output += '\n* It doesn\'t have any child element nodes';
|
|
119
|
+
break;
|
|
120
|
+
case 1:
|
|
121
|
+
output += `\n* It only has 1 child element node: \`${elementChildNodes[0].simpleSelector()}\``;
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
output += `\n* It has ${elementChildNodes.length} child element nodes: ${
|
|
125
|
+
elementChildNodes.map(node => `\`${node.simpleSelector()}\` (uid=${node.backendNodeId()})`).join(', ')}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
switch (textChildNodes.length) {
|
|
129
|
+
case 0:
|
|
130
|
+
output += '\n* It doesn\'t have any child text nodes';
|
|
131
|
+
break;
|
|
132
|
+
case 1:
|
|
133
|
+
output += '\n* It only has 1 child text node';
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
output += `\n* It has ${textChildNodes.length} child text nodes`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (element.nextSibling) {
|
|
141
|
+
const elementOrNodeElementNodeText = element.nextSibling.nodeType() === Node.ELEMENT_NODE ?
|
|
142
|
+
`an element (uid=${element.nextSibling.backendNodeId()})` :
|
|
143
|
+
'a non element';
|
|
144
|
+
output += `\n* It has a next sibling and it is ${elementOrNodeElementNodeText} node`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (element.previousSibling) {
|
|
148
|
+
const elementOrNodeElementNodeText = element.previousSibling.nodeType() === Node.ELEMENT_NODE ?
|
|
149
|
+
`an element (uid=${element.previousSibling.backendNodeId()})` :
|
|
150
|
+
'a non element';
|
|
151
|
+
output += `\n* It has a previous sibling and it is ${elementOrNodeElementNodeText} node`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (element.isInShadowTree()) {
|
|
155
|
+
output += '\n* It is in a shadow DOM tree.';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const parentNode = element.parentNode;
|
|
159
|
+
if (parentNode) {
|
|
160
|
+
const parentChildrenNodes = await parentNode.getChildNodesPromise();
|
|
161
|
+
output += `\n* Its parent's selector is \`${parentNode.simpleSelector()}\` (uid=${parentNode.backendNodeId()})`;
|
|
162
|
+
const elementOrNodeElementNodeText = parentNode.nodeType() === Node.ELEMENT_NODE ? 'an element' : 'a non element';
|
|
163
|
+
output += `\n* Its parent is ${elementOrNodeElementNodeText} node`;
|
|
164
|
+
if (parentNode.isShadowRoot()) {
|
|
165
|
+
output += '\n* Its parent is a shadow root.';
|
|
166
|
+
}
|
|
167
|
+
if (parentChildrenNodes) {
|
|
168
|
+
const childElementNodes =
|
|
169
|
+
parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.ELEMENT_NODE);
|
|
170
|
+
switch (childElementNodes.length) {
|
|
171
|
+
case 0:
|
|
172
|
+
break;
|
|
173
|
+
case 1:
|
|
174
|
+
output += '\n* Its parent has only 1 child element node';
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
output += `\n* Its parent has ${childElementNodes.length} child element nodes: ${
|
|
178
|
+
childElementNodes.map(node => `\`${node.simpleSelector()}\` (uid=${node.backendNodeId()})`)
|
|
179
|
+
.join(', ')}`;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const siblingTextNodes = parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.TEXT_NODE);
|
|
184
|
+
switch (siblingTextNodes.length) {
|
|
185
|
+
case 0:
|
|
186
|
+
break;
|
|
187
|
+
case 1:
|
|
188
|
+
output += '\n* Its parent has only 1 child text node';
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
output += `\n* Its parent has ${siblingTextNodes.length} child text nodes: ${
|
|
192
|
+
siblingTextNodes.map(node => `\`${node.simpleSelector()}\``).join(', ')}`;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return output.trim();
|
|
199
|
+
}
|
|
200
|
+
}
|