Skip to content

Commit

Permalink
- Enhanced iCloud sync with improved conflict detection and resolution.
Browse files Browse the repository at this point in the history
- Added client identification solution for better sync coordination.
- Added sequence numbering for precise sync ordering.
- Added monthly backup preservation system.
- Added sync metadata tracking per client.
- Improved file locking mechanism for safer concurrent operations.
- Improved timestamp precision using milliseconds.
- Implemented more robust cleanup strategy for old sync files.
- Added `sync now`button to Cloud sync setting panel.
  • Loading branch information
PeterBlenessy committed Dec 25, 2024
1 parent 4c91628 commit e337208
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 44 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Changelog
All notable changes to this project will be documented in this file.

Expand Down Expand Up @@ -28,6 +27,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- When removing a persona; should check if it is used in messages and alert user.
- When settings are restored from last message, personas with same name or id are duplicated if prompt or avatar has changed in persona settings compared to persona in message.

## v2.1.1 - 2024-12-25
- Enhanced iCloud sync with improved conflict detection and resolution.
- Added client identification solution for better sync coordination.
- Added sequence numbering for precise sync ordering.
- Added monthly backup preservation system.
- Added sync metadata tracking per client.
- Improved file locking mechanism for safer concurrent operations.
- Improved timestamp precision using milliseconds.
- Implemented more robust cleanup strategy for old sync files.
- Added `sync now`button to Cloud sync setting panel.

## v2.1.0 - 2024-12-25
- Added support for cloud sync of application data such as settings, personas, and conversations. This is available for Mac users using iCloud.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "team-ai",
"private": true,
"version": "2.1.0",
"version": "2.1.1",
"type": "module",
"license": "MIT",
"scripts": {
Expand Down
30 changes: 24 additions & 6 deletions src/components/CloudSyncSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
</q-item>

<!-- Settings Sync Option -->
<q-item tag="label">
<q-item>
<q-item-section avatar>
<q-icon name="mdi-cog-sync" :color="iconColor" />
</q-item-section>
Expand All @@ -81,7 +81,7 @@
</q-item>

<!-- Personas Sync Option -->
<q-item tag="label">
<q-item>
<q-item-section avatar>
<q-icon name="mdi-account-sync" :color="iconColor" />
</q-item-section>
Expand All @@ -103,7 +103,7 @@
</q-item>

<!-- Conversations Sync Option -->
<q-item tag="label">
<q-item>
<q-item-section avatar>
<q-icon name="mdi-message-text-clock" :color="iconColor" />
</q-item-section>
Expand All @@ -124,6 +124,8 @@
</q-item-section>
</q-item>

<q-separator spaced />

<!-- Last Sync Info -->
<q-item v-if="cloudSync && lastSync">
<q-item-section avatar>
Expand All @@ -135,17 +137,30 @@
{{ t('settings.cloud.lastSync.caption', { date: new Date(lastSync).toLocaleString() }) }}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn
dense flat
round
:icon="syncing ? 'mdi-sync-alert' : 'mdi-sync'"
:loading="syncing"
:disable="!isMacOS || !cloudSync"
@click="handleSync"
>
<q-tooltip>{{ t('settings.cloud.syncNow') }}</q-tooltip>
</q-btn>
</q-item-section>
</q-item>
</q-list>
</template>

<script>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useSettingsStore } from '../stores/settings-store';
import { platform } from '@tauri-apps/plugin-os';
import { useQuasar } from 'quasar';
import { useCloudSync } from '../composables/useCloudSync';
export default {
name: 'CloudSync',
Expand All @@ -156,6 +171,7 @@ export default {
const { cloudSync, cloudProvider, lastSync, syncOptions } = storeToRefs(settingsStore);
const isMacOS = computed(() => platform() === 'macos');
const { syncing, syncToCloud } = useCloudSync();
return {
t,
Expand All @@ -164,8 +180,10 @@ export default {
lastSync,
syncOptions,
isMacOS,
iconColor: computed(() => $q.dark.isActive ? 'grey-4' : 'grey-8')
iconColor: computed(() => $q.dark.isActive ? 'grey-4' : 'grey-8'),
syncing,
handleSync: syncToCloud
};
}
};
</script>
</script>
205 changes: 205 additions & 0 deletions src/composables/useCloudSync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { storeToRefs } from 'pinia';
import { useSettingsStore } from '../stores/settings-store';
import { useTeamsStore } from '../stores/teams-store';
import iCloudService from '../services/iCloudService';
import logger from '../services/logger';

export function useCloudSync() {
const { t } = useI18n();
const $q = useQuasar();
const settingsStore = useSettingsStore();
const teamsStore = useTeamsStore();
const syncing = ref(false);

const syncToCloud = async () => {
if (syncing.value) return;
syncing.value = true;

logger.log('[CloudSync] - iCloud sync requested');

if (!iCloudService.isAvailable()) {
$q.notify({
position: 'bottom-right',
icon: 'mdi-cloud-off',
type: 'warning',
message: t('icloud.sync.unavailable.message'),
caption: t('icloud.sync.unavailable.caption'),
timeout: 3000
});
syncing.value = false;
return;
}

try {
let notifyRef = $q.notify({
position: 'bottom-right',
timeout: 0,
spinner: true,
icon: 'mdi-cloud-sync',
message: t('icloud.sync.checking.message')
});

// Define sync configurations
const syncConfigs = [
{
type: 'settings',
enabled: settingsStore.syncOptions.settings,
getLatest: () => iCloudService.getLatestSettings(),
sync: (data) => iCloudService.syncSettings(data, settingsStore.syncOptions),
cleanup: () => iCloudService.cleanupOldSettings(5),
store: settingsStore,
getData: () => settingsStore.$state,
applyData: (data) => {
const newSettings = data.settings;
if (!settingsStore.syncOptions.personas) {
delete newSettings.personas;
}
settingsStore.$patch(newSettings);
}
},
{
type: 'personas',
enabled: settingsStore.syncOptions.personas,
getLatest: () => iCloudService.getLatestPersonas(),
sync: (data) => iCloudService.syncPersonas(data),
cleanup: () => iCloudService.cleanupOldPersonas(5),
store: teamsStore,
getData: () => teamsStore.personas,
applyData: (data) => teamsStore.personas = data.personas
},
{
type: 'conversations',
enabled: settingsStore.syncOptions.conversations,
getLatest: () => iCloudService.getLatestConversations(),
sync: () => iCloudService.syncConversations(teamsStore),
cleanup: () => iCloudService.cleanupOldConversations(5),
store: teamsStore,
getData: () => teamsStore.$state,
applyData: () => {}
}
];

// Handle sync for each enabled configuration
for (const config of syncConfigs) {
if (!config.enabled) continue;

try {
const latestData = await config.getLatest();

if (latestData && (!settingsStore.lastSync || new Date(latestData.timestamp) > new Date(settingsStore.lastSync))) {
if (notifyRef) {
notifyRef(); // Dismiss current notification
}

// Ask user to confirm sync
await new Promise((resolve) => {
$q.dialog({
title: t(`icloud.sync.${config.type}.found.title`),
message: t(`icloud.sync.${config.type}.found.message`),
cancel: true,
persistent: true,
ok: {
label: t(`icloud.sync.${config.type}.actions.sync`),
color: 'primary'
},
cancel: {
label: t(`icloud.sync.${config.type}.actions.skip`),
color: 'primary',
flat: true
}
}).onOk(async () => {
config.applyData(latestData);
$q.notify({
position: 'bottom-right',
type: 'positive',
icon: 'mdi-cloud-check',
message: t(`icloud.sync.${config.type}.loaded.message`),
timeout: 2000
});
resolve();
}).onCancel(() => resolve());
});
}

// Show upload notification
if (notifyRef) {
notifyRef(); // Dismiss previous notification
}
notifyRef = $q.notify({
position: 'bottom-right',
timeout: 0,
spinner: true,
icon: 'mdi-cloud-upload',
message: t('icloud.sync.inProgress.message')
});

// Sync current data
const currentData = config.getData();
if (currentData && (Array.isArray(currentData) ? currentData.length > 0 : true)) {
await config.sync(currentData);
await config.cleanup();

if (notifyRef) {
notifyRef(); // Dismiss upload notification
}

$q.notify({
position: 'bottom-right',
type: 'positive',
icon: 'mdi-cloud-check',
message: t(`icloud.sync.${config.type}.synced.message`),
timeout: 2000
});
}
} catch (error) {
if (notifyRef) {
notifyRef(); // Dismiss any pending notification
}

logger.error(`[CloudSync] - Error syncing ${config.type}: ${error}`);
$q.notify({
position: 'bottom-right',
type: 'negative',
icon: 'mdi-cloud-alert',
message: t(`icloud.sync.${config.type}.error.message`),
timeout: 2000
});
}
}

// Update last sync timestamp
settingsStore.lastSync = new Date().toISOString();

// Show final success notification
notifyRef();
$q.notify({
position: 'bottom-right',
type: 'positive',
icon: 'mdi-cloud-check',
message: t('icloud.sync.success.message'),
timeout: 2000
});

} catch (error) {
logger.error(`[CloudSync] - iCloud sync error: ${error}`);
$q.notify({
position: 'bottom-right',
type: 'negative',
icon: 'mdi-cloud-alert',
message: t('icloud.sync.error.message'),
caption: t('icloud.sync.error.caption'),
timeout: 3000
});
} finally {
syncing.value = false;
}
};

return {
syncing,
syncToCloud
};
}
15 changes: 0 additions & 15 deletions src/i18n/en.js

This file was deleted.

3 changes: 2 additions & 1 deletion src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ export default {
personasCaption: 'Sync your custom AI personas across devices',
conversations: 'Chat History',
conversationsCaption: 'Sync conversation history and messages'
}
},
syncNow: 'Sync now'
}
},

Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/hu.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ export default {
natural: 'Természetes',
}
},
cloud: {
syncNow: 'Szinkronizálj most'
}
},

page: {},
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/sv.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ export default {
vivid: 'Levande',
natural: 'Naturlig'
}
},
cloud: {
syncNow: 'Synkronisera nu'
}
},

Expand Down
Loading

0 comments on commit e337208

Please sign in to comment.