chrome-devtools-frontend 1.0.1592129 → 1.0.1593518
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/front_end/Images/src/tab-move.svg +1 -0
- package/front_end/application_tokens.css +4 -4
- package/front_end/core/host/UserMetrics.ts +2 -1
- package/front_end/core/root/ExperimentNames.ts +1 -0
- package/front_end/entrypoints/main/MainImpl.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -1
- package/front_end/generated/SupportedCSSProperties.js +2 -2
- package/front_end/generated/protocol-mapping.d.ts +13 -0
- package/front_end/generated/protocol-proxy-api.d.ts +12 -0
- package/front_end/generated/protocol.ts +75 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +24 -1
- package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +320 -26
- package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgentOverlay.ts +87 -0
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +8 -8
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +79 -48
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +16 -2
- package/front_end/models/computed_style/ComputedStyleModel.ts +40 -6
- package/front_end/models/javascript_metadata/NativeFunctions.js +6 -6
- package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +5 -3
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +39 -7
- package/front_end/panels/ai_assistance/README.md +61 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +141 -27
- package/front_end/panels/ai_assistance/components/ChatView.ts +5 -1
- package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +55 -1
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +19 -7
- package/front_end/panels/ai_assistance/components/chatMessage.css +30 -0
- package/front_end/panels/ai_assistance/components/walkthroughView.css +12 -7
- package/front_end/panels/animation/AnimationGroupPreviewUI.ts +1 -1
- package/front_end/panels/application/CookieItemsView.ts +194 -134
- package/front_end/panels/application/DeviceBoundSessionsTreeElement.ts +1 -1
- package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +1 -0
- package/front_end/panels/application/StorageItemsToolbar.ts +25 -2
- package/front_end/panels/application/components/BackForwardCacheView.ts +4 -2
- package/front_end/panels/application/cookieItemsView.css +28 -26
- package/front_end/panels/autofill/AutofillView.ts +1 -1
- package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +161 -154
- package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +1 -0
- package/front_end/panels/browser_debugger/domBreakpointsSidebarPane.css +58 -36
- package/front_end/panels/console/ConsoleViewMessage.ts +2 -1
- package/front_end/panels/console/consoleView.css +7 -2
- package/front_end/panels/elements/ComputedStyleWidget.ts +16 -26
- package/front_end/panels/elements/ElementsPanel.ts +26 -3
- package/front_end/panels/elements/ElementsTreeElement.ts +1 -0
- package/front_end/panels/elements/LayoutPane.ts +8 -7
- package/front_end/panels/elements/StandaloneStylesContainer.ts +254 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +33 -0
- package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +148 -1
- package/front_end/panels/elements/components/ComputedStyleTrace.ts +4 -0
- package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +1 -1
- package/front_end/panels/elements/components/StylePropertyEditor.ts +2 -1
- package/front_end/panels/elements/components/computedStyleProperty.css +1 -1
- package/front_end/panels/elements/elements-meta.ts +3 -5
- package/front_end/panels/elements/elements.ts +2 -0
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +6 -0
- package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +1 -1
- package/front_end/panels/linear_memory_inspector/components/ValueInterpreterDisplay.ts +1 -1
- package/front_end/panels/media/PlayerListView.ts +1 -1
- package/front_end/panels/network/NetworkLogViewColumns.ts +2 -1
- package/front_end/panels/network/components/RequestHeaderSection.ts +1 -1
- package/front_end/panels/protocol_monitor/JSONEditor.ts +1 -1
- package/front_end/panels/recorder/components/RecordingListView.ts +1 -1
- package/front_end/panels/recorder/components/StepEditor.ts +3 -3
- package/front_end/panels/settings/KeybindsSettingsTab.ts +2 -1
- package/front_end/panels/sources/components/HeadersView.ts +2 -2
- package/front_end/panels/timeline/components/BreadcrumbsUI.ts +1 -1
- package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -1
- package/front_end/panels/timeline/components/metricCard.css +0 -4
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +6 -6
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/package.json +4 -4
- package/front_end/third_party/puppeteer/package/src/bidi/core/Navigation.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/cdp/BrowserContext.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/suggestion_input/SuggestionInput.ts +7 -12
- package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +13 -2
- package/front_end/ui/components/tree_outline/TreeOutline.ts +6 -2
- package/front_end/ui/components/tree_outline/treeOutline.css +5 -0
- package/front_end/ui/legacy/ListControl.ts +5 -3
- package/front_end/ui/legacy/ListWidget.ts +1 -1
- package/front_end/ui/legacy/SoftContextMenu.ts +2 -1
- package/front_end/ui/legacy/Treeoutline.ts +1 -0
- package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +44 -24
- package/front_end/ui/legacy/components/data_grid/DataGrid.ts +4 -2
- package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +31 -0
- package/front_end/ui/visual_logging/Debugging.ts +4 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import * as Common from '../../../core/common/common.js';
|
|
6
6
|
import * as Host from '../../../core/host/host.js';
|
|
7
7
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
8
|
-
import * as Platform from '../../../core/platform/platform.js';
|
|
9
8
|
import * as Root from '../../../core/root/root.js';
|
|
10
9
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
11
10
|
import * as Logs from '../../logs/logs.js';
|
|
@@ -35,13 +34,12 @@ You are a Web Development Assistant integrated into Chrome DevTools. Your tone i
|
|
|
35
34
|
You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
|
|
36
35
|
|
|
37
36
|
# Considerations
|
|
38
|
-
* Determine what
|
|
37
|
+
* Determine what is the domain of the question - styling, network, sources, performance or other part of DevTools.
|
|
39
38
|
* Proactively try to gather additional data. If a select specific data can be selected, select one.
|
|
40
39
|
* Always try select single specific context before answering the question.
|
|
41
40
|
* Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
|
|
42
41
|
* When presenting solutions, clearly distinguish between the primary cause and contributing factors.
|
|
43
42
|
* Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
|
|
44
|
-
* When answering, always consider MULTIPLE possible solutions.
|
|
45
43
|
* If you are unable to gather more information provide a comprehensive guide to how to fix the issue using Chrome DevTools and explain how and why.
|
|
46
44
|
* You can suggest any panel or flow in Chrome DevTools that may help the user out
|
|
47
45
|
|
|
@@ -50,9 +48,13 @@ You aim to help developers of all levels, prioritizing teaching web concepts as
|
|
|
50
48
|
* Always specify the language for code blocks (e.g., \`\`\`css, \`\`\`javascript).
|
|
51
49
|
* Keep text responses concise and scannable.
|
|
52
50
|
|
|
51
|
+
* **CRITICAL** If a tool returns an empty list, immediately pivot to the next logical tool (e.g., from sources to network).
|
|
52
|
+
* **CRITICAL** Always exhaust all possible way to find and select context from different domains.
|
|
53
53
|
* **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
|
|
54
54
|
* **CRITICAL** NEVER output text before a function call. Always do a function call first.
|
|
55
55
|
* **CRITICAL** You are a debugging assistant in DevTools. 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.
|
|
56
|
+
* **CRITICAL** When referring to DevTools resource output a markdown link to the object using the format \`[<text>](#<type>-<ID>)\`.
|
|
57
|
+
* The only available types are \`#req\` for network request and \`#file\` for source files. Only use ID inside the link, never ask about user selecting by ID.
|
|
56
58
|
`;
|
|
57
59
|
|
|
58
60
|
/**
|
|
@@ -117,9 +119,11 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
requests.push({
|
|
122
|
+
id: request.requestId(),
|
|
120
123
|
url: request.url(),
|
|
121
124
|
statusCode: request.statusCode,
|
|
122
125
|
duration: i18n.TimeUtilities.secondsToString(request.duration),
|
|
126
|
+
transferSize: i18n.ByteUtilities.formatBytesToKb(request.transferSize),
|
|
123
127
|
});
|
|
124
128
|
}
|
|
125
129
|
|
|
@@ -135,18 +139,18 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
135
139
|
},
|
|
136
140
|
});
|
|
137
141
|
|
|
138
|
-
this.declareFunction<{
|
|
142
|
+
this.declareFunction<{id: string}>('selectNetworkRequest', {
|
|
139
143
|
description:
|
|
140
144
|
`Selects a specific network request to further provide information about. Use this when asked about network requests issues.`,
|
|
141
145
|
parameters: {
|
|
142
146
|
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
143
147
|
description: '',
|
|
144
148
|
nullable: true,
|
|
145
|
-
required: ['
|
|
149
|
+
required: ['id'],
|
|
146
150
|
properties: {
|
|
147
|
-
|
|
151
|
+
id: {
|
|
148
152
|
type: Host.AidaClient.ParametersTypes.STRING,
|
|
149
|
-
description: 'The
|
|
153
|
+
description: 'The id of the network request',
|
|
150
154
|
nullable: false,
|
|
151
155
|
},
|
|
152
156
|
},
|
|
@@ -154,15 +158,12 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
154
158
|
displayInfoFromArgs: args => {
|
|
155
159
|
return {
|
|
156
160
|
title: lockedString('Getting network request…'),
|
|
157
|
-
action: `selectNetworkRequest(${args.
|
|
161
|
+
action: `selectNetworkRequest(${args.id})`,
|
|
158
162
|
};
|
|
159
163
|
},
|
|
160
|
-
handler: async ({
|
|
161
|
-
// TODO: Switch to using IDs to make is easier to link to as well.
|
|
164
|
+
handler: async ({id}) => {
|
|
162
165
|
const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
|
|
163
|
-
return req.
|
|
164
|
-
req.url() === Platform.DevToolsPath.urlString`${url.slice(0, -1)}` ||
|
|
165
|
-
req.url() === Platform.DevToolsPath.urlString`${url}/`;
|
|
166
|
+
return req.requestId() === id;
|
|
166
167
|
});
|
|
167
168
|
|
|
168
169
|
if (request) {
|
|
@@ -195,32 +196,32 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
195
196
|
};
|
|
196
197
|
},
|
|
197
198
|
handler: async () => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
const files: Array<{file: string, id: number | undefined}> = [];
|
|
200
|
+
for (const file of ContextSelectionAgent.getUISourceCodes()) {
|
|
201
|
+
files.push({
|
|
202
|
+
file: file.fullDisplayName(),
|
|
203
|
+
id: ContextSelectionAgent.uiSourceCodeId.get(file),
|
|
204
|
+
});
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
return {
|
|
207
|
-
result:
|
|
208
|
+
result: files,
|
|
208
209
|
};
|
|
209
210
|
},
|
|
210
211
|
});
|
|
211
212
|
|
|
212
|
-
this.declareFunction<{
|
|
213
|
+
this.declareFunction<{id: number}>('selectSourceFile', {
|
|
213
214
|
description:
|
|
214
|
-
`Selects a source file. Use this when asked about files on the page. Use listSourceFiles
|
|
215
|
+
`Selects a source file. Use this when asked about files on the page. Use listSourceFiles to find the file ID.`,
|
|
215
216
|
parameters: {
|
|
216
217
|
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
217
218
|
description: '',
|
|
218
219
|
nullable: true,
|
|
219
|
-
required: ['
|
|
220
|
+
required: ['id'],
|
|
220
221
|
properties: {
|
|
221
|
-
|
|
222
|
-
type: Host.AidaClient.ParametersTypes.
|
|
223
|
-
description: 'The
|
|
222
|
+
id: {
|
|
223
|
+
type: Host.AidaClient.ParametersTypes.INTEGER,
|
|
224
|
+
description: 'The id (URL) of the file you want to select.',
|
|
224
225
|
nullable: false,
|
|
225
226
|
},
|
|
226
227
|
},
|
|
@@ -228,22 +229,19 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
228
229
|
displayInfoFromArgs: args => {
|
|
229
230
|
return {
|
|
230
231
|
title: lockedString('Getting source file…'),
|
|
231
|
-
action: `selectSourceFile(${args.
|
|
232
|
+
action: `selectSourceFile(${args.id})`,
|
|
232
233
|
};
|
|
233
234
|
},
|
|
234
235
|
handler: async params => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const files = this.#getUISourceCodes().filter(file => file.fullDisplayName() === params.name);
|
|
236
|
+
const file = ContextSelectionAgent.getUISourceCodes().find(
|
|
237
|
+
file => ContextSelectionAgent.uiSourceCodeId.get(file) === params.id);
|
|
238
238
|
|
|
239
|
-
if (
|
|
239
|
+
if (!file) {
|
|
240
240
|
return {
|
|
241
241
|
error: 'Unable to find file.',
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
// This help us pick the file that is resolved source map.
|
|
246
|
-
const file = files.find(f => f.contentType().isFromSourceMap()) ?? files[0];
|
|
247
245
|
return {
|
|
248
246
|
context: new FileContext(file),
|
|
249
247
|
description: 'User selected a source file',
|
|
@@ -293,14 +291,23 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
293
291
|
},
|
|
294
292
|
displayInfoFromArgs: () => {
|
|
295
293
|
return {
|
|
296
|
-
title: lockedString('
|
|
297
|
-
action: 'selectElement()',
|
|
294
|
+
title: lockedString('Select an element on the page or in the Elements panel'),
|
|
298
295
|
};
|
|
299
296
|
},
|
|
300
|
-
handler: async () => {
|
|
297
|
+
handler: async (_params, options) => {
|
|
301
298
|
if (!this.#onInspectElement) {
|
|
302
|
-
return {
|
|
299
|
+
return {
|
|
300
|
+
error: 'The inspect element action is not available.',
|
|
301
|
+
};
|
|
303
302
|
}
|
|
303
|
+
|
|
304
|
+
if (!options?.approved) {
|
|
305
|
+
return {
|
|
306
|
+
requiresApproval: true,
|
|
307
|
+
description: null,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
304
311
|
const node = await this.#onInspectElement();
|
|
305
312
|
if (node) {
|
|
306
313
|
return {
|
|
@@ -315,27 +322,51 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
315
322
|
});
|
|
316
323
|
}
|
|
317
324
|
|
|
318
|
-
|
|
325
|
+
async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
override async enhanceQuery(query: string): Promise<string> {
|
|
329
|
+
return query;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
static lastSourceId = 0;
|
|
333
|
+
static uiSourceCodeId = new WeakMap<Workspace.UISourceCode.UISourceCode, number>();
|
|
334
|
+
/**
|
|
335
|
+
* This is a heuristic algorithm that gets all the source files coming from the
|
|
336
|
+
* network and assigns unique ids to be linked from the LLM Markdown response.
|
|
337
|
+
* Steps we do:
|
|
338
|
+
* 1. Get all project that are coming from the Network. This scopes down
|
|
339
|
+
* sources exposed to the LLM
|
|
340
|
+
* 2. Remove all ignore listed source code. We further reduce thing that the
|
|
341
|
+
* user most likely does not have interest in, from global setting.
|
|
342
|
+
* 3.1. Source files don't have an uniqueId so we use the URL to differentiate
|
|
343
|
+
* them.
|
|
344
|
+
* 3.2. In cases where we encounter a duplicated URLs we prefer the latest one
|
|
345
|
+
* coming from SourceMaps (usually only one) as that has simple code and
|
|
346
|
+
* usually is what the user authored.
|
|
347
|
+
*/
|
|
348
|
+
static getUISourceCodes(): Workspace.UISourceCode.UISourceCode[] {
|
|
319
349
|
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
|
|
320
350
|
const projects =
|
|
321
351
|
workspace.projects().filter(project => project.type() === Workspace.Workspace.projectTypes.Network);
|
|
322
|
-
const uiSourceCodes =
|
|
352
|
+
const uiSourceCodes = new Map<string, Workspace.UISourceCode.UISourceCode>();
|
|
353
|
+
|
|
323
354
|
for (const project of projects) {
|
|
324
355
|
for (const uiSourceCode of project.uiSourceCodes()) {
|
|
325
356
|
if (uiSourceCode.isIgnoreListed()) {
|
|
326
357
|
continue;
|
|
327
358
|
}
|
|
328
|
-
|
|
359
|
+
const url = uiSourceCode.url();
|
|
360
|
+
// This helps us pick the file that is a resolved source map.
|
|
361
|
+
if (!uiSourceCodes.get(url) || uiSourceCode.contentType().isFromSourceMap()) {
|
|
362
|
+
uiSourceCodes.set(url, uiSourceCode);
|
|
363
|
+
if (!ContextSelectionAgent.uiSourceCodeId.has(uiSourceCode)) {
|
|
364
|
+
ContextSelectionAgent.uiSourceCodeId.set(uiSourceCode, ++ContextSelectionAgent.lastSourceId);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
329
367
|
}
|
|
330
368
|
}
|
|
331
369
|
|
|
332
|
-
return uiSourceCodes;
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
override async enhanceQuery(query: string): Promise<string> {
|
|
339
|
-
return query;
|
|
370
|
+
return [...uiSourceCodes.values()];
|
|
340
371
|
}
|
|
341
372
|
}
|
|
@@ -18,6 +18,7 @@ import {FREESTYLER_WORLD_NAME} from '../injected.js';
|
|
|
18
18
|
import {
|
|
19
19
|
type AgentOptions as BaseAgentOptions,
|
|
20
20
|
AiAgent,
|
|
21
|
+
type ComputedStyleAiWidget,
|
|
21
22
|
type ContextResponse,
|
|
22
23
|
ConversationContext,
|
|
23
24
|
type ConversationSuggestions,
|
|
@@ -325,7 +326,7 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
325
326
|
};
|
|
326
327
|
},
|
|
327
328
|
handler: async params => {
|
|
328
|
-
return await this
|
|
329
|
+
return await this.#getStyles(params.elements, params.styleProperties);
|
|
329
330
|
},
|
|
330
331
|
});
|
|
331
332
|
|
|
@@ -606,7 +607,9 @@ const data = {
|
|
|
606
607
|
return this.context?.getItem() ?? null;
|
|
607
608
|
}
|
|
608
609
|
|
|
609
|
-
async getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
|
|
610
|
+
async #getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
|
|
611
|
+
const widgets: ComputedStyleAiWidget[] = [];
|
|
612
|
+
|
|
610
613
|
const result:
|
|
611
614
|
Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
|
|
612
615
|
for (const uid of elements) {
|
|
@@ -630,6 +633,15 @@ const data = {
|
|
|
630
633
|
if (!matchedStyles) {
|
|
631
634
|
return {error: 'Error: Could not get authored styles.'};
|
|
632
635
|
}
|
|
636
|
+
widgets.push({
|
|
637
|
+
name: 'COMPUTED_STYLES',
|
|
638
|
+
data: {
|
|
639
|
+
computedStyles: styles,
|
|
640
|
+
backendNodeId: node.backendNodeId(),
|
|
641
|
+
matchedCascade: matchedStyles,
|
|
642
|
+
properties,
|
|
643
|
+
}
|
|
644
|
+
});
|
|
633
645
|
for (const prop of properties) {
|
|
634
646
|
result[uid].computed[prop] = styles.get(prop);
|
|
635
647
|
}
|
|
@@ -647,6 +659,7 @@ const data = {
|
|
|
647
659
|
}
|
|
648
660
|
return {
|
|
649
661
|
result: JSON.stringify(result, null, 2),
|
|
662
|
+
widgets,
|
|
650
663
|
};
|
|
651
664
|
}
|
|
652
665
|
|
|
@@ -701,6 +714,7 @@ const data = {
|
|
|
701
714
|
|
|
702
715
|
return {
|
|
703
716
|
requiresApproval: true,
|
|
717
|
+
description: lockedString('This code may modify page content. Continue?'),
|
|
704
718
|
};
|
|
705
719
|
}
|
|
706
720
|
if (result.canceled) {
|
|
@@ -12,17 +12,18 @@ import * as SDK from '../../core/sdk/sdk.js';
|
|
|
12
12
|
* Model trackComputedStyleUpdatesForNode method.
|
|
13
13
|
*/
|
|
14
14
|
export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
15
|
-
#node: SDK.DOMModel.DOMNode|null;
|
|
16
|
-
#cssModel: SDK.CSSModel.CSSModel|null;
|
|
17
|
-
private eventListeners: Common.EventTarget.EventDescriptor[];
|
|
15
|
+
#node: SDK.DOMModel.DOMNode|null = null;
|
|
16
|
+
#cssModel: SDK.CSSModel.CSSModel|null = null;
|
|
17
|
+
private eventListeners: Common.EventTarget.EventDescriptor[] = [];
|
|
18
18
|
private frameResizedTimer?: number;
|
|
19
19
|
private computedStylePromise?: Promise<ComputedStyle|null>;
|
|
20
20
|
|
|
21
21
|
constructor(node?: SDK.DOMModel.DOMNode|null) {
|
|
22
22
|
super();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (node) {
|
|
24
|
+
// Call the explicit setter to trigger the setup and event binding.
|
|
25
|
+
this.node = node;
|
|
26
|
+
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
get node(): SDK.DOMModel.DOMNode|null {
|
|
@@ -39,6 +40,21 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
39
40
|
return this.#cssModel?.isEnabled() ? this.#cssModel : null;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Clears all event listeners to ensure the instance can be GC'd without leaking memory.
|
|
45
|
+
*/
|
|
46
|
+
dispose(): void {
|
|
47
|
+
Common.EventTarget.removeEventListeners(this.eventListeners);
|
|
48
|
+
this.eventListeners = [];
|
|
49
|
+
this.node = null;
|
|
50
|
+
this.#cssModel = null;
|
|
51
|
+
this.computedStylePromise = undefined;
|
|
52
|
+
if (this.frameResizedTimer) {
|
|
53
|
+
clearTimeout(this.frameResizedTimer);
|
|
54
|
+
this.frameResizedTimer = undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
private updateModel(cssModel: SDK.CSSModel.CSSModel|null): void {
|
|
43
59
|
if (this.#cssModel === cssModel) {
|
|
44
60
|
return;
|
|
@@ -168,6 +184,24 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
168
184
|
}
|
|
169
185
|
return matchedStyles.node() === this.node ? matchedStyles : null;
|
|
170
186
|
}
|
|
187
|
+
|
|
188
|
+
computePropertyTraces(matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles):
|
|
189
|
+
Map<string, SDK.CSSProperty.CSSProperty[]> {
|
|
190
|
+
const result = new Map<string, SDK.CSSProperty.CSSProperty[]>();
|
|
191
|
+
for (const style of matchedStyles.nodeStyles()) {
|
|
192
|
+
const allProperties = style.allProperties();
|
|
193
|
+
for (const property of allProperties) {
|
|
194
|
+
if (!property.activeInStyle() || !matchedStyles.propertyState(property)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const matches = result.get(property.name) ?? [];
|
|
199
|
+
matches.push(property);
|
|
200
|
+
result.set(property.name, matches);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
171
205
|
}
|
|
172
206
|
|
|
173
207
|
export const enum Events {
|
|
@@ -7322,16 +7322,16 @@ export const NativeFunctions = [
|
|
|
7322
7322
|
signatures: [["type","eventInitDict"]]
|
|
7323
7323
|
},
|
|
7324
7324
|
{
|
|
7325
|
-
name: "
|
|
7326
|
-
signatures: [["
|
|
7325
|
+
name: "provideContext",
|
|
7326
|
+
signatures: [["?options"]]
|
|
7327
7327
|
},
|
|
7328
7328
|
{
|
|
7329
|
-
name: "
|
|
7330
|
-
signatures: [["
|
|
7329
|
+
name: "registerTool",
|
|
7330
|
+
signatures: [["tool"]]
|
|
7331
7331
|
},
|
|
7332
7332
|
{
|
|
7333
|
-
name: "
|
|
7334
|
-
signatures: [["
|
|
7333
|
+
name: "unregisterTool",
|
|
7334
|
+
signatures: [["name"]]
|
|
7335
7335
|
},
|
|
7336
7336
|
{
|
|
7337
7337
|
name: "SnapEvent",
|
|
@@ -452,9 +452,11 @@ export class AXBreadcrumb {
|
|
|
452
452
|
|
|
453
453
|
this.#element = document.createElement('div');
|
|
454
454
|
this.#element.classList.add('ax-breadcrumb');
|
|
455
|
-
this.#element.setAttribute(
|
|
456
|
-
|
|
457
|
-
|
|
455
|
+
this.#element.setAttribute('jslog', `${VisualLogging.treeItem().track({
|
|
456
|
+
click: true,
|
|
457
|
+
resize: true,
|
|
458
|
+
keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter'
|
|
459
|
+
})}`);
|
|
458
460
|
elementsToAXBreadcrumb.set(this.#element, this);
|
|
459
461
|
|
|
460
462
|
this.#nodeElement = document.createElement('div');
|
|
@@ -13,6 +13,7 @@ import * as SDK from '../../core/sdk/sdk.js';
|
|
|
13
13
|
import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
|
|
14
14
|
import * as Annotations from '../../models/annotations/annotations.js';
|
|
15
15
|
import * as Badges from '../../models/badges/badges.js';
|
|
16
|
+
import * as Greendev from '../../models/greendev/greendev.js';
|
|
16
17
|
import * as TextUtils from '../../models/text_utils/text_utils.js';
|
|
17
18
|
import type * as Trace from '../../models/trace/trace.js';
|
|
18
19
|
import * as Workspace from '../../models/workspace/workspace.js';
|
|
@@ -459,9 +460,23 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
|
|
|
459
460
|
}
|
|
460
461
|
}
|
|
461
462
|
|
|
462
|
-
|
|
463
|
+
if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled ||
|
|
464
|
+
Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent')) {
|
|
465
|
+
|
|
466
|
+
const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
|
|
467
|
+
/**
|
|
468
|
+
* We want to mark the walkthrough as loading only if it's showing the last
|
|
469
|
+
* message. Otherwise, a previous walkthrough will show as loading if we
|
|
470
|
+
* rely only on the isLoading flag.
|
|
471
|
+
*/
|
|
472
|
+
let walkthroughIsForLastMessage = false;
|
|
473
|
+
if(input.state === ViewState.CHAT_VIEW) {
|
|
474
|
+
const lastMessage = input.props.messages.at(-1);
|
|
475
|
+
if(lastMessage && input.props.walkthrough.activeMessage === lastMessage) {
|
|
476
|
+
walkthroughIsForLastMessage = true;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
463
479
|
|
|
464
|
-
if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
|
|
465
480
|
Lit.render(html`
|
|
466
481
|
${toolbarView(input)}
|
|
467
482
|
<div class="ai-assistance-view-container">
|
|
@@ -479,7 +494,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
|
|
|
479
494
|
${shouldShowWalkthrough ? html`
|
|
480
495
|
<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
|
|
481
496
|
message: input.props.walkthrough.activeMessage,
|
|
482
|
-
isLoading: input.props.isLoading,
|
|
497
|
+
isLoading: input.props.isLoading && walkthroughIsForLastMessage,
|
|
483
498
|
markdownRenderer: input.props.markdownRenderer,
|
|
484
499
|
onToggle: input.props.walkthrough.onToggle,
|
|
485
500
|
})}></devtools-widget>` : Lit.nothing}
|
|
@@ -915,7 +930,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
915
930
|
this.requestUpdate();
|
|
916
931
|
}
|
|
917
932
|
|
|
918
|
-
async handleBreakpointConversation(uiLocation: Workspace.UISourceCode.UILocation): Promise<void> {
|
|
933
|
+
async handleBreakpointConversation(uiLocation: Workspace.UISourceCode.UILocation, errorMsg?: string): Promise<void> {
|
|
919
934
|
const context = new AiAssistanceModel.BreakpointDebuggerAgent.BreakpointContext(uiLocation);
|
|
920
935
|
this.#selectedBreakpoint = context;
|
|
921
936
|
const conversation = new AiAssistanceModel.AiConversation.AiConversation(
|
|
@@ -934,6 +949,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
934
949
|
this.#conversation?.setContext(context);
|
|
935
950
|
this.requestUpdate();
|
|
936
951
|
await UI.ViewManager.ViewManager.instance().showView(AiAssistancePanel.panelName);
|
|
952
|
+
const prompt = errorMsg ? `debug the error "${errorMsg}" using breakpoint debugging agent` :
|
|
953
|
+
'debug the error using breakpoint debugging agent';
|
|
954
|
+
await this.#startConversation(prompt);
|
|
937
955
|
}
|
|
938
956
|
|
|
939
957
|
override wasShown(): void {
|
|
@@ -1434,11 +1452,19 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1434
1452
|
this.#updateConversationState();
|
|
1435
1453
|
}
|
|
1436
1454
|
|
|
1455
|
+
#clearWalkthrough(): void {
|
|
1456
|
+
this.#walkthrough.isExpanded = false;
|
|
1457
|
+
this.#walkthrough.activeMessage = null;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1437
1460
|
#onDeleteClicked(): void {
|
|
1438
1461
|
if (!this.#conversation) {
|
|
1439
1462
|
return;
|
|
1440
1463
|
}
|
|
1441
1464
|
|
|
1465
|
+
// Ensure we clear the walkthrough so it doesn't hold onto a chat that is about to be deleted.
|
|
1466
|
+
this.#clearWalkthrough();
|
|
1467
|
+
|
|
1442
1468
|
void AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().deleteHistoryEntry(this.#conversation.id);
|
|
1443
1469
|
this.#updateConversationState();
|
|
1444
1470
|
UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.chatDeleted));
|
|
@@ -1645,7 +1671,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1645
1671
|
let announcedAnswerLoading = false;
|
|
1646
1672
|
let announcedAnswerReady = false;
|
|
1647
1673
|
for await (const data of items) {
|
|
1648
|
-
step.
|
|
1674
|
+
step.requestApproval = undefined;
|
|
1649
1675
|
switch (data.type) {
|
|
1650
1676
|
case AiAssistanceModel.AiAgent.ResponseType.USER_QUERY: {
|
|
1651
1677
|
this.#messages.push({
|
|
@@ -1658,6 +1684,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1658
1684
|
parts: [],
|
|
1659
1685
|
};
|
|
1660
1686
|
this.#messages.push(systemMessage);
|
|
1687
|
+
if (Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent') &&
|
|
1688
|
+
this.#conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.BREAKPOINT) {
|
|
1689
|
+
this.#openWalkthrough(systemMessage);
|
|
1690
|
+
}
|
|
1661
1691
|
break;
|
|
1662
1692
|
}
|
|
1663
1693
|
case AiAssistanceModel.AiAgent.ResponseType.QUERYING: {
|
|
@@ -1702,10 +1732,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1702
1732
|
case AiAssistanceModel.AiAgent.ResponseType.SIDE_EFFECT: {
|
|
1703
1733
|
step.isLoading = false;
|
|
1704
1734
|
step.code ??= data.code;
|
|
1705
|
-
step.
|
|
1735
|
+
step.requestApproval = {
|
|
1736
|
+
description: data.description,
|
|
1706
1737
|
onAnswer: (result: boolean) => {
|
|
1707
1738
|
data.confirm(result);
|
|
1708
|
-
step.
|
|
1739
|
+
step.requestApproval = undefined;
|
|
1709
1740
|
this.requestUpdate();
|
|
1710
1741
|
},
|
|
1711
1742
|
};
|
|
@@ -1717,6 +1748,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1717
1748
|
step.code ??= data.code;
|
|
1718
1749
|
step.output ??= data.output;
|
|
1719
1750
|
step.canceled = data.canceled;
|
|
1751
|
+
step.widgets ??= data.widgets;
|
|
1720
1752
|
commitStep();
|
|
1721
1753
|
break;
|
|
1722
1754
|
}
|
|
@@ -83,3 +83,64 @@ The spinner displayed next to a step in the UI is intentionally tied to the **co
|
|
|
83
83
|
* **Providing Continuous Feedback**: Using the conversation's `isLoading` ensures a persistent visual indicator that the AI is actively working on the query, even if individual steps are quickly parsed. This prevents the UI from appearing unresponsive.
|
|
84
84
|
* **Avoiding a "Stuck" Feeling**: If the spinner were tied to `step.isLoading`, it would flicker on and off rapidly, potentially making the user feel that the AI has stopped processing or is stuck.
|
|
85
85
|
* **Clear Progress Visualization**: The spinner is dynamically moved to the *last* active step in the list as long as the overall conversation is loading. Once a step is completed, it receives a checkmark, and the spinner moves to the next active step.
|
|
86
|
+
|
|
87
|
+
## AI-Defined UI Widgets
|
|
88
|
+
|
|
89
|
+
To provide a richer user experience, AI functions can now return data that represents UI widgets. These widgets are then rendered directly within the AI assistance panel, allowing for more interactive and contextual responses.
|
|
90
|
+
|
|
91
|
+
### Data Structure for Widgets
|
|
92
|
+
|
|
93
|
+
This is achieved through a new type of `ModelMessagePart`: the `WidgetPart`.
|
|
94
|
+
|
|
95
|
+
- **`WidgetPart`**: Represents a UI widget to be rendered. It contains a `widget` object with the following properties:
|
|
96
|
+
- `name`: A string identifier for the widget to be rendered (e.g., `'freestyler'`). This name is used by the frontend to select the correct widget component.
|
|
97
|
+
- `data`: An object containing the data required by the widget. The structure of this data is specific to each widget.
|
|
98
|
+
|
|
99
|
+
A `ModelChatMessage` can contain one or more `WidgetPart`s, usually as part of the final answer.
|
|
100
|
+
|
|
101
|
+
**Example `ModelChatMessage` with a `WidgetPart`:**
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"entity": "MODEL",
|
|
106
|
+
"parts": [
|
|
107
|
+
{
|
|
108
|
+
"type": "answer",
|
|
109
|
+
"text": "Here is a widget to help you with CSS."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "widget",
|
|
113
|
+
"widget": {
|
|
114
|
+
"name": "freestyler",
|
|
115
|
+
"data": {
|
|
116
|
+
"css": "color: red;"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### From AI Function to UI
|
|
125
|
+
|
|
126
|
+
The process of rendering an AI-defined UI widget begins within the AI function handler itself. AI functions, typically defined in `front_end/models/ai_assistance/agents/AiAgent.js`, can return an `AiWidget` type as part of their response.
|
|
127
|
+
|
|
128
|
+
1. **AI Function Output**: An AI function constructs an `AiWidget` object, specifying its `name` (e.g., `'freestyler'` or `'COMPUTED_STYLES'`) and a `data` payload that contains all the necessary information for the widget to render.
|
|
129
|
+
|
|
130
|
+
2. **`ModelChatMessage` Integration**: This `AiWidget` is then encapsulated within a `WidgetPart`, which is added to the `parts` array of a `ModelChatMessage`. This `ModelChatMessage` is what the `ChatMessage` UI component receives.
|
|
131
|
+
|
|
132
|
+
3. **`ChatMessage` Processing**: In `front_end/panels/ai_assistance/components/ChatMessage.ts`, the `ChatMessage` component is responsible for iterating through the `parts` of a `ModelChatMessage`. When it encounters a `WidgetPart`, it delegates the rendering to the `renderStepWidgets` function.
|
|
133
|
+
|
|
134
|
+
4. **`renderStepWidgets` Mapping**: The `renderStepWidgets` function acts as a registry and renderer for different widget types. It reads the `widget.name` from the `WidgetPart` and, based on this name, calls a specific `make...Widget` function (e.g., `makeComputedStyleWidget` for `'COMPUTED_STYLES'` widgets).
|
|
135
|
+
|
|
136
|
+
5. **Widget Instantiation and Rendering**: Each `make...Widget` function is responsible for taking the `widget.data` and converting it into a `UI.Widget.widgetConfig` object. This configuration is then used with the `<devtools-widget>` Lit component, which dynamically instantiates the corresponding `UI.Widget` subclass and renders it into the DOM.
|
|
137
|
+
|
|
138
|
+
This modular approach ensures that new UI widgets can be introduced and managed by AI functions without requiring significant changes to the core messaging or rendering infrastructure.
|
|
139
|
+
|
|
140
|
+
The `ChatMessage` component is responsible for handling `WidgetPart`s. When it encounters a `WidgetPart`, it will:
|
|
141
|
+
|
|
142
|
+
1. Look up the widget name (`widget.name`) in a registry of available widget components.
|
|
143
|
+
2. Instantiate the corresponding widget component.
|
|
144
|
+
3. Pass the `widget.data` to the component as properties.
|
|
145
|
+
|
|
146
|
+
This allows for a flexible system where new widgets can be added to the frontend and then invoked by the AI without requiring changes to the core message handling logic.
|