- refactor(utils): extract Codex base_url parsing into shared helpers
- refactor(ProviderList): use shared base_url helpers - refactor(App): reuse shared base_url helpers for VS Code sync - fix(auto-sync): global shared VS Code auto-apply state (localStorage + event broadcast) - feat(tray): auto-apply to VS Code on Codex provider-switched when enabled - behavior: manual Apply enables auto-sync; manual Remove disables; official providers clear managed keys only - chore(typecheck): pass pnpm typecheck
This commit is contained in:
54
src/App.tsx
54
src/App.tsx
@@ -13,9 +13,12 @@ import { buttonStyles } from "./lib/styles";
|
|||||||
import { useDarkMode } from "./hooks/useDarkMode";
|
import { useDarkMode } from "./hooks/useDarkMode";
|
||||||
import { extractErrorMessage } from "./utils/errorUtils";
|
import { extractErrorMessage } from "./utils/errorUtils";
|
||||||
import { applyProviderToVSCode } from "./utils/vscodeSettings";
|
import { applyProviderToVSCode } from "./utils/vscodeSettings";
|
||||||
|
import { getCodexBaseUrl } from "./utils/providerConfigUtils";
|
||||||
|
import { useVSCodeAutoSync } from "./hooks/useVSCodeAutoSync";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { isDarkMode, toggleDarkMode } = useDarkMode();
|
const { isDarkMode, toggleDarkMode } = useDarkMode();
|
||||||
|
const { isAutoSyncEnabled } = useVSCodeAutoSync();
|
||||||
const [activeApp, setActiveApp] = useState<AppType>("claude");
|
const [activeApp, setActiveApp] = useState<AppType>("claude");
|
||||||
const [providers, setProviders] = useState<Record<string, Provider>>({});
|
const [providers, setProviders] = useState<Record<string, Provider>>({});
|
||||||
const [currentProviderId, setCurrentProviderId] = useState<string>("");
|
const [currentProviderId, setCurrentProviderId] = useState<string>("");
|
||||||
@@ -77,7 +80,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 监听托盘切换事件
|
// 监听托盘切换事件(包括菜单切换)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unlisten: (() => void) | null = null;
|
let unlisten: (() => void) | null = null;
|
||||||
|
|
||||||
@@ -92,6 +95,11 @@ function App() {
|
|||||||
if (data.appType === activeApp) {
|
if (data.appType === activeApp) {
|
||||||
await loadProviders();
|
await loadProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 若为 Codex 且开启自动同步,则静默同步到 VS Code(覆盖)
|
||||||
|
if (data.appType === "codex" && isAutoSyncEnabled) {
|
||||||
|
await syncCodexToVSCode(data.providerId, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("设置供应商切换监听器失败:", error);
|
console.error("设置供应商切换监听器失败:", error);
|
||||||
@@ -106,7 +114,7 @@ function App() {
|
|||||||
unlisten();
|
unlisten();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [activeApp]); // 依赖activeApp,切换应用时重新设置监听器
|
}, [activeApp, isAutoSyncEnabled]); // 依赖自动同步状态,确保拿到最新开关
|
||||||
|
|
||||||
const loadProviders = async () => {
|
const loadProviders = async () => {
|
||||||
const loadedProviders = await window.api.getProviders(activeApp);
|
const loadedProviders = await window.api.getProviders(activeApp);
|
||||||
@@ -176,11 +184,17 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 同步Codex供应商到VS Code设置
|
// 同步Codex供应商到VS Code设置
|
||||||
const syncCodexToVSCode = async (providerId: string) => {
|
const syncCodexToVSCode = async (providerId: string, silent = false) => {
|
||||||
try {
|
try {
|
||||||
const status = await window.api.getVSCodeSettingsStatus();
|
const status = await window.api.getVSCodeSettingsStatus();
|
||||||
if (!status.exists) {
|
if (!status.exists) {
|
||||||
showNotification("未找到 VS Code 用户设置文件 (settings.json)", "error", 3000);
|
if (!silent) {
|
||||||
|
showNotification(
|
||||||
|
"未找到 VS Code 用户设置文件 (settings.json)",
|
||||||
|
"error",
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,31 +202,43 @@ function App() {
|
|||||||
const provider = providers[providerId];
|
const provider = providers[providerId];
|
||||||
const isOfficial = provider?.category === "official";
|
const isOfficial = provider?.category === "official";
|
||||||
|
|
||||||
// 非官方供应商需要解析 base_url
|
// 非官方供应商需要解析 base_url(使用公共工具函数)
|
||||||
let baseUrl: string | undefined = undefined;
|
let baseUrl: string | undefined = undefined;
|
||||||
if (!isOfficial) {
|
if (!isOfficial) {
|
||||||
const text = typeof provider?.settingsConfig?.config === "string" ? provider.settingsConfig.config : "";
|
const parsed = getCodexBaseUrl(provider);
|
||||||
const baseUrlMatch = text.match(/base_url\s*=\s*(['"])([^'"]+)\1/);
|
if (!parsed) {
|
||||||
if (!baseUrlMatch || !baseUrlMatch[2]) {
|
if (!silent) {
|
||||||
showNotification("当前配置缺少 base_url,无法写入 VS Code", "error", 4000);
|
showNotification(
|
||||||
|
"当前配置缺少 base_url,无法写入 VS Code",
|
||||||
|
"error",
|
||||||
|
4000,
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
baseUrl = baseUrlMatch[2];
|
baseUrl = parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedSettings = applyProviderToVSCode(raw, { baseUrl, isOfficial });
|
const updatedSettings = applyProviderToVSCode(raw, {
|
||||||
|
baseUrl,
|
||||||
|
isOfficial,
|
||||||
|
});
|
||||||
if (updatedSettings !== raw) {
|
if (updatedSettings !== raw) {
|
||||||
await window.api.writeVSCodeSettings(updatedSettings);
|
await window.api.writeVSCodeSettings(updatedSettings);
|
||||||
|
if (!silent) {
|
||||||
showNotification("已同步到 VS Code", "success", 1500);
|
showNotification("已同步到 VS Code", "success", 1500);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 触发providers重新加载,以更新VS Code按钮状态
|
// 触发providers重新加载,以更新VS Code按钮状态
|
||||||
await loadProviders();
|
await loadProviders();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("同步到VS Code失败:", error);
|
console.error("同步到VS Code失败:", error);
|
||||||
|
if (!silent) {
|
||||||
const errorMessage = error?.message || "同步 VS Code 失败";
|
const errorMessage = error?.message || "同步 VS Code 失败";
|
||||||
showNotification(errorMessage, "error", 5000);
|
showNotification(errorMessage, "error", 5000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSwitchProvider = async (id: string) => {
|
const handleSwitchProvider = async (id: string) => {
|
||||||
@@ -229,9 +255,9 @@ function App() {
|
|||||||
// 更新托盘菜单
|
// 更新托盘菜单
|
||||||
await window.api.updateTrayMenu();
|
await window.api.updateTrayMenu();
|
||||||
|
|
||||||
// Codex: 切换供应商后自动同步到 VS Code
|
// Codex: 切换供应商后,只在自动同步启用时同步到 VS Code
|
||||||
if (activeApp === "codex") {
|
if (activeApp === "codex" && isAutoSyncEnabled) {
|
||||||
await syncCodexToVSCode(id);
|
await syncCodexToVSCode(id, true); // silent模式,不显示通知
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showNotification("切换失败,请检查配置", "error");
|
showNotification("切换失败,请检查配置", "error");
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ import { Provider } from "../types";
|
|||||||
import { Play, Edit3, Trash2, CheckCircle2, Users } from "lucide-react";
|
import { Play, Edit3, Trash2, CheckCircle2, Users } from "lucide-react";
|
||||||
import { buttonStyles, cardStyles, badgeStyles, cn } from "../lib/styles";
|
import { buttonStyles, cardStyles, badgeStyles, cn } from "../lib/styles";
|
||||||
import { AppType } from "../lib/tauri-api";
|
import { AppType } from "../lib/tauri-api";
|
||||||
import { applyProviderToVSCode, detectApplied, normalizeBaseUrl } from "../utils/vscodeSettings";
|
import {
|
||||||
|
applyProviderToVSCode,
|
||||||
|
detectApplied,
|
||||||
|
normalizeBaseUrl,
|
||||||
|
} from "../utils/vscodeSettings";
|
||||||
|
import { getCodexBaseUrl } from "../utils/providerConfigUtils";
|
||||||
|
import { useVSCodeAutoSync } from "../hooks/useVSCodeAutoSync";
|
||||||
// 不再在列表中显示分类徽章,避免造成困惑
|
// 不再在列表中显示分类徽章,避免造成困惑
|
||||||
|
|
||||||
interface ProviderListProps {
|
interface ProviderListProps {
|
||||||
@@ -13,7 +19,11 @@ interface ProviderListProps {
|
|||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
onEdit: (id: string) => void;
|
onEdit: (id: string) => void;
|
||||||
appType?: AppType;
|
appType?: AppType;
|
||||||
onNotify?: (message: string, type: "success" | "error", duration?: number) => void;
|
onNotify?: (
|
||||||
|
message: string,
|
||||||
|
type: "success" | "error",
|
||||||
|
duration?: number,
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderList: React.FC<ProviderListProps> = ({
|
const ProviderList: React.FC<ProviderListProps> = ({
|
||||||
@@ -53,22 +63,11 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 解析 Codex 配置中的 base_url(仅用于 VS Code 写入)
|
// 解析 Codex 配置中的 base_url(已提取到公共工具)
|
||||||
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*(['"])([^'\"]+)\1/);
|
|
||||||
return m && m[2] ? m[2] : undefined;
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// VS Code 按钮:仅在 Codex + 当前供应商显示;按钮文案根据是否“已应用”变化
|
// VS Code 按钮:仅在 Codex + 当前供应商显示;按钮文案根据是否"已应用"变化
|
||||||
const [vscodeAppliedFor, setVscodeAppliedFor] = useState<string | null>(null);
|
const [vscodeAppliedFor, setVscodeAppliedFor] = useState<string | null>(null);
|
||||||
|
const { enableAutoSync, disableAutoSync } = useVSCodeAutoSync();
|
||||||
|
|
||||||
// 当当前供应商或 appType 变化时,尝试读取 VS Code settings 并检测状态
|
// 当当前供应商或 appType 变化时,尝试读取 VS Code settings 并检测状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -91,7 +90,8 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
if (current && current.category !== "official") {
|
if (current && current.category !== "official") {
|
||||||
const base = getCodexBaseUrl(current);
|
const base = getCodexBaseUrl(current);
|
||||||
if (detected.apiBase && base) {
|
if (detected.apiBase && base) {
|
||||||
applied = normalizeBaseUrl(detected.apiBase) === normalizeBaseUrl(base);
|
applied =
|
||||||
|
normalizeBaseUrl(detected.apiBase) === normalizeBaseUrl(base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setVscodeAppliedFor(applied ? currentProviderId : null);
|
setVscodeAppliedFor(applied ? currentProviderId : null);
|
||||||
@@ -106,7 +106,11 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
try {
|
try {
|
||||||
const status = await window.api.getVSCodeSettingsStatus();
|
const status = await window.api.getVSCodeSettingsStatus();
|
||||||
if (!status.exists) {
|
if (!status.exists) {
|
||||||
onNotify?.("未找到 VS Code 用户设置文件 (settings.json)", "error", 3000);
|
onNotify?.(
|
||||||
|
"未找到 VS Code 用户设置文件 (settings.json)",
|
||||||
|
"error",
|
||||||
|
3000,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,15 +133,19 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
// 幂等:没有变化也提示成功
|
// 幂等:没有变化也提示成功
|
||||||
onNotify?.("已应用到 VS Code", "success", 1500);
|
onNotify?.("已应用到 VS Code", "success", 1500);
|
||||||
setVscodeAppliedFor(provider.id);
|
setVscodeAppliedFor(provider.id);
|
||||||
|
// 用户手动应用时,启用自动同步
|
||||||
|
enableAutoSync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await window.api.writeVSCodeSettings(next);
|
await window.api.writeVSCodeSettings(next);
|
||||||
onNotify?.("已应用到 VS Code", "success", 1500);
|
onNotify?.("已应用到 VS Code", "success", 1500);
|
||||||
setVscodeAppliedFor(provider.id);
|
setVscodeAppliedFor(provider.id);
|
||||||
|
// 用户手动应用时,启用自动同步
|
||||||
|
enableAutoSync();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
const msg = (e && e.message) ? e.message : "应用到 VS Code 失败";
|
const msg = e && e.message ? e.message : "应用到 VS Code 失败";
|
||||||
onNotify?.(msg, "error", 5000);
|
onNotify?.(msg, "error", 5000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -146,22 +154,33 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
try {
|
try {
|
||||||
const status = await window.api.getVSCodeSettingsStatus();
|
const status = await window.api.getVSCodeSettingsStatus();
|
||||||
if (!status.exists) {
|
if (!status.exists) {
|
||||||
onNotify?.("未找到 VS Code 用户设置文件 (settings.json)", "error", 3000);
|
onNotify?.(
|
||||||
|
"未找到 VS Code 用户设置文件 (settings.json)",
|
||||||
|
"error",
|
||||||
|
3000,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const raw = await window.api.readVSCodeSettings();
|
const raw = await window.api.readVSCodeSettings();
|
||||||
const next = applyProviderToVSCode(raw, { baseUrl: undefined, isOfficial: true });
|
const next = applyProviderToVSCode(raw, {
|
||||||
|
baseUrl: undefined,
|
||||||
|
isOfficial: true,
|
||||||
|
});
|
||||||
if (next === raw) {
|
if (next === raw) {
|
||||||
onNotify?.("已从 VS Code 移除", "success", 1500);
|
onNotify?.("已从 VS Code 移除", "success", 1500);
|
||||||
setVscodeAppliedFor(null);
|
setVscodeAppliedFor(null);
|
||||||
|
// 用户手动移除时,禁用自动同步
|
||||||
|
disableAutoSync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await window.api.writeVSCodeSettings(next);
|
await window.api.writeVSCodeSettings(next);
|
||||||
onNotify?.("已从 VS Code 移除", "success", 1500);
|
onNotify?.("已从 VS Code 移除", "success", 1500);
|
||||||
setVscodeAppliedFor(null);
|
setVscodeAppliedFor(null);
|
||||||
|
// 用户手动移除时,禁用自动同步
|
||||||
|
disableAutoSync();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
const msg = (e && e.message) ? e.message : "移除失败";
|
const msg = e && e.message ? e.message : "移除失败";
|
||||||
onNotify?.(msg, "error", 5000);
|
onNotify?.(msg, "error", 5000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -221,10 +240,12 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
{provider.name}
|
{provider.name}
|
||||||
</h3>
|
</h3>
|
||||||
{/* 分类徽章已移除 */}
|
{/* 分类徽章已移除 */}
|
||||||
<div className={cn(
|
<div
|
||||||
|
className={cn(
|
||||||
badgeStyles.success,
|
badgeStyles.success,
|
||||||
!isCurrent && "invisible"
|
!isCurrent && "invisible",
|
||||||
)}>
|
)}
|
||||||
|
>
|
||||||
<CheckCircle2 size={12} />
|
<CheckCircle2 size={12} />
|
||||||
当前使用
|
当前使用
|
||||||
</div>
|
</div>
|
||||||
@@ -254,7 +275,8 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 ml-4">
|
<div className="flex items-center gap-2 ml-4">
|
||||||
{appType === "codex" && provider.category !== "official" && (
|
{appType === "codex" &&
|
||||||
|
provider.category !== "official" && (
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
vscodeAppliedFor === provider.id
|
vscodeAppliedFor === provider.id
|
||||||
@@ -274,7 +296,9 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
: "将当前供应商应用到 VS Code"
|
: "将当前供应商应用到 VS Code"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{vscodeAppliedFor === provider.id ? "从 VS Code 移除" : "应用到 VS Code"}
|
{vscodeAppliedFor === provider.id
|
||||||
|
? "从 VS Code 移除"
|
||||||
|
: "应用到 VS Code"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getVersion } from "@tauri-apps/api/app";
|
|||||||
import "../lib/tauri-api";
|
import "../lib/tauri-api";
|
||||||
import { relaunchApp } from "../lib/updater";
|
import { relaunchApp } from "../lib/updater";
|
||||||
import { useUpdate } from "../contexts/UpdateContext";
|
import { useUpdate } from "../contexts/UpdateContext";
|
||||||
|
import { useVSCodeAutoSync } from "../hooks/useVSCodeAutoSync";
|
||||||
import type { Settings } from "../types";
|
import type { Settings } from "../types";
|
||||||
|
|
||||||
interface SettingsModalProps {
|
interface SettingsModalProps {
|
||||||
@@ -28,6 +29,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const [showUpToDate, setShowUpToDate] = useState(false);
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||||
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
||||||
useUpdate();
|
useUpdate();
|
||||||
|
const { isAutoSyncEnabled, toggleAutoSync } = useVSCodeAutoSync();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -201,6 +203,29 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
</label>
|
</label>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
|
{/* VS Code 自动同步设置 */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||||
|
Codex 设置
|
||||||
|
</h3>
|
||||||
|
<label className="flex items-center justify-between cursor-pointer">
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
自动同步到 VS Code
|
||||||
|
</span>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||||
|
切换 Codex 供应商时自动更新 VS Code 配置
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={isAutoSyncEnabled}
|
||||||
|
onChange={toggleAutoSync}
|
||||||
|
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 配置文件位置 */}
|
{/* 配置文件位置 */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||||
|
|||||||
93
src/hooks/useVSCodeAutoSync.ts
Normal file
93
src/hooks/useVSCodeAutoSync.ts
Normal file
@@ -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<boolean>(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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -244,7 +244,11 @@ export const tauriAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// VS Code: 获取 settings.json 状态
|
// VS Code: 获取 settings.json 状态
|
||||||
getVSCodeSettingsStatus: async (): Promise<{ exists: boolean; path: string; error?: string }> => {
|
getVSCodeSettingsStatus: async (): Promise<{
|
||||||
|
exists: boolean;
|
||||||
|
path: string;
|
||||||
|
error?: string;
|
||||||
|
}> => {
|
||||||
try {
|
try {
|
||||||
return await invoke("get_vscode_settings_status");
|
return await invoke("get_vscode_settings_status");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -287,3 +287,34 @@ export const hasTomlCommonConfigSnippet = (
|
|||||||
normalizeWhitespace(snippetString),
|
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<string, any> } | undefined | null,
|
||||||
|
): string | undefined => {
|
||||||
|
try {
|
||||||
|
const text =
|
||||||
|
typeof provider?.settingsConfig?.config === "string"
|
||||||
|
? (provider as any).settingsConfig.config
|
||||||
|
: "";
|
||||||
|
return extractCodexBaseUrl(text);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ export function removeManagedKeys(content: string): string {
|
|||||||
let out = content;
|
let out = content;
|
||||||
// 删除 chatgpt.apiBase
|
// 删除 chatgpt.apiBase
|
||||||
try {
|
try {
|
||||||
out = applyEdits(out, modify(out, ["chatgpt.apiBase"], undefined, { formattingOptions: fmt }));
|
out = applyEdits(
|
||||||
|
out,
|
||||||
|
modify(out, ["chatgpt.apiBase"], undefined, { formattingOptions: fmt }),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
// 忽略删除失败
|
// 忽略删除失败
|
||||||
}
|
}
|
||||||
@@ -69,8 +72,16 @@ export function removeManagedKeys(content: string): string {
|
|||||||
try {
|
try {
|
||||||
const data = parse(out) as any;
|
const data = parse(out) as any;
|
||||||
const cfg = data?.["chatgpt.config"];
|
const cfg = data?.["chatgpt.config"];
|
||||||
if (cfg && typeof cfg === "object" && !Array.isArray(cfg) && Object.keys(cfg).length === 0) {
|
if (
|
||||||
out = applyEdits(out, modify(out, ["chatgpt.config"], undefined, { formattingOptions: fmt }));
|
cfg &&
|
||||||
|
typeof cfg === "object" &&
|
||||||
|
!Array.isArray(cfg) &&
|
||||||
|
Object.keys(cfg).length === 0
|
||||||
|
) {
|
||||||
|
out = applyEdits(
|
||||||
|
out,
|
||||||
|
modify(out, ["chatgpt.config"], undefined, { formattingOptions: fmt }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 忽略解析失败,保持已删除的键
|
// 忽略解析失败,保持已删除的键
|
||||||
@@ -97,7 +108,10 @@ export function applyProviderToVSCode(
|
|||||||
};
|
};
|
||||||
out = JSON.stringify(obj, null, 2) + "\n";
|
out = JSON.stringify(obj, null, 2) + "\n";
|
||||||
} else {
|
} else {
|
||||||
out = applyEdits(out, modify(out, ["chatgpt.apiBase"], apiBase, { formattingOptions: fmt }));
|
out = applyEdits(
|
||||||
|
out,
|
||||||
|
modify(out, ["chatgpt.apiBase"], apiBase, { formattingOptions: fmt }),
|
||||||
|
);
|
||||||
out = applyEdits(
|
out = applyEdits(
|
||||||
out,
|
out,
|
||||||
modify(out, ["chatgpt.config", "preferred_auth_method"], "apikey", {
|
modify(out, ["chatgpt.config", "preferred_auth_method"], "apikey", {
|
||||||
|
|||||||
Reference in New Issue
Block a user