Cache Keepalive: arm per-chat (only on MESSAGE_SENT, disarm on CHAT_CHANGED); fixes false-arming on chat open
| @@ -52,10 +52,10 @@ const defaultSettings = { | |||
| 52 | 52 | ||
| 53 | let idleTimer = null; | 53 | let idleTimer = null; |
| 54 | let keepaliveActive = false; | 54 | let keepaliveActive = false; |
| 55 | // Whether keepalive has been "armed" this browser-tab session. It stays dormant until the | 55 | // Whether keepalive is "armed" for the CURRENT chat. It stays dormant until the user sends a new |
| 56 | // first real message/generation happens in the tab, because on a fresh load (even with chat | 56 | // message in the open chat — on a freshly loaded or switched-to chat (even with history already |
| 57 | // history already present) we have no way to know whether anything is still cached | 57 | // present) we have no way to know whether its prompt is still cached server-side, so pinging would |
| 58 | // server-side — so pinging before then would be pointless. Resets on page reload. | 58 | // be pointless. Reset on page reload and whenever the chat changes. |
| 59 | let armed = false; | 59 | let armed = false; |
| 60 | // Wall-clock time (Date.now() + delay) the pending idle timer is expected to fire, or null when | 60 | // Wall-clock time (Date.now() + delay) the pending idle timer is expected to fire, or null when |
| 61 | // none is scheduled. Used to detect a timer that fired far too late (sleep/throttle). | 61 | // none is scheduled. Used to detect a timer that fired far too late (sleep/throttle). |
| @@ -103,7 +103,7 @@ function clearIdleTimer() { | |||
| 103 | /** | 103 | /** |
| 104 | * (Re)schedules the keepalive for the ACTIVE profile based on how long it has been since | 104 | * (Re)schedules the keepalive for the ACTIVE profile based on how long it has been since |
| 105 | * that profile's last message. Does nothing (and leaves the timer cleared) until keepalive | 105 | * that profile's last message. Does nothing (and leaves the timer cleared) until keepalive |
| 106 | * has been armed for this tab session, or when it is disabled for the active connection. | 106 | * has been armed for the current chat, or when it is disabled for the active connection. |
| 107 | * Called on activity AND whenever the active profile changes, so each profile keeps its own | 107 | * Called on activity AND whenever the active profile changes, so each profile keeps its own |
| 108 | * independent idle clock. | 108 | * independent idle clock. |
| 109 | */ | 109 | */ |
| @@ -130,15 +130,26 @@ function markActivity() { | |||
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | /** | 132 | /** |
| 133 | * Real message/generation activity: arms keepalive for this tab session (if it wasn't already) | 133 | * The user sent a new message in the current chat — the ONLY thing that arms keepalive. |
| 134 | * and records the activity. Loading a chat's history does NOT call this. | 134 | * Background/quiet generations, swipes, regenerations, and loading chat history deliberately do |
| 135 | * NOT arm it: none of those prove the user is actively continuing THIS chat from a known-cached | ||
| 136 | * prompt (GENERATION_STARTED in particular fires for every quiet/background generation too). | ||
| 135 | */ | 137 | */ |
| 136 | function onChatActivity() { | 138 | function onMessageSent() { |
| 137 | armed = true; | 139 | armed = true; |
| 138 | markActivity(); | 140 | markActivity(); |
| 139 | } | 141 | } |
| 140 | 142 | ||
| 141 | /** | 143 | /** |
| 144 | * Opening or switching chats disarms keepalive: a freshly loaded chat is not proof its prompt is | ||
| 145 | * still cached server-side, so we wait for a new sent message in that chat before running again. | ||
| 146 | */ | ||
| 147 | function onChatChanged() { | ||
| 148 | armed = false; | ||
| 149 | scheduleIdleTimer(); | ||
| 150 | } | ||
| 151 | |||
| 152 | /** | ||
| 142 | * Fires a background ("quiet") generation against the active model to keep the | 153 | * Fires a background ("quiet") generation against the active model to keep the |
| 143 | * server-side prompt cache warm. The full prompt prefix (current chat context + | 154 | * server-side prompt cache warm. The full prompt prefix (current chat context + |
| 144 | * the keepalive message) is sent — which is what refreshes the cache — while the | 155 | * the keepalive message) is sent — which is what refreshes the cache — while the |
| @@ -165,7 +176,7 @@ async function fireKeepalive() { | |||
| 165 | // If the timer fired much later than scheduled (machine sleep, background-tab throttling, | 176 | // If the timer fired much later than scheduled (machine sleep, background-tab throttling, |
| 166 | // etc.), far more wall-clock time has elapsed than the idle interval, so the cache has | 177 | // etc.), far more wall-clock time has elapsed than the idle interval, so the cache has |
| 167 | // almost certainly expired. Don't ping on a stale assumption — go dormant until the next | 178 | // almost certainly expired. Don't ping on a stale assumption — go dormant until the next |
| 168 | // real message re-arms keepalive, exactly like a freshly opened tab. | 179 | // sent message re-arms keepalive, exactly like a freshly opened chat. |
| 169 | if (scheduledFireTime !== null && (Date.now() - scheduledFireTime) > MAX_TIMER_DRIFT_MS) { | 180 | if (scheduledFireTime !== null && (Date.now() - scheduledFireTime) > MAX_TIMER_DRIFT_MS) { |
| 170 | console.debug('[Keepalive] Idle timer fired', Date.now() - scheduledFireTime, 'ms late; skipping and disarming until next activity.'); | 181 | console.debug('[Keepalive] Idle timer fired', Date.now() - scheduledFireTime, 'ms late; skipping and disarming until next activity.'); |
| 171 | armed = false; | 182 | armed = false; |
| @@ -318,21 +329,23 @@ async function init() { | |||
| 318 | loadSettings(); | 329 | loadSettings(); |
| 319 | setupListeners(); | 330 | setupListeners(); |
| 320 | 331 | ||
| 321 | // A real message/generation arms keepalive for this tab session and counts as using the | 332 | // Only the user sending a new message arms keepalive (for the current chat). |
| 322 | // active profile. (Keepalive stays dormant until the first one — see `armed`.) | 333 | eventSource.on(event_types.MESSAGE_SENT, onMessageSent); |
| 323 | const activationEvents = [ | 334 | |
| 324 | event_types.MESSAGE_SENT, | 335 | // These reset the active profile's idle clock (so generation time isn't counted as idle) but |
| 336 | // never arm keepalive on their own — they also fire for background/quiet generations and while | ||
| 337 | // a chat is loading. They only re-arm the timer if a sent message already armed it. | ||
| 338 | const activityEvents = [ | ||
| 325 | event_types.MESSAGE_RECEIVED, | 339 | event_types.MESSAGE_RECEIVED, |
| 326 | event_types.GENERATION_STARTED, | 340 | event_types.GENERATION_STARTED, |
| 327 | event_types.GENERATION_ENDED, | 341 | event_types.GENERATION_ENDED, |
| 328 | ]; | 342 | ]; |
| 329 | for (const event of activationEvents) { | 343 | for (const event of activityEvents) { |
| 330 | eventSource.on(event, onChatActivity); | 344 | eventSource.on(event, markActivity); |
| 331 | } | 345 | } |
| 332 | 346 | ||
| 333 | // Switching chats re-targets the per-profile idle clock but does NOT arm keepalive on its | 347 | // Opening/switching a chat disarms keepalive — it must be re-armed by a new sent message. |
| 334 | // own — opening/loading a chat is not proof that anything is cached server-side. | 348 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| 335 | eventSource.on(event_types.CHAT_CHANGED, markActivity); | ||
| 336 | 349 | ||
| 337 | // When the active profile changes, re-target the idle timer at the new profile using | 350 | // When the active profile changes, re-target the idle timer at the new profile using |
| 338 | // ITS own last-activity time (do NOT mark activity — switching is not using it). | 351 | // ITS own last-activity time (do NOT mark activity — switching is not using it). |
| @@ -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. 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> | 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 send a new message in the current chat (opening, switching to, or reloading a chat isn't proof its prompt is still cached), and it also skips a ping if its timer fired far later than scheduled (e.g. after the machine slept).</small> |
| 20 | 20 | ||
| 21 | <hr> | 21 | <hr> |
| 22 | 22 | ||