"use client"; import { useState, useMemo } from "react"; import Modal from "./Modal"; import { getModelsByProviderId, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models"; import { AI_PROVIDERS } from "@/shared/constants/providers"; export default function ModelSelectModal({ isOpen, onClose, onSelect, selectedModel, activeProviders = [], title = "Select Model", modelAliases = {}, }) { const [searchQuery, setSearchQuery] = useState(""); // Group models by provider const groupedModels = useMemo(() => { const groups = {}; // Get active provider IDs const activeProviderIds = activeProviders.length > 0 ? activeProviders.map(p => p.provider) : Object.keys(AI_PROVIDERS); activeProviderIds.forEach((providerId) => { const alias = PROVIDER_ID_TO_ALIAS[providerId] || providerId; const providerInfo = AI_PROVIDERS[providerId] || { name: providerId, color: "#666" }; // For passthrough providers, get models from aliases if (providerInfo.passthroughModels) { const aliasModels = Object.entries(modelAliases) .filter(([, fullModel]) => fullModel.startsWith(`${alias}/`)) .map(([aliasName, fullModel]) => ({ id: fullModel.replace(`${alias}/`, ""), name: aliasName, value: fullModel, })); if (aliasModels.length > 0) { groups[providerId] = { name: providerInfo.name, alias: alias, color: providerInfo.color, models: aliasModels, }; } } else { const models = getModelsByProviderId(providerId); if (models.length > 0) { groups[providerId] = { name: providerInfo.name, alias: alias, color: providerInfo.color, models: models.map((m) => ({ id: m.id, name: m.name, value: `${alias}/${m.id}`, })), }; } } }); return groups; }, [activeProviders, modelAliases]); // Filter models by search query const filteredGroups = useMemo(() => { if (!searchQuery.trim()) return groupedModels; const query = searchQuery.toLowerCase(); const filtered = {}; Object.entries(groupedModels).forEach(([providerId, group]) => { const matchedModels = group.models.filter( (m) => m.name.toLowerCase().includes(query) || m.id.toLowerCase().includes(query) || group.name.toLowerCase().includes(query) ); if (matchedModels.length > 0) { filtered[providerId] = { ...group, models: matchedModels, }; } }); return filtered; }, [groupedModels, searchQuery]); const handleSelect = (model) => { onSelect(model); onClose(); setSearchQuery(""); }; return ( { onClose(); setSearchQuery(""); }} title={title} size="md" className="!p-4" > {/* Search - compact */}
search setSearchQuery(e.target.value)} className="w-full pl-8 pr-3 py-1.5 bg-surface border border-border rounded text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" />
{/* Models grouped by provider - compact */}
{Object.entries(filteredGroups).map(([providerId, group]) => (
{/* Provider header */}
{group.name} ({group.models.length})
{/* Models as wrap chips - compact */}
{group.models.map((model) => { const isSelected = selectedModel === model.value; return ( ); })}
))} {Object.keys(filteredGroups).length === 0 && (
search_off

No models found

)}
); }