ai-model-selector
v0.1.0
Published
React components and hooks for AI model selection — provider switching, capability filtering, model comparison, and compatibility guards.
Maintainers
Readme
ai-model-selector
React components and hooks for AI model selection. Built for apps that use multiple AI providers (Ollama, Claude, OpenRouter, or your own).
Single-Select Mode

Compare Mode

See the full Usage Guide for integration walkthrough, screenshots, and MLflow examples.
Features
- Provider switching — radio-style buttons to switch between AI providers
- Model dropdown — select element with parameter sizes, capability tags, and exclusion guards
- Capability filtering — filter models by capability (reasoning, writing, coding, etc.)
- Compare mode — multi-select checkboxes for side-by-side model comparison
- Model exclusion — automatically grey out models incompatible with your context (e.g., reasoning models that break JSON parsing)
- Experiment naming — label comparison runs for tracking with MLflow or similar
- Fully customizable — override styles, provide custom components, extend the model catalog
- Headless-friendly — use just the hook and build your own UI
- Tree-shakeable — import only what you need
Install
npm install ai-model-selectorQuick Start
import {
useModelSelection,
ProviderSelector,
ModelDropdown,
CapabilityBadges,
ModelFilterBar,
ModelCheckboxPanel,
} from "ai-model-selector";
function MyModelPicker() {
const ms = useModelSelection({
fetchModels: () => fetch("/api/models").then((r) => r.json()),
defaultProvider: "ollama",
excludeMode: "json-output", // grey out reasoning models
storageKey: "my-app-compare", // persist selections in localStorage
});
if (ms.compareMode) {
return (
<div>
<ModelFilterBar
providerFilters={ms.providerFilters}
capFilters={ms.capFilters}
hasActiveFilters={ms.hasActiveFilters}
onToggleProvider={ms.toggleProviderFilter}
onToggleCap={ms.toggleCapFilter}
onClear={ms.clearFilters}
/>
<ModelCheckboxPanel
models={ms.allModels}
filteredIds={ms.filteredIds}
hasActiveFilters={ms.hasActiveFilters}
selectedModels={ms.compareModels}
onToggle={ms.toggleCompareModel}
isExcluded={ms.isExcluded}
/>
</div>
);
}
return (
<div>
<ProviderSelector
providers={[
{ value: "ollama", label: "Ollama", badge: "Free" },
{ value: "claude", label: "Claude" },
{ value: "openrouter", label: "OpenRouter", badge: "Free" },
]}
value={ms.provider}
onChange={ms.setProvider}
/>
<ModelDropdown
models={ms.currentModels}
value={ms.selectedModel}
onChange={ms.setSelectedModel}
loading={ms.modelsLoading}
excludeMode="json-output"
label="Model"
/>
<CapabilityBadges
model={ms.currentModels.find((m) => m.id === ms.selectedModel)}
/>
</div>
);
}Hook-Only Usage
If you want the state management without the UI components:
import { useModelSelection } from "ai-model-selector";
function MyCustomUI() {
const {
provider,
setProvider,
selectedModel,
setSelectedModel,
currentModels,
compareMode,
setCompareMode,
compareModels,
toggleCompareModel,
isExcluded,
} = useModelSelection({
fetchModels: () => fetch("/api/models").then((r) => r.json()),
});
// Build your own UI using the state...
}Backend-Only (No React)
Use the catalog module for server-side validation without pulling in React:
import { isModelExcluded, getModelMetadata } from "ai-model-selector/catalog";
// Reject incompatible models in your API route
if (isModelExcluded(requestedModel, "json-output")) {
return { error: "This model cannot produce structured JSON" };
}
// Get model capabilities
const meta = getModelMetadata("gemma3:12b");
// { capabilities: ["general", "writing"], description: "Google all-rounder", parameterSize: "12B" }Custom Model Catalog
Extend or replace the default catalog:
import { createCatalogLookup, DEFAULT_CATALOG } from "ai-model-selector/catalog";
// Add your own models and keep the defaults as fallback
const { getModelMetadata, isModelExcluded } = createCatalogLookup([
["my-custom-model", {
capabilities: ["coding", "reasoning"],
description: "My fine-tuned model",
parameterSize: "13B",
excludeFromModes: ["json-output"],
}],
...DEFAULT_CATALOG,
]);Custom Styling
All components accept className props for styling. They use plain CSS class names — works with Tailwind, CSS modules, or any CSS framework.
<ProviderSelector
providers={providers}
value={provider}
onChange={setProvider}
className="flex gap-4"
buttonClassName="px-4 py-2 rounded-lg border"
activeClassName="bg-indigo-100 border-indigo-500 text-indigo-700"
badgeClassName="text-xs bg-green-100 text-green-700 px-1 rounded"
/>Custom Checkbox Component
Bring your own checkbox (e.g., shadcn/ui, Radix, Headless UI):
import { Checkbox } from "@/components/ui/checkbox"; // shadcn/ui
<ModelCheckboxPanel
models={ms.allModels}
filteredIds={ms.filteredIds}
hasActiveFilters={ms.hasActiveFilters}
selectedModels={ms.compareModels}
onToggle={ms.toggleCompareModel}
isExcluded={ms.isExcluded}
renderCheckbox={({ checked, disabled, onChange }) => (
<Checkbox checked={checked} disabled={disabled} onCheckedChange={onChange} />
)}
/>Models API Format
The fetchModels function should return models grouped by provider:
{
ollama: [
{ id: "gemma3:12b", name: "gemma3:12b", provider: "ollama", capabilities: ["general", "writing"], parameterSize: "12B" },
{ id: "mistral:7b", name: "mistral:7b", provider: "ollama", capabilities: ["writing", "general"], parameterSize: "7B" },
],
claude: [
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", provider: "claude", capabilities: ["general", "writing", "coding"] },
],
}API Reference
Hook
| Export | Description |
|--------|-------------|
| useModelSelection(options) | Main hook — manages all selection state |
| ModelSelectionState | TypeScript type for the hook's return value |
Components
| Component | Description |
|-----------|-------------|
| ProviderSelector | Radio-style provider buttons |
| ModelDropdown | Select element with exclusion guards |
| CapabilityBadges | Colored capability pills |
| ModelFilterBar | Provider + capability filter buttons |
| ModelCheckboxPanel | Multi-select model list with checkboxes |
| ExperimentNameInput | Text field for experiment labeling |
Catalog Functions
| Export | Description |
|--------|-------------|
| getModelMetadata(id) | Look up capabilities, description, parameter size |
| isModelExcluded(id, mode) | Check if a model is excluded from a mode |
| createCatalogLookup(catalog) | Create custom lookup functions from your own catalog |
| getCapabilityColor(cap) | Get the display color for a capability |
| DEFAULT_CATALOG | The built-in model catalog (extensible) |
| CAPABILITY_COLORS | Default color map for capabilities |
Types
| Type | Description |
|------|-------------|
| AIProvider | Provider identifier ("claude" \| "ollama" \| "openrouter" \| string) |
| ModelCapability | Capability tag ("reasoning" \| "writing" \| "coding" \| ...) |
| ModelMode | Context mode ("json-output" \| "free-text" \| string) |
| ModelInfo | Model metadata object |
| CatalogEntry | Catalog entry with capabilities and exclusions |
| ModelsByProvider | Models grouped by provider key |
Experiment Tracking (MLflow, W&B, etc.)
The experimentName from the hook is a plain string — pass it to your API and use it with any tracking system. The package does NOT include tracking dependencies.
// Frontend: pass experiment name to your API
await fetch("/api/generate", {
method: "POST",
body: JSON.stringify({
model: ms.selectedModel,
experimentName: ms.experimentName,
prompt: "...",
}),
});# Backend: log to MLflow (or any tracker)
import mlflow
mlflow.set_experiment(request.experiment_name)
with mlflow.start_run():
mlflow.log_param("model", request.model)
result = await run_inference(request.model, request.prompt)
mlflow.log_metric("latency_ms", result.latency_ms)See the Usage Guide for full Node.js and Python examples.
License
MIT
