diff --git a/src/locales/modules/settings.js b/src/locales/modules/settings.js index f34d28d..27b105e 100644 --- a/src/locales/modules/settings.js +++ b/src/locales/modules/settings.js @@ -28,6 +28,9 @@ export default { portableDesktopOnly: _('便携模式迁移仅支持桌面客户端', 'Portable migration is only available in the desktop client', '便攜模式遷移僅支援桌面客戶端', 'ポータブル移行はデスクトップクライアントでのみ利用できます', '휴대용 마이그레이션은 데스크톱 클라이언트에서만 사용할 수 있습니다'), portableAlreadyEnabled: _('已启用便携模式', 'Portable mode enabled', '已啟用便攜模式', 'ポータブルモード有効', '휴대용 모드 활성화됨'), portableEnabledHint: _('当前会话已从便携目录启动,配置和 OpenClaw 数据会优先写入该目录。', 'This session was started from a portable directory. Config and OpenClaw data are written there first.', '目前工作階段已從便攜目錄啟動,設定與 OpenClaw 資料會優先寫入該目錄。'), + portableThisPc: _('本机', 'This PC', '本機'), + portableUsb: _('U 盘', 'USB Drive', 'USB'), + portableStatusLocal: _('当前为本机模式', 'Currently in local mode', '目前為本機模式'), portableRestoreBtn: _('迁移回本机', 'Migrate to This Computer', '遷移回本機'), portableRestoreHint: _('把 U 盘上的配置、Agent、记忆等用户数据迁回本机默认位置;本机已有数据会先整体备份。', 'Copies your config, agents, and memory from the portable drive back to this computer; existing local data is backed up first.', '把 USB 上的設定、Agent、記憶等使用者資料遷回本機預設位置;本機已有資料會先整體備份。'), portableRestoreConfirm: _('确定把便携数据迁移回本机?', 'Migrate portable data back to this computer?', '確定把便攜資料遷移回本機?'), diff --git a/src/pages/settings.js b/src/pages/settings.js index 9e6b688..0596f77 100644 --- a/src/pages/settings.js +++ b/src/pages/settings.js @@ -9,6 +9,7 @@ import { t, getLang, setLang, getAvailableLangs, onLangChange } from '../lib/i18 import { isMacPlatform } from '../lib/app-state.js' import { renderSidebar } from '../components/sidebar.js' import { getActiveEngineId } from '../lib/engine-manager.js' +import { icon } from '../lib/icons.js' const isTauri = !!window.__TAURI_INTERNALS__ @@ -812,6 +813,22 @@ function portableTargetPlaceholder() { return isWin ? 'E:\\ClawPanelPortable' : '/Volumes/USB/ClawPanelPortable' } +// 迁移方向示意:高亮当前迁移方向(本机 ⇄ U 盘),一眼看懂数据往哪走 +function renderPortableDirection(toPortable) { + const node = (label, iconName, active) => ` + + ${icon(iconName, 14)} ${label} + ` + const arrow = ` + + ` + const host = node(t('settings.portableThisPc'), 'monitor', !toPortable) + const usb = node(t('settings.portableUsb'), 'inbox', toPortable) + return `
+ ${toPortable ? host + arrow + usb : usb + arrow + host} +
` +} + async function loadPortableMigration(page) { const bar = page.querySelector('#portable-bar') if (!bar) return @@ -821,29 +838,42 @@ async function loadPortableMigration(page) { } try { const status = await api.getPortableStatus() + const card = (inner) => ` +
${inner}
` + if (status?.enabled) { - bar.innerHTML = ` + // 便携模式:U 盘 → 本机 + bar.innerHTML = card(`
- ${t('settings.portableAlreadyEnabled')} - ${escapeHtml(status.root || '')} + + ${t('settings.portableAlreadyEnabled')} + ${escapeHtml(status.root || '')}
${t('settings.portableEnabledHint')}
-
- + ${renderPortableDirection(false)} +
+ + ${t('settings.portableRestoreHint')}
-
${t('settings.portableRestoreHint')}
- ` + `) return } - bar.innerHTML = ` + + // 本机模式:本机 → U 盘 + bar.innerHTML = card(` +
+ + ${t('settings.portableStatusLocal')} +
+ ${renderPortableDirection(true)}
- +
${t('settings.portableModeHint')}
- ` + `) } catch (e) { bar.innerHTML = `
${t('common.loadFailed')}: ${escapeHtml(String(e))}
` } @@ -890,6 +920,9 @@ async function handleMigrateToLocal(page) { ], }) if (!ok) return + const btn = page.querySelector('[data-action="migrate-local"]') + const originalLabel = btn?.innerHTML + if (btn) btn.innerHTML = t('settings.portableRestoring') if (resultEl) { resultEl.style.display = 'block' resultEl.innerHTML = `
${t('settings.portableRestoring')}
` @@ -903,6 +936,8 @@ async function handleMigrateToLocal(page) { resultEl.innerHTML = `
${escapeHtml(e?.message || String(e))}
` } throw e + } finally { + if (btn && originalLabel) btn.innerHTML = originalLabel } if (resultEl) { const backups = Array.isArray(report?.backups) ? report.backups : [] @@ -929,6 +964,9 @@ async function handleMigrateToPortable(page) { } const ok = await showConfirm(t('settings.portableMigrateConfirm')) if (!ok) return + const btn = page.querySelector('[data-action="migrate-portable"]') + const originalLabel = btn?.innerHTML + if (btn) btn.innerHTML = t('settings.portableMigrating') if (resultEl) { resultEl.style.display = 'block' resultEl.innerHTML = `
${t('settings.portableMigrating')}
` @@ -942,6 +980,8 @@ async function handleMigrateToPortable(page) { resultEl.innerHTML = `
${escapeHtml(e?.message || String(e))}
` } throw e + } finally { + if (btn && originalLabel) btn.innerHTML = originalLabel } if (resultEl) { resultEl.innerHTML = renderPortableMigrationResult(report)