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
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// Copyright 2025 The Chromium Authors
|
|
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
|
+
import '../../ui/components/tooltips/tooltips.js';
|
|
4
5
|
|
|
5
6
|
import * as Host from '../../core/host/host.js';
|
|
6
7
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
8
|
+
import * as Root from '../../core/root/root.js';
|
|
9
|
+
import * as AiCodeCompletion from '../../models/ai_code_completion/ai_code_completion.js';
|
|
10
|
+
import * as Buttons from '../../ui/components/buttons/buttons.js';
|
|
7
11
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
8
|
-
import {html, nothing, render} from '../../ui/lit/lit.js';
|
|
12
|
+
import {Directives, html, nothing, render} from '../../ui/lit/lit.js';
|
|
13
|
+
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
9
14
|
|
|
10
15
|
import styles from './aiCodeGenerationTeaser.css.js';
|
|
11
16
|
|
|
@@ -13,19 +18,55 @@ const UIStringsNotTranslate = {
|
|
|
13
18
|
/**
|
|
14
19
|
* @description Text for teaser to generate code.
|
|
15
20
|
*/
|
|
16
|
-
ctrlItoGenerateCode: '
|
|
21
|
+
ctrlItoGenerateCode: 'Ctrl+I to generate code',
|
|
17
22
|
/**
|
|
18
23
|
* @description Text for teaser to generate code in Mac.
|
|
19
24
|
*/
|
|
20
|
-
cmdItoGenerateCode: '
|
|
25
|
+
cmdItoGenerateCode: 'Cmd+I to generate code',
|
|
21
26
|
/**
|
|
22
|
-
* Text for teaser when generating suggestion.
|
|
27
|
+
* @description Text for teaser when generating suggestion.
|
|
23
28
|
*/
|
|
24
29
|
generating: 'Generating... (esc to cancel)',
|
|
25
30
|
/**
|
|
26
|
-
* Text for teaser for discoverability.
|
|
31
|
+
* @description Text for teaser for discoverability.
|
|
27
32
|
*/
|
|
28
33
|
writeACommentToGenerateCode: 'Write a comment to generate code',
|
|
34
|
+
/**
|
|
35
|
+
* @description Text for teaser when suggestion has been generated.
|
|
36
|
+
*/
|
|
37
|
+
tab: 'tab',
|
|
38
|
+
/**
|
|
39
|
+
* @description Text for teaser when suggestion has been generated.
|
|
40
|
+
*/
|
|
41
|
+
toAccept: 'to accept',
|
|
42
|
+
/**
|
|
43
|
+
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code generation in Console panel.
|
|
44
|
+
*/
|
|
45
|
+
tooltipDisclaimerTextForAiCodeGenerationInConsole:
|
|
46
|
+
'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.',
|
|
47
|
+
/**
|
|
48
|
+
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code generation in Console panel.
|
|
49
|
+
*/
|
|
50
|
+
tooltipDisclaimerTextForAiCodeGenerationNoLoggingInConsole:
|
|
51
|
+
'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.',
|
|
52
|
+
/**
|
|
53
|
+
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code generation in Sources panel.
|
|
54
|
+
*/
|
|
55
|
+
tooltipDisclaimerTextForAiCodeGenerationInSources:
|
|
56
|
+
'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.',
|
|
57
|
+
/**
|
|
58
|
+
* @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code generation in Sources panel.
|
|
59
|
+
*/
|
|
60
|
+
tooltipDisclaimerTextForAiCodeGenerationNoLoggingInSources:
|
|
61
|
+
'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.',
|
|
62
|
+
/**
|
|
63
|
+
* @description Text for tooltip button which redirects to AI settings
|
|
64
|
+
*/
|
|
65
|
+
manageInSettings: 'Manage in settings',
|
|
66
|
+
/**
|
|
67
|
+
* @description Title for disclaimer info button in the teaser to generate code.
|
|
68
|
+
*/
|
|
69
|
+
learnMoreAboutHowYourDataIsBeingUsed: 'Learn more about how your data is being used',
|
|
29
70
|
} as const;
|
|
30
71
|
|
|
31
72
|
const lockedString = i18n.i18n.lockedString;
|
|
@@ -35,17 +76,96 @@ export enum AiCodeGenerationTeaserDisplayState {
|
|
|
35
76
|
TRIGGER = 'trigger',
|
|
36
77
|
DISCOVERY = 'discovery',
|
|
37
78
|
LOADING = 'loading',
|
|
79
|
+
GENERATED = 'generated',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getTooltipDisclaimerText(noLogging: boolean, panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor): string {
|
|
83
|
+
switch (panel) {
|
|
84
|
+
case AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE:
|
|
85
|
+
return noLogging ?
|
|
86
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeGenerationNoLoggingInConsole) :
|
|
87
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeGenerationInConsole);
|
|
88
|
+
case AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES:
|
|
89
|
+
return noLogging ?
|
|
90
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeGenerationNoLoggingInSources) :
|
|
91
|
+
lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeGenerationInSources);
|
|
92
|
+
}
|
|
38
93
|
}
|
|
39
94
|
|
|
40
95
|
export interface ViewInput {
|
|
41
96
|
displayState: AiCodeGenerationTeaserDisplayState;
|
|
97
|
+
disclaimerTooltipId?: string;
|
|
98
|
+
noLogging: boolean;
|
|
99
|
+
onManageInSettingsTooltipClick: (event: Event) => void;
|
|
100
|
+
// TODO(b/472268298): Remove ContextFlavor explicitly and pass required values
|
|
101
|
+
panel?: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
42
102
|
}
|
|
43
103
|
|
|
44
|
-
export
|
|
104
|
+
export interface ViewOutput {
|
|
105
|
+
hideTooltip?: () => void;
|
|
106
|
+
setTimerText?: (text: string) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
|
|
110
|
+
|
|
111
|
+
export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
112
|
+
if (!input.panel) {
|
|
113
|
+
render(nothing, target);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
45
116
|
|
|
46
|
-
export const DEFAULT_VIEW: View = (input, _output, target) => {
|
|
47
117
|
let teaserLabel;
|
|
48
118
|
switch (input.displayState) {
|
|
119
|
+
case AiCodeGenerationTeaserDisplayState.TRIGGER: {
|
|
120
|
+
if (!input.disclaimerTooltipId) {
|
|
121
|
+
render(nothing, target);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const toGenerateCode = Host.Platform.isMac() ? lockedString(UIStringsNotTranslate.cmdItoGenerateCode) :
|
|
125
|
+
lockedString(UIStringsNotTranslate.ctrlItoGenerateCode);
|
|
126
|
+
const tooltipDisclaimerText = getTooltipDisclaimerText(input.noLogging, input.panel);
|
|
127
|
+
// TODO(b/472291834): Disclaimer icon should match the placeholder's color
|
|
128
|
+
// clang-format off
|
|
129
|
+
teaserLabel = html`<div class="ai-code-generation-teaser-trigger">
|
|
130
|
+
${toGenerateCode} <devtools-button
|
|
131
|
+
.data=${{
|
|
132
|
+
title: lockedString(UIStringsNotTranslate.learnMoreAboutHowYourDataIsBeingUsed),
|
|
133
|
+
size: Buttons.Button.Size.MICRO,
|
|
134
|
+
iconName: 'info',
|
|
135
|
+
variant: Buttons.Button.Variant.ICON,
|
|
136
|
+
jslogContext: 'ai-code-generation-teaser.info-button',
|
|
137
|
+
} as Buttons.Button.ButtonData}
|
|
138
|
+
aria-details=${input.disclaimerTooltipId}
|
|
139
|
+
aria-describedby=${input.disclaimerTooltipId}
|
|
140
|
+
></devtools-button>
|
|
141
|
+
<devtools-tooltip
|
|
142
|
+
id=${input.disclaimerTooltipId}
|
|
143
|
+
variant="rich"
|
|
144
|
+
jslogContext="ai-code-generation-disclaimer"
|
|
145
|
+
${Directives.ref(el => {
|
|
146
|
+
if (el instanceof HTMLElement) {
|
|
147
|
+
output.hideTooltip = () => {
|
|
148
|
+
el.hidePopover();
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
})}>
|
|
152
|
+
<div class="disclaimer-tooltip-container"><div class="tooltip-text">
|
|
153
|
+
${tooltipDisclaimerText}
|
|
154
|
+
</div>
|
|
155
|
+
<span
|
|
156
|
+
tabIndex="0"
|
|
157
|
+
class="link"
|
|
158
|
+
role="link"
|
|
159
|
+
jslog=${VisualLogging.link('open-ai-settings').track({
|
|
160
|
+
click: true,
|
|
161
|
+
})}
|
|
162
|
+
@click=${input.onManageInSettingsTooltipClick}
|
|
163
|
+
>${lockedString(UIStringsNotTranslate.manageInSettings)}</span></div></devtools-tooltip>
|
|
164
|
+
</div>`;
|
|
165
|
+
// clang-format on
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
49
169
|
case AiCodeGenerationTeaserDisplayState.DISCOVERY: {
|
|
50
170
|
const newBadge = UI.UIUtils.maybeCreateNewBadge(PROMOTION_ID);
|
|
51
171
|
teaserLabel = newBadge ?
|
|
@@ -55,14 +175,27 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
|
|
|
55
175
|
}
|
|
56
176
|
|
|
57
177
|
case AiCodeGenerationTeaserDisplayState.LOADING: {
|
|
58
|
-
|
|
178
|
+
// clang-format off
|
|
179
|
+
teaserLabel = html`
|
|
180
|
+
<span class="ai-code-generation-spinner"></span> ${lockedString(UIStringsNotTranslate.generating)}
|
|
181
|
+
<span class="ai-code-generation-timer" ${Directives.ref(el => {
|
|
182
|
+
if (el) {
|
|
183
|
+
output.setTimerText = (text: string) => {
|
|
184
|
+
el.textContent = text;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
})}></span>`;
|
|
188
|
+
// clang-format on
|
|
59
189
|
break;
|
|
60
190
|
}
|
|
61
191
|
|
|
62
|
-
case AiCodeGenerationTeaserDisplayState.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
192
|
+
case AiCodeGenerationTeaserDisplayState.GENERATED: {
|
|
193
|
+
// clang-format off
|
|
194
|
+
teaserLabel = html`<div class="ai-code-generation-teaser-generated">
|
|
195
|
+
<span>${lockedString(UIStringsNotTranslate.tab)}</span>
|
|
196
|
+
${lockedString(UIStringsNotTranslate.toAccept)}
|
|
197
|
+
</div>`;
|
|
198
|
+
// clang-format on
|
|
66
199
|
break;
|
|
67
200
|
}
|
|
68
201
|
}
|
|
@@ -73,7 +206,7 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
|
|
|
73
206
|
<style>${styles}</style>
|
|
74
207
|
<style>@scope to (devtools-widget > *) { ${UI.inspectorCommonStyles} }</style>
|
|
75
208
|
<div class="ai-code-generation-teaser">
|
|
76
|
-
|
|
209
|
+
${teaserLabel}
|
|
77
210
|
</div>
|
|
78
211
|
`, target
|
|
79
212
|
);
|
|
@@ -83,23 +216,39 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
|
|
|
83
216
|
// TODO(b/448063927): Add "Dont show again" for discovery teaser.
|
|
84
217
|
export class AiCodeGenerationTeaser extends UI.Widget.Widget {
|
|
85
218
|
readonly #view: View;
|
|
219
|
+
#viewOutput: ViewOutput = {};
|
|
86
220
|
|
|
87
221
|
#displayState = AiCodeGenerationTeaserDisplayState.TRIGGER;
|
|
222
|
+
#disclaimerTooltipId?: string;
|
|
223
|
+
#noLogging: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not.
|
|
224
|
+
#panel?: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
|
|
225
|
+
#timerIntervalId?: number;
|
|
226
|
+
#loadStartTime?: number;
|
|
88
227
|
|
|
89
228
|
constructor(view?: View) {
|
|
90
229
|
super();
|
|
91
230
|
this.markAsExternallyManaged();
|
|
231
|
+
this.#noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue ===
|
|
232
|
+
Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;
|
|
92
233
|
this.#view = view ?? DEFAULT_VIEW;
|
|
93
234
|
this.requestUpdate();
|
|
94
235
|
}
|
|
95
236
|
|
|
96
237
|
override performUpdate(): void {
|
|
97
|
-
const output = {};
|
|
98
238
|
this.#view(
|
|
99
239
|
{
|
|
100
240
|
displayState: this.#displayState,
|
|
241
|
+
onManageInSettingsTooltipClick: this.#onManageInSettingsTooltipClick.bind(this),
|
|
242
|
+
disclaimerTooltipId: this.#disclaimerTooltipId,
|
|
243
|
+
noLogging: this.#noLogging,
|
|
244
|
+
panel: this.#panel,
|
|
101
245
|
},
|
|
102
|
-
|
|
246
|
+
this.#viewOutput, this.contentElement);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override willHide(): void {
|
|
250
|
+
super.willHide();
|
|
251
|
+
this.#stopLoadingAnimation();
|
|
103
252
|
}
|
|
104
253
|
|
|
105
254
|
get displayState(): AiCodeGenerationTeaserDisplayState {
|
|
@@ -112,5 +261,52 @@ export class AiCodeGenerationTeaser extends UI.Widget.Widget {
|
|
|
112
261
|
}
|
|
113
262
|
this.#displayState = displayState;
|
|
114
263
|
this.requestUpdate();
|
|
264
|
+
if (this.#displayState === AiCodeGenerationTeaserDisplayState.LOADING) {
|
|
265
|
+
// wait update to complete so that setTimerText has been set properly
|
|
266
|
+
void this.updateComplete.then(() => {
|
|
267
|
+
void this.#startLoadingAnimation();
|
|
268
|
+
});
|
|
269
|
+
} else if (this.#loadStartTime) {
|
|
270
|
+
this.#stopLoadingAnimation();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#startLoadingAnimation(): void {
|
|
275
|
+
this.#stopLoadingAnimation();
|
|
276
|
+
this.#loadStartTime = performance.now();
|
|
277
|
+
|
|
278
|
+
this.#viewOutput.setTimerText?.('(0s)');
|
|
279
|
+
|
|
280
|
+
this.#timerIntervalId = window.setInterval(() => {
|
|
281
|
+
if (this.#loadStartTime) {
|
|
282
|
+
const elapsedSeconds = Math.floor((performance.now() - this.#loadStartTime) / 1000);
|
|
283
|
+
this.#viewOutput.setTimerText?.(`(${elapsedSeconds}s)`);
|
|
284
|
+
}
|
|
285
|
+
}, 1000);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#stopLoadingAnimation(): void {
|
|
289
|
+
if (this.#timerIntervalId) {
|
|
290
|
+
clearInterval(this.#timerIntervalId);
|
|
291
|
+
this.#timerIntervalId = undefined;
|
|
292
|
+
}
|
|
293
|
+
this.#loadStartTime = undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
set disclaimerTooltipId(disclaimerTooltipId: string) {
|
|
297
|
+
this.#disclaimerTooltipId = disclaimerTooltipId;
|
|
298
|
+
this.requestUpdate();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
set panel(panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor) {
|
|
302
|
+
this.#panel = panel;
|
|
303
|
+
this.requestUpdate();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#onManageInSettingsTooltipClick(event: Event): void {
|
|
307
|
+
event.stopPropagation();
|
|
308
|
+
this.#viewOutput.hideTooltip?.();
|
|
309
|
+
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
|
310
|
+
event.consume(true);
|
|
115
311
|
}
|
|
116
312
|
}
|
|
@@ -6,9 +6,73 @@
|
|
|
6
6
|
|
|
7
7
|
@scope to (devtools-widget > *) {
|
|
8
8
|
.ai-code-generation-teaser {
|
|
9
|
+
pointer-events: all;
|
|
10
|
+
font-style: italic;
|
|
11
|
+
padding-left: var(--sys-size-3);
|
|
12
|
+
line-height: var(--sys-size-7);
|
|
13
|
+
|
|
14
|
+
.ai-code-generation-teaser-trigger {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ai-code-generation-teaser-generated {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
gap: var(--sys-size-2);
|
|
22
|
+
color: var(--sys-color-primary);
|
|
23
|
+
|
|
24
|
+
span {
|
|
25
|
+
border: var(--sys-size-1) solid var(--sys-color-primary);
|
|
26
|
+
border-radius: var(--sys-shape-corner-extra-small);
|
|
27
|
+
padding: 0 var(--sys-size-3);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
9
31
|
.new-badge {
|
|
10
32
|
font-style: normal;
|
|
11
33
|
display: inline-block;
|
|
12
34
|
}
|
|
35
|
+
|
|
36
|
+
devtools-tooltip:popover-open {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
align-items: center;
|
|
40
|
+
|
|
41
|
+
.disclaimer-tooltip-container {
|
|
42
|
+
padding: var(--sys-size-4) 0;
|
|
43
|
+
max-width: var(--sys-size-30);
|
|
44
|
+
white-space: normal;
|
|
45
|
+
|
|
46
|
+
.tooltip-text {
|
|
47
|
+
color: var(--sys-color-on-surface-subtle);
|
|
48
|
+
padding: 0 var(--sys-size-5);
|
|
49
|
+
align-items: flex-start;
|
|
50
|
+
gap: 10px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.link {
|
|
54
|
+
margin: var(--sys-size-5) var(--sys-size-8) 0 var(--sys-size-5);
|
|
55
|
+
display: inline-block;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ai-code-generation-spinner::before {
|
|
61
|
+
content: "⠋";
|
|
62
|
+
animation: teaser-spinner-animation 1s linear infinite;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@keyframes teaser-spinner-animation {
|
|
67
|
+
0% { content: "⠋"; }
|
|
68
|
+
10% { content: "⠙"; }
|
|
69
|
+
20% { content: "⠹"; }
|
|
70
|
+
30% { content: "⠸"; }
|
|
71
|
+
40% { content: "⠼"; }
|
|
72
|
+
50% { content: "⠴"; }
|
|
73
|
+
60% { content: "⠦"; }
|
|
74
|
+
70% { content: "⠧"; }
|
|
75
|
+
80% { content: "⠇"; }
|
|
76
|
+
90% { content: "⠏"; }
|
|
13
77
|
}
|
|
14
78
|
}
|
|
@@ -649,7 +649,8 @@ export class ConsoleView extends UI.Widget.VBox implements
|
|
|
649
649
|
this.aiCodeCompletionSummaryToolbar = new AiCodeCompletionSummaryToolbar({
|
|
650
650
|
citationsTooltipId: CITATIONS_TOOLTIP_ID,
|
|
651
651
|
disclaimerTooltipId: DISCLAIMER_TOOLTIP_ID,
|
|
652
|
-
spinnerTooltipId: SPINNER_TOOLTIP_ID
|
|
652
|
+
spinnerTooltipId: SPINNER_TOOLTIP_ID,
|
|
653
|
+
panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE,
|
|
653
654
|
});
|
|
654
655
|
this.aiCodeCompletionSummaryToolbarContainer =
|
|
655
656
|
this.element.createChild('div', 'ai-code-completion-summary-toolbar-container');
|
|
@@ -347,7 +347,8 @@ export class ObjectEventListenerBar extends UI.TreeOutline.TreeElement {
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
const subtitle = title.createChild('span', 'event-listener-tree-subtitle');
|
|
350
|
-
const linkElement = linkifier.linkifyRawLocation(
|
|
350
|
+
const linkElement = linkifier.linkifyRawLocation(
|
|
351
|
+
this.#eventListener.location(), this.#eventListener.sourceURL(), undefined, {tabStop: true});
|
|
351
352
|
subtitle.appendChild(linkElement);
|
|
352
353
|
|
|
353
354
|
this.listItemElement.addEventListener('contextmenu', event => {
|
|
@@ -102,6 +102,7 @@ export class AiCodeCompletionPlugin extends Plugin {
|
|
|
102
102
|
this.#aiCodeCompletionDisclaimer = new PanelCommon.AiCodeCompletionDisclaimer();
|
|
103
103
|
this.#aiCodeCompletionDisclaimer.disclaimerTooltipId = DISCLAIMER_TOOLTIP_ID;
|
|
104
104
|
this.#aiCodeCompletionDisclaimer.spinnerTooltipId = SPINNER_TOOLTIP_ID;
|
|
105
|
+
this.#aiCodeCompletionDisclaimer.panel = AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES;
|
|
105
106
|
this.#aiCodeCompletionDisclaimer.show(this.#aiCodeCompletionDisclaimerContainer, undefined, true);
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -109,8 +110,11 @@ export class AiCodeCompletionPlugin extends Plugin {
|
|
|
109
110
|
if (this.#aiCodeCompletionCitationsToolbar) {
|
|
110
111
|
return;
|
|
111
112
|
}
|
|
112
|
-
this.#aiCodeCompletionCitationsToolbar =
|
|
113
|
-
|
|
113
|
+
this.#aiCodeCompletionCitationsToolbar = new PanelCommon.AiCodeCompletionSummaryToolbar({
|
|
114
|
+
citationsTooltipId: CITATIONS_TOOLTIP_ID,
|
|
115
|
+
hasTopBorder: true,
|
|
116
|
+
panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES
|
|
117
|
+
});
|
|
114
118
|
this.#aiCodeCompletionCitationsToolbar.show(this.#aiCodeCompletionCitationsToolbarContainer, undefined, true);
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Name: Dependencies sourced from the upstream `chromium` repository
|
|
2
2
|
URL: https://chromium.googlesource.com/chromium/src
|
|
3
3
|
Version: N/A
|
|
4
|
-
Revision:
|
|
4
|
+
Revision: e3edb4435f06ac3de39688c44cc50378c47e35e8
|
|
5
5
|
Update Mechanism: Manual (https://crbug.com/428069060)
|
|
6
6
|
License: BSD-3-Clause
|
|
7
7
|
License File: LICENSE
|
|
@@ -122,21 +122,25 @@ export class AiCodeCompletionProvider {
|
|
|
122
122
|
this.#aiCodeCompletion?.clearCachedRequest();
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// TODO(b/445394511): Update setup and cleanup method so that config callbacks are not
|
|
126
|
-
// called twice.
|
|
127
125
|
#setupAiCodeCompletion(): void {
|
|
128
126
|
if (!this.#editor || !this.#aiCodeCompletionConfig) {
|
|
129
127
|
return;
|
|
130
128
|
}
|
|
131
|
-
if (
|
|
132
|
-
this
|
|
133
|
-
|
|
134
|
-
this.#aiCodeCompletionConfig.completionContext.stopSequences);
|
|
129
|
+
if (this.#aiCodeCompletion) {
|
|
130
|
+
// early return as this means that code completion was previously setup
|
|
131
|
+
return;
|
|
135
132
|
}
|
|
133
|
+
this.#aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
|
|
134
|
+
{aidaClient: this.#aidaClient}, this.#aiCodeCompletionConfig.panel, undefined,
|
|
135
|
+
this.#aiCodeCompletionConfig.completionContext.stopSequences);
|
|
136
136
|
this.#aiCodeCompletionConfig.onFeatureEnabled();
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
#cleanupAiCodeCompletion(): void {
|
|
140
|
+
if (!this.#aiCodeCompletion) {
|
|
141
|
+
// early return as this means there is no code completion to clean up
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
140
144
|
if (this.#suggestionRenderingTimeout) {
|
|
141
145
|
clearTimeout(this.#suggestionRenderingTimeout);
|
|
142
146
|
this.#suggestionRenderingTimeout = undefined;
|
|
@@ -208,6 +212,21 @@ export class AiCodeCompletionProvider {
|
|
|
208
212
|
this.#editor?.editor.dispatch({effects: this.#teaserCompartment.reconfigure([])});
|
|
209
213
|
}
|
|
210
214
|
|
|
215
|
+
/**
|
|
216
|
+
* This method is responsible for fetching code completion suggestions and
|
|
217
|
+
* displaying them in the text editor.
|
|
218
|
+
*
|
|
219
|
+
* 1. **Debouncing requests:** As the user types, we don't want to send a request
|
|
220
|
+
* for every keystroke. Instead, we use debouncing to schedule a request
|
|
221
|
+
* only after the user has paused typing for a short period
|
|
222
|
+
* (AIDA_REQUEST_THROTTLER_TIMEOUT_MS). This prevents spamming the backend with
|
|
223
|
+
* requests for intermediate typing states.
|
|
224
|
+
*
|
|
225
|
+
* 2. **Delaying suggestions:** When a suggestion is received from the AIDA
|
|
226
|
+
* backend, we don't show it immediately. There is a minimum delay
|
|
227
|
+
* (DELAY_BEFORE_SHOWING_RESPONSE_MS) from when the request was sent to when
|
|
228
|
+
* the suggestion is displayed.
|
|
229
|
+
*/
|
|
211
230
|
#triggerAiCodeCompletion(update: CodeMirror.ViewUpdate): void {
|
|
212
231
|
if (!update.docChanged || !this.#editor || !this.#aiCodeCompletion) {
|
|
213
232
|
return;
|
|
@@ -287,8 +306,7 @@ export class AiCodeCompletionProvider {
|
|
|
287
306
|
citations,
|
|
288
307
|
rpcGlobalId,
|
|
289
308
|
} = sampleResponse;
|
|
290
|
-
const remainingDelay = Math.max(
|
|
291
|
-
AiCodeCompletion.AiCodeCompletion.DELAY_BEFORE_SHOWING_RESPONSE_MS - (performance.now() - startTime), 0);
|
|
309
|
+
const remainingDelay = Math.max(DELAY_BEFORE_SHOWING_RESPONSE_MS - (performance.now() - startTime), 0);
|
|
292
310
|
this.#suggestionRenderingTimeout = window.setTimeout(() => {
|
|
293
311
|
const currentCursorPosition = this.#editor?.editor.state.selection.main.head;
|
|
294
312
|
if (currentCursorPosition !== cursorPositionAtRequest) {
|
|
@@ -71,6 +71,10 @@ export class AiCodeCompletionTeaserPlaceholder extends CM.WidgetType {
|
|
|
71
71
|
super.destroy(dom);
|
|
72
72
|
this.teaser?.hideWidget();
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
override eq(other: AiCodeCompletionTeaserPlaceholder): boolean {
|
|
76
|
+
return this.teaser === other.teaser;
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
export function aiCodeCompletionTeaserPlaceholder(teaser: UI.Widget.Widget): CM.Extension {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright 2025 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 CodeMirror from '../../../third_party/codemirror.next/codemirror.next.js';
|
|
6
|
+
|
|
7
|
+
const LINE_COMMENT_PATTERN = /^(?:\/\/|#)\s*/;
|
|
8
|
+
const BLOCK_COMMENT_START_PATTERN = /^\/\*+\s*/;
|
|
9
|
+
const BLOCK_COMMENT_END_PATTERN = /\s*\*+\/$/;
|
|
10
|
+
const BLOCK_COMMENT_LINE_PREFIX_PATTERN = /^\s*\*\s?/;
|
|
11
|
+
|
|
12
|
+
function findLastNonWhitespacePos(state: CodeMirror.EditorState, cursorPosition: number): number {
|
|
13
|
+
const line = state.doc.lineAt(cursorPosition);
|
|
14
|
+
const textBefore = line.text.substring(0, cursorPosition - line.from);
|
|
15
|
+
const effectiveEnd = line.from + textBefore.trimEnd().length;
|
|
16
|
+
return effectiveEnd;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveCommentNode(state: CodeMirror.EditorState, cursorPosition: number): CodeMirror.SyntaxNode|undefined {
|
|
20
|
+
const tree = CodeMirror.syntaxTree(state);
|
|
21
|
+
const lookupPos = findLastNonWhitespacePos(state, cursorPosition);
|
|
22
|
+
// Find the innermost syntax node at the last non-whitespace character position.
|
|
23
|
+
// The bias of -1 makes it check the character to the left of the position.
|
|
24
|
+
const node = tree.resolveInner(lookupPos, -1);
|
|
25
|
+
const nodeType = node.type.name;
|
|
26
|
+
// Check if the node type is a comment AND the cursor is within the node's range.
|
|
27
|
+
if (nodeType.includes('Comment') && cursorPosition >= node.to) {
|
|
28
|
+
if (!nodeType.includes('BlockComment')) {
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
// An unclosed block comment can result in the parser inserting an error.
|
|
32
|
+
let hasInternalError = false;
|
|
33
|
+
tree.iterate({
|
|
34
|
+
from: node.from,
|
|
35
|
+
to: node.to,
|
|
36
|
+
enter: n => {
|
|
37
|
+
if (n.type.isError) {
|
|
38
|
+
hasInternalError = true;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
return hasInternalError ? undefined : node;
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractBlockComment(rawText: string): string|undefined {
|
|
50
|
+
// Remove /* and */, whitespace, and common leading asterisks on new lines
|
|
51
|
+
let cleaned = rawText.replace(BLOCK_COMMENT_START_PATTERN, '').replace(BLOCK_COMMENT_END_PATTERN, '');
|
|
52
|
+
// Remove leading " * " from multi-line block comments
|
|
53
|
+
cleaned = cleaned.split('\n').map(line => line.replace(BLOCK_COMMENT_LINE_PREFIX_PATTERN, '')).join('\n').trim();
|
|
54
|
+
return cleaned;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractLineComment(rawText: string): string {
|
|
58
|
+
return rawText.replace(LINE_COMMENT_PATTERN, '').trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class AiCodeGenerationParser {
|
|
62
|
+
static extractCommentText(state: CodeMirror.EditorState, cursorPosition: number): string|undefined {
|
|
63
|
+
const node = resolveCommentNode(state, cursorPosition);
|
|
64
|
+
if (!node) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const nodeType = node.type.name;
|
|
68
|
+
const rawText = state.doc.sliceString(node.from, node.to);
|
|
69
|
+
if (nodeType.includes('LineComment')) {
|
|
70
|
+
return extractLineComment(rawText);
|
|
71
|
+
}
|
|
72
|
+
if (nodeType.includes('BlockComment')) {
|
|
73
|
+
return extractBlockComment(rawText);
|
|
74
|
+
}
|
|
75
|
+
return rawText;
|
|
76
|
+
}
|
|
77
|
+
}
|