Template
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react';
|
|
import {
|
|
ModelSelector,
|
|
ModelSelectorContent,
|
|
ModelSelectorEmpty,
|
|
ModelSelectorGroup,
|
|
ModelSelectorInput,
|
|
ModelSelectorItem,
|
|
ModelSelectorList,
|
|
ModelSelectorName,
|
|
ModelSelectorTrigger,
|
|
} from '@/components/ai-elements/model-selector';
|
|
import { Button } from '@/components/ui/button';
|
|
import { openRouterModels, type OpenRouterModelId } from '@/lib/openrouter';
|
|
|
|
const providers: Record<string, string> = {
|
|
anthropic: 'Anthropic',
|
|
openai: 'OpenAI',
|
|
google: 'Google',
|
|
'x-ai': 'SpaceXAI',
|
|
'z-ai': 'Z.ai',
|
|
moonshotai: 'MoonshotAI',
|
|
minimax: 'MiniMax',
|
|
deepseek: 'DeepSeek',
|
|
meta: 'Meta',
|
|
qwen: 'Qwen',
|
|
};
|
|
|
|
const modelGroups = Object.entries(providers).map(([id, name]) => ({
|
|
name,
|
|
models: openRouterModels.filter((model) => model.id.startsWith(`${id}/`)),
|
|
}));
|
|
|
|
interface ChatModelSelectorProps {
|
|
value: OpenRouterModelId;
|
|
onChange: (modelId: OpenRouterModelId) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function ChatModelSelector({ value, onChange, disabled }: ChatModelSelectorProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const selectedModel = openRouterModels.find((model) => model.id === value);
|
|
|
|
return (
|
|
<ModelSelector open={open} onOpenChange={setOpen}>
|
|
<ModelSelectorTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
className="min-w-0 max-w-[min(16rem,calc(100vw-10rem))] text-muted-foreground"
|
|
aria-label={`Select model, current model: ${selectedModel?.name}`}
|
|
disabled={disabled}
|
|
>
|
|
<ModelSelectorName>{selectedModel?.name}</ModelSelectorName>
|
|
<RiArrowDownSLine className="size-4 shrink-0" aria-hidden="true" />
|
|
</Button>
|
|
</ModelSelectorTrigger>
|
|
<ModelSelectorContent title="Choose a model" className="max-w-[calc(100%-2rem)] sm:max-w-lg">
|
|
<ModelSelectorInput placeholder="Search models..." aria-label="Search models" className="pr-10" />
|
|
<ModelSelectorList className="max-h-[min(24rem,60dvh)]">
|
|
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
|
|
{modelGroups.map((group) => (
|
|
<ModelSelectorGroup heading={group.name} key={group.name}>
|
|
{group.models.map((model) => (
|
|
<ModelSelectorItem
|
|
key={model.id}
|
|
value={model.id}
|
|
keywords={[model.name, group.name]}
|
|
onSelect={() => {
|
|
onChange(model.id);
|
|
setOpen(false);
|
|
}}
|
|
>
|
|
<ModelSelectorName>{model.name}</ModelSelectorName>
|
|
{model.id === value && (
|
|
<>
|
|
<RiCheckLine className="ml-auto size-4" aria-hidden="true" />
|
|
<span className="sr-only">Selected</span>
|
|
</>
|
|
)}
|
|
</ModelSelectorItem>
|
|
))}
|
|
</ModelSelectorGroup>
|
|
))}
|
|
</ModelSelectorList>
|
|
</ModelSelectorContent>
|
|
</ModelSelector>
|
|
);
|
|
}
|