feat(cloud): add ChatGPT login and D1 sync
This commit is contained in:
1 parent
e2d873fe7f
commit
997859434e
8 files changed
+142
-19
No files matched your search
+63
-1
@@ -1,7 +1,7 @@
|
||||
/* Life OS reliability and productivity add-ons. */
|
||||
(() => {
|
||||
const DRAFT_KEY = 'lifeos_today_draft_v1';
|
||||
const APP_VERSION = '1.0.0';
|
||||
const APP_VERSION = '1.1.0';
|
||||
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'system'];
|
||||
const zh = () => (typeof lang === 'function' ? lang() : 'en') === 'zh';
|
||||
const copy = (en, cn) => (zh() ? cn : en);
|
||||
@@ -96,6 +96,68 @@
|
||||
link.download = `life-os-backup-${day()}.json`; link.click();
|
||||
const state = settings(); state.lastBackup = new Date().toISOString(); save('lifeos_settings', state); renderAll(); toast(copy('BACKUP CREATED', '备份已创建'));
|
||||
}
|
||||
const CLOUD_KEYS = { profile: 'lifeos_profile', memories: 'lifeos_memories', museum: 'lifeos_museum', settings: 'lifeos_settings', system: 'lifeos_system' };
|
||||
let cloudReady = false, applyingCloud = false, syncTimer = null, syncErrorShown = false;
|
||||
const cloudFallback = { profile: {}, memories: [], museum: [], settings: {}, system: {} };
|
||||
function cloudSnapshot() {
|
||||
return Object.fromEntries(Object.entries(CLOUD_KEYS).map(([field, key]) => {
|
||||
try { return [field, JSON.parse(localStorage.getItem(key) || JSON.stringify(cloudFallback[field]))]; } catch { return [field, cloudFallback[field]]; }
|
||||
}));
|
||||
}
|
||||
function hasLocalLife(state) { return state.memories.length || state.museum.length || Object.keys(state.profile).length || Object.keys(state.settings).length > 2; }
|
||||
function applyCloudState(state) {
|
||||
applyingCloud = true;
|
||||
Object.entries(CLOUD_KEYS).forEach(([field, key]) => localStorage.setItem(key, JSON.stringify(state[field] ?? cloudFallback[field])));
|
||||
applyingCloud = false;
|
||||
todayDraft = null; renderIcons(); renderAll(); applyLanguage();
|
||||
}
|
||||
async function uploadCloudState() {
|
||||
if (!cloudReady) return;
|
||||
const response = await fetch('/api/lifeos-state', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ state: cloudSnapshot() }) });
|
||||
if (!response.ok) throw new Error('sync failed');
|
||||
}
|
||||
function scheduleCloudSync() {
|
||||
if (!cloudReady || applyingCloud) return;
|
||||
clearTimeout(syncTimer);
|
||||
syncTimer = setTimeout(() => uploadCloudState().catch(() => {
|
||||
if (!syncErrorShown) { syncErrorShown = true; toast(copy('CLOUD SYNC PAUSED — CHANGES STAY ON THIS DEVICE', '云端同步暂停——更改仍保存在此设备')); }
|
||||
}), 450);
|
||||
}
|
||||
function migrationPrompt(state) {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.id = 'lifeos-cloud-migration';
|
||||
dialog.innerHTML = `<div class="lifeos-cloud-card"><div class="eyebrow">${copy('CLOUD SYNC READY', '云端同步已就绪')}</div><h2>${copy('Move this browser’s Life OS to the cloud?', '将此浏览器的 Life OS 迁移到云端?')}</h2><p>${copy('Your existing local records will become the first synced copy for this ChatGPT account.', '现有本地记录将成为这个 ChatGPT 账号的第一份云端同步数据。')}</p><div class="actions"><button class="primary" data-cloud="move">${copy('MIGRATE & SYNC', '迁移并同步')}</button><button class="secondary" data-cloud="empty">${copy('START FRESH', '从空白开始')}</button></div></div>`;
|
||||
document.body.append(dialog);
|
||||
dialog.querySelector('[data-cloud="move"]').addEventListener('click', async () => { dialog.remove(); cloudReady = true; await uploadCloudState(); toast(copy('LOCAL LIFE OS MIGRATED', '本地 Life OS 已迁移')); });
|
||||
dialog.querySelector('[data-cloud="empty"]').addEventListener('click', async () => { dialog.remove(); applyCloudState(cloudFallback); cloudReady = true; await uploadCloudState(); });
|
||||
}
|
||||
async function startCloudSync() {
|
||||
try {
|
||||
const response = await fetch('/api/lifeos-state', { cache: 'no-store' });
|
||||
if (!response.ok) throw new Error('cloud unavailable');
|
||||
const { state } = await response.json();
|
||||
const local = cloudSnapshot();
|
||||
if (state) {
|
||||
if (hasLocalLife(local)) localStorage.setItem('lifeos_precloud_backup_v1', JSON.stringify({ exportedAt: new Date().toISOString(), state: local }));
|
||||
applyCloudState(state); cloudReady = true; toast(copy('CLOUD LIFE OS RESTORED', '云端 Life OS 已恢复'));
|
||||
} else if (hasLocalLife(local)) {
|
||||
migrationPrompt(local);
|
||||
} else {
|
||||
cloudReady = true; await uploadCloudState();
|
||||
}
|
||||
} catch {
|
||||
if (!syncErrorShown) { syncErrorShown = true; toast(copy('CLOUD STORAGE UNAVAILABLE — LOCAL MODE ACTIVE', '云端存储暂不可用——已使用本地模式')); }
|
||||
}
|
||||
}
|
||||
const originalSetItem = Storage.prototype.setItem;
|
||||
Storage.prototype.setItem = function(key, value) {
|
||||
originalSetItem.call(this, key, value);
|
||||
if (this === localStorage && Object.values(CLOUD_KEYS).includes(key)) scheduleCloudSync();
|
||||
};
|
||||
const cloudStyle = document.createElement('style');
|
||||
cloudStyle.textContent = `#lifeos-cloud-migration{position:fixed;inset:0;z-index:9000;background:#071008b8;display:grid;place-items:center;padding:20px}.lifeos-cloud-card{width:min(470px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:25px;box-shadow:var(--shadow)}.lifeos-cloud-card h2{margin:10px 0;font-size:25px;letter-spacing:-.04em}.lifeos-cloud-card p{color:var(--muted);line-height:1.65;margin:0 0 20px}`;
|
||||
document.head.append(cloudStyle);
|
||||
startCloudSync();
|
||||
new MutationObserver(() => { wireToday(); wireLanguageControl(); polishSystemChrome(); }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title'] });
|
||||
wireToday();
|
||||
wireLanguageControl();
|
||||
|
||||
Reference in new issue
Block a user