diff --git a/package.json b/package.json index b152c6fd1d..95d003dda2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b51acd20aa..0758e726f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx index 7a8f739023..6d8b6e726c 100644 --- a/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx +++ b/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx @@ -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; - -// 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( - 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(), + ), + ); + const [providerModels, setProviderModels] = useState( + fullProviderMetadata.reduce((acc, meta) => { + acc.set(meta.id, orgSettings.allowList.providers[meta.id]?.models || []); + return acc; + }, new Map()), ); 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 = {}; - - 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 = {}; - - 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 = ({
@@ -302,46 +229,49 @@ const ProviderWhitelistForm = ({ Allow All Providers
-
- {providerMetadata.map((provider) => ( +
+ {fullProviderMetadata.map((provider) => (
-
- toggleProvider(provider.id)} - /> - +
+
{provider.label}
+
+ + toggleProvider(provider.id)} + /> +
-
- {provider.models.map((model) => ( -
- - toggleModel(provider.id, model.id) - } - /> - -
- ))} +
+ ({ + 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), + ) + } + />
))} diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000000..eef8c43d44 --- /dev/null +++ b/src/components/ui/command.tsx @@ -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) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + ...props +}: React.ComponentProps & { + title?: string + description?: string +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ className, ...props }: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ ...props }: React.ComponentProps) { + return +} + +function CommandGroup({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000000..39a5187e68 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -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) { + return +} + +function DialogTrigger({ ...props }: React.ComponentProps) { + return +} + +function DialogPortal({ ...props }: React.ComponentProps) { + return +} + +function DialogClose({ ...props }: React.ComponentProps) { + return +} + +function DialogOverlay({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ className, children, ...props }: React.ComponentProps) { + return ( + + + + {children} + + + Close + + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/multiple-selector.tsx b/src/components/ui/multiple-selector.tsx new file mode 100644 index 0000000000..f5e62684c0 --- /dev/null +++ b/src/components/ui/multiple-selector.tsx @@ -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; + /** + * 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; + /** Props of `CommandInput` */ + inputProps?: Omit< + React.ComponentPropsWithoutRef, + 'value' | 'placeholder' | 'disabled' + >; + /** hide the clear all button. */ + hideClearAllButton?: boolean; +} + +export interface MultipleSelectorRef { + selectedValue: Option[]; + input: HTMLInputElement; + focus: () => void; + reset: () => void; +} + +export function useDebounce(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = React.useState(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 +>(({ className, ...props }, forwardedRef) => { + const render = useCommandState((state) => state.filtered.count === 0); + + if (!render) return null; + + return ( +
+ ); +}); + +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, + ) => { + const inputRef = React.useRef(null); + const [open, setOpen] = React.useState(false); + const [onScrollbar, setOnScrollbar] = React.useState(false); + const [isLoading, setIsLoading] = React.useState(false); + const dropdownRef = React.useRef(null); // Added this + + const [selected, setSelected] = React.useState(value || []); + const [options, setOptions] = React.useState( + 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) => { + 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 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 = ( + { + 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}"`} + + ); + + // 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 ( + + {emptyIndicator} + + ); + } + + return {emptyIndicator}; + }, [creatable, emptyIndicator, onSearch, options]); + + const selectables = React.useMemo( + () => 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 ( + { + 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()} + > +
{ + if (disabled) return; + inputRef?.current?.focus(); + }} + > +
+ {selected.map((option) => { + return ( + + {option.label} + + + ); + })} + {/* Avoid having the "Search" Icon */} + { + 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, + )} + /> + +
+
+
+ {open && ( + { + setOnScrollbar(false); + }} + onMouseEnter={() => { + setOnScrollbar(true); + }} + onMouseUp={() => { + inputRef?.current?.focus(); + }} + > + {isLoading ? ( + <>{loadingIndicator} + ) : ( + <> + {EmptyItem()} + {CreatableItem()} + {!selectFirstItem && ( + + )} + {Object.entries(selectables).map(([key, dropdowns]) => ( + + <> + {dropdowns.map((option) => { + return ( + { + 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} + + ); + })} + + + ))} + + )} + + )} +
+
+ ); + }, +); + +MultipleSelector.displayName = 'MultipleSelector'; +export default MultipleSelector; diff --git a/src/lib/data/anthropic.json b/src/lib/data/anthropic.json new file mode 100644 index 0000000000..50b071bada --- /dev/null +++ b/src/lib/data/anthropic.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/bedrock.json b/src/lib/data/bedrock.json new file mode 100644 index 0000000000..651b6d853c --- /dev/null +++ b/src/lib/data/bedrock.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/chutes.json b/src/lib/data/chutes.json new file mode 100644 index 0000000000..624f4f36ef --- /dev/null +++ b/src/lib/data/chutes.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/deepseek.json b/src/lib/data/deepseek.json new file mode 100644 index 0000000000..ba28a8940b --- /dev/null +++ b/src/lib/data/deepseek.json @@ -0,0 +1,4 @@ +[ + "deepseek-chat", + "deepseek-reasoner" +] \ No newline at end of file diff --git a/src/lib/data/gemini.json b/src/lib/data/gemini.json new file mode 100644 index 0000000000..8049893505 --- /dev/null +++ b/src/lib/data/gemini.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/glama.json b/src/lib/data/glama.json new file mode 100644 index 0000000000..4c4f4b4096 --- /dev/null +++ b/src/lib/data/glama.json @@ -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" +] diff --git a/src/lib/data/groq.json b/src/lib/data/groq.json new file mode 100644 index 0000000000..b22131b2ed --- /dev/null +++ b/src/lib/data/groq.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/mistral.json b/src/lib/data/mistral.json new file mode 100644 index 0000000000..864b17070a --- /dev/null +++ b/src/lib/data/mistral.json @@ -0,0 +1,8 @@ +[ + "codestral-latest", + "mistral-large-latest", + "ministral-8b-latest", + "ministral-3b-latest", + "mistral-small-latest", + "pixtral-large-latest" +] \ No newline at end of file diff --git a/src/lib/data/openai-native.json b/src/lib/data/openai-native.json new file mode 100644 index 0000000000..09d26fc420 --- /dev/null +++ b/src/lib/data/openai-native.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/openrouter-models.json b/src/lib/data/openrouter-models.json new file mode 100644 index 0000000000..a4a0c43eca --- /dev/null +++ b/src/lib/data/openrouter-models.json @@ -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" +] diff --git a/src/lib/data/requesty-models.json b/src/lib/data/requesty-models.json new file mode 100644 index 0000000000..886b5c6844 --- /dev/null +++ b/src/lib/data/requesty-models.json @@ -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" +] diff --git a/src/lib/data/unbound.json b/src/lib/data/unbound.json new file mode 100644 index 0000000000..fefc0f0c6c --- /dev/null +++ b/src/lib/data/unbound.json @@ -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" +] diff --git a/src/lib/data/vertex.json b/src/lib/data/vertex.json new file mode 100644 index 0000000000..79ff6db672 --- /dev/null +++ b/src/lib/data/vertex.json @@ -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" +] \ No newline at end of file diff --git a/src/lib/data/xai.json b/src/lib/data/xai.json new file mode 100644 index 0000000000..59e1079346 --- /dev/null +++ b/src/lib/data/xai.json @@ -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" +] \ No newline at end of file