feat: complete stage 4 cleanup and code formatting
This commit completes stage 4 of the refactoring plan, focusing on cleanup and optimization of the modernized codebase. ## Key Changes ### Code Cleanup - Remove legacy `src/lib/styles.ts` (no longer needed) - Remove old modal components (`ImportProgressModal.tsx`, `ProviderList.tsx`) - Streamline `src/lib/tauri-api.ts` from 712 lines to 17 lines (-97.6%) - Remove global `window.api` pollution - Keep only event listeners (`tauriEvents.onProviderSwitched`) - All API calls now use modular `@/lib/api/*` layer ### Type System - Clean up `src/vite-env.d.ts` (remove 156 lines of outdated types) - Remove obsolete global type declarations - All TypeScript checks pass with zero errors ### Code Formatting - Format all source files with Prettier (82 files) - Fix formatting issues in 15 files: - App.tsx and core components - MCP management components - Settings module components - Provider management components - UI components ### Documentation Updates - Update `REFACTORING_CHECKLIST.md` with stage 4 progress - Mark completed tasks in `REFACTORING_MASTER_PLAN.md` ## Impact **Code Reduction:** - Total: -1,753 lines, +384 lines (net -1,369 lines) - tauri-api.ts: 712 → 17 lines (-97.6%) - Removed styles.ts: -82 lines - Removed vite-env.d.ts declarations: -156 lines **Quality Improvements:** - ✅ Zero TypeScript errors - ✅ Zero TODO/FIXME comments - ✅ 100% Prettier compliant - ✅ Zero `window.api` references - ✅ Fully modular API layer ## Testing - [x] TypeScript compilation passes - [x] Code formatting validated - [x] No linting errors Stage 4 completion: 100% Ready for stage 5 (testing and bug fixes)
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -18,7 +18,7 @@ export const mcpApi = {
|
||||
|
||||
async upsertServer(
|
||||
id: string,
|
||||
spec: McpServerSpec | Record<string, any>
|
||||
spec: McpServerSpec | Record<string, any>,
|
||||
): Promise<boolean> {
|
||||
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<number> {
|
||||
return await invoke("import_mcp_from_claude");
|
||||
},
|
||||
|
||||
async importFromCodex(): Promise<number> {
|
||||
return await invoke("import_mcp_from_codex");
|
||||
},
|
||||
|
||||
async upsertServerInConfig(
|
||||
app: AppType,
|
||||
id: string,
|
||||
spec: McpServer,
|
||||
options?: { syncOtherSide?: boolean }
|
||||
options?: { syncOtherSide?: boolean },
|
||||
): Promise<boolean> {
|
||||
const payload = {
|
||||
app,
|
||||
@@ -55,7 +63,7 @@ export const mcpApi = {
|
||||
async deleteServerInConfig(
|
||||
app: AppType,
|
||||
id: string,
|
||||
options?: { syncOtherSide?: boolean }
|
||||
options?: { syncOtherSide?: boolean },
|
||||
): Promise<boolean> {
|
||||
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<boolean> {
|
||||
return await invoke("set_mcp_enabled", { app, id, enabled });
|
||||
},
|
||||
|
||||
async syncEnabledToClaude(): Promise<boolean> {
|
||||
return await invoke("sync_enabled_mcp_to_claude");
|
||||
},
|
||||
|
||||
async syncEnabledToCodex(): Promise<boolean> {
|
||||
return await invoke("sync_enabled_mcp_to_codex");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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<Record<string, Provider>> {
|
||||
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<boolean> {
|
||||
return await invoke("update_providers_sort_order", {
|
||||
updates,
|
||||
@@ -72,4 +78,13 @@ export const providersApi = {
|
||||
app: appType,
|
||||
});
|
||||
},
|
||||
|
||||
async onSwitched(
|
||||
handler: (event: ProviderSwitchEvent) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return await listen("provider-switched", (event) => {
|
||||
const payload = event.payload as ProviderSwitchEvent;
|
||||
handler(payload);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ export const vscodeApi = {
|
||||
|
||||
async testApiEndpoints(
|
||||
urls: string[],
|
||||
options?: { timeoutSecs?: number }
|
||||
options?: { timeoutSecs?: number },
|
||||
): Promise<EndpointLatencyResult[]> {
|
||||
return await invoke("test_api_endpoints", {
|
||||
urls,
|
||||
@@ -30,7 +30,7 @@ export const vscodeApi = {
|
||||
|
||||
async getCustomEndpoints(
|
||||
appType: AppType,
|
||||
providerId: string
|
||||
providerId: string,
|
||||
): Promise<CustomEndpoint[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await invoke("update_endpoint_last_used", {
|
||||
app_type: appType,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { providersApi, settingsApi, type AppType } from "@/lib/api";
|
||||
import type { Provider, Settings } from "@/types";
|
||||
|
||||
const sortProviders = (
|
||||
providers: Record<string, Provider>
|
||||
providers: Record<string, Provider>,
|
||||
): Record<string, Provider> => {
|
||||
const sortedEntries = Object.values(providers)
|
||||
.sort((a, b) => {
|
||||
@@ -31,7 +31,7 @@ export interface ProvidersQueryData {
|
||||
}
|
||||
|
||||
export const useProvidersQuery = (
|
||||
appType: AppType
|
||||
appType: AppType,
|
||||
): UseQueryResult<ProvidersQueryData> => {
|
||||
return useQuery({
|
||||
queryKey: ["providers", appType],
|
||||
|
||||
@@ -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, "请填写配置内容")
|
||||
|
||||
@@ -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(" ");
|
||||
}
|
||||
@@ -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<Record<string, Provider>> => {
|
||||
try {
|
||||
return await invoke("get_providers", { app_type: app, app });
|
||||
} catch (error) {
|
||||
console.error("获取供应商列表失败:", error);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前供应商ID
|
||||
getCurrentProvider: async (app?: AppType): Promise<string> => {
|
||||
try {
|
||||
return await invoke("get_current_provider", { app_type: app, app });
|
||||
} catch (error) {
|
||||
console.error("获取当前供应商失败:", error);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
// 添加供应商
|
||||
addProvider: async (provider: Provider, app?: AppType): Promise<boolean> => {
|
||||
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<boolean> => {
|
||||
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<boolean> => {
|
||||
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<boolean> => {
|
||||
try {
|
||||
return await invoke("switch_provider", {
|
||||
id: providerId,
|
||||
app_type: app,
|
||||
app,
|
||||
});
|
||||
} catch (error) {
|
||||
// 让调用方拿到后端的详细错误信息
|
||||
console.error("切换供应商失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 导入当前配置为默认供应商
|
||||
importCurrentConfigAsDefault: async (
|
||||
app?: AppType,
|
||||
): Promise<ImportResult> => {
|
||||
try {
|
||||
const success = await invoke<boolean>("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<string> => {
|
||||
try {
|
||||
return await invoke("get_claude_code_config_path");
|
||||
} catch (error) {
|
||||
console.error("获取配置路径失败:", error);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前生效的配置目录
|
||||
getConfigDir: async (app?: AppType): Promise<string> => {
|
||||
try {
|
||||
return await invoke("get_config_dir", { app_type: app, app });
|
||||
} catch (error) {
|
||||
console.error("获取配置目录失败:", error);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
// 打开配置目录(按应用类型)
|
||||
openConfigFolder: async (app?: AppType): Promise<void> => {
|
||||
try {
|
||||
await invoke("open_config_folder", { app_type: app, app });
|
||||
} catch (error) {
|
||||
console.error("打开配置目录失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 选择配置目录(可选默认路径)
|
||||
selectConfigDirectory: async (
|
||||
defaultPath?: string,
|
||||
): Promise<string | null> => {
|
||||
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<void> => {
|
||||
try {
|
||||
await invoke("open_external", { url });
|
||||
} catch (error) {
|
||||
console.error("打开外部链接失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 更新托盘菜单
|
||||
updateTrayMenu: async (): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("update_tray_menu");
|
||||
} catch (error) {
|
||||
console.error("更新托盘菜单失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取应用设置
|
||||
getSettings: async (): Promise<Settings> => {
|
||||
try {
|
||||
return await invoke("get_settings");
|
||||
} catch (error) {
|
||||
console.error("获取设置失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 保存设置
|
||||
saveSettings: async (settings: Settings): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke("save_settings", { settings });
|
||||
} catch (error) {
|
||||
console.error("保存设置失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 重启应用程序
|
||||
restartApp: async (): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke("restart_app");
|
||||
} catch (error) {
|
||||
console.error("重启应用失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 检查更新
|
||||
checkForUpdates: async (): Promise<void> => {
|
||||
try {
|
||||
await invoke("check_for_updates");
|
||||
} catch (error) {
|
||||
console.error("检查更新失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 判断是否为便携模式
|
||||
isPortable: async (): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("is_portable_mode");
|
||||
} catch (error) {
|
||||
console.error("检测便携模式失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取应用配置文件路径
|
||||
getAppConfigPath: async (): Promise<string> => {
|
||||
try {
|
||||
return await invoke("get_app_config_path");
|
||||
} catch (error) {
|
||||
console.error("获取应用配置路径失败:", error);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
// 打开应用配置文件夹
|
||||
openAppConfigFolder: async (): Promise<void> => {
|
||||
try {
|
||||
await invoke("open_app_config_folder");
|
||||
} catch (error) {
|
||||
console.error("打开应用配置文件夹失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// Claude 插件:获取 ~/.claude/config.json 状态
|
||||
getClaudePluginStatus: async (): Promise<ConfigStatus> => {
|
||||
try {
|
||||
return await invoke<ConfigStatus>("get_claude_plugin_status");
|
||||
} catch (error) {
|
||||
console.error("获取 Claude 插件状态失败:", error);
|
||||
return { exists: false, path: "", error: String(error) };
|
||||
}
|
||||
},
|
||||
|
||||
// Claude 插件:读取配置内容
|
||||
readClaudePluginConfig: async (): Promise<string | null> => {
|
||||
try {
|
||||
return await invoke<string | null>("read_claude_plugin_config");
|
||||
} catch (error) {
|
||||
throw new Error(`读取 Claude 插件配置失败: ${String(error)}`);
|
||||
}
|
||||
},
|
||||
|
||||
// Claude 插件:应用或移除固定配置
|
||||
applyClaudePluginConfig: async (options: {
|
||||
official: boolean;
|
||||
}): Promise<boolean> => {
|
||||
const { official } = options;
|
||||
try {
|
||||
return await invoke<boolean>("apply_claude_plugin_config", { official });
|
||||
} catch (error) {
|
||||
throw new Error(`写入 Claude 插件配置失败: ${String(error)}`);
|
||||
}
|
||||
},
|
||||
|
||||
// Claude 插件:检测是否已应用目标配置
|
||||
isClaudePluginApplied: async (): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("is_claude_plugin_applied");
|
||||
} catch (error) {
|
||||
throw new Error(`检测 Claude 插件配置失败: ${String(error)}`);
|
||||
}
|
||||
},
|
||||
|
||||
// 查询供应商用量
|
||||
queryProviderUsage: async (
|
||||
providerId: string,
|
||||
app: AppType
|
||||
): Promise<import("../types").UsageResult> => {
|
||||
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<McpStatus> => {
|
||||
try {
|
||||
return await invoke<McpStatus>("get_claude_mcp_status");
|
||||
} catch (error) {
|
||||
console.error("获取 MCP 状态失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Claude MCP:读取 ~/.claude.json 文本
|
||||
readClaudeMcpConfig: async (): Promise<string | null> => {
|
||||
try {
|
||||
return await invoke<string | null>("read_claude_mcp_config");
|
||||
} catch (error) {
|
||||
console.error("读取 mcp.json 失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Claude MCP:新增/更新服务器定义
|
||||
upsertClaudeMcpServer: async (
|
||||
id: string,
|
||||
spec: McpServerSpec | Record<string, any>,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("upsert_claude_mcp_server", { id, spec });
|
||||
} catch (error) {
|
||||
console.error("保存 MCP 服务器失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Claude MCP:删除服务器定义
|
||||
deleteClaudeMcpServer: async (id: string): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("delete_claude_mcp_server", { id });
|
||||
} catch (error) {
|
||||
console.error("删除 MCP 服务器失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Claude MCP:校验命令是否在 PATH 中
|
||||
validateMcpCommand: async (cmd: string): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("validate_mcp_command", { cmd });
|
||||
} catch (error) {
|
||||
console.error("校验 MCP 命令失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 新:config.json 为 SSOT 的 MCP API(按客户端)
|
||||
getMcpConfig: async (app: AppType = "claude"): Promise<McpConfigResponse> => {
|
||||
try {
|
||||
return await invoke<McpConfigResponse>("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<boolean> => {
|
||||
try {
|
||||
const payload = {
|
||||
app,
|
||||
id,
|
||||
spec,
|
||||
...(options?.syncOtherSide !== undefined
|
||||
? { syncOtherSide: options.syncOtherSide }
|
||||
: {}),
|
||||
};
|
||||
return await invoke<boolean>("upsert_mcp_server_in_config", payload);
|
||||
} catch (error) {
|
||||
console.error("写入 MCP(config.json)失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
deleteMcpServerInConfig: async (
|
||||
app: AppType = "claude",
|
||||
id: string,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("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<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("set_mcp_enabled", { app, id, enabled });
|
||||
} catch (error) {
|
||||
console.error("设置 MCP 启用状态失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
syncEnabledMcpToClaude: async (): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("sync_enabled_mcp_to_claude");
|
||||
} catch (error) {
|
||||
console.error("同步启用 MCP 到 .claude.json 失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 手动同步:将启用的 MCP 投影到 ~/.codex/config.toml
|
||||
syncEnabledMcpToCodex: async (): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("sync_enabled_mcp_to_codex");
|
||||
} catch (error) {
|
||||
console.error("同步启用 MCP 到 config.toml 失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
importMcpFromClaude: async (): Promise<number> => {
|
||||
try {
|
||||
return await invoke<number>("import_mcp_from_claude");
|
||||
} catch (error) {
|
||||
console.error("从 ~/.claude.json 导入 MCP 失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 从 ~/.codex/config.toml 导入 MCP(Codex 作用域)
|
||||
importMcpFromCodex: async (): Promise<number> => {
|
||||
try {
|
||||
return await invoke<number>("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<any> => {
|
||||
try {
|
||||
return await invoke<any>("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<EndpointLatencyResult[]> => {
|
||||
try {
|
||||
return await invoke<EndpointLatencyResult[]>("test_api_endpoints", {
|
||||
urls,
|
||||
timeout_secs: options?.timeoutSecs,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("测速调用失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取自定义端点列表
|
||||
getCustomEndpoints: async (
|
||||
appType: AppType,
|
||||
providerId: string,
|
||||
): Promise<CustomEndpoint[]> => {
|
||||
try {
|
||||
return await invoke<CustomEndpoint[]>("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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<string | null> => {
|
||||
try {
|
||||
// 兼容参数命名差异:同时传递 default_name 与 defaultName
|
||||
const result = await invoke<string | null>("save_file_dialog", {
|
||||
default_name: defaultName,
|
||||
defaultName: defaultName,
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("打开保存对话框失败:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// 打开文件对话框
|
||||
openFileDialog: async (): Promise<string | null> => {
|
||||
try {
|
||||
const result = await invoke<string | null>("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<UnlistenFn> => {
|
||||
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<string | null> => {
|
||||
try {
|
||||
return await invoke<string | null>("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<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("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<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("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;
|
||||
Reference in New Issue
Block a user