Skip to content

Commit 3749394

Browse files
committed
fix(sync): preserve personalization across devices
Fixes #815
1 parent fb5f76f commit 3749394

12 files changed

Lines changed: 778 additions & 27 deletions

File tree

docs/en/guide/cloud-sync.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ Voyager Cloud Sync backs up your folders, prompt library, and starred messages t
44

55
## What Gets Synced
66

7-
| Data Type | Synced | Details |
8-
| -------------------- | ------ | ---------------------------------------------------------- |
9-
| Folder structure | Yes | All folders, nesting, colors, and conversation assignments |
10-
| Prompt library | Yes | All saved prompts with tags and folder organization |
11-
| Starred messages | Yes | Timeline bookmarks from any conversation |
12-
| Extension settings | No | Settings remain local to each browser |
13-
| Conversation content | No | Chat content stays on Google's servers |
7+
| Data Type | Synced | Details |
8+
| -------------------- | ------ | ---------------------------------------------------------------------------- |
9+
| Folder structure | Yes | All folders, nesting, colors, and conversation assignments |
10+
| Prompt library | Yes | All saved prompts with tags and folder organization |
11+
| Starred messages | Yes | Timeline bookmarks from any conversation |
12+
| Extension settings | Yes | Cross-device preferences such as sorting, colors, and input behavior |
13+
| Plugin configuration | Yes | Install/enable state and plugin settings; site permissions remain per-device |
14+
| Conversation content | No | Chat content stays on Google's servers |
1415

1516
## Features
1617

@@ -19,7 +20,7 @@ Voyager Cloud Sync backs up your folders, prompt library, and starred messages t
1920
- **Flexible Sync**: Support for manual uploading and downloading/merging of data.
2021

2122
::: info
22-
**Coming Soon**: The next version will support syncing starred conversations.
23+
Device identifiers, access tokens, caches, viewport coordinates, and temporary runtime state are intentionally not synced as personalization settings.
2324
:::
2425

2526
## How to Use
@@ -58,8 +59,8 @@ While Cloud Sync offers great convenience, we strongly recommend that you also p
5859
## How It Works
5960

6061
1. **Authorization**: You sign in with Google and grant Voyager the `drive.file` permission — the most restrictive Drive scope available. Voyager can only see files it created.
61-
2. **Upload**: When you click "Upload to Cloud," Voyager serializes your folders, prompts, and stars into a JSON file and writes it to a dedicated folder in your Google Drive.
62-
3. **Download & Merge**: When you click "Download & Merge" on another device, Voyager reads the JSON file and intelligently merges it with your local data. New folders and prompts are added; existing ones are updated without duplicating.
62+
2. **Upload**: When you click "Upload to Cloud," Voyager writes separate JSON files for folders, prompts, stars, forks, timeline hierarchy, personalization settings, and plugin configuration to a dedicated folder in your Google Drive. Highlight data is included when highlight cloud sync is enabled.
63+
3. **Download & Merge**: When you click "Download & Merge" on another device, Voyager reads those files and merges them by data type. New folders and prompts are added without duplication, cloud preferences are restored, and plugin entries are merged by plugin ID.
6364
4. **No background sync**: Sync is manual and on-demand. Voyager never syncs without your explicit action.
6465

6566
## Supported Platforms

docs/guide/cloud-sync.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
## 功能特点
66

77
- **多端同步**:利用 Google Drive 云端存储,在多台电脑上同步您的配置。
8-
- **全面覆盖**支持同步 **文件夹结构****提示词库 (Prompts)** 等核心数据
8+
- **全面覆盖**支持同步文件夹、提示词库、星标、分支、时间线层级、高亮、个性化设置和插件配置
99
- **数据安全**:数据存储在您自己的 Google Drive 空间中,不经过第三方服务器,确保隐私安全。
1010
- **灵活同步**:支持手动上传、下载合并数据。
1111

@@ -16,6 +16,16 @@
1616
3. 点击 **使用 Google 登录** 并完成授权。
1717
4. 授权成功后,点击 **上传到云端** 将本地数据同步到云端,或点击 **从云端下载合并** 将云端数据同步到本地。
1818

19+
## 同步内容
20+
21+
Voyager 会把不同类型的数据保存为独立 JSON 文件,避免大文件中的某一类数据覆盖其他内容:
22+
23+
- `gemini-voyager-settings.json`:跨设备的个性化设置,例如会话排序、文件夹字号、颜色和输入行为。
24+
- `gemini-voyager-plugins.json`:插件安装、启用状态及插件内部设置。插件所需的网站权限仍需在每台设备上单独授权。
25+
- 文件夹、提示词、星标、分支、时间线层级和高亮分别使用各自的同步文件;高亮内容仅在开启高亮云同步后上传。
26+
27+
窗口坐标、缓存、访问令牌、设备 ID 和临时任务状态不会写入个性化设置文件。
28+
1929
### 💡 极速同步
2030

2131
最简单的方法是在左侧侧边栏的**文件夹区域顶部**,直接点击“上传到云端”或“下载并合并”按钮。

src/core/services/GoogleDriveSyncService.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* Stores folders, prompts, and other sync data as separate files:
88
* - gemini-voyager-folders.json
99
* - gemini-voyager-prompts.json
10+
* - gemini-voyager-settings.json
11+
* - gemini-voyager-plugins.json
1012
* - gemini-voyager-starred.json
1113
* - gemini-voyager-highlights.json (account-scoped; independent from legacy sync data)
1214
*/
@@ -17,6 +19,7 @@ import type {
1719
ForkExportPayload,
1820
ForkNodesDataSync,
1921
HighlightExportPayload,
22+
PluginStateExportPayload,
2023
PromptExportPayload,
2124
PromptItem,
2225
SettingsExportPayload,
@@ -33,11 +36,13 @@ import { DEFAULT_SYNC_STATE } from '@/core/types/sync';
3336
import { isBrave } from '@/core/utils/browser';
3437
import { hashString } from '@/core/utils/hash';
3538
import { EXTENSION_VERSION } from '@/core/utils/version';
39+
import type { PluginStateMap } from '@/features/plugins/storage/pluginState';
3640

3741
const FOLDERS_FILE_NAME = 'gemini-voyager-folders.json';
3842
const AISTUDIO_FOLDERS_FILE_NAME = 'gemini-voyager-aistudio-folders.json';
3943
const PROMPTS_FILE_NAME = 'gemini-voyager-prompts.json';
4044
const SETTINGS_FILE_NAME = 'gemini-voyager-settings.json';
45+
const PLUGINS_FILE_NAME = 'gemini-voyager-plugins.json';
4146
const STARRED_FILE_NAME = 'gemini-voyager-starred.json';
4247
const FORKS_FILE_NAME = 'gemini-voyager-forks.json';
4348
const TIMELINE_HIERARCHY_FILE_NAME = 'gemini-voyager-timeline-hierarchy.json';
@@ -69,6 +74,7 @@ export class GoogleDriveSyncService {
6974
private aistudioFoldersFileId: string | null = null;
7075
private promptsFileId: string | null = null;
7176
private settingsFileId: string | null = null;
77+
private pluginsFileId: string | null = null;
7278
private starredFileId: string | null = null;
7379
private forksFileId: string | null = null;
7480
private timelineHierarchyFileId: string | null = null;
@@ -140,6 +146,7 @@ export class GoogleDriveSyncService {
140146
this.aistudioFoldersFileId = null;
141147
this.promptsFileId = null;
142148
this.settingsFileId = null;
149+
this.pluginsFileId = null;
143150
this.starredFileId = null;
144151
this.forksFileId = null;
145152
this.timelineHierarchyFileId = null;
@@ -169,6 +176,7 @@ export class GoogleDriveSyncService {
169176
accountScope: SyncAccountScope | null = null,
170177
timelineHierarchyAccountScope: SyncAccountScope | null = null,
171178
settings: Record<string, unknown> | null = null,
179+
plugins: PluginStateMap | null = null,
172180
): Promise<boolean> {
173181
try {
174182
this.updateState({ isSyncing: true, error: null });
@@ -212,6 +220,15 @@ export class GoogleDriveSyncService {
212220
}
213221
: null;
214222

223+
const pluginsPayload: PluginStateExportPayload | null = plugins
224+
? {
225+
format: 'gemini-voyager.plugins.v1',
226+
exportedAt: now.toISOString(),
227+
version: EXTENSION_VERSION,
228+
data: plugins,
229+
}
230+
: null;
231+
215232
// Upload folders file (platform-specific)
216233
const foldersBaseFileName =
217234
platform === 'aistudio' ? AISTUDIO_FOLDERS_FILE_NAME : FOLDERS_FILE_NAME;
@@ -235,6 +252,11 @@ export class GoogleDriveSyncService {
235252
console.log('[GoogleDriveSyncService] Settings uploaded successfully');
236253
}
237254

255+
if (pluginsPayload) {
256+
const pluginsFileId = await this.ensureFileId(token, PLUGINS_FILE_NAME, 'plugins');
257+
await this.uploadFileWithRetry(token, pluginsFileId, pluginsPayload);
258+
}
259+
238260
// Upload starred messages file (only for Gemini platform)
239261
if (platform === 'gemini' && starred) {
240262
// Truncate content in starred messages to save storage space
@@ -315,6 +337,7 @@ export class GoogleDriveSyncService {
315337
1 +
316338
(prompts.length > 0 ? 1 : 0) +
317339
(settingsPayload ? 1 : 0) +
340+
(pluginsPayload ? 1 : 0) +
318341
(platform === 'gemini' && starred ? 1 : 0) +
319342
(platform === 'gemini' && forks ? 1 : 0) +
320343
(platform === 'gemini' && timelineHierarchy ? 1 : 0);
@@ -513,6 +536,7 @@ export class GoogleDriveSyncService {
513536
folders: FolderExportPayload | null;
514537
prompts: PromptExportPayload | null;
515538
settings: SettingsExportPayload | null;
539+
plugins: PluginStateExportPayload | null;
516540
starred: StarredExportPayload | null;
517541
forks: ForkExportPayload | null;
518542
timelineHierarchy: TimelineHierarchyExportPayload | null;
@@ -557,6 +581,12 @@ export class GoogleDriveSyncService {
557581
console.log('[GoogleDriveSyncService] Settings downloaded');
558582
}
559583

584+
let plugins: PluginStateExportPayload | null = null;
585+
const pluginsFileId = await this.findFile(token, PLUGINS_FILE_NAME);
586+
if (pluginsFileId) {
587+
plugins = await this.downloadFileWithRetry(token, pluginsFileId);
588+
}
589+
560590
// Download starred messages file (only for Gemini platform)
561591
let starred: StarredExportPayload | null = null;
562592
if (platform === 'gemini') {
@@ -592,7 +622,15 @@ export class GoogleDriveSyncService {
592622
}
593623
}
594624

595-
if (!folders && !prompts && !settings && !starred && !forks && !timelineHierarchy) {
625+
if (
626+
!folders &&
627+
!prompts &&
628+
!settings &&
629+
!plugins &&
630+
!starred &&
631+
!forks &&
632+
!timelineHierarchy
633+
) {
596634
console.log(`[GoogleDriveSyncService] No sync files found for ${platform}`);
597635
this.updateState({ isSyncing: false });
598636
return null;
@@ -607,7 +645,7 @@ export class GoogleDriveSyncService {
607645
}
608646
await this.saveState();
609647

610-
return { folders, prompts, settings, starred, forks, timelineHierarchy };
648+
return { folders, prompts, settings, plugins, starred, forks, timelineHierarchy };
611649
} catch (error) {
612650
const errorMessage = error instanceof Error ? error.message : 'Download failed';
613651
console.error('[GoogleDriveSyncService] Download failed:', error);
@@ -904,6 +942,7 @@ export class GoogleDriveSyncService {
904942
| 'aistudio-folders'
905943
| 'prompts'
906944
| 'settings'
945+
| 'plugins'
907946
| 'starred'
908947
| 'forks'
909948
| 'timeline-hierarchy'
@@ -959,6 +998,7 @@ export class GoogleDriveSyncService {
959998
| 'aistudio-folders'
960999
| 'prompts'
9611000
| 'settings'
1001+
| 'plugins'
9621002
| 'starred'
9631003
| 'forks'
9641004
| 'timeline-hierarchy'
@@ -978,6 +1018,9 @@ export class GoogleDriveSyncService {
9781018
case 'settings':
9791019
this.settingsFileId = fileId;
9801020
break;
1021+
case 'plugins':
1022+
this.pluginsFileId = fileId;
1023+
break;
9811024
case 'starred':
9821025
this.starredFileId = fileId;
9831026
break;

0 commit comments

Comments
 (0)