chrome-devtools-frontend 1.0.1574367 → 1.0.1575635
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/.stylelintrc.json +1 -1
- package/AUTHORS +1 -0
- package/docs/contributing/settings-experiments-features.md +5 -2
- package/front_end/core/common/Settings.ts +1 -1
- package/front_end/core/host/InspectorFrontendHostAPI.ts +7 -1
- package/front_end/core/host/InspectorFrontendHostStub.ts +10 -0
- package/front_end/core/host/UserMetrics.ts +15 -0
- package/front_end/core/root/Runtime.ts +119 -39
- package/front_end/core/sdk/CSSMatchedStyles.ts +27 -0
- package/front_end/entrypoints/main/MainImpl.ts +25 -7
- package/front_end/entrypoints/node_app/app/NodeConnectionsPanel.ts +3 -2
- package/front_end/generated/InspectorBackendCommands.ts +37 -0
- package/front_end/generated/protocol-mapping.d.ts +257 -0
- package/front_end/generated/protocol-proxy-api.d.ts +256 -0
- package/front_end/generated/protocol.ts +359 -0
- package/front_end/models/ai_assistance/AiConversation.ts +31 -10
- package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +29 -5
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +73 -0
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +141 -0
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +3 -3
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +5 -4
- package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
- package/front_end/models/issues_manager/CorsIssue.ts +0 -3
- package/front_end/models/javascript_metadata/NativeFunctions.js +5 -0
- package/front_end/models/live-metrics/LiveMetrics.ts +31 -51
- package/front_end/models/stack_trace/StackTrace.ts +41 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +72 -6
- package/front_end/panels/ai_assistance/ai_assistance-meta.ts +1 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +24 -2
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +6 -4
- package/front_end/panels/ai_assistance/components/ChatView.ts +5 -6
- package/front_end/panels/ai_assistance/components/chatView.css +10 -0
- package/front_end/panels/application/StorageView.ts +3 -3
- package/front_end/panels/application/preloading/components/preloadingDisabledInfobar.css +1 -1
- package/front_end/panels/common/AiCodeGenerationUpgradeDialog.ts +32 -16
- package/front_end/panels/common/aiCodeCompletionSummaryToolbar.css +1 -1
- package/front_end/panels/console/ConsoleContextSelector.ts +4 -3
- package/front_end/panels/console/ConsoleFormat.ts +31 -2
- package/front_end/panels/console/ConsoleInsightTeaser.ts +6 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +46 -2
- package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
- package/front_end/panels/elements/StylePropertyTreeElement.ts +63 -0
- package/front_end/panels/elements/StylesSidebarPane.ts +17 -2
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +4 -0
- package/front_end/panels/issues/AffectedMetadataAllowedSitesView.ts +3 -3
- package/front_end/panels/layer_viewer/Layers3DView.ts +2 -1
- package/front_end/panels/lighthouse/LighthouseStartView.ts +7 -6
- package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorPane.ts +3 -2
- package/front_end/panels/network/RequestConditionsDrawer.ts +2 -3
- package/front_end/panels/network/SignedExchangeInfoView.ts +3 -4
- package/front_end/panels/network/components/HeaderSectionRow.css +1 -1
- package/front_end/panels/recorder/components/RecordingView.ts +2 -2
- package/front_end/panels/recorder/components/recordingView.css +5 -0
- package/front_end/panels/security/SecurityPanelSidebar.ts +10 -9
- package/front_end/panels/settings/AISettingsTab.ts +2 -1
- package/front_end/panels/settings/KeybindsSettingsTab.ts +3 -5
- package/front_end/panels/settings/SettingsScreen.ts +14 -9
- package/front_end/panels/settings/settings-meta.ts +1 -0
- package/front_end/panels/sources/CallStackSidebarPane.ts +10 -0
- package/front_end/panels/sources/DebuggerPlugin.ts +18 -12
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +4 -3
- package/front_end/panels/sources/SourcesNavigator.ts +2 -2
- package/front_end/panels/sources/SourcesPanel.ts +6 -3
- package/front_end/panels/sources/TabbedEditorContainer.ts +2 -2
- package/front_end/panels/sources/sources-meta.ts +2 -1
- package/front_end/panels/timeline/TimelinePanel.ts +2 -1
- package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
- package/front_end/panels/timeline/components/DetailsView.ts +7 -7
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
- package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +4 -4
- package/front_end/panels/whats_new/ReleaseNoteText.ts +11 -11
- package/front_end/panels/whats_new/releaseNoteView.css +1 -1
- package/front_end/panels/whats_new/resources/WNDT.md +6 -6
- 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/cdp/TargetManager.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js +30 -25
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.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/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 +30 -21
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js +31 -26
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.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 +3 -3
- package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +45 -44
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/buttons/Button.ts +2 -2
- package/front_end/ui/components/panel_feedback/PreviewToggle.ts +1 -1
- package/front_end/ui/components/panel_feedback/panelFeedback.css +1 -1
- package/front_end/ui/kit/link/Link.ts +31 -0
- package/front_end/ui/legacy/LinkContextMenuProvider.ts +3 -5
- package/front_end/ui/legacy/components/object_ui/ObjectPopoverHelper.ts +2 -2
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +336 -250
- package/front_end/ui/legacy/inspectorCommon.css +0 -1
- package/front_end/ui/legacy/legacy.ts +0 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
- package/front_end/ui/visual_logging/LoggingDriver.ts +10 -6
- package/mcp/HostBindings.ts +6 -0
- package/package.json +1 -1
- package/front_end/ui/legacy/XElement.ts +0 -41
- package/front_end/ui/legacy/XLink.ts +0 -128
|
@@ -0,0 +1,141 @@
|
|
|
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 Host from '../../../core/host/host.js';
|
|
6
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
7
|
+
import * as Root from '../../../core/root/root.js';
|
|
8
|
+
import * as Logs from '../../logs/logs.js';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
type AgentOptions,
|
|
12
|
+
AiAgent,
|
|
13
|
+
type ContextResponse,
|
|
14
|
+
type RequestOptions,
|
|
15
|
+
} from './AiAgent.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* WARNING: preamble defined in code is only used when userTier is
|
|
19
|
+
* TESTERS. Otherwise, a server-side preamble is used (see
|
|
20
|
+
* chrome_preambles.gcl). Sync local changes with the server-side.
|
|
21
|
+
*/
|
|
22
|
+
const preamble = `
|
|
23
|
+
You are a Web Development Assistant integrated into Chrome DevTools. Your tone is educational, supportive, and technically precise.
|
|
24
|
+
You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
|
|
25
|
+
|
|
26
|
+
# Considerations
|
|
27
|
+
* Determine what the question the domain of the question is - styling, network, sources, performance or other part of DevTools.
|
|
28
|
+
* When possible proactively try to gather additional data and select context that they user may find relevant to the question they are asking utilizing the function calls available to you.
|
|
29
|
+
* Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
|
|
30
|
+
* Always explore multiple possible explanations for the observed behavior before settling on a conclusion.
|
|
31
|
+
* When presenting solutions, clearly distinguish between the primary cause and contributing factors.
|
|
32
|
+
* Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
|
|
33
|
+
* When answering, always consider MULTIPLE possible solutions.
|
|
34
|
+
* 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.
|
|
35
|
+
* You can suggest any panel or flow in Chrome DevTools that may help the user out
|
|
36
|
+
|
|
37
|
+
# Formatting Guidelines
|
|
38
|
+
* Use Markdown for all code snippets.
|
|
39
|
+
* Always specify the language for code blocks (e.g., \`\`\`css, \`\`\`javascript).
|
|
40
|
+
* Keep text responses concise and scannable.
|
|
41
|
+
|
|
42
|
+
* ALWAYS OUTPUT a list of follow-up queries at the end of your text response. The format is SUGGESTIONS: ["suggestion1", "suggestion2", "suggestion3"]. Make sure that the array and the \`SUGGESTIONS: \` text is in the same line. You're also capable of executing the fix for the issue user mentioned. Reflect this in your suggestions.
|
|
43
|
+
* **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
|
|
44
|
+
* **CRITICAL** NEVER output text before a function call. Always do a function call first.
|
|
45
|
+
* **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.
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One agent instance handles one conversation. Create a new agent
|
|
50
|
+
* instance for a new conversation.
|
|
51
|
+
*/
|
|
52
|
+
export class ContextSelectionAgent extends AiAgent<never> {
|
|
53
|
+
readonly preamble = preamble;
|
|
54
|
+
readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_FILE_AGENT;
|
|
55
|
+
get userTier(): string|undefined {
|
|
56
|
+
// TODO: Make this depend on variable.
|
|
57
|
+
return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
|
|
58
|
+
}
|
|
59
|
+
get options(): RequestOptions {
|
|
60
|
+
const temperature = Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.temperature;
|
|
61
|
+
const modelId = Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.modelId;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
temperature,
|
|
65
|
+
modelId,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
constructor(opts: AgentOptions) {
|
|
70
|
+
super(opts);
|
|
71
|
+
|
|
72
|
+
this.declareFunction<Record<string, never>>('listNetworkRequests', {
|
|
73
|
+
description: `Gives a list of network requests`,
|
|
74
|
+
parameters: {
|
|
75
|
+
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
76
|
+
description: '',
|
|
77
|
+
nullable: true,
|
|
78
|
+
required: [],
|
|
79
|
+
properties: {},
|
|
80
|
+
},
|
|
81
|
+
handler: async (_params, options) => {
|
|
82
|
+
if (!options?.approved) {
|
|
83
|
+
return {
|
|
84
|
+
requiresApproval: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const requestURls = [];
|
|
89
|
+
for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) {
|
|
90
|
+
requestURls.push(request.url());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
result: requestURls,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.declareFunction<{url: string}>('selectNetworkRequest', {
|
|
100
|
+
description: `From the list of selected request select one to debug`,
|
|
101
|
+
parameters: {
|
|
102
|
+
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
103
|
+
description: '',
|
|
104
|
+
nullable: true,
|
|
105
|
+
required: ['url'],
|
|
106
|
+
properties: {
|
|
107
|
+
url: {
|
|
108
|
+
type: Host.AidaClient.ParametersTypes.STRING,
|
|
109
|
+
description: 'The url of the requests',
|
|
110
|
+
nullable: false,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
handler: async ({url}) => {
|
|
115
|
+
// TODO: Switch to using IDs to make is easier to link to as well.
|
|
116
|
+
const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
|
|
117
|
+
return req.url() === Platform.DevToolsPath.urlString`${url}` ||
|
|
118
|
+
req.url() === Platform.DevToolsPath.urlString`${url.slice(0, -1)}` ||
|
|
119
|
+
req.url() === Platform.DevToolsPath.urlString`${url}/`;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (request) {
|
|
123
|
+
return {
|
|
124
|
+
context: request,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
error: 'No request found',
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override async enhanceQuery(query: string): Promise<string> {
|
|
139
|
+
return query;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -57,7 +57,7 @@ Content:
|
|
|
57
57
|
"function_declarations": [
|
|
58
58
|
{
|
|
59
59
|
"name": "getStyles",
|
|
60
|
-
"description": "Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.\n\n**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.\n**CRITICAL** Always provide the explanation argument to explain what and why you query.",
|
|
60
|
+
"description": "Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.\n\n**CRITICAL** An element uid is a number, not a selector.\n**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.\n**CRITICAL** Always provide the explanation argument to explain what and why you query.",
|
|
61
61
|
"parameters": {
|
|
62
62
|
"type": 6,
|
|
63
63
|
"description": "",
|
|
@@ -70,9 +70,9 @@ Content:
|
|
|
70
70
|
},
|
|
71
71
|
"elements": {
|
|
72
72
|
"type": 5,
|
|
73
|
-
"description": "A list of element uids to get data for",
|
|
73
|
+
"description": "A list of element uids to get data for. These are numbers, not selectors.",
|
|
74
74
|
"items": {
|
|
75
|
-
"type":
|
|
75
|
+
"type": 3,
|
|
76
76
|
"description": "An element uid."
|
|
77
77
|
},
|
|
78
78
|
"nullable": false
|
|
@@ -279,13 +279,14 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
this.declareFunction<{
|
|
282
|
-
elements:
|
|
282
|
+
elements: number[],
|
|
283
283
|
styleProperties: string[],
|
|
284
284
|
explanation: string,
|
|
285
285
|
}>('getStyles', {
|
|
286
286
|
description:
|
|
287
287
|
`Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.
|
|
288
288
|
|
|
289
|
+
**CRITICAL** An element uid is a number, not a selector.
|
|
289
290
|
**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.
|
|
290
291
|
**CRITICAL** Always provide the explanation argument to explain what and why you query.`,
|
|
291
292
|
parameters: {
|
|
@@ -300,8 +301,8 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
300
301
|
},
|
|
301
302
|
elements: {
|
|
302
303
|
type: Host.AidaClient.ParametersTypes.ARRAY,
|
|
303
|
-
description: 'A list of element uids to get data for',
|
|
304
|
-
items: {type: Host.AidaClient.ParametersTypes.
|
|
304
|
+
description: 'A list of element uids to get data for. These are numbers, not selectors.',
|
|
305
|
+
items: {type: Host.AidaClient.ParametersTypes.INTEGER, description: `An element uid.`},
|
|
305
306
|
nullable: false,
|
|
306
307
|
},
|
|
307
308
|
styleProperties: {
|
|
@@ -605,7 +606,7 @@ const data = {
|
|
|
605
606
|
return this.context?.getItem() ?? null;
|
|
606
607
|
}
|
|
607
608
|
|
|
608
|
-
async getStyles(elements:
|
|
609
|
+
async getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
|
|
609
610
|
const result:
|
|
610
611
|
Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
|
|
611
612
|
for (const uid of elements) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as AgentProject from './AgentProject.js';
|
|
6
6
|
import * as AiAgent from './agents/AiAgent.js';
|
|
7
|
+
import * as ContextSelectionAgent from './agents/ContextSelectionAgent.js';
|
|
7
8
|
import * as FileAgent from './agents/FileAgent.js';
|
|
8
9
|
import * as NetworkAgent from './agents/NetworkAgent.js';
|
|
9
10
|
import * as PatchAgent from './agents/PatchAgent.js';
|
|
@@ -40,6 +41,7 @@ export {
|
|
|
40
41
|
AiUtils,
|
|
41
42
|
BuiltInAi,
|
|
42
43
|
ChangeManager,
|
|
44
|
+
ContextSelectionAgent,
|
|
43
45
|
ConversationHandler,
|
|
44
46
|
Debug,
|
|
45
47
|
EvaluateAction,
|
|
@@ -94,9 +94,6 @@ function getIssueCode(details: Protocol.Audits.CorsIssueDetails): IssueCode {
|
|
|
94
94
|
case Protocol.Network.CorsError.LocalNetworkAccessPermissionDenied:
|
|
95
95
|
return IssueCode.LOCAL_NETWORK_ACCESS_PERMISSION_DENIED;
|
|
96
96
|
}
|
|
97
|
-
// TODO(b/394636065): Remove this once browser protocol has rolled, as we
|
|
98
|
-
// will never hit this case.
|
|
99
|
-
return null as unknown as IssueCode;
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
export class CorsIssue extends Issue<Protocol.Audits.CorsIssueDetails, IssueCode> {
|
|
@@ -6044,6 +6044,11 @@ export const NativeFunctions = [
|
|
|
6044
6044
|
signatures: [["r"]],
|
|
6045
6045
|
receivers: ["FetchEvent"]
|
|
6046
6046
|
},
|
|
6047
|
+
{
|
|
6048
|
+
name: "respondWith",
|
|
6049
|
+
signatures: [["agentResponse"]],
|
|
6050
|
+
receivers: ["SubmitEvent"]
|
|
6051
|
+
},
|
|
6047
6052
|
{
|
|
6048
6053
|
name: "respondWith",
|
|
6049
6054
|
signatures: [["paymentAbortedResponse"]],
|
|
@@ -62,7 +62,29 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
62
62
|
|
|
63
63
|
private constructor() {
|
|
64
64
|
super();
|
|
65
|
-
SDK.TargetManager.TargetManager.instance()
|
|
65
|
+
const targetManager = SDK.TargetManager.TargetManager.instance();
|
|
66
|
+
targetManager.observeTargets(this, {scoped: true});
|
|
67
|
+
// Listen for target info changes to detect prerender activation.
|
|
68
|
+
// Scoped observers don't receive events when a prerendered target becomes
|
|
69
|
+
// primary because setScopeTarget() isn't called during that transition.
|
|
70
|
+
targetManager.addEventListener(
|
|
71
|
+
SDK.TargetManager.Events.AVAILABLE_TARGETS_CHANGED, this.#onAvailableTargetsChanged, this);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#onAvailableTargetsChanged(): void {
|
|
75
|
+
const primaryTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
76
|
+
if (primaryTarget && primaryTarget !== this.#target) {
|
|
77
|
+
// Primary target changed (e.g., prerender activation). Switch to it.
|
|
78
|
+
void this.#switchToTarget(primaryTarget);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async #switchToTarget(newTarget: SDK.Target.Target): Promise<void> {
|
|
83
|
+
if (this.#target) {
|
|
84
|
+
await this.disable();
|
|
85
|
+
}
|
|
86
|
+
this.#target = newTarget;
|
|
87
|
+
await this.enable();
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
static instance(opts: {forceNew?: boolean} = {forceNew: false}): LiveMetrics {
|
|
@@ -371,31 +393,6 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
371
393
|
this.#sendStatusUpdate();
|
|
372
394
|
}
|
|
373
395
|
|
|
374
|
-
async #getFrameForExecutionContextId(executionContextId: Protocol.Runtime.ExecutionContextId):
|
|
375
|
-
Promise<SDK.ResourceTreeModel.ResourceTreeFrame|null> {
|
|
376
|
-
if (!this.#target) {
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const runtimeModel = this.#target.model(SDK.RuntimeModel.RuntimeModel);
|
|
381
|
-
if (!runtimeModel) {
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const executionContext = runtimeModel.executionContext(executionContextId);
|
|
386
|
-
if (!executionContext) {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const frameId = executionContext.frameId;
|
|
391
|
-
if (!frameId) {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const frameManager = SDK.FrameManager.FrameManager.instance();
|
|
396
|
-
return await frameManager.getOrWaitForFrame(frameId);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
396
|
async #onBindingCalled(event: {data: Protocol.Runtime.BindingCalledEvent}): Promise<void> {
|
|
400
397
|
const {data} = event;
|
|
401
398
|
if (data.name !== Spec.EVENT_BINDING_NAME) {
|
|
@@ -407,22 +404,8 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
407
404
|
await this.#mutex.run(async () => {
|
|
408
405
|
const webVitalsEvent = JSON.parse(data.payload) as Spec.WebVitalsEvent;
|
|
409
406
|
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
// know that they are for the primary frame.
|
|
413
|
-
if (this.#lastResetContextId !== data.executionContextId) {
|
|
414
|
-
if (webVitalsEvent.name !== 'reset') {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// We should avoid calling this function for every event.
|
|
419
|
-
// If an interaction triggers a pre-rendered navigation then the old primary frame could
|
|
420
|
-
// be removed before we reach this point, and then it will hang forever.
|
|
421
|
-
const frame = await this.#getFrameForExecutionContextId(data.executionContextId);
|
|
422
|
-
if (!frame?.isPrimaryFrame()) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
407
|
+
// Track the execution context from 'reset' events for logInteractionScripts().
|
|
408
|
+
if (webVitalsEvent.name === 'reset') {
|
|
426
409
|
this.#lastResetContextId = data.executionContextId;
|
|
427
410
|
}
|
|
428
411
|
|
|
@@ -464,6 +447,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
464
447
|
}
|
|
465
448
|
|
|
466
449
|
async targetAdded(target: SDK.Target.Target): Promise<void> {
|
|
450
|
+
// Scoped observers can also receive events for OOPIFs and workers.
|
|
467
451
|
if (target !== SDK.TargetManager.TargetManager.instance().primaryPageTarget()) {
|
|
468
452
|
return;
|
|
469
453
|
}
|
|
@@ -472,20 +456,12 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
472
456
|
}
|
|
473
457
|
|
|
474
458
|
async targetRemoved(target: SDK.Target.Target): Promise<void> {
|
|
459
|
+
// Scoped observers can also receive events for OOPIFs and workers.
|
|
475
460
|
if (target !== this.#target) {
|
|
476
461
|
return;
|
|
477
462
|
}
|
|
478
463
|
await this.disable();
|
|
479
464
|
this.#target = undefined;
|
|
480
|
-
|
|
481
|
-
// If the user navigates to a page that was pre-rendered then the primary page target
|
|
482
|
-
// will be swapped and the old target will be removed. We should ensure live metrics
|
|
483
|
-
// remain enabled on the new primary page target.
|
|
484
|
-
const primaryPageTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
485
|
-
if (primaryPageTarget) {
|
|
486
|
-
this.#target = primaryPageTarget;
|
|
487
|
-
await this.enable();
|
|
488
|
-
}
|
|
489
465
|
}
|
|
490
466
|
|
|
491
467
|
async enable(): Promise<void> {
|
|
@@ -555,6 +531,10 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
555
531
|
return;
|
|
556
532
|
}
|
|
557
533
|
|
|
534
|
+
// Reset to ensure clean state when re-enabling on a new target.
|
|
535
|
+
// See crbug.com/478832430.
|
|
536
|
+
this.#lastResetContextId = undefined;
|
|
537
|
+
|
|
558
538
|
await this.#killAllLiveMetricContexts();
|
|
559
539
|
|
|
560
540
|
const runtimeModel = this.#target.model(SDK.RuntimeModel.RuntimeModel);
|
|
@@ -62,3 +62,44 @@ export const enum Events {
|
|
|
62
62
|
export interface EventTypes {
|
|
63
63
|
[Events.UPDATED]: void;
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A small wrapper around a DebuggableFrame usable as a UI.Context flavor.
|
|
68
|
+
* This is necessary as Frame and DebuggableFrame are updated in place, but
|
|
69
|
+
* for UI.Context we need a new instance.
|
|
70
|
+
*/
|
|
71
|
+
export class DebuggableFrameFlavor implements DebuggableFrame {
|
|
72
|
+
static #last?: DebuggableFrameFlavor;
|
|
73
|
+
|
|
74
|
+
readonly url?: string;
|
|
75
|
+
readonly uiSourceCode?: Workspace.UISourceCode.UISourceCode;
|
|
76
|
+
readonly name?: string;
|
|
77
|
+
readonly line: number;
|
|
78
|
+
readonly column: number;
|
|
79
|
+
readonly missingDebugInfo?: MissingDebugInfo;
|
|
80
|
+
readonly sdkFrame: SDK.DebuggerModel.CallFrame;
|
|
81
|
+
|
|
82
|
+
/** Use the static {@link for}. Only public to satisfy the `setFlavor` Ctor type */
|
|
83
|
+
constructor(frame: DebuggableFrame) {
|
|
84
|
+
this.url = frame.url;
|
|
85
|
+
this.uiSourceCode = frame.uiSourceCode;
|
|
86
|
+
this.name = frame.name;
|
|
87
|
+
this.line = frame.line;
|
|
88
|
+
this.column = frame.column;
|
|
89
|
+
this.missingDebugInfo = frame.missingDebugInfo;
|
|
90
|
+
this.sdkFrame = frame.sdkFrame;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @returns the same instance of DebuggableFrameFlavor for repeated calls with the same (i.e. deep equal) DebuggableFrame */
|
|
94
|
+
static for(frame: DebuggableFrame): DebuggableFrameFlavor {
|
|
95
|
+
function equals(a: DebuggableFrame, b: DebuggableFrame): boolean {
|
|
96
|
+
return a.url === b.url && a.uiSourceCode === b.uiSourceCode && a.name === b.name && a.line === b.line &&
|
|
97
|
+
a.column === b.column && a.sdkFrame === b.sdkFrame;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!DebuggableFrameFlavor.#last || !equals(DebuggableFrameFlavor.#last, frame)) {
|
|
101
|
+
DebuggableFrameFlavor.#last = new DebuggableFrameFlavor(frame);
|
|
102
|
+
}
|
|
103
|
+
return DebuggableFrameFlavor.#last;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -44,10 +44,6 @@ import {MarkdownRendererWithCodeBlock} from './components/MarkdownRendererWithCo
|
|
|
44
44
|
import {PerformanceAgentMarkdownRenderer} from './components/PerformanceAgentMarkdownRenderer.js';
|
|
45
45
|
import {isAiAssistancePatchingEnabled} from './PatchWidget.js';
|
|
46
46
|
|
|
47
|
-
// FIXME: this export is temporary to avoid rewriting tests.
|
|
48
|
-
export {ChatMessageEntity} from './components/ChatMessage.js';
|
|
49
|
-
export type {AnswerPart, ModelChatMessage, StepPart} from './components/ChatMessage.js';
|
|
50
|
-
|
|
51
47
|
const {html} = Lit;
|
|
52
48
|
|
|
53
49
|
const AI_ASSISTANCE_SEND_FEEDBACK = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
|
|
@@ -164,6 +160,10 @@ const UIStringsNotTranslate = {
|
|
|
164
160
|
*@description Placeholder text for the chat UI input.
|
|
165
161
|
*/
|
|
166
162
|
inputPlaceholderForPerformanceTraceNoContext: 'Record or select a performance trace to ask a question',
|
|
163
|
+
/**
|
|
164
|
+
*@description Placeholder text for the chat UI input.
|
|
165
|
+
*/
|
|
166
|
+
inputPlaceholderForNoContext: 'Ask AI Assistance',
|
|
167
167
|
/**
|
|
168
168
|
* @description Disclaimer text right after the chat input.
|
|
169
169
|
*/
|
|
@@ -204,6 +204,7 @@ const UIStringsNotTranslate = {
|
|
|
204
204
|
*/
|
|
205
205
|
inputDisclaimerForPerformanceEnterpriseNoLogging:
|
|
206
206
|
'Chat messages and data from your performance trace are sent to Google. The content you submit and that is generated by this feature will not be used to improve Google’s AI models. This is an experimental AI feature and won’t always get it right.',
|
|
207
|
+
|
|
207
208
|
} as const;
|
|
208
209
|
|
|
209
210
|
const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/AiAssistancePanel.ts', UIStrings);
|
|
@@ -259,6 +260,14 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
|
|
|
259
260
|
];
|
|
260
261
|
}
|
|
261
262
|
|
|
263
|
+
case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE: {
|
|
264
|
+
return [
|
|
265
|
+
{title: 'How can I use DevTools to debug?', jslogContext: 'empty'},
|
|
266
|
+
{title: 'What performance issues exist with my page?', jslogContext: 'empty'},
|
|
267
|
+
{title: 'What are the slowest requests on this page?', jslogContext: 'empty'},
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
|
|
262
271
|
default:
|
|
263
272
|
Platform.assertNever(conversation.type, 'Unknown conversation type');
|
|
264
273
|
}
|
|
@@ -395,7 +404,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
|
|
|
395
404
|
return html`
|
|
396
405
|
<devtools-ai-chat-view
|
|
397
406
|
.props=${input.props}
|
|
398
|
-
${Lit.Directives.ref(
|
|
407
|
+
${Lit.Directives.ref(el => {
|
|
399
408
|
if (!el || !(el instanceof ChatView)) {
|
|
400
409
|
return;
|
|
401
410
|
}
|
|
@@ -490,6 +499,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
490
499
|
#userInfo: {
|
|
491
500
|
accountImage?: string,
|
|
492
501
|
accountFullName?: string,
|
|
502
|
+
accountGivenName?: string,
|
|
493
503
|
};
|
|
494
504
|
#timelinePanelInstance: TimelinePanel.TimelinePanel.TimelinePanel|null = null;
|
|
495
505
|
#runAbortController = new AbortController();
|
|
@@ -509,6 +519,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
509
519
|
this.#userInfo = {
|
|
510
520
|
accountImage: syncInfo.accountImage,
|
|
511
521
|
accountFullName: syncInfo.accountFullName,
|
|
522
|
+
accountGivenName: syncInfo.accountGivenName,
|
|
512
523
|
};
|
|
513
524
|
|
|
514
525
|
if (UI.ActionRegistry.ActionRegistry.instance().hasAction('elements.toggle-element-search')) {
|
|
@@ -658,6 +669,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
658
669
|
targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE;
|
|
659
670
|
}
|
|
660
671
|
|
|
672
|
+
if (isAiAssistanceContextSelectionAgentEnabled() && !targetConversationType) {
|
|
673
|
+
return AiAssistanceModel.AiHistoryStorage.ConversationType.NONE;
|
|
674
|
+
}
|
|
675
|
+
|
|
661
676
|
return targetConversationType;
|
|
662
677
|
}
|
|
663
678
|
|
|
@@ -680,6 +695,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
680
695
|
new AiAssistanceModel.AiConversation.AiConversation(
|
|
681
696
|
targetConversationType, [], undefined, false, this.#aidaClient, this.#changeManager) :
|
|
682
697
|
undefined;
|
|
698
|
+
|
|
683
699
|
this.#updateConversationState(conversation);
|
|
684
700
|
}
|
|
685
701
|
|
|
@@ -807,6 +823,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
807
823
|
this.#userInfo = {
|
|
808
824
|
accountImage: syncInfo.accountImage,
|
|
809
825
|
accountFullName: syncInfo.accountFullName,
|
|
826
|
+
accountGivenName: syncInfo.accountGivenName,
|
|
810
827
|
};
|
|
811
828
|
this.requestUpdate();
|
|
812
829
|
}
|
|
@@ -930,7 +947,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
930
947
|
return true;
|
|
931
948
|
}
|
|
932
949
|
|
|
933
|
-
if (!this.#conversation.selectedContext) {
|
|
950
|
+
if (!this.#conversation.selectedContext && !isAiAssistanceContextSelectionAgentEnabled()) {
|
|
934
951
|
return true;
|
|
935
952
|
}
|
|
936
953
|
|
|
@@ -982,6 +999,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
982
999
|
|
|
983
1000
|
return lockedString(UIStringsNotTranslate.inputPlaceholderForPerformanceWithNoRecording);
|
|
984
1001
|
}
|
|
1002
|
+
case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
|
|
1003
|
+
return lockedString(UIStringsNotTranslate.inputPlaceholderForNoContext);
|
|
985
1004
|
}
|
|
986
1005
|
}
|
|
987
1006
|
|
|
@@ -1016,6 +1035,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1016
1035
|
return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformanceEnterpriseNoLogging);
|
|
1017
1036
|
}
|
|
1018
1037
|
return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformance);
|
|
1038
|
+
|
|
1039
|
+
case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
|
|
1040
|
+
return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformance);
|
|
1019
1041
|
}
|
|
1020
1042
|
}
|
|
1021
1043
|
|
|
@@ -1249,9 +1271,41 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1249
1271
|
return this.#selectedRequest;
|
|
1250
1272
|
case AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE:
|
|
1251
1273
|
return this.#selectedPerformanceTrace;
|
|
1274
|
+
case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
|
|
1275
|
+
return null;
|
|
1252
1276
|
}
|
|
1253
1277
|
}
|
|
1254
1278
|
|
|
1279
|
+
#handleConversationContextChange = (data: unknown): void => {
|
|
1280
|
+
if (data instanceof Workspace.UISourceCode.UISourceCode) {
|
|
1281
|
+
if (this.#selectedFile?.getItem() === data) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
this.#selectedFile = new AiAssistanceModel.FileAgent.FileContext(data);
|
|
1285
|
+
|
|
1286
|
+
} else if (data instanceof SDK.DOMModel.DOMNode) {
|
|
1287
|
+
if (this.#selectedElement?.getItem() === data ||
|
|
1288
|
+
// Ignore non node type like comments or html tags
|
|
1289
|
+
data.nodeType() === Node.ELEMENT_NODE) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
this.#selectedElement = new AiAssistanceModel.StylingAgent.NodeContext(data);
|
|
1293
|
+
} else if (data instanceof SDK.NetworkRequest.NetworkRequest) {
|
|
1294
|
+
if (this.#selectedRequest?.getItem() === data) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const calculator = NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator();
|
|
1298
|
+
this.#selectedRequest = new AiAssistanceModel.NetworkAgent.RequestContext(data, calculator);
|
|
1299
|
+
} else if (data instanceof AiAssistanceModel.AIContext.AgentFocus) {
|
|
1300
|
+
if (this.#selectedPerformanceTrace?.getItem() === data) {
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
this.#selectedPerformanceTrace = new AiAssistanceModel.PerformanceAgent.PerformanceTraceContext(data);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
this.#updateConversationState(this.#conversation);
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1255
1309
|
async #startConversation(
|
|
1256
1310
|
text: string,
|
|
1257
1311
|
imageInput?: Host.AidaClient.Part,
|
|
@@ -1453,6 +1507,14 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1453
1507
|
}
|
|
1454
1508
|
break;
|
|
1455
1509
|
}
|
|
1510
|
+
|
|
1511
|
+
case AiAssistanceModel.AiAgent.ResponseType.CONTEXT_CHANGE: {
|
|
1512
|
+
this.#handleConversationContextChange(data.context);
|
|
1513
|
+
step.isLoading = false;
|
|
1514
|
+
commitStep();
|
|
1515
|
+
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1456
1518
|
}
|
|
1457
1519
|
|
|
1458
1520
|
// Commit update intermediate step when not
|
|
@@ -1576,6 +1638,10 @@ function isAiAssistanceMultimodalInputEnabled(): boolean {
|
|
|
1576
1638
|
return Boolean(Root.Runtime.hostConfig.devToolsFreestyler?.multimodal);
|
|
1577
1639
|
}
|
|
1578
1640
|
|
|
1641
|
+
function isAiAssistanceContextSelectionAgentEnabled(): boolean {
|
|
1642
|
+
return Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceContextSelectionAgent?.enabled);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1579
1645
|
function isAiAssistanceServerSideLoggingEnabled(): boolean {
|
|
1580
1646
|
return !Root.Runtime.hostConfig.aidaAvailability?.disallowLogging;
|
|
1581
1647
|
}
|
|
@@ -174,6 +174,7 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
174
174
|
return new AiAssistance.ActionDelegate();
|
|
175
175
|
},
|
|
176
176
|
condition: config => isAnyFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
|
|
177
|
+
featurePromotionId: 'debug-with-ai',
|
|
177
178
|
});
|
|
178
179
|
|
|
179
180
|
UI.ActionRegistration.registerActionExtension({
|
|
@@ -135,6 +135,8 @@ export interface ViewInput {
|
|
|
135
135
|
onRemoveImageInput: () => void;
|
|
136
136
|
onImageUpload: (ev: Event) => void;
|
|
137
137
|
onImagePaste: (event: ClipboardEvent) => void;
|
|
138
|
+
onImageDragOver: (event: DragEvent) => void;
|
|
139
|
+
onImageDrop: (event: DragEvent) => void;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
export type ViewOutput = undefined;
|
|
@@ -289,6 +291,8 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
289
291
|
maxlength="10000"
|
|
290
292
|
@keydown=${input.onTextAreaKeyDown}
|
|
291
293
|
@paste=${input.onImagePaste}
|
|
294
|
+
@dragover=${input.onImageDragOver}
|
|
295
|
+
@drop=${input.onImageDrop}
|
|
292
296
|
@input=${(event: KeyboardEvent) => {
|
|
293
297
|
input.onTextInputChange((event.target as HTMLInputElement).value);
|
|
294
298
|
}}
|
|
@@ -550,12 +554,12 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
550
554
|
});
|
|
551
555
|
}
|
|
552
556
|
|
|
553
|
-
#
|
|
557
|
+
#handleImageDataTransferEvent(dataTransfer: DataTransfer|null, event: Event): void {
|
|
554
558
|
if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
|
|
555
559
|
return;
|
|
556
560
|
}
|
|
557
561
|
|
|
558
|
-
const files =
|
|
562
|
+
const files = dataTransfer?.files;
|
|
559
563
|
if (!files || files.length === 0) {
|
|
560
564
|
return;
|
|
561
565
|
}
|
|
@@ -567,6 +571,22 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
567
571
|
|
|
568
572
|
event.preventDefault();
|
|
569
573
|
void this.#handleLoadImage(imageFile);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#handleImagePaste = (event: ClipboardEvent): void => {
|
|
577
|
+
this.#handleImageDataTransferEvent(event.clipboardData, event);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
#handleImageDragOver = (event: DragEvent): void => {
|
|
581
|
+
if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
event.preventDefault();
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
#handleImageDrop = (event: DragEvent): void => {
|
|
589
|
+
this.#handleImageDataTransferEvent(event.dataTransfer, event);
|
|
570
590
|
};
|
|
571
591
|
|
|
572
592
|
async #handleLoadImage(file: File): Promise<void> {
|
|
@@ -663,6 +683,8 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
663
683
|
onTextAreaKeyDown: this.onTextAreaKeyDown,
|
|
664
684
|
onCancel: this.onCancel,
|
|
665
685
|
onImageUpload: this.onImageUpload,
|
|
686
|
+
onImageDragOver: this.#handleImageDragOver,
|
|
687
|
+
onImageDrop: this.#handleImageDrop,
|
|
666
688
|
},
|
|
667
689
|
undefined, this.contentElement);
|
|
668
690
|
}
|