import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Search, Download, Trash2, RefreshCw, Check, HardDrive, Cpu, AlertCircle, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Progress } from "@/components/ui/progress"; import { Badge } from "@/components/ui/badge"; import { useOllama } from "@/hooks/useOllama"; import { cn } from "@/lib/utils"; interface ModelStoreProps { ollamaUrl: string; onModelSelect?: (model: string) => void; selectedModel?: string; } export function ModelStore({ ollamaUrl, onModelSelect, selectedModel }: ModelStoreProps) { const [search, setSearch] = useState(""); const [view, setView] = useState<"library" | "installed">("library"); const { installedModels, isConnected, isLoading, downloadProgress, error, checkConnection, refreshModels, pullModel, deleteModel, getLibraryModels, } = useOllama(ollamaUrl); useEffect(() => { checkConnection(); }, [checkConnection]); const libraryModels = getLibraryModels(); const filteredLibrary = libraryModels.filter((m) => m.name.toLowerCase().includes(search.toLowerCase()) || m.description.toLowerCase().includes(search.toLowerCase()) || m.tags.some((t) => t.toLowerCase().includes(search.toLowerCase())) ); const filteredInstalled = installedModels.filter((m) => m.name.toLowerCase().includes(search.toLowerCase()) ); const formatSize = (bytes: number) => { const gb = bytes / (1024 * 1024 * 1024); if (gb >= 1) return `${gb.toFixed(1)} GB`; return `${(bytes / (1024 * 1024)).toFixed(0)} MB`; }; return (
{/* Header */}

Model Store

{isConnected ? "Connected" : "Offline"}
{/* Search */}
setSearch(e.target.value)} placeholder="Search models..." className="pl-9 bg-input/50 border-border" />
{/* View Toggle */}
{/* Error */} {error && (

{error}

)} {/* Content */}
{view === "library" ? ( filteredLibrary.map((model) => (

{model.name}

{model.isInstalled && ( )}

{model.description}

{model.tags.slice(0, 2).map((tag) => ( {tag} ))} {model.size && ( {model.size} )}
{downloadProgress[model.name] !== undefined ? (
{downloadProgress[model.name]}%
) : model.isInstalled ? ( ) : ( )}
)) ) : ( installedModels.length === 0 ? (

No models installed

Browse the library to download models

) : ( filteredInstalled.map((model) => (

{model.name}

{formatSize(model.size)} {model.details?.parameter_size && ( {model.details.parameter_size} )}
)) ) )}
); }