Skip to content

Commit 736355a

Browse files
samiyacDevtools-frontend LUCI CQ
authored andcommitted
Add code completion summary toolbar to styles pane
The summary toolbar is responsible for displaying loading state (using a spinner) and show privacy and recitation disclaimer. I have tried to implement this similar to existing toolbar integration in front_end/panels/console/ConsoleView.ts I had to update the CSS which is used for wrapping summary toolbar to use `@container` instead of `@media` - as part of this change, also had to update CSS for existing toolbar in Console panel. The text for disclaimer still needs to be updated. Bypass-Check-License: Existing file Fixed: 476101419, 476101019 Change-Id: I0610fc3fe5d60807c3045a8933e85aa5bf9e3abc Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7698167 Reviewed-by: Philip Pfaffe <pfaffe@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org> Commit-Queue: Samiya Caur <samiyac@chromium.org>
1 parent df3ba6c commit 736355a

6 files changed

Lines changed: 218 additions & 7 deletions

File tree

front_end/panels/common/aiCodeCompletionSummaryToolbar.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
border-left: var(--sys-size-1) solid var(--sys-color-divider);
5050
}
5151

52-
@media (width < 545px) {
52+
@container (width < 545px) {
5353
&.has-disclaimer.has-recitation-notice {
5454
height: 46px;
5555
flex-direction: column;

front_end/panels/console/consoleView.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,10 @@
633633
}
634634
}
635635

636+
.ai-code-completion-summary-toolbar-container {
637+
container-type: inline-size;
638+
}
639+
636640
@media (forced-colors: active) {
637641
.console-message-expand-icon,
638642
.console-warning-level .expand-group-icon {

front_end/panels/elements/StylesAiCodeCompletionProvider.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class StylesAiCodeCompletionProvider {
2323
startTime: number,
2424
onImpression: (rpcGlobalId: Host.AidaClient.RpcGlobalId, latency: number, sampleId?: number) => void,
2525
clearCachedRequest: () => void,
26+
citations: Host.AidaClient.Citation[],
2627
rpcGlobalId?: Host.AidaClient.RpcGlobalId,
2728
sampleId?: number,
2829
}|null) => void;
@@ -142,6 +143,7 @@ export class StylesAiCodeCompletionProvider {
142143
startTime,
143144
clearCachedRequest: this.clearCache.bind(this),
144145
onImpression: this.#aiCodeCompletion.registerUserImpression.bind(this.#aiCodeCompletion),
146+
citations: aidaSuggestion.citations,
145147
});
146148
}
147149

@@ -227,4 +229,12 @@ export class StylesAiCodeCompletionProvider {
227229
clearCache(): void {
228230
this.#aiCodeCompletion?.clearCachedRequest();
229231
}
232+
233+
onSuggestionAccepted(
234+
citations: Host.AidaClient.Citation[], rpcGlobalId?: Host.AidaClient.RpcGlobalId, sampleId?: number): void {
235+
this.#aiCodeCompletionConfig?.onSuggestionAccepted(citations);
236+
if (rpcGlobalId) {
237+
this.#aiCodeCompletion?.registerUserAcceptance(rpcGlobalId, sampleId);
238+
}
239+
}
230240
}

front_end/panels/elements/StylesSidebarPane.test.ts

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import * as Common from '../../core/common/common.js';
56
import * as Host from '../../core/host/host.js';
67
import * as SDK from '../../core/sdk/sdk.js';
78
import * as Protocol from '../../generated/protocol.js';
89
import * as ComputedStyle from '../../models/computed_style/computed_style.js';
9-
import {renderElementIntoDOM} from '../../testing/DOMHelpers.js';
10+
import {raf, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
1011
import {
1112
createTarget,
1213
describeWithEnvironment,
@@ -1203,6 +1204,110 @@ describe('StylesSidebarPane', () => {
12031204
});
12041205
});
12051206
});
1207+
1208+
describe('ai code completion provider callbacks', () => {
1209+
let stylesWrapper: UI.Widget.VBox;
1210+
let stylesSidebarPane: Elements.StylesSidebarPane.StylesSidebarPane;
1211+
1212+
beforeEach(async () => {
1213+
updateHostConfig({
1214+
devToolsAiCodeCompletionStyles: {
1215+
enabled: true,
1216+
},
1217+
aidaAvailability: {
1218+
enabled: true,
1219+
blockedByAge: false,
1220+
blockedByGeo: false,
1221+
},
1222+
});
1223+
const aiCodeCompletionProviderStub =
1224+
sinon.createStubInstance(TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider);
1225+
aiCodeCompletionProviderStub.extension.returns([]);
1226+
sinon.stub(TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider, 'createInstance')
1227+
.returns(aiCodeCompletionProviderStub);
1228+
Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', true);
1229+
stylesWrapper = new UI.Widget.VBox();
1230+
stylesWrapper.element.classList.add('style-panes-wrapper');
1231+
stylesSidebarPane =
1232+
new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
1233+
stylesSidebarPane.show(stylesWrapper.element);
1234+
});
1235+
1236+
it('initializes toolbar when the feature is enabled', async () => {
1237+
const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
1238+
assert.exists(providerConfig);
1239+
1240+
providerConfig.onFeatureEnabled();
1241+
1242+
assert.exists(stylesWrapper.contentElement.querySelector('div.ai-code-completion-summary-toolbar-container'));
1243+
});
1244+
1245+
it('cleans up toolbar when the feature is disabled', async () => {
1246+
const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
1247+
assert.exists(providerConfig);
1248+
providerConfig.onFeatureEnabled();
1249+
assert.exists(stylesWrapper.contentElement.querySelector('div.ai-code-completion-summary-toolbar-container'));
1250+
1251+
providerConfig.onFeatureDisabled();
1252+
1253+
assert.notExists(
1254+
stylesWrapper.contentElement.querySelector('div.ai-code-completion-summary-toolbar-container'));
1255+
});
1256+
1257+
it('shows a loading state when a request is triggered', async () => {
1258+
const setLoadingSpy = sinon.stub(PanelsCommon.AiCodeCompletionSummaryToolbar.prototype, 'setLoading');
1259+
const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
1260+
assert.exists(providerConfig);
1261+
providerConfig.onFeatureEnabled();
1262+
1263+
providerConfig.onRequestTriggered();
1264+
1265+
sinon.assert.calledOnce(setLoadingSpy);
1266+
assert.isTrue(setLoadingSpy.firstCall.args[0]);
1267+
});
1268+
1269+
it('hides the loading indicator when a response is received', async () => {
1270+
const setLoadingSpy = sinon.stub(PanelsCommon.AiCodeCompletionSummaryToolbar.prototype, 'setLoading');
1271+
const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
1272+
assert.exists(providerConfig);
1273+
providerConfig.onFeatureEnabled();
1274+
providerConfig.onRequestTriggered();
1275+
sinon.assert.calledOnce(setLoadingSpy);
1276+
assert.isTrue(setLoadingSpy.firstCall.args[0]);
1277+
1278+
providerConfig.onResponseReceived();
1279+
1280+
sinon.assert.calledTwice(setLoadingSpy);
1281+
assert.isFalse(setLoadingSpy.secondCall.args[0]);
1282+
});
1283+
1284+
it('attaches the citations toolbar when a suggestion with citations is accepted', async () => {
1285+
const updateCitationsSpy = sinon.spy(PanelsCommon.AiCodeCompletionSummaryToolbar.prototype, 'updateCitations');
1286+
const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
1287+
assert.exists(providerConfig);
1288+
1289+
providerConfig.onFeatureEnabled();
1290+
providerConfig.onResponseReceived();
1291+
1292+
providerConfig.onSuggestionAccepted([{uri: 'https://example.com/source'}]);
1293+
1294+
sinon.assert.calledOnce(updateCitationsSpy);
1295+
assert.deepEqual(updateCitationsSpy.firstCall.args, [['https://example.com/source']]);
1296+
});
1297+
1298+
it('does not attach the citations toolbar if there are no citations', async () => {
1299+
const updateCitationsSpy = sinon.spy(PanelsCommon.AiCodeCompletionSummaryToolbar.prototype, 'updateCitations');
1300+
const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
1301+
assert.exists(providerConfig);
1302+
1303+
providerConfig.onFeatureEnabled();
1304+
providerConfig.onResponseReceived();
1305+
1306+
providerConfig.onSuggestionAccepted([]);
1307+
1308+
sinon.assert.notCalled(updateCitationsSpy);
1309+
});
1310+
});
12061311
});
12071312

12081313
describe('IdleCallbackManager', () => {
@@ -1265,6 +1370,7 @@ describe('StylesSidebarPane', () => {
12651370
configurable: true,
12661371
});
12671372
sinon.stub(section, 'activeAiSuggestion').get(() => activeAiSuggestion);
1373+
section.commitActiveAiSuggestion.resolves();
12681374
mockTreeItem = {
12691375
property: {
12701376
name: 'color',
@@ -1471,6 +1577,7 @@ describe('StylesSidebarPane', () => {
14711577
startTime: 0,
14721578
clearCachedRequest: () => {},
14731579
onImpression: () => {},
1580+
citations: [],
14741581
});
14751582

14761583
assert.exists(section.activeAiSuggestion);
@@ -1492,6 +1599,7 @@ color: pink !important;`;
14921599
startTime: 0,
14931600
clearCachedRequest: () => {},
14941601
onImpression: () => {},
1602+
citations: [],
14951603
});
14961604

14971605
assert.exists(section.activeAiSuggestion);
@@ -1514,6 +1622,7 @@ color: pink !important;`;
15141622
startTime: 0,
15151623
clearCachedRequest: () => {},
15161624
onImpression: () => {},
1625+
citations: [],
15171626
});
15181627

15191628
assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
@@ -1535,6 +1644,7 @@ color: pink !important;`;
15351644
startTime: 0,
15361645
clearCachedRequest: () => {},
15371646
onImpression: () => {},
1647+
citations: [],
15381648
});
15391649

15401650
assert.strictEqual(section.activeAiSuggestion?.text, 'color: var(--rgb-color);');
@@ -1550,18 +1660,26 @@ color: pink !important;`;
15501660
it('accepts suggestion on Tab when suggest box is hidden', async () => {
15511661
cssPropertyPrompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(mockTreeItem, true);
15521662
cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
1663+
const onSuggestionAcceptedStub = aiCodeCompletionProvider.onSuggestionAccepted.returns();
15531664

15541665
cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
15551666
text: 'color: pink;',
15561667
from: 0,
15571668
startTime: 0,
15581669
clearCachedRequest: () => {},
15591670
onImpression: () => {},
1671+
citations: [{uri: 'https://example.com'}],
1672+
sampleId: 1,
1673+
rpcGlobalId: 1,
15601674
});
15611675
const tabEvent = new KeyboardEvent('keydown', {key: 'Tab'});
15621676
cssPropertyPrompt.onKeyDown(tabEvent);
1677+
// Required to make sure section.commitActiveAiSuggestion resolves
1678+
await raf();
15631679

15641680
sinon.assert.calledOnce(section.commitActiveAiSuggestion);
1681+
sinon.assert.calledOnce(onSuggestionAcceptedStub);
1682+
assert.deepEqual(onSuggestionAcceptedStub.firstCall.args, [[{uri: 'https://example.com'}], 1, 1]);
15651683
});
15661684

15671685
it('accepts auto complete suggestion and re-applies ghost text on first Tab accept when suggest box is visible',
@@ -1577,6 +1695,7 @@ color: pink !important;`;
15771695
startTime: 0,
15781696
clearCachedRequest: () => {},
15791697
onImpression: () => {},
1698+
citations: [],
15801699
});
15811700

15821701
assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
@@ -1601,6 +1720,7 @@ color: pink !important;`;
16011720
startTime: 0,
16021721
clearCachedRequest: () => {},
16031722
onImpression: () => {},
1723+
citations: [],
16041724
});
16051725

16061726
assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
@@ -1628,6 +1748,7 @@ color: pink !important;`;
16281748
startTime: 0,
16291749
clearCachedRequest: () => {},
16301750
onImpression: () => {},
1751+
citations: [],
16311752
});
16321753
assert.exists(section.activeAiSuggestion);
16331754

@@ -1647,6 +1768,7 @@ color: pink !important;`;
16471768
startTime: 0,
16481769
clearCachedRequest: () => {},
16491770
onImpression: () => {},
1771+
citations: [],
16501772
});
16511773
assert.exists(section.activeAiSuggestion);
16521774

@@ -1673,6 +1795,7 @@ color: pink !important;`;
16731795
startTime: 0,
16741796
clearCachedRequest: () => {},
16751797
onImpression: () => {},
1798+
citations: [],
16761799
});
16771800

16781801
assert.exists(section.activeAiSuggestion);
@@ -1692,6 +1815,7 @@ color: pink !important;`;
16921815
startTime: 0,
16931816
clearCachedRequest: () => {},
16941817
onImpression: () => {},
1818+
citations: [],
16951819
});
16961820
assert.exists(section.activeAiSuggestion);
16971821

0 commit comments

Comments
 (0)