From 3a9a8036d2cc970f2db9cd72adb06ea13fd0001d Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 19 Sep 2025 08:30:29 +0800 Subject: [PATCH 1/8] =?UTF-8?q?-=20feat(codex):=20Add=20=E2=80=9CApply=20t?= =?UTF-8?q?o=20VS=20Code/Remove=20from=20VS=20Code=E2=80=9D=20button=20on?= =?UTF-8?q?=20current=20Codex=20provider=20card=20-=20feat(tauri):=20Add?= =?UTF-8?q?=20commands=20to=20read/write=20VS=20Code=20settings.json=20wit?= =?UTF-8?q?h=20cross-variant=20detection=20(Code/Insiders/VSCodium/OSS)=20?= =?UTF-8?q?-=20fix(vscode):=20Use=20top-level=20keys=20=E2=80=9Cchatgpt.ap?= =?UTF-8?q?iBase=E2=80=9D=20and=20=E2=80=9Cchatgpt.config.preferred=5Fauth?= =?UTF-8?q?=5Fmethod=E2=80=9D=20-=20fix(vscode):=20Handle=20empty=20settin?= =?UTF-8?q?gs.json=20(skip=20deletes,=20direct=20write)=20to=20avoid=20?= =?UTF-8?q?=E2=80=9CCan=20not=20delete=20in=20empty=20document=E2=80=9D=20?= =?UTF-8?q?-=20fix(windows):=20Make=20atomic=20writes=20robust=20by=20remo?= =?UTF-8?q?ving=20target=20before=20rename=20-=20ui(provider-list):=20Impr?= =?UTF-8?q?ove=20error=20surfacing=20when=20applying/removing=20-=20chore(?= =?UTF-8?q?types):=20Extend=20window.api=20typings=20and=20tauri-api=20wra?= =?UTF-8?q?ppers=20for=20VS=20Code=20commands=20-=20deps:=20Add=20jsonc-pa?= =?UTF-8?q?rser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 8 ++ src-tauri/src/commands.rs | 35 +++++++++ src-tauri/src/config.rs | 14 +++- src-tauri/src/lib.rs | 4 + src-tauri/src/vscode.rs | 61 ++++++++++++++++ src/App.tsx | 2 + src/components/ProviderList.tsx | 126 +++++++++++++++++++++++++++++++- src/lib/tauri-api.ts | 28 +++++++ src/utils/vscodeSettings.ts | 110 ++++++++++++++++++++++++++++ src/vite-env.d.ts | 4 + 11 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 src-tauri/src/vscode.rs create mode 100644 src/utils/vscodeSettings.ts diff --git a/package.json b/package.json index 9ca8611..b335bfe 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@tauri-apps/api": "^2.8.0", "@tauri-apps/plugin-process": "^2.0.0", "@tauri-apps/plugin-updater": "^2.0.0", + "jsonc-parser": "^3.2.1", "codemirror": "^6.0.2", "lucide-react": "^0.542.0", "react": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9b265b..9cb16cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: codemirror: specifier: ^6.0.2 version: 6.0.2 + jsonc-parser: + specifier: ^3.2.1 + version: 3.3.1 lucide-react: specifier: ^0.542.0 version: 0.542.0(react@18.3.1) @@ -755,6 +758,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} @@ -1580,6 +1586,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + lightningcss-darwin-arm64@1.30.1: optional: true diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 8369f85..8bbf8c4 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -7,6 +7,8 @@ use tauri_plugin_opener::OpenerExt; use crate::app_config::AppType; use crate::codex_config; use crate::config::{get_claude_settings_path, ConfigStatus}; +use crate::vscode; +use crate::config; use crate::provider::Provider; use crate::store::AppState; @@ -633,3 +635,36 @@ pub async fn check_for_updates(handle: tauri::AppHandle) -> Result Ok(true) } + +/// VS Code: 获取用户 settings.json 状态 +#[tauri::command] +pub async fn get_vscode_settings_status() -> Result { + if let Some(p) = vscode::find_existing_settings() { + Ok(ConfigStatus { exists: true, path: p.to_string_lossy().to_string() }) + } else { + // 默认返回 macOS 稳定版路径(或其他平台首选项的第一个候选),但标记不存在 + let preferred = vscode::candidate_settings_paths().into_iter().next(); + Ok(ConfigStatus { exists: false, path: preferred.unwrap_or_default().to_string_lossy().to_string() }) + } +} + +/// VS Code: 读取 settings.json 文本(仅当文件存在) +#[tauri::command] +pub async fn read_vscode_settings() -> Result { + if let Some(p) = vscode::find_existing_settings() { + std::fs::read_to_string(&p).map_err(|e| format!("读取 VS Code 设置失败: {}", e)) + } else { + Err("未找到 VS Code 用户设置文件".to_string()) + } +} + +/// VS Code: 写入 settings.json 文本(仅当文件存在;不自动创建) +#[tauri::command] +pub async fn write_vscode_settings(content: String) -> Result { + if let Some(p) = vscode::find_existing_settings() { + config::write_text_file(&p, &content)?; + Ok(true) + } else { + Err("未找到 VS Code 用户设置文件".to_string()) + } +} diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 10b39c4..71d7b45 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -175,7 +175,19 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), String> { } } - fs::rename(&tmp, path).map_err(|e| format!("原子替换失败: {}", e))?; + #[cfg(windows)] + { + // Windows 上 rename 目标存在会失败,先移除再重命名(尽量接近原子性) + if path.exists() { + let _ = fs::remove_file(path); + } + fs::rename(&tmp, path).map_err(|e| format!("原子替换失败: {}", e))?; + } + + #[cfg(not(windows))] + { + fs::rename(&tmp, path).map_err(|e| format!("原子替换失败: {}", e))?; + } Ok(()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2dd5dd6..abc6d48 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2,6 +2,7 @@ mod app_config; mod codex_config; mod commands; mod config; +mod vscode; mod migration; mod provider; mod store; @@ -357,6 +358,9 @@ pub fn run() { commands::get_settings, commands::save_settings, commands::check_for_updates, + commands::get_vscode_settings_status, + commands::read_vscode_settings, + commands::write_vscode_settings, update_tray_menu, ]); diff --git a/src-tauri/src/vscode.rs b/src-tauri/src/vscode.rs new file mode 100644 index 0000000..be50924 --- /dev/null +++ b/src-tauri/src/vscode.rs @@ -0,0 +1,61 @@ +use std::path::{PathBuf}; + +/// 枚举可能的 VS Code 发行版配置目录名称 +fn vscode_product_dirs() -> Vec<&'static str> { + vec![ + "Code", // VS Code Stable + "Code - Insiders", // VS Code Insiders + "VSCodium", // VSCodium + "Code - OSS", // OSS 发行版 + ] +} + +/// 获取 VS Code 用户 settings.json 的候选路径列表(按优先级排序) +pub fn candidate_settings_paths() -> Vec { + let mut paths = Vec::new(); + + #[cfg(target_os = "macos")] + { + if let Some(home) = dirs::home_dir() { + for prod in vscode_product_dirs() { + paths.push( + home.join("Library").join("Application Support").join(prod).join("User").join("settings.json") + ); + } + } + } + + #[cfg(target_os = "windows")] + { + // Windows: %APPDATA%\Code\User\settings.json + if let Some(roaming) = dirs::config_dir() { + for prod in vscode_product_dirs() { + paths.push(roaming.join(prod).join("User").join("settings.json")); + } + } + } + + #[cfg(all(unix, not(target_os = "macos")))] + { + // Linux: ~/.config/Code/User/settings.json + if let Some(config) = dirs::config_dir() { + for prod in vscode_product_dirs() { + paths.push(config.join(prod).join("User").join("settings.json")); + } + } + } + + paths +} + +/// 返回第一个存在的 settings.json 路径 +pub fn find_existing_settings() -> Option { + for p in candidate_settings_paths() { + if let Ok(meta) = std::fs::metadata(&p) { + if meta.is_file() { + return Some(p); + } + } + } + None +} diff --git a/src/App.tsx b/src/App.tsx index d517384..452548a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -281,6 +281,8 @@ function App() { onSwitch={handleSwitchProvider} onDelete={handleDeleteProvider} onEdit={setEditingProviderId} + appType={activeApp} + onNotify={showNotification} /> diff --git a/src/components/ProviderList.tsx b/src/components/ProviderList.tsx index f6c4a80..32c5f4e 100644 --- a/src/components/ProviderList.tsx +++ b/src/components/ProviderList.tsx @@ -1,7 +1,9 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Provider } from "../types"; import { Play, Edit3, Trash2, CheckCircle2, Users } from "lucide-react"; import { buttonStyles, cardStyles, badgeStyles, cn } from "../lib/styles"; +import { AppType } from "../lib/tauri-api"; +import { applyProviderToVSCode, detectApplied } from "../utils/vscodeSettings"; // 不再在列表中显示分类徽章,避免造成困惑 interface ProviderListProps { @@ -10,6 +12,8 @@ interface ProviderListProps { onSwitch: (id: string) => void; onDelete: (id: string) => void; onEdit: (id: string) => void; + appType?: AppType; + onNotify?: (message: string, type: "success" | "error", duration?: number) => void; } const ProviderList: React.FC = ({ @@ -18,6 +22,8 @@ const ProviderList: React.FC = ({ onSwitch, onDelete, onEdit, + appType, + onNotify, }) => { // 提取API地址(兼容不同供应商配置:Claude env / Codex TOML) const getApiUrl = (provider: Provider): string => { @@ -46,6 +52,102 @@ const ProviderList: React.FC = ({ } }; + // 解析 Codex 配置中的 base_url(仅用于 VS Code 写入) + const getCodexBaseUrl = (provider: Provider): string | undefined => { + try { + const cfg = provider.settingsConfig; + const text = typeof cfg?.config === "string" ? cfg.config : ""; + if (!text) return undefined; + const m = text.match(/base_url\s*=\s*"([^"]+)"/); + return m && m[1] ? m[1] : undefined; + } catch { + return undefined; + } + }; + + // VS Code 按钮:仅在 Codex + 当前供应商显示;按钮文案根据是否“已应用”变化 + const [vscodeAppliedFor, setVscodeAppliedFor] = useState(null); + + // 当当前供应商或 appType 变化时,尝试读取 VS Code settings 并检测状态 + useEffect(() => { + const check = async () => { + if (appType !== "codex" || !currentProviderId) { + setVscodeAppliedFor(null); + return; + } + const status = await window.api.getVSCodeSettingsStatus(); + if (!status.exists) { + setVscodeAppliedFor(null); + return; + } + try { + const content = await window.api.readVSCodeSettings(); + const detected = detectApplied(content); + // 认为“已应用”的条件:存在任意一个我们管理的键 + const applied = detected.hasApiBase || detected.hasPreferredAuthMethod; + setVscodeAppliedFor(applied ? currentProviderId : null); + } catch { + setVscodeAppliedFor(null); + } + }; + check(); + }, [appType, currentProviderId]); + + const handleApplyToVSCode = async (provider: Provider) => { + try { + const status = await window.api.getVSCodeSettingsStatus(); + if (!status.exists) { + onNotify?.("未找到 VS Code 用户设置文件 (settings.json)", "error", 3000); + return; + } + + const raw = await window.api.readVSCodeSettings(); + + const isOfficial = provider.category === "official"; + const baseUrl = isOfficial ? undefined : getCodexBaseUrl(provider); + const next = applyProviderToVSCode(raw, { baseUrl, isOfficial }); + + if (next === raw) { + // 幂等:没有变化也提示成功 + onNotify?.("已应用到 VS Code", "success", 1500); + setVscodeAppliedFor(provider.id); + return; + } + + await window.api.writeVSCodeSettings(next); + onNotify?.("已应用到 VS Code", "success", 1500); + setVscodeAppliedFor(provider.id); + } catch (e: any) { + console.error(e); + const msg = (e && e.message) ? e.message : "应用到 VS Code 失败"; + onNotify?.(msg, "error", 5000); + } + }; + + const handleRemoveFromVSCode = async () => { + try { + const status = await window.api.getVSCodeSettingsStatus(); + if (!status.exists) { + onNotify?.("未找到 VS Code 用户设置文件 (settings.json)", "error", 3000); + return; + } + const raw = await window.api.readVSCodeSettings(); + const next = applyProviderToVSCode(raw, { baseUrl: undefined, isOfficial: true }); + if (next === raw) { + onNotify?.("已从 VS Code 移除", "success", 1500); + setVscodeAppliedFor(null); + return; + } + await window.api.writeVSCodeSettings(next); + onNotify?.("已从 VS Code 移除", "success", 1500); + setVscodeAppliedFor(null); + } catch (e: any) { + console.error(e); + const msg = (e && e.message) ? e.message : "移除失败"; + onNotify?.(msg, "error", 5000); + } + }; + // 对供应商列表进行排序 const sortedProviders = Object.values(providers).sort((a, b) => { // 按添加时间排序 @@ -133,6 +235,28 @@ const ProviderList: React.FC = ({
+ {appType === "codex" && isCurrent && ( + + )}
- {appType === "codex" && isCurrent && ( + {appType === "codex" && isCurrent && provider.category !== "official" && (
@@ -253,7 +254,7 @@ const ProviderList: React.FC = ({
- {appType === "codex" && isCurrent && provider.category !== "official" && ( + {appType === "codex" && provider.category !== "official" && ( - )} + {appType === "codex" && + provider.category !== "official" && ( + + )}
*/} + {/* VS Code 自动同步设置 */} +
+

+ Codex 设置 +

+ +
+ {/* 配置文件位置 */}

diff --git a/src/hooks/useVSCodeAutoSync.ts b/src/hooks/useVSCodeAutoSync.ts new file mode 100644 index 0000000..42564a4 --- /dev/null +++ b/src/hooks/useVSCodeAutoSync.ts @@ -0,0 +1,93 @@ +import { useState, useEffect, useCallback } from "react"; + +const VSCODE_AUTO_SYNC_KEY = "vscode-auto-sync-enabled"; +const VSCODE_AUTO_SYNC_EVENT = "vscode-auto-sync-changed"; + +export function useVSCodeAutoSync() { + const [isAutoSyncEnabled, setIsAutoSyncEnabled] = useState(false); + + // 从 localStorage 读取初始状态 + useEffect(() => { + try { + const saved = localStorage.getItem(VSCODE_AUTO_SYNC_KEY); + if (saved !== null) { + setIsAutoSyncEnabled(saved === "true"); + } + } catch (error) { + console.error("读取自动同步状态失败:", error); + } + }, []); + + // 订阅同窗口的自定义事件,以及跨窗口的 storage 事件,实现全局同步 + useEffect(() => { + const onCustom = (e: Event) => { + try { + const detail = (e as CustomEvent).detail as { enabled?: boolean } | undefined; + if (detail && typeof detail.enabled === "boolean") { + setIsAutoSyncEnabled(detail.enabled); + } else { + // 兜底:从 localStorage 读取 + const saved = localStorage.getItem(VSCODE_AUTO_SYNC_KEY); + if (saved !== null) setIsAutoSyncEnabled(saved === "true"); + } + } catch { + // 忽略 + } + }; + const onStorage = (e: StorageEvent) => { + if (e.key === VSCODE_AUTO_SYNC_KEY) { + setIsAutoSyncEnabled(e.newValue === "true"); + } + }; + window.addEventListener(VSCODE_AUTO_SYNC_EVENT, onCustom as EventListener); + window.addEventListener("storage", onStorage); + return () => { + window.removeEventListener(VSCODE_AUTO_SYNC_EVENT, onCustom as EventListener); + window.removeEventListener("storage", onStorage); + }; + }, []); + + // 启用自动同步 + const enableAutoSync = useCallback(() => { + try { + localStorage.setItem(VSCODE_AUTO_SYNC_KEY, "true"); + setIsAutoSyncEnabled(true); + // 通知同窗口其他订阅者 + window.dispatchEvent( + new CustomEvent(VSCODE_AUTO_SYNC_EVENT, { detail: { enabled: true } }), + ); + } catch (error) { + console.error("保存自动同步状态失败:", error); + } + }, []); + + // 禁用自动同步 + const disableAutoSync = useCallback(() => { + try { + localStorage.setItem(VSCODE_AUTO_SYNC_KEY, "false"); + setIsAutoSyncEnabled(false); + // 通知同窗口其他订阅者 + window.dispatchEvent( + new CustomEvent(VSCODE_AUTO_SYNC_EVENT, { detail: { enabled: false } }), + ); + } catch (error) { + console.error("保存自动同步状态失败:", error); + } + }, []); + + // 切换自动同步状态 + const toggleAutoSync = useCallback(() => { + if (isAutoSyncEnabled) { + disableAutoSync(); + } else { + enableAutoSync(); + } + }, [isAutoSyncEnabled, enableAutoSync, disableAutoSync]); + + return { + isAutoSyncEnabled, + enableAutoSync, + disableAutoSync, + toggleAutoSync, + }; +} diff --git a/src/lib/tauri-api.ts b/src/lib/tauri-api.ts index 5ef7b30..e14d517 100644 --- a/src/lib/tauri-api.ts +++ b/src/lib/tauri-api.ts @@ -244,7 +244,11 @@ export const tauriAPI = { }, // VS Code: 获取 settings.json 状态 - getVSCodeSettingsStatus: async (): Promise<{ exists: boolean; path: string; error?: string }> => { + getVSCodeSettingsStatus: async (): Promise<{ + exists: boolean; + path: string; + error?: string; + }> => { try { return await invoke("get_vscode_settings_status"); } catch (error) { diff --git a/src/utils/providerConfigUtils.ts b/src/utils/providerConfigUtils.ts index 930969f..7b2d3f1 100644 --- a/src/utils/providerConfigUtils.ts +++ b/src/utils/providerConfigUtils.ts @@ -287,3 +287,34 @@ export const hasTomlCommonConfigSnippet = ( normalizeWhitespace(snippetString), ); }; + +// ========== Codex base_url utils ========== + +// 从 Codex 的 TOML 配置文本中提取 base_url(支持单/双引号) +export const extractCodexBaseUrl = ( + configText: string | undefined | null, +): string | undefined => { + try { + const text = typeof configText === "string" ? configText : ""; + if (!text) return undefined; + const m = text.match(/base_url\s*=\s*(['"])([^'\"]+)\1/); + return m && m[2] ? m[2] : undefined; + } catch { + return undefined; + } +}; + +// 从 Provider 对象中提取 Codex base_url(当 settingsConfig.config 为 TOML 字符串时) +export const getCodexBaseUrl = ( + provider: { settingsConfig?: Record } | undefined | null, +): string | undefined => { + try { + const text = + typeof provider?.settingsConfig?.config === "string" + ? (provider as any).settingsConfig.config + : ""; + return extractCodexBaseUrl(text); + } catch { + return undefined; + } +}; diff --git a/src/utils/vscodeSettings.ts b/src/utils/vscodeSettings.ts index af33657..09ed9b4 100644 --- a/src/utils/vscodeSettings.ts +++ b/src/utils/vscodeSettings.ts @@ -37,7 +37,10 @@ export function removeManagedKeys(content: string): string { let out = content; // 删除 chatgpt.apiBase try { - out = applyEdits(out, modify(out, ["chatgpt.apiBase"], undefined, { formattingOptions: fmt })); + out = applyEdits( + out, + modify(out, ["chatgpt.apiBase"], undefined, { formattingOptions: fmt }), + ); } catch { // 忽略删除失败 } @@ -69,8 +72,16 @@ export function removeManagedKeys(content: string): string { try { const data = parse(out) as any; const cfg = data?.["chatgpt.config"]; - if (cfg && typeof cfg === "object" && !Array.isArray(cfg) && Object.keys(cfg).length === 0) { - out = applyEdits(out, modify(out, ["chatgpt.config"], undefined, { formattingOptions: fmt })); + if ( + cfg && + typeof cfg === "object" && + !Array.isArray(cfg) && + Object.keys(cfg).length === 0 + ) { + out = applyEdits( + out, + modify(out, ["chatgpt.config"], undefined, { formattingOptions: fmt }), + ); } } catch { // 忽略解析失败,保持已删除的键 @@ -97,7 +108,10 @@ export function applyProviderToVSCode( }; out = JSON.stringify(obj, null, 2) + "\n"; } else { - out = applyEdits(out, modify(out, ["chatgpt.apiBase"], apiBase, { formattingOptions: fmt })); + out = applyEdits( + out, + modify(out, ["chatgpt.apiBase"], apiBase, { formattingOptions: fmt }), + ); out = applyEdits( out, modify(out, ["chatgpt.config", "preferred_auth_method"], "apikey", { From 7522ba3e03290318845391b09dc8077b43500446 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 19 Sep 2025 15:10:11 +0800 Subject: [PATCH 7/8] feat: improve VS Code integration button and notifications - Add fixed width and center alignment for VS Code button to ensure consistent width between 'Apply' and 'Remove' states - Update notification messages to remind users to restart Codex plugin for changes to take effect - Restore distinct color scheme: green for 'Apply to VS Code', gray for 'Remove from VS Code' --- src/components/ProviderList.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ProviderList.tsx b/src/components/ProviderList.tsx index 6ca6607..490764c 100644 --- a/src/components/ProviderList.tsx +++ b/src/components/ProviderList.tsx @@ -131,7 +131,7 @@ const ProviderList: React.FC = ({ if (next === raw) { // 幂等:没有变化也提示成功 - onNotify?.("已应用到 VS Code", "success", 1500); + onNotify?.("已应用到 VS Code,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(provider.id); // 用户手动应用时,启用自动同步 enableAutoSync(); @@ -139,7 +139,7 @@ const ProviderList: React.FC = ({ } await window.api.writeVSCodeSettings(next); - onNotify?.("已应用到 VS Code", "success", 1500); + onNotify?.("已应用到 VS Code,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(provider.id); // 用户手动应用时,启用自动同步 enableAutoSync(); @@ -167,14 +167,14 @@ const ProviderList: React.FC = ({ isOfficial: true, }); if (next === raw) { - onNotify?.("已从 VS Code 移除", "success", 1500); + onNotify?.("已从 VS Code 移除,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(null); // 用户手动移除时,禁用自动同步 disableAutoSync(); return; } await window.api.writeVSCodeSettings(next); - onNotify?.("已从 VS Code 移除", "success", 1500); + onNotify?.("已从 VS Code 移除,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(null); // 用户手动移除时,禁用自动同步 disableAutoSync(); @@ -284,7 +284,7 @@ const ProviderList: React.FC = ({ : handleApplyToVSCode(provider) } className={cn( - "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors", + "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors w-[130px] justify-center", !isCurrent && "invisible", vscodeAppliedFor === provider.id ? "bg-gray-100 text-gray-800 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700" From 31cdc2a5cf4103c99c268d130d938d4d9f769fda Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 19 Sep 2025 15:48:35 +0800 Subject: [PATCH 8/8] - feat(vscode-sync): restore auto-sync logic and enable by default - refactor(settings): remove the VS Code auto-sync toggle from Settings UI - feat(provider-list): enable auto-sync after "Apply to VS Code"; disable after "Remove" - chore(prettier): run Prettier on changed files - verify: typecheck and renderer build pass - Files - added: src/hooks/useVSCodeAutoSync.ts - modified: src/App.tsx - modified: src/components/ProviderList.tsx - modified: src/components/SettingsModal.tsx - Notes - Auto-sync now defaults to enabled for new users (stored in localStorage; existing saved state is respected). - No settings toggle is shown; manual Apply/Remove in the list still works as before. --- src/App.tsx | 4 ++-- src/components/SettingsModal.tsx | 25 +------------------------ src/hooks/useVSCodeAutoSync.ts | 12 +++++++++--- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9025499..3fd7737 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -114,7 +114,7 @@ function App() { unlisten(); } }; - }, [activeApp, isAutoSyncEnabled]); // 依赖自动同步状态,确保拿到最新开关 + }, [activeApp, isAutoSyncEnabled]); const loadProviders = async () => { const loadedProviders = await window.api.getProviders(activeApp); @@ -183,7 +183,7 @@ function App() { }); }; - // 同步Codex供应商到VS Code设置 + // 同步Codex供应商到VS Code设置(静默覆盖) const syncCodexToVSCode = async (providerId: string, silent = false) => { try { const status = await window.api.getVSCodeSettingsStatus(); diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index e183e29..e4f5b42 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -11,7 +11,6 @@ import { getVersion } from "@tauri-apps/api/app"; import "../lib/tauri-api"; import { relaunchApp } from "../lib/updater"; import { useUpdate } from "../contexts/UpdateContext"; -import { useVSCodeAutoSync } from "../hooks/useVSCodeAutoSync"; import type { Settings } from "../types"; interface SettingsModalProps { @@ -29,7 +28,6 @@ export default function SettingsModal({ onClose }: SettingsModalProps) { const [showUpToDate, setShowUpToDate] = useState(false); const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } = useUpdate(); - const { isAutoSyncEnabled, toggleAutoSync } = useVSCodeAutoSync(); useEffect(() => { loadSettings(); @@ -203,28 +201,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {

*/} - {/* VS Code 自动同步设置 */} -
-

- Codex 设置 -

- -
+ {/* VS Code 自动同步设置已移除 */} {/* 配置文件位置 */}
diff --git a/src/hooks/useVSCodeAutoSync.ts b/src/hooks/useVSCodeAutoSync.ts index 42564a4..c762eab 100644 --- a/src/hooks/useVSCodeAutoSync.ts +++ b/src/hooks/useVSCodeAutoSync.ts @@ -4,7 +4,8 @@ const VSCODE_AUTO_SYNC_KEY = "vscode-auto-sync-enabled"; const VSCODE_AUTO_SYNC_EVENT = "vscode-auto-sync-changed"; export function useVSCodeAutoSync() { - const [isAutoSyncEnabled, setIsAutoSyncEnabled] = useState(false); + // 默认开启自动同步;若本地存储存在记录,则以记录为准 + const [isAutoSyncEnabled, setIsAutoSyncEnabled] = useState(true); // 从 localStorage 读取初始状态 useEffect(() => { @@ -22,7 +23,9 @@ export function useVSCodeAutoSync() { useEffect(() => { const onCustom = (e: Event) => { try { - const detail = (e as CustomEvent).detail as { enabled?: boolean } | undefined; + const detail = (e as CustomEvent).detail as + | { enabled?: boolean } + | undefined; if (detail && typeof detail.enabled === "boolean") { setIsAutoSyncEnabled(detail.enabled); } else { @@ -42,7 +45,10 @@ export function useVSCodeAutoSync() { window.addEventListener(VSCODE_AUTO_SYNC_EVENT, onCustom as EventListener); window.addEventListener("storage", onStorage); return () => { - window.removeEventListener(VSCODE_AUTO_SYNC_EVENT, onCustom as EventListener); + window.removeEventListener( + VSCODE_AUTO_SYNC_EVENT, + onCustom as EventListener, + ); window.removeEventListener("storage", onStorage); }; }, []);