refactor(types): rename AppType to AppId for semantic clarity

Rename `AppType` to `AppId` across the entire frontend codebase to better
reflect its purpose as an application identifier rather than a type category.
This aligns frontend naming with backend command parameter conventions.

Changes:
- Rename type `AppType` to `AppId` in src/lib/api/types.ts
- Remove `AppType` export from src/lib/api/index.ts
- Update all component props from `appType` to `appId` (43 files)
- Update all variable names from `appType` to `appId`
- Synchronize documentation (CHANGELOG, refactoring plans)
- Update test files and MSW mocks

BREAKING CHANGE: `AppType` type is no longer exported. Use `AppId` instead.
All component props have been renamed from `appType` to `appId`.
This commit is contained in:
Jason
2025-10-30 14:59:15 +08:00
parent 80dd6e9381
commit 8e4a0a1bbb
43 changed files with 327 additions and 347 deletions

View File

@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [3.5.0] - 2025-01-15 ## [3.5.0] - 2025-01-15
### ⚠ Breaking Changes
- Tauri 命令仅接受参数 `app`(取值:`claude`/`codex`);移除对 `app_type`/`appType` 的兼容。
- 前端类型命名统一为 `AppId`(移除 `AppType` 导出),变量命名统一为 `appId`
### ✨ New Features ### ✨ New Features
- **MCP (Model Context Protocol) Management** - Complete MCP server configuration management system - **MCP (Model Context Protocol) Management** - Complete MCP server configuration management system

View File

@@ -192,7 +192,7 @@ if (typeof window !== "undefined") {
} }
// 问题 2: 无缓存机制 // 问题 2: 无缓存机制
getProviders: async (app?: AppType) => { getProviders: async (app?: AppId) => {
try { try {
return await invoke("get_providers", { app }); return await invoke("get_providers", { app });
} catch (error) { } catch (error) {
@@ -794,7 +794,7 @@ export function ProviderList({ providers, currentProviderId, appType }) {
```typescript ```typescript
export function useDragSort( export function useDragSort(
providers: Record<string, Provider>, providers: Record<string, Provider>,
appType: AppType appType: AppId
) { ) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -1133,36 +1133,35 @@ export const queryClient = new QueryClient({
```typescript ```typescript
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { Provider } from "@/types"; import { Provider } from "@/types";
import type { AppId } from "@/lib/api";
export type AppType = "claude" | "codex";
export const providersApi = { export const providersApi = {
getAll: async (appType: AppType): Promise<Record<string, Provider>> => { getAll: async (appId: AppId): Promise<Record<string, Provider>> => {
return await invoke("get_providers", { app: appType }); return await invoke("get_providers", { app: appId });
}, },
getCurrent: async (appType: AppType): Promise<string> => { getCurrent: async (appId: AppId): Promise<string> => {
return await invoke("get_current_provider", { app: appType }); return await invoke("get_current_provider", { app: appId });
}, },
add: async (provider: Provider, appType: AppType): Promise<boolean> => { add: async (provider: Provider, appId: AppId): Promise<boolean> => {
return await invoke("add_provider", { provider, app: appType }); return await invoke("add_provider", { provider, app: appId });
}, },
update: async (provider: Provider, appType: AppType): Promise<boolean> => { update: async (provider: Provider, appId: AppId): Promise<boolean> => {
return await invoke("update_provider", { provider, app: appType }); return await invoke("update_provider", { provider, app: appId });
}, },
delete: async (id: string, appType: AppType): Promise<boolean> => { delete: async (id: string, appId: AppId): Promise<boolean> => {
return await invoke("delete_provider", { id, app: appType }); return await invoke("delete_provider", { id, app: appId });
}, },
switch: async (id: string, appType: AppType): Promise<boolean> => { switch: async (id: string, appId: AppId): Promise<boolean> => {
return await invoke("switch_provider", { id, app: appType }); return await invoke("switch_provider", { id, app: appId });
}, },
importDefault: async (appType: AppType): Promise<boolean> => { importDefault: async (appId: AppId): Promise<boolean> => {
return await invoke("import_default_config", { app: appType }); return await invoke("import_default_config", { app: appId });
}, },
updateTrayMenu: async (): Promise<boolean> => { updateTrayMenu: async (): Promise<boolean> => {
@@ -1171,9 +1170,9 @@ export const providersApi = {
updateSortOrder: async ( updateSortOrder: async (
updates: Array<{ id: string; sortIndex: number }>, updates: Array<{ id: string; sortIndex: number }>,
appType: AppType appId: AppId
): Promise<boolean> => { ): Promise<boolean> => {
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 ```typescript
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { providersApi, AppType } from "@/lib/api"; import { providersApi, type AppId } from "@/lib/api";
import { Provider } from "@/types"; import { Provider } from "@/types";
// 排序辅助函数 // 排序辅助函数
@@ -1213,7 +1212,7 @@ const sortProviders = (
); );
}; };
export const useProvidersQuery = (appType: AppType) => { export const useProvidersQuery = (appType: AppId) => {
return useQuery({ return useQuery({
queryKey: ["providers", appType], queryKey: ["providers", appType],
queryFn: async () => { queryFn: async () => {
@@ -1255,12 +1254,12 @@ export const useProvidersQuery = (appType: AppType) => {
```typescript ```typescript
import { useMutation, useQueryClient } from "@tanstack/react-query"; 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 { Provider } from "@/types";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export const useAddProviderMutation = (appType: AppType) => { export const useAddProviderMutation = (appType: AppId) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useTranslation(); 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 queryClient = useQueryClient();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -19,11 +19,11 @@
```typescript ```typescript
// 定义查询 Hook // 定义查询 Hook
export const useProvidersQuery = (appType: AppType) => { export const useProvidersQuery = (appId: AppId) => {
return useQuery({ return useQuery({
queryKey: ['providers', appType], queryKey: ['providers', appId],
queryFn: async () => { queryFn: async () => {
const data = await providersApi.getAll(appType) const data = await providersApi.getAll(appId)
return data return data
}, },
}) })
@@ -44,16 +44,16 @@ function MyComponent() {
```typescript ```typescript
// 定义 Mutation Hook // 定义 Mutation Hook
export const useAddProviderMutation = (appType: AppType) => { export const useAddProviderMutation = (appId: AppId) => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
return useMutation({ return useMutation({
mutationFn: async (provider: Provider) => { mutationFn: async (provider: Provider) => {
return await providersApi.add(provider, appType) return await providersApi.add(provider, appId)
}, },
onSuccess: () => { onSuccess: () => {
// 重新获取数据 // 重新获取数据
queryClient.invalidateQueries({ queryKey: ['providers', appType] }) queryClient.invalidateQueries({ queryKey: ['providers', appId] })
toast.success('添加成功') toast.success('添加成功')
}, },
onError: (error: Error) => { onError: (error: Error) => {
@@ -64,7 +64,7 @@ export const useAddProviderMutation = (appType: AppType) => {
// 在组件中使用 // 在组件中使用
function AddProviderDialog() { function AddProviderDialog() {
const mutation = useAddProviderMutation('claude') const mutation = useAddProviderMutation('claude')
const handleSubmit = (data: Provider) => { const handleSubmit = (data: Provider) => {
mutation.mutate(data) mutation.mutate(data)
@@ -84,23 +84,23 @@ function AddProviderDialog() {
### 乐观更新 ### 乐观更新
```typescript ```typescript
export const useSwitchProviderMutation = (appType: AppType) => { export const useSwitchProviderMutation = (appId: AppId) => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
return useMutation({ return useMutation({
mutationFn: async (providerId: string) => { mutationFn: async (providerId: string) => {
return await providersApi.switch(providerId, appType) return await providersApi.switch(providerId, appId)
}, },
// 乐观更新: 在请求发送前立即更新 UI // 乐观更新: 在请求发送前立即更新 UI
onMutate: async (providerId) => { 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, ...old,
currentProviderId: providerId, currentProviderId: providerId,
})) }))
@@ -109,12 +109,12 @@ export const useSwitchProviderMutation = (appType: AppType) => {
}, },
// 如果失败,回滚 // 如果失败,回滚
onError: (err, providerId, context) => { onError: (err, providerId, context) => {
queryClient.setQueryData(['providers', appType], context?.previousData) queryClient.setQueryData(['providers', appId], context?.previousData)
toast.error('切换失败') toast.error('切换失败')
}, },
// 无论成功失败,都重新获取数据 // 无论成功失败,都重新获取数据
onSettled: () => { onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['providers', appType] }) queryClient.invalidateQueries({ queryKey: ['providers', appId] })
}, },
}) })
} }
@@ -124,7 +124,7 @@ export const useSwitchProviderMutation = (appType: AppType) => {
```typescript ```typescript
// 第二个查询依赖第一个查询的结果 // 第二个查询依赖第一个查询的结果
const { data: providers } = useProvidersQuery(appType) const { data: providers } = useProvidersQuery(appId)
const currentProviderId = providers?.currentProviderId const currentProviderId = providers?.currentProviderId
const { data: currentProvider } = useQuery({ const { data: currentProvider } = useQuery({
@@ -432,13 +432,13 @@ useEffect(() => {
} }
} }
load() load()
}, [appType]) }, [appId])
``` ```
**新代码** (React Query): **新代码** (React Query):
```typescript ```typescript
const { data, isLoading, error } = useProvidersQuery(appType) const { data, isLoading, error } = useProvidersQuery(appId)
const providers = data?.providers || {} const providers = data?.providers || {}
const currentProviderId = data?.currentProviderId || '' const currentProviderId = data?.currentProviderId || ''
``` ```
@@ -688,7 +688,7 @@ const handleAdd = async (provider: Provider) => {
```typescript ```typescript
// 在组件中 // 在组件中
const addMutation = useAddProviderMutation(appType) const addMutation = useAddProviderMutation(appId)
const handleAdd = (provider: Provider) => { const handleAdd = (provider: Provider) => {
addMutation.mutate(provider) addMutation.mutate(provider)
@@ -709,7 +709,7 @@ const handleAdd = (provider: Provider) => {
### Q: 如何在 mutation 成功后关闭对话框? ### Q: 如何在 mutation 成功后关闭对话框?
```typescript ```typescript
const mutation = useAddProviderMutation(appType) const mutation = useAddProviderMutation(appId)
const handleSubmit = (data: Provider) => { const handleSubmit = (data: Provider) => {
mutation.mutate(data, { mutation.mutate(data, {
@@ -741,13 +741,13 @@ const schema = z.object({
const queryClient = useQueryClient() const queryClient = useQueryClient()
// 方式1: 使缓存失效,触发重新获取 // 方式1: 使缓存失效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['providers', appType] }) queryClient.invalidateQueries({ queryKey: ['providers', appId] })
// 方式2: 直接刷新 // 方式2: 直接刷新
queryClient.refetchQueries({ queryKey: ['providers', appType] }) queryClient.refetchQueries({ queryKey: ['providers', appId] })
// 方式3: 更新缓存数据 // 方式3: 更新缓存数据
queryClient.setQueryData(['providers', appType], newData) queryClient.setQueryData(['providers', appId], newData)
``` ```
### Q: 如何在组件外部使用 toast? ### Q: 如何在组件外部使用 toast?
@@ -811,7 +811,7 @@ const sortedProviders = useMemo(
```typescript ```typescript
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['providers', appType], queryKey: ['providers', appId],
queryFn: fetchProviders, queryFn: fetchProviders,
staleTime: 1000 * 60 * 5, // 5分钟内不重新获取 staleTime: 1000 * 60 * 5, // 5分钟内不重新获取
gcTime: 1000 * 60 * 10, // 10分钟后清除缓存 gcTime: 1000 * 60 * 10, // 10分钟后清除缓存

View File

@@ -7,7 +7,7 @@ import { useProvidersQuery } from "@/lib/query";
import { import {
providersApi, providersApi,
settingsApi, settingsApi,
type AppType, type AppId,
type ProviderSwitchEvent, type ProviderSwitchEvent,
} from "@/lib/api"; } from "@/lib/api";
import { useProviderActions } from "@/hooks/useProviderActions"; import { useProviderActions } from "@/hooks/useProviderActions";
@@ -26,7 +26,7 @@ import { Button } from "@/components/ui/button";
function App() { function App() {
const { t } = useTranslation(); const { t } = useTranslation();
const [activeApp, setActiveApp] = useState<AppType>("claude"); const [activeApp, setActiveApp] = useState<AppId>("claude");
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isAddOpen, setIsAddOpen] = useState(false); const [isAddOpen, setIsAddOpen] = useState(false);
@@ -222,7 +222,7 @@ function App() {
<ProviderList <ProviderList
providers={providers} providers={providers}
currentProviderId={currentProviderId} currentProviderId={currentProviderId}
appType={activeApp} appId={activeApp}
isLoading={isLoading} isLoading={isLoading}
isEditMode={isEditMode} isEditMode={isEditMode}
onSwitch={switchProvider} onSwitch={switchProvider}
@@ -239,7 +239,7 @@ function App() {
<AddProviderDialog <AddProviderDialog
open={isAddOpen} open={isAddOpen}
onOpenChange={setIsAddOpen} onOpenChange={setIsAddOpen}
appType={activeApp} appId={activeApp}
onSubmit={addProvider} onSubmit={addProvider}
/> />
@@ -252,13 +252,13 @@ function App() {
} }
}} }}
onSubmit={handleEditProvider} onSubmit={handleEditProvider}
appType={activeApp} appId={activeApp}
/> />
{usageProvider && ( {usageProvider && (
<UsageScriptModal <UsageScriptModal
provider={usageProvider} provider={usageProvider}
appType={activeApp} appId={activeApp}
isOpen={Boolean(usageProvider)} isOpen={Boolean(usageProvider)}
onClose={() => setUsageProvider(null)} onClose={() => setUsageProvider(null)}
onSave={(script) => { onSave={(script) => {
@@ -290,7 +290,7 @@ function App() {
<McpPanel <McpPanel
open={isMcpOpen} open={isMcpOpen}
onOpenChange={setIsMcpOpen} onOpenChange={setIsMcpOpen}
appType={activeApp} appId={activeApp}
/> />
</div> </div>
); );

View File

@@ -1,13 +1,13 @@
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import { ClaudeIcon, CodexIcon } from "./BrandIcons"; import { ClaudeIcon, CodexIcon } from "./BrandIcons";
interface AppSwitcherProps { interface AppSwitcherProps {
activeApp: AppType; activeApp: AppId;
onSwitch: (app: AppType) => void; onSwitch: (app: AppId) => void;
} }
export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) { export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
const handleSwitch = (app: AppType) => { const handleSwitch = (app: AppId) => {
if (app === activeApp) return; if (app === activeApp) return;
onSwitch(app); onSwitch(app);
}; };

View File

@@ -1,27 +1,23 @@
import React from "react"; import React from "react";
import { RefreshCw, AlertCircle } from "lucide-react"; import { RefreshCw, AlertCircle } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { type AppType } from "@/lib/api"; import { type AppId } from "@/lib/api";
import { useUsageQuery } from "@/lib/query/queries"; import { useUsageQuery } from "@/lib/query/queries";
import { UsageData } from "../types"; import { UsageData } from "../types";
interface UsageFooterProps { interface UsageFooterProps {
providerId: string; providerId: string;
appType: AppType; appId: AppId;
usageEnabled: boolean; // 是否启用了用量查询 usageEnabled: boolean; // 是否启用了用量查询
} }
const UsageFooter: React.FC<UsageFooterProps> = ({ const UsageFooter: React.FC<UsageFooterProps> = ({ providerId, appId, usageEnabled }) => {
providerId,
appType,
usageEnabled,
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
data: usage, data: usage,
isLoading: loading, isLoading: loading,
refetch, refetch,
} = useUsageQuery(providerId, appType, usageEnabled); } = useUsageQuery(providerId, appId, usageEnabled);
// 只在启用用量查询且有数据时显示 // 只在启用用量查询且有数据时显示
if (!usageEnabled || !usage) return null; if (!usageEnabled || !usage) return null;

View File

@@ -3,7 +3,7 @@ import { Play, Wand2 } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Provider, UsageScript } from "../types"; import { Provider, UsageScript } from "../types";
import { usageApi, type AppType } from "@/lib/api"; import { usageApi, type AppId } from "@/lib/api";
import JsonEditor from "./JsonEditor"; import JsonEditor from "./JsonEditor";
import * as prettier from "prettier/standalone"; import * as prettier from "prettier/standalone";
import * as parserBabel from "prettier/parser-babel"; import * as parserBabel from "prettier/parser-babel";
@@ -19,7 +19,7 @@ import { Button } from "@/components/ui/button";
interface UsageScriptModalProps { interface UsageScriptModalProps {
provider: Provider; provider: Provider;
appType: AppType; appId: AppId;
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onSave: (script: UsageScript) => void; onSave: (script: UsageScript) => void;
@@ -82,13 +82,7 @@ const PRESET_TEMPLATES: Record<string, string> = {
})`, })`,
}; };
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({ provider, appId, isOpen, onClose, onSave }) => {
provider,
appType,
isOpen,
onClose,
onSave,
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [script, setScript] = useState<UsageScript>(() => { const [script, setScript] = useState<UsageScript>(() => {
return ( return (
@@ -127,7 +121,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const handleTest = async () => { const handleTest = async () => {
setTesting(true); setTesting(true);
try { 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) { if (result.success && result.data && result.data.length > 0) {
// 显示所有套餐数据 // 显示所有套餐数据
const summary = result.data const summary = result.data

View File

@@ -19,7 +19,7 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; 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 { McpServer, McpServerSpec } from "@/types";
import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets"; import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets";
import McpWizardModal from "./McpWizardModal"; import McpWizardModal from "./McpWizardModal";
@@ -35,7 +35,7 @@ import {
import { useMcpValidation } from "./useMcpValidation"; import { useMcpValidation } from "./useMcpValidation";
interface McpFormModalProps { interface McpFormModalProps {
appType: AppType; appId: AppId;
editingId?: string; editingId?: string;
initialData?: McpServer; initialData?: McpServer;
onSave: ( onSave: (
@@ -53,7 +53,7 @@ interface McpFormModalProps {
* Codex: 使用 TOML 格式 * Codex: 使用 TOML 格式
*/ */
const McpFormModal: React.FC<McpFormModalProps> = ({ const McpFormModal: React.FC<McpFormModalProps> = ({
appType, appId,
editingId, editingId,
initialData, initialData,
onSave, onSave,
@@ -91,11 +91,11 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
isEditing ? hasAdditionalInfo : false, isEditing ? hasAdditionalInfo : false,
); );
// 根据 appType 决定初始格式 // 根据 appId 决定初始格式
const [formConfig, setFormConfig] = useState(() => { const [formConfig, setFormConfig] = useState(() => {
const spec = initialData?.server; const spec = initialData?.server;
if (!spec) return ""; if (!spec) return "";
if (appType === "codex") { if (appId === "codex") {
return mcpServerToToml(spec); return mcpServerToToml(spec);
} }
return JSON.stringify(spec, null, 2); return JSON.stringify(spec, null, 2);
@@ -109,11 +109,10 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
const [otherSideHasConflict, setOtherSideHasConflict] = useState(false); const [otherSideHasConflict, setOtherSideHasConflict] = useState(false);
// 判断是否使用 TOML 格式 // 判断是否使用 TOML 格式
const useToml = appType === "codex"; const useToml = appId === "codex";
const syncTargetLabel = const syncTargetLabel = appId === "claude" ? t("apps.codex") : t("apps.claude");
appType === "claude" ? t("apps.codex") : t("apps.claude"); const otherAppType: AppId = appId === "claude" ? "codex" : "claude";
const otherAppType: AppType = appType === "claude" ? "codex" : "claude"; const syncCheckboxId = useMemo(() => `sync-other-side-${appId}`, [appId]);
const syncCheckboxId = useMemo(() => `sync-other-side-${appType}`, [appType]);
// 检测另一侧是否有同名 MCP // 检测另一侧是否有同名 MCP
useEffect(() => { useEffect(() => {
@@ -418,7 +417,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
}; };
const getFormTitle = () => { const getFormTitle = () => {
if (appType === "claude") { if (appId === "claude") {
return isEditing ? t("mcp.editClaudeServer") : t("mcp.addClaudeServer"); return isEditing ? t("mcp.editClaudeServer") : t("mcp.addClaudeServer");
} else { } else {
return isEditing ? t("mcp.editCodexServer") : t("mcp.addCodexServer"); return isEditing ? t("mcp.editCodexServer") : t("mcp.addCodexServer");

View File

@@ -9,7 +9,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { type AppType } from "@/lib/api"; import { type AppId } from "@/lib/api";
import { McpServer } from "@/types"; import { McpServer } from "@/types";
import { useMcpActions } from "@/hooks/useMcpActions"; import { useMcpActions } from "@/hooks/useMcpActions";
import McpListItem from "./McpListItem"; import McpListItem from "./McpListItem";
@@ -19,14 +19,14 @@ import { ConfirmDialog } from "../ConfirmDialog";
interface McpPanelProps { interface McpPanelProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
appType: AppType; appId: AppId;
} }
/** /**
* MCP 管理面板 * MCP 管理面板
* 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行 * 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行
*/ */
const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => { const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appId }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isFormOpen, setIsFormOpen] = useState(false); const [isFormOpen, setIsFormOpen] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null); const [editingId, setEditingId] = useState<string | null>(null);
@@ -38,17 +38,16 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
} | null>(null); } | null>(null);
// Use MCP actions hook // Use MCP actions hook
const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } = const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } = useMcpActions(appId);
useMcpActions(appType);
useEffect(() => { useEffect(() => {
const setup = async () => { const setup = async () => {
try { try {
// Initialize: only import existing MCPs from corresponding client // Initialize: only import existing MCPs from corresponding client
if (appType === "claude") { if (appId === "claude") {
const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); const mcpApi = await import("@/lib/api").then((m) => m.mcpApi);
await mcpApi.importFromClaude(); await mcpApi.importFromClaude();
} else if (appType === "codex") { } else if (appId === "codex") {
const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); const mcpApi = await import("@/lib/api").then((m) => m.mcpApi);
await mcpApi.importFromCodex(); await mcpApi.importFromCodex();
} }
@@ -59,8 +58,8 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
} }
}; };
setup(); setup();
// Re-initialize when appType changes // Re-initialize when appId changes
}, [appType, reload]); }, [appId, reload]);
const handleEdit = (id: string) => { const handleEdit = (id: string) => {
setEditingId(id); setEditingId(id);
@@ -113,8 +112,7 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
[serverEntries], [serverEntries],
); );
const panelTitle = const panelTitle = appId === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle");
appType === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle");
return ( return (
<> <>
@@ -203,7 +201,7 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
{/* Form Modal */} {/* Form Modal */}
{isFormOpen && ( {isFormOpen && (
<McpFormModal <McpFormModal
appType={appType} appId={appId}
editingId={editingId || undefined} editingId={editingId || undefined}
initialData={editingId ? servers[editingId] : undefined} initialData={editingId ? servers[editingId] : undefined}
existingIds={Object.keys(servers)} existingIds={Object.keys(servers)}

View File

@@ -11,7 +11,7 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import type { Provider, CustomEndpoint } from "@/types"; import type { Provider, CustomEndpoint } from "@/types";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import { import {
ProviderForm, ProviderForm,
type ProviderFormValues, type ProviderFormValues,
@@ -22,14 +22,14 @@ import { codexProviderPresets } from "@/config/codexProviderPresets";
interface AddProviderDialogProps { interface AddProviderDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
appType: AppType; appId: AppId;
onSubmit: (provider: Omit<Provider, "id">) => Promise<void> | void; onSubmit: (provider: Omit<Provider, "id">) => Promise<void> | void;
} }
export function AddProviderDialog({ export function AddProviderDialog({
open, open,
onOpenChange, onOpenChange,
appType, appId,
onSubmit, onSubmit,
}: AddProviderDialogProps) { }: AddProviderDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -68,7 +68,7 @@ export function AddProviderDialog({
}; };
if (values.presetId) { if (values.presetId) {
if (appType === "claude") { if (appId === "claude") {
const presets = providerPresets; const presets = providerPresets;
const presetIndex = parseInt( const presetIndex = parseInt(
values.presetId.replace("claude-", ""), values.presetId.replace("claude-", ""),
@@ -83,7 +83,7 @@ export function AddProviderDialog({
preset.endpointCandidates.forEach(addUrl); preset.endpointCandidates.forEach(addUrl);
} }
} }
} else if (appType === "codex") { } else if (appId === "codex") {
const presets = codexProviderPresets; const presets = codexProviderPresets;
const presetIndex = parseInt( const presetIndex = parseInt(
values.presetId.replace("codex-", ""), values.presetId.replace("codex-", ""),
@@ -101,12 +101,12 @@ export function AddProviderDialog({
} }
} }
if (appType === "claude") { if (appId === "claude") {
const env = parsedConfig.env as Record<string, any> | undefined; const env = parsedConfig.env as Record<string, any> | undefined;
if (env?.ANTHROPIC_BASE_URL) { if (env?.ANTHROPIC_BASE_URL) {
addUrl(env.ANTHROPIC_BASE_URL); addUrl(env.ANTHROPIC_BASE_URL);
} }
} else if (appType === "codex") { } else if (appId === "codex") {
const config = parsedConfig.config as string | undefined; const config = parsedConfig.config as string | undefined;
if (config) { if (config) {
const baseUrlMatch = const baseUrlMatch =
@@ -139,11 +139,11 @@ export function AddProviderDialog({
await onSubmit(providerData); await onSubmit(providerData);
onOpenChange(false); onOpenChange(false);
}, },
[appType, onSubmit, onOpenChange], [appId, onSubmit, onOpenChange],
); );
const submitLabel = const submitLabel =
appType === "claude" appId === "claude"
? t("provider.addClaudeProvider") ? t("provider.addClaudeProvider")
: t("provider.addCodexProvider"); : t("provider.addCodexProvider");
@@ -157,7 +157,7 @@ export function AddProviderDialog({
<div className="flex-1 overflow-y-auto px-6 py-4"> <div className="flex-1 overflow-y-auto px-6 py-4">
<ProviderForm <ProviderForm
appType={appType} appId={appId}
submitLabel={t("common.add")} submitLabel={t("common.add")}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)} onCancel={() => onOpenChange(false)}

View File

@@ -15,14 +15,14 @@ import {
ProviderForm, ProviderForm,
type ProviderFormValues, type ProviderFormValues,
} from "@/components/providers/forms/ProviderForm"; } from "@/components/providers/forms/ProviderForm";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
interface EditProviderDialogProps { interface EditProviderDialogProps {
open: boolean; open: boolean;
provider: Provider | null; provider: Provider | null;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
onSubmit: (provider: Provider) => Promise<void> | void; onSubmit: (provider: Provider) => Promise<void> | void;
appType: AppType; appId: AppId;
} }
export function EditProviderDialog({ export function EditProviderDialog({
@@ -30,7 +30,7 @@ export function EditProviderDialog({
provider, provider,
onOpenChange, onOpenChange,
onSubmit, onSubmit,
appType, appId,
}: EditProviderDialogProps) { }: EditProviderDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -75,7 +75,7 @@ export function EditProviderDialog({
<div className="flex-1 overflow-y-auto px-6 py-4"> <div className="flex-1 overflow-y-auto px-6 py-4">
<ProviderForm <ProviderForm
appType={appType} appId={appId}
submitLabel={t("common.save")} submitLabel={t("common.save")}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)} onCancel={() => onOpenChange(false)}

View File

@@ -6,7 +6,7 @@ import type {
DraggableSyntheticListeners, DraggableSyntheticListeners,
} from "@dnd-kit/core"; } from "@dnd-kit/core";
import type { Provider } from "@/types"; import type { Provider } from "@/types";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ProviderActions } from "@/components/providers/ProviderActions"; import { ProviderActions } from "@/components/providers/ProviderActions";
@@ -21,7 +21,7 @@ interface DragHandleProps {
interface ProviderCardProps { interface ProviderCardProps {
provider: Provider; provider: Provider;
isCurrent: boolean; isCurrent: boolean;
appType: AppType; appId: AppId;
isEditMode?: boolean; isEditMode?: boolean;
onSwitch: (provider: Provider) => void; onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void; onEdit: (provider: Provider) => void;
@@ -61,7 +61,7 @@ const extractApiUrl = (provider: Provider, fallbackText: string) => {
export function ProviderCard({ export function ProviderCard({
provider, provider,
isCurrent, isCurrent,
appType, appId,
isEditMode = false, isEditMode = false,
onSwitch, onSwitch,
onEdit, onEdit,
@@ -179,11 +179,7 @@ export function ProviderCard({
/> />
</div> </div>
<UsageFooter <UsageFooter providerId={provider.id} appId={appId} usageEnabled={usageEnabled} />
providerId={provider.id}
appType={appType}
usageEnabled={usageEnabled}
/>
</div> </div>
); );
} }

View File

@@ -7,7 +7,7 @@ import {
} from "@dnd-kit/sortable"; } from "@dnd-kit/sortable";
import type { CSSProperties } from "react"; import type { CSSProperties } from "react";
import type { Provider } from "@/types"; import type { Provider } from "@/types";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import { useDragSort } from "@/hooks/useDragSort"; import { useDragSort } from "@/hooks/useDragSort";
import { ProviderCard } from "@/components/providers/ProviderCard"; import { ProviderCard } from "@/components/providers/ProviderCard";
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState"; import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
@@ -15,7 +15,7 @@ import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
interface ProviderListProps { interface ProviderListProps {
providers: Record<string, Provider>; providers: Record<string, Provider>;
currentProviderId: string; currentProviderId: string;
appType: AppType; appId: AppId;
isEditMode?: boolean; isEditMode?: boolean;
onSwitch: (provider: Provider) => void; onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void; onEdit: (provider: Provider) => void;
@@ -30,7 +30,7 @@ interface ProviderListProps {
export function ProviderList({ export function ProviderList({
providers, providers,
currentProviderId, currentProviderId,
appType, appId,
isEditMode = false, isEditMode = false,
onSwitch, onSwitch,
onEdit, onEdit,
@@ -43,7 +43,7 @@ export function ProviderList({
}: ProviderListProps) { }: ProviderListProps) {
const { sortedProviders, sensors, handleDragEnd } = useDragSort( const { sortedProviders, sensors, handleDragEnd } = useDragSort(
providers, providers,
appType, appId,
); );
if (isLoading) { if (isLoading) {
@@ -79,7 +79,7 @@ export function ProviderList({
key={provider.id} key={provider.id}
provider={provider} provider={provider}
isCurrent={provider.id === currentProviderId} isCurrent={provider.id === currentProviderId}
appType={appType} appId={appId}
isEditMode={isEditMode} isEditMode={isEditMode}
onSwitch={onSwitch} onSwitch={onSwitch}
onEdit={onEdit} onEdit={onEdit}
@@ -98,7 +98,7 @@ export function ProviderList({
interface SortableProviderCardProps { interface SortableProviderCardProps {
provider: Provider; provider: Provider;
isCurrent: boolean; isCurrent: boolean;
appType: AppType; appId: AppId;
isEditMode: boolean; isEditMode: boolean;
onSwitch: (provider: Provider) => void; onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void; onEdit: (provider: Provider) => void;
@@ -111,7 +111,7 @@ interface SortableProviderCardProps {
function SortableProviderCard({ function SortableProviderCard({
provider, provider,
isCurrent, isCurrent,
appType, appId,
isEditMode, isEditMode,
onSwitch, onSwitch,
onEdit, onEdit,
@@ -139,7 +139,7 @@ function SortableProviderCard({
<ProviderCard <ProviderCard
provider={provider} provider={provider}
isCurrent={isCurrent} isCurrent={isCurrent}
appType={appType} appId={appId}
isEditMode={isEditMode} isEditMode={isEditMode}
onSwitch={onSwitch} onSwitch={onSwitch}
onEdit={onEdit} onEdit={onEdit}

View File

@@ -149,7 +149,7 @@ export function ClaudeFormFields({
{/* 端点测速弹窗 */} {/* 端点测速弹窗 */}
{shouldShowSpeedTest && isEndpointModalOpen && ( {shouldShowSpeedTest && isEndpointModalOpen && (
<EndpointSpeedTest <EndpointSpeedTest
appType="claude" appId="claude"
value={baseUrl} value={baseUrl}
onChange={onBaseUrlChange} onChange={onBaseUrlChange}
initialEndpoints={speedTestEndpoints} initialEndpoints={speedTestEndpoints}

View File

@@ -80,7 +80,7 @@ export function CodexFormFields({
{/* 端点测速弹窗 - Codex */} {/* 端点测速弹窗 - Codex */}
{shouldShowSpeedTest && isEndpointModalOpen && ( {shouldShowSpeedTest && isEndpointModalOpen && (
<EndpointSpeedTest <EndpointSpeedTest
appType="codex" appId="codex"
value={codexBaseUrl} value={codexBaseUrl}
onChange={onBaseUrlChange} onChange={onBaseUrlChange}
initialEndpoints={speedTestEndpoints} initialEndpoints={speedTestEndpoints}

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Zap, Loader2, Plus, X, AlertCircle, Save } from "lucide-react"; import { Zap, Loader2, Plus, X, AlertCircle, Save } from "lucide-react";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import { vscodeApi } from "@/lib/api/vscode"; import { vscodeApi } from "@/lib/api/vscode";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -28,7 +28,7 @@ interface TestResult {
} }
interface EndpointSpeedTestProps { interface EndpointSpeedTestProps {
appType: AppType; appId: AppId;
providerId?: string; providerId?: string;
value: string; value: string;
onChange: (url: string) => void; onChange: (url: string) => void;
@@ -82,7 +82,7 @@ const buildInitialEntries = (
}; };
const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
appType, appId,
providerId, providerId,
value, value,
onChange, onChange,
@@ -114,7 +114,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
if (!providerId) return; if (!providerId) return;
const customEndpoints = await vscodeApi.getCustomEndpoints( const customEndpoints = await vscodeApi.getCustomEndpoints(
appType, appId,
providerId, providerId,
); );
@@ -167,7 +167,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [appType, visible, providerId, t]); }, [appId, visible, providerId, t]);
useEffect(() => { useEffect(() => {
setEntries((prev) => { setEntries((prev) => {
@@ -284,7 +284,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
// 保存到后端 // 保存到后端
try { try {
if (providerId) { if (providerId) {
await vscodeApi.addCustomEndpoint(appType, providerId, sanitized); await vscodeApi.addCustomEndpoint(appId, providerId, sanitized);
} }
// 更新本地状态 // 更新本地状态
@@ -318,7 +318,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
entries, entries,
normalizedSelected, normalizedSelected,
onChange, onChange,
appType, appId,
providerId, providerId,
t, t,
]); ]);
@@ -331,7 +331,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
// 如果有 providerId尝试从后端删除 // 如果有 providerId尝试从后端删除
if (entry.isCustom && providerId) { if (entry.isCustom && providerId) {
try { try {
await vscodeApi.removeCustomEndpoint(appType, providerId, entry.url); await vscodeApi.removeCustomEndpoint(appId, providerId, entry.url);
} catch (error) { } catch (error) {
const errorMsg = const errorMsg =
error instanceof Error ? error.message : String(error); error instanceof Error ? error.message : String(error);
@@ -362,7 +362,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
return next; return next;
}); });
}, },
[normalizedSelected, onChange, appType, providerId, t], [normalizedSelected, onChange, appId, providerId, t],
); );
const runSpeedTest = useCallback(async () => { const runSpeedTest = useCallback(async () => {
@@ -387,7 +387,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
try { try {
const results = await vscodeApi.testApiEndpoints(urls, { const results = await vscodeApi.testApiEndpoints(urls, {
timeoutSecs: ENDPOINT_TIMEOUT_SECS[appType], timeoutSecs: ENDPOINT_TIMEOUT_SECS[appId],
}); });
const resultMap = new Map( const resultMap = new Map(
@@ -437,7 +437,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
} finally { } finally {
setIsTesting(false); setIsTesting(false);
} }
}, [entries, autoSelect, appType, normalizedSelected, onChange, t]); }, [entries, autoSelect, appId, normalizedSelected, onChange, t]);
const handleSelect = useCallback( const handleSelect = useCallback(
async (url: string) => { async (url: string) => {
@@ -447,7 +447,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
const entry = entries.find((e) => e.url === url); const entry = entries.find((e) => e.url === url);
if (entry?.isCustom && providerId) { if (entry?.isCustom && providerId) {
try { try {
await vscodeApi.updateEndpointLastUsed(appType, providerId, url); await vscodeApi.updateEndpointLastUsed(appId, providerId, url);
} catch (error) { } catch (error) {
console.error(t("endpointTest.updateLastUsedFailed"), error); console.error(t("endpointTest.updateLastUsedFailed"), error);
} }
@@ -455,7 +455,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
onChange(url); onChange(url);
}, },
[normalizedSelected, onChange, appType, entries, providerId, t], [normalizedSelected, onChange, appId, entries, providerId, t],
); );
return ( return (

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form"; import { Form } from "@/components/ui/form";
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider"; 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 type { ProviderCategory, ProviderMeta } from "@/types";
import { providerPresets, type ProviderPreset } from "@/config/providerPresets"; import { providerPresets, type ProviderPreset } from "@/config/providerPresets";
import { import {
@@ -45,7 +45,7 @@ type PresetEntry = {
}; };
interface ProviderFormProps { interface ProviderFormProps {
appType: AppType; appId: AppId;
submitLabel: string; submitLabel: string;
onSubmit: (values: ProviderFormValues) => void; onSubmit: (values: ProviderFormValues) => void;
onCancel: () => void; onCancel: () => void;
@@ -60,7 +60,7 @@ interface ProviderFormProps {
} }
export function ProviderForm({ export function ProviderForm({
appType, appId,
submitLabel, submitLabel,
onSubmit, onSubmit,
onCancel, onCancel,
@@ -86,7 +86,7 @@ export function ProviderForm({
// 使用 category hook // 使用 category hook
const { category } = useProviderCategory({ const { category } = useProviderCategory({
appType, appId,
selectedPresetId, selectedPresetId,
isEditMode, isEditMode,
initialCategory: initialData?.category, initialCategory: initialData?.category,
@@ -95,7 +95,7 @@ export function ProviderForm({
useEffect(() => { useEffect(() => {
setSelectedPresetId(initialData ? null : "custom"); setSelectedPresetId(initialData ? null : "custom");
setActivePreset(null); setActivePreset(null);
}, [appType, initialData]); }, [appId, initialData]);
const defaultValues: ProviderFormData = useMemo( const defaultValues: ProviderFormData = useMemo(
() => ({ () => ({
@@ -103,11 +103,11 @@ export function ProviderForm({
websiteUrl: initialData?.websiteUrl ?? "", websiteUrl: initialData?.websiteUrl ?? "",
settingsConfig: initialData?.settingsConfig settingsConfig: initialData?.settingsConfig
? JSON.stringify(initialData.settingsConfig, null, 2) ? JSON.stringify(initialData.settingsConfig, null, 2)
: appType === "codex" : appId === "codex"
? CODEX_DEFAULT_CONFIG ? CODEX_DEFAULT_CONFIG
: CLAUDE_DEFAULT_CONFIG, : CLAUDE_DEFAULT_CONFIG,
}), }),
[initialData, appType], [initialData, appId],
); );
const form = useForm<ProviderFormData>({ const form = useForm<ProviderFormData>({
@@ -130,7 +130,7 @@ export function ProviderForm({
// 使用 Base URL hook (仅 Claude 模式) // 使用 Base URL hook (仅 Claude 模式)
const { baseUrl, handleClaudeBaseUrlChange } = useBaseUrlState({ const { baseUrl, handleClaudeBaseUrlChange } = useBaseUrlState({
appType, appType: appId,
category, category,
settingsConfig: form.watch("settingsConfig"), settingsConfig: form.watch("settingsConfig"),
codexConfig: "", codexConfig: "",
@@ -202,7 +202,7 @@ export function ProviderForm({
); );
const presetEntries = useMemo(() => { const presetEntries = useMemo(() => {
if (appType === "codex") { if (appId === "codex") {
return codexProviderPresets.map<PresetEntry>((preset, index) => ({ return codexProviderPresets.map<PresetEntry>((preset, index) => ({
id: `codex-${index}`, id: `codex-${index}`,
preset, preset,
@@ -212,7 +212,7 @@ export function ProviderForm({
id: `claude-${index}`, id: `claude-${index}`,
preset, preset,
})); }));
}, [appType]); }, [appId]);
// 使用 Kimi 模型选择器 hook // 使用 Kimi 模型选择器 hook
const { const {
@@ -240,8 +240,8 @@ export function ProviderForm({
handleTemplateValueChange, handleTemplateValueChange,
validateTemplateValues, validateTemplateValues,
} = useTemplateValues({ } = useTemplateValues({
selectedPresetId: appType === "claude" ? selectedPresetId : null, selectedPresetId: appId === "claude" ? selectedPresetId : null,
presetEntries: appType === "claude" ? presetEntries : [], presetEntries: appId === "claude" ? presetEntries : [],
settingsConfig: form.watch("settingsConfig"), settingsConfig: form.watch("settingsConfig"),
onConfigChange: (config) => form.setValue("settingsConfig", config), onConfigChange: (config) => form.setValue("settingsConfig", config),
}); });
@@ -256,7 +256,7 @@ export function ProviderForm({
} = useCommonConfigSnippet({ } = useCommonConfigSnippet({
settingsConfig: form.watch("settingsConfig"), settingsConfig: form.watch("settingsConfig"),
onConfigChange: (config) => form.setValue("settingsConfig", config), onConfigChange: (config) => form.setValue("settingsConfig", config),
initialData: appType === "claude" ? initialData : undefined, initialData: appId === "claude" ? initialData : undefined,
}); });
// 使用 Codex 通用配置片段 hook (仅 Codex 模式) // 使用 Codex 通用配置片段 hook (仅 Codex 模式)
@@ -269,14 +269,14 @@ export function ProviderForm({
} = useCodexCommonConfig({ } = useCodexCommonConfig({
codexConfig, codexConfig,
onConfigChange: handleCodexConfigChange, onConfigChange: handleCodexConfigChange,
initialData: appType === "codex" ? initialData : undefined, initialData: appId === "codex" ? initialData : undefined,
}); });
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
const handleSubmit = (values: ProviderFormData) => { const handleSubmit = (values: ProviderFormData) => {
// 验证模板变量(仅 Claude 模式) // 验证模板变量(仅 Claude 模式)
if (appType === "claude" && templateValueEntries.length > 0) { if (appId === "claude" && templateValueEntries.length > 0) {
const validation = validateTemplateValues(); const validation = validateTemplateValues();
if (!validation.isValid && validation.missingField) { if (!validation.isValid && validation.missingField) {
form.setError("settingsConfig", { form.setError("settingsConfig", {
@@ -293,7 +293,7 @@ export function ProviderForm({
let settingsConfig: string; let settingsConfig: string;
// Codex: 组合 auth 和 config // Codex: 组合 auth 和 config
if (appType === "codex") { if (appId === "codex") {
try { try {
const authJson = JSON.parse(codexAuth); const authJson = JSON.parse(codexAuth);
const configObj = { const configObj = {
@@ -359,7 +359,7 @@ export function ProviderForm({
shouldShowApiKeyLink: shouldShowClaudeApiKeyLink, shouldShowApiKeyLink: shouldShowClaudeApiKeyLink,
websiteUrl: claudeWebsiteUrl, websiteUrl: claudeWebsiteUrl,
} = useApiKeyLink({ } = useApiKeyLink({
appType: "claude", appId: "claude",
category, category,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
@@ -371,7 +371,7 @@ export function ProviderForm({
shouldShowApiKeyLink: shouldShowCodexApiKeyLink, shouldShowApiKeyLink: shouldShowCodexApiKeyLink,
websiteUrl: codexWebsiteUrl, websiteUrl: codexWebsiteUrl,
} = useApiKeyLink({ } = useApiKeyLink({
appType: "codex", appId: "codex",
category, category,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
@@ -380,7 +380,7 @@ export function ProviderForm({
// 使用自定义端点 hook // 使用自定义端点 hook
const customEndpointsMap = useCustomEndpoints({ const customEndpointsMap = useCustomEndpoints({
appType, appId,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
draftCustomEndpoints, draftCustomEndpoints,
@@ -390,7 +390,7 @@ export function ProviderForm({
// 使用端点测速候选 hook // 使用端点测速候选 hook
const speedTestEndpoints = useSpeedTestEndpoints({ const speedTestEndpoints = useSpeedTestEndpoints({
appType, appId,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
baseUrl, baseUrl,
@@ -405,7 +405,7 @@ export function ProviderForm({
form.reset(defaultValues); form.reset(defaultValues);
// Codex 自定义模式:重置为空配置 // Codex 自定义模式:重置为空配置
if (appType === "codex") { if (appId === "codex") {
resetCodexConfig({}, ""); resetCodexConfig({}, "");
} }
return; return;
@@ -421,7 +421,7 @@ export function ProviderForm({
category: entry.preset.category, category: entry.preset.category,
}); });
if (appType === "codex") { if (appId === "codex") {
const preset = entry.preset as CodexProviderPreset; const preset = entry.preset as CodexProviderPreset;
const auth = preset.auth ?? {}; const auth = preset.auth ?? {};
const config = preset.config ?? ""; const config = preset.config ?? "";
@@ -474,7 +474,7 @@ export function ProviderForm({
<BasicFormFields form={form} /> <BasicFormFields form={form} />
{/* Claude 专属字段 */} {/* Claude 专属字段 */}
{appType === "claude" && ( {appId === "claude" && (
<ClaudeFormFields <ClaudeFormFields
shouldShowApiKey={shouldShowApiKey( shouldShowApiKey={shouldShowApiKey(
form.watch("settingsConfig"), form.watch("settingsConfig"),
@@ -510,7 +510,7 @@ export function ProviderForm({
)} )}
{/* Codex 专属字段 */} {/* Codex 专属字段 */}
{appType === "codex" && ( {appId === "codex" && (
<CodexFormFields <CodexFormFields
codexApiKey={codexApiKey} codexApiKey={codexApiKey}
onApiKeyChange={handleCodexApiKeyChange} onApiKeyChange={handleCodexApiKeyChange}
@@ -528,7 +528,7 @@ export function ProviderForm({
)} )}
{/* 配置编辑器Claude 使用通用配置编辑器Codex 使用专用编辑器 */} {/* 配置编辑器Claude 使用通用配置编辑器Codex 使用专用编辑器 */}
{appType === "codex" ? ( {appId === "codex" ? (
<CodexConfigEditor <CodexConfigEditor
authValue={codexAuth} authValue={codexAuth}
configValue={codexConfig} configValue={codexConfig}

View File

@@ -1,5 +1,5 @@
import { useMemo } from "react"; import { useMemo } from "react";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import type { ProviderCategory } from "@/types"; import type { ProviderCategory } from "@/types";
import type { ProviderPreset } from "@/config/providerPresets"; import type { ProviderPreset } from "@/config/providerPresets";
import type { CodexProviderPreset } from "@/config/codexProviderPresets"; import type { CodexProviderPreset } from "@/config/codexProviderPresets";
@@ -10,7 +10,7 @@ type PresetEntry = {
}; };
interface UseApiKeyLinkProps { interface UseApiKeyLinkProps {
appType: AppType; appId: AppId;
category?: ProviderCategory; category?: ProviderCategory;
selectedPresetId: string | null; selectedPresetId: string | null;
presetEntries: PresetEntry[]; presetEntries: PresetEntry[];
@@ -21,7 +21,7 @@ interface UseApiKeyLinkProps {
* 管理 API Key 获取链接的显示和 URL * 管理 API Key 获取链接的显示和 URL
*/ */
export function useApiKeyLink({ export function useApiKeyLink({
appType, appId,
category, category,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
@@ -53,12 +53,7 @@ export function useApiKeyLink({
}, [selectedPresetId, presetEntries, formWebsiteUrl]); }, [selectedPresetId, presetEntries, formWebsiteUrl]);
return { return {
shouldShowApiKeyLink: shouldShowApiKeyLink: appId === "claude" ? shouldShowApiKeyLink : appId === "codex" ? shouldShowApiKeyLink : false,
appType === "claude"
? shouldShowApiKeyLink
: appType === "codex"
? shouldShowApiKeyLink
: false,
websiteUrl: getWebsiteUrl, websiteUrl: getWebsiteUrl,
}; };
} }

View File

@@ -1,5 +1,5 @@
import { useMemo } from "react"; import { useMemo } from "react";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import type { CustomEndpoint } from "@/types"; import type { CustomEndpoint } from "@/types";
import type { ProviderPreset } from "@/config/providerPresets"; import type { ProviderPreset } from "@/config/providerPresets";
import type { CodexProviderPreset } from "@/config/codexProviderPresets"; import type { CodexProviderPreset } from "@/config/codexProviderPresets";
@@ -10,7 +10,7 @@ type PresetEntry = {
}; };
interface UseCustomEndpointsProps { interface UseCustomEndpointsProps {
appType: AppType; appId: AppId;
selectedPresetId: string | null; selectedPresetId: string | null;
presetEntries: PresetEntry[]; presetEntries: PresetEntry[];
draftCustomEndpoints: string[]; draftCustomEndpoints: string[];
@@ -27,7 +27,7 @@ interface UseCustomEndpointsProps {
* 3. 当前选中的 Base URL * 3. 当前选中的 Base URL
*/ */
export function useCustomEndpoints({ export function useCustomEndpoints({
appType, appId,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
draftCustomEndpoints, draftCustomEndpoints,
@@ -58,7 +58,7 @@ export function useCustomEndpoints({
} }
// 3. 当前 Base URL // 3. 当前 Base URL
if (appType === "codex") { if (appId === "codex") {
push(codexBaseUrl); push(codexBaseUrl);
} else { } else {
push(baseUrl); push(baseUrl);
@@ -80,7 +80,7 @@ export function useCustomEndpoints({
return customMap; return customMap;
}, [ }, [
appType, appId,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
draftCustomEndpoints, draftCustomEndpoints,

View File

@@ -1,11 +1,11 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import type { ProviderCategory } from "@/types"; import type { ProviderCategory } from "@/types";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import { providerPresets } from "@/config/providerPresets"; import { providerPresets } from "@/config/providerPresets";
import { codexProviderPresets } from "@/config/codexProviderPresets"; import { codexProviderPresets } from "@/config/codexProviderPresets";
interface UseProviderCategoryProps { interface UseProviderCategoryProps {
appType: AppType; appId: AppId;
selectedPresetId: string | null; selectedPresetId: string | null;
isEditMode: boolean; isEditMode: boolean;
initialCategory?: ProviderCategory; initialCategory?: ProviderCategory;
@@ -16,7 +16,7 @@ interface UseProviderCategoryProps {
* 根据选择的预设自动更新类别 * 根据选择的预设自动更新类别
*/ */
export function useProviderCategory({ export function useProviderCategory({
appType, appId,
selectedPresetId, selectedPresetId,
isEditMode, isEditMode,
initialCategory, initialCategory,
@@ -47,14 +47,14 @@ export function useProviderCategory({
const [, type, indexStr] = match; const [, type, indexStr] = match;
const index = parseInt(indexStr, 10); const index = parseInt(indexStr, 10);
if (type === "codex" && appType === "codex") { if (type === "codex" && appId === "codex") {
const preset = codexProviderPresets[index]; const preset = codexProviderPresets[index];
if (preset) { if (preset) {
setCategory( setCategory(
preset.category || (preset.isOfficial ? "official" : undefined), preset.category || (preset.isOfficial ? "official" : undefined),
); );
} }
} else if (type === "claude" && appType === "claude") { } else if (type === "claude" && appId === "claude") {
const preset = providerPresets[index]; const preset = providerPresets[index];
if (preset) { if (preset) {
setCategory( setCategory(
@@ -62,7 +62,7 @@ export function useProviderCategory({
); );
} }
} }
}, [appType, selectedPresetId, isEditMode, initialCategory]); }, [appId, selectedPresetId, isEditMode, initialCategory]);
return { category, setCategory }; return { category, setCategory };
} }

View File

@@ -1,5 +1,5 @@
import { useMemo } from "react"; import { useMemo } from "react";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import type { ProviderPreset } from "@/config/providerPresets"; import type { ProviderPreset } from "@/config/providerPresets";
import type { CodexProviderPreset } from "@/config/codexProviderPresets"; import type { CodexProviderPreset } from "@/config/codexProviderPresets";
import type { ProviderMeta, EndpointCandidate } from "@/types"; import type { ProviderMeta, EndpointCandidate } from "@/types";
@@ -10,7 +10,7 @@ type PresetEntry = {
}; };
interface UseSpeedTestEndpointsProps { interface UseSpeedTestEndpointsProps {
appType: AppType; appId: AppId;
selectedPresetId: string | null; selectedPresetId: string | null;
presetEntries: PresetEntry[]; presetEntries: PresetEntry[];
baseUrl: string; baseUrl: string;
@@ -31,7 +31,7 @@ interface UseSpeedTestEndpointsProps {
* 4. 预设中的 endpointCandidates * 4. 预设中的 endpointCandidates
*/ */
export function useSpeedTestEndpoints({ export function useSpeedTestEndpoints({
appType, appId,
selectedPresetId, selectedPresetId,
presetEntries, presetEntries,
baseUrl, baseUrl,
@@ -39,7 +39,7 @@ export function useSpeedTestEndpoints({
initialData, initialData,
}: UseSpeedTestEndpointsProps) { }: UseSpeedTestEndpointsProps) {
const claudeEndpoints = useMemo<EndpointCandidate[]>(() => { const claudeEndpoints = useMemo<EndpointCandidate[]>(() => {
if (appType !== "claude") return []; if (appId !== "claude") return [];
const map = new Map<string, EndpointCandidate>(); const map = new Map<string, EndpointCandidate>();
// 所有端点都标记为 isCustom: true给用户完全的管理自由 // 所有端点都标记为 isCustom: true给用户完全的管理自由
@@ -94,10 +94,10 @@ export function useSpeedTestEndpoints({
} }
return Array.from(map.values()); return Array.from(map.values());
}, [appType, baseUrl, initialData, selectedPresetId, presetEntries]); }, [appId, baseUrl, initialData, selectedPresetId, presetEntries]);
const codexEndpoints = useMemo<EndpointCandidate[]>(() => { const codexEndpoints = useMemo<EndpointCandidate[]>(() => {
if (appType !== "codex") return []; if (appId !== "codex") return [];
const map = new Map<string, EndpointCandidate>(); const map = new Map<string, EndpointCandidate>();
// 所有端点都标记为 isCustom: true给用户完全的管理自由 // 所有端点都标记为 isCustom: true给用户完全的管理自由
@@ -155,7 +155,7 @@ export function useSpeedTestEndpoints({
} }
return Array.from(map.values()); 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;
} }

View File

@@ -3,7 +3,7 @@ import { FolderSearch, Undo2 } from "lucide-react";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import type { AppType } from "@/lib/api"; import type { AppId } from "@/lib/api";
import type { ResolvedDirectories } from "@/hooks/useSettings"; import type { ResolvedDirectories } from "@/hooks/useSettings";
interface DirectorySettingsProps { interface DirectorySettingsProps {
@@ -14,9 +14,9 @@ interface DirectorySettingsProps {
onResetAppConfig: () => Promise<void>; onResetAppConfig: () => Promise<void>;
claudeDir?: string; claudeDir?: string;
codexDir?: string; codexDir?: string;
onDirectoryChange: (app: AppType, value?: string) => void; onDirectoryChange: (app: AppId, value?: string) => void;
onBrowseDirectory: (app: AppType) => Promise<void>; onBrowseDirectory: (app: AppId) => Promise<void>;
onResetDirectory: (app: AppType) => Promise<void>; onResetDirectory: (app: AppId) => Promise<void>;
} }
export function DirectorySettings({ export function DirectorySettings({

View File

@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { homeDir, join } from "@tauri-apps/api/path"; 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"; import type { SettingsFormState } from "./useSettingsForm";
type DirectoryKey = "appConfig" | "claude" | "codex"; type DirectoryKey = "appConfig" | "claude" | "codex";
@@ -33,7 +33,7 @@ const computeDefaultAppConfigDir = async (): Promise<string | undefined> => {
}; };
const computeDefaultConfigDir = async ( const computeDefaultConfigDir = async (
app: AppType, app: AppId,
): Promise<string | undefined> => { ): Promise<string | undefined> => {
try { try {
const home = await homeDir(); const home = await homeDir();
@@ -58,11 +58,11 @@ export interface UseDirectorySettingsResult {
resolvedDirs: ResolvedDirectories; resolvedDirs: ResolvedDirectories;
isLoading: boolean; isLoading: boolean;
initialAppConfigDir?: string; initialAppConfigDir?: string;
updateDirectory: (app: AppType, value?: string) => void; updateDirectory: (app: AppId, value?: string) => void;
updateAppConfigDir: (value?: string) => void; updateAppConfigDir: (value?: string) => void;
browseDirectory: (app: AppType) => Promise<void>; browseDirectory: (app: AppId) => Promise<void>;
browseAppConfigDir: () => Promise<void>; browseAppConfigDir: () => Promise<void>;
resetDirectory: (app: AppType) => Promise<void>; resetDirectory: (app: AppId) => Promise<void>;
resetAppConfigDir: () => Promise<void>; resetAppConfigDir: () => Promise<void>;
resetAllDirectories: (claudeDir?: string, codexDir?: string) => void; resetAllDirectories: (claudeDir?: string, codexDir?: string) => void;
} }
@@ -187,14 +187,14 @@ export function useDirectorySettings({
); );
const updateDirectory = useCallback( const updateDirectory = useCallback(
(app: AppType, value?: string) => { (app: AppId, value?: string) => {
updateDirectoryState(app === "claude" ? "claude" : "codex", value); updateDirectoryState(app === "claude" ? "claude" : "codex", value);
}, },
[updateDirectoryState], [updateDirectoryState],
); );
const browseDirectory = useCallback( const browseDirectory = useCallback(
async (app: AppType) => { async (app: AppId) => {
const key: DirectoryKey = app === "claude" ? "claude" : "codex"; const key: DirectoryKey = app === "claude" ? "claude" : "codex";
const currentValue = const currentValue =
key === "claude" key === "claude"
@@ -239,7 +239,7 @@ export function useDirectorySettings({
}, [appConfigDir, resolvedDirs.appConfig, t, updateDirectoryState]); }, [appConfigDir, resolvedDirs.appConfig, t, updateDirectoryState]);
const resetDirectory = useCallback( const resetDirectory = useCallback(
async (app: AppType) => { async (app: AppId) => {
const key: DirectoryKey = app === "claude" ? "claude" : "codex"; const key: DirectoryKey = app === "claude" ? "claude" : "codex";
if (!defaultsRef.current[key]) { if (!defaultsRef.current[key]) {
const fallback = await computeDefaultConfigDir(app); const fallback = await computeDefaultConfigDir(app);

View File

@@ -11,11 +11,11 @@ import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import type { Provider } from "@/types"; import type { Provider } from "@/types";
import { providersApi, type AppType } from "@/lib/api"; import { providersApi, type AppId } from "@/lib/api";
export function useDragSort( export function useDragSort(
providers: Record<string, Provider>, providers: Record<string, Provider>,
appType: AppType, appId: AppId,
) { ) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
@@ -73,9 +73,9 @@ export function useDragSort(
})); }));
try { try {
await providersApi.updateSortOrder(updates, appType); await providersApi.updateSortOrder(updates, appId);
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: ["providers", appType], queryKey: ["providers", appId],
}); });
toast.success( toast.success(
t("provider.sortUpdated", { t("provider.sortUpdated", {
@@ -91,7 +91,7 @@ export function useDragSort(
); );
} }
}, },
[sortedProviders, appType, queryClient, t], [sortedProviders, appId, queryClient, t],
); );
return { return {

View File

@@ -1,7 +1,7 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { mcpApi, type AppType } from "@/lib/api"; import { mcpApi, type AppId } from "@/lib/api";
import type { McpServer } from "@/types"; import type { McpServer } from "@/types";
import { import {
extractErrorMessage, extractErrorMessage,
@@ -30,7 +30,7 @@ export interface UseMcpActionsResult {
* - Delete server * - Delete server
* - Error handling and toast notifications * - Error handling and toast notifications
*/ */
export function useMcpActions(appType: AppType): UseMcpActionsResult { export function useMcpActions(appId: AppId): UseMcpActionsResult {
const { t } = useTranslation(); const { t } = useTranslation();
const [servers, setServers] = useState<Record<string, McpServer>>({}); const [servers, setServers] = useState<Record<string, McpServer>>({});
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -38,7 +38,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
const reload = useCallback(async () => { const reload = useCallback(async () => {
setLoading(true); setLoading(true);
try { try {
const cfg = await mcpApi.getConfig(appType); const cfg = await mcpApi.getConfig(appId);
setServers(cfg.servers || {}); setServers(cfg.servers || {});
} catch (error) { } catch (error) {
console.error("[useMcpActions] Failed to load MCP config", error); console.error("[useMcpActions] Failed to load MCP config", error);
@@ -50,7 +50,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [appType, t]); }, [appId, t]);
const toggleEnabled = useCallback( const toggleEnabled = useCallback(
async (id: string, enabled: boolean) => { async (id: string, enabled: boolean) => {
@@ -65,7 +65,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
})); }));
try { try {
await mcpApi.setEnabled(appType, id, enabled); await mcpApi.setEnabled(appId, id, enabled);
toast.success(enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"), { toast.success(enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"), {
duration: 1500, duration: 1500,
}); });
@@ -79,7 +79,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
}); });
} }
}, },
[appType, servers, t], [appId, servers, t],
); );
const saveServer = useCallback( const saveServer = useCallback(
@@ -90,7 +90,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
) => { ) => {
try { try {
const payload: McpServer = { ...server, id }; const payload: McpServer = { ...server, id };
await mcpApi.upsertServerInConfig(appType, id, payload, { await mcpApi.upsertServerInConfig(appId, id, payload, {
syncOtherSide: options?.syncOtherSide, syncOtherSide: options?.syncOtherSide,
}); });
await reload(); await reload();
@@ -104,13 +104,13 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
throw error; throw error;
} }
}, },
[appType, reload, t], [appId, reload, t],
); );
const deleteServer = useCallback( const deleteServer = useCallback(
async (id: string) => { async (id: string) => {
try { try {
await mcpApi.deleteServerInConfig(appType, id); await mcpApi.deleteServerInConfig(appId, id);
await reload(); await reload();
toast.success(t("mcp.msg.deleted"), { duration: 1500 }); toast.success(t("mcp.msg.deleted"), { duration: 1500 });
} catch (error) { } catch (error) {
@@ -122,7 +122,7 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
throw error; throw error;
} }
}, },
[appType, reload, t], [appId, reload, t],
); );
return { return {

View File

@@ -2,7 +2,7 @@ import { useCallback } from "react";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; 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 type { Provider, UsageScript } from "@/types";
import { import {
useAddProviderMutation, useAddProviderMutation,
@@ -16,7 +16,7 @@ import { extractErrorMessage } from "@/utils/errorUtils";
* Hook for managing provider actions (add, update, delete, switch) * Hook for managing provider actions (add, update, delete, switch)
* Extracts business logic from App.tsx * Extracts business logic from App.tsx
*/ */
export function useProviderActions(activeApp: AppType) { export function useProviderActions(activeApp: AppId) {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@@ -1,7 +1,7 @@
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; 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 { useSettingsQuery, useSaveSettingsMutation } from "@/lib/query";
import type { Settings } from "@/types"; import type { Settings } from "@/types";
import { useSettingsForm, type SettingsFormState } from "./useSettingsForm"; import { useSettingsForm, type SettingsFormState } from "./useSettingsForm";
@@ -26,11 +26,11 @@ export interface UseSettingsResult {
resolvedDirs: ResolvedDirectories; resolvedDirs: ResolvedDirectories;
requiresRestart: boolean; requiresRestart: boolean;
updateSettings: (updates: Partial<SettingsFormState>) => void; updateSettings: (updates: Partial<SettingsFormState>) => void;
updateDirectory: (app: AppType, value?: string) => void; updateDirectory: (app: AppId, value?: string) => void;
updateAppConfigDir: (value?: string) => void; updateAppConfigDir: (value?: string) => void;
browseDirectory: (app: AppType) => Promise<void>; browseDirectory: (app: AppId) => Promise<void>;
browseAppConfigDir: () => Promise<void>; browseAppConfigDir: () => Promise<void>;
resetDirectory: (app: AppType) => Promise<void>; resetDirectory: (app: AppId) => Promise<void>;
resetAppConfigDir: () => Promise<void>; resetAppConfigDir: () => Promise<void>;
saveSettings: () => Promise<SaveResult | null>; saveSettings: () => Promise<SaveResult | null>;
resetSettings: () => void; resetSettings: () => void;

View File

@@ -1,4 +1,4 @@
export type { AppType, AppId } from "./types"; export type { AppId } from "./types";
export { providersApi } from "./providers"; export { providersApi } from "./providers";
export { settingsApi } from "./settings"; export { settingsApi } from "./settings";
export { mcpApi } from "./mcp"; export { mcpApi } from "./mcp";

View File

@@ -5,7 +5,7 @@ import type {
McpServerSpec, McpServerSpec,
McpStatus, McpStatus,
} from "@/types"; } from "@/types";
import type { AppType } from "./types"; import type { AppId } from "./types";
export const mcpApi = { export const mcpApi = {
async getStatus(): Promise<McpStatus> { async getStatus(): Promise<McpStatus> {
@@ -31,7 +31,7 @@ export const mcpApi = {
return await invoke("validate_mcp_command", { cmd }); return await invoke("validate_mcp_command", { cmd });
}, },
async getConfig(app: AppType = "claude"): Promise<McpConfigResponse> { async getConfig(app: AppId = "claude"): Promise<McpConfigResponse> {
return await invoke("get_mcp_config", { app }); return await invoke("get_mcp_config", { app });
}, },
@@ -44,7 +44,7 @@ export const mcpApi = {
}, },
async upsertServerInConfig( async upsertServerInConfig(
app: AppType, app: AppId,
id: string, id: string,
spec: McpServer, spec: McpServer,
options?: { syncOtherSide?: boolean }, options?: { syncOtherSide?: boolean },
@@ -61,7 +61,7 @@ export const mcpApi = {
}, },
async deleteServerInConfig( async deleteServerInConfig(
app: AppType, app: AppId,
id: string, id: string,
options?: { syncOtherSide?: boolean }, options?: { syncOtherSide?: boolean },
): Promise<boolean> { ): Promise<boolean> {
@@ -76,7 +76,7 @@ export const mcpApi = {
}, },
async setEnabled( async setEnabled(
app: AppType, app: AppId,
id: string, id: string,
enabled: boolean, enabled: boolean,
): Promise<boolean> { ): Promise<boolean> {

View File

@@ -1,7 +1,7 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import type { Provider } from "@/types"; import type { Provider } from "@/types";
import type { AppType } from "./types"; import type { AppId } from "./types";
export interface ProviderSortUpdate { export interface ProviderSortUpdate {
id: string; id: string;
@@ -9,37 +9,37 @@ export interface ProviderSortUpdate {
} }
export interface ProviderSwitchEvent { export interface ProviderSwitchEvent {
appType: AppType; appType: AppId;
providerId: string; providerId: string;
} }
export const providersApi = { export const providersApi = {
async getAll(appType: AppType): Promise<Record<string, Provider>> { async getAll(appId: AppId): Promise<Record<string, Provider>> {
return await invoke("get_providers", { app: appType }); return await invoke("get_providers", { app: appId });
}, },
async getCurrent(appType: AppType): Promise<string> { async getCurrent(appId: AppId): Promise<string> {
return await invoke("get_current_provider", { app: appType }); return await invoke("get_current_provider", { app: appId });
}, },
async add(provider: Provider, appType: AppType): Promise<boolean> { async add(provider: Provider, appId: AppId): Promise<boolean> {
return await invoke("add_provider", { provider, app: appType }); return await invoke("add_provider", { provider, app: appId });
}, },
async update(provider: Provider, appType: AppType): Promise<boolean> { async update(provider: Provider, appId: AppId): Promise<boolean> {
return await invoke("update_provider", { provider, app: appType }); return await invoke("update_provider", { provider, app: appId });
}, },
async delete(id: string, appType: AppType): Promise<boolean> { async delete(id: string, appId: AppId): Promise<boolean> {
return await invoke("delete_provider", { id, app: appType }); return await invoke("delete_provider", { id, app: appId });
}, },
async switch(id: string, appType: AppType): Promise<boolean> { async switch(id: string, appId: AppId): Promise<boolean> {
return await invoke("switch_provider", { id, app: appType }); return await invoke("switch_provider", { id, app: appId });
}, },
async importDefault(appType: AppType): Promise<boolean> { async importDefault(appId: AppId): Promise<boolean> {
return await invoke("import_default_config", { app: appType }); return await invoke("import_default_config", { app: appId });
}, },
async updateTrayMenu(): Promise<boolean> { async updateTrayMenu(): Promise<boolean> {
@@ -48,9 +48,9 @@ export const providersApi = {
async updateSortOrder( async updateSortOrder(
updates: ProviderSortUpdate[], updates: ProviderSortUpdate[],
appType: AppType, appId: AppId,
): Promise<boolean> { ): Promise<boolean> {
return await invoke("update_providers_sort_order", { updates, app: appType }); return await invoke("update_providers_sort_order", { updates, app: appId });
}, },
async onSwitched( async onSwitched(

View File

@@ -1,6 +1,6 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import type { Settings } from "@/types"; import type { Settings } from "@/types";
import type { AppType } from "./types"; import type { AppId } from "./types";
export interface ConfigTransferResult { export interface ConfigTransferResult {
success: boolean; success: boolean;
@@ -30,12 +30,12 @@ export const settingsApi = {
return await invoke("is_portable_mode"); return await invoke("is_portable_mode");
}, },
async getConfigDir(appType: AppType): Promise<string> { async getConfigDir(appId: AppId): Promise<string> {
return await invoke("get_config_dir", { app: appType }); return await invoke("get_config_dir", { app: appId });
}, },
async openConfigFolder(appType: AppType): Promise<void> { async openConfigFolder(appId: AppId): Promise<void> {
await invoke("open_config_folder", { app: appType }); await invoke("open_config_folder", { app: appId });
}, },
async selectConfigDirectory(defaultPath?: string): Promise<string | null> { async selectConfigDirectory(defaultPath?: string): Promise<string | null> {

View File

@@ -1,3 +1,2 @@
export type AppType = "claude" | "codex"; // 前端统一使用 AppId 作为应用标识(与后端命令参数 `app` 一致)
// 为避免与后端 Rust `AppType` 枚举语义混淆,可使用更贴近“标识符”的别名 export type AppId = "claude" | "codex";
export type AppId = AppType;

View File

@@ -1,12 +1,12 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import type { UsageResult } from "@/types"; import type { UsageResult } from "@/types";
import type { AppType } from "./types"; import type { AppId } from "./types";
export const usageApi = { export const usageApi = {
async query(providerId: string, appType: AppType): Promise<UsageResult> { async query(providerId: string, appId: AppId): Promise<UsageResult> {
return await invoke("query_provider_usage", { return await invoke("query_provider_usage", {
provider_id: providerId, provider_id: providerId,
app: appType, app: appId,
}); });
}, },
}; };

View File

@@ -1,6 +1,6 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import type { CustomEndpoint } from "@/types"; import type { CustomEndpoint } from "@/types";
import type { AppType } from "./types"; import type { AppId } from "./types";
export interface EndpointLatencyResult { export interface EndpointLatencyResult {
url: string; url: string;
@@ -10,8 +10,8 @@ export interface EndpointLatencyResult {
} }
export const vscodeApi = { export const vscodeApi = {
async getLiveProviderSettings(appType: AppType) { async getLiveProviderSettings(appId: AppId) {
return await invoke("read_live_provider_settings", { app: appType }); return await invoke("read_live_provider_settings", { app: appId });
}, },
async testApiEndpoints( async testApiEndpoints(
@@ -25,46 +25,46 @@ export const vscodeApi = {
}, },
async getCustomEndpoints( async getCustomEndpoints(
appType: AppType, appId: AppId,
providerId: string, providerId: string,
): Promise<CustomEndpoint[]> { ): Promise<CustomEndpoint[]> {
return await invoke("get_custom_endpoints", { return await invoke("get_custom_endpoints", {
app: appType, app: appId,
provider_id: providerId, provider_id: providerId,
}); });
}, },
async addCustomEndpoint( async addCustomEndpoint(
appType: AppType, appId: AppId,
providerId: string, providerId: string,
url: string, url: string,
): Promise<void> { ): Promise<void> {
await invoke("add_custom_endpoint", { await invoke("add_custom_endpoint", {
app: appType, app: appId,
provider_id: providerId, provider_id: providerId,
url, url,
}); });
}, },
async removeCustomEndpoint( async removeCustomEndpoint(
appType: AppType, appId: AppId,
providerId: string, providerId: string,
url: string, url: string,
): Promise<void> { ): Promise<void> {
await invoke("remove_custom_endpoint", { await invoke("remove_custom_endpoint", {
app: appType, app: appId,
provider_id: providerId, provider_id: providerId,
url, url,
}); });
}, },
async updateEndpointLastUsed( async updateEndpointLastUsed(
appType: AppType, appId: AppId,
providerId: string, providerId: string,
url: string, url: string,
): Promise<void> { ): Promise<void> {
await invoke("update_endpoint_last_used", { await invoke("update_endpoint_last_used", {
app: appType, app: appId,
provider_id: providerId, provider_id: providerId,
url, url,
}); });

View File

@@ -1,10 +1,10 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; 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"; import type { Provider, Settings } from "@/types";
export const useAddProviderMutation = (appType: AppType) => { export const useAddProviderMutation = (appId: AppId) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -15,11 +15,11 @@ export const useAddProviderMutation = (appType: AppType) => {
id: crypto.randomUUID(), id: crypto.randomUUID(),
createdAt: Date.now(), createdAt: Date.now(),
}; };
await providersApi.add(newProvider, appType); await providersApi.add(newProvider, appId);
return newProvider; return newProvider;
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
await providersApi.updateTrayMenu(); await providersApi.updateTrayMenu();
toast.success( toast.success(
t("notifications.providerAdded", { 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 queryClient = useQueryClient();
const { t } = useTranslation(); const { t } = useTranslation();
return useMutation({ return useMutation({
mutationFn: async (provider: Provider) => { mutationFn: async (provider: Provider) => {
await providersApi.update(provider, appType); await providersApi.update(provider, appId);
return provider; return provider;
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
toast.success( toast.success(
t("notifications.updateSuccess", { t("notifications.updateSuccess", {
defaultValue: "供应商更新成功", defaultValue: "供应商更新成功",
@@ -66,16 +66,16 @@ export const useUpdateProviderMutation = (appType: AppType) => {
}); });
}; };
export const useDeleteProviderMutation = (appType: AppType) => { export const useDeleteProviderMutation = (appId: AppId) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useTranslation(); const { t } = useTranslation();
return useMutation({ return useMutation({
mutationFn: async (providerId: string) => { mutationFn: async (providerId: string) => {
await providersApi.delete(providerId, appType); await providersApi.delete(providerId, appId);
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
await providersApi.updateTrayMenu(); await providersApi.updateTrayMenu();
toast.success( toast.success(
t("notifications.deleteSuccess", { 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 queryClient = useQueryClient();
const { t } = useTranslation(); const { t } = useTranslation();
return useMutation({ return useMutation({
mutationFn: async (providerId: string) => { mutationFn: async (providerId: string) => {
return await providersApi.switch(providerId, appType); return await providersApi.switch(providerId, appId);
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appType] }); await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
await providersApi.updateTrayMenu(); await providersApi.updateTrayMenu();
toast.success( toast.success(
t("notifications.switchSuccess", { t("notifications.switchSuccess", {
defaultValue: "切换供应商成功", defaultValue: "切换供应商成功",
appName: t(`apps.${appType}`, { defaultValue: appType }), appName: t(`apps.${appId}`, { defaultValue: appId }),
}), }),
); );
}, },

View File

@@ -3,7 +3,7 @@ import {
type UseQueryResult, type UseQueryResult,
keepPreviousData, keepPreviousData,
} from "@tanstack/react-query"; } 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"; import type { Provider, Settings, UsageResult } from "@/types";
const sortProviders = ( const sortProviders = (
@@ -35,33 +35,33 @@ export interface ProvidersQueryData {
} }
export const useProvidersQuery = ( export const useProvidersQuery = (
appType: AppType, appId: AppId,
): UseQueryResult<ProvidersQueryData> => { ): UseQueryResult<ProvidersQueryData> => {
return useQuery({ return useQuery({
queryKey: ["providers", appType], queryKey: ["providers", appId],
placeholderData: keepPreviousData, placeholderData: keepPreviousData,
queryFn: async () => { queryFn: async () => {
let providers: Record<string, Provider> = {}; let providers: Record<string, Provider> = {};
let currentProviderId = ""; let currentProviderId = "";
try { try {
providers = await providersApi.getAll(appType); providers = await providersApi.getAll(appId);
} catch (error) { } catch (error) {
console.error("获取供应商列表失败:", error); console.error("获取供应商列表失败:", error);
} }
try { try {
currentProviderId = await providersApi.getCurrent(appType); currentProviderId = await providersApi.getCurrent(appId);
} catch (error) { } catch (error) {
console.error("获取当前供应商失败:", error); console.error("获取当前供应商失败:", error);
} }
if (Object.keys(providers).length === 0) { if (Object.keys(providers).length === 0) {
try { try {
const success = await providersApi.importDefault(appType); const success = await providersApi.importDefault(appId);
if (success) { if (success) {
providers = await providersApi.getAll(appType); providers = await providersApi.getAll(appId);
currentProviderId = await providersApi.getCurrent(appType); currentProviderId = await providersApi.getCurrent(appId);
} }
} catch (error) { } catch (error) {
console.error("导入默认配置失败:", error); console.error("导入默认配置失败:", error);
@@ -85,12 +85,12 @@ export const useSettingsQuery = (): UseQueryResult<Settings> => {
export const useUsageQuery = ( export const useUsageQuery = (
providerId: string, providerId: string,
appType: AppType, appId: AppId,
enabled: boolean = true, enabled: boolean = true,
): UseQueryResult<UsageResult> => { ): UseQueryResult<UsageResult> => {
return useQuery({ return useQuery({
queryKey: ["usage", providerId, appType], queryKey: ["usage", providerId, appId],
queryFn: async () => usageApi.query(providerId, appType), queryFn: async () => usageApi.query(providerId, appId),
enabled: enabled && !!providerId, enabled: enabled && !!providerId,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
staleTime: 5 * 60 * 1000, // 5分钟 staleTime: 5 * 60 * 1000, // 5分钟

View File

@@ -61,7 +61,7 @@ describe("AddProviderDialog", () => {
<AddProviderDialog <AddProviderDialog
open open
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
appType="claude" appId="claude"
onSubmit={handleSubmit} onSubmit={handleSubmit}
/>, />,
); );
@@ -97,7 +97,7 @@ describe("AddProviderDialog", () => {
<AddProviderDialog <AddProviderDialog
open open
onOpenChange={vi.fn()} onOpenChange={vi.fn()}
appType="claude" appId="claude"
onSubmit={handleSubmit} onSubmit={handleSubmit}
/>, />,
); );

View File

@@ -114,7 +114,7 @@ const renderForm = (props?: Partial<React.ComponentProps<typeof McpFormModal>>)
const onClose = overrideOnClose ?? vi.fn(); const onClose = overrideOnClose ?? vi.fn();
render( render(
<McpFormModal <McpFormModal
appType="claude" appId="claude"
onSave={onSave} onSave={onSave}
onClose={onClose} onClose={onClose}
existingIds={[]} existingIds={[]}
@@ -260,7 +260,7 @@ const renderForm = (props?: Partial<React.ComponentProps<typeof McpFormModal>>)
}); });
it("TOML 模式下自动提取 ID 并成功保存", async () => { it("TOML 模式下自动提取 ID 并成功保存", async () => {
const { onSave } = renderForm({ appType: "codex" }); const { onSave } = renderForm({ appId: "codex" });
const configTextarea = screen.getByPlaceholderText( const configTextarea = screen.getByPlaceholderText(
"mcp.form.tomlPlaceholder", "mcp.form.tomlPlaceholder",
@@ -288,7 +288,7 @@ command = "run"
}); });
it("TOML 模式下缺少命令时展示错误提示并阻止提交", async () => { it("TOML 模式下缺少命令时展示错误提示并阻止提交", async () => {
const { onSave } = renderForm({ appType: "codex" }); const { onSave } = renderForm({ appId: "codex" });
const configTextarea = screen.getByPlaceholderText( const configTextarea = screen.getByPlaceholderText(
"mcp.form.tomlPlaceholder", "mcp.form.tomlPlaceholder",
@@ -319,7 +319,7 @@ type = "stdio"
} as McpServer; } as McpServer;
const { onSave } = renderForm({ const { onSave } = renderForm({
appType: "claude", appId: "claude",
editingId: "existing", editingId: "existing",
initialData, initialData,
}); });

View File

@@ -122,7 +122,7 @@ describe("ProviderList Component", () => {
<ProviderList <ProviderList
providers={{}} providers={{}}
currentProviderId="" currentProviderId=""
appType="claude" appId="claude"
onSwitch={vi.fn()} onSwitch={vi.fn()}
onEdit={vi.fn()} onEdit={vi.fn()}
onDelete={vi.fn()} onDelete={vi.fn()}
@@ -150,7 +150,7 @@ describe("ProviderList Component", () => {
<ProviderList <ProviderList
providers={{}} providers={{}}
currentProviderId="" currentProviderId=""
appType="claude" appId="claude"
onSwitch={vi.fn()} onSwitch={vi.fn()}
onEdit={vi.fn()} onEdit={vi.fn()}
onDelete={vi.fn()} onDelete={vi.fn()}
@@ -189,7 +189,7 @@ describe("ProviderList Component", () => {
<ProviderList <ProviderList
providers={{ a: providerA, b: providerB }} providers={{ a: providerA, b: providerB }}
currentProviderId="b" currentProviderId="b"
appType="claude" appId="claude"
isEditMode isEditMode
onSwitch={handleSwitch} onSwitch={handleSwitch}
onEdit={handleEdit} onEdit={handleEdit}

View File

@@ -41,13 +41,13 @@ vi.mock("@/components/providers/ProviderList", () => ({
})); }));
vi.mock("@/components/providers/AddProviderDialog", () => ({ vi.mock("@/components/providers/AddProviderDialog", () => ({
AddProviderDialog: ({ open, onOpenChange, onSubmit, appType }: any) => AddProviderDialog: ({ open, onOpenChange, onSubmit, appId }: any) =>
open ? ( open ? (
<div data-testid="add-provider-dialog"> <div data-testid="add-provider-dialog">
<button <button
onClick={() => onClick={() =>
onSubmit({ onSubmit({
name: `New ${appType} Provider`, name: `New ${appId} Provider`,
settingsConfig: {}, settingsConfig: {},
category: "custom", category: "custom",
sortIndex: 99, sortIndex: 99,

View File

@@ -139,7 +139,7 @@ const renderPanel = (props?: Partial<React.ComponentProps<typeof McpPanel>>) =>
const client = createTestQueryClient(); const client = createTestQueryClient();
return render( return render(
<QueryClientProvider client={client}> <QueryClientProvider client={client}>
<McpPanel open onOpenChange={() => {}} appType="claude" {...props} /> <McpPanel open onOpenChange={() => {}} appId="claude" {...props} />
</QueryClientProvider>, </QueryClientProvider>,
); );
}; };
@@ -229,4 +229,3 @@ describe("McpPanel integration", () => {
await waitFor(() => expect(deleteServerMock).toHaveBeenCalledWith("sample")); await waitFor(() => expect(deleteServerMock).toHaveBeenCalledWith("sample"));
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { http, HttpResponse } from "msw"; import { http, HttpResponse } from "msw";
import type { AppType } from "@/lib/api/types"; import type { AppId } from "@/lib/api/types";
import type { McpServer, Provider, Settings } from "@/types"; import type { McpServer, Provider, Settings } from "@/types";
import { import {
addProvider, addProvider,
@@ -37,19 +37,19 @@ const success = <T>(payload: T) => HttpResponse.json(payload as any);
export const handlers = [ export const handlers = [
http.post(`${TAURI_ENDPOINT}/get_providers`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/get_providers`, async ({ request }) => {
const { app } = await withJson<{ app: AppType }>(request); const { app } = await withJson<{ app: AppId }>(request);
return success(getProviders(app)); return success(getProviders(app));
}), }),
http.post(`${TAURI_ENDPOINT}/get_current_provider`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/get_current_provider`, async ({ request }) => {
const { app } = await withJson<{ app: AppType }>(request); const { app } = await withJson<{ app: AppId }>(request);
return success(getCurrentProviderId(app)); return success(getCurrentProviderId(app));
}), }),
http.post(`${TAURI_ENDPOINT}/update_providers_sort_order`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/update_providers_sort_order`, async ({ request }) => {
const { updates = [], app } = await withJson<{ const { updates = [], app } = await withJson<{
updates: { id: string; sortIndex: number }[]; updates: { id: string; sortIndex: number }[];
app: AppType; app: AppId;
}>(request); }>(request);
updateSortOrder(app, updates); updateSortOrder(app, updates);
return success(true); return success(true);
@@ -58,7 +58,7 @@ export const handlers = [
http.post(`${TAURI_ENDPOINT}/update_tray_menu`, () => success(true)), http.post(`${TAURI_ENDPOINT}/update_tray_menu`, () => success(true)),
http.post(`${TAURI_ENDPOINT}/switch_provider`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/switch_provider`, async ({ request }) => {
const { id, app } = await withJson<{ id: string; app: AppType }>(request); const { id, app } = await withJson<{ id: string; app: AppId }>(request);
const providers = listProviders(app); const providers = listProviders(app);
if (!providers[id]) { if (!providers[id]) {
return HttpResponse.json(false, { status: 404 }); return HttpResponse.json(false, { status: 404 });
@@ -70,7 +70,7 @@ export const handlers = [
http.post(`${TAURI_ENDPOINT}/add_provider`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/add_provider`, async ({ request }) => {
const { provider, app } = await withJson<{ const { provider, app } = await withJson<{
provider: Provider & { id?: string }; provider: Provider & { id?: string };
app: AppType; app: AppId;
}>(request); }>(request);
const newId = provider.id ?? `mock-${Date.now()}`; const newId = provider.id ?? `mock-${Date.now()}`;
@@ -81,14 +81,14 @@ export const handlers = [
http.post(`${TAURI_ENDPOINT}/update_provider`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/update_provider`, async ({ request }) => {
const { provider, app } = await withJson<{ const { provider, app } = await withJson<{
provider: Provider; provider: Provider;
app: AppType; app: AppId;
}>(request); }>(request);
updateProvider(app, provider); updateProvider(app, provider);
return success(true); return success(true);
}), }),
http.post(`${TAURI_ENDPOINT}/delete_provider`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/delete_provider`, async ({ request }) => {
const { id, app } = await withJson<{ id: string; app: AppType }>(request); const { id, app } = await withJson<{ id: string; app: AppId }>(request);
deleteProvider(app, id); deleteProvider(app, id);
return success(true); return success(true);
}), }),
@@ -102,7 +102,7 @@ export const handlers = [
// MCP APIs // MCP APIs
http.post(`${TAURI_ENDPOINT}/get_mcp_config`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/get_mcp_config`, async ({ request }) => {
const { app } = await withJson<{ app: AppType }>(request); const { app } = await withJson<{ app: AppId }>(request);
return success(getMcpConfig(app)); return success(getMcpConfig(app));
}), }),
@@ -111,7 +111,7 @@ export const handlers = [
http.post(`${TAURI_ENDPOINT}/set_mcp_enabled`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/set_mcp_enabled`, async ({ request }) => {
const { app, id, enabled } = await withJson<{ const { app, id, enabled } = await withJson<{
app: AppType; app: AppId;
id: string; id: string;
enabled: boolean; enabled: boolean;
}>(request); }>(request);
@@ -121,7 +121,7 @@ export const handlers = [
http.post(`${TAURI_ENDPOINT}/upsert_mcp_server_in_config`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/upsert_mcp_server_in_config`, async ({ request }) => {
const { app, id, spec } = await withJson<{ const { app, id, spec } = await withJson<{
app: AppType; app: AppId;
id: string; id: string;
spec: McpServer; spec: McpServer;
}>(request); }>(request);
@@ -130,7 +130,7 @@ export const handlers = [
}), }),
http.post(`${TAURI_ENDPOINT}/delete_mcp_server_in_config`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/delete_mcp_server_in_config`, async ({ request }) => {
const { app, id } = await withJson<{ app: AppType; id: string }>(request); const { app, id } = await withJson<{ app: AppId; id: string }>(request);
deleteMcpServer(app, id); deleteMcpServer(app, id);
return success(true); return success(true);
}), }),
@@ -162,7 +162,7 @@ export const handlers = [
}), }),
http.post(`${TAURI_ENDPOINT}/get_config_dir`, async ({ request }) => { http.post(`${TAURI_ENDPOINT}/get_config_dir`, async ({ request }) => {
const { app } = await withJson<{ app: AppType }>(request); const { app } = await withJson<{ app: AppId }>(request);
return success(app === "claude" ? "/default/claude" : "/default/codex"); return success(app === "claude" ? "/default/claude" : "/default/codex");
}), }),

View File

@@ -1,9 +1,9 @@
import type { AppType } from "@/lib/api/types"; import type { AppId } from "@/lib/api/types";
import type { McpServer, Provider, Settings } from "@/types"; import type { McpServer, Provider, Settings } from "@/types";
type ProvidersByApp = Record<AppType, Record<string, Provider>>; type ProvidersByApp = Record<AppId, Record<string, Provider>>;
type CurrentProviderState = Record<AppType, string>; type CurrentProviderState = Record<AppId, string>;
type McpConfigState = Record<AppType, Record<string, McpServer>>; type McpConfigState = Record<AppId, Record<string, McpServer>>;
const createDefaultProviders = (): ProvidersByApp => ({ const createDefaultProviders = (): ProvidersByApp => ({
claude: { claude: {
@@ -126,29 +126,29 @@ export const resetProviderState = () => {
}; };
}; };
export const getProviders = (appType: AppType) => export const getProviders = (appType: AppId) =>
cloneProviders(providers)[appType] ?? {}; cloneProviders(providers)[appType] ?? {};
export const getCurrentProviderId = (appType: AppType) => current[appType] ?? ""; export const getCurrentProviderId = (appType: AppId) => current[appType] ?? "";
export const setCurrentProviderId = (appType: AppType, providerId: string) => { export const setCurrentProviderId = (appType: AppId, providerId: string) => {
current[appType] = providerId; current[appType] = providerId;
}; };
export const updateProviders = (appType: AppType, data: Record<string, Provider>) => { export const updateProviders = (appType: AppId, data: Record<string, Provider>) => {
providers[appType] = cloneProviders({ [appType]: data } as ProvidersByApp)[appType]; providers[appType] = cloneProviders({ [appType]: data } as ProvidersByApp)[appType];
}; };
export const setProviders = (appType: AppType, data: Record<string, Provider>) => { export const setProviders = (appType: AppId, data: Record<string, Provider>) => {
providers[appType] = JSON.parse(JSON.stringify(data)) as Record<string, Provider>; providers[appType] = JSON.parse(JSON.stringify(data)) as Record<string, Provider>;
}; };
export const addProvider = (appType: AppType, provider: Provider) => { export const addProvider = (appType: AppId, provider: Provider) => {
providers[appType] = providers[appType] ?? {}; providers[appType] = providers[appType] ?? {};
providers[appType][provider.id] = provider; providers[appType][provider.id] = provider;
}; };
export const updateProvider = (appType: AppType, provider: Provider) => { export const updateProvider = (appType: AppId, provider: Provider) => {
if (!providers[appType]) return; if (!providers[appType]) return;
providers[appType][provider.id] = { providers[appType][provider.id] = {
...providers[appType][provider.id], ...providers[appType][provider.id],
@@ -156,7 +156,7 @@ export const updateProvider = (appType: AppType, provider: Provider) => {
}; };
}; };
export const deleteProvider = (appType: AppType, providerId: string) => { export const deleteProvider = (appType: AppId, providerId: string) => {
if (!providers[appType]) return; if (!providers[appType]) return;
delete providers[appType][providerId]; delete providers[appType][providerId];
if (current[appType] === providerId) { if (current[appType] === providerId) {
@@ -166,7 +166,7 @@ export const deleteProvider = (appType: AppType, providerId: string) => {
}; };
export const updateSortOrder = ( export const updateSortOrder = (
appType: AppType, appType: AppId,
updates: { id: string; sortIndex: number }[], updates: { id: string; sortIndex: number }[],
) => { ) => {
if (!providers[appType]) return; if (!providers[appType]) return;
@@ -178,7 +178,7 @@ export const updateSortOrder = (
}); });
}; };
export const listProviders = (appType: AppType) => export const listProviders = (appType: AppId) =>
JSON.parse(JSON.stringify(providers[appType] ?? {})) as Record<string, Provider>; JSON.parse(JSON.stringify(providers[appType] ?? {})) as Record<string, Provider>;
export const getSettings = () => JSON.parse(JSON.stringify(settingsState)) as Settings; export const getSettings = () => JSON.parse(JSON.stringify(settingsState)) as Settings;
@@ -193,7 +193,7 @@ export const setAppConfigDirOverrideState = (value: string | null) => {
appConfigDirOverride = value; appConfigDirOverride = value;
}; };
export const getMcpConfig = (appType: AppType) => { export const getMcpConfig = (appType: AppId) => {
const servers = JSON.parse( const servers = JSON.parse(
JSON.stringify(mcpConfigs[appType] ?? {}), JSON.stringify(mcpConfigs[appType] ?? {}),
) as Record<string, McpServer>; ) as Record<string, McpServer>;
@@ -203,12 +203,12 @@ export const getMcpConfig = (appType: AppType) => {
}; };
}; };
export const setMcpConfig = (appType: AppType, value: Record<string, McpServer>) => { export const setMcpConfig = (appType: AppId, value: Record<string, McpServer>) => {
mcpConfigs[appType] = JSON.parse(JSON.stringify(value)) as Record<string, McpServer>; mcpConfigs[appType] = JSON.parse(JSON.stringify(value)) as Record<string, McpServer>;
}; };
export const setMcpServerEnabled = ( export const setMcpServerEnabled = (
appType: AppType, appType: AppId,
id: string, id: string,
enabled: boolean, enabled: boolean,
) => { ) => {
@@ -220,7 +220,7 @@ export const setMcpServerEnabled = (
}; };
export const upsertMcpServer = ( export const upsertMcpServer = (
appType: AppType, appType: AppId,
id: string, id: string,
server: McpServer, server: McpServer,
) => { ) => {
@@ -230,7 +230,7 @@ export const upsertMcpServer = (
mcpConfigs[appType][id] = JSON.parse(JSON.stringify(server)) as McpServer; mcpConfigs[appType][id] = JSON.parse(JSON.stringify(server)) as McpServer;
}; };
export const deleteMcpServer = (appType: AppType, id: string) => { export const deleteMcpServer = (appType: AppId, id: string) => {
if (!mcpConfigs[appType]) return; if (!mcpConfigs[appType]) return;
delete mcpConfigs[appType][id]; delete mcpConfigs[appType][id];
}; };