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 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, new DOMInheritanceCascade(this, nodeCascade, this.#registeredProperties));
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
- // All properties are inherited for highlight pseudos.
996
- if (this.#isInherited && !this.#isHighlightPseudoCascade && !metadata.isPropertyInherited(property.name)) {
997
- continue;
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 Array.from(this.#styleToNodeCascade.keys());
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 and displaying them in the text editor.
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 extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
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. **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).
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
- tooltipDisclaimerTextForAiCodeCompletion:
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
- tooltipDisclaimerTextForAiCodeCompletionNoLogging:
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
- ${input.noLogging ? lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletionNoLogging) : lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletion)}
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
  }