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.
This commit is contained in:
Jason
2025-10-16 17:40:25 +08:00
parent 2c1346a23d
commit 98c35c7c62
13 changed files with 2322 additions and 2 deletions

View File

@@ -7,12 +7,14 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import type { Provider } from "@/types";
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;
@@ -36,6 +38,7 @@ export function AddProviderDialog({
unknown
>;
// 构造基础提交数据
const providerData: Omit<Provider, "id"> = {
name: values.name.trim(),
websiteUrl: values.websiteUrl?.trim() || undefined,
@@ -43,10 +46,80 @@ export function AddProviderDialog({
...(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);
},
[onSubmit, onOpenChange],
[appType, onSubmit, onOpenChange],
);
const submitLabel =