From 26a8b1f5dcc59e781f748f13da0048a15ea801da Mon Sep 17 00:00:00 2001 From: yiqi-017 <22300246009@m.fudan.edu.cn> Date: Sun, 5 Jul 2026 22:28:17 +0800 Subject: [PATCH 1/7] feat(desktop): add import-memory feature in settings Lets users import memory/ and temp/model_responses/ from an existing GenericAgent install into the desktop app's ga_root, so past sessions and memory are not lost when running the standalone desktop bundle. - bridge: POST /memory/import copies memory (backup-then-overwrite) and model_responses (fill-missing), returns counts + backup dir - tauri: pick_directory command (rfd native folder picker) - frontend: "Import memory" button in Settings > Features + zh/en i18n Co-Authored-By: Claude Opus 4.8 --- frontends/desktop/src-tauri/src/lib.rs | 9 ++- frontends/desktop/static/app.js | 37 +++++++++++- frontends/desktop/static/index.html | 6 +- frontends/desktop_bridge.py | 83 +++++++++++++++++++++++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/frontends/desktop/src-tauri/src/lib.rs b/frontends/desktop/src-tauri/src/lib.rs index 69ab0e3e..5f1fcf27 100644 --- a/frontends/desktop/src-tauri/src/lib.rs +++ b/frontends/desktop/src-tauri/src/lib.rs @@ -726,6 +726,13 @@ fn export_mykey(content: String) -> Result, String> { } } +#[tauri::command] +fn pick_directory() -> Option { + rfd::FileDialog::new() + .pick_folder() + .map(|p| p.to_string_lossy().into_owned()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let args: Vec = std::env::args().collect(); @@ -767,7 +774,7 @@ pub fn run() { let _ = w.set_focus(); } })) - .invoke_handler(tauri::generate_handler![start_bridge_with_config, start_bridge, get_config, export_mykey, shortcut_should_ask, shortcut_decide]) + .invoke_handler(tauri::generate_handler![start_bridge_with_config, start_bridge, get_config, export_mykey, pick_directory, shortcut_should_ask, shortcut_decide]) .setup(move |app| { // Show the loading window immediately so the first-run prepare isn't a blank screen. // The window starts on loading.html (a local page), so no "connection refused" flash. diff --git a/frontends/desktop/static/app.js b/frontends/desktop/static/app.js index d1f1f319..25d291d2 100644 --- a/frontends/desktop/static/app.js +++ b/frontends/desktop/static/app.js @@ -216,6 +216,7 @@ let bridgeUiOffline = false; case 'services/bridge/exit': return http('/services/bridge/exit', { method: 'POST' }); case 'services/mykey/get': return http('/services/mykey'); case 'services/mykey/save': return http('/services/mykey', { method: 'POST', body: params || {} }); + case 'memory/import': return http('/memory/import', { method: 'POST', body: params || {} }); case 'services/conductor/model/get': return http('/services/conductor/model'); case 'services/conductor/model/save': return http('/services/conductor/model', { method: 'POST', body: params || {} }); case 'app/path/selectGaRoot': return http('/config'); @@ -284,6 +285,7 @@ let bridgeUiOffline = false; getServicePanel: () => rpc('services/panel', {}), getMykeyContent: () => rpc('services/mykey/get', {}), saveMykeyContent: (content) => rpc('services/mykey/save', { content }), + importMemory: (sourceDir) => rpc('memory/import', { sourceDir }), getConductorModel: () => rpc('services/conductor/model/get', {}), saveConductorModel: (llmNo) => rpc('services/conductor/model/save', { llmNo }), tauriInvoke, @@ -336,7 +338,7 @@ const I18N = { 'customPreset.removeTitle': '删除', 'customPreset.editTitle': '编辑', 'builtinPreset.restoreBtn': '恢复默认预设', - 'set.appearance': '外观', 'set.plainUi': '素色', 'set.fontSize': '聊天字号', 'set.lang': '语言', 'set.model': '模型', 'set.addModel': '添加模型', 'set.features': '功能', 'set.importMykey': '导入已有模型配置(mykey.py)', 'set.exportMykey': '导出当前模型配置', 'set.serviceManager': '后台服务管理', + 'set.appearance': '外观', 'set.plainUi': '素色', 'set.fontSize': '聊天字号', 'set.lang': '语言', 'set.model': '模型', 'set.addModel': '添加模型', 'set.features': '功能', 'set.importMykey': '导入已有模型配置(mykey.py)', 'set.exportMykey': '导出当前模型配置', 'set.importMemory': '导入已有记忆与会话记录', 'set.serviceManager': '后台服务管理', 'shortcut.askConfirm': '是否在桌面创建 GenericAgent 快捷方式?', 'appearance.light': '浅色', 'appearance.dark': '深色', 'set.noModels': '暂无模型,点击下方添加', @@ -456,6 +458,10 @@ const I18N = { 'modal.mykeyConfig': 'mykey.py 配置', 'sys.configSaved': '配置已保存', 'sys.mykeyImported': '模型配置已导入', + 'sys.memoryImported': '记忆已导入', + 'err.memoryImport': '导入记忆失败', + 'sys.memoryImportBackup': '原记忆已备份至', + 'sys.memoryImportPrompt': '请输入要导入的 GenericAgent 目录的完整路径:', 'sys.mykeyExported': '模型配置已导出', 'st.starting': '启动中…', 'st.stopping': '停止中…', 'st.online': '在线', 'st.offline': '离线', 'st.error': '错误', 'st.running': '运行', 'st.abnormal': '异常', 'act.configure': '配置', 'act.logs': '日志', 'act.restart': '重启', 'act.stop': '停止', 'act.start': '启动', 'act.exit': '退出', @@ -509,7 +515,7 @@ const I18N = { 'customPreset.removeTitle': 'Delete', 'customPreset.editTitle': 'Edit', 'builtinPreset.restoreBtn': 'Restore defaults', - 'set.appearance': 'Appearance', 'set.plainUi': 'Plain', 'set.fontSize': 'Chat font size', 'set.lang': 'Language', 'set.model': 'Model', 'set.addModel': 'Add model', 'set.features': 'Features', 'set.importMykey': 'Import model config (mykey.py)', 'set.exportMykey': 'Export current model config', 'set.serviceManager': 'Service manager', + 'set.appearance': 'Appearance', 'set.plainUi': 'Plain', 'set.fontSize': 'Chat font size', 'set.lang': 'Language', 'set.model': 'Model', 'set.addModel': 'Add model', 'set.features': 'Features', 'set.importMykey': 'Import model config (mykey.py)', 'set.exportMykey': 'Export current model config', 'set.importMemory': 'Import existing memory & sessions', 'set.serviceManager': 'Service manager', 'shortcut.askConfirm': 'Create a desktop shortcut for GenericAgent?', 'appearance.light': 'Light', 'appearance.dark': 'Dark', 'set.noModels': 'No models yet — add one below', @@ -629,6 +635,10 @@ const I18N = { 'modal.mykeyConfig': 'mykey.py', 'sys.configSaved': 'Configuration saved', 'sys.mykeyImported': 'Model config imported', + 'sys.memoryImported': 'Memory imported', + 'err.memoryImport': 'Failed to import memory', + 'sys.memoryImportBackup': 'Previous memory backed up to', + 'sys.memoryImportPrompt': 'Enter the full path of the GenericAgent directory to import from:', 'sys.mykeyExported': 'Model config exported', 'st.starting': 'Starting…', 'st.stopping': 'Stopping…', 'st.online': 'Online', 'st.offline': 'Offline', 'st.error': 'Error', 'st.running': 'Running', 'st.abnormal': 'Error', 'act.configure': 'Configure', 'act.logs': 'Logs', 'act.restart': 'Restart', 'act.stop': 'Stop', 'act.start': 'Start', 'act.exit': 'Exit', @@ -1006,6 +1016,29 @@ bindClick('export-mykey-btn', async (e) => { showChanToast(t('err.mykeyExport'), err.message || String(err), 'err'); } }); +async function importMemoryFromDir() { + let sourceDir = ''; + if (window.__TAURI__?.core?.invoke) { + sourceDir = await window.ga.tauriInvoke('pick_directory'); + } else { + sourceDir = window.prompt(t('sys.memoryImportPrompt')) || ''; + } + if (!sourceDir) return; + const res = await window.ga.importMemory(sourceDir); + if (!res || res.ok === false) throw new Error((res && res.error) || 'import failed'); + const detail = `memory: ${res.memoryCopied}, model_responses: ${res.responsesCopied}` + + (res.responsesSkipped ? ` (skip ${res.responsesSkipped})` : '') + + (res.backupDir ? `\n${t('sys.memoryImportBackup')}: ${res.backupDir}` : ''); + showChanToast(t('sys.memoryImported'), detail, 'ok'); +} +bindClick('import-memory-btn', async (e) => { + e.stopPropagation(); + try { + await importMemoryFromDir(); + } catch (err) { + showChanToast(t('err.memoryImport'), err.message || String(err), 'err'); + } +}); // 侧边栏「快速接入」:点击官方模型按钮 → 打开预填好的添加模型表单 const pqEl = document.getElementById('provider-quickstart'); if (pqEl) pqEl.addEventListener('click', (e) => { diff --git a/frontends/desktop/static/index.html b/frontends/desktop/static/index.html index 3390ca8b..833643bb 100644 --- a/frontends/desktop/static/index.html +++ b/frontends/desktop/static/index.html @@ -649,6 +649,10 @@ + + + +