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:
@@ -1,13 +1,13 @@
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import { ClaudeIcon, CodexIcon } from "./BrandIcons";
|
||||
|
||||
interface AppSwitcherProps {
|
||||
activeApp: AppType;
|
||||
onSwitch: (app: AppType) => void;
|
||||
activeApp: AppId;
|
||||
onSwitch: (app: AppId) => void;
|
||||
}
|
||||
|
||||
export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
|
||||
const handleSwitch = (app: AppType) => {
|
||||
const handleSwitch = (app: AppId) => {
|
||||
if (app === activeApp) return;
|
||||
onSwitch(app);
|
||||
};
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
import React from "react";
|
||||
import { RefreshCw, AlertCircle } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type AppType } from "@/lib/api";
|
||||
import { type AppId } from "@/lib/api";
|
||||
import { useUsageQuery } from "@/lib/query/queries";
|
||||
import { UsageData } from "../types";
|
||||
|
||||
interface UsageFooterProps {
|
||||
providerId: string;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
usageEnabled: boolean; // 是否启用了用量查询
|
||||
}
|
||||
|
||||
const UsageFooter: React.FC<UsageFooterProps> = ({
|
||||
providerId,
|
||||
appType,
|
||||
usageEnabled,
|
||||
}) => {
|
||||
const UsageFooter: React.FC<UsageFooterProps> = ({ providerId, appId, usageEnabled }) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data: usage,
|
||||
isLoading: loading,
|
||||
refetch,
|
||||
} = useUsageQuery(providerId, appType, usageEnabled);
|
||||
} = useUsageQuery(providerId, appId, usageEnabled);
|
||||
|
||||
// 只在启用用量查询且有数据时显示
|
||||
if (!usageEnabled || !usage) return null;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Play, Wand2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider, UsageScript } from "../types";
|
||||
import { usageApi, type AppType } from "@/lib/api";
|
||||
import { usageApi, type AppId } from "@/lib/api";
|
||||
import JsonEditor from "./JsonEditor";
|
||||
import * as prettier from "prettier/standalone";
|
||||
import * as parserBabel from "prettier/parser-babel";
|
||||
@@ -19,7 +19,7 @@ import { Button } from "@/components/ui/button";
|
||||
|
||||
interface UsageScriptModalProps {
|
||||
provider: Provider;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (script: UsageScript) => void;
|
||||
@@ -82,13 +82,7 @@ const PRESET_TEMPLATES: Record<string, string> = {
|
||||
})`,
|
||||
};
|
||||
|
||||
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
provider,
|
||||
appType,
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
}) => {
|
||||
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({ provider, appId, isOpen, onClose, onSave }) => {
|
||||
const { t } = useTranslation();
|
||||
const [script, setScript] = useState<UsageScript>(() => {
|
||||
return (
|
||||
@@ -127,7 +121,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
const handleTest = async () => {
|
||||
setTesting(true);
|
||||
try {
|
||||
const result = await usageApi.query(provider.id, appType);
|
||||
const result = await usageApi.query(provider.id, appId);
|
||||
if (result.success && result.data && result.data.length > 0) {
|
||||
// 显示所有套餐数据
|
||||
const summary = result.data
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { mcpApi, type AppType } from "@/lib/api";
|
||||
import { mcpApi, type AppId } from "@/lib/api";
|
||||
import { McpServer, McpServerSpec } from "@/types";
|
||||
import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets";
|
||||
import McpWizardModal from "./McpWizardModal";
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
import { useMcpValidation } from "./useMcpValidation";
|
||||
|
||||
interface McpFormModalProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
editingId?: string;
|
||||
initialData?: McpServer;
|
||||
onSave: (
|
||||
@@ -53,7 +53,7 @@ interface McpFormModalProps {
|
||||
* Codex: 使用 TOML 格式
|
||||
*/
|
||||
const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
appType,
|
||||
appId,
|
||||
editingId,
|
||||
initialData,
|
||||
onSave,
|
||||
@@ -91,11 +91,11 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
isEditing ? hasAdditionalInfo : false,
|
||||
);
|
||||
|
||||
// 根据 appType 决定初始格式
|
||||
// 根据 appId 决定初始格式
|
||||
const [formConfig, setFormConfig] = useState(() => {
|
||||
const spec = initialData?.server;
|
||||
if (!spec) return "";
|
||||
if (appType === "codex") {
|
||||
if (appId === "codex") {
|
||||
return mcpServerToToml(spec);
|
||||
}
|
||||
return JSON.stringify(spec, null, 2);
|
||||
@@ -109,11 +109,10 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
const [otherSideHasConflict, setOtherSideHasConflict] = useState(false);
|
||||
|
||||
// 判断是否使用 TOML 格式
|
||||
const useToml = appType === "codex";
|
||||
const syncTargetLabel =
|
||||
appType === "claude" ? t("apps.codex") : t("apps.claude");
|
||||
const otherAppType: AppType = appType === "claude" ? "codex" : "claude";
|
||||
const syncCheckboxId = useMemo(() => `sync-other-side-${appType}`, [appType]);
|
||||
const useToml = appId === "codex";
|
||||
const syncTargetLabel = appId === "claude" ? t("apps.codex") : t("apps.claude");
|
||||
const otherAppType: AppId = appId === "claude" ? "codex" : "claude";
|
||||
const syncCheckboxId = useMemo(() => `sync-other-side-${appId}`, [appId]);
|
||||
|
||||
// 检测另一侧是否有同名 MCP
|
||||
useEffect(() => {
|
||||
@@ -418,7 +417,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
};
|
||||
|
||||
const getFormTitle = () => {
|
||||
if (appType === "claude") {
|
||||
if (appId === "claude") {
|
||||
return isEditing ? t("mcp.editClaudeServer") : t("mcp.addClaudeServer");
|
||||
} else {
|
||||
return isEditing ? t("mcp.editCodexServer") : t("mcp.addCodexServer");
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { type AppType } from "@/lib/api";
|
||||
import { type AppId } from "@/lib/api";
|
||||
import { McpServer } from "@/types";
|
||||
import { useMcpActions } from "@/hooks/useMcpActions";
|
||||
import McpListItem from "./McpListItem";
|
||||
@@ -19,14 +19,14 @@ import { ConfirmDialog } from "../ConfirmDialog";
|
||||
interface McpPanelProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP 管理面板
|
||||
* 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行
|
||||
*/
|
||||
const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
|
||||
const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appId }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isFormOpen, setIsFormOpen] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
@@ -38,17 +38,16 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
|
||||
} | null>(null);
|
||||
|
||||
// Use MCP actions hook
|
||||
const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } =
|
||||
useMcpActions(appType);
|
||||
const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } = useMcpActions(appId);
|
||||
|
||||
useEffect(() => {
|
||||
const setup = async () => {
|
||||
try {
|
||||
// Initialize: only import existing MCPs from corresponding client
|
||||
if (appType === "claude") {
|
||||
if (appId === "claude") {
|
||||
const mcpApi = await import("@/lib/api").then((m) => m.mcpApi);
|
||||
await mcpApi.importFromClaude();
|
||||
} else if (appType === "codex") {
|
||||
} else if (appId === "codex") {
|
||||
const mcpApi = await import("@/lib/api").then((m) => m.mcpApi);
|
||||
await mcpApi.importFromCodex();
|
||||
}
|
||||
@@ -59,8 +58,8 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
|
||||
}
|
||||
};
|
||||
setup();
|
||||
// Re-initialize when appType changes
|
||||
}, [appType, reload]);
|
||||
// Re-initialize when appId changes
|
||||
}, [appId, reload]);
|
||||
|
||||
const handleEdit = (id: string) => {
|
||||
setEditingId(id);
|
||||
@@ -113,8 +112,7 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
|
||||
[serverEntries],
|
||||
);
|
||||
|
||||
const panelTitle =
|
||||
appType === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle");
|
||||
const panelTitle = appId === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -203,7 +201,7 @@ const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
|
||||
{/* Form Modal */}
|
||||
{isFormOpen && (
|
||||
<McpFormModal
|
||||
appType={appType}
|
||||
appId={appId}
|
||||
editingId={editingId || undefined}
|
||||
initialData={editingId ? servers[editingId] : undefined}
|
||||
existingIds={Object.keys(servers)}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Provider, CustomEndpoint } from "@/types";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import {
|
||||
ProviderForm,
|
||||
type ProviderFormValues,
|
||||
@@ -22,14 +22,14 @@ import { codexProviderPresets } from "@/config/codexProviderPresets";
|
||||
interface AddProviderDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
onSubmit: (provider: Omit<Provider, "id">) => Promise<void> | void;
|
||||
}
|
||||
|
||||
export function AddProviderDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
appType,
|
||||
appId,
|
||||
onSubmit,
|
||||
}: AddProviderDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
@@ -68,7 +68,7 @@ export function AddProviderDialog({
|
||||
};
|
||||
|
||||
if (values.presetId) {
|
||||
if (appType === "claude") {
|
||||
if (appId === "claude") {
|
||||
const presets = providerPresets;
|
||||
const presetIndex = parseInt(
|
||||
values.presetId.replace("claude-", ""),
|
||||
@@ -83,7 +83,7 @@ export function AddProviderDialog({
|
||||
preset.endpointCandidates.forEach(addUrl);
|
||||
}
|
||||
}
|
||||
} else if (appType === "codex") {
|
||||
} else if (appId === "codex") {
|
||||
const presets = codexProviderPresets;
|
||||
const presetIndex = parseInt(
|
||||
values.presetId.replace("codex-", ""),
|
||||
@@ -101,12 +101,12 @@ export function AddProviderDialog({
|
||||
}
|
||||
}
|
||||
|
||||
if (appType === "claude") {
|
||||
if (appId === "claude") {
|
||||
const env = parsedConfig.env as Record<string, any> | undefined;
|
||||
if (env?.ANTHROPIC_BASE_URL) {
|
||||
addUrl(env.ANTHROPIC_BASE_URL);
|
||||
}
|
||||
} else if (appType === "codex") {
|
||||
} else if (appId === "codex") {
|
||||
const config = parsedConfig.config as string | undefined;
|
||||
if (config) {
|
||||
const baseUrlMatch =
|
||||
@@ -139,11 +139,11 @@ export function AddProviderDialog({
|
||||
await onSubmit(providerData);
|
||||
onOpenChange(false);
|
||||
},
|
||||
[appType, onSubmit, onOpenChange],
|
||||
[appId, onSubmit, onOpenChange],
|
||||
);
|
||||
|
||||
const submitLabel =
|
||||
appType === "claude"
|
||||
appId === "claude"
|
||||
? t("provider.addClaudeProvider")
|
||||
: t("provider.addCodexProvider");
|
||||
|
||||
@@ -157,7 +157,7 @@ export function AddProviderDialog({
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
<ProviderForm
|
||||
appType={appType}
|
||||
appId={appId}
|
||||
submitLabel={t("common.add")}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => onOpenChange(false)}
|
||||
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
ProviderForm,
|
||||
type ProviderFormValues,
|
||||
} from "@/components/providers/forms/ProviderForm";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
|
||||
interface EditProviderDialogProps {
|
||||
open: boolean;
|
||||
provider: Provider | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSubmit: (provider: Provider) => Promise<void> | void;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
}
|
||||
|
||||
export function EditProviderDialog({
|
||||
@@ -30,7 +30,7 @@ export function EditProviderDialog({
|
||||
provider,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
appType,
|
||||
appId,
|
||||
}: EditProviderDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -75,7 +75,7 @@ export function EditProviderDialog({
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
<ProviderForm
|
||||
appType={appType}
|
||||
appId={appId}
|
||||
submitLabel={t("common.save")}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => onOpenChange(false)}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
DraggableSyntheticListeners,
|
||||
} from "@dnd-kit/core";
|
||||
import type { Provider } from "@/types";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ProviderActions } from "@/components/providers/ProviderActions";
|
||||
@@ -21,7 +21,7 @@ interface DragHandleProps {
|
||||
interface ProviderCardProps {
|
||||
provider: Provider;
|
||||
isCurrent: boolean;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
isEditMode?: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
@@ -61,7 +61,7 @@ const extractApiUrl = (provider: Provider, fallbackText: string) => {
|
||||
export function ProviderCard({
|
||||
provider,
|
||||
isCurrent,
|
||||
appType,
|
||||
appId,
|
||||
isEditMode = false,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
@@ -179,11 +179,7 @@ export function ProviderCard({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UsageFooter
|
||||
providerId={provider.id}
|
||||
appType={appType}
|
||||
usageEnabled={usageEnabled}
|
||||
/>
|
||||
<UsageFooter providerId={provider.id} appId={appId} usageEnabled={usageEnabled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@dnd-kit/sortable";
|
||||
import type { CSSProperties } from "react";
|
||||
import type { Provider } from "@/types";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import { useDragSort } from "@/hooks/useDragSort";
|
||||
import { ProviderCard } from "@/components/providers/ProviderCard";
|
||||
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
|
||||
@@ -15,7 +15,7 @@ import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
|
||||
interface ProviderListProps {
|
||||
providers: Record<string, Provider>;
|
||||
currentProviderId: string;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
isEditMode?: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
@@ -30,7 +30,7 @@ interface ProviderListProps {
|
||||
export function ProviderList({
|
||||
providers,
|
||||
currentProviderId,
|
||||
appType,
|
||||
appId,
|
||||
isEditMode = false,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
@@ -43,7 +43,7 @@ export function ProviderList({
|
||||
}: ProviderListProps) {
|
||||
const { sortedProviders, sensors, handleDragEnd } = useDragSort(
|
||||
providers,
|
||||
appType,
|
||||
appId,
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -79,7 +79,7 @@ export function ProviderList({
|
||||
key={provider.id}
|
||||
provider={provider}
|
||||
isCurrent={provider.id === currentProviderId}
|
||||
appType={appType}
|
||||
appId={appId}
|
||||
isEditMode={isEditMode}
|
||||
onSwitch={onSwitch}
|
||||
onEdit={onEdit}
|
||||
@@ -98,7 +98,7 @@ export function ProviderList({
|
||||
interface SortableProviderCardProps {
|
||||
provider: Provider;
|
||||
isCurrent: boolean;
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
isEditMode: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
@@ -111,7 +111,7 @@ interface SortableProviderCardProps {
|
||||
function SortableProviderCard({
|
||||
provider,
|
||||
isCurrent,
|
||||
appType,
|
||||
appId,
|
||||
isEditMode,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
@@ -139,7 +139,7 @@ function SortableProviderCard({
|
||||
<ProviderCard
|
||||
provider={provider}
|
||||
isCurrent={isCurrent}
|
||||
appType={appType}
|
||||
appId={appId}
|
||||
isEditMode={isEditMode}
|
||||
onSwitch={onSwitch}
|
||||
onEdit={onEdit}
|
||||
|
||||
@@ -149,7 +149,7 @@ export function ClaudeFormFields({
|
||||
{/* 端点测速弹窗 */}
|
||||
{shouldShowSpeedTest && isEndpointModalOpen && (
|
||||
<EndpointSpeedTest
|
||||
appType="claude"
|
||||
appId="claude"
|
||||
value={baseUrl}
|
||||
onChange={onBaseUrlChange}
|
||||
initialEndpoints={speedTestEndpoints}
|
||||
|
||||
@@ -80,7 +80,7 @@ export function CodexFormFields({
|
||||
{/* 端点测速弹窗 - Codex */}
|
||||
{shouldShowSpeedTest && isEndpointModalOpen && (
|
||||
<EndpointSpeedTest
|
||||
appType="codex"
|
||||
appId="codex"
|
||||
value={codexBaseUrl}
|
||||
onChange={onBaseUrlChange}
|
||||
initialEndpoints={speedTestEndpoints}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -28,7 +28,7 @@ interface TestResult {
|
||||
}
|
||||
|
||||
interface EndpointSpeedTestProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
providerId?: string;
|
||||
value: string;
|
||||
onChange: (url: string) => void;
|
||||
@@ -82,7 +82,7 @@ const buildInitialEntries = (
|
||||
};
|
||||
|
||||
const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
appType,
|
||||
appId,
|
||||
providerId,
|
||||
value,
|
||||
onChange,
|
||||
@@ -114,7 +114,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
if (!providerId) return;
|
||||
|
||||
const customEndpoints = await vscodeApi.getCustomEndpoints(
|
||||
appType,
|
||||
appId,
|
||||
providerId,
|
||||
);
|
||||
|
||||
@@ -167,7 +167,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [appType, visible, providerId, t]);
|
||||
}, [appId, visible, providerId, t]);
|
||||
|
||||
useEffect(() => {
|
||||
setEntries((prev) => {
|
||||
@@ -284,7 +284,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
// 保存到后端
|
||||
try {
|
||||
if (providerId) {
|
||||
await vscodeApi.addCustomEndpoint(appType, providerId, sanitized);
|
||||
await vscodeApi.addCustomEndpoint(appId, providerId, sanitized);
|
||||
}
|
||||
|
||||
// 更新本地状态
|
||||
@@ -318,7 +318,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
entries,
|
||||
normalizedSelected,
|
||||
onChange,
|
||||
appType,
|
||||
appId,
|
||||
providerId,
|
||||
t,
|
||||
]);
|
||||
@@ -331,7 +331,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
// 如果有 providerId,尝试从后端删除
|
||||
if (entry.isCustom && providerId) {
|
||||
try {
|
||||
await vscodeApi.removeCustomEndpoint(appType, providerId, entry.url);
|
||||
await vscodeApi.removeCustomEndpoint(appId, providerId, entry.url);
|
||||
} catch (error) {
|
||||
const errorMsg =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
@@ -362,7 +362,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[normalizedSelected, onChange, appType, providerId, t],
|
||||
[normalizedSelected, onChange, appId, providerId, t],
|
||||
);
|
||||
|
||||
const runSpeedTest = useCallback(async () => {
|
||||
@@ -387,7 +387,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
|
||||
try {
|
||||
const results = await vscodeApi.testApiEndpoints(urls, {
|
||||
timeoutSecs: ENDPOINT_TIMEOUT_SECS[appType],
|
||||
timeoutSecs: ENDPOINT_TIMEOUT_SECS[appId],
|
||||
});
|
||||
|
||||
const resultMap = new Map(
|
||||
@@ -437,7 +437,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
}, [entries, autoSelect, appType, normalizedSelected, onChange, t]);
|
||||
}, [entries, autoSelect, appId, normalizedSelected, onChange, t]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (url: string) => {
|
||||
@@ -447,7 +447,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
const entry = entries.find((e) => e.url === url);
|
||||
if (entry?.isCustom && providerId) {
|
||||
try {
|
||||
await vscodeApi.updateEndpointLastUsed(appType, providerId, url);
|
||||
await vscodeApi.updateEndpointLastUsed(appId, providerId, url);
|
||||
} catch (error) {
|
||||
console.error(t("endpointTest.updateLastUsedFailed"), error);
|
||||
}
|
||||
@@ -455,7 +455,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
|
||||
onChange(url);
|
||||
},
|
||||
[normalizedSelected, onChange, appType, entries, providerId, t],
|
||||
[normalizedSelected, onChange, appId, entries, providerId, t],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import type { ProviderCategory, ProviderMeta } from "@/types";
|
||||
import { providerPresets, type ProviderPreset } from "@/config/providerPresets";
|
||||
import {
|
||||
@@ -45,7 +45,7 @@ type PresetEntry = {
|
||||
};
|
||||
|
||||
interface ProviderFormProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
submitLabel: string;
|
||||
onSubmit: (values: ProviderFormValues) => void;
|
||||
onCancel: () => void;
|
||||
@@ -60,7 +60,7 @@ interface ProviderFormProps {
|
||||
}
|
||||
|
||||
export function ProviderForm({
|
||||
appType,
|
||||
appId,
|
||||
submitLabel,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
@@ -86,7 +86,7 @@ export function ProviderForm({
|
||||
|
||||
// 使用 category hook
|
||||
const { category } = useProviderCategory({
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
isEditMode,
|
||||
initialCategory: initialData?.category,
|
||||
@@ -95,7 +95,7 @@ export function ProviderForm({
|
||||
useEffect(() => {
|
||||
setSelectedPresetId(initialData ? null : "custom");
|
||||
setActivePreset(null);
|
||||
}, [appType, initialData]);
|
||||
}, [appId, initialData]);
|
||||
|
||||
const defaultValues: ProviderFormData = useMemo(
|
||||
() => ({
|
||||
@@ -103,11 +103,11 @@ export function ProviderForm({
|
||||
websiteUrl: initialData?.websiteUrl ?? "",
|
||||
settingsConfig: initialData?.settingsConfig
|
||||
? JSON.stringify(initialData.settingsConfig, null, 2)
|
||||
: appType === "codex"
|
||||
: appId === "codex"
|
||||
? CODEX_DEFAULT_CONFIG
|
||||
: CLAUDE_DEFAULT_CONFIG,
|
||||
}),
|
||||
[initialData, appType],
|
||||
[initialData, appId],
|
||||
);
|
||||
|
||||
const form = useForm<ProviderFormData>({
|
||||
@@ -130,7 +130,7 @@ export function ProviderForm({
|
||||
|
||||
// 使用 Base URL hook (仅 Claude 模式)
|
||||
const { baseUrl, handleClaudeBaseUrlChange } = useBaseUrlState({
|
||||
appType,
|
||||
appType: appId,
|
||||
category,
|
||||
settingsConfig: form.watch("settingsConfig"),
|
||||
codexConfig: "",
|
||||
@@ -202,7 +202,7 @@ export function ProviderForm({
|
||||
);
|
||||
|
||||
const presetEntries = useMemo(() => {
|
||||
if (appType === "codex") {
|
||||
if (appId === "codex") {
|
||||
return codexProviderPresets.map<PresetEntry>((preset, index) => ({
|
||||
id: `codex-${index}`,
|
||||
preset,
|
||||
@@ -212,7 +212,7 @@ export function ProviderForm({
|
||||
id: `claude-${index}`,
|
||||
preset,
|
||||
}));
|
||||
}, [appType]);
|
||||
}, [appId]);
|
||||
|
||||
// 使用 Kimi 模型选择器 hook
|
||||
const {
|
||||
@@ -240,8 +240,8 @@ export function ProviderForm({
|
||||
handleTemplateValueChange,
|
||||
validateTemplateValues,
|
||||
} = useTemplateValues({
|
||||
selectedPresetId: appType === "claude" ? selectedPresetId : null,
|
||||
presetEntries: appType === "claude" ? presetEntries : [],
|
||||
selectedPresetId: appId === "claude" ? selectedPresetId : null,
|
||||
presetEntries: appId === "claude" ? presetEntries : [],
|
||||
settingsConfig: form.watch("settingsConfig"),
|
||||
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
||||
});
|
||||
@@ -256,7 +256,7 @@ export function ProviderForm({
|
||||
} = useCommonConfigSnippet({
|
||||
settingsConfig: form.watch("settingsConfig"),
|
||||
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
||||
initialData: appType === "claude" ? initialData : undefined,
|
||||
initialData: appId === "claude" ? initialData : undefined,
|
||||
});
|
||||
|
||||
// 使用 Codex 通用配置片段 hook (仅 Codex 模式)
|
||||
@@ -269,14 +269,14 @@ export function ProviderForm({
|
||||
} = useCodexCommonConfig({
|
||||
codexConfig,
|
||||
onConfigChange: handleCodexConfigChange,
|
||||
initialData: appType === "codex" ? initialData : undefined,
|
||||
initialData: appId === "codex" ? initialData : undefined,
|
||||
});
|
||||
|
||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||
|
||||
const handleSubmit = (values: ProviderFormData) => {
|
||||
// 验证模板变量(仅 Claude 模式)
|
||||
if (appType === "claude" && templateValueEntries.length > 0) {
|
||||
if (appId === "claude" && templateValueEntries.length > 0) {
|
||||
const validation = validateTemplateValues();
|
||||
if (!validation.isValid && validation.missingField) {
|
||||
form.setError("settingsConfig", {
|
||||
@@ -293,7 +293,7 @@ export function ProviderForm({
|
||||
let settingsConfig: string;
|
||||
|
||||
// Codex: 组合 auth 和 config
|
||||
if (appType === "codex") {
|
||||
if (appId === "codex") {
|
||||
try {
|
||||
const authJson = JSON.parse(codexAuth);
|
||||
const configObj = {
|
||||
@@ -359,7 +359,7 @@ export function ProviderForm({
|
||||
shouldShowApiKeyLink: shouldShowClaudeApiKeyLink,
|
||||
websiteUrl: claudeWebsiteUrl,
|
||||
} = useApiKeyLink({
|
||||
appType: "claude",
|
||||
appId: "claude",
|
||||
category,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
@@ -371,7 +371,7 @@ export function ProviderForm({
|
||||
shouldShowApiKeyLink: shouldShowCodexApiKeyLink,
|
||||
websiteUrl: codexWebsiteUrl,
|
||||
} = useApiKeyLink({
|
||||
appType: "codex",
|
||||
appId: "codex",
|
||||
category,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
@@ -380,7 +380,7 @@ export function ProviderForm({
|
||||
|
||||
// 使用自定义端点 hook
|
||||
const customEndpointsMap = useCustomEndpoints({
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
draftCustomEndpoints,
|
||||
@@ -390,7 +390,7 @@ export function ProviderForm({
|
||||
|
||||
// 使用端点测速候选 hook
|
||||
const speedTestEndpoints = useSpeedTestEndpoints({
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
baseUrl,
|
||||
@@ -405,7 +405,7 @@ export function ProviderForm({
|
||||
form.reset(defaultValues);
|
||||
|
||||
// Codex 自定义模式:重置为空配置
|
||||
if (appType === "codex") {
|
||||
if (appId === "codex") {
|
||||
resetCodexConfig({}, "");
|
||||
}
|
||||
return;
|
||||
@@ -421,7 +421,7 @@ export function ProviderForm({
|
||||
category: entry.preset.category,
|
||||
});
|
||||
|
||||
if (appType === "codex") {
|
||||
if (appId === "codex") {
|
||||
const preset = entry.preset as CodexProviderPreset;
|
||||
const auth = preset.auth ?? {};
|
||||
const config = preset.config ?? "";
|
||||
@@ -474,7 +474,7 @@ export function ProviderForm({
|
||||
<BasicFormFields form={form} />
|
||||
|
||||
{/* Claude 专属字段 */}
|
||||
{appType === "claude" && (
|
||||
{appId === "claude" && (
|
||||
<ClaudeFormFields
|
||||
shouldShowApiKey={shouldShowApiKey(
|
||||
form.watch("settingsConfig"),
|
||||
@@ -510,7 +510,7 @@ export function ProviderForm({
|
||||
)}
|
||||
|
||||
{/* Codex 专属字段 */}
|
||||
{appType === "codex" && (
|
||||
{appId === "codex" && (
|
||||
<CodexFormFields
|
||||
codexApiKey={codexApiKey}
|
||||
onApiKeyChange={handleCodexApiKeyChange}
|
||||
@@ -528,7 +528,7 @@ export function ProviderForm({
|
||||
)}
|
||||
|
||||
{/* 配置编辑器:Claude 使用通用配置编辑器,Codex 使用专用编辑器 */}
|
||||
{appType === "codex" ? (
|
||||
{appId === "codex" ? (
|
||||
<CodexConfigEditor
|
||||
authValue={codexAuth}
|
||||
configValue={codexConfig}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import type { ProviderCategory } from "@/types";
|
||||
import type { ProviderPreset } from "@/config/providerPresets";
|
||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||
@@ -10,7 +10,7 @@ type PresetEntry = {
|
||||
};
|
||||
|
||||
interface UseApiKeyLinkProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
category?: ProviderCategory;
|
||||
selectedPresetId: string | null;
|
||||
presetEntries: PresetEntry[];
|
||||
@@ -21,7 +21,7 @@ interface UseApiKeyLinkProps {
|
||||
* 管理 API Key 获取链接的显示和 URL
|
||||
*/
|
||||
export function useApiKeyLink({
|
||||
appType,
|
||||
appId,
|
||||
category,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
@@ -53,12 +53,7 @@ export function useApiKeyLink({
|
||||
}, [selectedPresetId, presetEntries, formWebsiteUrl]);
|
||||
|
||||
return {
|
||||
shouldShowApiKeyLink:
|
||||
appType === "claude"
|
||||
? shouldShowApiKeyLink
|
||||
: appType === "codex"
|
||||
? shouldShowApiKeyLink
|
||||
: false,
|
||||
shouldShowApiKeyLink: appId === "claude" ? shouldShowApiKeyLink : appId === "codex" ? shouldShowApiKeyLink : false,
|
||||
websiteUrl: getWebsiteUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import type { CustomEndpoint } from "@/types";
|
||||
import type { ProviderPreset } from "@/config/providerPresets";
|
||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||
@@ -10,7 +10,7 @@ type PresetEntry = {
|
||||
};
|
||||
|
||||
interface UseCustomEndpointsProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
selectedPresetId: string | null;
|
||||
presetEntries: PresetEntry[];
|
||||
draftCustomEndpoints: string[];
|
||||
@@ -27,7 +27,7 @@ interface UseCustomEndpointsProps {
|
||||
* 3. 当前选中的 Base URL
|
||||
*/
|
||||
export function useCustomEndpoints({
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
draftCustomEndpoints,
|
||||
@@ -58,7 +58,7 @@ export function useCustomEndpoints({
|
||||
}
|
||||
|
||||
// 3. 当前 Base URL
|
||||
if (appType === "codex") {
|
||||
if (appId === "codex") {
|
||||
push(codexBaseUrl);
|
||||
} else {
|
||||
push(baseUrl);
|
||||
@@ -80,7 +80,7 @@ export function useCustomEndpoints({
|
||||
|
||||
return customMap;
|
||||
}, [
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
draftCustomEndpoints,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import type { ProviderCategory } from "@/types";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import { providerPresets } from "@/config/providerPresets";
|
||||
import { codexProviderPresets } from "@/config/codexProviderPresets";
|
||||
|
||||
interface UseProviderCategoryProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
selectedPresetId: string | null;
|
||||
isEditMode: boolean;
|
||||
initialCategory?: ProviderCategory;
|
||||
@@ -16,7 +16,7 @@ interface UseProviderCategoryProps {
|
||||
* 根据选择的预设自动更新类别
|
||||
*/
|
||||
export function useProviderCategory({
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
isEditMode,
|
||||
initialCategory,
|
||||
@@ -47,14 +47,14 @@ export function useProviderCategory({
|
||||
const [, type, indexStr] = match;
|
||||
const index = parseInt(indexStr, 10);
|
||||
|
||||
if (type === "codex" && appType === "codex") {
|
||||
if (type === "codex" && appId === "codex") {
|
||||
const preset = codexProviderPresets[index];
|
||||
if (preset) {
|
||||
setCategory(
|
||||
preset.category || (preset.isOfficial ? "official" : undefined),
|
||||
);
|
||||
}
|
||||
} else if (type === "claude" && appType === "claude") {
|
||||
} else if (type === "claude" && appId === "claude") {
|
||||
const preset = providerPresets[index];
|
||||
if (preset) {
|
||||
setCategory(
|
||||
@@ -62,7 +62,7 @@ export function useProviderCategory({
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [appType, selectedPresetId, isEditMode, initialCategory]);
|
||||
}, [appId, selectedPresetId, isEditMode, initialCategory]);
|
||||
|
||||
return { category, setCategory };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import type { ProviderPreset } from "@/config/providerPresets";
|
||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||
import type { ProviderMeta, EndpointCandidate } from "@/types";
|
||||
@@ -10,7 +10,7 @@ type PresetEntry = {
|
||||
};
|
||||
|
||||
interface UseSpeedTestEndpointsProps {
|
||||
appType: AppType;
|
||||
appId: AppId;
|
||||
selectedPresetId: string | null;
|
||||
presetEntries: PresetEntry[];
|
||||
baseUrl: string;
|
||||
@@ -31,7 +31,7 @@ interface UseSpeedTestEndpointsProps {
|
||||
* 4. 预设中的 endpointCandidates
|
||||
*/
|
||||
export function useSpeedTestEndpoints({
|
||||
appType,
|
||||
appId,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
baseUrl,
|
||||
@@ -39,7 +39,7 @@ export function useSpeedTestEndpoints({
|
||||
initialData,
|
||||
}: UseSpeedTestEndpointsProps) {
|
||||
const claudeEndpoints = useMemo<EndpointCandidate[]>(() => {
|
||||
if (appType !== "claude") return [];
|
||||
if (appId !== "claude") return [];
|
||||
|
||||
const map = new Map<string, EndpointCandidate>();
|
||||
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
||||
@@ -94,10 +94,10 @@ export function useSpeedTestEndpoints({
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
}, [appType, baseUrl, initialData, selectedPresetId, presetEntries]);
|
||||
}, [appId, baseUrl, initialData, selectedPresetId, presetEntries]);
|
||||
|
||||
const codexEndpoints = useMemo<EndpointCandidate[]>(() => {
|
||||
if (appType !== "codex") return [];
|
||||
if (appId !== "codex") return [];
|
||||
|
||||
const map = new Map<string, EndpointCandidate>();
|
||||
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
||||
@@ -155,7 +155,7 @@ export function useSpeedTestEndpoints({
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
}, [appType, codexBaseUrl, initialData, selectedPresetId, presetEntries]);
|
||||
}, [appId, codexBaseUrl, initialData, selectedPresetId, presetEntries]);
|
||||
|
||||
return appType === "codex" ? codexEndpoints : claudeEndpoints;
|
||||
return appId === "codex" ? codexEndpoints : claudeEndpoints;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FolderSearch, Undo2 } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { AppType } from "@/lib/api";
|
||||
import type { AppId } from "@/lib/api";
|
||||
import type { ResolvedDirectories } from "@/hooks/useSettings";
|
||||
|
||||
interface DirectorySettingsProps {
|
||||
@@ -14,9 +14,9 @@ interface DirectorySettingsProps {
|
||||
onResetAppConfig: () => Promise<void>;
|
||||
claudeDir?: string;
|
||||
codexDir?: string;
|
||||
onDirectoryChange: (app: AppType, value?: string) => void;
|
||||
onBrowseDirectory: (app: AppType) => Promise<void>;
|
||||
onResetDirectory: (app: AppType) => Promise<void>;
|
||||
onDirectoryChange: (app: AppId, value?: string) => void;
|
||||
onBrowseDirectory: (app: AppId) => Promise<void>;
|
||||
onResetDirectory: (app: AppId) => Promise<void>;
|
||||
}
|
||||
|
||||
export function DirectorySettings({
|
||||
|
||||
Reference in New Issue
Block a user