diff --git a/CHANGELOG.md b/CHANGELOG.md index b9902cf..4e521d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [3.5.0] - 2025-01-15 +### ⚠ Breaking Changes + +- Tauri 命令仅接受参数 `app`(取值:`claude`/`codex`);移除对 `app_type`/`appType` 的兼容。 +- 前端类型命名统一为 `AppId`(移除 `AppType` 导出),变量命名统一为 `appId`。 + ### ✨ New Features - **MCP (Model Context Protocol) Management** - Complete MCP server configuration management system diff --git a/docs/REFACTORING_MASTER_PLAN.md b/docs/REFACTORING_MASTER_PLAN.md index 617273e..f38e19d 100644 --- a/docs/REFACTORING_MASTER_PLAN.md +++ b/docs/REFACTORING_MASTER_PLAN.md @@ -192,7 +192,7 @@ if (typeof window !== "undefined") { } // 问题 2: 无缓存机制 -getProviders: async (app?: AppType) => { +getProviders: async (app?: AppId) => { try { return await invoke("get_providers", { app }); } catch (error) { @@ -794,7 +794,7 @@ export function ProviderList({ providers, currentProviderId, appType }) { ```typescript export function useDragSort( providers: Record, - appType: AppType + appType: AppId ) { const queryClient = useQueryClient(); const { t } = useTranslation(); @@ -1133,36 +1133,35 @@ export const queryClient = new QueryClient({ ```typescript import { invoke } from "@tauri-apps/api/core"; import { Provider } from "@/types"; - -export type AppType = "claude" | "codex"; +import type { AppId } from "@/lib/api"; export const providersApi = { - getAll: async (appType: AppType): Promise> => { - return await invoke("get_providers", { app: appType }); + getAll: async (appId: AppId): Promise> => { + return await invoke("get_providers", { app: appId }); }, - getCurrent: async (appType: AppType): Promise => { - return await invoke("get_current_provider", { app: appType }); + getCurrent: async (appId: AppId): Promise => { + return await invoke("get_current_provider", { app: appId }); }, - add: async (provider: Provider, appType: AppType): Promise => { - return await invoke("add_provider", { provider, app: appType }); + add: async (provider: Provider, appId: AppId): Promise => { + return await invoke("add_provider", { provider, app: appId }); }, - update: async (provider: Provider, appType: AppType): Promise => { - return await invoke("update_provider", { provider, app: appType }); + update: async (provider: Provider, appId: AppId): Promise => { + return await invoke("update_provider", { provider, app: appId }); }, - delete: async (id: string, appType: AppType): Promise => { - return await invoke("delete_provider", { id, app: appType }); + delete: async (id: string, appId: AppId): Promise => { + return await invoke("delete_provider", { id, app: appId }); }, - switch: async (id: string, appType: AppType): Promise => { - return await invoke("switch_provider", { id, app: appType }); + switch: async (id: string, appId: AppId): Promise => { + return await invoke("switch_provider", { id, app: appId }); }, - importDefault: async (appType: AppType): Promise => { - return await invoke("import_default_config", { app: appType }); + importDefault: async (appId: AppId): Promise => { + return await invoke("import_default_config", { app: appId }); }, updateTrayMenu: async (): Promise => { @@ -1171,9 +1170,9 @@ export const providersApi = { updateSortOrder: async ( updates: Array<{ id: string; sortIndex: number }>, - appType: AppType + appId: AppId ): Promise => { - return await invoke("update_providers_sort_order", { updates, app: appType }); + return await invoke("update_providers_sort_order", { updates, app: appId }); }, }; ``` @@ -1190,7 +1189,7 @@ export const providersApi = { ```typescript import { useQuery } from "@tanstack/react-query"; -import { providersApi, AppType } from "@/lib/api"; +import { providersApi, type AppId } from "@/lib/api"; import { Provider } from "@/types"; // 排序辅助函数 @@ -1213,7 +1212,7 @@ const sortProviders = ( ); }; -export const useProvidersQuery = (appType: AppType) => { +export const useProvidersQuery = (appType: AppId) => { return useQuery({ queryKey: ["providers", appType], queryFn: async () => { @@ -1255,12 +1254,12 @@ export const useProvidersQuery = (appType: AppType) => { ```typescript import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { providersApi, AppType } from "@/lib/api"; +import { providersApi, type AppId } from "@/lib/api"; import { Provider } from "@/types"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; -export const useAddProviderMutation = (appType: AppType) => { +export const useAddProviderMutation = (appType: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); @@ -1285,7 +1284,7 @@ export const useAddProviderMutation = (appType: AppType) => { }); }; -export const useSwitchProviderMutation = (appType: AppType) => { +export const useSwitchProviderMutation = (appType: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); diff --git a/docs/REFACTORING_REFERENCE.md b/docs/REFACTORING_REFERENCE.md index ee6c876..9102d0f 100644 --- a/docs/REFACTORING_REFERENCE.md +++ b/docs/REFACTORING_REFERENCE.md @@ -19,11 +19,11 @@ ```typescript // 定义查询 Hook -export const useProvidersQuery = (appType: AppType) => { +export const useProvidersQuery = (appId: AppId) => { return useQuery({ - queryKey: ['providers', appType], + queryKey: ['providers', appId], queryFn: async () => { - const data = await providersApi.getAll(appType) + const data = await providersApi.getAll(appId) return data }, }) @@ -44,16 +44,16 @@ function MyComponent() { ```typescript // 定义 Mutation Hook -export const useAddProviderMutation = (appType: AppType) => { +export const useAddProviderMutation = (appId: AppId) => { const queryClient = useQueryClient() return useMutation({ mutationFn: async (provider: Provider) => { - return await providersApi.add(provider, appType) + return await providersApi.add(provider, appId) }, onSuccess: () => { // 重新获取数据 - queryClient.invalidateQueries({ queryKey: ['providers', appType] }) + queryClient.invalidateQueries({ queryKey: ['providers', appId] }) toast.success('添加成功') }, onError: (error: Error) => { @@ -64,7 +64,7 @@ export const useAddProviderMutation = (appType: AppType) => { // 在组件中使用 function AddProviderDialog() { - const mutation = useAddProviderMutation('claude') +const mutation = useAddProviderMutation('claude') const handleSubmit = (data: Provider) => { mutation.mutate(data) @@ -84,23 +84,23 @@ function AddProviderDialog() { ### 乐观更新 ```typescript -export const useSwitchProviderMutation = (appType: AppType) => { +export const useSwitchProviderMutation = (appId: AppId) => { const queryClient = useQueryClient() return useMutation({ mutationFn: async (providerId: string) => { - return await providersApi.switch(providerId, appType) + return await providersApi.switch(providerId, appId) }, // 乐观更新: 在请求发送前立即更新 UI onMutate: async (providerId) => { // 取消正在进行的查询 - await queryClient.cancelQueries({ queryKey: ['providers', appType] }) + await queryClient.cancelQueries({ queryKey: ['providers', appId] }) // 保存当前数据(以便回滚) - const previousData = queryClient.getQueryData(['providers', appType]) + const previousData = queryClient.getQueryData(['providers', appId]) // 乐观更新 - queryClient.setQueryData(['providers', appType], (old: any) => ({ + queryClient.setQueryData(['providers', appId], (old: any) => ({ ...old, currentProviderId: providerId, })) @@ -109,12 +109,12 @@ export const useSwitchProviderMutation = (appType: AppType) => { }, // 如果失败,回滚 onError: (err, providerId, context) => { - queryClient.setQueryData(['providers', appType], context?.previousData) + queryClient.setQueryData(['providers', appId], context?.previousData) toast.error('切换失败') }, // 无论成功失败,都重新获取数据 onSettled: () => { - queryClient.invalidateQueries({ queryKey: ['providers', appType] }) + queryClient.invalidateQueries({ queryKey: ['providers', appId] }) }, }) } @@ -124,7 +124,7 @@ export const useSwitchProviderMutation = (appType: AppType) => { ```typescript // 第二个查询依赖第一个查询的结果 -const { data: providers } = useProvidersQuery(appType) +const { data: providers } = useProvidersQuery(appId) const currentProviderId = providers?.currentProviderId const { data: currentProvider } = useQuery({ @@ -432,13 +432,13 @@ useEffect(() => { } } load() -}, [appType]) +}, [appId]) ``` **新代码** (React Query): ```typescript -const { data, isLoading, error } = useProvidersQuery(appType) +const { data, isLoading, error } = useProvidersQuery(appId) const providers = data?.providers || {} const currentProviderId = data?.currentProviderId || '' ``` @@ -688,7 +688,7 @@ const handleAdd = async (provider: Provider) => { ```typescript // 在组件中 -const addMutation = useAddProviderMutation(appType) +const addMutation = useAddProviderMutation(appId) const handleAdd = (provider: Provider) => { addMutation.mutate(provider) @@ -709,7 +709,7 @@ const handleAdd = (provider: Provider) => { ### Q: 如何在 mutation 成功后关闭对话框? ```typescript -const mutation = useAddProviderMutation(appType) +const mutation = useAddProviderMutation(appId) const handleSubmit = (data: Provider) => { mutation.mutate(data, { @@ -741,13 +741,13 @@ const schema = z.object({ const queryClient = useQueryClient() // 方式1: 使缓存失效,触发重新获取 -queryClient.invalidateQueries({ queryKey: ['providers', appType] }) +queryClient.invalidateQueries({ queryKey: ['providers', appId] }) // 方式2: 直接刷新 -queryClient.refetchQueries({ queryKey: ['providers', appType] }) +queryClient.refetchQueries({ queryKey: ['providers', appId] }) // 方式3: 更新缓存数据 -queryClient.setQueryData(['providers', appType], newData) +queryClient.setQueryData(['providers', appId], newData) ``` ### Q: 如何在组件外部使用 toast? @@ -811,7 +811,7 @@ const sortedProviders = useMemo( ```typescript const { data } = useQuery({ - queryKey: ['providers', appType], + queryKey: ['providers', appId], queryFn: fetchProviders, staleTime: 1000 * 60 * 5, // 5分钟内不重新获取 gcTime: 1000 * 60 * 10, // 10分钟后清除缓存 diff --git a/src/App.tsx b/src/App.tsx index 4eafd86..7bc4ad1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import { useProvidersQuery } from "@/lib/query"; import { providersApi, settingsApi, - type AppType, + type AppId, type ProviderSwitchEvent, } from "@/lib/api"; import { useProviderActions } from "@/hooks/useProviderActions"; @@ -26,7 +26,7 @@ import { Button } from "@/components/ui/button"; function App() { const { t } = useTranslation(); - const [activeApp, setActiveApp] = useState("claude"); + const [activeApp, setActiveApp] = useState("claude"); const [isEditMode, setIsEditMode] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isAddOpen, setIsAddOpen] = useState(false); @@ -222,7 +222,7 @@ function App() { @@ -252,13 +252,13 @@ function App() { } }} onSubmit={handleEditProvider} - appType={activeApp} + appId={activeApp} /> {usageProvider && ( setUsageProvider(null)} onSave={(script) => { @@ -290,7 +290,7 @@ function App() { ); diff --git a/src/components/AppSwitcher.tsx b/src/components/AppSwitcher.tsx index b37b58f..76a2ef6 100644 --- a/src/components/AppSwitcher.tsx +++ b/src/components/AppSwitcher.tsx @@ -1,13 +1,13 @@ -import type { AppType } from "@/lib/api"; +import type { AppId } from "@/lib/api"; import { ClaudeIcon, CodexIcon } from "./BrandIcons"; interface AppSwitcherProps { - activeApp: AppType; - onSwitch: (app: AppType) => void; + activeApp: AppId; + onSwitch: (app: AppId) => void; } export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) { - const handleSwitch = (app: AppType) => { + const handleSwitch = (app: AppId) => { if (app === activeApp) return; onSwitch(app); }; diff --git a/src/components/UsageFooter.tsx b/src/components/UsageFooter.tsx index 0784fea..f434d9f 100644 --- a/src/components/UsageFooter.tsx +++ b/src/components/UsageFooter.tsx @@ -1,27 +1,23 @@ import React from "react"; import { RefreshCw, AlertCircle } from "lucide-react"; import { useTranslation } from "react-i18next"; -import { type AppType } from "@/lib/api"; +import { type AppId } from "@/lib/api"; import { useUsageQuery } from "@/lib/query/queries"; import { UsageData } from "../types"; interface UsageFooterProps { providerId: string; - appType: AppType; + appId: AppId; usageEnabled: boolean; // 是否启用了用量查询 } -const UsageFooter: React.FC = ({ - providerId, - appType, - usageEnabled, -}) => { +const UsageFooter: React.FC = ({ providerId, appId, usageEnabled }) => { const { t } = useTranslation(); const { data: usage, isLoading: loading, refetch, - } = useUsageQuery(providerId, appType, usageEnabled); + } = useUsageQuery(providerId, appId, usageEnabled); // 只在启用用量查询且有数据时显示 if (!usageEnabled || !usage) return null; diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index 69110b7..22fe5a4 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -3,7 +3,7 @@ import { Play, Wand2 } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { Provider, UsageScript } from "../types"; -import { usageApi, type AppType } from "@/lib/api"; +import { usageApi, type AppId } from "@/lib/api"; import JsonEditor from "./JsonEditor"; import * as prettier from "prettier/standalone"; import * as parserBabel from "prettier/parser-babel"; @@ -19,7 +19,7 @@ import { Button } from "@/components/ui/button"; interface UsageScriptModalProps { provider: Provider; - appType: AppType; + appId: AppId; isOpen: boolean; onClose: () => void; onSave: (script: UsageScript) => void; @@ -82,13 +82,7 @@ const PRESET_TEMPLATES: Record = { })`, }; -const UsageScriptModal: React.FC = ({ - provider, - appType, - isOpen, - onClose, - onSave, -}) => { +const UsageScriptModal: React.FC = ({ provider, appId, isOpen, onClose, onSave }) => { const { t } = useTranslation(); const [script, setScript] = useState(() => { return ( @@ -127,7 +121,7 @@ const UsageScriptModal: React.FC = ({ const handleTest = async () => { setTesting(true); try { - const result = await usageApi.query(provider.id, appType); + const result = await usageApi.query(provider.id, appId); if (result.success && result.data && result.data.length > 0) { // 显示所有套餐数据 const summary = result.data diff --git a/src/components/mcp/McpFormModal.tsx b/src/components/mcp/McpFormModal.tsx index a030465..7a64c32 100644 --- a/src/components/mcp/McpFormModal.tsx +++ b/src/components/mcp/McpFormModal.tsx @@ -19,7 +19,7 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; -import { mcpApi, type AppType } from "@/lib/api"; +import { mcpApi, type AppId } from "@/lib/api"; import { McpServer, McpServerSpec } from "@/types"; import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets"; import McpWizardModal from "./McpWizardModal"; @@ -35,7 +35,7 @@ import { import { useMcpValidation } from "./useMcpValidation"; interface McpFormModalProps { - appType: AppType; + appId: AppId; editingId?: string; initialData?: McpServer; onSave: ( @@ -53,7 +53,7 @@ interface McpFormModalProps { * Codex: 使用 TOML 格式 */ const McpFormModal: React.FC = ({ - appType, + appId, editingId, initialData, onSave, @@ -91,11 +91,11 @@ const McpFormModal: React.FC = ({ isEditing ? hasAdditionalInfo : false, ); - // 根据 appType 决定初始格式 + // 根据 appId 决定初始格式 const [formConfig, setFormConfig] = useState(() => { const spec = initialData?.server; if (!spec) return ""; - if (appType === "codex") { + if (appId === "codex") { return mcpServerToToml(spec); } return JSON.stringify(spec, null, 2); @@ -109,11 +109,10 @@ const McpFormModal: React.FC = ({ const [otherSideHasConflict, setOtherSideHasConflict] = useState(false); // 判断是否使用 TOML 格式 - const useToml = appType === "codex"; - const syncTargetLabel = - appType === "claude" ? t("apps.codex") : t("apps.claude"); - const otherAppType: AppType = appType === "claude" ? "codex" : "claude"; - const syncCheckboxId = useMemo(() => `sync-other-side-${appType}`, [appType]); + const useToml = appId === "codex"; + const syncTargetLabel = appId === "claude" ? t("apps.codex") : t("apps.claude"); + const otherAppType: AppId = appId === "claude" ? "codex" : "claude"; + const syncCheckboxId = useMemo(() => `sync-other-side-${appId}`, [appId]); // 检测另一侧是否有同名 MCP useEffect(() => { @@ -418,7 +417,7 @@ const McpFormModal: React.FC = ({ }; const getFormTitle = () => { - if (appType === "claude") { + if (appId === "claude") { return isEditing ? t("mcp.editClaudeServer") : t("mcp.addClaudeServer"); } else { return isEditing ? t("mcp.editCodexServer") : t("mcp.addCodexServer"); diff --git a/src/components/mcp/McpPanel.tsx b/src/components/mcp/McpPanel.tsx index a055680..d019bc8 100644 --- a/src/components/mcp/McpPanel.tsx +++ b/src/components/mcp/McpPanel.tsx @@ -9,7 +9,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { type AppType } from "@/lib/api"; +import { type AppId } from "@/lib/api"; import { McpServer } from "@/types"; import { useMcpActions } from "@/hooks/useMcpActions"; import McpListItem from "./McpListItem"; @@ -19,14 +19,14 @@ import { ConfirmDialog } from "../ConfirmDialog"; interface McpPanelProps { open: boolean; onOpenChange: (open: boolean) => void; - appType: AppType; + appId: AppId; } /** * MCP 管理面板 * 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行 */ -const McpPanel: React.FC = ({ open, onOpenChange, appType }) => { +const McpPanel: React.FC = ({ open, onOpenChange, appId }) => { const { t } = useTranslation(); const [isFormOpen, setIsFormOpen] = useState(false); const [editingId, setEditingId] = useState(null); @@ -38,17 +38,16 @@ const McpPanel: React.FC = ({ open, onOpenChange, appType }) => { } | null>(null); // Use MCP actions hook - const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } = - useMcpActions(appType); + const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } = useMcpActions(appId); useEffect(() => { const setup = async () => { try { // Initialize: only import existing MCPs from corresponding client - if (appType === "claude") { + if (appId === "claude") { const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); await mcpApi.importFromClaude(); - } else if (appType === "codex") { + } else if (appId === "codex") { const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); await mcpApi.importFromCodex(); } @@ -59,8 +58,8 @@ const McpPanel: React.FC = ({ open, onOpenChange, appType }) => { } }; setup(); - // Re-initialize when appType changes - }, [appType, reload]); + // Re-initialize when appId changes + }, [appId, reload]); const handleEdit = (id: string) => { setEditingId(id); @@ -113,8 +112,7 @@ const McpPanel: React.FC = ({ open, onOpenChange, appType }) => { [serverEntries], ); - const panelTitle = - appType === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle"); + const panelTitle = appId === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle"); return ( <> @@ -203,7 +201,7 @@ const McpPanel: React.FC = ({ open, onOpenChange, appType }) => { {/* Form Modal */} {isFormOpen && ( void; - appType: AppType; + appId: AppId; onSubmit: (provider: Omit) => Promise | void; } export function AddProviderDialog({ open, onOpenChange, - appType, + appId, onSubmit, }: AddProviderDialogProps) { const { t } = useTranslation(); @@ -68,7 +68,7 @@ export function AddProviderDialog({ }; if (values.presetId) { - if (appType === "claude") { + if (appId === "claude") { const presets = providerPresets; const presetIndex = parseInt( values.presetId.replace("claude-", ""), @@ -83,7 +83,7 @@ export function AddProviderDialog({ preset.endpointCandidates.forEach(addUrl); } } - } else if (appType === "codex") { + } else if (appId === "codex") { const presets = codexProviderPresets; const presetIndex = parseInt( values.presetId.replace("codex-", ""), @@ -101,12 +101,12 @@ export function AddProviderDialog({ } } - if (appType === "claude") { + if (appId === "claude") { const env = parsedConfig.env as Record | undefined; if (env?.ANTHROPIC_BASE_URL) { addUrl(env.ANTHROPIC_BASE_URL); } - } else if (appType === "codex") { + } else if (appId === "codex") { const config = parsedConfig.config as string | undefined; if (config) { const baseUrlMatch = @@ -139,11 +139,11 @@ export function AddProviderDialog({ await onSubmit(providerData); onOpenChange(false); }, - [appType, onSubmit, onOpenChange], + [appId, onSubmit, onOpenChange], ); const submitLabel = - appType === "claude" + appId === "claude" ? t("provider.addClaudeProvider") : t("provider.addCodexProvider"); @@ -157,7 +157,7 @@ export function AddProviderDialog({
onOpenChange(false)} diff --git a/src/components/providers/EditProviderDialog.tsx b/src/components/providers/EditProviderDialog.tsx index 57e48c1..52864c7 100644 --- a/src/components/providers/EditProviderDialog.tsx +++ b/src/components/providers/EditProviderDialog.tsx @@ -15,14 +15,14 @@ import { ProviderForm, type ProviderFormValues, } from "@/components/providers/forms/ProviderForm"; -import type { AppType } from "@/lib/api"; +import type { AppId } from "@/lib/api"; interface EditProviderDialogProps { open: boolean; provider: Provider | null; onOpenChange: (open: boolean) => void; onSubmit: (provider: Provider) => Promise | void; - appType: AppType; + appId: AppId; } export function EditProviderDialog({ @@ -30,7 +30,7 @@ export function EditProviderDialog({ provider, onOpenChange, onSubmit, - appType, + appId, }: EditProviderDialogProps) { const { t } = useTranslation(); @@ -75,7 +75,7 @@ export function EditProviderDialog({
onOpenChange(false)} diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index 7d753ba..7a635ba 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -6,7 +6,7 @@ import type { DraggableSyntheticListeners, } from "@dnd-kit/core"; import type { Provider } from "@/types"; -import type { AppType } from "@/lib/api"; +import type { AppId } from "@/lib/api"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { ProviderActions } from "@/components/providers/ProviderActions"; @@ -21,7 +21,7 @@ interface DragHandleProps { interface ProviderCardProps { provider: Provider; isCurrent: boolean; - appType: AppType; + appId: AppId; isEditMode?: boolean; onSwitch: (provider: Provider) => void; onEdit: (provider: Provider) => void; @@ -61,7 +61,7 @@ const extractApiUrl = (provider: Provider, fallbackText: string) => { export function ProviderCard({ provider, isCurrent, - appType, + appId, isEditMode = false, onSwitch, onEdit, @@ -179,11 +179,7 @@ export function ProviderCard({ />
- +
); } diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index 0481b56..580b358 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -7,7 +7,7 @@ import { } from "@dnd-kit/sortable"; import type { CSSProperties } from "react"; import type { Provider } from "@/types"; -import type { AppType } from "@/lib/api"; +import type { AppId } from "@/lib/api"; import { useDragSort } from "@/hooks/useDragSort"; import { ProviderCard } from "@/components/providers/ProviderCard"; import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState"; @@ -15,7 +15,7 @@ import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState"; interface ProviderListProps { providers: Record; currentProviderId: string; - appType: AppType; + appId: AppId; isEditMode?: boolean; onSwitch: (provider: Provider) => void; onEdit: (provider: Provider) => void; @@ -30,7 +30,7 @@ interface ProviderListProps { export function ProviderList({ providers, currentProviderId, - appType, + appId, isEditMode = false, onSwitch, onEdit, @@ -43,7 +43,7 @@ export function ProviderList({ }: ProviderListProps) { const { sortedProviders, sensors, handleDragEnd } = useDragSort( providers, - appType, + appId, ); if (isLoading) { @@ -79,7 +79,7 @@ export function ProviderList({ key={provider.id} provider={provider} isCurrent={provider.id === currentProviderId} - appType={appType} + appId={appId} isEditMode={isEditMode} onSwitch={onSwitch} onEdit={onEdit} @@ -98,7 +98,7 @@ export function ProviderList({ interface SortableProviderCardProps { provider: Provider; isCurrent: boolean; - appType: AppType; + appId: AppId; isEditMode: boolean; onSwitch: (provider: Provider) => void; onEdit: (provider: Provider) => void; @@ -111,7 +111,7 @@ interface SortableProviderCardProps { function SortableProviderCard({ provider, isCurrent, - appType, + appId, isEditMode, onSwitch, onEdit, @@ -139,7 +139,7 @@ function SortableProviderCard({ void; @@ -82,7 +82,7 @@ const buildInitialEntries = ( }; const EndpointSpeedTest: React.FC = ({ - appType, + appId, providerId, value, onChange, @@ -114,7 +114,7 @@ const EndpointSpeedTest: React.FC = ({ if (!providerId) return; const customEndpoints = await vscodeApi.getCustomEndpoints( - appType, + appId, providerId, ); @@ -167,7 +167,7 @@ const EndpointSpeedTest: React.FC = ({ return () => { cancelled = true; }; - }, [appType, visible, providerId, t]); + }, [appId, visible, providerId, t]); useEffect(() => { setEntries((prev) => { @@ -284,7 +284,7 @@ const EndpointSpeedTest: React.FC = ({ // 保存到后端 try { if (providerId) { - await vscodeApi.addCustomEndpoint(appType, providerId, sanitized); + await vscodeApi.addCustomEndpoint(appId, providerId, sanitized); } // 更新本地状态 @@ -318,7 +318,7 @@ const EndpointSpeedTest: React.FC = ({ entries, normalizedSelected, onChange, - appType, + appId, providerId, t, ]); @@ -331,7 +331,7 @@ const EndpointSpeedTest: React.FC = ({ // 如果有 providerId,尝试从后端删除 if (entry.isCustom && providerId) { try { - await vscodeApi.removeCustomEndpoint(appType, providerId, entry.url); + await vscodeApi.removeCustomEndpoint(appId, providerId, entry.url); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); @@ -362,7 +362,7 @@ const EndpointSpeedTest: React.FC = ({ return next; }); }, - [normalizedSelected, onChange, appType, providerId, t], + [normalizedSelected, onChange, appId, providerId, t], ); const runSpeedTest = useCallback(async () => { @@ -387,7 +387,7 @@ const EndpointSpeedTest: React.FC = ({ try { const results = await vscodeApi.testApiEndpoints(urls, { - timeoutSecs: ENDPOINT_TIMEOUT_SECS[appType], + timeoutSecs: ENDPOINT_TIMEOUT_SECS[appId], }); const resultMap = new Map( @@ -437,7 +437,7 @@ const EndpointSpeedTest: React.FC = ({ } finally { setIsTesting(false); } - }, [entries, autoSelect, appType, normalizedSelected, onChange, t]); + }, [entries, autoSelect, appId, normalizedSelected, onChange, t]); const handleSelect = useCallback( async (url: string) => { @@ -447,7 +447,7 @@ const EndpointSpeedTest: React.FC = ({ const entry = entries.find((e) => e.url === url); if (entry?.isCustom && providerId) { try { - await vscodeApi.updateEndpointLastUsed(appType, providerId, url); + await vscodeApi.updateEndpointLastUsed(appId, providerId, url); } catch (error) { console.error(t("endpointTest.updateLastUsedFailed"), error); } @@ -455,7 +455,7 @@ const EndpointSpeedTest: React.FC = ({ onChange(url); }, - [normalizedSelected, onChange, appType, entries, providerId, t], + [normalizedSelected, onChange, appId, entries, providerId, t], ); return ( diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 0f9ea3d..948b721 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider"; -import type { AppType } from "@/lib/api"; +import type { AppId } from "@/lib/api"; import type { ProviderCategory, ProviderMeta } from "@/types"; import { providerPresets, type ProviderPreset } from "@/config/providerPresets"; import { @@ -45,7 +45,7 @@ type PresetEntry = { }; interface ProviderFormProps { - appType: AppType; + appId: AppId; submitLabel: string; onSubmit: (values: ProviderFormValues) => void; onCancel: () => void; @@ -60,7 +60,7 @@ interface ProviderFormProps { } export function ProviderForm({ - appType, + appId, submitLabel, onSubmit, onCancel, @@ -86,7 +86,7 @@ export function ProviderForm({ // 使用 category hook const { category } = useProviderCategory({ - appType, + appId, selectedPresetId, isEditMode, initialCategory: initialData?.category, @@ -95,7 +95,7 @@ export function ProviderForm({ useEffect(() => { setSelectedPresetId(initialData ? null : "custom"); setActivePreset(null); - }, [appType, initialData]); + }, [appId, initialData]); const defaultValues: ProviderFormData = useMemo( () => ({ @@ -103,11 +103,11 @@ export function ProviderForm({ websiteUrl: initialData?.websiteUrl ?? "", settingsConfig: initialData?.settingsConfig ? JSON.stringify(initialData.settingsConfig, null, 2) - : appType === "codex" + : appId === "codex" ? CODEX_DEFAULT_CONFIG : CLAUDE_DEFAULT_CONFIG, }), - [initialData, appType], + [initialData, appId], ); const form = useForm({ @@ -130,7 +130,7 @@ export function ProviderForm({ // 使用 Base URL hook (仅 Claude 模式) const { baseUrl, handleClaudeBaseUrlChange } = useBaseUrlState({ - appType, + appType: appId, category, settingsConfig: form.watch("settingsConfig"), codexConfig: "", @@ -202,7 +202,7 @@ export function ProviderForm({ ); const presetEntries = useMemo(() => { - if (appType === "codex") { + if (appId === "codex") { return codexProviderPresets.map((preset, index) => ({ id: `codex-${index}`, preset, @@ -212,7 +212,7 @@ export function ProviderForm({ id: `claude-${index}`, preset, })); - }, [appType]); + }, [appId]); // 使用 Kimi 模型选择器 hook const { @@ -240,8 +240,8 @@ export function ProviderForm({ handleTemplateValueChange, validateTemplateValues, } = useTemplateValues({ - selectedPresetId: appType === "claude" ? selectedPresetId : null, - presetEntries: appType === "claude" ? presetEntries : [], + selectedPresetId: appId === "claude" ? selectedPresetId : null, + presetEntries: appId === "claude" ? presetEntries : [], settingsConfig: form.watch("settingsConfig"), onConfigChange: (config) => form.setValue("settingsConfig", config), }); @@ -256,7 +256,7 @@ export function ProviderForm({ } = useCommonConfigSnippet({ settingsConfig: form.watch("settingsConfig"), onConfigChange: (config) => form.setValue("settingsConfig", config), - initialData: appType === "claude" ? initialData : undefined, + initialData: appId === "claude" ? initialData : undefined, }); // 使用 Codex 通用配置片段 hook (仅 Codex 模式) @@ -269,14 +269,14 @@ export function ProviderForm({ } = useCodexCommonConfig({ codexConfig, onConfigChange: handleCodexConfigChange, - initialData: appType === "codex" ? initialData : undefined, + initialData: appId === "codex" ? initialData : undefined, }); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); const handleSubmit = (values: ProviderFormData) => { // 验证模板变量(仅 Claude 模式) - if (appType === "claude" && templateValueEntries.length > 0) { + if (appId === "claude" && templateValueEntries.length > 0) { const validation = validateTemplateValues(); if (!validation.isValid && validation.missingField) { form.setError("settingsConfig", { @@ -293,7 +293,7 @@ export function ProviderForm({ let settingsConfig: string; // Codex: 组合 auth 和 config - if (appType === "codex") { + if (appId === "codex") { try { const authJson = JSON.parse(codexAuth); const configObj = { @@ -359,7 +359,7 @@ export function ProviderForm({ shouldShowApiKeyLink: shouldShowClaudeApiKeyLink, websiteUrl: claudeWebsiteUrl, } = useApiKeyLink({ - appType: "claude", + appId: "claude", category, selectedPresetId, presetEntries, @@ -371,7 +371,7 @@ export function ProviderForm({ shouldShowApiKeyLink: shouldShowCodexApiKeyLink, websiteUrl: codexWebsiteUrl, } = useApiKeyLink({ - appType: "codex", + appId: "codex", category, selectedPresetId, presetEntries, @@ -380,7 +380,7 @@ export function ProviderForm({ // 使用自定义端点 hook const customEndpointsMap = useCustomEndpoints({ - appType, + appId, selectedPresetId, presetEntries, draftCustomEndpoints, @@ -390,7 +390,7 @@ export function ProviderForm({ // 使用端点测速候选 hook const speedTestEndpoints = useSpeedTestEndpoints({ - appType, + appId, selectedPresetId, presetEntries, baseUrl, @@ -405,7 +405,7 @@ export function ProviderForm({ form.reset(defaultValues); // Codex 自定义模式:重置为空配置 - if (appType === "codex") { + if (appId === "codex") { resetCodexConfig({}, ""); } return; @@ -421,7 +421,7 @@ export function ProviderForm({ category: entry.preset.category, }); - if (appType === "codex") { + if (appId === "codex") { const preset = entry.preset as CodexProviderPreset; const auth = preset.auth ?? {}; const config = preset.config ?? ""; @@ -474,7 +474,7 @@ export function ProviderForm({ {/* Claude 专属字段 */} - {appType === "claude" && ( + {appId === "claude" && ( (() => { - if (appType !== "claude") return []; + if (appId !== "claude") return []; const map = new Map(); // 所有端点都标记为 isCustom: true,给用户完全的管理自由 @@ -94,10 +94,10 @@ export function useSpeedTestEndpoints({ } return Array.from(map.values()); - }, [appType, baseUrl, initialData, selectedPresetId, presetEntries]); + }, [appId, baseUrl, initialData, selectedPresetId, presetEntries]); const codexEndpoints = useMemo(() => { - if (appType !== "codex") return []; + if (appId !== "codex") return []; const map = new Map(); // 所有端点都标记为 isCustom: true,给用户完全的管理自由 @@ -155,7 +155,7 @@ export function useSpeedTestEndpoints({ } return Array.from(map.values()); - }, [appType, codexBaseUrl, initialData, selectedPresetId, presetEntries]); + }, [appId, codexBaseUrl, initialData, selectedPresetId, presetEntries]); - return appType === "codex" ? codexEndpoints : claudeEndpoints; + return appId === "codex" ? codexEndpoints : claudeEndpoints; } diff --git a/src/components/settings/DirectorySettings.tsx b/src/components/settings/DirectorySettings.tsx index e55352c..17b1799 100644 --- a/src/components/settings/DirectorySettings.tsx +++ b/src/components/settings/DirectorySettings.tsx @@ -3,7 +3,7 @@ import { FolderSearch, Undo2 } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { useTranslation } from "react-i18next"; -import type { AppType } from "@/lib/api"; +import type { AppId } from "@/lib/api"; import type { ResolvedDirectories } from "@/hooks/useSettings"; interface DirectorySettingsProps { @@ -14,9 +14,9 @@ interface DirectorySettingsProps { onResetAppConfig: () => Promise; claudeDir?: string; codexDir?: string; - onDirectoryChange: (app: AppType, value?: string) => void; - onBrowseDirectory: (app: AppType) => Promise; - onResetDirectory: (app: AppType) => Promise; + onDirectoryChange: (app: AppId, value?: string) => void; + onBrowseDirectory: (app: AppId) => Promise; + onResetDirectory: (app: AppId) => Promise; } export function DirectorySettings({ diff --git a/src/hooks/useDirectorySettings.ts b/src/hooks/useDirectorySettings.ts index 29a99ab..2502134 100644 --- a/src/hooks/useDirectorySettings.ts +++ b/src/hooks/useDirectorySettings.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { homeDir, join } from "@tauri-apps/api/path"; -import { settingsApi, type AppType } from "@/lib/api"; +import { settingsApi, type AppId } from "@/lib/api"; import type { SettingsFormState } from "./useSettingsForm"; type DirectoryKey = "appConfig" | "claude" | "codex"; @@ -33,7 +33,7 @@ const computeDefaultAppConfigDir = async (): Promise => { }; const computeDefaultConfigDir = async ( - app: AppType, + app: AppId, ): Promise => { try { const home = await homeDir(); @@ -58,11 +58,11 @@ export interface UseDirectorySettingsResult { resolvedDirs: ResolvedDirectories; isLoading: boolean; initialAppConfigDir?: string; - updateDirectory: (app: AppType, value?: string) => void; + updateDirectory: (app: AppId, value?: string) => void; updateAppConfigDir: (value?: string) => void; - browseDirectory: (app: AppType) => Promise; + browseDirectory: (app: AppId) => Promise; browseAppConfigDir: () => Promise; - resetDirectory: (app: AppType) => Promise; + resetDirectory: (app: AppId) => Promise; resetAppConfigDir: () => Promise; resetAllDirectories: (claudeDir?: string, codexDir?: string) => void; } @@ -187,14 +187,14 @@ export function useDirectorySettings({ ); const updateDirectory = useCallback( - (app: AppType, value?: string) => { + (app: AppId, value?: string) => { updateDirectoryState(app === "claude" ? "claude" : "codex", value); }, [updateDirectoryState], ); const browseDirectory = useCallback( - async (app: AppType) => { + async (app: AppId) => { const key: DirectoryKey = app === "claude" ? "claude" : "codex"; const currentValue = key === "claude" @@ -239,7 +239,7 @@ export function useDirectorySettings({ }, [appConfigDir, resolvedDirs.appConfig, t, updateDirectoryState]); const resetDirectory = useCallback( - async (app: AppType) => { + async (app: AppId) => { const key: DirectoryKey = app === "claude" ? "claude" : "codex"; if (!defaultsRef.current[key]) { const fallback = await computeDefaultConfigDir(app); diff --git a/src/hooks/useDragSort.ts b/src/hooks/useDragSort.ts index 2112980..55f3c02 100644 --- a/src/hooks/useDragSort.ts +++ b/src/hooks/useDragSort.ts @@ -11,11 +11,11 @@ import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import type { Provider } from "@/types"; -import { providersApi, type AppType } from "@/lib/api"; +import { providersApi, type AppId } from "@/lib/api"; export function useDragSort( providers: Record, - appType: AppType, + appId: AppId, ) { const queryClient = useQueryClient(); const { t, i18n } = useTranslation(); @@ -73,9 +73,9 @@ export function useDragSort( })); try { - await providersApi.updateSortOrder(updates, appType); + await providersApi.updateSortOrder(updates, appId); await queryClient.invalidateQueries({ - queryKey: ["providers", appType], + queryKey: ["providers", appId], }); toast.success( t("provider.sortUpdated", { @@ -91,7 +91,7 @@ export function useDragSort( ); } }, - [sortedProviders, appType, queryClient, t], + [sortedProviders, appId, queryClient, t], ); return { diff --git a/src/hooks/useMcpActions.ts b/src/hooks/useMcpActions.ts index 0de499d..7968668 100644 --- a/src/hooks/useMcpActions.ts +++ b/src/hooks/useMcpActions.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import { mcpApi, type AppType } from "@/lib/api"; +import { mcpApi, type AppId } from "@/lib/api"; import type { McpServer } from "@/types"; import { extractErrorMessage, @@ -30,7 +30,7 @@ export interface UseMcpActionsResult { * - Delete server * - Error handling and toast notifications */ -export function useMcpActions(appType: AppType): UseMcpActionsResult { +export function useMcpActions(appId: AppId): UseMcpActionsResult { const { t } = useTranslation(); const [servers, setServers] = useState>({}); const [loading, setLoading] = useState(false); @@ -38,7 +38,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { const reload = useCallback(async () => { setLoading(true); try { - const cfg = await mcpApi.getConfig(appType); + const cfg = await mcpApi.getConfig(appId); setServers(cfg.servers || {}); } catch (error) { console.error("[useMcpActions] Failed to load MCP config", error); @@ -50,7 +50,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { } finally { setLoading(false); } - }, [appType, t]); + }, [appId, t]); const toggleEnabled = useCallback( async (id: string, enabled: boolean) => { @@ -65,7 +65,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { })); try { - await mcpApi.setEnabled(appType, id, enabled); + await mcpApi.setEnabled(appId, id, enabled); toast.success(enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"), { duration: 1500, }); @@ -79,7 +79,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { }); } }, - [appType, servers, t], + [appId, servers, t], ); const saveServer = useCallback( @@ -90,7 +90,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { ) => { try { const payload: McpServer = { ...server, id }; - await mcpApi.upsertServerInConfig(appType, id, payload, { + await mcpApi.upsertServerInConfig(appId, id, payload, { syncOtherSide: options?.syncOtherSide, }); await reload(); @@ -104,13 +104,13 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { throw error; } }, - [appType, reload, t], + [appId, reload, t], ); const deleteServer = useCallback( async (id: string) => { try { - await mcpApi.deleteServerInConfig(appType, id); + await mcpApi.deleteServerInConfig(appId, id); await reload(); toast.success(t("mcp.msg.deleted"), { duration: 1500 }); } catch (error) { @@ -122,7 +122,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult { throw error; } }, - [appType, reload, t], + [appId, reload, t], ); return { diff --git a/src/hooks/useProviderActions.ts b/src/hooks/useProviderActions.ts index 2e0d0a7..0de665b 100644 --- a/src/hooks/useProviderActions.ts +++ b/src/hooks/useProviderActions.ts @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; -import { providersApi, settingsApi, type AppType } from "@/lib/api"; +import { providersApi, settingsApi, type AppId } from "@/lib/api"; import type { Provider, UsageScript } from "@/types"; import { useAddProviderMutation, @@ -16,7 +16,7 @@ import { extractErrorMessage } from "@/utils/errorUtils"; * Hook for managing provider actions (add, update, delete, switch) * Extracts business logic from App.tsx */ -export function useProviderActions(activeApp: AppType) { +export function useProviderActions(activeApp: AppId) { const { t } = useTranslation(); const queryClient = useQueryClient(); diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 9cf7046..7934aa6 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -1,7 +1,7 @@ import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import { settingsApi, type AppType } from "@/lib/api"; +import { settingsApi, type AppId } from "@/lib/api"; import { useSettingsQuery, useSaveSettingsMutation } from "@/lib/query"; import type { Settings } from "@/types"; import { useSettingsForm, type SettingsFormState } from "./useSettingsForm"; @@ -26,11 +26,11 @@ export interface UseSettingsResult { resolvedDirs: ResolvedDirectories; requiresRestart: boolean; updateSettings: (updates: Partial) => void; - updateDirectory: (app: AppType, value?: string) => void; + updateDirectory: (app: AppId, value?: string) => void; updateAppConfigDir: (value?: string) => void; - browseDirectory: (app: AppType) => Promise; + browseDirectory: (app: AppId) => Promise; browseAppConfigDir: () => Promise; - resetDirectory: (app: AppType) => Promise; + resetDirectory: (app: AppId) => Promise; resetAppConfigDir: () => Promise; saveSettings: () => Promise; resetSettings: () => void; diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 0f8e0c7..b10787f 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,4 +1,4 @@ -export type { AppType, AppId } from "./types"; +export type { AppId } from "./types"; export { providersApi } from "./providers"; export { settingsApi } from "./settings"; export { mcpApi } from "./mcp"; diff --git a/src/lib/api/mcp.ts b/src/lib/api/mcp.ts index e6b3070..7c2378f 100644 --- a/src/lib/api/mcp.ts +++ b/src/lib/api/mcp.ts @@ -5,7 +5,7 @@ import type { McpServerSpec, McpStatus, } from "@/types"; -import type { AppType } from "./types"; +import type { AppId } from "./types"; export const mcpApi = { async getStatus(): Promise { @@ -31,7 +31,7 @@ export const mcpApi = { return await invoke("validate_mcp_command", { cmd }); }, - async getConfig(app: AppType = "claude"): Promise { + async getConfig(app: AppId = "claude"): Promise { return await invoke("get_mcp_config", { app }); }, @@ -44,7 +44,7 @@ export const mcpApi = { }, async upsertServerInConfig( - app: AppType, + app: AppId, id: string, spec: McpServer, options?: { syncOtherSide?: boolean }, @@ -61,7 +61,7 @@ export const mcpApi = { }, async deleteServerInConfig( - app: AppType, + app: AppId, id: string, options?: { syncOtherSide?: boolean }, ): Promise { @@ -76,7 +76,7 @@ export const mcpApi = { }, async setEnabled( - app: AppType, + app: AppId, id: string, enabled: boolean, ): Promise { diff --git a/src/lib/api/providers.ts b/src/lib/api/providers.ts index 9c54d55..14c8e11 100644 --- a/src/lib/api/providers.ts +++ b/src/lib/api/providers.ts @@ -1,7 +1,7 @@ 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"; +import type { AppId } from "./types"; export interface ProviderSortUpdate { id: string; @@ -9,37 +9,37 @@ export interface ProviderSortUpdate { } export interface ProviderSwitchEvent { - appType: AppType; + appType: AppId; providerId: string; } export const providersApi = { - async getAll(appType: AppType): Promise> { - return await invoke("get_providers", { app: appType }); + async getAll(appId: AppId): Promise> { + return await invoke("get_providers", { app: appId }); }, - async getCurrent(appType: AppType): Promise { - return await invoke("get_current_provider", { app: appType }); + async getCurrent(appId: AppId): Promise { + return await invoke("get_current_provider", { app: appId }); }, - async add(provider: Provider, appType: AppType): Promise { - return await invoke("add_provider", { provider, app: appType }); + async add(provider: Provider, appId: AppId): Promise { + return await invoke("add_provider", { provider, app: appId }); }, - async update(provider: Provider, appType: AppType): Promise { - return await invoke("update_provider", { provider, app: appType }); + async update(provider: Provider, appId: AppId): Promise { + return await invoke("update_provider", { provider, app: appId }); }, - async delete(id: string, appType: AppType): Promise { - return await invoke("delete_provider", { id, app: appType }); + async delete(id: string, appId: AppId): Promise { + return await invoke("delete_provider", { id, app: appId }); }, - async switch(id: string, appType: AppType): Promise { - return await invoke("switch_provider", { id, app: appType }); + async switch(id: string, appId: AppId): Promise { + return await invoke("switch_provider", { id, app: appId }); }, - async importDefault(appType: AppType): Promise { - return await invoke("import_default_config", { app: appType }); + async importDefault(appId: AppId): Promise { + return await invoke("import_default_config", { app: appId }); }, async updateTrayMenu(): Promise { @@ -48,9 +48,9 @@ export const providersApi = { async updateSortOrder( updates: ProviderSortUpdate[], - appType: AppType, + appId: AppId, ): Promise { - return await invoke("update_providers_sort_order", { updates, app: appType }); + return await invoke("update_providers_sort_order", { updates, app: appId }); }, async onSwitched( diff --git a/src/lib/api/settings.ts b/src/lib/api/settings.ts index bb09019..5e30ec6 100644 --- a/src/lib/api/settings.ts +++ b/src/lib/api/settings.ts @@ -1,6 +1,6 @@ import { invoke } from "@tauri-apps/api/core"; import type { Settings } from "@/types"; -import type { AppType } from "./types"; +import type { AppId } from "./types"; export interface ConfigTransferResult { success: boolean; @@ -30,12 +30,12 @@ export const settingsApi = { return await invoke("is_portable_mode"); }, - async getConfigDir(appType: AppType): Promise { - return await invoke("get_config_dir", { app: appType }); + async getConfigDir(appId: AppId): Promise { + return await invoke("get_config_dir", { app: appId }); }, - async openConfigFolder(appType: AppType): Promise { - await invoke("open_config_folder", { app: appType }); + async openConfigFolder(appId: AppId): Promise { + await invoke("open_config_folder", { app: appId }); }, async selectConfigDirectory(defaultPath?: string): Promise { diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts index 68abc51..3e48fc9 100644 --- a/src/lib/api/types.ts +++ b/src/lib/api/types.ts @@ -1,3 +1,2 @@ -export type AppType = "claude" | "codex"; -// 为避免与后端 Rust `AppType` 枚举语义混淆,可使用更贴近“标识符”的别名 -export type AppId = AppType; +// 前端统一使用 AppId 作为应用标识(与后端命令参数 `app` 一致) +export type AppId = "claude" | "codex"; diff --git a/src/lib/api/usage.ts b/src/lib/api/usage.ts index cbc0682..89cfe30 100644 --- a/src/lib/api/usage.ts +++ b/src/lib/api/usage.ts @@ -1,12 +1,12 @@ import { invoke } from "@tauri-apps/api/core"; import type { UsageResult } from "@/types"; -import type { AppType } from "./types"; +import type { AppId } from "./types"; export const usageApi = { - async query(providerId: string, appType: AppType): Promise { + async query(providerId: string, appId: AppId): Promise { return await invoke("query_provider_usage", { provider_id: providerId, - app: appType, + app: appId, }); }, }; diff --git a/src/lib/api/vscode.ts b/src/lib/api/vscode.ts index 1ddea7c..124c05e 100644 --- a/src/lib/api/vscode.ts +++ b/src/lib/api/vscode.ts @@ -1,6 +1,6 @@ import { invoke } from "@tauri-apps/api/core"; import type { CustomEndpoint } from "@/types"; -import type { AppType } from "./types"; +import type { AppId } from "./types"; export interface EndpointLatencyResult { url: string; @@ -10,8 +10,8 @@ export interface EndpointLatencyResult { } export const vscodeApi = { - async getLiveProviderSettings(appType: AppType) { - return await invoke("read_live_provider_settings", { app: appType }); + async getLiveProviderSettings(appId: AppId) { + return await invoke("read_live_provider_settings", { app: appId }); }, async testApiEndpoints( @@ -25,46 +25,46 @@ export const vscodeApi = { }, async getCustomEndpoints( - appType: AppType, + appId: AppId, providerId: string, ): Promise { return await invoke("get_custom_endpoints", { - app: appType, + app: appId, provider_id: providerId, }); }, async addCustomEndpoint( - appType: AppType, + appId: AppId, providerId: string, url: string, ): Promise { await invoke("add_custom_endpoint", { - app: appType, + app: appId, provider_id: providerId, url, }); }, async removeCustomEndpoint( - appType: AppType, + appId: AppId, providerId: string, url: string, ): Promise { await invoke("remove_custom_endpoint", { - app: appType, + app: appId, provider_id: providerId, url, }); }, async updateEndpointLastUsed( - appType: AppType, + appId: AppId, providerId: string, url: string, ): Promise { await invoke("update_endpoint_last_used", { - app: appType, + app: appId, provider_id: providerId, url, }); diff --git a/src/lib/query/mutations.ts b/src/lib/query/mutations.ts index 0053b70..1e3b1a1 100644 --- a/src/lib/query/mutations.ts +++ b/src/lib/query/mutations.ts @@ -1,10 +1,10 @@ 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 AppId } from "@/lib/api"; import type { Provider, Settings } from "@/types"; -export const useAddProviderMutation = (appType: AppType) => { +export const useAddProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); @@ -15,11 +15,11 @@ export const useAddProviderMutation = (appType: AppType) => { id: crypto.randomUUID(), createdAt: Date.now(), }; - await providersApi.add(newProvider, appType); + await providersApi.add(newProvider, appId); return newProvider; }, onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); + await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); await providersApi.updateTrayMenu(); toast.success( t("notifications.providerAdded", { @@ -38,17 +38,17 @@ export const useAddProviderMutation = (appType: AppType) => { }); }; -export const useUpdateProviderMutation = (appType: AppType) => { +export const useUpdateProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async (provider: Provider) => { - await providersApi.update(provider, appType); + await providersApi.update(provider, appId); return provider; }, onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); + await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); toast.success( t("notifications.updateSuccess", { defaultValue: "供应商更新成功", @@ -66,16 +66,16 @@ export const useUpdateProviderMutation = (appType: AppType) => { }); }; -export const useDeleteProviderMutation = (appType: AppType) => { +export const useDeleteProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async (providerId: string) => { - await providersApi.delete(providerId, appType); + await providersApi.delete(providerId, appId); }, onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); + await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); await providersApi.updateTrayMenu(); toast.success( t("notifications.deleteSuccess", { @@ -94,21 +94,21 @@ export const useDeleteProviderMutation = (appType: AppType) => { }); }; -export const useSwitchProviderMutation = (appType: AppType) => { +export const useSwitchProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async (providerId: string) => { - return await providersApi.switch(providerId, appType); + return await providersApi.switch(providerId, appId); }, onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); + await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); await providersApi.updateTrayMenu(); toast.success( t("notifications.switchSuccess", { defaultValue: "切换供应商成功", - appName: t(`apps.${appType}`, { defaultValue: appType }), + appName: t(`apps.${appId}`, { defaultValue: appId }), }), ); }, diff --git a/src/lib/query/queries.ts b/src/lib/query/queries.ts index f2e9b87..21f0945 100644 --- a/src/lib/query/queries.ts +++ b/src/lib/query/queries.ts @@ -3,7 +3,7 @@ import { type UseQueryResult, keepPreviousData, } from "@tanstack/react-query"; -import { providersApi, settingsApi, usageApi, type AppType } from "@/lib/api"; +import { providersApi, settingsApi, usageApi, type AppId } from "@/lib/api"; import type { Provider, Settings, UsageResult } from "@/types"; const sortProviders = ( @@ -35,33 +35,33 @@ export interface ProvidersQueryData { } export const useProvidersQuery = ( - appType: AppType, + appId: AppId, ): UseQueryResult => { return useQuery({ - queryKey: ["providers", appType], + queryKey: ["providers", appId], placeholderData: keepPreviousData, queryFn: async () => { let providers: Record = {}; let currentProviderId = ""; try { - providers = await providersApi.getAll(appType); + providers = await providersApi.getAll(appId); } catch (error) { console.error("获取供应商列表失败:", error); } try { - currentProviderId = await providersApi.getCurrent(appType); + currentProviderId = await providersApi.getCurrent(appId); } catch (error) { console.error("获取当前供应商失败:", error); } if (Object.keys(providers).length === 0) { try { - const success = await providersApi.importDefault(appType); + const success = await providersApi.importDefault(appId); if (success) { - providers = await providersApi.getAll(appType); - currentProviderId = await providersApi.getCurrent(appType); + providers = await providersApi.getAll(appId); + currentProviderId = await providersApi.getCurrent(appId); } } catch (error) { console.error("导入默认配置失败:", error); @@ -85,12 +85,12 @@ export const useSettingsQuery = (): UseQueryResult => { export const useUsageQuery = ( providerId: string, - appType: AppType, + appId: AppId, enabled: boolean = true, ): UseQueryResult => { return useQuery({ - queryKey: ["usage", providerId, appType], - queryFn: async () => usageApi.query(providerId, appType), + queryKey: ["usage", providerId, appId], + queryFn: async () => usageApi.query(providerId, appId), enabled: enabled && !!providerId, refetchOnWindowFocus: false, staleTime: 5 * 60 * 1000, // 5分钟 diff --git a/tests/components/AddProviderDialog.test.tsx b/tests/components/AddProviderDialog.test.tsx index 82d5b1e..5dc76ad 100644 --- a/tests/components/AddProviderDialog.test.tsx +++ b/tests/components/AddProviderDialog.test.tsx @@ -61,7 +61,7 @@ describe("AddProviderDialog", () => { , ); @@ -97,7 +97,7 @@ describe("AddProviderDialog", () => { , ); diff --git a/tests/components/McpFormModal.test.tsx b/tests/components/McpFormModal.test.tsx index 6f72ff7..367815e 100644 --- a/tests/components/McpFormModal.test.tsx +++ b/tests/components/McpFormModal.test.tsx @@ -114,7 +114,7 @@ const renderForm = (props?: Partial>) const onClose = overrideOnClose ?? vi.fn(); render( >) }); it("TOML 模式下自动提取 ID 并成功保存", async () => { - const { onSave } = renderForm({ appType: "codex" }); + const { onSave } = renderForm({ appId: "codex" }); const configTextarea = screen.getByPlaceholderText( "mcp.form.tomlPlaceholder", @@ -288,7 +288,7 @@ command = "run" }); it("TOML 模式下缺少命令时展示错误提示并阻止提交", async () => { - const { onSave } = renderForm({ appType: "codex" }); + const { onSave } = renderForm({ appId: "codex" }); const configTextarea = screen.getByPlaceholderText( "mcp.form.tomlPlaceholder", @@ -319,7 +319,7 @@ type = "stdio" } as McpServer; const { onSave } = renderForm({ - appType: "claude", + appId: "claude", editingId: "existing", initialData, }); diff --git a/tests/components/ProviderList.test.tsx b/tests/components/ProviderList.test.tsx index 79184e4..e5da7cc 100644 --- a/tests/components/ProviderList.test.tsx +++ b/tests/components/ProviderList.test.tsx @@ -122,7 +122,7 @@ describe("ProviderList Component", () => { { { ({ })); vi.mock("@/components/providers/AddProviderDialog", () => ({ - AddProviderDialog: ({ open, onOpenChange, onSubmit, appType }: any) => + AddProviderDialog: ({ open, onOpenChange, onSubmit, appId }: any) => open ? (