blyon1995 Copilot commited on
Commit
255a635
·
1 Parent(s): 825c9b3

fix: videos disappear after auto-reset or localStorage quota failure

Browse files

Root causes:
1. Auto-reset calls store.clearMessages(), wiping messages from
localStorage. Any subsequent loadConversation() (sidebar click,
new chat then back) read from the now-empty store, losing the
video.
2. Large video base64 dataUrls can exceed localStorage quota; the
message is never persisted, so it vanishes on any reload.

Fix: add this._sessionMessages (Map<convId, messages[]>) as an
in-memory cache for the current page session.

- Every user/assistant message is pushed to _sessionMessages in
addition to (or instead of, when quota fails) localStorage.
- _selectConversation and _loadCurrentConversation use
_sessionConvFor(conv) which substitutes the in-memory messages
when available, so media dataUrls survive auto-resets and quota
failures for the lifetime of the page.
- Auto-reset (store.clearMessages) does NOT clear _sessionMessages —
it only resets the API context, not the visual history.
- /clean explicitly calls _clearSessionMsgs so the user's manual
wipe still works correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Files changed (1) hide show
  1. src/components/app.js +40 -8
src/components/app.js CHANGED
@@ -21,6 +21,10 @@ export class App {
21
  this.modelPicker = new ModelPicker();
22
  this.settingsModal = new SettingsModal();
23
  this._sidebarOpen = false;
 
 
 
 
24
  }
25
 
26
  init() {
@@ -34,6 +38,25 @@ export class App {
34
  this.inputBar.focus();
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  _render() {
38
  this.root.className = 'flex h-screen overflow-hidden bg-[var(--c-bg)]';
39
 
@@ -155,7 +178,7 @@ export class App {
155
  if (currentId) {
156
  const conv = store.getCurrentConversation();
157
  if (conv) {
158
- this.chat.loadConversation(conv);
159
  this.modelPicker.syncToConversation(conv);
160
  this.inputBar.setModel(this.modelPicker.getModel());
161
  this._updateContextInfo();
@@ -188,7 +211,7 @@ export class App {
188
  this._updateContextInfo();
189
  return;
190
  }
191
- this.chat.loadConversation(conv);
192
  this.modelPicker.syncToConversation(conv);
193
  this.inputBar.setModel(this.modelPicker.getModel());
194
  this.sidebar.update();
@@ -206,6 +229,7 @@ export class App {
206
  const convId = store.getCurrentConversationId();
207
  if (convId) {
208
  store.clearMessages(convId);
 
209
  this.chat.clear();
210
  this._updateContextInfo();
211
  }
@@ -260,6 +284,7 @@ export class App {
260
  try {
261
  this.chat.clearError();
262
  store.addMessage(convId, userMessage);
 
263
  this.chat.appendUserMessage(userMessage);
264
  this._updateContextInfo();
265
  renderOk = true;
@@ -305,11 +330,13 @@ export class App {
305
 
306
  // Finalize
307
  this.chat.finalizeAssistantMessage(fullText);
308
- store.addMessage(convId, {
309
  role: 'assistant',
310
  content: fullText,
311
  timestamp: new Date().toISOString(),
312
- });
 
 
313
  this._updateContextInfo();
314
  this.sidebar.update();
315
  } catch (err) {
@@ -360,6 +387,7 @@ export class App {
360
  async _handleAudioTask({ convId, model, settings, instruction, audio }) {
361
  const userMessage = this._buildAudioUserMessage(audio, instruction);
362
  store.addMessage(convId, userMessage);
 
363
  this.chat.appendUserMessage(userMessage);
364
  this._updateContextInfo();
365
 
@@ -380,11 +408,13 @@ export class App {
380
  this.chat.hideTypingIndicator();
381
  this.chat.startAssistantMessage();
382
  this.chat.finalizeAssistantMessage(transcript);
383
- store.addMessage(convId, {
384
  role: 'assistant',
385
  content: transcript,
386
  timestamp: new Date().toISOString(),
387
- });
 
 
388
  this._updateContextInfo();
389
  this.sidebar.update();
390
  return;
@@ -418,11 +448,13 @@ export class App {
418
  }
419
 
420
  this.chat.finalizeAssistantMessage(fullText);
421
- store.addMessage(convId, {
422
  role: 'assistant',
423
  content: fullText,
424
  timestamp: new Date().toISOString(),
425
- });
 
 
426
  this._updateContextInfo();
427
  this.sidebar.update();
428
  } catch (err) {
 
21
  this.modelPicker = new ModelPicker();
22
  this.settingsModal = new SettingsModal();
23
  this._sidebarOpen = false;
24
+ // In-memory message cache keyed by convId. Persists for the page session so
25
+ // that media (video/image dataUrls) remains visible even after an auto-reset
26
+ // clears localStorage, or when localStorage quota prevents persistence.
27
+ this._sessionMessages = new Map();
28
  }
29
 
30
  init() {
 
38
  this.inputBar.focus();
39
  }
40
 
41
+ // --- Session message cache helpers ---
42
+
43
+ _pushSessionMsg(convId, msg) {
44
+ if (!this._sessionMessages.has(convId)) this._sessionMessages.set(convId, []);
45
+ this._sessionMessages.get(convId).push(msg);
46
+ }
47
+
48
+ _clearSessionMsgs(convId) {
49
+ this._sessionMessages.delete(convId);
50
+ }
51
+
52
+ // Returns a conv-like object backed by in-memory messages when available,
53
+ // so that media dataUrls survive auto-resets and localStorage quota failures.
54
+ _sessionConvFor(conv) {
55
+ if (!conv) return conv;
56
+ const mem = this._sessionMessages.get(conv.id);
57
+ return mem?.length ? { ...conv, messages: mem } : conv;
58
+ }
59
+
60
  _render() {
61
  this.root.className = 'flex h-screen overflow-hidden bg-[var(--c-bg)]';
62
 
 
178
  if (currentId) {
179
  const conv = store.getCurrentConversation();
180
  if (conv) {
181
+ this.chat.loadConversation(this._sessionConvFor(conv));
182
  this.modelPicker.syncToConversation(conv);
183
  this.inputBar.setModel(this.modelPicker.getModel());
184
  this._updateContextInfo();
 
211
  this._updateContextInfo();
212
  return;
213
  }
214
+ this.chat.loadConversation(this._sessionConvFor(conv));
215
  this.modelPicker.syncToConversation(conv);
216
  this.inputBar.setModel(this.modelPicker.getModel());
217
  this.sidebar.update();
 
229
  const convId = store.getCurrentConversationId();
230
  if (convId) {
231
  store.clearMessages(convId);
232
+ this._clearSessionMsgs(convId);
233
  this.chat.clear();
234
  this._updateContextInfo();
235
  }
 
284
  try {
285
  this.chat.clearError();
286
  store.addMessage(convId, userMessage);
287
+ this._pushSessionMsg(convId, userMessage);
288
  this.chat.appendUserMessage(userMessage);
289
  this._updateContextInfo();
290
  renderOk = true;
 
330
 
331
  // Finalize
332
  this.chat.finalizeAssistantMessage(fullText);
333
+ const assistantMsg = {
334
  role: 'assistant',
335
  content: fullText,
336
  timestamp: new Date().toISOString(),
337
+ };
338
+ store.addMessage(convId, assistantMsg);
339
+ this._pushSessionMsg(convId, assistantMsg);
340
  this._updateContextInfo();
341
  this.sidebar.update();
342
  } catch (err) {
 
387
  async _handleAudioTask({ convId, model, settings, instruction, audio }) {
388
  const userMessage = this._buildAudioUserMessage(audio, instruction);
389
  store.addMessage(convId, userMessage);
390
+ this._pushSessionMsg(convId, userMessage);
391
  this.chat.appendUserMessage(userMessage);
392
  this._updateContextInfo();
393
 
 
408
  this.chat.hideTypingIndicator();
409
  this.chat.startAssistantMessage();
410
  this.chat.finalizeAssistantMessage(transcript);
411
+ const assistantMsg = {
412
  role: 'assistant',
413
  content: transcript,
414
  timestamp: new Date().toISOString(),
415
+ };
416
+ store.addMessage(convId, assistantMsg);
417
+ this._pushSessionMsg(convId, assistantMsg);
418
  this._updateContextInfo();
419
  this.sidebar.update();
420
  return;
 
448
  }
449
 
450
  this.chat.finalizeAssistantMessage(fullText);
451
+ const assistantMsg = {
452
  role: 'assistant',
453
  content: fullText,
454
  timestamp: new Date().toISOString(),
455
+ };
456
+ store.addMessage(convId, assistantMsg);
457
+ this._pushSessionMsg(convId, assistantMsg);
458
  this._updateContextInfo();
459
  this.sidebar.update();
460
  } catch (err) {