chrome-devtools-frontend 1.0.1562885 → 1.0.1563377
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/AUTHORS +1 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +46 -10
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +2 -190
- package/front_end/models/ai_code_generation/AiCodeGeneration.ts +13 -5
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -17
- package/front_end/panels/ai_assistance/components/ChatInput.ts +22 -0
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +39 -5
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -0
- package/front_end/panels/common/AiCodeGenerationTeaser.ts +211 -15
- package/front_end/panels/common/aiCodeGenerationTeaser.css +64 -0
- package/front_end/panels/console/ConsoleView.ts +2 -1
- package/front_end/panels/event_listeners/EventListenersView.ts +2 -1
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -2
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +26 -8
- package/front_end/ui/components/text_editor/AiCodeCompletionTeaserPlaceholder.ts +4 -0
- package/front_end/ui/components/text_editor/AiCodeGenerationParser.ts +77 -0
- package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +77 -40
- package/front_end/ui/components/text_editor/text_editor.ts +1 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +3 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
package/AUTHORS
CHANGED
|
@@ -36,6 +36,7 @@ Douglas Chiang <douglasdothnc@gmail.com>
|
|
|
36
36
|
Dragonish <no.web.developer@outlook.com>
|
|
37
37
|
Eden Wang <nedenwang@gmail.com>
|
|
38
38
|
Edward Trist <edwardtrist@gmail.com>
|
|
39
|
+
Emir D <emrd434@gmail.com>
|
|
39
40
|
Ergün Erdoğmuş <erdogmusergun@gmail.com>
|
|
40
41
|
Eric Rannaud <eric.rannaud@gmail.com>
|
|
41
42
|
Faisal Salman <fyzlman@gmail.com>
|
|
@@ -649,12 +649,13 @@ export class CSSMatchedStyles {
|
|
|
649
649
|
// DOMInheritanceCascades.
|
|
650
650
|
for (const [pseudoType, nodeCascade] of pseudoCascades.entries()) {
|
|
651
651
|
pseudoInheritanceCascades.set(
|
|
652
|
-
pseudoType, new DOMInheritanceCascade(this, nodeCascade, this.#registeredProperties));
|
|
652
|
+
pseudoType, new DOMInheritanceCascade(this, nodeCascade, this.#registeredProperties, this.#mainDOMCascade));
|
|
653
653
|
}
|
|
654
654
|
|
|
655
655
|
for (const [highlightName, nodeCascade] of customHighlightPseudoCascades.entries()) {
|
|
656
656
|
customHighlightPseudoInheritanceCascades.set(
|
|
657
|
-
highlightName,
|
|
657
|
+
highlightName,
|
|
658
|
+
new DOMInheritanceCascade(this, nodeCascade, this.#registeredProperties, this.#mainDOMCascade));
|
|
658
659
|
}
|
|
659
660
|
|
|
660
661
|
return [pseudoInheritanceCascades, customHighlightPseudoInheritanceCascades];
|
|
@@ -959,17 +960,15 @@ class NodeCascade {
|
|
|
959
960
|
#matchedStyles: CSSMatchedStyles;
|
|
960
961
|
readonly styles: CSSStyleDeclaration[];
|
|
961
962
|
readonly #isInherited: boolean;
|
|
962
|
-
readonly #isHighlightPseudoCascade: boolean;
|
|
963
963
|
readonly propertiesState = new Map<CSSProperty, PropertyState>();
|
|
964
964
|
readonly activeProperties = new Map<string, CSSProperty>();
|
|
965
965
|
readonly #node: DOMNode;
|
|
966
966
|
constructor(
|
|
967
967
|
matchedStyles: CSSMatchedStyles, styles: CSSStyleDeclaration[], node: DOMNode, isInherited: boolean,
|
|
968
|
-
isHighlightPseudoCascade = false) {
|
|
968
|
+
readonly isHighlightPseudoCascade = false) {
|
|
969
969
|
this.#matchedStyles = matchedStyles;
|
|
970
970
|
this.styles = styles;
|
|
971
971
|
this.#isInherited = isInherited;
|
|
972
|
-
this.#isHighlightPseudoCascade = isHighlightPseudoCascade;
|
|
973
972
|
this.#node = node;
|
|
974
973
|
}
|
|
975
974
|
|
|
@@ -992,9 +991,16 @@ class NodeCascade {
|
|
|
992
991
|
// Do not pick non-inherited properties from inherited styles.
|
|
993
992
|
const metadata = cssMetadata();
|
|
994
993
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
994
|
+
if (this.#isInherited) {
|
|
995
|
+
if (this.isHighlightPseudoCascade) {
|
|
996
|
+
// All properties are inherited for highlight pseudos, but custom
|
|
997
|
+
// variables do not come from the inherited pseudo elements.
|
|
998
|
+
if (property.name.startsWith('--')) {
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
} else if (!metadata.isPropertyInherited(property.name)) {
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
998
1004
|
}
|
|
999
1005
|
|
|
1000
1006
|
// When a property does not have a range in an otherwise ranged CSSStyleDeclaration,
|
|
@@ -1085,6 +1091,7 @@ class NodeCascade {
|
|
|
1085
1091
|
function isRegular(declaration: CSSProperty|CSSRegisteredProperty): declaration is CSSProperty {
|
|
1086
1092
|
return 'ownerStyle' in declaration;
|
|
1087
1093
|
}
|
|
1094
|
+
|
|
1088
1095
|
export class CSSValueSource {
|
|
1089
1096
|
readonly declaration: CSSProperty|CSSRegisteredProperty;
|
|
1090
1097
|
constructor(declaration: CSSProperty|CSSRegisteredProperty) {
|
|
@@ -1176,15 +1183,27 @@ class DOMInheritanceCascade {
|
|
|
1176
1183
|
readonly #nodeCascades: NodeCascade[];
|
|
1177
1184
|
#registeredProperties: CSSRegisteredProperty[];
|
|
1178
1185
|
readonly #matchedStyles: CSSMatchedStyles;
|
|
1186
|
+
readonly #fallbackCascade: DOMInheritanceCascade|null = null;
|
|
1187
|
+
readonly #styles: CSSStyleDeclaration[] = [];
|
|
1179
1188
|
constructor(
|
|
1180
|
-
matchedStyles: CSSMatchedStyles, nodeCascades: NodeCascade[], registeredProperties: CSSRegisteredProperty[]
|
|
1189
|
+
matchedStyles: CSSMatchedStyles, nodeCascades: NodeCascade[], registeredProperties: CSSRegisteredProperty[],
|
|
1190
|
+
fallbackCascade: DOMInheritanceCascade|null = null) {
|
|
1181
1191
|
this.#nodeCascades = nodeCascades;
|
|
1182
1192
|
this.#matchedStyles = matchedStyles;
|
|
1183
1193
|
this.#registeredProperties = registeredProperties;
|
|
1194
|
+
this.#fallbackCascade = fallbackCascade;
|
|
1184
1195
|
|
|
1185
1196
|
for (const nodeCascade of nodeCascades) {
|
|
1186
1197
|
for (const style of nodeCascade.styles) {
|
|
1187
1198
|
this.#styleToNodeCascade.set(style, nodeCascade);
|
|
1199
|
+
this.#styles.push(style);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (fallbackCascade) {
|
|
1203
|
+
for (const [style, nodeCascade] of fallbackCascade.#styleToNodeCascade) {
|
|
1204
|
+
if (!this.#styles.includes(style)) {
|
|
1205
|
+
this.#styleToNodeCascade.set(style, nodeCascade);
|
|
1206
|
+
}
|
|
1188
1207
|
}
|
|
1189
1208
|
}
|
|
1190
1209
|
}
|
|
@@ -1248,6 +1267,9 @@ class DOMInheritanceCascade {
|
|
|
1248
1267
|
}
|
|
1249
1268
|
}
|
|
1250
1269
|
}
|
|
1270
|
+
if (this.#fallbackCascade && (!nodeCascade.isHighlightPseudoCascade || property.name.startsWith('--'))) {
|
|
1271
|
+
return this.#fallbackCascade.resolveProperty(property.name, property.ownerStyle);
|
|
1272
|
+
}
|
|
1251
1273
|
return null;
|
|
1252
1274
|
}
|
|
1253
1275
|
|
|
@@ -1538,7 +1560,7 @@ class DOMInheritanceCascade {
|
|
|
1538
1560
|
}
|
|
1539
1561
|
|
|
1540
1562
|
styles(): CSSStyleDeclaration[] {
|
|
1541
|
-
return
|
|
1563
|
+
return this.#styles;
|
|
1542
1564
|
}
|
|
1543
1565
|
|
|
1544
1566
|
propertyState(property: CSSProperty): PropertyState|null {
|
|
@@ -1610,6 +1632,20 @@ class DOMInheritanceCascade {
|
|
|
1610
1632
|
rule.propertyName(),
|
|
1611
1633
|
initialValue !== null ? {value: initialValue, declaration: new CSSValueSource(rule)} : null);
|
|
1612
1634
|
}
|
|
1635
|
+
if (this.#fallbackCascade) {
|
|
1636
|
+
this.#fallbackCascade.ensureInitialized();
|
|
1637
|
+
for (const [cascade, available] of this.#fallbackCascade.#availableCSSVariables) {
|
|
1638
|
+
this.#availableCSSVariables.set(cascade, available);
|
|
1639
|
+
}
|
|
1640
|
+
for (const [cascade, computed] of this.#fallbackCascade.#computedCSSVariables) {
|
|
1641
|
+
this.#computedCSSVariables.set(cascade, computed);
|
|
1642
|
+
}
|
|
1643
|
+
for (const [key, value] of this.#fallbackCascade.#availableCSSVariables.get(
|
|
1644
|
+
this.#fallbackCascade.#nodeCascades[0]) ??
|
|
1645
|
+
[]) {
|
|
1646
|
+
accumulatedCSSVariables.set(key, value);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1613
1649
|
for (let i = this.#nodeCascades.length - 1; i >= 0; --i) {
|
|
1614
1650
|
const nodeCascade = this.#nodeCascades[i];
|
|
1615
1651
|
const variableNames = [];
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
-
import * as Common from '../../core/common/common.js';
|
|
6
5
|
import * as Host from '../../core/host/host.js';
|
|
7
6
|
import * as Root from '../../core/root/root.js';
|
|
8
7
|
|
|
9
8
|
import {debugLog} from './debug.js';
|
|
10
9
|
|
|
11
|
-
export const DELAY_BEFORE_SHOWING_RESPONSE_MS = 500;
|
|
12
|
-
export const AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS = 200;
|
|
13
|
-
|
|
14
10
|
/**
|
|
15
11
|
* TODO(b/404796739): Remove these definitions of AgentOptions and RequestOptions and
|
|
16
12
|
* use the existing ones which are used for AI assistance panel agents.
|
|
@@ -140,20 +136,9 @@ const console = {
|
|
|
140
136
|
|
|
141
137
|
/**
|
|
142
138
|
* The AiCodeCompletion class is responsible for fetching code completion suggestions
|
|
143
|
-
* from the AIDA backend
|
|
144
|
-
*
|
|
145
|
-
* 1. **Debouncing requests:** As the user types, we don't want to send a request
|
|
146
|
-
* for every keystroke. Instead, we use debouncing to schedule a request
|
|
147
|
-
* only after the user has paused typing for a short period
|
|
148
|
-
* (AIDA_REQUEST_THROTTLER_TIMEOUT_MS). This prevents spamming the backend with
|
|
149
|
-
* requests for intermediate typing states.
|
|
150
|
-
*
|
|
151
|
-
* 2. **Delaying suggestions:** When a suggestion is received from the AIDA
|
|
152
|
-
* backend, we don't show it immediately. There is a minimum delay
|
|
153
|
-
* (DELAY_BEFORE_SHOWING_RESPONSE_MS) from when the request was sent to when
|
|
154
|
-
* the suggestion is displayed.
|
|
139
|
+
* from the AIDA backend.
|
|
155
140
|
*/
|
|
156
|
-
export class AiCodeCompletion
|
|
141
|
+
export class AiCodeCompletion {
|
|
157
142
|
#stopSequences: string[];
|
|
158
143
|
#renderingTimeout?: number;
|
|
159
144
|
#aidaRequestCache?: CachedRequest;
|
|
@@ -166,7 +151,6 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
|
|
|
166
151
|
readonly #serverSideLoggingEnabled: boolean;
|
|
167
152
|
|
|
168
153
|
constructor(opts: AgentOptions, panel: ContextFlavor, callbacks?: Callbacks, stopSequences?: string[]) {
|
|
169
|
-
super();
|
|
170
154
|
this.#aidaClient = opts.aidaClient;
|
|
171
155
|
this.#serverSideLoggingEnabled = opts.serverSideLoggingEnabled ?? false;
|
|
172
156
|
this.#panel = panel;
|
|
@@ -174,14 +158,6 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
|
|
|
174
158
|
this.#callbacks = callbacks;
|
|
175
159
|
}
|
|
176
160
|
|
|
177
|
-
#debouncedRequestAidaSuggestion = Common.Debouncer.debounce(
|
|
178
|
-
(prefix: string, suffix: string, cursorPositionAtRequest: number,
|
|
179
|
-
inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage) => {
|
|
180
|
-
void this.#requestAidaSuggestion(
|
|
181
|
-
this.#buildRequest(prefix, suffix, inferenceLanguage), cursorPositionAtRequest);
|
|
182
|
-
},
|
|
183
|
-
AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS);
|
|
184
|
-
|
|
185
161
|
#buildRequest(
|
|
186
162
|
prefix: string, suffix: string,
|
|
187
163
|
inferenceLanguage: Host.AidaClient.AidaInferenceLanguage = Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT,
|
|
@@ -246,125 +222,6 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
|
|
|
246
222
|
};
|
|
247
223
|
}
|
|
248
224
|
|
|
249
|
-
#pickSampleFromResponse(response: Host.AidaClient.CompletionResponse): Host.AidaClient.GenerationSample|null {
|
|
250
|
-
if (!response.generatedSamples.length) {
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// `currentHint` is the portion of a standard autocomplete suggestion that the user has not yet typed.
|
|
255
|
-
// For example, if the user types `document.queryS` and the autocomplete suggests `document.querySelector`,
|
|
256
|
-
// the `currentHint` is `elector`.
|
|
257
|
-
const currentHintInMenu = this.#callbacks?.getCompletionHint();
|
|
258
|
-
// TODO(ergunsh): We should not do this check here. Instead, the AI code suggestions should be provided
|
|
259
|
-
// as it is to the view plugin. The view plugin should choose which one to use based on the completion hint
|
|
260
|
-
// and selected completion.
|
|
261
|
-
if (!currentHintInMenu) {
|
|
262
|
-
return response.generatedSamples[0];
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// TODO(ergunsh): This does not handle looking for `selectedCompletion`. The `currentHint` is `null`
|
|
266
|
-
// for the Sources panel case.
|
|
267
|
-
// Even though there is no match, we still return the first suggestion which will be displayed
|
|
268
|
-
// when the traditional autocomplete menu is closed.
|
|
269
|
-
return response.generatedSamples.find(sample => sample.generationString.startsWith(currentHintInMenu)) ??
|
|
270
|
-
response.generatedSamples[0];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async #generateSampleForRequest(request: Host.AidaClient.CompletionRequest, cursor: number): Promise<{
|
|
274
|
-
suggestionText: string,
|
|
275
|
-
fromCache: boolean,
|
|
276
|
-
citations: Host.AidaClient.Citation[],
|
|
277
|
-
rpcGlobalId?: Host.AidaClient.RpcGlobalId,
|
|
278
|
-
sampleId?: number,
|
|
279
|
-
}|null> {
|
|
280
|
-
const {response, fromCache} = await this.#completeCodeCached(request);
|
|
281
|
-
debugLog('At cursor position', cursor, {request, response, fromCache});
|
|
282
|
-
if (!response) {
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const suggestionSample = this.#pickSampleFromResponse(response);
|
|
287
|
-
if (!suggestionSample) {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const shouldBlock =
|
|
292
|
-
suggestionSample.attributionMetadata?.attributionAction === Host.AidaClient.RecitationAction.BLOCK;
|
|
293
|
-
if (shouldBlock) {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const isRepetitive = this.#checkIfSuggestionRepeatsExistingText(suggestionSample.generationString, request);
|
|
298
|
-
if (isRepetitive) {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const suggestionText = this.#trimSuggestionOverlap(suggestionSample.generationString, request);
|
|
303
|
-
if (suggestionText.length === 0) {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
suggestionText,
|
|
309
|
-
sampleId: suggestionSample.sampleId,
|
|
310
|
-
fromCache,
|
|
311
|
-
citations: suggestionSample.attributionMetadata?.citations ?? [],
|
|
312
|
-
rpcGlobalId: response.metadata.rpcGlobalId,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async #requestAidaSuggestion(request: Host.AidaClient.CompletionRequest, cursorPositionAtRequest: number):
|
|
317
|
-
Promise<void> {
|
|
318
|
-
const startTime = performance.now();
|
|
319
|
-
this.dispatchEventToListeners(Events.REQUEST_TRIGGERED, {});
|
|
320
|
-
// Registering AiCodeCompletionRequestTriggered metric even if the request is served from cache
|
|
321
|
-
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionRequestTriggered);
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const sampleResponse = await this.#generateSampleForRequest(request, cursorPositionAtRequest);
|
|
325
|
-
if (!sampleResponse) {
|
|
326
|
-
this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {});
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const {
|
|
331
|
-
suggestionText,
|
|
332
|
-
sampleId,
|
|
333
|
-
fromCache,
|
|
334
|
-
citations,
|
|
335
|
-
rpcGlobalId,
|
|
336
|
-
} = sampleResponse;
|
|
337
|
-
const remainingDelay = Math.max(DELAY_BEFORE_SHOWING_RESPONSE_MS - (performance.now() - startTime), 0);
|
|
338
|
-
this.#renderingTimeout = window.setTimeout(() => {
|
|
339
|
-
const currentCursorPosition = this.#callbacks?.getSelectionHead();
|
|
340
|
-
if (currentCursorPosition !== cursorPositionAtRequest) {
|
|
341
|
-
this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {});
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
this.#callbacks?.setAiAutoCompletion({
|
|
345
|
-
text: suggestionText,
|
|
346
|
-
from: cursorPositionAtRequest,
|
|
347
|
-
rpcGlobalId,
|
|
348
|
-
sampleId,
|
|
349
|
-
startTime,
|
|
350
|
-
onImpression: this.registerUserImpression.bind(this),
|
|
351
|
-
clearCachedRequest: this.clearCachedRequest.bind(this),
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
if (fromCache) {
|
|
355
|
-
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionResponseServedFromCache);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
debugLog('Suggestion dispatched to the editor', suggestionText, 'at cursor position', cursorPositionAtRequest);
|
|
359
|
-
this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {citations});
|
|
360
|
-
}, remainingDelay);
|
|
361
|
-
} catch (e) {
|
|
362
|
-
debugLog('Error while fetching code completion suggestions from AIDA', e);
|
|
363
|
-
this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {});
|
|
364
|
-
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionError);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
225
|
get #userTier(): string|undefined {
|
|
369
226
|
return Root.Runtime.hostConfig.devToolsAiCodeCompletion?.userTier;
|
|
370
227
|
}
|
|
@@ -379,30 +236,6 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
|
|
|
379
236
|
};
|
|
380
237
|
}
|
|
381
238
|
|
|
382
|
-
/**
|
|
383
|
-
* Removes the end of a suggestion if it overlaps with the start of the suffix.
|
|
384
|
-
*/
|
|
385
|
-
#trimSuggestionOverlap(generationString: string, request: Host.AidaClient.CompletionRequest): string {
|
|
386
|
-
const suffix = request.suffix;
|
|
387
|
-
if (!suffix) {
|
|
388
|
-
return generationString;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Iterate from the longest possible overlap down to the shortest
|
|
392
|
-
for (let i = Math.min(generationString.length, suffix.length); i > 0; i--) {
|
|
393
|
-
const overlapCandidate = suffix.substring(0, i);
|
|
394
|
-
if (generationString.endsWith(overlapCandidate)) {
|
|
395
|
-
return generationString.slice(0, -i);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return generationString;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
#checkIfSuggestionRepeatsExistingText(generationString: string, request: Host.AidaClient.CompletionRequest): boolean {
|
|
402
|
-
const {prefix, suffix} = request;
|
|
403
|
-
return Boolean(prefix.includes(generationString.trim()) || suffix?.includes(generationString.trim()));
|
|
404
|
-
}
|
|
405
|
-
|
|
406
239
|
#checkCachedRequestForResponse(request: Host.AidaClient.CompletionRequest): Host.AidaClient.CompletionResponse|null {
|
|
407
240
|
if (!this.#aidaRequestCache || this.#aidaRequestCache.request.suffix !== request.suffix ||
|
|
408
241
|
JSON.stringify(this.#aidaRequestCache.request.options) !== JSON.stringify(request.options)) {
|
|
@@ -476,12 +309,6 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
|
|
|
476
309
|
this.#aidaRequestCache = undefined;
|
|
477
310
|
}
|
|
478
311
|
|
|
479
|
-
onTextChanged(
|
|
480
|
-
prefix: string, suffix: string, cursorPositionAtRequest: number,
|
|
481
|
-
inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage): void {
|
|
482
|
-
this.#debouncedRequestAidaSuggestion(prefix, suffix, cursorPositionAtRequest, inferenceLanguage);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
312
|
async completeCode(
|
|
486
313
|
prefix: string, suffix: string, cursorPositionAtRequest: number,
|
|
487
314
|
inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage,
|
|
@@ -525,18 +352,3 @@ export const enum ContextFlavor {
|
|
|
525
352
|
CONSOLE = 'console', // generated code can contain console specific APIs like `$0`.
|
|
526
353
|
SOURCES = 'sources',
|
|
527
354
|
}
|
|
528
|
-
|
|
529
|
-
export const enum Events {
|
|
530
|
-
RESPONSE_RECEIVED = 'ResponseReceived',
|
|
531
|
-
REQUEST_TRIGGERED = 'RequestTriggered',
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export interface ResponseReceivedEvent {
|
|
535
|
-
citations?: Host.AidaClient.Citation[];
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
export interface EventTypes {
|
|
539
|
-
[Events.RESPONSE_RECEIVED]: ResponseReceivedEvent;
|
|
540
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
541
|
-
[Events.REQUEST_TRIGGERED]: {};
|
|
542
|
-
}
|
|
@@ -13,9 +13,15 @@ Your role is to act as an expert pair programmer within the Chrome DevTools envi
|
|
|
13
13
|
|
|
14
14
|
**Core Directives (Adhere to these strictly):**
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
1. **Language and Quality:**
|
|
17
|
+
* Generate code that is modern, efficient, and idiomatic for the inferred language (e.g., modern JavaScript/ES6+, semantic HTML5, efficient CSS).
|
|
18
|
+
* Where appropriate, include basic error handling (e.g., for API calls).
|
|
19
|
+
* Determine the programming language from the user's prompt.
|
|
20
|
+
|
|
21
|
+
2. **Output Format (Strict):**
|
|
22
|
+
* **Return ONLY code blocks.** * Do NOT include any introductory text, explanations, or concluding remarks.
|
|
23
|
+
* Do NOT provide step-by-step guides or descriptions of how the code works.
|
|
24
|
+
* Inline comments within the code are permitted and encouraged for clarity.
|
|
19
25
|
`;
|
|
20
26
|
|
|
21
27
|
export const additionalContextForConsole = `
|
|
@@ -59,6 +65,10 @@ export class AiCodeGeneration {
|
|
|
59
65
|
function validTemperature(temperature: number|undefined): number|undefined {
|
|
60
66
|
return typeof temperature === 'number' && temperature >= 0 ? temperature : undefined;
|
|
61
67
|
}
|
|
68
|
+
|
|
69
|
+
// Workaround: Combine preamble and target language into the main prompt to enforce instructions.
|
|
70
|
+
// The API and model ignores system-level instructions provided in the preamble field of the request.
|
|
71
|
+
prompt = preamble + prompt + '\n**Target Language:** ' + inferenceLanguage;
|
|
62
72
|
return {
|
|
63
73
|
client: Host.AidaClient.CLIENT_NAME,
|
|
64
74
|
preamble,
|
|
@@ -70,10 +80,8 @@ export class AiCodeGeneration {
|
|
|
70
80
|
},
|
|
71
81
|
use_case: Host.AidaClient.UseCase.CODE_GENERATION,
|
|
72
82
|
options: {
|
|
73
|
-
inference_language: inferenceLanguage,
|
|
74
83
|
temperature: validTemperature(this.#options.temperature),
|
|
75
84
|
model_id: this.#options.modelId || undefined,
|
|
76
|
-
expect_code_output: true,
|
|
77
85
|
},
|
|
78
86
|
metadata: {
|
|
79
87
|
disable_user_content_logging: !(this.#serverSideLoggingEnabled ?? false),
|
|
@@ -6663,18 +6663,6 @@ export const NativeFunctions = [
|
|
|
6663
6663
|
name: "moveBefore",
|
|
6664
6664
|
signatures: [["node","child"]]
|
|
6665
6665
|
},
|
|
6666
|
-
{
|
|
6667
|
-
name: "patchBetween",
|
|
6668
|
-
signatures: [["prev_child","next_child"]]
|
|
6669
|
-
},
|
|
6670
|
-
{
|
|
6671
|
-
name: "patchAfter",
|
|
6672
|
-
signatures: [["ref"]]
|
|
6673
|
-
},
|
|
6674
|
-
{
|
|
6675
|
-
name: "patchBefore",
|
|
6676
|
-
signatures: [["ref"]]
|
|
6677
|
-
},
|
|
6678
6666
|
{
|
|
6679
6667
|
name: "QuotaExceededError",
|
|
6680
6668
|
signatures: [["?message","?options"]]
|
|
@@ -7193,10 +7181,6 @@ export const NativeFunctions = [
|
|
|
7193
7181
|
name: "setMenuListOptionsBoundsInAXTree",
|
|
7194
7182
|
signatures: [["options_bounds","children_updated"]]
|
|
7195
7183
|
},
|
|
7196
|
-
{
|
|
7197
|
-
name: "PatchEvent",
|
|
7198
|
-
signatures: [["type","init"]]
|
|
7199
|
-
},
|
|
7200
7184
|
{
|
|
7201
7185
|
name: "allowsFeature",
|
|
7202
7186
|
signatures: [["feature","?origin"]]
|
|
@@ -9088,7 +9072,7 @@ export const NativeFunctions = [
|
|
|
9088
9072
|
},
|
|
9089
9073
|
{
|
|
9090
9074
|
name: "copyElementImageToTexture",
|
|
9091
|
-
signatures: [["source","destination"],["source","width","height","destination"]]
|
|
9075
|
+
signatures: [["source","destination"],["source","width","height","destination"],["source","sx","sy","swidth","sheight","destination"]]
|
|
9092
9076
|
},
|
|
9093
9077
|
{
|
|
9094
9078
|
name: "setIndexBuffer",
|
|
@@ -134,6 +134,7 @@ export interface ViewInput {
|
|
|
134
134
|
onTakeScreenshot: () => void;
|
|
135
135
|
onRemoveImageInput: () => void;
|
|
136
136
|
onImageUpload: (ev: Event) => void;
|
|
137
|
+
onImagePaste: (event: ClipboardEvent) => void;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
export type ViewOutput = undefined;
|
|
@@ -287,6 +288,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
287
288
|
wrap="hard"
|
|
288
289
|
maxlength="10000"
|
|
289
290
|
@keydown=${input.onTextAreaKeyDown}
|
|
291
|
+
@paste=${input.onImagePaste}
|
|
290
292
|
@input=${(event: KeyboardEvent) => {
|
|
291
293
|
input.onTextInputChange((event.target as HTMLInputElement).value);
|
|
292
294
|
}}
|
|
@@ -548,6 +550,25 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
548
550
|
});
|
|
549
551
|
}
|
|
550
552
|
|
|
553
|
+
#handleImagePaste = (event: ClipboardEvent): void => {
|
|
554
|
+
if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const files = event.clipboardData?.files;
|
|
559
|
+
if (!files || files.length === 0) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const imageFile = Array.from(files).find(file => file.type.startsWith('image/'));
|
|
564
|
+
if (!imageFile) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
event.preventDefault();
|
|
569
|
+
void this.#handleLoadImage(imageFile);
|
|
570
|
+
};
|
|
571
|
+
|
|
551
572
|
async #handleLoadImage(file: File): Promise<void> {
|
|
552
573
|
const showLoadingTimeout = setTimeout(() => {
|
|
553
574
|
this.#imageInput = {isLoading: true};
|
|
@@ -631,6 +652,7 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
631
652
|
textAreaRef: this.#textAreaRef,
|
|
632
653
|
onContextClick: this.onContextClick,
|
|
633
654
|
onInspectElementClick: this.onInspectElementClick,
|
|
655
|
+
onImagePaste: this.#handleImagePaste,
|
|
634
656
|
onNewConversation: this.onNewConversation,
|
|
635
657
|
onTextInputChange: () => {
|
|
636
658
|
this.requestUpdate();
|
|
@@ -8,6 +8,7 @@ import '../../ui/components/tooltips/tooltips.js';
|
|
|
8
8
|
import * as Host from '../../core/host/host.js';
|
|
9
9
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
10
10
|
import * as Root from '../../core/root/root.js';
|
|
11
|
+
import * as AiCodeCompletion from '../../models/ai_code_completion/ai_code_completion.js';
|
|
11
12
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
12
13
|
import {Directives, html, nothing, render} from '../../ui/lit/lit.js';
|
|
13
14
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
@@ -26,13 +27,23 @@ const UIStringsNotTranslate = {
|
|
|
26
27
|
/**
|
|
27
28
|
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code completion.
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
+
tooltipDisclaimerTextForAiCodeCompletionInConsole:
|
|
30
31
|
'To generate code suggestions, your console input and the history of your current console session are shared with Google. This data may be seen by human reviewers to improve this feature.',
|
|
31
32
|
/**
|
|
32
33
|
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code completion.
|
|
33
34
|
*/
|
|
34
|
-
|
|
35
|
-
'To generate code suggestions, your console input and the history of your current console session are shared with Google. This data will not be used to improve Google’s AI models.',
|
|
35
|
+
tooltipDisclaimerTextForAiCodeCompletionNoLoggingInConsole:
|
|
36
|
+
'To generate code suggestions, your console input and the history of your current console session are shared with Google. This data will not be used to improve Google’s AI models. Your organization may change these settings at any time.',
|
|
37
|
+
/**
|
|
38
|
+
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code generation in Sources panel.
|
|
39
|
+
*/
|
|
40
|
+
tooltipDisclaimerTextForAiCodeCompletionInSources:
|
|
41
|
+
'To generate code suggestions, the contents of the currently open file are shared with Google. This data may be seen by human reviewers to improve this feature.',
|
|
42
|
+
/**
|
|
43
|
+
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code generation in Sources panel.
|
|
44
|
+
*/
|
|
45
|
+
tooltipDisclaimerTextForAiCodeCompletionNoLoggingInSources:
|
|
46
|
+
'To generate code suggestions, the contents of the currently open file are shared with Google. This data will not be used to improve Google’s AI models. Your organization may change these settings at any time.',
|
|
36
47
|
/**
|
|
37
48
|
* Text for tooltip shown on hovering over spinner.
|
|
38
49
|
*/
|
|
@@ -49,12 +60,27 @@ const UIStringsNotTranslate = {
|
|
|
49
60
|
|
|
50
61
|
const lockedString = i18n.i18n.lockedString;
|
|
51
62
|
|
|
63
|
+
function getTooltipDisclaimerText(noLogging: boolean, panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor): string {
|
|
64
|
+
switch (panel) {
|
|
65
|
+
case AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE:
|
|
66
|
+
return noLogging ?
|
|
67
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletionNoLoggingInConsole) :
|
|
68
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletionInConsole);
|
|
69
|
+
case AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES:
|
|
70
|
+
return noLogging ?
|
|
71
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletionNoLoggingInSources) :
|
|
72
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletionInSources);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
52
76
|
export interface ViewInput {
|
|
53
77
|
disclaimerTooltipId?: string;
|
|
54
78
|
spinnerTooltipId?: string;
|
|
55
79
|
noLogging: boolean;
|
|
56
80
|
aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
|
|
57
81
|
onManageInSettingsTooltipClick: () => void;
|
|
82
|
+
// TODO(b/472268298): Remove ContextFlavor explicitly and pass required values
|
|
83
|
+
panel?: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
86
|
export interface ViewOutput {
|
|
@@ -67,10 +93,11 @@ export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) =
|
|
|
67
93
|
export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View =
|
|
68
94
|
(input, output, target) => {
|
|
69
95
|
if (input.aidaAvailability !== Host.AidaClient.AidaAccessPreconditions.AVAILABLE || !input.disclaimerTooltipId ||
|
|
70
|
-
!input.spinnerTooltipId) {
|
|
96
|
+
!input.spinnerTooltipId || !input.panel) {
|
|
71
97
|
render(nothing, target);
|
|
72
98
|
return;
|
|
73
99
|
}
|
|
100
|
+
const tooltipDisclaimerText = getTooltipDisclaimerText(input.noLogging, input.panel);
|
|
74
101
|
// clang-format off
|
|
75
102
|
render(
|
|
76
103
|
html`
|
|
@@ -118,7 +145,7 @@ export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View =
|
|
|
118
145
|
}
|
|
119
146
|
})}>
|
|
120
147
|
<div class="disclaimer-tooltip-container"><div class="tooltip-text">
|
|
121
|
-
${
|
|
148
|
+
${tooltipDisclaimerText}
|
|
122
149
|
</div>
|
|
123
150
|
<span
|
|
124
151
|
tabIndex="0"
|
|
@@ -146,6 +173,7 @@ export class AiCodeCompletionDisclaimer extends UI.Widget.Widget {
|
|
|
146
173
|
#loading = false;
|
|
147
174
|
#loadingStartTime = 0;
|
|
148
175
|
#spinnerLoadingTimeout: number|undefined;
|
|
176
|
+
#panel?: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
149
177
|
|
|
150
178
|
#aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
|
|
151
179
|
#boundOnAidaAvailabilityChange: () => Promise<void>;
|
|
@@ -196,6 +224,11 @@ export class AiCodeCompletionDisclaimer extends UI.Widget.Widget {
|
|
|
196
224
|
}
|
|
197
225
|
}
|
|
198
226
|
|
|
227
|
+
set panel(panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor) {
|
|
228
|
+
this.#panel = panel;
|
|
229
|
+
this.requestUpdate();
|
|
230
|
+
}
|
|
231
|
+
|
|
199
232
|
async #onAidaAvailabilityChange(): Promise<void> {
|
|
200
233
|
const currentAidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions();
|
|
201
234
|
if (currentAidaAvailability !== this.#aidaAvailability) {
|
|
@@ -217,6 +250,7 @@ export class AiCodeCompletionDisclaimer extends UI.Widget.Widget {
|
|
|
217
250
|
noLogging: this.#noLogging,
|
|
218
251
|
aidaAvailability: this.#aidaAvailability,
|
|
219
252
|
onManageInSettingsTooltipClick: this.#onManageInSettingsTooltipClick.bind(this),
|
|
253
|
+
panel: this.#panel,
|
|
220
254
|
},
|
|
221
255
|
this.#viewOutput, this.contentElement);
|
|
222
256
|
}
|
|
@@ -7,6 +7,7 @@ import '../../ui/components/tooltips/tooltips.js';
|
|
|
7
7
|
|
|
8
8
|
import * as Host from '../../core/host/host.js';
|
|
9
9
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
10
|
+
import type * as AiCodeCompletion from '../../models/ai_code_completion/ai_code_completion.js';
|
|
10
11
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
11
12
|
import {Directives, html, nothing, render} from '../../ui/lit/lit.js';
|
|
12
13
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
@@ -32,6 +33,7 @@ export interface AiCodeCompletionSummaryToolbarProps {
|
|
|
32
33
|
disclaimerTooltipId?: string;
|
|
33
34
|
spinnerTooltipId?: string;
|
|
34
35
|
hasTopBorder?: boolean;
|
|
36
|
+
panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export interface ViewInput {
|
|
@@ -42,6 +44,7 @@ export interface ViewInput {
|
|
|
42
44
|
loading: boolean;
|
|
43
45
|
hasTopBorder: boolean;
|
|
44
46
|
aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
|
|
47
|
+
panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
|
|
@@ -65,6 +68,7 @@ export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View = (input, _output, target) => {
|
|
|
65
68
|
disclaimerTooltipId: input.disclaimerTooltipId,
|
|
66
69
|
spinnerTooltipId: input.spinnerTooltipId,
|
|
67
70
|
loading: input.loading,
|
|
71
|
+
panel: input.panel,
|
|
68
72
|
})} class="disclaimer-widget"></devtools-widget>` : nothing;
|
|
69
73
|
|
|
70
74
|
const recitationNotice = input.citations && input.citations.size > 0 ?
|
|
@@ -110,6 +114,7 @@ export class AiCodeCompletionSummaryToolbar extends UI.Widget.Widget {
|
|
|
110
114
|
#citations = new Set<string>();
|
|
111
115
|
#loading = false;
|
|
112
116
|
#hasTopBorder = false;
|
|
117
|
+
#panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
113
118
|
|
|
114
119
|
#aidaAvailability?: Host.AidaClient.AidaAccessPreconditions;
|
|
115
120
|
#boundOnAidaAvailabilityChange: () => Promise<void>;
|
|
@@ -120,6 +125,7 @@ export class AiCodeCompletionSummaryToolbar extends UI.Widget.Widget {
|
|
|
120
125
|
this.#spinnerTooltipId = props.spinnerTooltipId;
|
|
121
126
|
this.#citationsTooltipId = props.citationsTooltipId;
|
|
122
127
|
this.#hasTopBorder = props.hasTopBorder ?? false;
|
|
128
|
+
this.#panel = props.panel;
|
|
123
129
|
this.#boundOnAidaAvailabilityChange = this.#onAidaAvailabilityChange.bind(this);
|
|
124
130
|
this.#view = view ?? DEFAULT_SUMMARY_TOOLBAR_VIEW;
|
|
125
131
|
this.requestUpdate();
|
|
@@ -158,6 +164,7 @@ export class AiCodeCompletionSummaryToolbar extends UI.Widget.Widget {
|
|
|
158
164
|
loading: this.#loading,
|
|
159
165
|
hasTopBorder: this.#hasTopBorder,
|
|
160
166
|
aidaAvailability: this.#aidaAvailability,
|
|
167
|
+
panel: this.#panel,
|
|
161
168
|
},
|
|
162
169
|
undefined, this.contentElement);
|
|
163
170
|
}
|