Luca448 commited on
Commit
47d819f
·
1 Parent(s): ca6398d

Savestate: Add background interval sync and instant push triggers for multi-device sync

Browse files
index.html CHANGED
@@ -51,7 +51,7 @@
51
  </head>
52
  <body class="dark-theme in-chat-view">
53
 
54
- <div id="live-version-badge" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-secondary); opacity: 0.6; z-index: 99999; pointer-events: none; font-weight: 500; font-family: monospace; letter-spacing: 1px; background: rgba(0,0,0,0.2); padding: 2px 8px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.05);">BUILD 1.4.5</div>
55
 
56
  <!-- SVG Filters for Liquid Glass Refraction -->
57
  <svg width="0" height="0" style="position: absolute; pointer-events: none;">
 
51
  </head>
52
  <body class="dark-theme in-chat-view">
53
 
54
+ <div id="live-version-badge" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-secondary); opacity: 0.6; z-index: 99999; pointer-events: none; font-weight: 500; font-family: monospace; letter-spacing: 1px; background: rgba(0,0,0,0.2); padding: 2px 8px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.05);">BUILD 1.4.6</div>
55
 
56
  <!-- SVG Filters for Liquid Glass Refraction -->
57
  <svg width="0" height="0" style="position: absolute; pointer-events: none;">
src/services/sync-service.js CHANGED
@@ -23,6 +23,13 @@ class SyncManager {
23
  // Called once on app startup to trigger initial sync if already logged in
24
  if (authService.user && authService.isCryptoUnlocked()) {
25
  this.sync();
 
 
 
 
 
 
 
26
  }
27
  }
28
 
@@ -33,11 +40,17 @@ class SyncManager {
33
 
34
  console.log('[Sync] Starting Sync...');
35
  try {
36
- await this.syncSettings(userId);
37
- await this.syncConversations(userId);
38
- await this.syncMessages(userId);
 
39
 
40
  console.log('[Sync] Sync completed successfully.');
 
 
 
 
 
41
  this.lastSyncTime = Date.now();
42
  } catch (e) {
43
  console.error('[Sync] Error during sync:', e);
@@ -92,16 +105,21 @@ class SyncManager {
92
  }, { onConflict: 'user_id, key' });
93
  }
94
 
 
95
  // Pull any setting from remote that isn't locally set (e.g. from another device)
96
  if (remoteSettings) {
97
  for (const row of remoteSettings) {
98
  const localVal = db.getSetting(row.key);
99
  if (!localVal) {
100
  const decrypted = await cryptoService.decrypt(row.value);
101
- if (decrypted) db.setSetting(row.key, decrypted);
 
 
 
102
  }
103
  }
104
  }
 
105
  }
106
 
107
  async syncConversations(userId) {
@@ -140,6 +158,7 @@ class SyncManager {
140
  }
141
  }
142
 
 
143
  // Pull from remote if remote is newer or doesn't exist locally
144
  if (remoteConvs) {
145
  for (const remote of remoteConvs) {
@@ -163,9 +182,11 @@ class SyncManager {
163
  [title, emoji, preview, remote.model_used, remote.updated_at, remote.id]
164
  );
165
  }
 
166
  }
167
  }
168
  }
 
169
  }
170
 
171
  async syncMessages(userId) {
@@ -198,6 +219,7 @@ class SyncManager {
198
  }
199
  }
200
 
 
201
  // Pull remote to local
202
  for (const remote of (remoteMsgs || [])) {
203
  if (!localIds.has(remote.id)) {
@@ -213,9 +235,11 @@ class SyncManager {
213
  "INSERT INTO messages (id, conversation_id, role, content, model_used, style_used, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
214
  [remoteFull.id, remoteFull.conversation_id, remoteFull.role, content, remoteFull.model_used, remoteFull.style_used, remoteFull.created_at]
215
  );
 
216
  }
217
  }
218
  }
 
219
  }
220
 
221
  }
 
23
  // Called once on app startup to trigger initial sync if already logged in
24
  if (authService.user && authService.isCryptoUnlocked()) {
25
  this.sync();
26
+
27
+ // Auto-Sync every 15 seconds
28
+ setInterval(() => {
29
+ if (authService.user && authService.isCryptoUnlocked()) {
30
+ this.sync();
31
+ }
32
+ }, 15000);
33
  }
34
  }
35
 
 
40
 
41
  console.log('[Sync] Starting Sync...');
42
  try {
43
+ let hasUpdates = false;
44
+ if (await this.syncSettings(userId)) hasUpdates = true;
45
+ if (await this.syncConversations(userId)) hasUpdates = true;
46
+ if (await this.syncMessages(userId)) hasUpdates = true;
47
 
48
  console.log('[Sync] Sync completed successfully.');
49
+
50
+ if (hasUpdates) {
51
+ console.log('[Sync] New remote changes found, dispatching event.');
52
+ window.dispatchEvent(new CustomEvent('cloud_sync_updated'));
53
+ }
54
  this.lastSyncTime = Date.now();
55
  } catch (e) {
56
  console.error('[Sync] Error during sync:', e);
 
105
  }, { onConflict: 'user_id, key' });
106
  }
107
 
108
+ let updated = false;
109
  // Pull any setting from remote that isn't locally set (e.g. from another device)
110
  if (remoteSettings) {
111
  for (const row of remoteSettings) {
112
  const localVal = db.getSetting(row.key);
113
  if (!localVal) {
114
  const decrypted = await cryptoService.decrypt(row.value);
115
+ if (decrypted) {
116
+ db.setSetting(row.key, decrypted);
117
+ updated = true;
118
+ }
119
  }
120
  }
121
  }
122
+ return updated;
123
  }
124
 
125
  async syncConversations(userId) {
 
158
  }
159
  }
160
 
161
+ let updated = false;
162
  // Pull from remote if remote is newer or doesn't exist locally
163
  if (remoteConvs) {
164
  for (const remote of remoteConvs) {
 
182
  [title, emoji, preview, remote.model_used, remote.updated_at, remote.id]
183
  );
184
  }
185
+ updated = true;
186
  }
187
  }
188
  }
189
+ return updated;
190
  }
191
 
192
  async syncMessages(userId) {
 
219
  }
220
  }
221
 
222
+ let updated = false;
223
  // Pull remote to local
224
  for (const remote of (remoteMsgs || [])) {
225
  if (!localIds.has(remote.id)) {
 
235
  "INSERT INTO messages (id, conversation_id, role, content, model_used, style_used, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
236
  [remoteFull.id, remoteFull.conversation_id, remoteFull.role, content, remoteFull.model_used, remoteFull.style_used, remoteFull.created_at]
237
  );
238
+ updated = true;
239
  }
240
  }
241
  }
242
+ return updated;
243
  }
244
 
245
  }
src/views/chat/chat-view.js CHANGED
@@ -5,6 +5,7 @@ import { PresentationRenderer } from '../../utils/presentation-renderer.js';
5
  import { deepResearchLoader } from '../../utils/deep-research-loader.js';
6
  import { presentationLoader } from '../../utils/presentation-loader.js';
7
  import { getApiUrl } from '../../config.js';
 
8
 
9
  window.youtubeSearchCache = window.youtubeSearchCache || {};
10
 
@@ -159,6 +160,14 @@ export class ChatView {
159
  this.setupTimelineObserver();
160
  this.setupAnnotations();
161
 
 
 
 
 
 
 
 
 
162
  // Inject dynamic styles to bypass aggressive browser CSS caching
163
  if (!document.getElementById('dynamic-source-styles')) {
164
  const style = document.createElement('style');
@@ -4180,6 +4189,9 @@ body { margin: 0; padding: 0; background: #000; overflow: hidden; display: flex;
4180
 
4181
  this.appendMessageElement('user', text, true);
4182
  this.buildTimeline();
 
 
 
4183
 
4184
  // Auto-update title if it's the first message
4185
  const msgs = db.getMessages(this.currentConversationId);
@@ -4346,6 +4358,9 @@ body { margin: 0; padding: 0; background: #000; overflow: hidden; display: flex;
4346
  db.addMessage(aiMsgId, this.currentConversationId, 'assistant', processedResponseText, model, 'default');
4347
  this.appendMessageElement('assistant', processedResponseText, false, true, text);
4348
 
 
 
 
4349
  // 4. Dynamic AI Title Generation (Only after the very first Q&A pair)
4350
  const currentMsgs = db.getMessages(this.currentConversationId);
4351
  if (currentMsgs.length === 2) { // 1 User msg + 1 AI msg
 
5
  import { deepResearchLoader } from '../../utils/deep-research-loader.js';
6
  import { presentationLoader } from '../../utils/presentation-loader.js';
7
  import { getApiUrl } from '../../config.js';
8
+ import { syncManager } from '../../services/sync-service.js';
9
 
10
  window.youtubeSearchCache = window.youtubeSearchCache || {};
11
 
 
160
  this.setupTimelineObserver();
161
  this.setupAnnotations();
162
 
163
+ // Listen for cloud sync updates to refresh sidebar
164
+ window.addEventListener('cloud_sync_updated', () => {
165
+ if (typeof this.loadHistory === 'function') {
166
+ this.loadHistory();
167
+ }
168
+ });
169
+
170
+
171
  // Inject dynamic styles to bypass aggressive browser CSS caching
172
  if (!document.getElementById('dynamic-source-styles')) {
173
  const style = document.createElement('style');
 
4189
 
4190
  this.appendMessageElement('user', text, true);
4191
  this.buildTimeline();
4192
+
4193
+ // Trigger sync instantly so the message uploads to cloud
4194
+ syncManager.sync();
4195
 
4196
  // Auto-update title if it's the first message
4197
  const msgs = db.getMessages(this.currentConversationId);
 
4358
  db.addMessage(aiMsgId, this.currentConversationId, 'assistant', processedResponseText, model, 'default');
4359
  this.appendMessageElement('assistant', processedResponseText, false, true, text);
4360
 
4361
+ // Trigger sync for AI response
4362
+ syncManager.sync();
4363
+
4364
  // 4. Dynamic AI Title Generation (Only after the very first Q&A pair)
4365
  const currentMsgs = db.getMessages(this.currentConversationId);
4366
  if (currentMsgs.length === 2) { // 1 User msg + 1 AI msg