Spaces:
Running
Running
File size: 10,124 Bytes
5012205 d8c725b 8c61b89 5012205 6377651 8c61b89 e87a324 5012205 264f96c 5012205 d8c725b 5012205 d8c725b 5012205 264f96c 5012205 d8c725b 5012205 d8c725b 5012205 d8c725b 5012205 d8c725b 5012205 264f96c 5012205 264f96c 5012205 264f96c 5012205 264f96c 2517ed6 5012205 264f96c 5012205 264f96c 5012205 264f96c 5012205 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
"use client";
import { MODELS, modelDetails, type modelID, defaultModel } from "@/ai/providers";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "./ui/select";
import { cn } from "@/lib/utils";
import { Sparkles, Zap, Info, Bolt, Code, Brain, Lightbulb, Image, Gauge, Rocket, Bot } from "lucide-react";
import { useState, useEffect } from "react";
interface ModelPickerProps {
selectedModel: modelID;
setSelectedModel: (model: modelID) => void;
}
export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProps) => {
const [hoveredModel, setHoveredModel] = useState<modelID | null>(null);
// Ensure we always have a valid model ID
const validModelId = MODELS.includes(selectedModel) ? selectedModel : defaultModel;
// If the selected model is invalid, update it to the default
useEffect(() => {
if (selectedModel !== validModelId) {
setSelectedModel(validModelId as modelID);
}
}, [selectedModel, validModelId, setSelectedModel]);
// Function to get the appropriate icon for each provider
const getProviderIcon = (provider: string) => {
switch (provider.toLowerCase()) {
case 'anthropic':
return <Sparkles className="h-3 w-3 text-orange-600" />;
case 'openai':
return <Zap className="h-3 w-3 text-green-500" />;
case 'google':
return <Zap className="h-3 w-3 text-red-500" />;
case 'groq':
return <Sparkles className="h-3 w-3 text-blue-500" />;
case 'xai':
return <Sparkles className="h-3 w-3 text-yellow-500" />;
default:
return <Info className="h-3 w-3 text-blue-500" />;
}
};
// Function to get capability icon
const getCapabilityIcon = (capability: string) => {
switch (capability.toLowerCase()) {
case 'code':
return <Code className="h-2.5 w-2.5" />;
case 'reasoning':
return <Brain className="h-2.5 w-2.5" />;
case 'research':
return <Lightbulb className="h-2.5 w-2.5" />;
case 'vision':
return <Image className="h-2.5 w-2.5" />;
case 'fast':
case 'rapid':
return <Bolt className="h-2.5 w-2.5" />;
case 'efficient':
case 'compact':
return <Gauge className="h-2.5 w-2.5" />;
case 'creative':
case 'balance':
return <Rocket className="h-2.5 w-2.5" />;
case 'agentic':
return <Bot className="h-2.5 w-2.5" />;
default:
return <Info className="h-2.5 w-2.5" />;
}
};
// Get capability badge color
const getCapabilityColor = (capability: string) => {
switch (capability.toLowerCase()) {
case 'code':
return "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300";
case 'reasoning':
case 'research':
return "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300";
case 'vision':
return "bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300";
case 'fast':
case 'rapid':
return "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300";
case 'efficient':
case 'compact':
return "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300";
case 'creative':
case 'balance':
return "bg-rose-100 text-rose-800 dark:bg-rose-900/30 dark:text-rose-300";
case 'agentic':
return "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300";
default:
return "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300";
}
};
// Get current model details to display
const displayModelId = hoveredModel || validModelId;
const currentModelDetails = modelDetails[displayModelId];
// Handle model change
const handleModelChange = (modelId: string) => {
if (MODELS.includes(modelId)) {
const typedModelId = modelId as modelID;
setSelectedModel(typedModelId);
}
};
return (
<div className="absolute bottom-2 left-2 z-10">
<Select
value={validModelId}
onValueChange={handleModelChange}
defaultValue={validModelId}
>
<SelectTrigger
className="max-w-[200px] sm:max-w-fit sm:w-56 px-2 sm:px-3 h-8 sm:h-9 rounded-full group border-primary/20 bg-primary/5 hover:bg-primary/10 dark:bg-primary/10 dark:hover:bg-primary/20 transition-all duration-200 ring-offset-background focus:ring-2 focus:ring-primary/30 focus:ring-offset-2"
>
<SelectValue
placeholder="Select model"
className="text-xs font-medium flex items-center gap-1 sm:gap-2 text-primary dark:text-primary-foreground"
>
<div className="flex items-center gap-1 sm:gap-2">
{getProviderIcon(modelDetails[validModelId].provider)}
<span className="font-medium truncate">{modelDetails[validModelId].name}</span>
</div>
</SelectValue>
</SelectTrigger>
<SelectContent
align="start"
className="bg-background/95 dark:bg-muted/95 backdrop-blur-sm border-border/80 rounded-lg overflow-hidden p-0 w-[280px] sm:w-[350px] md:w-[515px]"
>
<div className="grid grid-cols-1 sm:grid-cols-[120px_1fr] md:grid-cols-[200px_1fr] items-start">
{/* Model selector column */}
<div className="sm:border-r border-border/40 bg-muted/20 p-0 pr-1">
<SelectGroup className="space-y-1">
{MODELS.map((id) => {
const modelId = id as modelID;
return (
<SelectItem
key={id}
value={id}
onMouseEnter={() => setHoveredModel(modelId)}
onMouseLeave={() => setHoveredModel(null)}
className={cn(
"!px-2 sm:!px-3 py-1.5 sm:py-2 cursor-pointer rounded-md text-xs transition-colors duration-150",
"hover:bg-primary/5 hover:text-primary-foreground",
"focus:bg-primary/10 focus:text-primary focus:outline-none",
"data-[highlighted]:bg-primary/10 data-[highlighted]:text-primary",
validModelId === id && "!bg-primary/15 !text-primary font-medium"
)}
>
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-1.5">
{getProviderIcon(modelDetails[modelId].provider)}
<span className="font-medium truncate">{modelDetails[modelId].name}</span>
</div>
<span className="text-[10px] sm:text-xs text-muted-foreground">
{modelDetails[modelId].provider}
</span>
</div>
</SelectItem>
);
})}
</SelectGroup>
</div>
{/* Model details column - hidden on smallest screens, visible on sm+ */}
<div className="sm:block hidden p-2 sm:p-3 md:p-4 flex-col">
<div>
<div className="flex items-center gap-2 mb-1">
{getProviderIcon(currentModelDetails.provider)}
<h3 className="text-sm font-semibold">{currentModelDetails.name}</h3>
</div>
<div className="text-xs text-muted-foreground mb-1">
Provider: <span className="font-medium">{currentModelDetails.provider}</span>
</div>
{/* Capability badges */}
<div className="flex flex-wrap gap-1 mt-2 mb-3">
{currentModelDetails.capabilities.map((capability) => (
<span
key={capability}
className={cn(
"inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded-full font-medium",
getCapabilityColor(capability)
)}
>
{getCapabilityIcon(capability)}
<span>{capability}</span>
</span>
))}
</div>
<div className="text-xs text-foreground/90 leading-relaxed mb-3 hidden md:block">
{currentModelDetails.description}
</div>
</div>
<div className="bg-muted/40 rounded-md p-2 hidden md:block">
<div className="text-[10px] text-muted-foreground flex justify-between items-center">
<span>API Version:</span>
<code className="bg-background/80 px-2 py-0.5 rounded text-[10px] font-mono">
{currentModelDetails.apiVersion}
</code>
</div>
</div>
</div>
{/* Condensed model details for mobile only */}
<div className="p-3 sm:hidden border-t border-border/30">
<div className="flex flex-wrap gap-1 mb-2">
{currentModelDetails.capabilities.slice(0, 4).map((capability) => (
<span
key={capability}
className={cn(
"inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded-full font-medium",
getCapabilityColor(capability)
)}
>
{getCapabilityIcon(capability)}
<span>{capability}</span>
</span>
))}
{currentModelDetails.capabilities.length > 4 && (
<span className="text-[10px] text-muted-foreground">+{currentModelDetails.capabilities.length - 4} more</span>
)}
</div>
</div>
</div>
</SelectContent>
</Select>
</div>
);
};
|