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)