Cache Keepalive: stay dormant until the first message/generation of the tab session

4b6db4ab8cabb9ea2efba3790631e37f4f6a1c64

permissionBRICK <permissionBRICK@gmail.com>

2 files changed, +30 -11Ignore whitespace
public/scripts/extensions/keepalive/index.js+29 -10
@@ -47,6 +47,11 @@ const defaultSettings = {
4747
48let idleTimer = null;48let idleTimer = null;
49let keepaliveActive = false;49let keepaliveActive = false;
50// Whether keepalive has been "armed" this browser-tab session. It stays dormant until the
51// first real message/generation happens in the tab, because on a fresh load (even with chat
52// history already present) we have no way to know whether anything is still cached
53// server-side — so pinging before then would be pointless. Resets on page reload.
54let armed = false;
50// Per-profile "time since last message", keyed by connection profile id (or NONE_KEY).55// Per-profile "time since last message", keyed by connection profile id (or NONE_KEY).
51// In-memory only: on reload every profile is treated as freshly active.56// In-memory only: on reload every profile is treated as freshly active.
52const lastActivity = {};57const lastActivity = {};
@@ -89,13 +94,14 @@ function clearIdleTimer() {
8994
90/**95/**
91 * (Re)schedules the keepalive for the ACTIVE profile based on how long it has been since96 * (Re)schedules the keepalive for the ACTIVE profile based on how long it has been since
92 * that profile's last message. Does nothing (and leaves the timer cleared) when keepalive is97 * that profile's last message. Does nothing (and leaves the timer cleared) until keepalive
93 * disabled for the active connection. Called on activity AND whenever the active profile98 * has been armed for this tab session, or when it is disabled for the active connection.
94 * changes, so each profile keeps its own independent idle clock.99 * Called on activity AND whenever the active profile changes, so each profile keeps its own
100 * independent idle clock.
95 */101 */
96function scheduleIdleTimer() {102function scheduleIdleTimer() {
97 clearIdleTimer();103 clearIdleTimer();
98 if (!isKeepaliveEnabledForActive()) {104 if (!armed || !isKeepaliveEnabledForActive()) {
99 return;105 return;
100 }106 }
101 const timeoutMs = Math.max(60, Number(extension_settings[MODULE].timeoutSeconds) || 295) * 1000;107 const timeoutMs = Math.max(60, Number(extension_settings[MODULE].timeoutSeconds) || 295) * 1000;
@@ -113,6 +119,15 @@ function markActivity() {
113}119}
114120
115/**121/**
122 * Real message/generation activity: arms keepalive for this tab session (if it wasn't already)
123 * and records the activity. Loading a chat's history does NOT call this.
124 */
125function onChatActivity() {
126 armed = true;
127 markActivity();
128}
129
130/**
116 * Fires a background ("quiet") generation against the active model to keep the131 * Fires a background ("quiet") generation against the active model to keep the
117 * server-side prompt cache warm. The full prompt prefix (current chat context +132 * server-side prompt cache warm. The full prompt prefix (current chat context +
118 * the keepalive message) is sent — which is what refreshes the cache — while the133 * the keepalive message) is sent — which is what refreshes the cache — while the
@@ -132,7 +147,7 @@ function markActivity() {
132 * deactivateSendButtons() reflects the busy state — and release both in `finally`.147 * deactivateSendButtons() reflects the busy state — and release both in `finally`.
133 */148 */
134async function fireKeepalive() {149async function fireKeepalive() {
135 if (!isKeepaliveEnabledForActive()) {150 if (!armed || !isKeepaliveEnabledForActive()) {
136 return;151 return;
137 }152 }
138153
@@ -280,18 +295,22 @@ async function init() {
280 loadSettings();295 loadSettings();
281 setupListeners();296 setupListeners();
282297
283 // Any chat or generation activity counts as "using" the active profile.298 // A real message/generation arms keepalive for this tab session and counts as using the
284 const activityEvents = [299 // active profile. (Keepalive stays dormant until the first one — see `armed`.)
300 const activationEvents = [
285 event_types.MESSAGE_SENT,301 event_types.MESSAGE_SENT,
286 event_types.MESSAGE_RECEIVED,302 event_types.MESSAGE_RECEIVED,
287 event_types.GENERATION_STARTED,303 event_types.GENERATION_STARTED,
288 event_types.GENERATION_ENDED,304 event_types.GENERATION_ENDED,
289 event_types.CHAT_CHANGED,
290 ];305 ];
291 for (const event of activityEvents) {306 for (const event of activationEvents) {
292 eventSource.on(event, markActivity);307 eventSource.on(event, onChatActivity);
293 }308 }
294309
310 // Switching chats re-targets the per-profile idle clock but does NOT arm keepalive on its
311 // own — opening/loading a chat is not proof that anything is cached server-side.
312 eventSource.on(event_types.CHAT_CHANGED, markActivity);
313
295 // When the active profile changes, re-target the idle timer at the new profile using314 // When the active profile changes, re-target the idle timer at the new profile using
296 // ITS own last-activity time (do NOT mark activity — switching is not using it).315 // ITS own last-activity time (do NOT mark activity — switching is not using it).
297 eventSource.on(event_types.CONNECTION_PROFILE_LOADED, scheduleIdleTimer);316 eventSource.on(event_types.CONNECTION_PROFILE_LOADED, scheduleIdleTimer);
public/scripts/extensions/keepalive/settings.html+1 -1
@@ -16,7 +16,7 @@
16 <label for="keepalive_message" data-i18n="ext_keepalive_message">Keepalive message</label>16 <label for="keepalive_message" data-i18n="ext_keepalive_message">Keepalive message</label>
17 <input id="keepalive_message" class="text_pole" type="text" />17 <input id="keepalive_message" class="text_pole" type="text" />
1818
19 <small data-i18n="ext_keepalive_help">After this many seconds without activity on the active connection profile, the current chat plus this message (sent as a hidden user message) is submitted to that profile to keep its server-side prompt cache warm. The completion is capped to a single token and is never saved to the chat. Sending is briefly locked while it runs. Only the profile you're currently using is pinged; each profile has its own idle timer.</small>19 <small data-i18n="ext_keepalive_help">After this many seconds without activity on the active connection profile, the current chat plus this message (sent as a hidden user message) is submitted to that profile to keep its server-side prompt cache warm. The completion is capped to a single token and is never saved to the chat. Sending is briefly locked while it runs. Only the profile you're currently using is pinged; each profile has its own idle timer. Keepalive stays off until you've sent at least one message since opening the tab (a freshly loaded chat isn't proof anything is still cached).</small>
2020
21 <hr>21 <hr>
2222