新增源配置文件管理
This commit is contained in:
@@ -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> {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,49 +641,98 @@ 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">
|
||||||
{!editingConfig ? (
|
{showSourceFile ? (
|
||||||
<Button
|
<>
|
||||||
variant="outline"
|
{!editingSourceFile ? (
|
||||||
size="sm"
|
<Button
|
||||||
onClick={() => setEditingConfig(true)}
|
variant="outline"
|
||||||
>
|
size="sm"
|
||||||
<Edit3 className="h-4 w-4 mr-1" />
|
onClick={() => setEditingSourceFile(true)}
|
||||||
{t('common.edit')}
|
>
|
||||||
</Button>
|
<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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button
|
{!editingConfig ? (
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
setEditingConfig(false);
|
onClick={() => setEditingConfig(true)}
|
||||||
setConfigJson(JSON.stringify(currentConfig, null, 2));
|
>
|
||||||
}}
|
<Edit3 className="h-4 w-4 mr-1" />
|
||||||
>
|
{t('common.edit')}
|
||||||
<X className="h-4 w-4 mr-1" />
|
</Button>
|
||||||
{t('common.cancel')}
|
) : (
|
||||||
</Button>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={saveJsonConfig}
|
onClick={() => {
|
||||||
disabled={savingConfig}
|
setEditingConfig(false);
|
||||||
>
|
setConfigJson(JSON.stringify(currentConfig, null, 2));
|
||||||
{savingConfig ? (
|
}}
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-white mr-1" />
|
>
|
||||||
) : (
|
<X className="h-4 w-4 mr-1" />
|
||||||
<Save className="h-4 w-4 mr-1" />
|
{t('common.cancel')}
|
||||||
)}
|
</Button>
|
||||||
{t('common.save')}
|
<Button
|
||||||
</Button>
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onClick={saveJsonConfig}
|
||||||
|
disabled={savingConfig}
|
||||||
|
>
|
||||||
|
{savingConfig ? (
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -644,10 +741,16 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user