More realistic allowlist screen (#43)

This commit is contained in:
John Richmond 2025-05-27 10:20:35 -07:00 committed by GitHub
parent 082ba3ce80
commit ef42763ad9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1925 additions and 243 deletions

View file

@ -54,6 +54,7 @@
"@tanstack/react-table": "^8.20.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.7.1",

21
pnpm-lock.yaml generated
View file

@ -89,6 +89,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
cmdk:
specifier: ^1.1.1
version: 1.1.1(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
date-fns:
specifier: ^4.1.0
version: 4.1.0
@ -4368,6 +4371,12 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@ -13426,6 +13435,18 @@ snapshots:
clsx@2.1.1: {}
cmdk@1.1.1(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.3)(react@19.1.0)
'@radix-ui/react-dialog': 1.1.13(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-id': 1.1.1(@types/react@19.1.3)(react@19.1.0)
'@radix-ui/react-primitive': 2.1.2(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
co@4.6.0: {}
collect-v8-coverage@1.0.2: {}

View file

@ -1,91 +1,65 @@
'use client';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { type ProviderName } from '@roo-code/types';
import {
type OrganizationAllowList,
type OrganizationSettings,
ORGANIZATION_ALLOW_ALL,
} from '@/types';
import { type OrganizationSettings, ORGANIZATION_ALLOW_ALL } from '@/types';
import {
getOrganizationSettings,
updateOrganization,
} from '@/actions/organizationSettings';
import { Badge, Button, Checkbox, Label } from '@/components/ui';
import MultipleSelector from '@/components/ui/multiple-selector';
type ProviderSetting = {
allowAll: boolean;
models?: string[];
import openRouterModels from '@/lib/data/openrouter-models.json';
import anthropicModels from '@/lib/data/anthropic.json';
import geminiModels from '@/lib/data/gemini.json';
import deepseekModels from '@/lib/data/deepseek.json';
import openaiNativeModels from '@/lib/data/openai-native.json';
import vertexModels from '@/lib/data/vertex.json';
import bedrockModels from '@/lib/data/bedrock.json';
import mistralModels from '@/lib/data/mistral.json';
import requestyModels from '@/lib/data/requesty-models.json';
import groqModels from '@/lib/data/groq.json';
import xaiModels from '@/lib/data/xai.json';
import unboundModels from '@/lib/data/unbound.json';
import glamaModels from '@/lib/data/glama.json';
import chutesModels from '@/lib/data/chutes.json';
type BaseProvider = {
id: ProviderName;
label: string;
models: string[];
};
// Define a type for providers record
type ProvidersRecord = Record<ProviderName, ProviderSetting>;
// Provider metadata without state information
const providerMetadata: {
id: ProviderName;
name: string;
models: {
id: string;
name: string;
}[];
}[] = [
{
id: 'openai-native',
name: 'OpenAI',
models: [
{ id: 'gpt-4', name: 'GPT-4' },
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo' },
{ id: 'gpt-4o', name: 'GPT-4o' },
],
},
{
id: 'anthropic',
name: 'Anthropic',
models: [
{ id: 'claude-3-opus-20240229', name: 'Claude 3 Opus' },
{ id: 'claude-3-7-sonnet-20250219', name: 'Claude 3 Sonnet' },
{
id: 'claude-3-7-sonnet-20250219:thinking',
name: 'Claude 3 Sonnet Thinking',
},
{ id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku' },
],
},
{
id: 'openrouter',
name: 'OpenRouter',
models: [
{ id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus' },
{ id: 'anthropic/claude-3.7-sonnet', name: 'Claude 3.7 Sonnet' },
{
id: 'anthropic/claude-3.7-sonnet:thinking',
name: 'Claude 3.7 Sonnet Thinking',
},
{ id: 'anthropic/claude-3.5-haiku', name: 'Claude 3.5 Haiku' },
{ id: 'openai/gpt-4', name: 'GPT-4' },
{ id: 'openai/gpt-3.5-turbo', name: 'GPT-3.5 Turbo' },
{ id: 'openai/gpt-4o', name: 'GPT-4o' },
{
id: 'google/gemini-2.5-flash-preview',
name: 'Gemini 2.5 Flash Preview',
},
],
},
{
id: 'mistral',
name: 'Mistral AI',
models: [
{ id: 'mistral-large-latest', name: 'Mistral Large' },
{ id: 'mistral-small-latest', name: 'Mistral Small' },
],
},
];
const providers = (
[
{ id: 'openrouter', label: 'OpenRouter', models: openRouterModels },
{ id: 'anthropic', label: 'Anthropic', models: anthropicModels },
{ id: 'gemini', label: 'Google Gemini', models: geminiModels },
{ id: 'deepseek', label: 'DeepSeek', models: deepseekModels },
{ id: 'openai-native', label: 'OpenAI', models: openaiNativeModels },
{ id: 'openai', label: 'OpenAI Compatible', models: [] },
{ id: 'vertex', label: 'GCP Vertex AI', models: vertexModels },
{ id: 'bedrock', label: 'Amazon Bedrock', models: bedrockModels },
{ id: 'glama', label: 'Glama', models: glamaModels },
{ id: 'vscode-lm', label: 'VS Code LM API', models: [] },
{ id: 'mistral', label: 'Mistral', models: mistralModels },
{ id: 'lmstudio', label: 'LM Studio', models: [] },
{ id: 'ollama', label: 'Ollama', models: [] },
{ id: 'unbound', label: 'Unbound', models: unboundModels },
{ id: 'requesty', label: 'Requesty', models: requestyModels },
{ id: 'human-relay', label: 'Human Relay', models: [] },
{ id: 'xai', label: 'xAI (Grok)', models: xaiModels },
{ id: 'groq', label: 'Groq', models: groqModels },
{ id: 'chutes', label: 'Chutes AI', models: chutesModels },
{ id: 'litellm', label: 'LiteLLM', models: [] },
] satisfies BaseProvider[]
).sort((a, b) => a.label.localeCompare(b.label));
type ProviderWhitelistFormProps = {
orgSettings: OrganizationSettings;
@ -98,162 +72,115 @@ const ProviderWhitelistForm = ({
}: ProviderWhitelistFormProps) => {
const t = useTranslations('ProviderWhitelist');
const [allowList, setAllowList] = useState<OrganizationAllowList>(
orgSettings?.allowList || ORGANIZATION_ALLOW_ALL,
const fullProviderMetadata = useMemo(
() =>
providers.map((provider) => {
const models = orgSettings.allowList.providers[provider.id]?.models;
if (models) {
const providerModels = new Set(provider.models);
const difference = models.filter(
(model) => !providerModels.has(model),
);
if (difference.length > 0) {
return {
...provider,
models: [...provider.models, ...difference],
};
}
}
return provider;
}),
[orgSettings],
);
const [allowAll, setAllowAll] = useState(orgSettings.allowList.allowAll);
const [providerAllowAll, setProviderAllowAll] = useState(
Object.entries(orgSettings.allowList.providers).reduce(
(acc, [provider, providerSettings]) => {
if (providerSettings.allowAll) {
acc.add(provider);
}
return acc;
},
new Set<string>(),
),
);
const [providerModels, setProviderModels] = useState(
fullProviderMetadata.reduce((acc, meta) => {
acc.set(meta.id, orgSettings.allowList.providers[meta.id]?.models || []);
return acc;
}, new Map<ProviderName, string[]>()),
);
const [hasChanges, setHasChanges] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const isAllowAllChecked = () => allowList.allowAll;
const isProviderChecked = (providerId: ProviderName) => {
if (allowList.allowAll) return true;
return allowList.providers[providerId]?.allowAll || false;
};
const isModelChecked = (providerId: ProviderName, modelId: string) => {
if (allowList.allowAll) return true;
const provider = allowList.providers[providerId];
if (!provider) return false;
if (provider.allowAll) return true;
return provider.models?.includes(modelId) || false;
};
const isProviderAllowAll = (providerId: ProviderName) =>
providerAllowAll.has(providerId);
const toggleAllowAll = () => {
const newAllowAll = !allowList.allowAll;
if (newAllowAll) {
setAllowList({ allowAll: true, providers: {} });
} else {
const newProviders: Partial<ProvidersRecord> = {};
providerMetadata.forEach((provider) => {
newProviders[provider.id] = { allowAll: true };
});
setAllowList({
allowAll: false,
providers: newProviders,
});
}
setAllowAll(!allowAll);
setHasChanges(true);
};
const toggleProvider = (providerId: ProviderName) => {
if (allowList.allowAll) {
const newProviders: Partial<ProvidersRecord> = {};
providerMetadata.forEach((provider) => {
if (provider.id !== providerId) {
newProviders[provider.id] = { allowAll: true };
}
});
setAllowList({
allowAll: false,
providers: newProviders,
});
const newProviderAllowAll = new Set(providerAllowAll);
if (providerAllowAll.has(providerId)) {
newProviderAllowAll.delete(providerId);
} else {
const newProviders = { ...allowList.providers };
const providersRecord = newProviders;
const isCurrentlyEnabled = isProviderChecked(providerId);
if (isCurrentlyEnabled) {
if (providersRecord[providerId]) {
providersRecord[providerId] = {
...providersRecord[providerId],
allowAll: false,
models: [],
};
}
} else {
providersRecord[providerId] = { allowAll: true };
}
setAllowList({ ...allowList, providers: newProviders });
newProviderAllowAll.add(providerId);
}
setProviderAllowAll(newProviderAllowAll);
setHasChanges(true);
};
const toggleModel = (providerId: ProviderName, modelId: string) => {
if (allowList.allowAll || isProviderChecked(providerId)) {
const newProviders = { ...allowList.providers };
const providersRecord = newProviders;
if (allowList.allowAll) {
providerMetadata.forEach((provider) => {
providersRecord[provider.id] = { allowAll: true };
});
const provider = providerMetadata.find((p) => p.id === providerId);
if (provider) {
const models = provider.models
.filter((m) => m.id !== modelId)
.map((m) => m.id);
providersRecord[providerId] = { allowAll: false, models };
}
setAllowList({ allowAll: false, providers: newProviders });
} else {
const provider = providerMetadata.find((p) => p.id === providerId);
if (provider) {
const models = provider.models
.filter((m) => m.id !== modelId)
.map((m) => m.id);
providersRecord[providerId] = { allowAll: false, models };
setAllowList({ ...allowList, providers: newProviders });
}
}
} else {
const newProviders = { ...allowList.providers };
const providersRecord = newProviders;
const provider = providersRecord[providerId] || {
allowAll: false,
models: [],
};
const models = provider.models || [];
const isCurrentlyEnabled = models.includes(modelId);
if (isCurrentlyEnabled) {
providersRecord[providerId] = {
...provider,
models: models.filter((m: string) => m !== modelId),
};
} else {
providersRecord[providerId] = {
...provider,
models: [...models, modelId],
};
const allModels =
providerMetadata
.find((p) => p.id === providerId)
?.models.map((m) => m.id) || [];
const enabledModels = providersRecord[providerId].models || [];
if (
allModels.length === enabledModels.length &&
allModels.every((m) => enabledModels.includes(m))
) {
providersRecord[providerId] = { allowAll: true };
}
}
setAllowList({ ...allowList, providers: newProviders });
}
const setModels = (providerId: ProviderName, models: string[]) => {
const newProviderModels = new Map(providerModels);
newProviderModels.set(providerId, models);
setProviderModels(newProviderModels);
setHasChanges(true);
};
const saveChanges = async () => {
setIsSaving(true);
try {
let allowList;
if (allowAll) {
allowList = ORGANIZATION_ALLOW_ALL;
} else {
allowList = {
allowAll: false,
providers: fullProviderMetadata.reduce(
(acc, meta) => {
if (providerAllowAll.has(meta.id)) {
acc[meta.id] = {
allowAll: true,
};
} else {
const models = providerModels.get(meta.id);
if (models && models.length > 0) {
acc[meta.id] = {
allowAll: false,
models: models,
};
}
}
return acc;
},
{} as Record<
ProviderName,
{
allowAll: boolean;
models?: string[];
}
>,
),
};
}
const result = await updateOrganization({
allowList: allowList,
});
@ -291,7 +218,7 @@ const ProviderWhitelistForm = ({
<div className="mb-4 flex items-center space-x-2">
<Checkbox
id="allow-all-providers"
checked={isAllowAllChecked()}
checked={allowAll}
onCheckedChange={toggleAllowAll}
disabled={isSaving}
/>
@ -302,46 +229,49 @@ const ProviderWhitelistForm = ({
Allow All Providers
</Label>
</div>
<div className={isAllowAllChecked() ? 'opacity-50' : ''}>
{providerMetadata.map((provider) => (
<div className={allowAll ? 'opacity-50' : ''}>
{fullProviderMetadata.map((provider) => (
<div key={provider.id} className="mb-4">
<div className="mb-2 flex items-center space-x-2">
<Checkbox
id={`provider-${provider.id}`}
checked={isProviderChecked(provider.id)}
disabled={isAllowAllChecked() || isSaving}
onCheckedChange={() => toggleProvider(provider.id)}
/>
<Label
htmlFor={`provider-${provider.id}`}
className="text-sm font-medium"
>
{provider.name}
</Label>
<div className="mb-2 flex items-center justify-between">
<div className="text-sm font-medium">{provider.label}</div>
<div className="flex items-center space-x-2">
<Label
htmlFor={`provider-${provider.id}`}
className="text-sm"
>
Allow all models
</Label>
<Checkbox
id={`provider-${provider.id}`}
checked={isProviderAllowAll(provider.id)}
disabled={allowAll || isSaving}
onCheckedChange={() => toggleProvider(provider.id)}
/>
</div>
</div>
<div className="ml-6 space-y-2">
{provider.models.map((model) => (
<div key={model.id} className="flex items-center space-x-2">
<Checkbox
id={`model-${model.id}`}
checked={isModelChecked(provider.id, model.id)}
disabled={
isAllowAllChecked() ||
isProviderChecked(provider.id) ||
isSaving
}
onCheckedChange={() =>
toggleModel(provider.id, model.id)
}
/>
<Label
htmlFor={`model-${model.id}`}
className={`text-xs ${isAllowAllChecked() || !isProviderChecked(provider.id) ? 'text-muted-foreground' : ''}`}
>
{model.name}
</Label>
</div>
))}
<div className="max-w-full">
<MultipleSelector
defaultOptions={provider.models.map((model) => ({
label: model,
value: model,
disable: providerModels.get(provider.id)?.includes(model),
}))}
value={(providerModels.get(provider.id) || []).map(
(model) => ({
label: model,
value: model,
}),
)}
creatable
disabled={allowAll || isSaving}
placeholder="Pick models..."
onChange={(options) =>
setModels(
provider.id,
options.map((option) => option.value),
)
}
/>
</div>
</div>
))}

View file

@ -0,0 +1,134 @@
"use client"
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-sm",
className,
)}
{...props}
/>
)
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-sm bg-transparent py-3 outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
</div>
)
}
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
{...props}
/>
)
}
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center" {...props} />
}
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className,
)}
{...props}
/>
)
}
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-accent/5 -mx-1 h-px", className)}
{...props}
/>
)
}
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-xs px-2 py-1.5 outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"text-foreground active:opacity-80 cursor-pointer group",
className,
)}
{...props}
/>
)
}
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View file

@ -0,0 +1,110 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
)
}
function DialogContent({ className, children, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}>
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
)
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
)
}
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}

View file

@ -0,0 +1,637 @@
'use client';
import { Command as CommandPrimitive, useCommandState } from 'cmdk';
import { X } from 'lucide-react';
import * as React from 'react';
import { forwardRef, useEffect } from 'react';
import { Badge } from '@/components/ui/badge';
import {
Command,
CommandGroup,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { cn } from '@/lib/utils';
export interface Option {
value: string;
label: string;
disable?: boolean;
/** fixed option that can't be removed. */
fixed?: boolean;
/** Group the options by providing key. */
[key: string]: string | boolean | undefined;
}
interface GroupOption {
[key: string]: Option[];
}
interface MultipleSelectorProps {
value?: Option[];
defaultOptions?: Option[];
/** manually controlled options */
options?: Option[];
placeholder?: string;
/** Loading component. */
loadingIndicator?: React.ReactNode;
/** Empty component. */
emptyIndicator?: React.ReactNode;
/** Debounce time for async search. Only work with `onSearch`. */
delay?: number;
/**
* Only work with `onSearch` prop. Trigger search when `onFocus`.
* For example, when user click on the input, it will trigger the search to get initial options.
**/
triggerSearchOnFocus?: boolean;
/** async search */
onSearch?: (value: string) => Promise<Option[]>;
/**
* sync search. This search will not showing loadingIndicator.
* The rest props are the same as async search.
* i.e.: creatable, groupBy, delay.
**/
onSearchSync?: (value: string) => Option[];
onChange?: (options: Option[]) => void;
/** Limit the maximum number of selected options. */
maxSelected?: number;
/** When the number of selected options exceeds the limit, the onMaxSelected will be called. */
onMaxSelected?: (maxLimit: number) => void;
/** Hide the placeholder when there are options selected. */
hidePlaceholderWhenSelected?: boolean;
disabled?: boolean;
/** Group the options base on provided key. */
groupBy?: string;
className?: string;
badgeClassName?: string;
/**
* First item selected is a default behavior by cmdk. That is why the default is true.
* This is a workaround solution by add a dummy item.
*
* @reference: https://github.com/pacocoursey/cmdk/issues/171
*/
selectFirstItem?: boolean;
/** Allow user to create option when there is no option matched. */
creatable?: boolean;
/** Props of `Command` */
commandProps?: React.ComponentPropsWithoutRef<typeof Command>;
/** Props of `CommandInput` */
inputProps?: Omit<
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>,
'value' | 'placeholder' | 'disabled'
>;
/** hide the clear all button. */
hideClearAllButton?: boolean;
}
export interface MultipleSelectorRef {
selectedValue: Option[];
input: HTMLInputElement;
focus: () => void;
reset: () => void;
}
export function useDebounce<T>(value: T, delay?: number): T {
const [debouncedValue, setDebouncedValue] = React.useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
function transToGroupOption(options: Option[], groupBy?: string) {
if (options.length === 0) {
return {};
}
if (!groupBy) {
return {
'': options,
};
}
const groupOption: GroupOption = {};
options.forEach((option) => {
const key = (option[groupBy] as string) || '';
if (!groupOption[key]) {
groupOption[key] = [];
}
groupOption[key].push(option);
});
return groupOption;
}
function removePickedOption(groupOption: GroupOption, picked: Option[]) {
const cloneOption = JSON.parse(JSON.stringify(groupOption)) as GroupOption;
for (const [key, value] of Object.entries(cloneOption)) {
cloneOption[key] = value.filter(
(val) => !picked.find((p) => p.value === val.value),
);
}
return cloneOption;
}
function isOptionsExist(groupOption: GroupOption, targetOption: Option[]) {
for (const [, value] of Object.entries(groupOption)) {
if (
value.some((option) => targetOption.find((p) => p.value === option.value))
) {
return true;
}
}
return false;
}
/**
* The `CommandEmpty` of shadcn/ui will cause the cmdk empty not rendering correctly.
* So we create one and copy the `Empty` implementation from `cmdk`.
*
* @reference: https://github.com/hsuanyi-chou/shadcn-ui-expansions/issues/34#issuecomment-1949561607
**/
const CommandEmpty = forwardRef<
HTMLDivElement,
React.ComponentProps<typeof CommandPrimitive.Empty>
>(({ className, ...props }, forwardedRef) => {
const render = useCommandState((state) => state.filtered.count === 0);
if (!render) return null;
return (
<div
ref={forwardedRef}
className={cn('py-6 text-center text-sm', className)}
// eslint-disable-next-line react/no-unknown-property
cmdk-empty=""
role="presentation"
{...props}
/>
);
});
CommandEmpty.displayName = 'CommandEmpty';
const MultipleSelector = React.forwardRef<
MultipleSelectorRef,
MultipleSelectorProps
>(
(
{
value,
onChange,
placeholder,
defaultOptions: arrayDefaultOptions = [],
options: arrayOptions,
delay,
onSearch,
onSearchSync,
loadingIndicator,
emptyIndicator,
maxSelected = Number.MAX_SAFE_INTEGER,
onMaxSelected,
hidePlaceholderWhenSelected,
disabled,
groupBy,
className,
badgeClassName,
selectFirstItem = true,
creatable = false,
triggerSearchOnFocus = false,
commandProps,
inputProps,
hideClearAllButton = false,
}: MultipleSelectorProps,
ref: React.Ref<MultipleSelectorRef>,
) => {
const inputRef = React.useRef<HTMLInputElement>(null);
const [open, setOpen] = React.useState(false);
const [onScrollbar, setOnScrollbar] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);
const dropdownRef = React.useRef<HTMLDivElement>(null); // Added this
const [selected, setSelected] = React.useState<Option[]>(value || []);
const [options, setOptions] = React.useState<GroupOption>(
transToGroupOption(arrayDefaultOptions, groupBy),
);
const [inputValue, setInputValue] = React.useState('');
const debouncedSearchTerm = useDebounce(inputValue, delay || 500);
React.useImperativeHandle(
ref,
() => ({
selectedValue: [...selected],
input: inputRef.current as HTMLInputElement,
focus: () => inputRef?.current?.focus(),
reset: () => setSelected([]),
}),
[selected],
);
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node) &&
inputRef.current &&
!inputRef.current.contains(event.target as Node)
) {
setOpen(false);
inputRef.current.blur();
}
};
const handleUnselect = React.useCallback(
(option: Option) => {
const newOptions = selected.filter((s) => s.value !== option.value);
setSelected(newOptions);
onChange?.(newOptions);
},
[onChange, selected],
);
const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === 'Delete' || e.key === 'Backspace') {
if (input.value === '' && selected.length > 0) {
const lastSelectOption = selected[selected.length - 1];
// If last item is fixed, we should not remove it.
if (lastSelectOption && !lastSelectOption.fixed) {
handleUnselect(lastSelectOption);
}
}
}
// This is not a default behavior of the <input /> field
if (e.key === 'Escape') {
input.blur();
}
}
},
[handleUnselect, selected],
);
useEffect(() => {
if (open) {
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchend', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchend', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchend', handleClickOutside);
};
}, [open]);
useEffect(() => {
if (value) {
setSelected(value);
}
}, [value]);
useEffect(() => {
/** If `onSearch` is provided, do not trigger options updated. */
if (!arrayOptions || onSearch) {
return;
}
const newOption = transToGroupOption(arrayOptions || [], groupBy);
if (JSON.stringify(newOption) !== JSON.stringify(options)) {
setOptions(newOption);
}
}, [arrayDefaultOptions, arrayOptions, groupBy, onSearch, options]);
useEffect(() => {
/** sync search */
const doSearchSync = () => {
const res = onSearchSync?.(debouncedSearchTerm);
setOptions(transToGroupOption(res || [], groupBy));
};
const exec = async () => {
if (!onSearchSync || !open) return;
if (triggerSearchOnFocus) {
doSearchSync();
}
if (debouncedSearchTerm) {
doSearchSync();
}
};
void exec();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSearchTerm, groupBy, open, triggerSearchOnFocus]);
useEffect(() => {
/** async search */
const doSearch = async () => {
setIsLoading(true);
const res = await onSearch?.(debouncedSearchTerm);
setOptions(transToGroupOption(res || [], groupBy));
setIsLoading(false);
};
const exec = async () => {
if (!onSearch || !open) return;
if (triggerSearchOnFocus) {
await doSearch();
}
if (debouncedSearchTerm) {
await doSearch();
}
};
void exec();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSearchTerm, groupBy, open, triggerSearchOnFocus]);
const CreatableItem = () => {
if (!creatable) return undefined;
if (
isOptionsExist(options, [{ value: inputValue, label: inputValue }]) ||
selected.find((s) => s.value === inputValue)
) {
return undefined;
}
const Item = (
<CommandItem
value={inputValue}
className="cursor-pointer"
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={(value: string) => {
if (selected.length >= maxSelected) {
onMaxSelected?.(selected.length);
return;
}
setInputValue('');
const newOptions = [...selected, { value, label: value }];
setSelected(newOptions);
onChange?.(newOptions);
}}
>
{`Create "${inputValue}"`}
</CommandItem>
);
// For normal creatable
if (!onSearch && inputValue.length > 0) {
return Item;
}
// For async search creatable. avoid showing creatable item before loading at first.
if (onSearch && debouncedSearchTerm.length > 0 && !isLoading) {
return Item;
}
return undefined;
};
const EmptyItem = React.useCallback(() => {
if (!emptyIndicator) return undefined;
// For async search that showing emptyIndicator
if (onSearch && !creatable && Object.keys(options).length === 0) {
return (
<CommandItem value="-" disabled>
{emptyIndicator}
</CommandItem>
);
}
return <CommandEmpty>{emptyIndicator}</CommandEmpty>;
}, [creatable, emptyIndicator, onSearch, options]);
const selectables = React.useMemo<GroupOption>(
() => removePickedOption(options, selected),
[options, selected],
);
/** Avoid Creatable Selector freezing or lagging when paste a long string. */
const commandFilter = React.useCallback(() => {
if (commandProps?.filter) {
return commandProps.filter;
}
if (creatable) {
return (value: string, search: string) => {
return value.toLowerCase().includes(search.toLowerCase()) ? 1 : -1;
};
}
// Using default filter in `cmdk`. We don't have to provide it.
return undefined;
}, [creatable, commandProps?.filter]);
return (
<Command
ref={dropdownRef}
{...commandProps}
onKeyDown={(e) => {
handleKeyDown(e);
commandProps?.onKeyDown?.(e);
}}
className={cn(
'h-auto overflow-visible bg-transparent',
commandProps?.className,
)}
shouldFilter={
commandProps?.shouldFilter !== undefined
? commandProps.shouldFilter
: !onSearch
} // When onSearch is provided, we don't want to filter the options. You can still override it.
filter={commandFilter()}
>
<div
className={cn(
'min-h-10 rounded-md border border-input text-base ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 md:text-sm',
{
'px-3 py-2': selected.length !== 0,
'cursor-text': !disabled && selected.length !== 0,
},
className,
)}
onClick={() => {
if (disabled) return;
inputRef?.current?.focus();
}}
>
<div className="relative flex flex-wrap gap-1">
{selected.map((option) => {
return (
<Badge
key={option.value}
className={cn(
'data-[disabled]:bg-muted-foreground data-[disabled]:text-muted data-[disabled]:hover:bg-muted-foreground',
'data-[fixed]:bg-muted-foreground data-[fixed]:text-muted data-[fixed]:hover:bg-muted-foreground',
badgeClassName,
)}
data-fixed={option.fixed}
data-disabled={disabled || undefined}
>
{option.label}
<button
type="button"
className={cn(
'ml-1 rounded-full outline-none ring-offset-background focus:ring-2 focus:ring-ring focus:ring-offset-2',
(disabled || option.fixed) && 'hidden',
)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleUnselect(option);
}
}}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onClick={() => handleUnselect(option)}
>
<X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
</button>
</Badge>
);
})}
{/* Avoid having the "Search" Icon */}
<CommandPrimitive.Input
{...inputProps}
ref={inputRef}
value={inputValue}
disabled={disabled}
onValueChange={(value) => {
setInputValue(value);
inputProps?.onValueChange?.(value);
}}
onBlur={(event) => {
if (!onScrollbar) {
setOpen(false);
}
inputProps?.onBlur?.(event);
}}
onFocus={(event) => {
setOpen(true);
inputProps?.onFocus?.(event);
}}
placeholder={
hidePlaceholderWhenSelected && selected.length !== 0
? ''
: placeholder
}
className={cn(
'flex-1 bg-transparent outline-none placeholder:text-muted-foreground',
{
'w-full': hidePlaceholderWhenSelected,
'px-3 py-2': selected.length === 0,
'ml-1': selected.length !== 0,
},
inputProps?.className,
)}
/>
<button
type="button"
onClick={() => {
setSelected(selected.filter((s) => s.fixed));
onChange?.(selected.filter((s) => s.fixed));
}}
className={cn(
'absolute right-0 h-6 w-6 p-0',
(hideClearAllButton ||
disabled ||
selected.length < 1 ||
selected.filter((s) => s.fixed).length === selected.length) &&
'hidden',
)}
>
<X />
</button>
</div>
</div>
<div className="relative">
{open && (
<CommandList
className="absolute top-1 z-10 w-full rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in"
onMouseLeave={() => {
setOnScrollbar(false);
}}
onMouseEnter={() => {
setOnScrollbar(true);
}}
onMouseUp={() => {
inputRef?.current?.focus();
}}
>
{isLoading ? (
<>{loadingIndicator}</>
) : (
<>
{EmptyItem()}
{CreatableItem()}
{!selectFirstItem && (
<CommandItem value="-" className="hidden" />
)}
{Object.entries(selectables).map(([key, dropdowns]) => (
<CommandGroup
key={key}
heading={key}
className="h-full overflow-auto"
>
<>
{dropdowns.map((option) => {
return (
<CommandItem
key={option.value}
value={option.label}
disabled={option.disable}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => {
if (selected.length >= maxSelected) {
onMaxSelected?.(selected.length);
return;
}
setInputValue('');
const newOptions = [...selected, option];
setSelected(newOptions);
onChange?.(newOptions);
}}
className={cn(
'cursor-pointer',
option.disable &&
'cursor-default text-muted-foreground',
)}
>
{option.label}
</CommandItem>
);
})}
</>
</CommandGroup>
))}
</>
)}
</CommandList>
)}
</div>
</Command>
);
},
);
MultipleSelector.displayName = 'MultipleSelector';
export default MultipleSelector;

View file

@ -0,0 +1,8 @@
[
"claude-3-7-sonnet-20250219:thinking",
"claude-3-7-sonnet-20250219",
"claude-3-5-sonnet-20241022",
"claude-3-5-haiku-20241022",
"claude-3-opus-20240229",
"claude-3-haiku-20240307"
]

32
src/lib/data/bedrock.json Normal file
View file

@ -0,0 +1,32 @@
[
"amazon.nova-pro-v1:0",
"amazon.nova-pro-latency-optimized-v1:0",
"amazon.nova-lite-v1:0",
"amazon.nova-micro-v1:0",
"anthropic.claude-3-7-sonnet-20250219-v1:0",
"anthropic.claude-3-5-sonnet-20241022-v2:0",
"anthropic.claude-3-5-haiku-20241022-v1:0",
"anthropic.claude-3-5-sonnet-20240620-v1:0",
"anthropic.claude-3-opus-20240229-v1:0",
"anthropic.claude-3-sonnet-20240229-v1:0",
"anthropic.claude-3-haiku-20240307-v1:0",
"anthropic.claude-2-1-v1:0",
"anthropic.claude-2-0-v1:0",
"anthropic.claude-instant-v1:0",
"deepseek.r1-v1:0",
"meta.llama3-3-70b-instruct-v1:0",
"meta.llama3-2-90b-instruct-v1:0",
"meta.llama3-2-11b-instruct-v1:0",
"meta.llama3-2-3b-instruct-v1:0",
"meta.llama3-2-1b-instruct-v1:0",
"meta.llama3-1-405b-instruct-v1:0",
"meta.llama3-1-70b-instruct-v1:0",
"meta.llama3-1-70b-instruct-latency-optimized-v1:0",
"meta.llama3-1-8b-instruct-v1:0",
"meta.llama3-70b-instruct-v1:0",
"meta.llama3-8b-instruct-v1:0",
"amazon.titan-text-lite-v1:0",
"amazon.titan-text-express-v1:0",
"amazon.titan-text-embeddings-v1:0",
"amazon.titan-text-embeddings-v2:0"
]

23
src/lib/data/chutes.json Normal file
View file

@ -0,0 +1,23 @@
[
"deepseek-ai/DeepSeek-R1",
"deepseek-ai/DeepSeek-V3",
"unsloth/Llama-3.3-70B-Instruct",
"chutesai/Llama-4-Scout-17B-16E-Instruct",
"unsloth/Mistral-Nemo-Instruct-2407",
"unsloth/gemma-3-12b-it",
"NousResearch/DeepHermes-3-Llama-3-8B-Preview",
"unsloth/gemma-3-4b-it",
"nvidia/Llama-3_3-Nemotron-Super-49B-v1",
"nvidia/Llama-3_1-Nemotron-Ultra-253B-v1",
"chutesai/Llama-4-Maverick-17B-128E-Instruct-FP8",
"deepseek-ai/DeepSeek-V3-Base",
"deepseek-ai/DeepSeek-R1-Zero",
"deepseek-ai/DeepSeek-V3-0324",
"Qwen/Qwen3-235B-A22B",
"Qwen/Qwen3-32B",
"Qwen/Qwen3-30B-A3B",
"Qwen/Qwen3-14B",
"Qwen/Qwen3-8B",
"microsoft/MAI-DS-R1-FP8",
"tngtech/DeepSeek-R1T-Chimera"
]

View file

@ -0,0 +1,4 @@
[
"deepseek-chat",
"deepseek-reasoner"
]

21
src/lib/data/gemini.json Normal file
View file

@ -0,0 +1,21 @@
[
"gemini-2.5-flash-preview-04-17:thinking",
"gemini-2.5-flash-preview-04-17",
"gemini-2.5-flash-preview-05-20:thinking",
"gemini-2.5-flash-preview-05-20",
"gemini-2.5-pro-exp-03-25",
"gemini-2.5-pro-preview-03-25",
"gemini-2.5-pro-preview-05-06",
"gemini-2.0-flash-001",
"gemini-2.0-flash-lite-preview-02-05",
"gemini-2.0-pro-exp-02-05",
"gemini-2.0-flash-thinking-exp-01-21",
"gemini-2.0-flash-thinking-exp-1219",
"gemini-2.0-flash-exp",
"gemini-1.5-flash-002",
"gemini-1.5-flash-exp-0827",
"gemini-1.5-flash-8b-exp-0827",
"gemini-1.5-pro-002",
"gemini-1.5-pro-exp-0827",
"gemini-exp-1206"
]

96
src/lib/data/glama.json Normal file
View file

@ -0,0 +1,96 @@
[
"alibaba/qwen-plus",
"alibaba/qwen-turbo",
"anthropic/claude-3-5-haiku-20241022",
"anthropic/claude-3-5-haiku",
"anthropic/claude-3-5-sonnet-20240620",
"anthropic/claude-3-5-sonnet-20241022",
"anthropic/claude-3-5-sonnet",
"anthropic/claude-3-7-sonnet-20250219",
"anthropic/claude-3-7-sonnet",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-opus",
"avian/deepseek-r1",
"avian/llama-3.1-405b-instruct",
"avian/llama-3.1-70b-instruct",
"avian/llama-3.1-8b-instruct",
"avian/llama-3.3-70b-instruct",
"cloudflare/deepseek-r1-distill-qwen-32b",
"cohere/command-r-08-2024",
"cohere/command-r-plus-08-2024",
"cohere/command-r-plus",
"cohere/command-r",
"cohere/command-r7b-12-2024",
"cohere/command-r7b",
"deepinfra/qwq-32b-preview",
"deepseek/deepseek-chat-v3",
"deepseek/deepseek-chat",
"deepseek/deepseek-r1",
"fireworks/deepseek-chat-v3",
"fireworks/deepseek-chat",
"google-ai-studio/gemini-2.0-flash-001",
"google-ai-studio/gemini-2.0-flash-lite-preview-02-05",
"google-ai-studio/gemini-2.0-flash-lite",
"google-ai-studio/gemini-2.0-flash",
"google-ai-studio/gemini-2.5-flash-preview-04-17",
"google-ai-studio/gemini-2.5-flash",
"google-ai-studio/gemini-2.5-pro-exp-03-25",
"google-ai-studio/gemini-2.5-pro-preview-05-06",
"google-ai-studio/gemini-2.5-pro",
"google-vertex/gemini-1.5-flash-001",
"google-vertex/gemini-1.5-flash-002",
"google-vertex/gemini-1.5-flash",
"google-vertex/gemini-1.5-pro-001",
"google-vertex/gemini-1.5-pro-002",
"google-vertex/gemini-1.5-pro",
"google-vertex/gemini-2.0-flash-001",
"google-vertex/gemini-2.0-flash-lite-preview-02-05",
"google-vertex/gemini-2.0-flash-lite",
"google-vertex/gemini-2.0-flash-thinking-exp-01-21",
"google-vertex/gemini-2.0-flash-thinking",
"google-vertex/gemini-2.0-flash",
"google-vertex/gemini-2.5-flash-preview-04-17",
"google-vertex/gemini-2.5-flash",
"google-vertex/gemini-2.5-pro-preview-05-06",
"google-vertex/gemini-2.5-pro",
"groq/deepseek-r1-distill-llama-70b",
"mistral/ministral-3b-2410",
"mistral/ministral-3b",
"mistral/ministral-8b-2410",
"mistral/ministral-8b",
"mistral/mistral-large-2411",
"mistral/mistral-large",
"openai/gpt-4.1-2025-04-14",
"openai/gpt-4.1-mini-2025-04-14",
"openai/gpt-4.1-mini",
"openai/gpt-4.1-nano-2025-04-14",
"openai/gpt-4.1-nano",
"openai/gpt-4.1",
"openai/gpt-4.5-preview-2025-02-27",
"openai/gpt-4.5",
"openai/gpt-4o-2024-05-13",
"openai/gpt-4o-2024-08-06",
"openai/gpt-4o-2024-11-20",
"openai/gpt-4o-mini-2024-07-18",
"openai/gpt-4o-mini",
"openai/gpt-4o",
"openai/o1-2024-12-17",
"openai/o1-mini-2024-09-12",
"openai/o3-2025-04-16",
"openai/o3-mini-2025-01-31",
"openai/o3-mini-high",
"openai/o3-mini",
"openai/o3",
"openai/o4-mini-2025-04-16",
"openai/o4-mini",
"perplexity/r1-1776",
"perplexity/r1",
"xai/grok-2-1212",
"xai/grok-2",
"xai/grok-3-beta",
"xai/grok-3-fast-beta",
"xai/grok-3-mini-beta",
"xai/grok-3-mini-fast-beta",
"xai/grok-beta",
"xai/grok"
]

9
src/lib/data/groq.json Normal file
View file

@ -0,0 +1,9 @@
[
"llama-3.1-8b-instant",
"llama-3.3-70b-versatile",
"meta-llama/llama-4-scout-17b-16e-instruct",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"mistral-saba-24b",
"qwen-qwq-32b",
"deepseek-r1-distill-llama-70b"
]

View file

@ -0,0 +1,8 @@
[
"codestral-latest",
"mistral-large-latest",
"ministral-8b-latest",
"ministral-3b-latest",
"mistral-small-latest",
"pixtral-large-latest"
]

View file

@ -0,0 +1,20 @@
[
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4.1-nano",
"o3",
"o3-high",
"o3-low",
"o4-mini",
"o4-mini-high",
"o4-mini-low",
"o3-mini",
"o3-mini-high",
"o3-mini-low",
"o1",
"o1-preview",
"o1-mini",
"gpt-4.5-preview",
"gpt-4o",
"gpt-4o-mini"
]

View file

@ -0,0 +1,321 @@
[
"aetherwiing/mn-starcannon-12b",
"agentica-org/deepcoder-14b-preview:free",
"ai21/jamba-1.6-large",
"ai21/jamba-1.6-mini",
"ai21/jamba-instruct",
"aion-labs/aion-1.0-mini",
"aion-labs/aion-1.0",
"aion-labs/aion-rp-llama-3.1-8b",
"alfredpros/codellama-7b-instruct-solidity",
"all-hands/openhands-lm-32b-v0.1",
"allenai/olmo-7b-instruct",
"alpindale/goliath-120b",
"alpindale/magnum-72b",
"amazon/nova-lite-v1",
"amazon/nova-micro-v1",
"amazon/nova-pro-v1",
"anthracite-org/magnum-v2-72b",
"anthracite-org/magnum-v4-72b",
"anthropic/claude-2:beta",
"anthropic/claude-2.0:beta",
"anthropic/claude-2.0",
"anthropic/claude-2.1:beta",
"anthropic/claude-2.1",
"anthropic/claude-2",
"anthropic/claude-3-haiku:beta",
"anthropic/claude-3-haiku",
"anthropic/claude-3-opus:beta",
"anthropic/claude-3-opus",
"anthropic/claude-3-sonnet:beta",
"anthropic/claude-3-sonnet",
"anthropic/claude-3.5-haiku-20241022:beta",
"anthropic/claude-3.5-haiku-20241022",
"anthropic/claude-3.5-haiku:beta",
"anthropic/claude-3.5-haiku",
"anthropic/claude-3.5-sonnet-20240620:beta",
"anthropic/claude-3.5-sonnet-20240620",
"anthropic/claude-3.5-sonnet:beta",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.7-sonnet:beta",
"anthropic/claude-3.7-sonnet:thinking",
"anthropic/claude-3.7-sonnet",
"arcee-ai/arcee-blitz",
"arcee-ai/caller-large",
"arcee-ai/coder-large",
"arcee-ai/maestro-reasoning",
"arcee-ai/spotlight",
"arcee-ai/virtuoso-large",
"arcee-ai/virtuoso-medium-v2",
"arliai/qwq-32b-arliai-rpr-v1:free",
"cognitivecomputations/dolphin-mixtral-8x22b",
"cognitivecomputations/dolphin3.0-mistral-24b:free",
"cognitivecomputations/dolphin3.0-r1-mistral-24b:free",
"cohere/command-a",
"cohere/command-r-03-2024",
"cohere/command-r-08-2024",
"cohere/command-r-plus-04-2024",
"cohere/command-r-plus-08-2024",
"cohere/command-r-plus",
"cohere/command-r",
"cohere/command-r7b-12-2024",
"cohere/command",
"deepseek/deepseek-chat-v3-0324:free",
"deepseek/deepseek-chat-v3-0324",
"deepseek/deepseek-chat:free",
"deepseek/deepseek-chat",
"deepseek/deepseek-coder",
"deepseek/deepseek-prover-v2:free",
"deepseek/deepseek-prover-v2",
"deepseek/deepseek-r1-distill-llama-70b:free",
"deepseek/deepseek-r1-distill-llama-70b",
"deepseek/deepseek-r1-distill-llama-8b",
"deepseek/deepseek-r1-distill-qwen-1.5b",
"deepseek/deepseek-r1-distill-qwen-14b:free",
"deepseek/deepseek-r1-distill-qwen-14b",
"deepseek/deepseek-r1-distill-qwen-32b:free",
"deepseek/deepseek-r1-distill-qwen-32b",
"deepseek/deepseek-r1-zero:free",
"deepseek/deepseek-r1:free",
"deepseek/deepseek-r1",
"deepseek/deepseek-v3-base:free",
"eleutherai/llemma_7b",
"eva-unit-01/eva-llama-3.33-70b",
"eva-unit-01/eva-qwen-2.5-32b",
"eva-unit-01/eva-qwen-2.5-72b",
"featherless/qwerky-72b:free",
"google/gemini-2.0-flash-001",
"google/gemini-2.0-flash-exp:free",
"google/gemini-2.0-flash-lite-001",
"google/gemini-2.5-flash-preview-05-20:thinking",
"google/gemini-2.5-flash-preview-05-20",
"google/gemini-2.5-flash-preview:thinking",
"google/gemini-2.5-flash-preview",
"google/gemini-2.5-pro-exp-03-25",
"google/gemini-2.5-pro-preview",
"google/gemini-flash-1.5-8b",
"google/gemini-flash-1.5",
"google/gemini-pro-1.5",
"google/gemma-2-27b-it",
"google/gemma-2-9b-it:free",
"google/gemma-2-9b-it",
"google/gemma-3-12b-it:free",
"google/gemma-3-12b-it",
"google/gemma-3-1b-it:free",
"google/gemma-3-27b-it:free",
"google/gemma-3-27b-it",
"google/gemma-3-4b-it:free",
"google/gemma-3-4b-it",
"gryphe/mythomax-l2-13b",
"inception/mercury-coder-small-beta",
"infermatic/mn-inferor-12b",
"inflection/inflection-3-pi",
"inflection/inflection-3-productivity",
"jondurbin/airoboros-l2-70b",
"liquid/lfm-3b",
"liquid/lfm-40b",
"liquid/lfm-7b",
"mancer/weaver",
"meta-llama/llama-2-70b-chat",
"meta-llama/llama-3-70b-instruct",
"meta-llama/llama-3-8b-instruct",
"meta-llama/llama-3.1-405b-instruct",
"meta-llama/llama-3.1-405b:free",
"meta-llama/llama-3.1-405b",
"meta-llama/llama-3.1-70b-instruct",
"meta-llama/llama-3.1-8b-instruct:free",
"meta-llama/llama-3.1-8b-instruct",
"meta-llama/llama-3.2-11b-vision-instruct:free",
"meta-llama/llama-3.2-11b-vision-instruct",
"meta-llama/llama-3.2-1b-instruct:free",
"meta-llama/llama-3.2-1b-instruct",
"meta-llama/llama-3.2-3b-instruct:free",
"meta-llama/llama-3.2-3b-instruct",
"meta-llama/llama-3.2-90b-vision-instruct",
"meta-llama/llama-3.3-70b-instruct:free",
"meta-llama/llama-3.3-70b-instruct",
"meta-llama/llama-3.3-8b-instruct:free",
"meta-llama/llama-4-maverick:free",
"meta-llama/llama-4-maverick",
"meta-llama/llama-4-scout:free",
"meta-llama/llama-4-scout",
"meta-llama/llama-guard-2-8b",
"meta-llama/llama-guard-3-8b",
"meta-llama/llama-guard-4-12b",
"microsoft/mai-ds-r1:free",
"microsoft/phi-3-medium-128k-instruct",
"microsoft/phi-3-mini-128k-instruct",
"microsoft/phi-3.5-mini-128k-instruct",
"microsoft/phi-4-multimodal-instruct",
"microsoft/phi-4-reasoning-plus:free",
"microsoft/phi-4-reasoning-plus",
"microsoft/phi-4-reasoning:free",
"microsoft/phi-4",
"microsoft/wizardlm-2-8x22b",
"minimax/minimax-01",
"mistralai/codestral-2501",
"mistralai/codestral-mamba",
"mistralai/ministral-3b",
"mistralai/ministral-8b",
"mistralai/mistral-7b-instruct-v0.1",
"mistralai/mistral-7b-instruct-v0.2",
"mistralai/mistral-7b-instruct-v0.3",
"mistralai/mistral-7b-instruct:free",
"mistralai/mistral-7b-instruct",
"mistralai/mistral-large-2407",
"mistralai/mistral-large-2411",
"mistralai/mistral-large",
"mistralai/mistral-medium-3",
"mistralai/mistral-medium",
"mistralai/mistral-nemo:free",
"mistralai/mistral-nemo",
"mistralai/mistral-saba",
"mistralai/mistral-small-24b-instruct-2501:free",
"mistralai/mistral-small-24b-instruct-2501",
"mistralai/mistral-small-3.1-24b-instruct:free",
"mistralai/mistral-small-3.1-24b-instruct",
"mistralai/mistral-small",
"mistralai/mistral-tiny",
"mistralai/mixtral-8x22b-instruct",
"mistralai/mixtral-8x7b-instruct",
"mistralai/pixtral-12b",
"mistralai/pixtral-large-2411",
"moonshotai/kimi-vl-a3b-thinking:free",
"moonshotai/moonlight-16b-a3b-instruct:free",
"neversleep/llama-3-lumimaid-70b",
"neversleep/llama-3-lumimaid-8b:extended",
"neversleep/llama-3-lumimaid-8b",
"neversleep/llama-3.1-lumimaid-70b",
"neversleep/llama-3.1-lumimaid-8b",
"neversleep/noromaid-20b",
"nothingiisreal/mn-celeste-12b",
"nousresearch/deephermes-3-llama-3-8b-preview:free",
"nousresearch/deephermes-3-mistral-24b-preview:free",
"nousresearch/hermes-2-pro-llama-3-8b",
"nousresearch/hermes-3-llama-3.1-405b",
"nousresearch/hermes-3-llama-3.1-70b",
"nousresearch/nous-hermes-2-mixtral-8x7b-dpo",
"nvidia/llama-3.1-nemotron-70b-instruct",
"nvidia/llama-3.1-nemotron-ultra-253b-v1:free",
"nvidia/llama-3.3-nemotron-super-49b-v1:free",
"nvidia/llama-3.3-nemotron-super-49b-v1",
"open-r1/olympiccoder-32b:free",
"openai/chatgpt-4o-latest",
"openai/codex-mini",
"openai/gpt-3.5-turbo-0125",
"openai/gpt-3.5-turbo-0613",
"openai/gpt-3.5-turbo-1106",
"openai/gpt-3.5-turbo-16k",
"openai/gpt-3.5-turbo-instruct",
"openai/gpt-3.5-turbo",
"openai/gpt-4-0314",
"openai/gpt-4-1106-preview",
"openai/gpt-4-32k-0314",
"openai/gpt-4-32k",
"openai/gpt-4-turbo-preview",
"openai/gpt-4-turbo",
"openai/gpt-4.1-mini",
"openai/gpt-4.1-nano",
"openai/gpt-4.1",
"openai/gpt-4.5-preview",
"openai/gpt-4",
"openai/gpt-4o-2024-05-13",
"openai/gpt-4o-2024-08-06",
"openai/gpt-4o-2024-11-20",
"openai/gpt-4o-mini-2024-07-18",
"openai/gpt-4o-mini-search-preview",
"openai/gpt-4o-mini",
"openai/gpt-4o-search-preview",
"openai/gpt-4o:extended",
"openai/gpt-4o",
"openai/o1-mini-2024-09-12",
"openai/o1-mini",
"openai/o1-preview-2024-09-12",
"openai/o1-preview",
"openai/o1-pro",
"openai/o1",
"openai/o3-mini-high",
"openai/o3-mini",
"openai/o3",
"openai/o4-mini-high",
"openai/o4-mini",
"opengvlab/internvl3-14b:free",
"opengvlab/internvl3-2b:free",
"openrouter/auto",
"perplexity/llama-3.1-sonar-large-128k-online",
"perplexity/llama-3.1-sonar-small-128k-online",
"perplexity/r1-1776",
"perplexity/sonar-deep-research",
"perplexity/sonar-pro",
"perplexity/sonar-reasoning-pro",
"perplexity/sonar-reasoning",
"perplexity/sonar",
"pygmalionai/mythalion-13b",
"qwen/qwen-2-72b-instruct",
"qwen/qwen-2.5-72b-instruct:free",
"qwen/qwen-2.5-72b-instruct",
"qwen/qwen-2.5-7b-instruct:free",
"qwen/qwen-2.5-7b-instruct",
"qwen/qwen-2.5-coder-32b-instruct:free",
"qwen/qwen-2.5-coder-32b-instruct",
"qwen/qwen-2.5-vl-7b-instruct:free",
"qwen/qwen-2.5-vl-7b-instruct",
"qwen/qwen-max",
"qwen/qwen-plus",
"qwen/qwen-turbo",
"qwen/qwen-vl-max",
"qwen/qwen-vl-plus",
"qwen/qwen2.5-coder-7b-instruct",
"qwen/qwen2.5-vl-32b-instruct:free",
"qwen/qwen2.5-vl-32b-instruct",
"qwen/qwen2.5-vl-3b-instruct:free",
"qwen/qwen2.5-vl-72b-instruct:free",
"qwen/qwen2.5-vl-72b-instruct",
"qwen/qwen3-0.6b-04-28:free",
"qwen/qwen3-1.7b:free",
"qwen/qwen3-14b:free",
"qwen/qwen3-14b",
"qwen/qwen3-235b-a22b:free",
"qwen/qwen3-235b-a22b",
"qwen/qwen3-30b-a3b:free",
"qwen/qwen3-30b-a3b",
"qwen/qwen3-32b:free",
"qwen/qwen3-32b",
"qwen/qwen3-4b:free",
"qwen/qwen3-8b:free",
"qwen/qwen3-8b",
"qwen/qwq-32b-preview",
"qwen/qwq-32b:free",
"qwen/qwq-32b",
"raifle/sorcererlm-8x22b",
"rekaai/reka-flash-3:free",
"sao10k/fimbulvetr-11b-v2",
"sao10k/l3-euryale-70b",
"sao10k/l3-lunaris-8b",
"sao10k/l3.1-euryale-70b",
"sao10k/l3.3-euryale-70b",
"scb10x/llama3.1-typhoon2-70b-instruct",
"scb10x/llama3.1-typhoon2-8b-instruct",
"shisa-ai/shisa-v2-llama3.3-70b:free",
"sophosympatheia/midnight-rose-70b",
"thedrummer/anubis-pro-105b-v1",
"thedrummer/rocinante-12b",
"thedrummer/skyfall-36b-v2",
"thedrummer/unslopnemo-12b",
"thudm/glm-4-32b:free",
"thudm/glm-4-32b",
"thudm/glm-4-9b:free",
"thudm/glm-z1-32b:free",
"thudm/glm-z1-32b",
"thudm/glm-z1-9b:free",
"thudm/glm-z1-rumination-32b",
"tngtech/deepseek-r1t-chimera:free",
"undi95/remm-slerp-l2-13b",
"undi95/toppy-m-7b",
"x-ai/grok-2-1212",
"x-ai/grok-2-vision-1212",
"x-ai/grok-3-beta",
"x-ai/grok-3-mini-beta",
"x-ai/grok-beta",
"x-ai/grok-vision-beta"
]

View file

@ -0,0 +1,230 @@
[
"alibaba/qwen-plus",
"alibaba/qwen-turbo",
"anthropic/claude-3-5-haiku-20241022",
"anthropic/claude-3-5-haiku-latest",
"anthropic/claude-3-5-sonnet-20240620",
"anthropic/claude-3-5-sonnet-20241022",
"anthropic/claude-3-5-sonnet-latest",
"anthropic/claude-3-7-sonnet-20250219",
"anthropic/claude-3-7-sonnet-latest",
"anthropic/claude-3-haiku-20240307",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-opus-latest",
"anthropic/claude-3-sonnet-20240229",
"cline/claude-3-5-sonnet:alpha-v2",
"cline/claude-3-5-sonnet:alpha",
"cline/claude-3-5-sonnet",
"cline/claude-3-7-sonnet:1024",
"cline/claude-3-7-sonnet:16384",
"cline/claude-3-7-sonnet:64000",
"cline/claude-3-7-sonnet:8192",
"cline/claude-3-7-sonnet:high",
"cline/claude-3-7-sonnet:low",
"cline/claude-3-7-sonnet:max",
"cline/claude-3-7-sonnet:medium",
"cline/claude-3-7-sonnet",
"cline/deepseek-reasoner:alpha",
"cline/o3-mini:high",
"cline/o3-mini:low",
"cline/o3-mini:medium",
"cline/o3-mini",
"coding/claude-3-7-sonnet",
"coding/gemini-2.5-pro-preview-03-25",
"coding/gemini-2.5-pro-preview-05-06",
"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
"deepinfra/deepseek-ai/DeepSeek-R1",
"deepinfra/deepseek-ai/DeepSeek-V3",
"deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct",
"deepinfra/meta-llama/Llama-3.2-90B-Vision-Instruct",
"deepinfra/meta-llama/Llama-3.3-70B-Instruct-Turbo",
"deepinfra/meta-llama/Llama-3.3-70B-Instruct",
"deepinfra/meta-llama/Meta-Llama-3.1-405B-Instruct",
"deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct",
"deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
"deepinfra/microsoft/phi-4",
"deepinfra/microsoft/WizardLM-2-8x22B",
"deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct",
"deepinfra/Qwen/Qwen2.5-72B-Instruct",
"deepinfra/Qwen/Qwen2.5-Coder-32B-Instruct",
"deepinfra/Qwen/Qwen3-235B-A22B",
"deepinfra/Qwen/Qwen3-32B",
"deepinfra/Qwen/QwQ-32B",
"deepseek/deepseek-chat",
"deepseek/deepseek-reasoner",
"google/gemini-1.5-flash-8b-latest",
"google/gemini-1.5-flash-8b",
"google/gemini-1.5-flash-latest",
"google/gemini-1.5-flash",
"google/gemini-1.5-pro-latest",
"google/gemini-1.5-pro",
"google/gemini-2.0-flash-001",
"google/gemini-2.0-flash-exp",
"google/gemini-2.0-flash-thinking-exp-01-21",
"google/gemini-2.0-pro-exp-02-05",
"google/gemini-2.5-flash-preview-04-17",
"google/gemini-2.5-pro-exp-03-25",
"google/gemini-2.5-pro-preview-03-25",
"google/gemini-2.5-pro-preview-05-06",
"google/gemma-3-27b-it",
"groq/qwen-qwq-32b",
"minimaxi/DeepSeek-R1",
"minimaxi/MiniMax-Text-01",
"mistral/mistral-large-latest",
"mistral/mistral-small-latest",
"mistral/open-mistral-7b",
"nebius/deepseek-ai/DeepSeek-R1-fast",
"nebius/deepseek-ai/DeepSeek-R1",
"nebius/deepseek-ai/DeepSeek-V3-0324-fast",
"nebius/deepseek-ai/DeepSeek-V3-0324",
"nebius/deepseek-ai/DeepSeek-V3",
"nebius/meta-llama/Llama-3.3-70B-Instruct",
"nebius/Qwen/QwQ-32B-fast",
"nebius/Qwen/QwQ-32B",
"novita/deepseek/deepseek_v3",
"novita/deepseek/deepseek-prover-v2-671b",
"novita/deepseek/deepseek-r1-distill-llama-70b",
"novita/deepseek/deepseek-r1-distill-qwen-14b",
"novita/deepseek/deepseek-r1-distill-qwen-32b",
"novita/deepseek/deepseek-r1-turbo",
"novita/deepseek/deepseek-r1",
"novita/deepseek/deepseek-v3-0324",
"novita/deepseek/deepseek-v3-turbo",
"novita/google/gemma-2-9b-it",
"novita/gryphe/mythomax-l2-13b",
"novita/jondurbin/airoboros-l2-70b",
"novita/meta-llama/llama-3-70b-instruct",
"novita/meta-llama/llama-3-8b-instruct",
"novita/meta-llama/llama-3.1-70b-instruct",
"novita/meta-llama/llama-3.1-8b-instruct-bf16",
"novita/meta-llama/llama-3.1-8b-instruct-max",
"novita/meta-llama/llama-3.1-8b-instruct",
"novita/meta-llama/llama-3.2-11b-vision-instruct",
"novita/meta-llama/llama-3.2-1b-instruct",
"novita/meta-llama/llama-3.2-3b-instruct",
"novita/meta-llama/llama-3.3-70b-instruct",
"novita/meta-llama/llama-4-maverick-17b-128e-instruct-fp8",
"novita/microsoft/wizardlm-2-8x22b",
"novita/mistralai/mistral-7b-instruct",
"novita/mistralai/mistral-nemo",
"novita/nousresearch/hermes-2-pro-llama-3-8b",
"novita/nousresearch/nous-hermes-llama2-13b",
"novita/openchat/openchat-7b",
"novita/qwen/qwen-2-7b-instruct",
"novita/qwen/qwen-2-vl-72b-instruct",
"novita/qwen/qwen-2.5-72b-instruct",
"novita/qwen/qwen2.5-vl-72b-instruct",
"novita/qwen/qwen3-235b-a22b-fp8",
"novita/qwen/qwq-32b",
"novita/sao10k/l3-70b-euryale-v2.1",
"novita/sao10k/l3-8b-lunaris",
"novita/Sao10K/L3-8B-Stheno-v3.2",
"novita/sao10k/l31-70b-euryale-v2.2",
"novita/sophosympatheia/midnight-rose-70b",
"novita/teknium/openhermes-2.5-mistral-7b",
"openai/chatgpt-4o-latest",
"openai/gpt-4.1-2025-04-14",
"openai/gpt-4.1-mini-2025-04-14",
"openai/gpt-4.1-mini",
"openai/gpt-4.1-nano-2025-04-14",
"openai/gpt-4.1-nano",
"openai/gpt-4.1",
"openai/gpt-4.5-preview",
"openai/gpt-4o-2024-05-13",
"openai/gpt-4o-2024-08-06",
"openai/gpt-4o-2024-11-20",
"openai/gpt-4o-mini-2024-07-18",
"openai/gpt-4o-mini",
"openai/gpt-4o",
"openai/o1-2024-12-17",
"openai/o1-mini-2024-09-12",
"openai/o1-mini:high",
"openai/o1-mini:low",
"openai/o1-mini:medium",
"openai/o1-mini",
"openai/o1:high",
"openai/o1:low",
"openai/o1:medium",
"openai/o1",
"openai/o3-2025-04-16",
"openai/o3-mini-2025-01-31",
"openai/o3-mini:high",
"openai/o3-mini:low",
"openai/o3-mini:medium",
"openai/o3-mini",
"openai/o4-mini-2025-04-16",
"openai/o4-mini:high",
"openai/o4-mini:low",
"openai/o4-mini:medium",
"openai/o4-mini",
"parasail/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
"parasail/meta-llama/Llama-4-Scout-17B-16E-Instruct",
"parasail/parasail-anubis-pro",
"parasail/parasail-deepseek-r1",
"parasail/parasail-eva-25-72b-v02-fp8",
"parasail/parasail-gemma3-27b-it",
"parasail/parasail-mistral-7b-instruct-03",
"parasail/parasail-mistral-nemo",
"parasail/parasail-mythomax-13b",
"parasail/parasail-qwen-coder32b-longcontext-128",
"parasail/parasail-qwen25-vl-72b-instruct",
"parasail/parasail-skyfall-36b-v2-fp8",
"parasail/parasail-wayfarer-70b-llama33-fp8",
"perplexity/sonar-pro",
"perplexity/sonar-reasoning-pro",
"perplexity/sonar",
"together/deepseek-ai/DeepSeek-R1",
"together/deepseek-ai/DeepSeek-V3",
"together/deepseek-llm-67b-chat",
"together/meta-llama/Llama-2-13b-chat-hf",
"together/meta-llama/Llama-2-70b-hf",
"together/meta-llama/Llama-2-7b-chat-hf",
"together/meta-llama/Llama-3-70b-chat-hf",
"together/meta-llama/Llama-3-8b-chat-hf",
"together/meta-llama/Llama-3.2-3B-Instruct-Turbo",
"together/meta-llama/Llama-3.3-70B-Instruct-Turbo",
"together/meta-llama/LlamaGuard-2-8b",
"together/meta-llama/Meta-Llama-3-70B-Instruct-Lite",
"together/meta-llama/Meta-Llama-3-70B-Instruct-Turbo",
"together/meta-llama/Meta-Llama-3-8B-Instruct-Lite",
"together/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
"together/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
"together/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
"together/meta-llama/Meta-Llama-Guard-3-8B",
"together/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
"together/nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
"together/Qwen/Qwen2-72B-Instruct",
"together/Qwen/Qwen2.5-72B-Instruct-Turbo",
"together/Qwen/Qwen2.5-7B-Instruct-Turbo",
"together/Qwen/Qwen2.5-Coder-32B-Instruct",
"together/Qwen/QwQ-32B-Preview",
"together/upstage/SOLAR-10.7B-Instruct-v1.0",
"vertex/anthropic/claude-3-5-sonnet-20241022",
"vertex/anthropic/claude-3-5-sonnet-20241022@europe-west1",
"vertex/anthropic/claude-3-5-sonnet-20241022@us-east5",
"vertex/anthropic/claude-3-5-sonnet-latest",
"vertex/anthropic/claude-3-5-sonnet-latest@europe-west1",
"vertex/anthropic/claude-3-5-sonnet-latest@us-east5",
"vertex/anthropic/claude-3-5-sonnet",
"vertex/anthropic/claude-3-5-sonnet@europe-west1",
"vertex/anthropic/claude-3-5-sonnet@us-east5",
"vertex/anthropic/claude-3-7-sonnet-20250219",
"vertex/anthropic/claude-3-7-sonnet-20250219@europe-west1",
"vertex/anthropic/claude-3-7-sonnet-20250219@us-east5",
"vertex/anthropic/claude-3-7-sonnet-latest",
"vertex/anthropic/claude-3-7-sonnet-latest@europe-west1",
"vertex/anthropic/claude-3-7-sonnet-latest@us-east5",
"vertex/anthropic/claude-3-7-sonnet",
"vertex/anthropic/claude-3-7-sonnet@europe-west1",
"vertex/anthropic/claude-3-7-sonnet@us-east5",
"xai/grok-2-1212",
"xai/grok-2-latest",
"xai/grok-3-beta",
"xai/grok-3-fast-beta",
"xai/grok-3-mini-beta:high",
"xai/grok-3-mini-beta:low",
"xai/grok-3-mini-beta",
"xai/grok-3-mini-fast-beta:high",
"xai/grok-3-mini-fast-beta:low",
"xai/grok-3-mini-fast-beta"
]

41
src/lib/data/unbound.json Normal file
View file

@ -0,0 +1,41 @@
[
"anthropic/claude-3-5-haiku-20241022",
"anthropic/claude-3-5-sonnet-20241022",
"anthropic/claude-3-7-sonnet-20250219",
"anthropic/claude-3-haiku-20240307",
"anthropic/claude-3-opus-20240229",
"anthropic/claude-3-sonnet-20240229",
"bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0",
"bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
"dashscope/qwen2.5-7b-instruct",
"dashscope/qwen2.5-vl-72b-instruct",
"google/gemini-1.5-flash",
"google/gemini-2.0-flash",
"google/gemini-2.0-flash-lite",
"google/gemini-2.0-pro-exp-02-05",
"google/gemini-2.5-flash-preview-04-17",
"google/gemini-2.5-pro-exp-03-25",
"google/gemini-2.5-pro-preview-05-06",
"meta/meta-llama-3.3-70b-instruct",
"mistral-ai/codestral-latest",
"openai/gpt-3.5-turbo",
"openai/gpt-4.1",
"openai/gpt-4.1-mini",
"openai/gpt-4.1-nano",
"openai/gpt-4.5-preview",
"openai/gpt-4o",
"openai/gpt-4o-mini",
"openai/o1-mini",
"openai/o1-preview",
"openai/o3",
"openai/o3-mini",
"openai/o4-mini",
"vertex-ai/custom.deepseek-distill-llama-3-8b-instruct",
"vertex-ai/deepseek-distill-llama-3-8b-instruct",
"x-ai/grok-2-latest",
"x-ai/grok-3-beta",
"x-ai/grok-3-fast-beta",
"x-ai/grok-3-mini-beta",
"x-ai/grok-3-mini-fast-beta",
"x-ai/grok-beta"
]

22
src/lib/data/vertex.json Normal file
View file

@ -0,0 +1,22 @@
[
"gemini-2.5-flash-preview-05-20:thinking",
"gemini-2.5-flash-preview-05-20",
"gemini-2.5-flash-preview-04-17:thinking",
"gemini-2.5-flash-preview-04-17",
"gemini-2.5-pro-preview-03-25",
"gemini-2.5-pro-preview-05-06",
"gemini-2.5-pro-exp-03-25",
"gemini-2.0-pro-exp-02-05",
"gemini-2.0-flash-001",
"gemini-2.0-flash-lite-001",
"gemini-2.0-flash-thinking-exp-01-21",
"gemini-1.5-flash-002",
"gemini-1.5-pro-002",
"claude-3-7-sonnet@20250219:thinking",
"claude-3-7-sonnet@20250219",
"claude-3-5-sonnet-v2@20241022",
"claude-3-5-sonnet@20240620",
"claude-3-5-haiku@20241022",
"claude-3-opus@20240229",
"claude-3-haiku@20240307"
]

14
src/lib/data/xai.json Normal file
View file

@ -0,0 +1,14 @@
[
"grok-3-beta",
"grok-3-fast-beta",
"grok-3-mini-beta",
"grok-3-mini-fast-beta",
"grok-2-latest",
"grok-2",
"grok-2-1212",
"grok-2-vision-latest",
"grok-2-vision",
"grok-2-vision-1212",
"grok-vision-beta",
"grok-beta"
]