Skip to content

Commit e440167

Browse files
Lightning00BladeDevtools-frontend LUCI CQ
authored andcommitted
[AI Assistance] Stash Inspector protocol changes
Stash inspector protocol changes while waiting for writing to disc. Uses git like names for convenience. Fixed: 399561324 Change-Id: I65b97df4aaa94ef652b898de38d3c7b0d76b1093 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6387119 Auto-Submit: Nikolay Vitkov <[email protected]> Reviewed-by: Ergün Erdoğmuş <[email protected]> Commit-Queue: Nikolay Vitkov <[email protected]>
1 parent 5cb7bc8 commit e440167

File tree

11 files changed

+292
-138
lines changed

11 files changed

+292
-138
lines changed

front_end/core/i18n/i18nImpl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ function getLocaleFetchUrl(locale: Intl.UnicodeBCP47LocaleIdentifier, location:
5959

6060
/**
6161
* Fetches the locale data of the specified locale.
62-
* Callers have to ensure that `locale` is an officilly supported locale.
62+
* Callers have to ensure that `locale` is an officially supported locale.
6363
* Depending whether a locale is present in `bundledLocales`, the data will be
6464
* fetched locally or remotely.
6565
*/
6666
export async function fetchAndRegisterLocaleData(
6767
locale: Intl.UnicodeBCP47LocaleIdentifier, location = self.location.toString()): Promise<void> {
6868
const localeDataTextPromise = fetch(getLocaleFetchUrl(locale, location)).then(result => result.json());
6969
const timeoutPromise =
70-
new Promise((resolve, reject) => window.setTimeout(() => reject(new Error('timed out fetching locale')), 5000));
70+
new Promise<never>((_, reject) => window.setTimeout(() => reject(new Error('timed out fetching locale')), 5000));
7171
const localeData = await Promise.race([timeoutPromise, localeDataTextPromise]);
7272
i18nInstance.registerLocaleData(locale, localeData);
7373
}

front_end/models/ai_assistance/ChangeManager.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,86 @@ div {
226226
}`);
227227
});
228228
});
229+
230+
describe('stashes', () => {
231+
it('can stash changes', async () => {
232+
const changeManager = new AiAssistanceModel.ChangeManager();
233+
const cssModel = createModel();
234+
await changeManager.addChange(cssModel, frameId, {
235+
groupId: agentId,
236+
selector: 'div',
237+
className: 'ai-style-change-1',
238+
styles: {
239+
color: 'blue',
240+
},
241+
});
242+
assert(cssModel.setStyleSheetText.calledOnce);
243+
assert.deepEqual(
244+
cssModel.setStyleSheetText.lastCall.args,
245+
['1', '.ai-style-change-1 {\n div& {\n color: blue;\n }\n}', true],
246+
);
247+
await changeManager.stashChanges();
248+
assert(cssModel.setStyleSheetText.calledTwice);
249+
assert.deepEqual(
250+
cssModel.setStyleSheetText.lastCall.args,
251+
['1', '', true],
252+
);
253+
});
254+
255+
it('can restore changes', async () => {
256+
const changeManager = new AiAssistanceModel.ChangeManager();
257+
const cssModel = createModel();
258+
await changeManager.addChange(cssModel, frameId, {
259+
groupId: agentId,
260+
selector: 'div',
261+
className: 'ai-style-change-1',
262+
styles: {
263+
color: 'blue',
264+
},
265+
});
266+
assert(cssModel.setStyleSheetText.calledOnce);
267+
assert.deepEqual(
268+
cssModel.setStyleSheetText.lastCall.args,
269+
['1', '.ai-style-change-1 {\n div& {\n color: blue;\n }\n}', true],
270+
);
271+
await changeManager.stashChanges();
272+
assert(cssModel.setStyleSheetText.calledTwice);
273+
assert.deepEqual(
274+
cssModel.setStyleSheetText.lastCall.args,
275+
['1', '', true],
276+
);
277+
await changeManager.popStashedChanges();
278+
assert(cssModel.setStyleSheetText.calledThrice);
279+
assert.deepEqual(
280+
cssModel.setStyleSheetText.lastCall.args,
281+
['1', '.ai-style-change-1 {\n div& {\n color: blue;\n }\n}', true],
282+
);
283+
});
284+
285+
it('can discard changes', async () => {
286+
const changeManager = new AiAssistanceModel.ChangeManager();
287+
const cssModel = createModel();
288+
await changeManager.addChange(cssModel, frameId, {
289+
groupId: agentId,
290+
selector: 'div',
291+
className: 'ai-style-change-1',
292+
styles: {
293+
color: 'blue',
294+
},
295+
});
296+
assert(cssModel.setStyleSheetText.calledOnce);
297+
assert.deepEqual(
298+
cssModel.setStyleSheetText.lastCall.args,
299+
['1', '.ai-style-change-1 {\n div& {\n color: blue;\n }\n}', true],
300+
);
301+
await changeManager.stashChanges();
302+
assert(cssModel.setStyleSheetText.calledTwice);
303+
assert.deepEqual(
304+
cssModel.setStyleSheetText.lastCall.args,
305+
['1', '', true],
306+
);
307+
await changeManager.dropStashedChanges();
308+
assert(cssModel.setStyleSheetText.calledTwice);
309+
});
310+
});
229311
});

front_end/models/ai_assistance/ChangeManager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,36 @@ export class ChangeManager {
3131
readonly #cssModelToStylesheetId =
3232
new Map<SDK.CSSModel.CSSModel, Map<Protocol.Page.FrameId, Protocol.CSS.StyleSheetId>>();
3333
readonly #stylesheetChanges = new Map<Protocol.CSS.StyleSheetId, Change[]>();
34+
readonly #backupStylesheetChanges = new Map<Protocol.CSS.StyleSheetId, Change[]>();
35+
36+
async stashChanges(): Promise<void> {
37+
for (const [cssModel, stylesheetMap] of this.#cssModelToStylesheetId.entries()) {
38+
const stylesheetIds = Array.from(stylesheetMap.values());
39+
await Promise.allSettled(stylesheetIds.map(async id => {
40+
this.#backupStylesheetChanges.set(id, this.#stylesheetChanges.get(id) ?? []);
41+
this.#stylesheetChanges.delete(id);
42+
await cssModel.setStyleSheetText(id, '', true);
43+
}));
44+
}
45+
}
46+
47+
dropStashedChanges(): void {
48+
this.#backupStylesheetChanges.clear();
49+
}
50+
51+
async popStashedChanges(): Promise<void> {
52+
const cssModelAndStyleSheets = Array.from(this.#cssModelToStylesheetId.entries());
53+
54+
await Promise.allSettled(cssModelAndStyleSheets.map(async ([cssModel, stylesheetMap]) => {
55+
const frameAndStylesheet = Array.from(stylesheetMap.entries());
56+
return await Promise.allSettled(frameAndStylesheet.map(async ([frameId, stylesheetId]) => {
57+
const changes = this.#backupStylesheetChanges.get(stylesheetId) ?? [];
58+
return await Promise.allSettled(changes.map(async change => {
59+
return await this.addChange(cssModel, frameId, change);
60+
}));
61+
}));
62+
}));
63+
}
3464

3565
async clear(): Promise<void> {
3666
const models = Array.from(this.#cssModelToStylesheetId.keys());
@@ -39,6 +69,7 @@ export class ChangeManager {
3969
}));
4070
this.#cssModelToStylesheetId.clear();
4171
this.#stylesheetChanges.clear();
72+
this.#backupStylesheetChanges.clear();
4273
const firstFailed = results.find(result => result.status === 'rejected');
4374
if (firstFailed) {
4475
console.error(firstFailed.reason);
@@ -124,6 +155,7 @@ ${formatStyles(change.styles)}
124155
// Empty stylesheets.
125156
const results = await Promise.allSettled(stylesheetIds.map(async id => {
126157
this.#stylesheetChanges.delete(id);
158+
this.#backupStylesheetChanges.delete(id);
127159
await cssModel.setStyleSheetText(id, '', true);
128160
}));
129161
this.#cssModelToStylesheetId.delete(cssModel);

front_end/models/ai_assistance/agents/PatchAgent.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,10 @@ export class PatchAgent extends AiAgent<Workspace.Workspace.Project> {
134134
type: Host.AidaClient.ParametersTypes.ARRAY,
135135
description: 'List of file names from the project',
136136
nullable: false,
137-
items: {type: Host.AidaClient.ParametersTypes.STRING, description: 'File name'}
137+
items: {
138+
type: Host.AidaClient.ParametersTypes.STRING,
139+
description: 'File name',
140+
}
138141
}
139142
},
140143
},

front_end/panels/ai_assistance/AiAssistancePanel.test.ts

Lines changed: 103 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -626,116 +626,118 @@ describeWithMockConnection('AI Assistance Panel', () => {
626626
});
627627
});
628628

629-
it('should have empty state after clear chat', async () => {
630-
const {panel, view} = await createAiAssistancePanel({
631-
aidaClient: mockAidaClient([[{explanation: 'test'}]]),
632-
});
629+
describe('empty state', () => {
630+
it('should have empty state after clear chat', async () => {
631+
const {panel, view} = await createAiAssistancePanel({
632+
aidaClient: mockAidaClient([[{explanation: 'test'}]]),
633+
});
633634

634-
panel.handleAction('freestyler.elements-floating-button');
635-
(await view.nextInput).onTextSubmit('test');
636-
assert.deepEqual((await view.nextInput).messages, [
637-
{
638-
entity: AiAssistancePanel.ChatMessageEntity.USER,
639-
text: 'test',
640-
imageInput: undefined,
641-
},
642-
{
643-
answer: 'test',
644-
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
645-
rpcId: undefined,
646-
suggestions: undefined,
647-
steps: [],
648-
},
649-
]);
635+
panel.handleAction('freestyler.elements-floating-button');
636+
(await view.nextInput).onTextSubmit('test');
637+
assert.deepEqual((await view.nextInput).messages, [
638+
{
639+
entity: AiAssistancePanel.ChatMessageEntity.USER,
640+
text: 'test',
641+
imageInput: undefined,
642+
},
643+
{
644+
answer: 'test',
645+
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
646+
rpcId: undefined,
647+
suggestions: undefined,
648+
steps: [],
649+
},
650+
]);
650651

651-
view.input.onDeleteClick();
652-
assert.deepEqual((await view.nextInput).messages, []);
653-
assert.isUndefined(view.input.conversationType);
654-
});
652+
view.input.onDeleteClick();
653+
assert.deepEqual((await view.nextInput).messages, []);
654+
assert.isUndefined(view.input.conversationType);
655+
});
655656

656-
it('should select default agent based on open panel after clearing the chat', async () => {
657-
updateHostConfig({
658-
devToolsFreestyler: {
659-
enabled: true,
660-
},
657+
it('should select default agent based on open panel after clearing the chat', async () => {
658+
updateHostConfig({
659+
devToolsFreestyler: {
660+
enabled: true,
661+
},
662+
});
663+
UI.Context.Context.instance().setFlavor(
664+
Elements.ElementsPanel.ElementsPanel, sinon.createStubInstance(Elements.ElementsPanel.ElementsPanel));
665+
const {panel, view} = await createAiAssistancePanel({aidaClient: mockAidaClient([[{explanation: 'test'}]])});
666+
panel.handleAction('freestyler.elements-floating-button');
667+
(await view.nextInput).onTextSubmit('test');
668+
assert.deepEqual((await view.nextInput).messages, [
669+
{
670+
entity: AiAssistancePanel.ChatMessageEntity.USER,
671+
text: 'test',
672+
imageInput: undefined,
673+
},
674+
{
675+
answer: 'test',
676+
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
677+
rpcId: undefined,
678+
suggestions: undefined,
679+
steps: [],
680+
},
681+
]);
682+
view.input.onDeleteClick();
683+
assert.deepEqual((await view.nextInput).messages, []);
684+
assert.deepEqual(view.input.conversationType, AiAssistanceModel.ConversationType.STYLING);
661685
});
662-
UI.Context.Context.instance().setFlavor(
663-
Elements.ElementsPanel.ElementsPanel, sinon.createStubInstance(Elements.ElementsPanel.ElementsPanel));
664-
const {panel, view} = await createAiAssistancePanel({aidaClient: mockAidaClient([[{explanation: 'test'}]])});
665-
panel.handleAction('freestyler.elements-floating-button');
666-
(await view.nextInput).onTextSubmit('test');
667-
assert.deepEqual((await view.nextInput).messages, [
668-
{
669-
entity: AiAssistancePanel.ChatMessageEntity.USER,
670-
text: 'test',
671-
imageInput: undefined,
672-
},
673-
{
674-
answer: 'test',
675-
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
676-
rpcId: undefined,
677-
suggestions: undefined,
678-
steps: [],
679-
},
680-
]);
681-
view.input.onDeleteClick();
682-
assert.deepEqual((await view.nextInput).messages, []);
683-
assert.deepEqual(view.input.conversationType, AiAssistanceModel.ConversationType.STYLING);
684-
});
685686

686-
it('should have empty state after clear chat history', async () => {
687-
const {panel, view} = await createAiAssistancePanel(
688-
{aidaClient: mockAidaClient([[{explanation: 'test'}], [{explanation: 'test2'}]])});
687+
it('should have empty state after clear chat history', async () => {
688+
const {panel, view} = await createAiAssistancePanel(
689+
{aidaClient: mockAidaClient([[{explanation: 'test'}], [{explanation: 'test2'}]])});
689690

690-
panel.handleAction('freestyler.elements-floating-button');
691-
(await view.nextInput).onTextSubmit('User question to Freestyler?');
692-
assert.deepEqual((await view.nextInput).messages, [
693-
{
694-
entity: AiAssistancePanel.ChatMessageEntity.USER,
695-
text: 'User question to Freestyler?',
696-
imageInput: undefined,
697-
},
698-
{
699-
answer: 'test',
700-
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
701-
rpcId: undefined,
702-
suggestions: undefined,
703-
steps: [],
704-
},
705-
]);
691+
panel.handleAction('freestyler.elements-floating-button');
692+
(await view.nextInput).onTextSubmit('User question to Freestyler?');
693+
assert.deepEqual((await view.nextInput).messages, [
694+
{
695+
entity: AiAssistancePanel.ChatMessageEntity.USER,
696+
text: 'User question to Freestyler?',
697+
imageInput: undefined,
698+
},
699+
{
700+
answer: 'test',
701+
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
702+
rpcId: undefined,
703+
suggestions: undefined,
704+
steps: [],
705+
},
706+
]);
706707

707-
panel.handleAction('drjones.network-floating-button');
708-
(await view.nextInput).onTextSubmit('User question to DrJones?');
709-
assert.deepEqual((await view.nextInput).messages, [
710-
{
711-
entity: AiAssistancePanel.ChatMessageEntity.USER,
712-
text: 'User question to DrJones?',
713-
imageInput: undefined,
714-
},
715-
{
716-
answer: 'test2',
717-
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
718-
rpcId: undefined,
719-
suggestions: undefined,
720-
steps: [],
721-
},
722-
]);
708+
panel.handleAction('drjones.network-floating-button');
709+
(await view.nextInput).onTextSubmit('User question to DrJones?');
710+
assert.deepEqual((await view.nextInput).messages, [
711+
{
712+
entity: AiAssistancePanel.ChatMessageEntity.USER,
713+
text: 'User question to DrJones?',
714+
imageInput: undefined,
715+
},
716+
{
717+
answer: 'test2',
718+
entity: AiAssistancePanel.ChatMessageEntity.MODEL,
719+
rpcId: undefined,
720+
suggestions: undefined,
721+
steps: [],
722+
},
723+
]);
723724

724-
let contextMenu = getMenu(() => {
725-
view.input.onHistoryClick(new MouseEvent('click'));
726-
});
727-
const clearAll = findMenuItemWithLabel(contextMenu.footerSection(), 'Clear local chats')!;
728-
assert.isDefined(clearAll);
729-
contextMenu.invokeHandler(clearAll.id());
730-
assert.deepEqual((await view.nextInput).messages, []);
731-
assert.isUndefined(view.input.conversationType);
732-
contextMenu.discard();
733-
734-
contextMenu = getMenu(() => {
735-
view.input.onHistoryClick(new MouseEvent('click'));
725+
let contextMenu = getMenu(() => {
726+
view.input.onHistoryClick(new MouseEvent('click'));
727+
});
728+
const clearAll = findMenuItemWithLabel(contextMenu.footerSection(), 'Clear local chats')!;
729+
assert.isDefined(clearAll);
730+
contextMenu.invokeHandler(clearAll.id());
731+
assert.deepEqual((await view.nextInput).messages, []);
732+
assert.isUndefined(view.input.conversationType);
733+
contextMenu.discard();
734+
735+
contextMenu = getMenu(() => {
736+
view.input.onHistoryClick(new MouseEvent('click'));
737+
});
738+
const menuItem = findMenuItemWithLabel(contextMenu.defaultSection(), 'No past conversations');
739+
assert(menuItem);
736740
});
737-
const menuItem = findMenuItemWithLabel(contextMenu.defaultSection(), 'No past conversations');
738-
assert(menuItem);
739741
});
740742

741743
describe('cross-origin', () => {

front_end/panels/ai_assistance/AiAssistancePanel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
839839
inputPlaceholder: this.#getChatInputPlaceholder(),
840840
disclaimerText: this.#getDisclaimerText(),
841841
isTextInputEmpty: this.#isTextInputEmpty,
842+
changeManager: this.#changeManager,
842843
onNewChatClick: this.#handleNewChatRequest.bind(this),
843844
onHistoryClick: this.#onHistoryClicked.bind(this),
844845
onDeleteClick: this.#onDeleteClicked.bind(this),

0 commit comments

Comments
 (0)