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

@@ -0,0 +1,114 @@
import { useState, useCallback, useRef, useEffect } from "react";
import { extractCodexBaseUrl, setCodexBaseUrl as setCodexBaseUrlInConfig } from "@/utils/providerConfigUtils";
import type { ProviderCategory } from "@/types";
interface UseBaseUrlStateProps {
appType: "claude" | "codex";
category: ProviderCategory | undefined;
settingsConfig: string;
codexConfig?: string;
onSettingsConfigChange: (config: string) => void;
onCodexConfigChange?: (config: string) => void;
}
/**
* 管理 Base URL 状态
* 支持 Claude (JSON) 和 Codex (TOML) 两种格式
*/
export function useBaseUrlState({
appType,
category,
settingsConfig,
codexConfig,
onSettingsConfigChange,
onCodexConfigChange,
}: UseBaseUrlStateProps) {
const [baseUrl, setBaseUrl] = useState("");
const [codexBaseUrl, setCodexBaseUrl] = useState("");
const isUpdatingRef = useRef(false);
// 从配置同步到 stateClaude
useEffect(() => {
if (appType !== "claude") return;
if (category !== "third_party" && category !== "custom") return;
if (isUpdatingRef.current) return;
try {
const config = JSON.parse(settingsConfig || "{}");
const envUrl: unknown = config?.env?.ANTHROPIC_BASE_URL;
if (typeof envUrl === "string" && envUrl && envUrl !== baseUrl) {
setBaseUrl(envUrl.trim());
}
} catch {
// ignore
}
}, [appType, category, settingsConfig, baseUrl]);
// 从配置同步到 stateCodex
useEffect(() => {
if (appType !== "codex") return;
if (category !== "third_party" && category !== "custom") return;
if (isUpdatingRef.current) return;
if (!codexConfig) return;
const extracted = extractCodexBaseUrl(codexConfig) || "";
if (extracted !== codexBaseUrl) {
setCodexBaseUrl(extracted);
}
}, [appType, category, codexConfig, codexBaseUrl]);
// 处理 Claude Base URL 变化
const handleClaudeBaseUrlChange = useCallback(
(url: string) => {
const sanitized = url.trim().replace(/\/+$/, "");
setBaseUrl(sanitized);
isUpdatingRef.current = true;
try {
const config = JSON.parse(settingsConfig || "{}");
if (!config.env) {
config.env = {};
}
config.env.ANTHROPIC_BASE_URL = sanitized;
onSettingsConfigChange(JSON.stringify(config, null, 2));
} catch {
// ignore
} finally {
setTimeout(() => {
isUpdatingRef.current = false;
}, 0);
}
},
[settingsConfig, onSettingsConfigChange],
);
// 处理 Codex Base URL 变化
const handleCodexBaseUrlChange = useCallback(
(url: string) => {
const sanitized = url.trim().replace(/\/+$/, "");
setCodexBaseUrl(sanitized);
if (!sanitized || !onCodexConfigChange) {
return;
}
isUpdatingRef.current = true;
const updatedConfig = setCodexBaseUrlInConfig(codexConfig || "", sanitized);
onCodexConfigChange(updatedConfig);
setTimeout(() => {
isUpdatingRef.current = false;
}, 0);
},
[codexConfig, onCodexConfigChange],
);
return {
baseUrl,
setBaseUrl,
codexBaseUrl,
setCodexBaseUrl,
handleClaudeBaseUrlChange,
handleCodexBaseUrlChange,
};
}