Skip to content

Commit deae76b

Browse files
nickgratoMardak
authored andcommitted
Bug 2011774 - Normalize tokens.existing_memories into message.memoriesApplied and persist token data r=Mardak,ai-frontend-reviewers
Responses currently include both memoriesApplied and tokens (including tokens.existing_memories). We should treat message.memoriesApplied as the single source of truth for the UI by normalizing/merging tokens.existing_memories into message.memoriesApplied during response handling (ideally before dispatching to the child so the child stays dumb). Persist the token-derived memory data so reloaded/continued conversations have the same memoriesApplied state without needing stream tokens. Differential Revision: https://phabricator.services.mozilla.com/D280096
1 parent 96800d9 commit deae76b

4 files changed

Lines changed: 121 additions & 5 deletions

File tree

browser/components/aiwindow/ui/components/ai-window/ai-window.mjs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,25 @@ export class AIWindow extends MozLitElement {
215215
this.#updateConversation();
216216
}
217217

218+
/**
219+
* Processes tokens from the AI response stream and updates the message.
220+
* Adds all tokens to their respective arrays in the tokens object and
221+
* builds the memoriesApplied array for existing_memory tokens.
222+
*
223+
* @param {Array<{key: string, value: string}>} tokens - Array of parsed tokens from the stream
224+
* @param {ChatMessage} currentMessage - The message object being updated
225+
*/
226+
handleTokens = (tokens, currentMessage) => {
227+
tokens.forEach(({ key, value }) => {
228+
currentMessage.tokens[key].push(value);
229+
230+
// Build Applied Memories Array
231+
if (key === "existing_memory") {
232+
currentMessage.memoriesApplied.push(value);
233+
}
234+
});
235+
};
236+
218237
/**
219238
* Fetches an AI response based on the current user prompt.
220239
* Validates the prompt, updates conversation state, streams the response,
@@ -280,16 +299,17 @@ export class AIWindow extends MozLitElement {
280299
};
281300
}
282301

302+
if (!currentMessage.memoriesApplied) {
303+
currentMessage.memoriesApplied = [];
304+
}
305+
283306
if (plainText) {
284307
currentMessage.content.body += plainText;
285308
}
286309

287310
if (tokens?.length) {
288-
tokens.forEach(token => {
289-
currentMessage.tokens[token.key].push(token.value);
290-
});
311+
this.handleTokens(tokens, currentMessage);
291312
}
292-
293313
this.#updateConversation();
294314
this.#dispatchMessageToChatContent(currentMessage);
295315
this.requestUpdate?.();

browser/components/aiwindow/ui/modules/ChatSql.sys.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ INSERT INTO message (
111111
jsonb(:web_search_queries_jsonb)
112112
)
113113
ON CONFLICT(message_id) DO UPDATE SET
114-
is_active_branch = :is_active_branch;
114+
is_active_branch = :is_active_branch,
115+
memories_applied_jsonb = jsonb(:memories_applied_jsonb),
116+
content_jsonb = jsonb(:content);
115117
`;
116118

117119
export const CONVERSATIONS_MOST_RECENT = `

browser/components/aiwindow/ui/test/browser/browser.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ support-files = [
2828

2929
["browser_aiwindow_memories.js"]
3030

31+
["browser_aiwindow_memory_tokens.js"]
32+
3133
["browser_aiwindow_newtab_preload.js"]
3234

3335
["browser_aiwindow_search_button.js"]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
* http://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
"use strict";
5+
6+
add_task(async function test_handleTokens_builds_memoriesApplied_array() {
7+
await SpecialPowers.pushPrefEnv({
8+
set: [["browser.aiwindow.enabled", true]],
9+
});
10+
11+
// Open AI Window to get access to ai-window element
12+
const newAIWindow = await BrowserTestUtils.openNewBrowserWindow({
13+
openerWindow: null,
14+
aiWindow: true,
15+
});
16+
const browser = newAIWindow.gBrowser.selectedBrowser;
17+
18+
await SpecialPowers.spawn(browser, [], async () => {
19+
await content.customElements.whenDefined("ai-window");
20+
21+
const aiWindowElement = content.document.querySelector("ai-window");
22+
Assert.ok(aiWindowElement, "ai-window element should exist");
23+
24+
// Create a mock message that matches the real message structure
25+
const mockMessage = {
26+
tokens: {
27+
search: [],
28+
existing_memory: [],
29+
},
30+
memoriesApplied: [],
31+
};
32+
33+
// Test tokens including memory and non-memory tokens
34+
const testTokens = [
35+
{ key: "existing_memory", value: "user asked about cats" },
36+
{ key: "search", value: "cat behavior" }, // non-memory token
37+
{ key: "existing_memory", value: "user prefers tabby cats" },
38+
{ key: "existing_memory", value: "user has a pet cat named Fluffy" },
39+
];
40+
41+
// Call the actual handleTokens method
42+
aiWindowElement.handleTokens(testTokens, mockMessage);
43+
44+
// Verify memory tokens were added to memoriesApplied
45+
Assert.equal(
46+
mockMessage.memoriesApplied.length,
47+
3,
48+
"Should have 3 memory tokens in memoriesApplied"
49+
);
50+
Assert.equal(
51+
mockMessage.memoriesApplied[0],
52+
"user asked about cats",
53+
"First memory token should match"
54+
);
55+
Assert.equal(
56+
mockMessage.memoriesApplied[1],
57+
"user prefers tabby cats",
58+
"Second memory token should match"
59+
);
60+
Assert.equal(
61+
mockMessage.memoriesApplied[2],
62+
"user has a pet cat named Fluffy",
63+
"Third memory token should match"
64+
);
65+
66+
// Verify all tokens (including non-memory) were added to tokens object
67+
Assert.equal(
68+
mockMessage.tokens.existing_memory.length,
69+
3,
70+
"Should have 3 existing_memory tokens in tokens object"
71+
);
72+
Assert.equal(
73+
mockMessage.tokens.search.length,
74+
1,
75+
"Should have 1 search token"
76+
);
77+
Assert.equal(
78+
mockMessage.tokens.search[0],
79+
"cat behavior",
80+
"Search token should match"
81+
);
82+
83+
// Verify non-memory tokens were NOT added to memoriesApplied
84+
Assert.ok(
85+
!mockMessage.memoriesApplied.includes("cat behavior"),
86+
"Search tokens should not be in memoriesApplied"
87+
);
88+
});
89+
90+
await BrowserTestUtils.closeWindow(newAIWindow);
91+
await SpecialPowers.popPrefEnv();
92+
});

0 commit comments

Comments
 (0)