Files
cc-switch/src/components/providers/AddProviderDialog.tsx
T
Jason 98c35c7c62 refactor: create modular hooks and integrate API key input
- Create custom hooks for state management:
  - useProviderCategory: manages provider category state
  - useApiKeyState: manages API key input with auto-sync to config
  - useBaseUrlState: manages base URL for Claude and Codex
  - useModelState: manages model selection state

- Integrate API key input into simplified ProviderForm:
  - Add ApiKeyInput component for Claude mode
  - Auto-populate API key into settings config
  - Disable for official providers

- Fix EndpointSpeedTest type errors:
  - Fix import paths to use @ alias
  - Add temporary type definitions
  - Format all TODO comments properly
  - Remove incorrect type assertions
  - Comment out unimplemented window.api checks

All TypeScript type checks now pass.
2025-10-16 17:40:25 +08:00

154 lines
4.9 KiB
TypeScript

import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import type { Provider, CustomEndpoint } from "@/types";
import type { AppType } from "@/lib/api";
import {
ProviderForm,
type ProviderFormValues,
} from "@/components/providers/forms/ProviderForm";
import { providerPresets } from "@/config/providerPresets";
import { codexProviderPresets } from "@/config/codexProviderPresets";
interface AddProviderDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
appType: AppType;
onSubmit: (provider: Omit<Provider, "id">) => Promise<void> | void;
}
export function AddProviderDialog({
open,
onOpenChange,
appType,
onSubmit,
}: AddProviderDialogProps) {
const { t } = useTranslation();
const handleSubmit = useCallback(
async (values: ProviderFormValues) => {
const parsedConfig = JSON.parse(values.settingsConfig) as Record<
string,
unknown
>;
// 构造基础提交数据
const providerData: Omit<Provider, "id"> = {
name: values.name.trim(),
websiteUrl: values.websiteUrl?.trim() || undefined,
settingsConfig: parsedConfig,
...(values.presetCategory ? { category: values.presetCategory } : {}),
};
// 收集端点候选(仅新增供应商时)
// 1. 从预设配置中获取 endpointCandidates
// 2. 从当前配置中提取 baseUrl (ANTHROPIC_BASE_URL 或 Codex base_url)
const urlSet = new Set<string>();
const addUrl = (rawUrl?: string) => {
const url = (rawUrl || "").trim().replace(/\/+$/, "");
if (url && url.startsWith("http")) {
urlSet.add(url);
}
};
// 如果选择了预设,获取预设中的 endpointCandidates
if (values.presetId) {
if (appType === "claude") {
const presets = providerPresets;
const presetIndex = parseInt(values.presetId.replace("claude-", ""));
if (!isNaN(presetIndex) && presetIndex >= 0 && presetIndex < presets.length) {
const preset = presets[presetIndex];
if (preset?.endpointCandidates) {
preset.endpointCandidates.forEach(addUrl);
}
}
} else if (appType === "codex") {
const presets = codexProviderPresets;
const presetIndex = parseInt(values.presetId.replace("codex-", ""));
if (!isNaN(presetIndex) && presetIndex >= 0 && presetIndex < presets.length) {
const preset = presets[presetIndex];
if ((preset as any).endpointCandidates) {
(preset as any).endpointCandidates.forEach(addUrl);
}
}
}
}
// 从当前配置中提取 baseUrl
if (appType === "claude") {
const env = parsedConfig.env as Record<string, any> | undefined;
if (env?.ANTHROPIC_BASE_URL) {
addUrl(env.ANTHROPIC_BASE_URL);
}
} else if (appType === "codex") {
// Codex 的 baseUrl 在 config.toml 字符串中
const config = parsedConfig.config as string | undefined;
if (config) {
const baseUrlMatch = config.match(/base_url\s*=\s*["']([^"']+)["']/);
if (baseUrlMatch?.[1]) {
addUrl(baseUrlMatch[1]);
}
}
}
// 如果收集到了端点,添加到 meta.custom_endpoints
const urls = Array.from(urlSet);
if (urls.length > 0) {
const now = Date.now();
const customEndpoints: Record<string, CustomEndpoint> = {};
urls.forEach((url) => {
customEndpoints[url] = {
url,
addedAt: now,
lastUsed: undefined,
};
});
providerData.meta = {
custom_endpoints: customEndpoints,
};
}
await onSubmit(providerData);
onOpenChange(false);
},
[appType, onSubmit, onOpenChange],
);
const submitLabel =
appType === "claude"
? t("provider.addClaudeProvider", { defaultValue: "添加 Claude 供应商" })
: t("provider.addCodexProvider", { defaultValue: "添加 Codex 供应商" });
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle>{submitLabel}</DialogTitle>
<DialogDescription>
{t("provider.addDescription", {
defaultValue: "填写信息后即可在列表中快速切换供应商。",
})}
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto -mx-6 px-6">
<ProviderForm
appType={appType}
submitLabel={t("common.add", { defaultValue: "添加" })}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
/>
</div>
</DialogContent>
</Dialog>
);
}