feat(config): unify common config snippets persistence across all apps
- Add unified `common_config_snippets` structure to MultiAppConfig - Implement `get_common_config_snippet` and `set_common_config_snippet` commands - Replace localStorage with config.json persistence for Codex and Gemini - Auto-migrate legacy `claude_common_config_snippet` to new unified structure - Deprecate individual API methods in favor of unified interface - Add automatic migration from localStorage on first load BREAKING CHANGE: Common config snippets now stored in unified `common_config_snippets` object instead of separate fields
This commit is contained in:
@@ -174,6 +174,39 @@ impl FromStr for AppType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 通用配置片段(按应用分治)
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct CommonConfigSnippets {
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub claude: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub codex: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub gemini: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommonConfigSnippets {
|
||||||
|
/// 获取指定应用的通用配置片段
|
||||||
|
pub fn get(&self, app: &AppType) -> Option<&String> {
|
||||||
|
match app {
|
||||||
|
AppType::Claude => self.claude.as_ref(),
|
||||||
|
AppType::Codex => self.codex.as_ref(),
|
||||||
|
AppType::Gemini => self.gemini.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置指定应用的通用配置片段
|
||||||
|
pub fn set(&mut self, app: &AppType, snippet: Option<String>) {
|
||||||
|
match app {
|
||||||
|
AppType::Claude => self.claude = snippet,
|
||||||
|
AppType::Codex => self.codex = snippet,
|
||||||
|
AppType::Gemini => self.gemini = snippet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 多应用配置结构(向后兼容)
|
/// 多应用配置结构(向后兼容)
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct MultiAppConfig {
|
pub struct MultiAppConfig {
|
||||||
@@ -188,7 +221,10 @@ pub struct MultiAppConfig {
|
|||||||
/// Prompt 配置(按客户端分治)
|
/// Prompt 配置(按客户端分治)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub prompts: PromptRoot,
|
pub prompts: PromptRoot,
|
||||||
/// Claude 通用配置片段(JSON 字符串,用于跨供应商共享配置)
|
/// 通用配置片段(按应用分治)
|
||||||
|
#[serde(default)]
|
||||||
|
pub common_config_snippets: CommonConfigSnippets,
|
||||||
|
/// Claude 通用配置片段(旧字段,用于向后兼容迁移)
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub claude_common_config_snippet: Option<String>,
|
pub claude_common_config_snippet: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -209,6 +245,7 @@ impl Default for MultiAppConfig {
|
|||||||
apps,
|
apps,
|
||||||
mcp: McpRoot::default(),
|
mcp: McpRoot::default(),
|
||||||
prompts: PromptRoot::default(),
|
prompts: PromptRoot::default(),
|
||||||
|
common_config_snippets: CommonConfigSnippets::default(),
|
||||||
claude_common_config_snippet: None,
|
claude_common_config_snippet: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,6 +315,13 @@ impl MultiAppConfig {
|
|||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 迁移通用配置片段:claude_common_config_snippet → common_config_snippets.claude
|
||||||
|
if let Some(old_claude_snippet) = config.claude_common_config_snippet.take() {
|
||||||
|
log::info!("迁移通用配置:claude_common_config_snippet → common_config_snippets.claude");
|
||||||
|
config.common_config_snippets.claude = Some(old_claude_snippet);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
if updated {
|
if updated {
|
||||||
log::info!("配置结构已更新(包括 MCP 迁移或 Prompt 自动导入),保存配置...");
|
log::info!("配置结构已更新(包括 MCP 迁移或 Prompt 自动导入),保存配置...");
|
||||||
config.save()?;
|
config.save()?;
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ pub async fn open_app_config_folder(handle: AppHandle) -> Result<bool, String> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取 Claude 通用配置片段
|
/// 获取 Claude 通用配置片段(已废弃,使用 get_common_config_snippet)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_claude_common_config_snippet(
|
pub async fn get_claude_common_config_snippet(
|
||||||
state: tauri::State<'_, crate::store::AppState>,
|
state: tauri::State<'_, crate::store::AppState>,
|
||||||
@@ -145,10 +145,10 @@ pub async fn get_claude_common_config_snippet(
|
|||||||
.config
|
.config
|
||||||
.read()
|
.read()
|
||||||
.map_err(|e| format!("读取配置锁失败: {e}"))?;
|
.map_err(|e| format!("读取配置锁失败: {e}"))?;
|
||||||
Ok(guard.claude_common_config_snippet.clone())
|
Ok(guard.common_config_snippets.claude.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 设置 Claude 通用配置片段
|
/// 设置 Claude 通用配置片段(已废弃,使用 set_common_config_snippet)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn set_claude_common_config_snippet(
|
pub async fn set_claude_common_config_snippet(
|
||||||
snippet: String,
|
snippet: String,
|
||||||
@@ -165,7 +165,7 @@ pub async fn set_claude_common_config_snippet(
|
|||||||
.map_err(|e| format!("无效的 JSON 格式: {e}"))?;
|
.map_err(|e| format!("无效的 JSON 格式: {e}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
guard.claude_common_config_snippet = if snippet.trim().is_empty() {
|
guard.common_config_snippets.claude = if snippet.trim().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(snippet)
|
Some(snippet)
|
||||||
@@ -174,3 +174,69 @@ pub async fn set_claude_common_config_snippet(
|
|||||||
guard.save().map_err(|e| e.to_string())?;
|
guard.save().map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取通用配置片段(统一接口)
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_common_config_snippet(
|
||||||
|
app_type: String,
|
||||||
|
state: tauri::State<'_, crate::store::AppState>,
|
||||||
|
) -> Result<Option<String>, String> {
|
||||||
|
use crate::app_config::AppType;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
let app = AppType::from_str(&app_type)
|
||||||
|
.map_err(|e| format!("无效的应用类型: {}", e))?;
|
||||||
|
|
||||||
|
let guard = state
|
||||||
|
.config
|
||||||
|
.read()
|
||||||
|
.map_err(|e| format!("读取配置锁失败: {}", e))?;
|
||||||
|
|
||||||
|
Ok(guard.common_config_snippets.get(&app).cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置通用配置片段(统一接口)
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_common_config_snippet(
|
||||||
|
app_type: String,
|
||||||
|
snippet: String,
|
||||||
|
state: tauri::State<'_, crate::store::AppState>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use crate::app_config::AppType;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
let app = AppType::from_str(&app_type)
|
||||||
|
.map_err(|e| format!("无效的应用类型: {}", e))?;
|
||||||
|
|
||||||
|
let mut guard = state
|
||||||
|
.config
|
||||||
|
.write()
|
||||||
|
.map_err(|e| format!("写入配置锁失败: {}", e))?;
|
||||||
|
|
||||||
|
// 验证格式(根据应用类型)
|
||||||
|
if !snippet.trim().is_empty() {
|
||||||
|
match app {
|
||||||
|
AppType::Claude | AppType::Gemini => {
|
||||||
|
// 验证 JSON 格式
|
||||||
|
serde_json::from_str::<serde_json::Value>(&snippet)
|
||||||
|
.map_err(|e| format!("无效的 JSON 格式: {}", e))?;
|
||||||
|
}
|
||||||
|
AppType::Codex => {
|
||||||
|
// TOML 格式暂不验证(或可使用 toml crate)
|
||||||
|
// 注意:TOML 验证较为复杂,暂时跳过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard.common_config_snippets.set(
|
||||||
|
&app,
|
||||||
|
if snippet.trim().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(snippet)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
guard.save().map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -517,6 +517,8 @@ pub fn run() {
|
|||||||
commands::open_app_config_folder,
|
commands::open_app_config_folder,
|
||||||
commands::get_claude_common_config_snippet,
|
commands::get_claude_common_config_snippet,
|
||||||
commands::set_claude_common_config_snippet,
|
commands::set_claude_common_config_snippet,
|
||||||
|
commands::get_common_config_snippet,
|
||||||
|
commands::set_common_config_snippet,
|
||||||
commands::read_live_provider_settings,
|
commands::read_live_provider_settings,
|
||||||
commands::get_settings,
|
commands::get_settings,
|
||||||
commands::save_settings,
|
commands::save_settings,
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import {
|
|||||||
updateTomlCommonConfigSnippet,
|
updateTomlCommonConfigSnippet,
|
||||||
hasTomlCommonConfigSnippet,
|
hasTomlCommonConfigSnippet,
|
||||||
} from "@/utils/providerConfigUtils";
|
} from "@/utils/providerConfigUtils";
|
||||||
|
import { configApi } from "@/lib/api";
|
||||||
|
|
||||||
const CODEX_COMMON_CONFIG_STORAGE_KEY = "cc-switch:codex-common-config-snippet";
|
const LEGACY_STORAGE_KEY = "cc-switch:codex-common-config-snippet";
|
||||||
const DEFAULT_CODEX_COMMON_CONFIG_SNIPPET = `# Common Codex config
|
const DEFAULT_CODEX_COMMON_CONFIG_SNIPPET = `# Common Codex config
|
||||||
# Add your common TOML configuration here`;
|
# Add your common TOML configuration here`;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ interface UseCodexCommonConfigProps {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理 Codex 通用配置片段 (TOML 格式)
|
* 管理 Codex 通用配置片段 (TOML 格式)
|
||||||
|
* 从 config.json 读取和保存,支持从 localStorage 平滑迁移
|
||||||
*/
|
*/
|
||||||
export function useCodexCommonConfig({
|
export function useCodexCommonConfig({
|
||||||
codexConfig,
|
codexConfig,
|
||||||
@@ -26,31 +28,69 @@ export function useCodexCommonConfig({
|
|||||||
}: UseCodexCommonConfigProps) {
|
}: UseCodexCommonConfigProps) {
|
||||||
const [useCommonConfig, setUseCommonConfig] = useState(false);
|
const [useCommonConfig, setUseCommonConfig] = useState(false);
|
||||||
const [commonConfigSnippet, setCommonConfigSnippetState] = useState<string>(
|
const [commonConfigSnippet, setCommonConfigSnippetState] = useState<string>(
|
||||||
() => {
|
DEFAULT_CODEX_COMMON_CONFIG_SNIPPET,
|
||||||
if (typeof window === "undefined") {
|
|
||||||
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const stored = window.localStorage.getItem(
|
|
||||||
CODEX_COMMON_CONFIG_STORAGE_KEY,
|
|
||||||
);
|
|
||||||
if (stored && stored.trim()) {
|
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore localStorage 读取失败
|
|
||||||
}
|
|
||||||
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
const [commonConfigError, setCommonConfigError] = useState("");
|
const [commonConfigError, setCommonConfigError] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
// 用于跟踪是否正在通过通用配置更新
|
// 用于跟踪是否正在通过通用配置更新
|
||||||
const isUpdatingFromCommonConfig = useRef(false);
|
const isUpdatingFromCommonConfig = useRef(false);
|
||||||
|
|
||||||
|
// 初始化:从 config.json 加载,支持从 localStorage 迁移
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
|
const loadSnippet = async () => {
|
||||||
|
try {
|
||||||
|
// 使用统一 API 加载
|
||||||
|
const snippet = await configApi.getCommonConfigSnippet("codex");
|
||||||
|
|
||||||
|
if (snippet && snippet.trim()) {
|
||||||
|
if (mounted) {
|
||||||
|
setCommonConfigSnippetState(snippet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果 config.json 中没有,尝试从 localStorage 迁移
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
try {
|
||||||
|
const legacySnippet =
|
||||||
|
window.localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||||
|
if (legacySnippet && legacySnippet.trim()) {
|
||||||
|
// 迁移到 config.json
|
||||||
|
await configApi.setCommonConfigSnippet("codex", legacySnippet);
|
||||||
|
if (mounted) {
|
||||||
|
setCommonConfigSnippetState(legacySnippet);
|
||||||
|
}
|
||||||
|
// 清理 localStorage
|
||||||
|
window.localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||||
|
console.log(
|
||||||
|
"[迁移] Codex 通用配置已从 localStorage 迁移到 config.json",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[迁移] 从 localStorage 迁移失败:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载 Codex 通用配置失败:", error);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadSnippet();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 初始化时检查通用配置片段(编辑模式)
|
// 初始化时检查通用配置片段(编辑模式)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData?.settingsConfig) {
|
if (initialData?.settingsConfig && !isLoading) {
|
||||||
const config =
|
const config =
|
||||||
typeof initialData.settingsConfig.config === "string"
|
typeof initialData.settingsConfig.config === "string"
|
||||||
? initialData.settingsConfig.config
|
? initialData.settingsConfig.config
|
||||||
@@ -58,24 +98,7 @@ export function useCodexCommonConfig({
|
|||||||
const hasCommon = hasTomlCommonConfigSnippet(config, commonConfigSnippet);
|
const hasCommon = hasTomlCommonConfigSnippet(config, commonConfigSnippet);
|
||||||
setUseCommonConfig(hasCommon);
|
setUseCommonConfig(hasCommon);
|
||||||
}
|
}
|
||||||
}, [initialData, commonConfigSnippet]);
|
}, [initialData, commonConfigSnippet, isLoading]);
|
||||||
|
|
||||||
// 同步本地存储的通用配置片段
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === "undefined") return;
|
|
||||||
try {
|
|
||||||
if (commonConfigSnippet.trim()) {
|
|
||||||
window.localStorage.setItem(
|
|
||||||
CODEX_COMMON_CONFIG_STORAGE_KEY,
|
|
||||||
commonConfigSnippet,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
window.localStorage.removeItem(CODEX_COMMON_CONFIG_STORAGE_KEY);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}, [commonConfigSnippet]);
|
|
||||||
|
|
||||||
// 处理通用配置开关
|
// 处理通用配置开关
|
||||||
const handleCommonConfigToggle = useCallback(
|
const handleCommonConfigToggle = useCallback(
|
||||||
@@ -114,6 +137,12 @@ export function useCodexCommonConfig({
|
|||||||
|
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
|
// 保存到 config.json(清空)
|
||||||
|
configApi.setCommonConfigSnippet("codex", "").catch((error) => {
|
||||||
|
console.error("保存 Codex 通用配置失败:", error);
|
||||||
|
setCommonConfigError(`保存失败: ${error}`);
|
||||||
|
});
|
||||||
|
|
||||||
if (useCommonConfig) {
|
if (useCommonConfig) {
|
||||||
const { updatedConfig } = updateTomlCommonConfigSnippet(
|
const { updatedConfig } = updateTomlCommonConfigSnippet(
|
||||||
codexConfig,
|
codexConfig,
|
||||||
@@ -128,6 +157,11 @@ export function useCodexCommonConfig({
|
|||||||
|
|
||||||
// TOML 格式校验较为复杂,暂时不做校验,直接清空错误
|
// TOML 格式校验较为复杂,暂时不做校验,直接清空错误
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
|
// 保存到 config.json
|
||||||
|
configApi.setCommonConfigSnippet("codex", value).catch((error) => {
|
||||||
|
console.error("保存 Codex 通用配置失败:", error);
|
||||||
|
setCommonConfigError(`保存失败: ${error}`);
|
||||||
|
});
|
||||||
|
|
||||||
// 若当前启用通用配置,需要替换为最新片段
|
// 若当前启用通用配置,需要替换为最新片段
|
||||||
if (useCommonConfig) {
|
if (useCommonConfig) {
|
||||||
@@ -165,7 +199,7 @@ export function useCodexCommonConfig({
|
|||||||
|
|
||||||
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isUpdatingFromCommonConfig.current) {
|
if (isUpdatingFromCommonConfig.current || isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const hasCommon = hasTomlCommonConfigSnippet(
|
const hasCommon = hasTomlCommonConfigSnippet(
|
||||||
@@ -173,12 +207,13 @@ export function useCodexCommonConfig({
|
|||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
);
|
);
|
||||||
setUseCommonConfig(hasCommon);
|
setUseCommonConfig(hasCommon);
|
||||||
}, [codexConfig, commonConfigSnippet]);
|
}, [codexConfig, commonConfigSnippet, isLoading]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useCommonConfig,
|
useCommonConfig,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
commonConfigError,
|
commonConfigError,
|
||||||
|
isLoading,
|
||||||
handleCommonConfigToggle,
|
handleCommonConfigToggle,
|
||||||
handleCommonConfigSnippetChange,
|
handleCommonConfigSnippetChange,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export function useCommonConfigSnippet({
|
|||||||
|
|
||||||
const loadSnippet = async () => {
|
const loadSnippet = async () => {
|
||||||
try {
|
try {
|
||||||
// 尝试从 config.json 加载
|
// 使用统一 API 加载
|
||||||
const snippet = await configApi.getClaudeCommonConfigSnippet();
|
const snippet = await configApi.getCommonConfigSnippet("claude");
|
||||||
|
|
||||||
if (snippet && snippet.trim()) {
|
if (snippet && snippet.trim()) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -59,14 +59,14 @@ export function useCommonConfigSnippet({
|
|||||||
window.localStorage.getItem(LEGACY_STORAGE_KEY);
|
window.localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||||
if (legacySnippet && legacySnippet.trim()) {
|
if (legacySnippet && legacySnippet.trim()) {
|
||||||
// 迁移到 config.json
|
// 迁移到 config.json
|
||||||
await configApi.setClaudeCommonConfigSnippet(legacySnippet);
|
await configApi.setCommonConfigSnippet("claude", legacySnippet);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setCommonConfigSnippetState(legacySnippet);
|
setCommonConfigSnippetState(legacySnippet);
|
||||||
}
|
}
|
||||||
// 清理 localStorage
|
// 清理 localStorage
|
||||||
window.localStorage.removeItem(LEGACY_STORAGE_KEY);
|
window.localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||||
console.log(
|
console.log(
|
||||||
"[迁移] 通用配置已从 localStorage 迁移到 config.json",
|
"[迁移] Claude 通用配置已从 localStorage 迁移到 config.json",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -139,8 +139,9 @@ export function useCommonConfigSnippet({
|
|||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
// 保存到 config.json(清空)
|
// 保存到 config.json(清空)
|
||||||
configApi.setClaudeCommonConfigSnippet("").catch((error) => {
|
configApi.setCommonConfigSnippet("claude", "").catch((error) => {
|
||||||
console.error("保存通用配置失败:", error);
|
console.error("保存通用配置失败:", error);
|
||||||
|
setCommonConfigError(`保存失败: ${error}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (useCommonConfig) {
|
if (useCommonConfig) {
|
||||||
@@ -162,7 +163,7 @@ export function useCommonConfigSnippet({
|
|||||||
} else {
|
} else {
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
// 保存到 config.json
|
// 保存到 config.json
|
||||||
configApi.setClaudeCommonConfigSnippet(value).catch((error) => {
|
configApi.setCommonConfigSnippet("claude", value).catch((error) => {
|
||||||
console.error("保存通用配置失败:", error);
|
console.error("保存通用配置失败:", error);
|
||||||
setCommonConfigError(`保存失败: ${error}`);
|
setCommonConfigError(`保存失败: ${error}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from "react";
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
|
import { configApi } from "@/lib/api";
|
||||||
|
|
||||||
const GEMINI_COMMON_CONFIG_STORAGE_KEY =
|
const LEGACY_STORAGE_KEY = "cc-switch:gemini-common-config-snippet";
|
||||||
"cc-switch:gemini-common-config-snippet";
|
|
||||||
const DEFAULT_GEMINI_COMMON_CONFIG_SNIPPET = `{
|
const DEFAULT_GEMINI_COMMON_CONFIG_SNIPPET = `{
|
||||||
"timeout": 30000,
|
"timeout": 30000,
|
||||||
"maxRetries": 3
|
"maxRetries": 3
|
||||||
@@ -105,6 +105,7 @@ function hasCommonConfigSnippet(config: any, commonConfig: any): boolean {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理 Gemini 通用配置片段 (JSON 格式)
|
* 管理 Gemini 通用配置片段 (JSON 格式)
|
||||||
|
* 从 config.json 读取和保存,支持从 localStorage 平滑迁移
|
||||||
*/
|
*/
|
||||||
export function useGeminiCommonConfig({
|
export function useGeminiCommonConfig({
|
||||||
configValue,
|
configValue,
|
||||||
@@ -113,31 +114,69 @@ export function useGeminiCommonConfig({
|
|||||||
}: UseGeminiCommonConfigProps) {
|
}: UseGeminiCommonConfigProps) {
|
||||||
const [useCommonConfig, setUseCommonConfig] = useState(false);
|
const [useCommonConfig, setUseCommonConfig] = useState(false);
|
||||||
const [commonConfigSnippet, setCommonConfigSnippetState] = useState<string>(
|
const [commonConfigSnippet, setCommonConfigSnippetState] = useState<string>(
|
||||||
() => {
|
DEFAULT_GEMINI_COMMON_CONFIG_SNIPPET,
|
||||||
if (typeof window === "undefined") {
|
|
||||||
return DEFAULT_GEMINI_COMMON_CONFIG_SNIPPET;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const stored = window.localStorage.getItem(
|
|
||||||
GEMINI_COMMON_CONFIG_STORAGE_KEY,
|
|
||||||
);
|
|
||||||
if (stored && stored.trim()) {
|
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore localStorage 读取失败
|
|
||||||
}
|
|
||||||
return DEFAULT_GEMINI_COMMON_CONFIG_SNIPPET;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
const [commonConfigError, setCommonConfigError] = useState("");
|
const [commonConfigError, setCommonConfigError] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
// 用于跟踪是否正在通过通用配置更新
|
// 用于跟踪是否正在通过通用配置更新
|
||||||
const isUpdatingFromCommonConfig = useRef(false);
|
const isUpdatingFromCommonConfig = useRef(false);
|
||||||
|
|
||||||
|
// 初始化:从 config.json 加载,支持从 localStorage 迁移
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
|
const loadSnippet = async () => {
|
||||||
|
try {
|
||||||
|
// 使用统一 API 加载
|
||||||
|
const snippet = await configApi.getCommonConfigSnippet("gemini");
|
||||||
|
|
||||||
|
if (snippet && snippet.trim()) {
|
||||||
|
if (mounted) {
|
||||||
|
setCommonConfigSnippetState(snippet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果 config.json 中没有,尝试从 localStorage 迁移
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
try {
|
||||||
|
const legacySnippet =
|
||||||
|
window.localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||||
|
if (legacySnippet && legacySnippet.trim()) {
|
||||||
|
// 迁移到 config.json
|
||||||
|
await configApi.setCommonConfigSnippet("gemini", legacySnippet);
|
||||||
|
if (mounted) {
|
||||||
|
setCommonConfigSnippetState(legacySnippet);
|
||||||
|
}
|
||||||
|
// 清理 localStorage
|
||||||
|
window.localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||||
|
console.log(
|
||||||
|
"[迁移] Gemini 通用配置已从 localStorage 迁移到 config.json",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[迁移] 从 localStorage 迁移失败:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载 Gemini 通用配置失败:", error);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadSnippet();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 初始化时检查通用配置片段(编辑模式)
|
// 初始化时检查通用配置片段(编辑模式)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData?.settingsConfig) {
|
if (initialData?.settingsConfig && !isLoading) {
|
||||||
try {
|
try {
|
||||||
const config =
|
const config =
|
||||||
typeof initialData.settingsConfig.config === "object"
|
typeof initialData.settingsConfig.config === "object"
|
||||||
@@ -150,24 +189,7 @@ export function useGeminiCommonConfig({
|
|||||||
// ignore parse error
|
// ignore parse error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialData, commonConfigSnippet]);
|
}, [initialData, commonConfigSnippet, isLoading]);
|
||||||
|
|
||||||
// 同步本地存储的通用配置片段
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === "undefined") return;
|
|
||||||
try {
|
|
||||||
if (commonConfigSnippet.trim()) {
|
|
||||||
window.localStorage.setItem(
|
|
||||||
GEMINI_COMMON_CONFIG_STORAGE_KEY,
|
|
||||||
commonConfigSnippet,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
window.localStorage.removeItem(GEMINI_COMMON_CONFIG_STORAGE_KEY);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}, [commonConfigSnippet]);
|
|
||||||
|
|
||||||
// 处理通用配置开关
|
// 处理通用配置开关
|
||||||
const handleCommonConfigToggle = useCallback(
|
const handleCommonConfigToggle = useCallback(
|
||||||
@@ -214,6 +236,12 @@ export function useGeminiCommonConfig({
|
|||||||
|
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
|
// 保存到 config.json(清空)
|
||||||
|
configApi.setCommonConfigSnippet("gemini", "").catch((error) => {
|
||||||
|
console.error("保存 Gemini 通用配置失败:", error);
|
||||||
|
setCommonConfigError(`保存失败: ${error}`);
|
||||||
|
});
|
||||||
|
|
||||||
if (useCommonConfig) {
|
if (useCommonConfig) {
|
||||||
// 移除旧的通用配置
|
// 移除旧的通用配置
|
||||||
try {
|
try {
|
||||||
@@ -236,6 +264,11 @@ export function useGeminiCommonConfig({
|
|||||||
try {
|
try {
|
||||||
JSON.parse(value);
|
JSON.parse(value);
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
|
// 保存到 config.json
|
||||||
|
configApi.setCommonConfigSnippet("gemini", value).catch((error) => {
|
||||||
|
console.error("保存 Gemini 通用配置失败:", error);
|
||||||
|
setCommonConfigError(`保存失败: ${error}`);
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setCommonConfigError("通用配置片段格式错误(必须是有效的 JSON)");
|
setCommonConfigError("通用配置片段格式错误(必须是有效的 JSON)");
|
||||||
return;
|
return;
|
||||||
@@ -276,7 +309,7 @@ export function useGeminiCommonConfig({
|
|||||||
|
|
||||||
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isUpdatingFromCommonConfig.current) {
|
if (isUpdatingFromCommonConfig.current || isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -287,12 +320,13 @@ export function useGeminiCommonConfig({
|
|||||||
} catch {
|
} catch {
|
||||||
// ignore parse error
|
// ignore parse error
|
||||||
}
|
}
|
||||||
}, [configValue, commonConfigSnippet]);
|
}, [configValue, commonConfigSnippet, isLoading]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useCommonConfig,
|
useCommonConfig,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
commonConfigError,
|
commonConfigError,
|
||||||
|
isLoading,
|
||||||
handleCommonConfigToggle,
|
handleCommonConfigToggle,
|
||||||
handleCommonConfigSnippetChange,
|
handleCommonConfigSnippetChange,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
// 配置相关 API
|
// 配置相关 API
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
export type AppType = "claude" | "codex" | "gemini";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 Claude 通用配置片段
|
* 获取 Claude 通用配置片段(已废弃,使用 getCommonConfigSnippet)
|
||||||
* @returns 通用配置片段(JSON 字符串),如果不存在则返回 null
|
* @returns 通用配置片段(JSON 字符串),如果不存在则返回 null
|
||||||
|
* @deprecated 使用 getCommonConfigSnippet('claude') 替代
|
||||||
*/
|
*/
|
||||||
export async function getClaudeCommonConfigSnippet(): Promise<string | null> {
|
export async function getClaudeCommonConfigSnippet(): Promise<string | null> {
|
||||||
return invoke<string | null>("get_claude_common_config_snippet");
|
return invoke<string | null>("get_claude_common_config_snippet");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置 Claude 通用配置片段
|
* 设置 Claude 通用配置片段(已废弃,使用 setCommonConfigSnippet)
|
||||||
* @param snippet - 通用配置片段(JSON 字符串)
|
* @param snippet - 通用配置片段(JSON 字符串)
|
||||||
* @throws 如果 JSON 格式无效
|
* @throws 如果 JSON 格式无效
|
||||||
|
* @deprecated 使用 setCommonConfigSnippet('claude', snippet) 替代
|
||||||
*/
|
*/
|
||||||
export async function setClaudeCommonConfigSnippet(
|
export async function setClaudeCommonConfigSnippet(
|
||||||
snippet: string,
|
snippet: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return invoke("set_claude_common_config_snippet", { snippet });
|
return invoke("set_claude_common_config_snippet", { snippet });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通用配置片段(统一接口)
|
||||||
|
* @param appType - 应用类型(claude/codex/gemini)
|
||||||
|
* @returns 通用配置片段(原始字符串),如果不存在则返回 null
|
||||||
|
*/
|
||||||
|
export async function getCommonConfigSnippet(
|
||||||
|
appType: AppType,
|
||||||
|
): Promise<string | null> {
|
||||||
|
return invoke<string | null>("get_common_config_snippet", { appType });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置通用配置片段(统一接口)
|
||||||
|
* @param appType - 应用类型(claude/codex/gemini)
|
||||||
|
* @param snippet - 通用配置片段(原始字符串)
|
||||||
|
* @throws 如果格式无效(Claude/Gemini 验证 JSON,Codex 暂不验证)
|
||||||
|
*/
|
||||||
|
export async function setCommonConfigSnippet(
|
||||||
|
appType: AppType,
|
||||||
|
snippet: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return invoke("set_common_config_snippet", { appType, snippet });
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user