Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
setInterval: "resource://gre/modules/Timer.sys.mjs",
clearInterval: "resource://gre/modules/Timer.sys.mjs",
MemoriesManager:
"moz-src:///browser/components/aiwindow/models/memories/MemoriesManager.sys.mjs",
getRecentChats:
"moz-src:///browser/components/aiwindow/models/memories/MemoriesChatSource.sys.mjs",
CONVERSATION:
"moz-src:///browser/components/aiwindow/models/memories/MemoriesConstants.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "console", function () {
return console.createInstance({
prefix: "MemoriesConversationScheduler",
maxLogLevelPref: "browser.smartwindow.memoriesLogLevel",
});
});
// Generate memories if there have been at least 10 user messages since the last run
const MEMORIES_SCHEDULER_MESSAGES_THRESHOLD = 10;
// Conversation scheduler tick every 2 mins
const MEMORIES_SCHEDULER_INTERVAL_MS = 2 * 60 * 1000;
// Cooldown period - don't run more than once every 4 hours
//TODO: pref only for test purposes. will be later reverted
const MEMORIES_SCHEDULER_COOLDOWN_MS = Services.prefs.getIntPref(
"browser.smartwindow.memoriesSchedulerCooldownInMs",
4 * 60 * 60 * 1000
);
/**
* Schedules periodic generation of conversation-based memories.
* Triggers memories generation when number of user messages exceeds the configured threshold ({@link MEMORIES_SCHEDULER_MESSAGES_THRESHOLD})
*
* E.g. Usage: MemoriesConversationScheduler.maybeInit()
*/
export class MemoriesConversationScheduler {
#intervalHandle = 0;
#destroyed = false;
#running = false;
/** @type {MemoriesConversationScheduler | null} */
static #instance = null;
static maybeInit() {
if (
!lazy.MemoriesManager.shouldEnableMemoriesFromSchedulers(
lazy.CONVERSATION
)
) {
return null;
}
if (!this.#instance) {
this.#instance = new MemoriesConversationScheduler();
}
return this.#instance;
}
constructor() {
this.#startInterval();
lazy.console.debug("Initialized");
}
/**
* Starts the interval that periodically evaluates history drift and
* potentially triggers memory generation.
*
* @throws {Error} If an interval is already running.
*/
#startInterval() {
if (this.#intervalHandle) {
throw new Error(
"Attempting to start an interval when one already existed"
);
}
this.#intervalHandle = lazy.setInterval(
this.#onInterval,
MEMORIES_SCHEDULER_INTERVAL_MS
);
}
/**
* Stops the currently running interval, if any.
*/
#stopInterval() {
if (this.#intervalHandle) {
lazy.clearInterval(this.#intervalHandle);
this.#intervalHandle = 0;
}
}
#onInterval = async () => {
if (this.#destroyed) {
lazy.console.warn("Interval fired after destroy; ignoring.");
return;
}
// Re-check gating conditions on every tick (AIWindow may have closed, prefs may have changed).
if (
!lazy.MemoriesManager.shouldEnableMemoriesFromSchedulers(
lazy.CONVERSATION
)
) {
lazy.console.debug(
"Memories from conversation scheduler no longer enabled; stopping conversation scheduler."
);
this.destroy();
// Also clear singleton so it can be re-initialized later when conditions become true again.
MemoriesConversationScheduler.#instance = null;
return;
}
if (this.#running) {
lazy.console.debug(
"Skipping run because a previous run is still in progress."
);
return;
}
this.#running = true;
this.#stopInterval();
try {
// Detect whether conversation memories were generated before.
const lastMemoryTs =
(await lazy.MemoriesManager.getLastConversationMemoryTimestamp()) ?? 0;
const now = Date.now();
// Cooldown check - don't run more than once every 4 hours.
if (
lastMemoryTs > 0 &&
now - lastMemoryTs < MEMORIES_SCHEDULER_COOLDOWN_MS
) {
lazy.console.debug(
`Cooldown not met; last run was ${Math.floor(
(now - lastMemoryTs) / (60 * 1000)
)}m ago (<${Math.floor(
MEMORIES_SCHEDULER_COOLDOWN_MS / (60 * 60 * 1000)
)}h). Skipping.`
);
return;
}
// Get user chat messages
const chatMessagesSinceLastMemory =
await lazy.getRecentChats(lastMemoryTs);
// Not enough new messages
if (
chatMessagesSinceLastMemory.length <
MEMORIES_SCHEDULER_MESSAGES_THRESHOLD
) {
return;
}
// Generate memories
await lazy.MemoriesManager.generateMemoriesFromConversationHistory();
} catch (error) {
lazy.console.error("Failed to generate conversation memories", error);
} finally {
if (
!this.#destroyed &&
lazy.MemoriesManager.shouldEnableMemoriesFromSchedulers(
lazy.CONVERSATION
)
) {
this.#startInterval();
}
this.#running = false;
}
};
destroy() {
this.#stopInterval();
this.#destroyed = true;
MemoriesConversationScheduler.#instance = null;
lazy.console.debug("Destroyed");
}
async runNowForTesting() {
await this.#onInterval();
}
}