import { useCombobox } from "downshift"; import { useEffect, useRef, useState } from "react"; import { SpinnerIcon, EyeIcon, EyeSlashIcon } from "@phosphor-icons/react"; import ModelRow from "./ModelRow"; import type { Model } from "../models"; type GatewayProvider = "openai" | "anthropic" | "google" | "xai"; type AuthMethod = "provider-key" | "gateway"; // Latest external provider models for each provider (top 5) const EXTERNAL_MODELS: Record< GatewayProvider, Array<{ id: string; name: string; description: string }> > = { openai: [ { id: "openai/gpt-5.2", name: "gpt-5.2", description: "Premier model for coding and agentic tasks across industries" }, { id: "openai/gpt-5.2-pro", name: "gpt-5.2-pro", description: "Enhanced GPT-5.2 with smarter and more precise responses" }, { id: "openai/gpt-5-mini", name: "gpt-5-mini", description: "Faster, cost-efficient version of GPT-5, ideal for well-defined tasks" }, { id: "openai/gpt-5-nano", name: "gpt-5-nano", description: "Fastest and most cost-efficient version of GPT-5" }, { id: "openai/gpt-5", name: "gpt-5", description: "Intelligent reasoning model for coding and agentic tasks" } ], anthropic: [ { id: "anthropic/claude-sonnet-4-5-20250929", name: "claude-sonnet-4-5-20250929", description: "Recommended: Best balance of intelligence, speed, and cost. Excellent for coding and agentic tasks" }, { id: "anthropic/claude-opus-4-5-20251101", name: "claude-opus-4-5-20251101", description: "Premium model combining maximum intelligence with practical performance" }, { id: "anthropic/claude-haiku-4-5-20251001", name: "claude-haiku-4-5-20251001", description: "Fastest model with near-frontier intelligence, optimized for real-time interactions" }, { id: "anthropic/claude-opus-4-1-20250805", name: "claude-opus-4-1-20250805", description: "Legacy: Advanced model for complex reasoning tasks (migrate to Opus 4.5)" }, { id: "anthropic/claude-sonnet-4-20250514", name: "claude-sonnet-4-20250514", description: "Legacy: High-performance balanced model (migrate to Sonnet 4.5)" } ], google: [ { id: "google-ai-studio/gemini-3-pro-preview", name: "gemini-3-pro-preview", description: "Most intelligent model for multimodal understanding, best for agentic and vibe-coding tasks" }, { id: "google-ai-studio/gemini-3-flash-preview", name: "gemini-3-flash-preview", description: "Most intelligent model built for speed, combining frontier intelligence with superior search" }, { id: "google-ai-studio/gemini-2.5-pro", name: "gemini-2.5-pro", description: "State-of-the-art thinking model for complex problems in code, math, and STEM" }, { id: "google-ai-studio/gemini-2.5-flash", name: "gemini-2.5-flash", description: "Best price-performance model, well-rounded capabilities for large-scale processing" }, { id: "google-ai-studio/gemini-2.5-flash-lite", name: "gemini-2.5-flash-lite", description: "Fastest flash model optimized for cost-efficiency and high throughput" } ], xai: [ { id: "xai/grok-4-1-fast-reasoning", name: "grok-4-1-fast-reasoning", description: "Advanced reasoning capabilities with a 2 million token context window" }, { id: "xai/grok-4-1-fast-non-reasoning", name: "grok-4-1-fast-non-reasoning", description: "Optimized for speed with a 2 million token context window" }, { id: "xai/grok-3", name: "grok-3", description: "Standard model with a 256k token context window" }, { id: "xai/grok-3-mini", name: "grok-3-mini", description: "Smaller, faster variant with a 256k token context window" }, { id: "xai/grok-2-vision-1212", name: "grok-2-vision-1212", description: "Supports image input with a 131,072 token context window" } ] }; type FilterState = { [key: string]: "show" | "hide" | null; }; interface UnifiedModelSelectorProps { workersAiModels: Model[]; activeWorkersAiModel: Model | undefined; isLoadingWorkersAi: boolean; useExternalProvider: boolean; externalProvider: GatewayProvider; externalModel: string | undefined; authMethod: AuthMethod; providerApiKey?: string; gatewayAccountId?: string; gatewayId?: string; gatewayApiKey?: string; onModeChange: (useExternal: boolean) => void; onWorkersAiModelSelect: (model: Model | null) => void; onExternalProviderChange: (provider: GatewayProvider) => void; onExternalModelSelect: (modelId: string) => void; onAuthMethodChange: (method: AuthMethod) => void; onProviderApiKeyChange: (key: string) => void; onGatewayAccountIdChange: (id: string) => void; onGatewayIdChange: (id: string) => void; onGatewayApiKeyChange: (key: string) => void; } const UnifiedModelSelector = ({ workersAiModels, activeWorkersAiModel, isLoadingWorkersAi, useExternalProvider, externalProvider, externalModel, authMethod, providerApiKey, gatewayAccountId, gatewayId, gatewayApiKey, onModeChange, onWorkersAiModelSelect, onExternalProviderChange, onExternalModelSelect, onAuthMethodChange, onProviderApiKeyChange, onGatewayAccountIdChange, onGatewayIdChange, onGatewayApiKeyChange }: UnifiedModelSelectorProps) => { const [showProviderKey, setShowProviderKey] = useState(false); const [showGatewayKey, setShowGatewayKey] = useState(false); const [inputItems, setInputItems] = useState(workersAiModels); const [inputValue, setInputValue] = useState(""); const [selectedItem, setSelectedItem] = useState( activeWorkersAiModel || null ); const [filterState, setFilterState] = useState(() => { const storedFilters = sessionStorage.getItem("modelFilters"); if (storedFilters) { try { return JSON.parse(storedFilters); } catch (e) { console.error("Failed to parse stored filters", e); } } return { Beta: null, LoRA: null, MCP: "show" }; }); const inputRef = useRef(null); useEffect(() => { if (!useExternalProvider) { setInputItems(workersAiModels); setSelectedItem(activeWorkersAiModel || null); } }, [workersAiModels, activeWorkersAiModel, useExternalProvider]); useEffect(() => { if (useExternalProvider) return; let filteredItems = workersAiModels; if (inputValue) { filteredItems = filteredItems.filter((model) => model.name.includes(inputValue) ); } for (const [tag, state] of Object.entries(filterState)) { if (state === "show") { filteredItems = filteredItems.filter((model) => model.properties.some((prop) => { if ( tag === "Beta" && prop.property_id === "beta" && prop.value === "true" ) return true; if ( tag === "LoRA" && prop.property_id === "lora" && prop.value === "true" ) return true; if ( tag === "MCP" && prop.property_id === "function_calling" && prop.value === "true" ) return true; return false; }) ); } else if (state === "hide") { filteredItems = filteredItems.filter( (model) => !model.properties.some((prop) => { if ( tag === "Beta" && prop.property_id === "beta" && prop.value === "true" ) return true; if ( tag === "LoRA" && prop.property_id === "lora" && prop.value === "true" ) return true; if ( tag === "MCP" && prop.property_id === "function_calling" && prop.value === "true" ) return true; return false; }) ); } } setInputItems(filteredItems); sessionStorage.setItem("modelFilters", JSON.stringify(filterState)); }, [filterState, inputValue, workersAiModels, useExternalProvider]); const toggleFilter = (tag: string, event: React.MouseEvent) => { setFilterState((prev) => { const currentState = prev[tag]; let newState = { ...prev }; if (!event.shiftKey && currentState === null) { newState = Object.keys(prev).reduce((acc, key) => { acc[key] = null; return acc; }, {} as FilterState); } if (currentState === null) newState[tag] = "show"; else if (currentState === "show") newState[tag] = "hide"; else newState[tag] = null; return newState; }); }; const { isOpen, getToggleButtonProps, getLabelProps, getMenuProps, getInputProps, highlightedIndex, getItemProps } = useCombobox({ inputValue, items: inputItems, itemToString: (item) => item?.name || "", onInputValueChange: ({ inputValue, type }) => { if (type === useCombobox.stateChangeTypes.InputChange) { setInputValue(inputValue || ""); } }, onSelectedItemChange: ({ selectedItem: newSelectedItem }) => { onWorkersAiModelSelect(newSelectedItem); setSelectedItem(newSelectedItem); inputRef.current?.blur(); } }); const externalModels = EXTERNAL_MODELS[externalProvider] || []; return (
{/* Mode Toggle */}
{!useExternalProvider ? ( /* Workers AI Model Selector */
{!isLoadingWorkersAi && Object.keys(filterState).map((tag) => ( ))}
setInputValue("")} disabled={isLoadingWorkersAi} />
{!isLoadingWorkersAi && !inputValue && selectedItem && ( )}
{isLoadingWorkersAi ? ( ) : isOpen ? ( <>↑ ) : ( <>↓ )}
    {isOpen && inputItems.length === 0 && (
  • No models found
  • )} {isOpen && inputItems.map((item, index) => (
  • ))}
) : ( /* External Provider Models Selector */
{externalModels.map((model) => ( ))}
{/* Authentication Method */}
{authMethod === "provider-key" ? (
onProviderApiKeyChange(e.target.value)} placeholder={`Enter your ${externalProvider === "xai" ? "xAI" : externalProvider} API key`} required className="w-full p-1.5 pr-8 border border-kumo-line rounded-md text-xs bg-kumo-base text-kumo-default" />
) : (
onGatewayAccountIdChange(e.target.value)} placeholder="Cloudflare account ID" required className="w-full p-1.5 border border-kumo-line rounded-md text-xs bg-kumo-base text-kumo-default" />
onGatewayIdChange(e.target.value)} placeholder="AI Gateway ID" required className="w-full p-1.5 border border-kumo-line rounded-md text-xs bg-kumo-base text-kumo-default" />
onGatewayApiKeyChange(e.target.value)} placeholder="Cloudflare API key" required className="w-full p-1.5 pr-8 border border-kumo-line rounded-md text-xs bg-kumo-base text-kumo-default" />

Unified Billing: Uses Cloudflare credits.{" "} Load credits .

)}
)}
); }; export default UnifiedModelSelector;