新增源配置文件管理

This commit is contained in:
2025-10-26 03:56:42 +08:00
parent d0973caf37
commit 6fd07b0bc0
4 changed files with 238 additions and 42 deletions

View File

@@ -738,6 +738,48 @@ pub async fn save_claude_settings(settings: serde_json::Value) -> Result<String,
Ok("Settings saved successfully".to_string()) Ok("Settings saved successfully".to_string())
} }
/// Reads the Claude settings backup file
#[tauri::command]
pub async fn get_claude_settings_backup() -> Result<ClaudeSettings, String> {
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<String, String> {
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 /// Recursively finds all CLAUDE.md files in a project directory
#[tauri::command] #[tauri::command]
pub async fn find_claude_md_files(project_path: String) -> Result<Vec<ClaudeMdFile>, String> { pub async fn find_claude_md_files(project_path: String) -> Result<Vec<ClaudeMdFile>, String> {

View File

@@ -24,11 +24,12 @@ use commands::claude::{
cancel_claude_execution, check_auto_checkpoint, check_claude_version, cleanup_old_checkpoints, cancel_claude_execution, check_auto_checkpoint, check_claude_version, cleanup_old_checkpoints,
clear_checkpoint_manager, continue_claude_code, create_checkpoint, execute_claude_code, clear_checkpoint_manager, continue_claude_code, create_checkpoint, execute_claude_code,
find_claude_md_files, fork_from_checkpoint, get_checkpoint_diff, get_checkpoint_settings, 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_checkpoint_state_stats, get_claude_session_output, get_claude_settings,
get_project_sessions, get_recently_modified_files, get_session_timeline, get_system_prompt, get_claude_settings_backup, get_hooks_config, get_project_sessions,
list_checkpoints, list_directory_contents, list_projects, list_running_claude_sessions, get_recently_modified_files, get_session_timeline, get_system_prompt, list_checkpoints,
load_session_history, open_new_session, read_claude_md_file, restore_checkpoint, list_directory_contents, list_projects, list_running_claude_sessions, load_session_history,
resume_claude_code, save_claude_md_file, save_claude_settings, save_system_prompt, 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, search_files, track_checkpoint_message, track_session_messages,
unwatch_claude_project_directory, update_checkpoint_settings, update_hooks_config, unwatch_claude_project_directory, update_checkpoint_settings, update_hooks_config,
validate_hook_command, watch_claude_project_directory, ClaudeProcessState, validate_hook_command, watch_claude_project_directory, ClaudeProcessState,
@@ -333,11 +334,13 @@ fn main() {
list_projects, list_projects,
get_project_sessions, get_project_sessions,
get_claude_settings, get_claude_settings,
get_claude_settings_backup,
open_new_session, open_new_session,
get_system_prompt, get_system_prompt,
check_claude_version, check_claude_version,
save_system_prompt, save_system_prompt,
save_claude_settings, save_claude_settings,
save_claude_settings_backup,
watch_claude_project_directory, watch_claude_project_directory,
unwatch_claude_project_directory, unwatch_claude_project_directory,
find_claude_md_files, find_claude_md_files,

View File

@@ -90,6 +90,13 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
const [flushingDns, setFlushingDns] = useState(false); const [flushingDns, setFlushingDns] = useState(false);
const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null); 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<string>('');
const [savingSourceFile, setSavingSourceFile] = useState(false);
const [loadingSourceFile, setLoadingSourceFile] = useState(false);
// 导入进度相关状态 // 导入进度相关状态
const [importing, setImporting] = useState(false); const [importing, setImporting] = useState(false);
const [importProgress, setImportProgress] = useState(0); const [importProgress, setImportProgress] = useState(0);
@@ -255,6 +262,47 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ 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 额度 // 查询 PackyCode 额度
const fetchPackycodeQuota = async (stationId: string) => { const fetchPackycodeQuota = async (stationId: string) => {
@@ -584,7 +632,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{jsonConfigView ? ( {jsonConfigView || showSourceFile ? (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<div className="flex gap-2"> <div className="flex gap-2">
@@ -593,14 +641,61 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
size="sm" size="sm"
onClick={() => { onClick={() => {
setJsonConfigView(false); setJsonConfigView(false);
setShowSourceFile(false);
setEditingConfig(false); setEditingConfig(false);
setEditingSourceFile(false);
}} }}
> >
<ArrowLeft className="h-4 w-4 mr-1" /> <ArrowLeft className="h-4 w-4 mr-1" />
{t('common.back')} {t('common.back')}
</Button> </Button>
<div className="text-sm font-medium flex items-center">
{showSourceFile ? '源文件 (settings.backup.json)' : t('relayStation.viewJson')}
</div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
{showSourceFile ? (
<>
{!editingSourceFile ? (
<Button
variant="outline"
size="sm"
onClick={() => setEditingSourceFile(true)}
>
<Edit3 className="h-4 w-4 mr-1" />
{t('common.edit')}
</Button>
) : (
<>
<Button
variant="outline"
size="sm"
onClick={() => {
setEditingSourceFile(false);
setSourceFileJson(sourceFileJson);
}}
>
<X className="h-4 w-4 mr-1" />
{t('common.cancel')}
</Button>
<Button
variant="default"
size="sm"
onClick={saveSourceFile}
disabled={savingSourceFile}
>
{savingSourceFile ? (
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-white mr-1" />
) : (
<Save className="h-4 w-4 mr-1" />
)}
{t('common.save')}
</Button>
</>
)}
</>
) : (
<>
{!editingConfig ? ( {!editingConfig ? (
<Button <Button
variant="outline" variant="outline"
@@ -638,16 +733,24 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
</Button> </Button>
</> </>
)} )}
</>
)}
</div> </div>
</div> </div>
<div className="border rounded-lg overflow-hidden" style={{ height: '400px' }}> <div className="border rounded-lg overflow-hidden" style={{ height: '400px' }}>
<MonacoEditor <MonacoEditor
language="json" language="json"
theme="vs-dark" theme="vs-dark"
value={configJson} value={showSourceFile ? sourceFileJson : configJson}
onChange={(value) => setConfigJson(value || '')} onChange={(value) => {
if (showSourceFile) {
setSourceFileJson(value || '');
} else {
setConfigJson(value || '');
}
}}
options={{ options={{
readOnly: !editingConfig, readOnly: showSourceFile ? !editingSourceFile : !editingConfig,
minimap: { enabled: false }, minimap: { enabled: false },
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
fontSize: 12, fontSize: 12,
@@ -725,6 +828,20 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
<Eye className="h-4 w-4 mr-2" /> <Eye className="h-4 w-4 mr-2" />
<span className="text-xs truncate">{t('relayStation.viewJson')}</span> <span className="text-xs truncate">{t('relayStation.viewJson')}</span>
</Button> </Button>
<Button
variant="outline"
size="sm"
onClick={handleViewSourceFile}
disabled={loadingSourceFile}
className="w-full h-9 justify-start"
>
{loadingSourceFile ? (
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current mr-2" />
) : (
<Edit3 className="h-4 w-4 mr-2" />
)}
<span className="text-xs truncate"></span>
</Button>
</div> </div>
</div> </div>
)} )}

View File

@@ -829,6 +829,40 @@ export const api = {
} }
}, },
/**
* Reads the Claude settings backup file
* @returns Promise resolving to the backup settings object
*/
async getClaudeSettingsBackup(): Promise<ClaudeSettings> {
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<string> {
try {
return await invoke<string>("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 * Finds all CLAUDE.md files in a project directory
* @param projectPath - The absolute path to the project * @param projectPath - The absolute path to the project