Cache Keepalive: stay dormant until the first message/generation of the tab session
| @@ -47,6 +47,11 @@ const defaultSettings = { | |||
| 47 | 47 | ||
| 48 | let idleTimer = null; | 48 | let idleTimer = null; |
| 49 | let keepaliveActive = false; | 49 | let 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. | ||
| 54 | let 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. |
| 52 | const lastActivity = {}; | 57 | const lastActivity = {}; |
| @@ -89,13 +94,14 @@ function clearIdleTimer() { | |||
| 89 | 94 | ||
| 90 | /** | 95 | /** |
| 91 | * (Re)schedules the keepalive for the ACTIVE profile based on how long it has been since | 96 | * (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 is | 97 | * 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 profile | 98 | * 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 | */ |
| 96 | function scheduleIdleTimer() { | 102 | function 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 | } |
| 114 | 120 | ||
| 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 | */ | ||
| 125 | function onChatActivity() { | ||
| 126 | armed = true; | ||
| 127 | markActivity(); | ||
| 128 | } | ||
| 129 | |||
| 130 | /** | ||
| 116 | * Fires a background ("quiet") generation against the active model to keep the | 131 | * 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 the | 133 | * 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 | */ |
| 134 | async function fireKeepalive() { | 149 | async function fireKeepalive() { |
| 135 | if (!isKeepaliveEnabledForActive()) { | 150 | if (!armed || !isKeepaliveEnabledForActive()) { |
| 136 | return; | 151 | return; |
| 137 | } | 152 | } |
| 138 | 153 | ||
| @@ -280,18 +295,22 @@ async function init() { | |||
| 280 | loadSettings(); | 295 | loadSettings(); |
| 281 | setupListeners(); | 296 | setupListeners(); |
| 282 | 297 | ||
| 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 | } |
| 294 | 309 | ||
| 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 using | 314 | // 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); |
| @@ -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" /> |
| 18 | 18 | ||
| 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> |
| 20 | 20 | ||
| 21 | <hr> | 21 | <hr> |
| 22 | 22 | ||