-
- {t("settings.windowBehavior")}
-
+ {t("settings.windowBehavior")}
{t("settings.windowBehaviorHint", {
defaultValue: "配置窗口最小化与 Claude 插件联动策略。",
@@ -27,9 +25,7 @@ export function WindowSettings({ settings, onChange }: WindowSettingsProps) {
title={t("settings.minimizeToTray")}
description={t("settings.minimizeToTrayDescription")}
checked={settings.minimizeToTrayOnClose}
- onCheckedChange={(value) =>
- onChange({ minimizeToTrayOnClose: value })
- }
+ onCheckedChange={(value) => onChange({ minimizeToTrayOnClose: value })}
/>
(
{...props}
/>
);
- }
+ },
);
Button.displayName = "Button";
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index 285c6db..fcb1c94 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -19,7 +19,7 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
- className
+ className,
)}
{...props}
/>
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
- className
+ className,
)}
{...props}
>
@@ -57,7 +57,7 @@ const DialogHeader = ({
@@ -71,7 +71,7 @@ const DialogFooter = ({
@@ -86,7 +86,7 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index b4f462d..8bad3e7 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -41,7 +41,7 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref}
className={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
- className
+ className,
)}
{...props}
/>
@@ -59,7 +59,7 @@ const DropdownMenuContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
@@ -78,7 +78,7 @@ const DropdownMenuItem = React.forwardRef<
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -93,7 +93,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
+ className,
)}
checked={checked}
{...props}
@@ -127,7 +127,7 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
+ className,
)}
{...props}
>
@@ -139,8 +139,7 @@ const DropdownMenuRadioItem = React.forwardRef<
{children}
));
-DropdownMenuRadioItem.displayName =
- DropdownMenuPrimitive.RadioItem.displayName;
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef,
@@ -153,7 +152,7 @@ const DropdownMenuLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold text-muted-foreground",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -170,15 +169,17 @@ const DropdownMenuSeparator = React.forwardRef<
{...props}
/>
));
-DropdownMenuSeparator.displayName =
- DropdownMenuPrimitive.Separator.displayName;
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes) => (
);
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
index 89ed363..ef96189 100644
--- a/src/components/ui/form.tsx
+++ b/src/components/ui/form.tsx
@@ -15,18 +15,18 @@ const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TName extends FieldPath = FieldPath,
> = {
name: TName;
};
const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
+ {} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TName extends FieldPath = FieldPath,
>({
...props
}: ControllerProps) => {
@@ -61,7 +61,7 @@ const useFormField = () => {
};
const FormItemContext = React.createContext<{ id: string }>(
- {} as { id: string }
+ {} as { id: string },
);
const FormItem = React.forwardRef<
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index dc50ca3..cb7a63b 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -10,13 +10,13 @@ const Input = React.forwardRef(
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
- className
+ className,
)}
ref={ref}
{...props}
/>
);
- }
+ },
);
Input.displayName = "Input";
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
index ced2949..ff31a29 100644
--- a/src/components/ui/label.tsx
+++ b/src/components/ui/label.tsx
@@ -10,7 +10,7 @@ const Label = React.forwardRef<
ref={ref}
className={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index c7db9af..bf2025f 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
- className
+ className,
)}
{...props}
>
@@ -38,7 +38,7 @@ const SelectContent = React.forwardRef<
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
position={position}
{...props}
@@ -50,7 +50,7 @@ const SelectContent = React.forwardRef<
className={cn(
"p-1",
position === "popper" &&
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
@@ -83,7 +83,7 @@ const SelectItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
+ className,
)}
{...props}
>
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index 08392e7..8873e08 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -8,7 +8,8 @@ export function Toaster() {
theme="system"
toastOptions={{
classNames: {
- toast: "group rounded-md border bg-background text-foreground shadow-lg",
+ toast:
+ "group rounded-md border bg-background text-foreground shadow-lg",
title: "text-sm font-semibold",
description: "text-sm text-muted-foreground",
closeButton:
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
index 4187e3a..8185b5b 100644
--- a/src/components/ui/switch.tsx
+++ b/src/components/ui/switch.tsx
@@ -10,13 +10,13 @@ const Switch = React.forwardRef<
ref={ref}
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent bg-input shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
- className
+ className,
)}
{...props}
>
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
index 1971862..6e46cc3 100644
--- a/src/components/ui/tabs.tsx
+++ b/src/components/ui/tabs.tsx
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
- className
+ className,
)}
{...props}
/>
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
ref={ref}
className={cn(
"inline-flex min-w-[120px] items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=inactive]:opacity-60",
- className
+ className,
)}
{...props}
/>
@@ -42,7 +42,7 @@ const TabsContent = React.forwardRef<
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
index 3b200f3..f880762 100644
--- a/src/components/ui/textarea.tsx
+++ b/src/components/ui/textarea.tsx
@@ -9,13 +9,13 @@ const Textarea = React.forwardRef(
);
- }
+ },
);
Textarea.displayName = "Textarea";
diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts
index 0d6f3eb..3d0666c 100644
--- a/src/hooks/useSettings.ts
+++ b/src/hooks/useSettings.ts
@@ -62,12 +62,17 @@ const computeDefaultAppConfigDir = async (): Promise => {
const home = await homeDir();
return await join(home, ".cc-switch");
} catch (error) {
- console.error("[useSettings] Failed to resolve default app config dir", error);
+ console.error(
+ "[useSettings] Failed to resolve default app config dir",
+ error,
+ );
return undefined;
}
};
-const computeDefaultConfigDir = async (app: AppType): Promise => {
+const computeDefaultConfigDir = async (
+ app: AppType,
+): Promise => {
try {
const home = await homeDir();
const folder = app === "claude" ? ".claude" : ".codex";
@@ -83,8 +88,12 @@ export function useSettings(): UseSettingsResult {
const { data, isLoading } = useSettingsQuery();
const saveMutation = useSaveSettingsMutation();
- const [settingsState, setSettingsState] = useState(null);
- const [appConfigDir, setAppConfigDir] = useState(undefined);
+ const [settingsState, setSettingsState] = useState(
+ null,
+ );
+ const [appConfigDir, setAppConfigDir] = useState(
+ undefined,
+ );
const [configPath, setConfigPath] = useState("");
const [isPortable, setIsPortable] = useState(false);
const [requiresRestart, setRequiresRestart] = useState(false);
@@ -135,7 +144,8 @@ export function useSettings(): UseSettingsResult {
...data,
showInTray: data.showInTray ?? true,
minimizeToTrayOnClose: data.minimizeToTrayOnClose ?? true,
- enableClaudePluginIntegration: data.enableClaudePluginIntegration ?? false,
+ enableClaudePluginIntegration:
+ data.enableClaudePluginIntegration ?? false,
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
codexConfigDir: sanitizeDir(data.codexConfigDir),
language: normalizedLanguage,
@@ -286,8 +296,8 @@ export function useSettings(): UseSettingsResult {
const key: DirectoryKey = app === "claude" ? "claude" : "codex";
const currentValue =
key === "claude"
- ? settingsState?.claudeConfigDir ?? resolvedDirs.claude
- : settingsState?.codexConfigDir ?? resolvedDirs.codex;
+ ? (settingsState?.claudeConfigDir ?? resolvedDirs.claude)
+ : (settingsState?.codexConfigDir ?? resolvedDirs.codex);
try {
const picked = await settingsApi.selectConfigDirectory(currentValue);
@@ -377,7 +387,8 @@ export function useSettings(): UseSettingsResult {
...data,
showInTray: data.showInTray ?? true,
minimizeToTrayOnClose: data.minimizeToTrayOnClose ?? true,
- enableClaudePluginIntegration: data.enableClaudePluginIntegration ?? false,
+ enableClaudePluginIntegration:
+ data.enableClaudePluginIntegration ?? false,
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
codexConfigDir: sanitizeDir(data.codexConfigDir),
language: normalizedLanguage,
@@ -387,7 +398,8 @@ export function useSettings(): UseSettingsResult {
syncLanguage(initialLanguageRef.current);
setAppConfigDir(initialAppConfigDirRef.current);
setResolvedDirs({
- appConfig: initialAppConfigDirRef.current ?? defaultsRef.current.appConfig,
+ appConfig:
+ initialAppConfigDirRef.current ?? defaultsRef.current.appConfig,
claude: normalized.claudeConfigDir ?? defaultsRef.current.claude,
codex: normalized.codexConfigDir ?? defaultsRef.current.codex,
});
@@ -423,7 +435,10 @@ export function useSettings(): UseSettingsResult {
await settingsApi.applyClaudePluginConfig({ official: true });
}
} catch (error) {
- console.warn("[useSettings] Failed to sync Claude plugin config", error);
+ console.warn(
+ "[useSettings] Failed to sync Claude plugin config",
+ error,
+ );
toast.error(
t("notifications.syncClaudePluginFailed", {
defaultValue: "同步 Claude 插件失败",
@@ -436,7 +451,10 @@ export function useSettings(): UseSettingsResult {
window.localStorage.setItem("language", payload.language as Language);
}
} catch (error) {
- console.warn("[useSettings] Failed to persist language preference", error);
+ console.warn(
+ "[useSettings] Failed to persist language preference",
+ error,
+ );
}
initialLanguageRef.current = payload.language as Language;
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 9390f33..cfb3b96 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -4,3 +4,4 @@ export { settingsApi } from "./settings";
export { mcpApi } from "./mcp";
export { usageApi } from "./usage";
export { vscodeApi } from "./vscode";
+export type { ProviderSwitchEvent } from "./providers";
diff --git a/src/lib/api/mcp.ts b/src/lib/api/mcp.ts
index 0c17f86..e6b3070 100644
--- a/src/lib/api/mcp.ts
+++ b/src/lib/api/mcp.ts
@@ -18,7 +18,7 @@ export const mcpApi = {
async upsertServer(
id: string,
- spec: McpServerSpec | Record
+ spec: McpServerSpec | Record,
): Promise {
return await invoke("upsert_claude_mcp_server", { id, spec });
},
@@ -35,11 +35,19 @@ export const mcpApi = {
return await invoke("get_mcp_config", { app });
},
+ async importFromClaude(): Promise {
+ return await invoke("import_mcp_from_claude");
+ },
+
+ async importFromCodex(): Promise {
+ return await invoke("import_mcp_from_codex");
+ },
+
async upsertServerInConfig(
app: AppType,
id: string,
spec: McpServer,
- options?: { syncOtherSide?: boolean }
+ options?: { syncOtherSide?: boolean },
): Promise {
const payload = {
app,
@@ -55,7 +63,7 @@ export const mcpApi = {
async deleteServerInConfig(
app: AppType,
id: string,
- options?: { syncOtherSide?: boolean }
+ options?: { syncOtherSide?: boolean },
): Promise {
const payload = {
app,
@@ -66,4 +74,20 @@ export const mcpApi = {
};
return await invoke("delete_mcp_server_in_config", payload);
},
+
+ async setEnabled(
+ app: AppType,
+ id: string,
+ enabled: boolean,
+ ): Promise {
+ return await invoke("set_mcp_enabled", { app, id, enabled });
+ },
+
+ async syncEnabledToClaude(): Promise {
+ return await invoke("sync_enabled_mcp_to_claude");
+ },
+
+ async syncEnabledToCodex(): Promise {
+ return await invoke("sync_enabled_mcp_to_codex");
+ },
};
diff --git a/src/lib/api/providers.ts b/src/lib/api/providers.ts
index 4f44bc4..d7541ee 100644
--- a/src/lib/api/providers.ts
+++ b/src/lib/api/providers.ts
@@ -1,4 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
+import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import type { Provider } from "@/types";
import type { AppType } from "./types";
@@ -7,6 +8,11 @@ export interface ProviderSortUpdate {
sortIndex: number;
}
+export interface ProviderSwitchEvent {
+ appType: AppType;
+ providerId: string;
+}
+
export const providersApi = {
async getAll(appType: AppType): Promise> {
return await invoke("get_providers", { app_type: appType, app: appType });
@@ -64,7 +70,7 @@ export const providersApi = {
async updateSortOrder(
updates: ProviderSortUpdate[],
- appType: AppType
+ appType: AppType,
): Promise {
return await invoke("update_providers_sort_order", {
updates,
@@ -72,4 +78,13 @@ export const providersApi = {
app: appType,
});
},
+
+ async onSwitched(
+ handler: (event: ProviderSwitchEvent) => void,
+ ): Promise {
+ return await listen("provider-switched", (event) => {
+ const payload = event.payload as ProviderSwitchEvent;
+ handler(payload);
+ });
+ },
};
diff --git a/src/lib/api/vscode.ts b/src/lib/api/vscode.ts
index c9d77c0..7346fd6 100644
--- a/src/lib/api/vscode.ts
+++ b/src/lib/api/vscode.ts
@@ -20,7 +20,7 @@ export const vscodeApi = {
async testApiEndpoints(
urls: string[],
- options?: { timeoutSecs?: number }
+ options?: { timeoutSecs?: number },
): Promise {
return await invoke("test_api_endpoints", {
urls,
@@ -30,7 +30,7 @@ export const vscodeApi = {
async getCustomEndpoints(
appType: AppType,
- providerId: string
+ providerId: string,
): Promise {
return await invoke("get_custom_endpoints", {
app_type: appType,
@@ -44,7 +44,7 @@ export const vscodeApi = {
async addCustomEndpoint(
appType: AppType,
providerId: string,
- url: string
+ url: string,
): Promise {
await invoke("add_custom_endpoint", {
app_type: appType,
@@ -59,7 +59,7 @@ export const vscodeApi = {
async removeCustomEndpoint(
appType: AppType,
providerId: string,
- url: string
+ url: string,
): Promise {
await invoke("remove_custom_endpoint", {
app_type: appType,
@@ -74,7 +74,7 @@ export const vscodeApi = {
async updateEndpointLastUsed(
appType: AppType,
providerId: string,
- url: string
+ url: string,
): Promise {
await invoke("update_endpoint_last_used", {
app_type: appType,
diff --git a/src/lib/query/mutations.ts b/src/lib/query/mutations.ts
index 167c2b3..0053b70 100644
--- a/src/lib/query/mutations.ts
+++ b/src/lib/query/mutations.ts
@@ -1,11 +1,7 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
-import {
- providersApi,
- settingsApi,
- type AppType,
-} from "@/lib/api";
+import { providersApi, settingsApi, type AppType } from "@/lib/api";
import type { Provider, Settings } from "@/types";
export const useAddProviderMutation = (appType: AppType) => {
@@ -28,7 +24,7 @@ export const useAddProviderMutation = (appType: AppType) => {
toast.success(
t("notifications.providerAdded", {
defaultValue: "供应商已添加",
- })
+ }),
);
},
onError: (error: Error) => {
@@ -36,7 +32,7 @@ export const useAddProviderMutation = (appType: AppType) => {
t("notifications.addFailed", {
defaultValue: "添加供应商失败: {{error}}",
error: error.message,
- })
+ }),
);
},
});
@@ -56,7 +52,7 @@ export const useUpdateProviderMutation = (appType: AppType) => {
toast.success(
t("notifications.updateSuccess", {
defaultValue: "供应商更新成功",
- })
+ }),
);
},
onError: (error: Error) => {
@@ -64,7 +60,7 @@ export const useUpdateProviderMutation = (appType: AppType) => {
t("notifications.updateFailed", {
defaultValue: "更新供应商失败: {{error}}",
error: error.message,
- })
+ }),
);
},
});
@@ -84,7 +80,7 @@ export const useDeleteProviderMutation = (appType: AppType) => {
toast.success(
t("notifications.deleteSuccess", {
defaultValue: "供应商已删除",
- })
+ }),
);
},
onError: (error: Error) => {
@@ -92,7 +88,7 @@ export const useDeleteProviderMutation = (appType: AppType) => {
t("notifications.deleteFailed", {
defaultValue: "删除供应商失败: {{error}}",
error: error.message,
- })
+ }),
);
},
});
@@ -113,7 +109,7 @@ export const useSwitchProviderMutation = (appType: AppType) => {
t("notifications.switchSuccess", {
defaultValue: "切换供应商成功",
appName: t(`apps.${appType}`, { defaultValue: appType }),
- })
+ }),
);
},
onError: (error: Error) => {
@@ -121,7 +117,7 @@ export const useSwitchProviderMutation = (appType: AppType) => {
t("notifications.switchFailed", {
defaultValue: "切换供应商失败: {{error}}",
error: error.message,
- })
+ }),
);
},
});
@@ -140,7 +136,7 @@ export const useSaveSettingsMutation = () => {
toast.success(
t("notifications.settingsSaved", {
defaultValue: "设置已保存",
- })
+ }),
);
},
onError: (error: Error) => {
@@ -148,7 +144,7 @@ export const useSaveSettingsMutation = () => {
t("notifications.settingsSaveFailed", {
defaultValue: "保存设置失败: {{error}}",
error: error.message,
- })
+ }),
);
},
});
diff --git a/src/lib/query/queries.ts b/src/lib/query/queries.ts
index e0f27fd..8c12403 100644
--- a/src/lib/query/queries.ts
+++ b/src/lib/query/queries.ts
@@ -3,7 +3,7 @@ import { providersApi, settingsApi, type AppType } from "@/lib/api";
import type { Provider, Settings } from "@/types";
const sortProviders = (
- providers: Record
+ providers: Record,
): Record => {
const sortedEntries = Object.values(providers)
.sort((a, b) => {
@@ -31,7 +31,7 @@ export interface ProvidersQueryData {
}
export const useProvidersQuery = (
- appType: AppType
+ appType: AppType,
): UseQueryResult => {
return useQuery({
queryKey: ["providers", appType],
diff --git a/src/lib/schemas/provider.ts b/src/lib/schemas/provider.ts
index 4e97090..3d3da56 100644
--- a/src/lib/schemas/provider.ts
+++ b/src/lib/schemas/provider.ts
@@ -2,11 +2,7 @@ import { z } from "zod";
export const providerSchema = z.object({
name: z.string().min(1, "请填写供应商名称"),
- websiteUrl: z
- .string()
- .url("请输入有效的网址")
- .optional()
- .or(z.literal("")),
+ websiteUrl: z.string().url("请输入有效的网址").optional().or(z.literal("")),
settingsConfig: z
.string()
.min(1, "请填写配置内容")
diff --git a/src/lib/styles.ts b/src/lib/styles.ts
deleted file mode 100644
index 3f5950c..0000000
--- a/src/lib/styles.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * 复用的 Tailwind 样式组合,覆盖常见 UI 模式
- */
-
-// 按钮样式
-export const buttonStyles = {
- // 主按钮:蓝底白字
- primary:
- "px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-sm font-medium",
-
- // 次按钮:灰背景,深色文本
- secondary:
- "px-4 py-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 rounded-lg transition-colors text-sm font-medium",
-
- // 危险按钮:用于不可撤销/破坏性操作
- danger:
- "px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700 transition-colors text-sm font-medium",
-
- // MCP 专属按钮:绿底白字
- mcp: "px-4 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700 transition-colors text-sm font-medium",
-
- // 幽灵按钮:无背景,仅悬浮反馈
- ghost:
- "px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors text-sm font-medium",
-
- // 图标按钮:小尺寸,仅图标
- icon: "p-1.5 text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors",
-
- // 禁用态:可与其他样式组合
- disabled: "opacity-50 cursor-not-allowed pointer-events-none",
-} as const;
-
-// 卡片样式
-export const cardStyles = {
- // 基础卡片容器
- base: "bg-white rounded-lg border border-gray-200 p-4 dark:bg-gray-900 dark:border-gray-700",
-
- // 带悬浮效果的卡片
- interactive:
- "bg-white rounded-lg border border-gray-200 p-4 hover:border-gray-300 hover:shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:hover:border-gray-600 transition-[border-color,box-shadow] duration-200",
-
- // 选中/激活态卡片
- selected:
- "bg-white rounded-lg border border-blue-500 shadow-sm bg-blue-50 p-4 dark:bg-gray-900 dark:border-blue-400 dark:bg-blue-400/10",
-} as const;
-
-// 输入控件样式
-export const inputStyles = {
- // 文本输入框
- text: "w-full px-3 py-2 border border-gray-200 rounded-lg focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20 outline-none dark:bg-gray-900 dark:border-gray-700 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20 transition-colors",
-
- // 下拉选择框
- select:
- "w-full px-3 py-2 border border-gray-200 rounded-lg focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20 outline-none bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20 transition-colors",
-
- // 复选框
- checkbox:
- "w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20 border-gray-300 dark:border-gray-600 dark:bg-gray-800",
-} as const;
-
-// 徽标(Badge)样式
-export const badgeStyles = {
- // 成功徽标
- success:
- "inline-flex items-center gap-1 px-2 py-1 bg-green-500/10 text-green-500 rounded-md text-xs font-medium",
-
- // 信息徽标
- info: "inline-flex items-center gap-1 px-2 py-1 bg-blue-500/10 text-blue-500 rounded-md text-xs font-medium",
-
- // 警告徽标
- warning:
- "inline-flex items-center gap-1 px-2 py-1 bg-amber-500/10 text-amber-500 rounded-md text-xs font-medium",
-
- // 错误徽标
- error:
- "inline-flex items-center gap-1 px-2 py-1 bg-red-500/10 text-red-500 rounded-md text-xs font-medium",
-} as const;
-
-// 组合类名的工具函数
-export function cn(...classes: (string | undefined | false)[]) {
- return classes.filter(Boolean).join(" ");
-}
diff --git a/src/lib/tauri-api.ts b/src/lib/tauri-api.ts
index 219c053..0187b75 100644
--- a/src/lib/tauri-api.ts
+++ b/src/lib/tauri-api.ts
@@ -1,712 +1,17 @@
-import { invoke } from "@tauri-apps/api/core";
-import { listen, UnlistenFn } from "@tauri-apps/api/event";
-import {
- Provider,
- Settings,
- CustomEndpoint,
- McpStatus,
- McpServer,
- McpServerSpec,
- McpConfigResponse,
-} from "../types";
+import { listen, type UnlistenFn } from "@tauri-apps/api/event";
+import type { AppType } from "@/lib/api";
-// 应用类型
-export type AppType = "claude" | "codex";
-
-// 定义配置状态类型
-interface ConfigStatus {
- exists: boolean;
- path: string;
- error?: string;
+export interface ProviderSwitchedPayload {
+ appType: AppType;
+ providerId: string;
}
-// 定义导入结果类型
-interface ImportResult {
- success: boolean;
- message?: string;
-}
-
-export interface EndpointLatencyResult {
- url: string;
- latency: number | null;
- status?: number;
- error?: string;
-}
-
-// Tauri API 封装,提供统一的全局 API 接口
-export const tauriAPI = {
- // 获取所有供应商
- getProviders: async (app?: AppType): Promise> => {
- try {
- return await invoke("get_providers", { app_type: app, app });
- } catch (error) {
- console.error("获取供应商列表失败:", error);
- return {};
- }
- },
-
- // 获取当前供应商ID
- getCurrentProvider: async (app?: AppType): Promise => {
- try {
- return await invoke("get_current_provider", { app_type: app, app });
- } catch (error) {
- console.error("获取当前供应商失败:", error);
- return "";
- }
- },
-
- // 添加供应商
- addProvider: async (provider: Provider, app?: AppType): Promise => {
- try {
- return await invoke("add_provider", { provider, app_type: app, app });
- } catch (error) {
- console.error("添加供应商失败:", error);
- throw error;
- }
- },
-
- // 更新供应商
- updateProvider: async (
- provider: Provider,
- app?: AppType,
- ): Promise => {
- try {
- return await invoke("update_provider", { provider, app_type: app, app });
- } catch (error) {
- console.error("更新供应商失败:", error);
- throw error;
- }
- },
-
- // 删除供应商
- deleteProvider: async (id: string, app?: AppType): Promise => {
- try {
- return await invoke("delete_provider", { id, app_type: app, app });
- } catch (error) {
- console.error("删除供应商失败:", error);
- throw error;
- }
- },
-
- // 切换供应商
- switchProvider: async (
- providerId: string,
- app?: AppType,
- ): Promise => {
- try {
- return await invoke("switch_provider", {
- id: providerId,
- app_type: app,
- app,
- });
- } catch (error) {
- // 让调用方拿到后端的详细错误信息
- console.error("切换供应商失败:", error);
- throw error;
- }
- },
-
- // 导入当前配置为默认供应商
- importCurrentConfigAsDefault: async (
- app?: AppType,
- ): Promise => {
- try {
- const success = await invoke("import_default_config", {
- app_type: app,
- app,
- });
- return {
- success,
- message: success ? "成功导入默认配置" : "导入失败",
- };
- } catch (error) {
- console.error("导入默认配置失败:", error);
- return {
- success: false,
- message: String(error),
- };
- }
- },
-
- // 获取 Claude Code 配置文件路径
- getClaudeCodeConfigPath: async (): Promise => {
- try {
- return await invoke("get_claude_code_config_path");
- } catch (error) {
- console.error("获取配置路径失败:", error);
- return "";
- }
- },
-
- // 获取当前生效的配置目录
- getConfigDir: async (app?: AppType): Promise => {
- try {
- return await invoke("get_config_dir", { app_type: app, app });
- } catch (error) {
- console.error("获取配置目录失败:", error);
- return "";
- }
- },
-
- // 打开配置目录(按应用类型)
- openConfigFolder: async (app?: AppType): Promise => {
- try {
- await invoke("open_config_folder", { app_type: app, app });
- } catch (error) {
- console.error("打开配置目录失败:", error);
- }
- },
-
- // 选择配置目录(可选默认路径)
- selectConfigDirectory: async (
- defaultPath?: string,
- ): Promise => {
- try {
- // 后端参数为 snake_case:default_path
- return await invoke("pick_directory", { default_path: defaultPath });
- } catch (error) {
- console.error("选择配置目录失败:", error);
- return null;
- }
- },
-
- // 打开外部链接
- openExternal: async (url: string): Promise => {
- try {
- await invoke("open_external", { url });
- } catch (error) {
- console.error("打开外部链接失败:", error);
- }
- },
-
- // 更新托盘菜单
- updateTrayMenu: async (): Promise => {
- try {
- return await invoke("update_tray_menu");
- } catch (error) {
- console.error("更新托盘菜单失败:", error);
- return false;
- }
- },
-
- // 获取应用设置
- getSettings: async (): Promise => {
- try {
- return await invoke("get_settings");
- } catch (error) {
- console.error("获取设置失败:", error);
- throw error;
- }
- },
-
- // 保存设置
- saveSettings: async (settings: Settings): Promise => {
- try {
- return await invoke("save_settings", { settings });
- } catch (error) {
- console.error("保存设置失败:", error);
- return false;
- }
- },
-
- // 重启应用程序
- restartApp: async (): Promise => {
- try {
- return await invoke("restart_app");
- } catch (error) {
- console.error("重启应用失败:", error);
- return false;
- }
- },
-
- // 检查更新
- checkForUpdates: async (): Promise => {
- try {
- await invoke("check_for_updates");
- } catch (error) {
- console.error("检查更新失败:", error);
- }
- },
-
- // 判断是否为便携模式
- isPortable: async (): Promise => {
- try {
- return await invoke("is_portable_mode");
- } catch (error) {
- console.error("检测便携模式失败:", error);
- return false;
- }
- },
-
- // 获取应用配置文件路径
- getAppConfigPath: async (): Promise => {
- try {
- return await invoke("get_app_config_path");
- } catch (error) {
- console.error("获取应用配置路径失败:", error);
- return "";
- }
- },
-
- // 打开应用配置文件夹
- openAppConfigFolder: async (): Promise => {
- try {
- await invoke("open_app_config_folder");
- } catch (error) {
- console.error("打开应用配置文件夹失败:", error);
- }
- },
-
- // Claude 插件:获取 ~/.claude/config.json 状态
- getClaudePluginStatus: async (): Promise => {
- try {
- return await invoke("get_claude_plugin_status");
- } catch (error) {
- console.error("获取 Claude 插件状态失败:", error);
- return { exists: false, path: "", error: String(error) };
- }
- },
-
- // Claude 插件:读取配置内容
- readClaudePluginConfig: async (): Promise => {
- try {
- return await invoke("read_claude_plugin_config");
- } catch (error) {
- throw new Error(`读取 Claude 插件配置失败: ${String(error)}`);
- }
- },
-
- // Claude 插件:应用或移除固定配置
- applyClaudePluginConfig: async (options: {
- official: boolean;
- }): Promise => {
- const { official } = options;
- try {
- return await invoke("apply_claude_plugin_config", { official });
- } catch (error) {
- throw new Error(`写入 Claude 插件配置失败: ${String(error)}`);
- }
- },
-
- // Claude 插件:检测是否已应用目标配置
- isClaudePluginApplied: async (): Promise => {
- try {
- return await invoke("is_claude_plugin_applied");
- } catch (error) {
- throw new Error(`检测 Claude 插件配置失败: ${String(error)}`);
- }
- },
-
- // 查询供应商用量
- queryProviderUsage: async (
- providerId: string,
- app: AppType
- ): Promise => {
- try {
- return await invoke("query_provider_usage", {
- provider_id: providerId,
- providerId: providerId,
- app_type: app,
- app: app,
- appType: app,
- });
- } catch (error) {
- throw new Error(`查询用量失败: ${String(error)}`);
- }
- },
-
- // Claude MCP:获取状态(用户级 ~/.claude.json)
- getClaudeMcpStatus: async (): Promise => {
- try {
- return await invoke("get_claude_mcp_status");
- } catch (error) {
- console.error("获取 MCP 状态失败:", error);
- throw error;
- }
- },
-
- // Claude MCP:读取 ~/.claude.json 文本
- readClaudeMcpConfig: async (): Promise => {
- try {
- return await invoke("read_claude_mcp_config");
- } catch (error) {
- console.error("读取 mcp.json 失败:", error);
- throw error;
- }
- },
-
- // Claude MCP:新增/更新服务器定义
- upsertClaudeMcpServer: async (
- id: string,
- spec: McpServerSpec | Record,
- ): Promise => {
- try {
- return await invoke("upsert_claude_mcp_server", { id, spec });
- } catch (error) {
- console.error("保存 MCP 服务器失败:", error);
- throw error;
- }
- },
-
- // Claude MCP:删除服务器定义
- deleteClaudeMcpServer: async (id: string): Promise => {
- try {
- return await invoke("delete_claude_mcp_server", { id });
- } catch (error) {
- console.error("删除 MCP 服务器失败:", error);
- throw error;
- }
- },
-
- // Claude MCP:校验命令是否在 PATH 中
- validateMcpCommand: async (cmd: string): Promise => {
- try {
- return await invoke("validate_mcp_command", { cmd });
- } catch (error) {
- console.error("校验 MCP 命令失败:", error);
- return false;
- }
- },
-
- // 新:config.json 为 SSOT 的 MCP API(按客户端)
- getMcpConfig: async (app: AppType = "claude"): Promise => {
- try {
- return await invoke("get_mcp_config", { app });
- } catch (error) {
- console.error("获取 MCP 配置失败:", error);
- throw error;
- }
- },
-
- upsertMcpServerInConfig: async (
- app: AppType = "claude",
- id: string,
- spec: McpServer,
- options?: { syncOtherSide?: boolean },
- ): Promise => {
- try {
- const payload = {
- app,
- id,
- spec,
- ...(options?.syncOtherSide !== undefined
- ? { syncOtherSide: options.syncOtherSide }
- : {}),
- };
- return await invoke("upsert_mcp_server_in_config", payload);
- } catch (error) {
- console.error("写入 MCP(config.json)失败:", error);
- throw error;
- }
- },
-
- deleteMcpServerInConfig: async (
- app: AppType = "claude",
- id: string,
- ): Promise => {
- try {
- return await invoke("delete_mcp_server_in_config", { app, id });
- } catch (error) {
- console.error("删除 MCP(config.json)失败:", error);
- throw error;
- }
- },
-
- setMcpEnabled: async (
- app: AppType = "claude",
- id: string,
- enabled: boolean,
- ): Promise => {
- try {
- return await invoke("set_mcp_enabled", { app, id, enabled });
- } catch (error) {
- console.error("设置 MCP 启用状态失败:", error);
- throw error;
- }
- },
-
- syncEnabledMcpToClaude: async (): Promise => {
- try {
- return await invoke("sync_enabled_mcp_to_claude");
- } catch (error) {
- console.error("同步启用 MCP 到 .claude.json 失败:", error);
- throw error;
- }
- },
-
- // 手动同步:将启用的 MCP 投影到 ~/.codex/config.toml
- syncEnabledMcpToCodex: async (): Promise => {
- try {
- return await invoke("sync_enabled_mcp_to_codex");
- } catch (error) {
- console.error("同步启用 MCP 到 config.toml 失败:", error);
- throw error;
- }
- },
-
- importMcpFromClaude: async (): Promise => {
- try {
- return await invoke("import_mcp_from_claude");
- } catch (error) {
- console.error("从 ~/.claude.json 导入 MCP 失败:", error);
- throw error;
- }
- },
-
- // 从 ~/.codex/config.toml 导入 MCP(Codex 作用域)
- importMcpFromCodex: async (): Promise => {
- try {
- return await invoke("import_mcp_from_codex");
- } catch (error) {
- console.error("从 ~/.codex/config.toml 导入 MCP 失败:", error);
- throw error;
- }
- },
-
- // 读取当前生效(live)的 provider settings(根据 appType)
- // Codex: { auth: object, config: string }
- // Claude: settings.json 内容
- getLiveProviderSettings: async (app?: AppType): Promise => {
- try {
- return await invoke("read_live_provider_settings", {
- app_type: app,
- app,
- appType: app,
- });
- } catch (error) {
- console.error("读取 live 配置失败:", error);
- throw error;
- }
- },
-
- // ours: 第三方/自定义供应商——测速与端点管理
- // 第三方/自定义供应商:批量测试端点延迟
- testApiEndpoints: async (
- urls: string[],
- options?: { timeoutSecs?: number },
- ): Promise => {
- try {
- return await invoke("test_api_endpoints", {
- urls,
- timeout_secs: options?.timeoutSecs,
- });
- } catch (error) {
- console.error("测速调用失败:", error);
- throw error;
- }
- },
-
- // 获取自定义端点列表
- getCustomEndpoints: async (
- appType: AppType,
- providerId: string,
- ): Promise => {
- try {
- return await invoke("get_custom_endpoints", {
- // 兼容不同后端参数命名
- app_type: appType,
- app: appType,
- appType: appType,
- provider_id: providerId,
- providerId: providerId,
- });
- } catch (error) {
- console.error("获取自定义端点列表失败:", error);
- return [];
- }
- },
-
- // 添加自定义端点
- addCustomEndpoint: async (
- appType: AppType,
- providerId: string,
- url: string,
- ): Promise => {
- try {
- await invoke("add_custom_endpoint", {
- app_type: appType,
- app: appType,
- appType: appType,
- provider_id: providerId,
- providerId: providerId,
- url,
- });
- } catch (error) {
- console.error("添加自定义端点失败:", error);
- // 尽量抛出可读信息
- if (error instanceof Error) {
- throw error;
- } else {
- throw new Error(String(error));
- }
- }
- },
-
- // 删除自定义端点
- removeCustomEndpoint: async (
- appType: AppType,
- providerId: string,
- url: string,
- ): Promise => {
- try {
- await invoke("remove_custom_endpoint", {
- app_type: appType,
- app: appType,
- appType: appType,
- provider_id: providerId,
- providerId: providerId,
- url,
- });
- } catch (error) {
- console.error("删除自定义端点失败:", error);
- throw error;
- }
- },
-
- // 更新端点最后使用时间
- updateEndpointLastUsed: async (
- appType: AppType,
- providerId: string,
- url: string,
- ): Promise => {
- try {
- await invoke("update_endpoint_last_used", {
- app_type: appType,
- app: appType,
- appType: appType,
- provider_id: providerId,
- providerId: providerId,
- url,
- });
- } catch (error) {
- console.error("更新端点最后使用时间失败:", error);
- // 不抛出错误,因为这不是关键操作
- }
- },
-
- // theirs: 导入导出与文件对话框
- // 导出配置到文件
- exportConfigToFile: async (
- filePath: string,
- ): Promise<{
- success: boolean;
- message: string;
- filePath: string;
- }> => {
- try {
- // 兼容参数命名差异:同时传递 file_path 与 filePath
- return await invoke("export_config_to_file", {
- file_path: filePath,
- filePath: filePath,
- });
- } catch (error) {
- throw new Error(`导出配置失败: ${String(error)}`);
- }
- },
-
- // 从文件导入配置
- importConfigFromFile: async (
- filePath: string,
- ): Promise<{
- success: boolean;
- message: string;
- backupId?: string;
- }> => {
- try {
- // 兼容参数命名差异:同时传递 file_path 与 filePath
- return await invoke("import_config_from_file", {
- file_path: filePath,
- filePath: filePath,
- });
- } catch (error) {
- throw new Error(`导入配置失败: ${String(error)}`);
- }
- },
-
- // 保存文件对话框
- saveFileDialog: async (defaultName: string): Promise => {
- try {
- // 兼容参数命名差异:同时传递 default_name 与 defaultName
- const result = await invoke("save_file_dialog", {
- default_name: defaultName,
- defaultName: defaultName,
- });
- return result;
- } catch (error) {
- console.error("打开保存对话框失败:", error);
- return null;
- }
- },
-
- // 打开文件对话框
- openFileDialog: async (): Promise => {
- try {
- const result = await invoke("open_file_dialog");
- return result;
- } catch (error) {
- console.error("打开文件对话框失败:", error);
- return null;
- }
- },
-
- // 监听供应商切换事件
+export const tauriEvents = {
onProviderSwitched: async (
- callback: (data: { appType: string; providerId: string }) => void,
+ handler: (payload: ProviderSwitchedPayload) => void,
): Promise => {
- const unlisten = await listen("provider-switched", (event) => {
- try {
- // 事件 payload 形如 { appType: string, providerId: string }
- callback(event.payload as any);
- } catch (e) {
- console.error("处理 provider-switched 事件失败: ", e);
- }
+ return await listen("provider-switched", (event) => {
+ handler(event.payload as ProviderSwitchedPayload);
});
- return unlisten;
- },
-
- // 获取 app_config_dir 覆盖配置(从 Store)
- getAppConfigDirOverride: async (): Promise => {
- try {
- return await invoke("get_app_config_dir_override");
- } catch (error) {
- console.error("获取 app_config_dir 覆盖配置失败:", error);
- return null;
- }
- },
-
- // 设置 app_config_dir 覆盖配置(到 Store)
- setAppConfigDirOverride: async (path: string | null): Promise => {
- try {
- return await invoke("set_app_config_dir_override", { path });
- } catch (error) {
- console.error("设置 app_config_dir 覆盖配置失败:", error);
- throw error;
- }
- },
-
- // Update providers sort order
- updateProvidersSortOrder: async (
- updates: Array<{ id: string; sortIndex: number }>,
- app?: AppType,
- ): Promise => {
- try {
- return await invoke("update_providers_sort_order", {
- updates,
- app_type: app,
- app,
- });
- } catch (error) {
- console.error("更新供应商排序失败:", error);
- throw error;
- }
},
};
-
-// 创建全局 API 对象,兼容现有代码
-if (typeof window !== "undefined") {
- // 绑定到 window.api,避免 Electron 命名造成误解
- // API 内部已做 try/catch,非 Tauri 环境下也会安全返回默认值
- (window as any).api = tauriAPI;
-}
-
-export default tauriAPI;
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index 7b055a1..2aba1b4 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -3,8 +3,6 @@ import ReactDOM from "react-dom/client";
import App from "./App";
import { UpdateProvider } from "./contexts/UpdateContext";
import "./index.css";
-// 导入 Tauri API(自动绑定到 window.api)
-import "./lib/tauri-api";
// 导入国际化配置
import "./i18n";
import { QueryClientProvider } from "@tanstack/react-query";
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index d202b01..65d4902 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -1,159 +1,3 @@
///
-import {
- Provider,
- Settings,
- CustomEndpoint,
- McpStatus,
- McpConfigResponse,
- McpServer,
- McpServerSpec,
-} from "./types";
-import { AppType } from "./lib/tauri-api";
-import type { UnlistenFn } from "@tauri-apps/api/event";
-
-interface ImportResult {
- success: boolean;
- message?: string;
-}
-
-interface ConfigStatus {
- exists: boolean;
- path: string;
- error?: string;
-}
-
-declare global {
- interface Window {
- api: {
- getProviders: (app?: AppType) => Promise>;
- getCurrentProvider: (app?: AppType) => Promise;
- addProvider: (provider: Provider, app?: AppType) => Promise;
- deleteProvider: (id: string, app?: AppType) => Promise;
- updateProvider: (provider: Provider, app?: AppType) => Promise;
- switchProvider: (providerId: string, app?: AppType) => Promise;
- importCurrentConfigAsDefault: (app?: AppType) => Promise;
- getClaudeCodeConfigPath: () => Promise;
- getClaudeConfigStatus: () => Promise;
- getConfigStatus: (app?: AppType) => Promise;
- getConfigDir: (app?: AppType) => Promise;
- saveFileDialog: (defaultName: string) => Promise;
- openFileDialog: () => Promise;
- exportConfigToFile: (filePath: string) => Promise<{
- success: boolean;
- message: string;
- filePath: string;
- }>;
- importConfigFromFile: (filePath: string) => Promise<{
- success: boolean;
- message: string;
- backupId?: string;
- }>;
- selectConfigDirectory: (defaultPath?: string) => Promise;
- openConfigFolder: (app?: AppType) => Promise;
- openExternal: (url: string) => Promise;
- updateTrayMenu: () => Promise;
- onProviderSwitched: (
- callback: (data: { appType: string; providerId: string }) => void,
- ) => Promise;
- getSettings: () => Promise;
- saveSettings: (settings: Settings) => Promise;
- restartApp: () => Promise;
- checkForUpdates: () => Promise;
- isPortable: () => Promise;
- getAppConfigPath: () => Promise;
- openAppConfigFolder: () => Promise;
- // Claude 插件配置能力
- getClaudePluginStatus: () => Promise;
- readClaudePluginConfig: () => Promise;
- applyClaudePluginConfig: (options: {
- official: boolean;
- }) => Promise;
- isClaudePluginApplied: () => Promise;
- // 查询供应商用量
- queryProviderUsage: (
- providerId: string,
- app: AppType
- ) => Promise;
- // Claude MCP
- getClaudeMcpStatus: () => Promise;
- readClaudeMcpConfig: () => Promise;
- upsertClaudeMcpServer: (
- id: string,
- spec: McpServerSpec | Record,
- ) => Promise;
- deleteClaudeMcpServer: (id: string) => Promise;
- validateMcpCommand: (cmd: string) => Promise;
- // 新:config.json 为 SSOT 的 MCP API
- getMcpConfig: (app?: AppType) => Promise;
- upsertMcpServerInConfig: (
- app: AppType | undefined,
- id: string,
- spec: McpServer,
- options?: { syncOtherSide?: boolean },
- ) => Promise;
- deleteMcpServerInConfig: (
- app: AppType | undefined,
- id: string,
- ) => Promise;
- setMcpEnabled: (
- app: AppType | undefined,
- id: string,
- enabled: boolean,
- ) => Promise;
- syncEnabledMcpToClaude: () => Promise;
- syncEnabledMcpToCodex: () => Promise;
- importMcpFromClaude: () => Promise;
- importMcpFromCodex: () => Promise;
- // 读取当前生效(live)的 provider settings(根据 appType)
- // Codex: { auth: object, config: string }
- // Claude: settings.json 内容
- getLiveProviderSettings: (app?: AppType) => Promise;
- testApiEndpoints: (
- urls: string[],
- options?: { timeoutSecs?: number },
- ) => Promise<
- Array<{
- url: string;
- latency: number | null;
- status?: number;
- error?: string;
- }>
- >;
- // 自定义端点管理
- getCustomEndpoints: (
- appType: AppType,
- providerId: string,
- ) => Promise;
- addCustomEndpoint: (
- appType: AppType,
- providerId: string,
- url: string,
- ) => Promise;
- removeCustomEndpoint: (
- appType: AppType,
- providerId: string,
- url: string,
- ) => Promise;
- updateEndpointLastUsed: (
- appType: AppType,
- providerId: string,
- url: string,
- ) => Promise;
- // Provider sort order management
- updateProvidersSortOrder: (
- updates: Array<{ id: string; sortIndex: number }>,
- app?: AppType,
- ) => Promise;
- // app_config_dir override via Store
- getAppConfigDirOverride: () => Promise;
- setAppConfigDirOverride: (path: string | null) => Promise;
- };
- platform: {
- isMac: boolean;
- };
- __TAURI__?: any;
- }
-}
-
export {};