import React from 'react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { useConfigStore } from '@/stores/useConfigStore'; import { useUIStore } from '@/stores/useUIStore'; import { useDeviceInfo } from '@/lib/device'; import { RiArrowDownSLine, RiArrowRightSLine, RiPencilAiLine, RiStarFill, RiStarLine, RiTimeLine } from '@remixicon/react'; import { cn } from '@/lib/utils'; import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; import { ProviderLogo } from '@/components/ui/ProviderLogo'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { useModelLists } from '@/hooks/useModelLists'; type ProviderModel = Record & { id?: string; name?: string }; interface ModelSelectorProps { providerId: string; modelId: string; onChange: (providerId: string, modelId: string) => void; className?: string; } export const ModelSelector: React.FC = ({ providerId, modelId, onChange, className }) => { const { providers, modelsMetadata } = useConfigStore(); const isMobile = useUIStore(state => state.isMobile); const { toggleFavoriteModel, isFavoriteModel, addRecentModel } = useUIStore(); const { favoriteModelsList, recentModelsList } = useModelLists(); const { isMobile: deviceIsMobile } = useDeviceInfo(); const isActuallyMobile = isMobile || deviceIsMobile; const [isMobilePanelOpen, setIsMobilePanelOpen] = React.useState(false); const [expandedMobileProviders, setExpandedMobileProviders] = React.useState>(new Set()); const closeMobilePanel = () => setIsMobilePanelOpen(false); const toggleMobileProviderExpansion = (providerId: string) => { setExpandedMobileProviders(prev => { const newSet = new Set(prev); if (newSet.has(providerId)) { newSet.delete(providerId); } else { newSet.add(providerId); } return newSet; }); }; const getModelDisplayName = (model: Record) => { const name = model?.name || model?.id || ''; const nameStr = String(name); if (nameStr.length > 40) { return nameStr.substring(0, 37) + '...'; } return nameStr; }; const getModelMetadata = (providerId: string, modelId: string) => { const key = `${providerId}/${modelId}`; return modelsMetadata.get(key); }; const handleProviderAndModelChange = (newProviderId: string, newModelId: string) => { onChange(newProviderId, newModelId); // Add to recent models on successful selection addRecentModel(newProviderId, newModelId); }; const renderMobileModelPanel = () => { if (!isActuallyMobile) return null; return (
{/* Favorites Section for Mobile */} {favoriteModelsList.length > 0 && (
Favorites
{favoriteModelsList.map(({ model, providerID, modelID }) => { const isSelectedModel = providerID === providerId && modelID === modelId; const metadata = getModelMetadata(providerID, modelID); return (
); })}
)} {/* Recents Section for Mobile */} {recentModelsList.length > 0 && (
Recents
{recentModelsList.map(({ model, providerID, modelID }) => { const isSelectedModel = providerID === providerId && modelID === modelId; const metadata = getModelMetadata(providerID, modelID); return (
); })}
)} {providers.map((provider) => { const providerModels = Array.isArray(provider.models) ? provider.models : []; if (providerModels.length === 0) return null; const isActiveProvider = provider.id === providerId; const isExpanded = expandedMobileProviders.has(provider.id); return (
{isExpanded && (
{providerModels.map((modelItem: ProviderModel) => { const isSelectedModel = provider.id === providerId && modelItem.id === modelId; const metadata = getModelMetadata(provider.id as string, modelItem.id as string); return (
{isSelectedModel && (
)}
); })}
)}
); })}
); }; return ( <> {isActuallyMobile ? ( ) : (
{providerId ? ( <> ) : ( )} {providerId && modelId ? `${providerId}/${modelId}` : 'Not selected'}
{/* Favorites Section */} {favoriteModelsList.length > 0 && ( Favorites {favoriteModelsList.map(({ model, providerID, modelID }) => { const metadata = getModelMetadata(providerID, modelID); return ( { e.preventDefault(); handleProviderAndModelChange(providerID, modelID); }} >
{getModelDisplayName(model)} {typeof (metadata as unknown as Record)?.description === 'string' && ( {(metadata as unknown as Record).description as React.ReactNode} )}
); })}
)} {/* Recents Section */} {recentModelsList.length > 0 && ( Recent {recentModelsList.map(({ model, providerID, modelID }) => { const metadata = getModelMetadata(providerID, modelID); return ( { e.preventDefault(); handleProviderAndModelChange(providerID, modelID); }} >
{getModelDisplayName(model)} {typeof (metadata as unknown as Record)?.description === 'string' && ( {(metadata as unknown as Record).description as React.ReactNode} )}
); })}
)} {/* Separator before providers */} {(favoriteModelsList.length > 0 || recentModelsList.length > 0) && ( )} {providers.map((provider) => { const providerModels = Array.isArray(provider.models) ? provider.models : []; if (providerModels.length === 0) { return ( {provider.name} (No models) ); } return ( {provider.name} {providerModels.map((modelItem: ProviderModel) => { const metadata = getModelMetadata(provider.id as string, modelItem.id as string); return ( { e.preventDefault(); handleProviderAndModelChange(provider.id as string, modelItem.id as string); }} >
{getModelDisplayName(modelItem)} {typeof (metadata as unknown as Record)?.description === 'string' && ( {(metadata as unknown as Record).description as React.ReactNode} )}
); })}
); })} handleProviderAndModelChange('', '')} > No model (optional)
)} {renderMobileModelPanel()} ); };