feat: migrate Claude common config snippet from localStorage to config.json

Migrate the Claude common config snippet storage from browser localStorage
to the persistent config.json file for better cross-device sync and backup support.

**Backend Changes:**
- Add `claude_common_config_snippet` field to `MultiAppConfig` struct
- Add `get_claude_common_config_snippet` and `set_claude_common_config_snippet` Tauri commands
- Include JSON validation in the setter command

**Frontend Changes:**
- Create new `lib/api/config.ts` API module
- Refactor `useCommonConfigSnippet` hook to use config.json instead of localStorage
- Add automatic one-time migration from localStorage to config.json
- Add loading state during initialization

**Benefits:**
- Cross-device synchronization via backup/restore
- More reliable persistence than browser storage
- Centralized configuration management
- Seamless migration for existing users
This commit is contained in:
Jason
2025-11-13 22:45:58 +08:00
parent 434c64f38d
commit 21fd7cc9fd
6 changed files with 135 additions and 38 deletions

View File

@@ -95,6 +95,9 @@ pub struct MultiAppConfig {
/// Prompt 配置(按客户端分治)
#[serde(default)]
pub prompts: PromptRoot,
/// Claude 通用配置片段JSON 字符串,用于跨供应商共享配置)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claude_common_config_snippet: Option<String>,
}
fn default_version() -> u32 {
@@ -113,6 +116,7 @@ impl Default for MultiAppConfig {
apps,
mcp: McpRoot::default(),
prompts: PromptRoot::default(),
claude_common_config_snippet: None,
}
}
}

View File

@@ -135,3 +135,39 @@ pub async fn open_app_config_folder(handle: AppHandle) -> Result<bool, String> {
Ok(true)
}
/// 获取 Claude 通用配置片段
#[tauri::command]
pub async fn get_claude_common_config_snippet(
state: tauri::State<'_, crate::store::AppState>,
) -> Result<Option<String>, String> {
let guard = state.config.read().map_err(|e| format!("读取配置锁失败: {e}"))?;
Ok(guard.claude_common_config_snippet.clone())
}
/// 设置 Claude 通用配置片段
#[tauri::command]
pub async fn set_claude_common_config_snippet(
snippet: String,
state: tauri::State<'_, crate::store::AppState>,
) -> Result<(), String> {
let mut guard = state
.config
.write()
.map_err(|e| format!("写入配置锁失败: {e}"))?;
// 验证是否为有效的 JSON如果不为空
if !snippet.trim().is_empty() {
serde_json::from_str::<serde_json::Value>(&snippet)
.map_err(|e| format!("无效的 JSON 格式: {e}"))?;
}
guard.claude_common_config_snippet = if snippet.trim().is_empty() {
None
} else {
Some(snippet)
};
guard.save().map_err(|e| e.to_string())?;
Ok(())
}

View File

@@ -511,6 +511,8 @@ pub fn run() {
commands::get_init_error,
commands::get_app_config_path,
commands::open_app_config_folder,
commands::get_claude_common_config_snippet,
commands::set_claude_common_config_snippet,
commands::read_live_provider_settings,
commands::get_settings,
commands::save_settings,