Files
CC-Switch/src/components/providers/forms/GeminiFormFields.tsx
T
Jason b972f0a3bd feat(presets): upgrade default models to Opus 5, GPT-5.6 Sol and Gemini 3.6 Flash
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.
2026-07-26 19:08:33 +08:00

213 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}
/>
)}
</>
);
}