From 6fd07b0bc0e00e2c208e0caacfca7342712ac99e Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sun, 26 Oct 2025 03:56:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=BA=90=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/claude.rs | 42 ++++++ src-tauri/src/main.rs | 13 +- src/components/RelayStationManager.tsx | 191 ++++++++++++++++++++----- src/lib/api.ts | 34 +++++ 4 files changed, 238 insertions(+), 42 deletions(-) diff --git a/src-tauri/src/commands/claude.rs b/src-tauri/src/commands/claude.rs index 4d297cb..9cb7d15 100644 --- a/src-tauri/src/commands/claude.rs +++ b/src-tauri/src/commands/claude.rs @@ -738,6 +738,48 @@ pub async fn save_claude_settings(settings: serde_json::Value) -> Result Result { + log::info!("Reading Claude settings backup"); + + let claude_dir = get_claude_dir().map_err(|e| e.to_string())?; + let backup_path = claude_dir.join("settings.backup.json"); + + if !backup_path.exists() { + log::warn!("Settings backup file not found, returning empty settings"); + return Ok(ClaudeSettings { + data: serde_json::json!({}), + }); + } + + let content = fs::read_to_string(&backup_path) + .map_err(|e| format!("Failed to read settings backup file: {}", e))?; + + let data: serde_json::Value = serde_json::from_str(&content) + .map_err(|e| format!("Failed to parse settings backup JSON: {}", e))?; + + Ok(ClaudeSettings { data }) +} + +/// Saves the Claude settings backup file +#[tauri::command] +pub async fn save_claude_settings_backup(settings: serde_json::Value) -> Result { + log::info!("Saving Claude settings backup"); + + let claude_dir = get_claude_dir().map_err(|e| e.to_string())?; + let backup_path = claude_dir.join("settings.backup.json"); + + // Pretty print the JSON with 2-space indentation + let json_string = serde_json::to_string_pretty(&settings) + .map_err(|e| format!("Failed to serialize settings backup: {}", e))?; + + fs::write(&backup_path, json_string) + .map_err(|e| format!("Failed to write settings backup file: {}", e))?; + + Ok("Settings backup saved successfully".to_string()) +} + /// Recursively finds all CLAUDE.md files in a project directory #[tauri::command] pub async fn find_claude_md_files(project_path: String) -> Result, String> { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5f85ee4..1e5db27 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -24,11 +24,12 @@ use commands::claude::{ cancel_claude_execution, check_auto_checkpoint, check_claude_version, cleanup_old_checkpoints, clear_checkpoint_manager, continue_claude_code, create_checkpoint, execute_claude_code, find_claude_md_files, fork_from_checkpoint, get_checkpoint_diff, get_checkpoint_settings, - get_checkpoint_state_stats, get_claude_session_output, get_claude_settings, get_hooks_config, - get_project_sessions, get_recently_modified_files, get_session_timeline, get_system_prompt, - list_checkpoints, list_directory_contents, list_projects, list_running_claude_sessions, - load_session_history, open_new_session, read_claude_md_file, restore_checkpoint, - resume_claude_code, save_claude_md_file, save_claude_settings, save_system_prompt, + get_checkpoint_state_stats, get_claude_session_output, get_claude_settings, + get_claude_settings_backup, get_hooks_config, get_project_sessions, + get_recently_modified_files, get_session_timeline, get_system_prompt, list_checkpoints, + list_directory_contents, list_projects, list_running_claude_sessions, load_session_history, + open_new_session, read_claude_md_file, restore_checkpoint, resume_claude_code, + save_claude_md_file, save_claude_settings, save_claude_settings_backup, save_system_prompt, search_files, track_checkpoint_message, track_session_messages, unwatch_claude_project_directory, update_checkpoint_settings, update_hooks_config, validate_hook_command, watch_claude_project_directory, ClaudeProcessState, @@ -333,11 +334,13 @@ fn main() { list_projects, get_project_sessions, get_claude_settings, + get_claude_settings_backup, open_new_session, get_system_prompt, check_claude_version, save_system_prompt, save_claude_settings, + save_claude_settings_backup, watch_claude_project_directory, unwatch_claude_project_directory, find_claude_md_files, diff --git a/src/components/RelayStationManager.tsx b/src/components/RelayStationManager.tsx index a99bc64..81f7e56 100644 --- a/src/components/RelayStationManager.tsx +++ b/src/components/RelayStationManager.tsx @@ -89,6 +89,13 @@ const RelayStationManager: React.FC = ({ onBack }) => const [savingConfig, setSavingConfig] = useState(false); const [flushingDns, setFlushingDns] = useState(false); const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null); + + // 源文件备份相关状态 + const [showSourceFile, setShowSourceFile] = useState(false); + const [editingSourceFile, setEditingSourceFile] = useState(false); + const [sourceFileJson, setSourceFileJson] = useState(''); + const [savingSourceFile, setSavingSourceFile] = useState(false); + const [loadingSourceFile, setLoadingSourceFile] = useState(false); // 导入进度相关状态 const [importing, setImporting] = useState(false); @@ -255,6 +262,47 @@ const RelayStationManager: React.FC = ({ onBack }) => } }; + // 加载源文件备份 + const loadSourceFile = async () => { + try { + setLoadingSourceFile(true); + const settings = await api.getClaudeSettingsBackup(); + setSourceFileJson(JSON.stringify(settings, null, 2)); + } catch (error) { + console.error('Failed to load source file:', error); + showToast('加载源文件失败', 'error'); + } finally { + setLoadingSourceFile(false); + } + }; + + // 保存源文件备份 + const saveSourceFile = async () => { + try { + setSavingSourceFile(true); + const parsedSettings = JSON.parse(sourceFileJson); + await api.saveClaudeSettingsBackup(parsedSettings); + showToast('源文件保存成功', 'success'); + setEditingSourceFile(false); + } catch (error) { + if (error instanceof SyntaxError) { + showToast('JSON 格式无效', 'error'); + } else { + console.error('Failed to save source file:', error); + showToast('保存源文件失败', 'error'); + } + } finally { + setSavingSourceFile(false); + } + }; + + // 打开源文件查看 + const handleViewSourceFile = async () => { + await loadSourceFile(); + setShowSourceFile(true); + setEditingSourceFile(false); + }; + // 查询 PackyCode 额度 const fetchPackycodeQuota = async (stationId: string) => { @@ -584,7 +632,7 @@ const RelayStationManager: React.FC = ({ onBack }) => - {jsonConfigView ? ( + {jsonConfigView || showSourceFile ? (
@@ -593,49 +641,98 @@ const RelayStationManager: React.FC = ({ onBack }) => size="sm" onClick={() => { setJsonConfigView(false); + setShowSourceFile(false); setEditingConfig(false); + setEditingSourceFile(false); }} > {t('common.back')} +
+ {showSourceFile ? '源文件 (settings.backup.json)' : t('relayStation.viewJson')} +
- {!editingConfig ? ( - + {showSourceFile ? ( + <> + {!editingSourceFile ? ( + + ) : ( + <> + + + + )} + ) : ( <> - - + {!editingConfig ? ( + + ) : ( + <> + + + + )} )}
@@ -644,10 +741,16 @@ const RelayStationManager: React.FC = ({ onBack }) => setConfigJson(value || '')} + value={showSourceFile ? sourceFileJson : configJson} + onChange={(value) => { + if (showSourceFile) { + setSourceFileJson(value || ''); + } else { + setConfigJson(value || ''); + } + }} options={{ - readOnly: !editingConfig, + readOnly: showSourceFile ? !editingSourceFile : !editingConfig, minimap: { enabled: false }, scrollBeyondLastLine: false, fontSize: 12, @@ -725,6 +828,20 @@ const RelayStationManager: React.FC = ({ onBack }) => {t('relayStation.viewJson')} +
)} diff --git a/src/lib/api.ts b/src/lib/api.ts index 8292865..de3f373 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -829,6 +829,40 @@ export const api = { } }, + /** + * Reads the Claude settings backup file + * @returns Promise resolving to the backup settings object + */ + async getClaudeSettingsBackup(): Promise { + try { + const result = await invoke<{ data: ClaudeSettings }>("get_claude_settings_backup"); + console.log("Raw result from get_claude_settings_backup:", result); + + if (result && typeof result === 'object' && 'data' in result) { + return result.data; + } + + return result as ClaudeSettings; + } catch (error) { + console.error("Failed to get Claude settings backup:", error); + throw error; + } + }, + + /** + * Saves the Claude settings backup file + * @param settings - The backup settings object to save + * @returns Promise resolving when the backup settings are saved + */ + async saveClaudeSettingsBackup(settings: ClaudeSettings): Promise { + try { + return await invoke("save_claude_settings_backup", { settings }); + } catch (error) { + console.error("Failed to save Claude settings backup:", error); + throw error; + } + }, + /** * Finds all CLAUDE.md files in a project directory * @param projectPath - The absolute path to the project