Template
Improve chat navigation, model selection, and streaming
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user