mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 00:35:32 +08:00
b972f0a3bd
Bump the default model IDs across every preset file and the downstream defaults that mirror them: - claude-opus-4-8 / anthropic/claude-opus-4.8 / global.anthropic.claude-opus-4-8 -> claude-opus-5, covering all three naming forms - gpt-5.5 and the bare gpt-5.6 -> gpt-5.6-sol - gemini-3.5-flash -> gemini-3.6-flash Add gemini-3.6-flash to the built-in pricing seed (1.50 in / 7.50 out / 0.15 cache read per million). The seed runs INSERT OR IGNORE on every startup, so existing databases pick the row up without overriding prices the user edited by hand. Advance the Claude Desktop opus route ID in step with the frontend SSOT: CURRENT_OPUS_ROUTE_ID becomes claude-opus-5 and claude-opus-4-8 takes over the LEGACY slot, so route IDs stored in existing user configs still resolve through is_compatible_opus_route_alias. Also sync omo.ts recommendations, form placeholders, the SudoCode partner blurb and all four locales, plus the preset assertions that pin these IDs.
213 lines
6.4 KiB
TypeScript
213 lines
6.4 KiB
TypeScript
import { useCallback, useState } from "react";
|
||
import { useTranslation } from "react-i18next";
|
||
import { FormLabel } from "@/components/ui/form";
|
||
import { Download, Info, Loader2 } from "lucide-react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { toast } from "sonner";
|
||
import EndpointSpeedTest from "./EndpointSpeedTest";
|
||
import { ApiKeySection, EndpointField, ModelInputWithFetch } from "./shared";
|
||
import {
|
||
fetchModelsForConfig,
|
||
showFetchModelsError,
|
||
type FetchedModel,
|
||
} from "@/lib/api/model-fetch";
|
||
import type { ProviderCategory } from "@/types";
|
||
|
||
interface EndpointCandidate {
|
||
url: string;
|
||
}
|
||
|
||
interface GeminiFormFieldsProps {
|
||
providerId?: string;
|
||
// API Key
|
||
shouldShowApiKey: boolean;
|
||
apiKey: string;
|
||
onApiKeyChange: (key: string) => void;
|
||
category?: ProviderCategory;
|
||
shouldShowApiKeyLink: boolean;
|
||
websiteUrl: string;
|
||
isPartner?: boolean;
|
||
partnerPromotionKey?: string;
|
||
|
||
// Base URL
|
||
shouldShowSpeedTest: boolean;
|
||
baseUrl: string;
|
||
onBaseUrlChange: (url: string) => void;
|
||
isEndpointModalOpen: boolean;
|
||
onEndpointModalToggle: (open: boolean) => void;
|
||
onCustomEndpointsChange: (endpoints: string[]) => void;
|
||
autoSelect: boolean;
|
||
onAutoSelectChange: (checked: boolean) => void;
|
||
|
||
// Model
|
||
shouldShowModelField: boolean;
|
||
model: string;
|
||
onModelChange: (value: string) => void;
|
||
|
||
// Speed Test Endpoints
|
||
speedTestEndpoints: EndpointCandidate[];
|
||
}
|
||
|
||
export function GeminiFormFields({
|
||
providerId,
|
||
shouldShowApiKey,
|
||
apiKey,
|
||
onApiKeyChange,
|
||
category,
|
||
shouldShowApiKeyLink,
|
||
websiteUrl,
|
||
isPartner,
|
||
partnerPromotionKey,
|
||
shouldShowSpeedTest,
|
||
baseUrl,
|
||
onBaseUrlChange,
|
||
isEndpointModalOpen,
|
||
onEndpointModalToggle,
|
||
onCustomEndpointsChange,
|
||
autoSelect,
|
||
onAutoSelectChange,
|
||
shouldShowModelField,
|
||
model,
|
||
onModelChange,
|
||
speedTestEndpoints,
|
||
}: GeminiFormFieldsProps) {
|
||
const { t } = useTranslation();
|
||
|
||
const [fetchedModels, setFetchedModels] = useState<FetchedModel[]>([]);
|
||
const [isFetchingModels, setIsFetchingModels] = useState(false);
|
||
|
||
const handleFetchModels = useCallback(() => {
|
||
if (!baseUrl || !apiKey) {
|
||
showFetchModelsError(null, t, {
|
||
hasApiKey: !!apiKey,
|
||
hasBaseUrl: !!baseUrl,
|
||
});
|
||
return;
|
||
}
|
||
setIsFetchingModels(true);
|
||
fetchModelsForConfig(baseUrl, apiKey)
|
||
.then((models) => {
|
||
setFetchedModels(models);
|
||
if (models.length === 0) {
|
||
toast.info(t("providerForm.fetchModelsEmpty"));
|
||
} else {
|
||
toast.success(
|
||
t("providerForm.fetchModelsSuccess", { count: models.length }),
|
||
);
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.warn("[ModelFetch] Failed:", err);
|
||
showFetchModelsError(err, t);
|
||
})
|
||
.finally(() => setIsFetchingModels(false));
|
||
}, [baseUrl, apiKey, t]);
|
||
|
||
// 检测是否为 Google 官方(使用 OAuth)
|
||
const isGoogleOfficial =
|
||
partnerPromotionKey?.toLowerCase() === "google-official";
|
||
|
||
return (
|
||
<>
|
||
{/* Google OAuth 提示 */}
|
||
{isGoogleOfficial && (
|
||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-800 dark:bg-blue-950">
|
||
<div className="flex gap-3">
|
||
<Info className="h-5 w-5 flex-shrink-0 text-blue-600 dark:text-blue-400" />
|
||
<div className="space-y-1">
|
||
<p className="text-sm font-medium text-blue-900 dark:text-blue-100">
|
||
{t("provider.form.gemini.oauthTitle", {
|
||
defaultValue: "OAuth 认证模式",
|
||
})}
|
||
</p>
|
||
<p className="text-sm text-blue-700 dark:text-blue-300">
|
||
{t("provider.form.gemini.oauthHint", {
|
||
defaultValue:
|
||
"Google 官方使用 OAuth 个人认证,无需填写 API Key。首次使用时会自动打开浏览器进行登录。",
|
||
})}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* API Key 输入框 */}
|
||
{shouldShowApiKey && !isGoogleOfficial && (
|
||
<ApiKeySection
|
||
value={apiKey}
|
||
onChange={onApiKeyChange}
|
||
category={category}
|
||
shouldShowLink={shouldShowApiKeyLink}
|
||
websiteUrl={websiteUrl}
|
||
isPartner={isPartner}
|
||
partnerPromotionKey={partnerPromotionKey}
|
||
/>
|
||
)}
|
||
|
||
{/* Base URL 输入框(统一使用与 Codex 相同的样式与交互) */}
|
||
{shouldShowSpeedTest && (
|
||
<EndpointField
|
||
id="baseUrl"
|
||
label={t("providerForm.apiEndpoint", { defaultValue: "API 端点" })}
|
||
value={baseUrl}
|
||
onChange={onBaseUrlChange}
|
||
placeholder={t("providerForm.apiEndpointPlaceholder", {
|
||
defaultValue: "https://your-api-endpoint.com/",
|
||
})}
|
||
onManageClick={() => onEndpointModalToggle(true)}
|
||
/>
|
||
)}
|
||
|
||
{/* Model 输入框 */}
|
||
{shouldShowModelField && (
|
||
<div className="space-y-2">
|
||
<div className="flex items-center justify-between">
|
||
<FormLabel htmlFor="gemini-model">
|
||
{t("provider.form.gemini.model", { defaultValue: "模型" })}
|
||
</FormLabel>
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={handleFetchModels}
|
||
disabled={isFetchingModels}
|
||
className="h-7 gap-1"
|
||
>
|
||
{isFetchingModels ? (
|
||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||
) : (
|
||
<Download className="h-3.5 w-3.5" />
|
||
)}
|
||
{t("providerForm.fetchModels")}
|
||
</Button>
|
||
</div>
|
||
<ModelInputWithFetch
|
||
id="gemini-model"
|
||
value={model}
|
||
onChange={onModelChange}
|
||
placeholder="gemini-3.6-flash"
|
||
fetchedModels={fetchedModels}
|
||
isLoading={isFetchingModels}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* 端点测速弹窗 */}
|
||
{shouldShowSpeedTest && isEndpointModalOpen && (
|
||
<EndpointSpeedTest
|
||
appId="gemini"
|
||
providerId={providerId}
|
||
value={baseUrl}
|
||
onChange={onBaseUrlChange}
|
||
initialEndpoints={speedTestEndpoints}
|
||
visible={isEndpointModalOpen}
|
||
onClose={() => onEndpointModalToggle(false)}
|
||
autoSelect={autoSelect}
|
||
onAutoSelectChange={onAutoSelectChange}
|
||
onCustomEndpointsChange={onCustomEndpointsChange}
|
||
/>
|
||
)}
|
||
</>
|
||
);
|
||
}
|