shadimage
paceui-gsap
uiai-modal-selectorsource: ok · hit

Modal Selector

AI-focused model selector with preview, tooltips, and capability indicators built-in

npx shadcn add "https://gsap.paceui.com/r/ai-modal-selector.json"
free

Where it goes

testimonial

Interaction

modaltooltipdropdown

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
animationgsap

Registry dependencies

@paceui-gsap/swapbutton

Files1 · source cached 10/8/2026, 12:45:14 PM

components/gsap/ai-modal-selector.tsx
"use client";

import { ComponentProps, ReactNode, useMemo, useState } from "react";

import {
    BrainIcon,
    CodeIcon,
    EyeIcon,
    FileSearch2Icon,
    LanguagesIcon,
    LayersIcon,
    SpeechIcon,
    VariableIcon,
    VideoIcon,
    Wand2Icon,
    WrenchIcon,
} from "lucide-react";

import { cn } from "@/lib/utils";

import { Swap } from "@/components/gsap/swap";
import { Button } from "@/components/ui/button";
import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

type ModalCapability =
    | "vision"
    | "thinking"
    | "code"
    | "speech"
    | "search"
    | "tools"
    | "multimodal"
    | "video"
    | "math"
    | "multilingual"
    | "creativity";

export type ModalOption = {
    value: string;
    image: string;
    name: string;
    darkInvertImage?: boolean;
    description: string;
    capabilities: ModalCapability[];
};

type ModalSelectorProps = ComponentProps<typeof Button> & {
    showCapabilities?: boolean;
    showDescription?: boolean;
};

const modals: ModalOption[] = [
    {
        value: "openai-gpt-4o",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/openai.svg",
        name: "GPT-4o",
        darkInvertImage: true,
        description: "Omni model supporting seamless text, audio, and vision integration.",
        capabilities: ["vision", "code", "speech", "tools", "multimodal"],
    },
    {
        value: "google-gemini-2-0-pro",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/gemini-color.svg",
        name: "Gemini 2.0 Pro",
        description: "Next-generation performance with ultra-long context windows.",
        capabilities: ["vision", "thinking", "speech", "tools", "video"],
    },
    {
        value: "anthropic-claude-3-5-sonnet",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/claude-color.svg",
        name: "Claude 3.5 Sonnet",
        description: "Industry-leading intelligence with a focus on coding and nuance.",
        capabilities: ["thinking", "code", "vision", "tools"],
    },
    {
        value: "xai-grok-3",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/grok.svg",
        name: "Grok-3",
        darkInvertImage: true,
        description: "Real-time information processing with enhanced logical reasoning.",
        capabilities: ["thinking", "search", "code"],
    },
    {
        value: "deepseek-v3",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/deepseek-color.svg",
        name: "DeepSeek-V3",
        description: "High-performance Mixture-of-Experts model for advanced logic.",
        capabilities: ["thinking", "code", "math"],
    },
    {
        value: "meta-llama-3-1-405b",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/meta-color.svg",
        name: "Llama 3.1 (405B)",
        description: "The premier open-source model for complex reasoning and scale.",
        capabilities: ["thinking", "code", "tools"],
    },
    {
        value: "mistral-large-latest",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/mistral-color.svg",
        name: "Mistral Large",
        description: "European powerhouse optimized for multilingual reasoning.",
        capabilities: ["thinking", "code", "multilingual"],
    },
    {
        value: "stability-sd3-ultra",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/stability-color.svg",
        name: "Stable Diffusion 3 Ultra",
        description: "State-of-the-art text-to-image with superior prompt adherence.",
        capabilities: ["vision", "creativity"],
    },
    {
        value: "midjourney-v6-1",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/midjourney.svg",
        name: "Midjourney v6.1",
        darkInvertImage: true,
        description: "Highly aesthetic and photorealistic image generation.",
        capabilities: ["vision", "creativity"],
    },
    {
        value: "perplexity-sonar-pro",
        image: "https://unpkg.com/@lobehub/icons-static-svg@latest/icons/perplexity-color.svg",
        name: "Sonar Pro",
        description: "Search-centric model designed for cited, real-time factual answers.",
        capabilities: ["search", "thinking"],
    },
];

const CapabilityBadge = ({ capability }: { capability: ModalCapability }) => {
    const styles: Record<ModalCapability, { color: string; text: string; icon: ReactNode }> = {
        thinking: {
            color: "bg-purple-500/10 hover:bg-purple-500/20 text-purple-500",
            text: "Thinking",
            icon: <BrainIcon className="size-4.5" />,
        },
        code: {
            color: "bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-500",
            text: "Code",
            icon: <CodeIcon className="size-4.5" />,
        },
        vision: {
            color: "bg-green-500/10 hover:bg-green-500/20 text-green-500",
            text: "Vision",
            icon: <EyeIcon className="size-4.5" />,
        },
        creativity: {
            color: "bg-orange-500/10 hover:bg-orange-500/20 text-orange-500",
            text: "Creativity",
            icon: <Wand2Icon className="size-4.5" />,
        },
        search: {
            color: "bg-teal-500/10 hover:bg-teal-500/20 text-teal-500",
            text: "Search",
            icon: <FileSearch2Icon className="size-4.5" />,
        },
        speech: {
            color: "bg-pink-500/10 hover:bg-pink-500/20 text-pink-500",
            text: "Speech",
            icon: <SpeechIcon className="size-4.5" />,
        },
        tools: {
            color: "bg-blue-500/10 hover:bg-blue-500/20 text-blue-500",
            text: "Tools",
            icon: <WrenchIcon className="size-4.5" />,
        },
        video: {
            color: "bg-rose-500/10 hover:bg-rose-500/20 text-rose-500",
            text: "Video",
            icon: <VideoIcon className="size-4.5" />,
        },
        multilingual: {
            color: "bg-amber-500/10 hover:bg-amber-500/20 text-amber-500",
            text: "Global",
            icon: <LanguagesIcon className="size-4.5" />,
        },
        multimodal: {
            color: "bg-indigo-500/10 hover:bg-indigo-500/20 text-indigo-500",
            text: "Multimodal",
            icon: <LayersIcon className="size-4.5" />,
        },
        math: {
            color: "bg-slate-500/10 hover:bg-slate-500/20 text-slate-500",
            text: "Math",
            icon: <VariableIcon className="size-4.5" />,
        },
    };

    const defaultStyle = {
        color: "bg-purple-500/10 hover:bg-purple-500/20 text-purple-500",
        text: "Thinking",
        icon: <BrainIcon className="size-4.5" />,
    };

    const { color, text, icon } = styles[capability] ?? defaultStyle;

    return (
        <Tooltip>
            <TooltipTrigger render={<div className={cn("rounded p-1", color)}>{icon}</div>}></TooltipTrigger>
            <TooltipContent>
                <p>{text}</p>
            </TooltipContent>
        </Tooltip>
    );
};

export const AiModalSelector = ({
    showDescription = true,
    showCapabilities = true,
    variant = "outline",
    className,
    ...props
}: ModalSelectorProps) => {
    const [selectedModal, setSelectedModal] = useState(modals[0].value);

    const selectedModalItem = useMemo(() => {
        return modals.find((item) => item.value === selectedModal) ?? modals[0];
    }, [selectedModal]);

    return (
        <TooltipProvider>
            <DropdownMenu>
                <DropdownMenuTrigger
                    render={
                        <Button
                            {...props}
                            variant={variant}
                            className={cn("cursor-pointer overflow-hidden shadow-none select-none", className)}>
                            <Swap state={selectedModalItem} effects={["blur", "slideDown", "opacity"]}>
                                {(state) => (
                                    <div className="flex items-center gap-2.5">
                                        <img
                                            src={state.image}
                                            className={cn("min-w-4.5", { "dark:invert": state.darkInvertImage })}
                                            alt={`${state.name} logo`}
                                        />
                                        <p className="text-base overflow-ellipsis">{state.name}</p>
                                        {showCapabilities && (
                                            <div className="flex items-center gap-1.5">
                                                {state.capabilities.map((capability) => (
                                                    <CapabilityBadge capability={capability} key={capability} />
                                                ))}
                                            </div>
                                        )}
                                    </div>
                                )}
                            </Swap>
                        </Button>
                    }></DropdownMenuTrigger>
                <DropdownMenuContent className="w-80 shadow-xs transition-all hover:shadow-md" align="start">
                    <ScrollArea className="h-80">
                        <div className="space-y-0.5">
                            {modals.map((item) => (
                                <DropdownMenuItem
                                    key={item.value}
                                    onClick={() => setSelectedModal(item.value)}
                                    className={cn("group relative cursor-pointer gap-3", {
                                        "bg-accent": selectedModal === item.value,
                                    })}>
                                    <img
                                        src={item.image}
                                        alt={`${item.name} logo`}
                                        className={cn("size-6", {
                                            "dark:invert": item.darkInvertImage,
                                        })}
                                    />
                                    <div className="grow">
                                        <div className="flex items-center gap-2">
                                            <p className="grow text-base/none font-medium">{item.name}</p>
                                        </div>
                                        {showDescription && (
                                            <p className="text-muted-foreground mt-0.5 line-clamp-1">
                                                {item.description}
                                            </p>
                                        )}
                                    </div>
                                    {showCapabilities && (
                                        <div className="absolute end-0 flex translate-x-5 scale-90 items-center gap-1.5 rounded bg-inherit px-2.5 py-1 opacity-0 transition-all duration-300 group-hover:translate-x-0 group-hover:scale-100 group-hover:opacity-100">
                                            {item.capabilities.map((capability) => (
                                                <CapabilityBadge capability={capability} key={capability} />
                                            ))}
                                        </div>
                                    )}
                                </DropdownMenuItem>
                            ))}
                        </div>
                    </ScrollArea>
                </DropdownMenuContent>
            </DropdownMenu>
        </TooltipProvider>
    );
};

More from paceui-gsap

Browse all components in this library →