import { Autocomplete, Box, CircularProgress, FormControl, FormHelperText, InputLabel, MenuItem, Select, Stack, TextField, Tooltip, Typography, alpha, useTheme, } from "@mui/material"; import React, { useCallback, useState } from "react"; import { useModelList } from "../hooks/useModelList"; import { PROVIDERS } from "../utils/constants"; import { TooltipIcon } from "./TooltipIcon"; const TRANSITION = "all 0.3s ease-in-out"; interface SuggestedModel { provider: string; model: string; label: string; } interface ModelProviderSelectorProps { model: string; provider: string; onModelChange: (value: string) => void; onProviderChange: (value: string) => void; size?: "small" | "medium"; variant?: "form" | "toolbar" | "suggestions"; showValidation?: boolean; isDisabled?: boolean; // Props for suggestions variant suggestedModels?: SuggestedModel[]; selectedSuggestion?: string; onSuggestionChange?: (value: string, provider: string, model: string) => void; customProvider?: string; customModel?: string; onCustomProviderChange?: (provider: string) => void; onCustomModelChange?: (model: string) => void; selectedOption?: "suggested" | "custom"; onOptionChange?: (option: "suggested" | "custom") => void; } interface ModelSelectProps { model: string; provider: string; availableModels: string[]; loading: boolean; error: string | null; onModelChange: (value: string) => void; size?: "small" | "medium"; variant?: "form" | "toolbar"; showValidation?: boolean; fullWidth?: boolean; isDisabled?: boolean; } const ModelSelect: React.FC = ({ model, provider, availableModels, loading, error, onModelChange, size = "medium", variant = "form", showValidation = false, fullWidth = false, isDisabled = false, }) => { const isToolbar = variant === "toolbar"; if (!provider) { return ( ( )} noOptionsText="Select a provider first" /> ); } if (loading) { return ( ( , }, }} /> )} noOptionsText="Loading models..." /> ); } if (error) { return ( onModelChange(e.target.value)} error={showValidation && !model.trim()} helperText={ error || (showValidation && !model.trim() ? "Model ID is required" : "") } sx={isToolbar ? { minWidth: 250, height: 40 } : undefined} variant="outlined" disabled={isDisabled} /> ); } return ( { onModelChange(newValue || ""); }} disabled={isDisabled} fullWidth={fullWidth} size={size} freeSolo autoHighlight filterOptions={(options, { inputValue }) => { return options.filter((option) => option.toLowerCase().includes(inputValue.toLowerCase()), ); }} renderInput={(params) => ( theme.palette.background.default, p: 1, ml: -1, borderRadius: 1, } : {} } > {showValidation && !model.trim() ? "Model is required" : ""} } sx={isToolbar ? { minWidth: 250, height: 40 } : undefined} /> )} noOptionsText={ availableModels.length === 0 ? "No models available" : "No matching models" } /> ); }; interface ProviderSelectProps { provider: string; onProviderChange: (value: string) => void; size?: "small" | "medium"; variant?: "form" | "toolbar"; showValidation?: boolean; fullWidth?: boolean; isDisabled?: boolean; } const ProviderSelect: React.FC = ({ provider, onProviderChange, size = "medium", variant = "form", showValidation = false, fullWidth = false, isDisabled = false, }: ProviderSelectProps) => { const isToolbar = variant === "toolbar"; return ( Provider {showValidation && !provider.trim() && ( Provider is required )} ); }; export const ModelProviderSelector: React.FC = ({ model, provider, onModelChange, onProviderChange, size = "medium", variant = "form", showValidation = false, isDisabled = false, // Suggestions variant props suggestedModels = [], selectedSuggestion = "", onSuggestionChange, customProvider = "", customModel = "", onCustomProviderChange, onCustomModelChange, selectedOption = "suggested", onOptionChange, }: ModelProviderSelectorProps) => { const { availableModels, loading, error } = useModelList(provider); const theme = useTheme(); const isToolbar = variant === "toolbar"; const isSuggestions = variant === "suggestions"; const fieldSize = isToolbar ? "small" : size; // Tooltip state for toolbar variant const [providerTooltipOpen, setProviderTooltipOpen] = useState(false); const [modelTooltipOpen, setModelTooltipOpen] = useState(false); const handleProviderChange = useCallback( (newProvider: string) => { onProviderChange(newProvider); }, [onProviderChange], ); const handleSuggestionSelectChange = (value: string) => { if (onSuggestionChange) { const selectedModel = suggestedModels.find( (_, index) => index.toString() === value, ); if (selectedModel) { onSuggestionChange(value, selectedModel.provider, selectedModel.model); } } }; const handleCustomClick = () => { if (onOptionChange) { onOptionChange("custom"); // Initialize with currently selected suggestion if (selectedSuggestion && onCustomProviderChange && onCustomModelChange) { const selectedModelIndex = parseInt(selectedSuggestion); const selectedModel = suggestedModels[selectedModelIndex]; if (selectedModel) { onCustomProviderChange(selectedModel.provider); onCustomModelChange(selectedModel.model); } } } }; // Suggestions variant if (isSuggestions) { return ( Select a provider and model {/* Suggested Models Box */} !isDisabled && onOptionChange && onOptionChange("suggested") } sx={{ p: 1.5, borderRadius: 1.5, border: "2px solid", borderColor: isDisabled ? "divider" : selectedOption === "suggested" ? theme.palette.primary.main : alpha(theme.palette.primary.main, 0.2), backgroundColor: isDisabled ? alpha(theme.palette.action.disabled, 0.05) : selectedOption === "suggested" ? alpha(theme.palette.primary.main, 0.1) : alpha(theme.palette.primary.main, 0.03), cursor: isDisabled ? "not-allowed" : "pointer", opacity: isDisabled ? 0.5 : 1, transition: TRANSITION, "&:hover": isDisabled ? {} : { borderColor: alpha(theme.palette.primary.main, 0.4), backgroundColor: alpha(theme.palette.primary.main, 0.08), }, }} > Suggested models Choose from pre-configured provider and model Provider/Model {/* Custom Box */} !isDisabled && handleCustomClick()} sx={{ p: 1.5, borderRadius: 1.5, border: "2px solid", borderColor: isDisabled ? "divider" : selectedOption === "custom" ? theme.palette.primary.main : alpha(theme.palette.primary.main, 0.2), backgroundColor: isDisabled ? alpha(theme.palette.action.disabled, 0.05) : selectedOption === "custom" ? alpha(theme.palette.primary.main, 0.1) : alpha(theme.palette.primary.main, 0.03), cursor: isDisabled ? "not-allowed" : "pointer", opacity: isDisabled ? 0.5 : 1, transition: TRANSITION, "&:hover": isDisabled ? {} : { borderColor: alpha(theme.palette.primary.main, 0.4), backgroundColor: alpha(theme.palette.primary.main, 0.08), }, }} > Custom configuration Choose your own inference provider and model e.stopPropagation()}> {})} onProviderChange={onCustomProviderChange || (() => {})} variant="form" size="small" showValidation={true} isDisabled={isDisabled} /> ); } // Toolbar variant if (isToolbar) { return ( <> {/* Provider Select with Tooltip */} setProviderTooltipOpen(true)} onMouseLeave={() => setProviderTooltipOpen(false)} onClick={() => setProviderTooltipOpen(false)} > {/* Model Select with Tooltip */} setModelTooltipOpen(true)} onMouseLeave={() => setModelTooltipOpen(false)} onClick={() => setModelTooltipOpen(false)} > ); } // Form variant (vertical layout with spacing) return ( {/* Provider Selection */} {/* Model Selection */} ); };