Skip to content

Commit 8648a37

Browse files
Samiya CaurDevtools-frontend LUCI CQ
authored andcommitted
[AiAssistance] Add screenshot image input to history
There is a storage limit (similar to how it is set and calculated in front_end/panels/recorder/models/ScreenshotStorage.ts) Pending: A placeholder image in case image is no longer available in history Bug: 394029490 Change-Id: I797eb9aa77145585e51cbb4df17034f1bc21d15e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6276245 Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Samiya Caur <[email protected]>
1 parent 91746ae commit 8648a37

File tree

12 files changed

+330
-23
lines changed

12 files changed

+330
-23
lines changed

front_end/panels/ai_assistance/AiAssistancePanel.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,12 +613,11 @@ describeWithMockConnection('AI Assistance Panel', () => {
613613
contextMenu.invokeHandler(freestylerEntry.id());
614614
});
615615
assert.isTrue(view.lastCall.args[0].isReadOnly);
616-
// Currently history should not store image input
617616
assert.deepEqual(view.lastCall.args[0].messages, [
618617
{
619618
entity: AiAssistance.ChatMessageEntity.USER,
620619
text: 'User question to Freestyler?',
621-
imageInput: undefined,
620+
imageInput,
622621
},
623622
{
624623
answer: 'test',

front_end/panels/ai_assistance/AiAssistancePanel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
278278
accountFullName: syncInfo.accountFullName,
279279
};
280280

281-
this.#conversations = AiHistoryStorage.instance().getHistory().map(item => Conversation.fromSerialized(item));
281+
this.#conversations = AiHistoryStorage.instance().getHistory().map(item => {
282+
return new Conversation(item.type, item.history, item.id, true);
283+
});
282284

283285
if (isAiAssistancePatchingEnabled()) {
284286
// TODO: this is temporary code that should be replaced with workflow selection flow.
@@ -961,12 +963,14 @@ export class AiAssistancePanel extends UI.Panel.Panel {
961963
throw new Error('cross-origin context data should not be included');
962964
}
963965

966+
const image = isAiAssistanceMultimodalInputEnabled() ? imageInput : undefined;
967+
const imageId = image ? crypto.randomUUID() : undefined;
964968
const runner = this.#currentAgent.run(
965969
text, {
966970
signal,
967971
selected: context,
968972
},
969-
isAiAssistanceMultimodalInputEnabled() ? imageInput : undefined);
973+
image, imageId);
970974
UI.ARIAUtils.alert(lockedString(UIStringsNotTranslate.answerLoading));
971975
await this.#doConversation(this.#saveResponsesToCurrentConversation(runner));
972976
UI.ARIAUtils.alert(lockedString(UIStringsNotTranslate.answerReady));

front_end/panels/ai_assistance/AiHistoryStorage.test.ts

Lines changed: 194 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,39 @@ describe('AiHistoryStorage', () => {
2222
type: AiAssistance.ConversationType.NETWORK,
2323
history: [],
2424
};
25+
const agent4: AiAssistance.SerializedConversation = {
26+
id: 'id4',
27+
type: AiAssistance.ConversationType.STYLING,
28+
history: [
29+
{
30+
type: AiAssistance.ResponseType.USER_QUERY,
31+
query: 'text',
32+
imageId: 'image-id1',
33+
imageInput: undefined,
34+
},
35+
{
36+
type: AiAssistance.ResponseType.ANSWER,
37+
text: 'answer',
38+
complete: true,
39+
},
40+
{
41+
type: AiAssistance.ResponseType.USER_QUERY,
42+
query: 'text',
43+
imageId: 'image-id2',
44+
imageInput: undefined,
45+
},
46+
],
47+
};
48+
const serializedImage1: AiAssistance.SerializedImage = {
49+
id: 'image-id1',
50+
data: 'imageInput',
51+
mimeType: 'image/jpeg',
52+
};
53+
const serializedImage2: AiAssistance.SerializedImage = {
54+
id: 'image-id2',
55+
data: 'imageInput',
56+
mimeType: 'image/jpeg',
57+
};
2558

2659
beforeEach(() => {
2760
let data: Record<string, string> = {};
@@ -48,8 +81,8 @@ describe('AiHistoryStorage', () => {
4881
});
4982
});
5083

51-
function getStorage() {
52-
return AiAssistance.AiHistoryStorage.instance(true);
84+
function getStorage(maxStorageSize?: number) {
85+
return AiAssistance.AiHistoryStorage.instance({forceNew: true, maxStorageSize});
5386
}
5487

5588
it('should create and retrieve history entry', async () => {
@@ -141,6 +174,77 @@ describe('AiHistoryStorage', () => {
141174
},
142175
],
143176
);
177+
assert.deepEqual(
178+
storage.getImageHistory(),
179+
[],
180+
);
181+
182+
await storage.upsertImage(serializedImage1);
183+
await storage.upsertImage(serializedImage2);
184+
await storage.upsertHistoryEntry(agent4);
185+
assert.deepEqual(
186+
storage.getHistory(),
187+
[
188+
{
189+
id: 'id1',
190+
type: 'freestyler' as AiAssistance.ConversationType,
191+
history: [
192+
{
193+
type: AiAssistance.ResponseType.USER_QUERY,
194+
query: 'text',
195+
},
196+
],
197+
},
198+
{
199+
id: 'id2',
200+
type: 'drjones-file' as AiAssistance.ConversationType,
201+
history: [],
202+
},
203+
{
204+
id: 'id3',
205+
type: 'drjones-network-request' as AiAssistance.ConversationType,
206+
history: [],
207+
},
208+
{
209+
id: 'id4',
210+
type: 'freestyler' as AiAssistance.ConversationType,
211+
history: [
212+
{
213+
type: AiAssistance.ResponseType.USER_QUERY,
214+
query: 'text',
215+
imageId: 'image-id1',
216+
imageInput: undefined,
217+
},
218+
{
219+
type: AiAssistance.ResponseType.ANSWER,
220+
text: 'answer',
221+
complete: true,
222+
},
223+
{
224+
type: AiAssistance.ResponseType.USER_QUERY,
225+
query: 'text',
226+
imageId: 'image-id2',
227+
imageInput: undefined,
228+
},
229+
],
230+
},
231+
],
232+
);
233+
assert.deepEqual(
234+
storage.getImageHistory(),
235+
[
236+
{
237+
id: 'image-id1',
238+
data: 'imageInput',
239+
mimeType: 'image/jpeg',
240+
},
241+
{
242+
id: 'image-id2',
243+
data: 'imageInput',
244+
mimeType: 'image/jpeg',
245+
}
246+
],
247+
);
144248
});
145249

146250
it('should delete a single entry', async () => {
@@ -155,9 +259,38 @@ describe('AiHistoryStorage', () => {
155259
{
156260
id: 'id1',
157261
type: 'freestyler' as AiAssistance.ConversationType,
158-
history: [
262+
history: [],
263+
},
264+
{
265+
id: 'id3',
266+
type: 'drjones-network-request' as AiAssistance.ConversationType,
267+
history: [],
268+
},
269+
],
270+
);
271+
});
159272

160-
],
273+
it('should delete image history entry', async () => {
274+
const storage = getStorage();
275+
await storage.upsertHistoryEntry(agent1);
276+
await storage.upsertHistoryEntry(agent2);
277+
await storage.upsertHistoryEntry(agent3);
278+
await storage.upsertImage(serializedImage1);
279+
await storage.upsertImage(serializedImage2);
280+
await storage.upsertHistoryEntry(agent4);
281+
await storage.deleteHistoryEntry('id4');
282+
assert.deepEqual(
283+
storage.getHistory(),
284+
[
285+
{
286+
id: 'id1',
287+
type: 'freestyler' as AiAssistance.ConversationType,
288+
history: [],
289+
},
290+
{
291+
id: 'id2',
292+
type: 'drjones-file' as AiAssistance.ConversationType,
293+
history: [],
161294
},
162295
{
163296
id: 'id3',
@@ -166,17 +299,74 @@ describe('AiHistoryStorage', () => {
166299
},
167300
],
168301
);
302+
assert.deepEqual(
303+
storage.getImageHistory(),
304+
[],
305+
);
169306
});
170307

171308
it('should delete all entries', async () => {
172309
const storage = getStorage();
173310
await storage.upsertHistoryEntry(agent1);
174311
await storage.upsertHistoryEntry(agent2);
175312
await storage.upsertHistoryEntry(agent3);
313+
await storage.upsertImage(serializedImage1);
314+
await storage.upsertImage(serializedImage2);
315+
await storage.upsertHistoryEntry(agent4);
176316
await storage.deleteAll();
177317
assert.deepEqual(
178318
storage.getHistory(),
179319
[],
180320
);
321+
assert.deepEqual(
322+
storage.getImageHistory(),
323+
[],
324+
);
325+
});
326+
327+
it('should limit the amount of stored images', async () => {
328+
const storage = getStorage(2);
329+
330+
await storage.upsertImage({
331+
id: 'image-id1',
332+
data: '1',
333+
mimeType: 'image/jpeg',
334+
});
335+
await storage.upsertHistoryEntry(agent1);
336+
await storage.upsertImage({
337+
id: 'image-id2',
338+
data: '2',
339+
mimeType: 'image/jpeg',
340+
});
341+
await storage.upsertImage({
342+
id: 'image-id3',
343+
data: '3',
344+
mimeType: 'image/jpeg',
345+
});
346+
await storage.upsertHistoryEntry(agent2);
347+
await storage.upsertImage({
348+
id: 'image-id4',
349+
data: '4',
350+
mimeType: 'image/jpeg',
351+
});
352+
await storage.upsertHistoryEntry(agent3);
353+
const imageHistory = storage.getImageHistory();
354+
const imageData1 = imageHistory.find(item => item.id === 'image-id1');
355+
const imageData2 = imageHistory.find(item => item.id === 'image-id2');
356+
const imageData3 = imageHistory.find(item => item.id === 'image-id3');
357+
const imageData4 = imageHistory.find(item => item.id === 'image-id4');
358+
359+
assert.notExists(imageData1);
360+
assert.notExists(imageData2);
361+
assert.deepEqual(imageData3, {
362+
id: 'image-id3',
363+
data: '3',
364+
mimeType: 'image/jpeg',
365+
});
366+
assert.deepEqual(imageData4, {
367+
id: 'image-id4',
368+
data: '4',
369+
mimeType: 'image/jpeg',
370+
});
181371
});
182372
});

0 commit comments

Comments
 (0)