Download src/components/kernel/KernelSelector.tsx from TechAvenger/kernel-convolution-visualizer: direct link, hf CLI and curl.
- Browser
- Download file 7.65 kB
-
https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/a371b772986b5140963410baab96fbb379877010/src/components/kernel/KernelSelector.tsx
- Command line
-
hf download hf://spaces/TechAvenger/kernel-convolution-visualizer@a371b772986b5140963410baab96fbb379877010/src/components/kernel/KernelSelector.tsx
-
curl -L -o KernelSelector.tsx https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/a371b772986b5140963410baab96fbb379877010/src/components/kernel/KernelSelector.tsx
7.65 kB
| 'use client' | |
| import { useState } from 'react' | |
| import { useAppStore } from '@/store' | |
| import { PREDEFINED_KERNELS, KERNEL_CATEGORIES, getKernelsByCategory } from '@/lib/kernels' | |
| import type { KernelCategory } from '@/lib/kernels' | |
| import { Button, Card, CardContent, Badge, Input } from '@/components/ui' | |
| import { cn } from '@/utils/helpers' | |
| import { Search, SlidersHorizontal, Plus } from 'lucide-react' | |
| export function KernelSelector() { | |
| const { selectedKernel, setSelectedKernel, customKernelMatrix, customKernelSize, customKernelNormalize, setCustomKernel, setCustomKernelSize, setCustomKernelNormalize, updateCustomKernelCell } = useAppStore() | |
| const [activeCategory, setActiveCategory] = useState<KernelCategory>('blur') | |
| const [searchQuery, setSearchQuery] = useState('') | |
| const [showCustom, setShowCustom] = useState(false) | |
| const filteredKernels = PREDEFINED_KERNELS.filter( | |
| (k) => | |
| k.name.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| k.category.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| k.description.toLowerCase().includes(searchQuery.toLowerCase()) | |
| ) | |
| const kernelsByCategory = KERNEL_CATEGORIES.map((cat) => ({ | |
| ...cat, | |
| kernels: getKernelsByCategory(cat.value).filter((k) => | |
| k.name.toLowerCase().includes(searchQuery.toLowerCase()) | |
| ), | |
| })) | |
| const handleKernelSelect = (kernel: typeof PREDEFINED_KERNELS[0]) => { | |
| setSelectedKernel(kernel) | |
| setShowCustom(false) | |
| } | |
| const handleCustomKernelSubmit = () => { | |
| const kernel = { | |
| id: `custom-${Date.now()}`, | |
| name: `Custom ${customKernelSize}×${customKernelSize}`, | |
| category: 'custom' as KernelCategory, | |
| matrix: customKernelMatrix, | |
| size: customKernelSize, | |
| normalize: customKernelNormalize, | |
| description: 'User-defined custom kernel', | |
| } | |
| setSelectedKernel(kernel) | |
| } | |
| return ( | |
| <Card className="w-full max-w-2xl"> | |
| <div className="flex items-center justify-between p-4 border-b dark:border-gray-700"> | |
| <h2 className="text-lg font-semibold">Kernel Selector</h2> | |
| <Button variant="ghost" size="icon" onClick={() => setShowCustom(!showCustom)}> | |
| <Plus className="h-4 w-4" /> | |
| </Button> | |
| </div> | |
| <CardContent className="p-4"> | |
| {/* Search */} | |
| <div className="relative mb-4"> | |
| <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" /> | |
| <input | |
| type="text" | |
| placeholder="Search kernels..." | |
| value={searchQuery} | |
| onChange={(e) => setSearchQuery(e.target.value)} | |
| className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-800 dark:text-white" | |
| /> | |
| </div> | |
| {/* Category Tabs */} | |
| <div className="flex gap-1 mb-4 overflow-x-auto pb-2"> | |
| {KERNEL_CATEGORIES.map((cat) => ( | |
| <button | |
| key={cat.value} | |
| onClick={() => setActiveCategory(cat.value)} | |
| className={cn( | |
| 'px-3 py-1.5 text-sm rounded-full whitespace-nowrap transition-colors', | |
| activeCategory === cat.value | |
| ? 'bg-blue-600 text-white' | |
| : 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800' | |
| )} | |
| > | |
| {cat.label} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Custom Kernel Editor */} | |
| {showCustom && ( | |
| <div className="mb-4 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg border dark:border-gray-700"> | |
| <h3 className="font-medium mb-3">Custom Kernel Editor</h3> | |
| <div className="flex items-center gap-4 mb-3"> | |
| <Input | |
| type="number" | |
| label="Size" | |
| value={customKernelSize} | |
| onChange={(e) => setCustomKernelSize(Math.max(3, Math.min(9, parseInt(e.target.value) || 3)))} | |
| min={3} | |
| max={9} | |
| step={2} | |
| className="w-24" | |
| /> | |
| <label className="flex items-center gap-2 text-sm"> | |
| <input | |
| type="checkbox" | |
| checked={customKernelNormalize} | |
| onChange={(e) => setCustomKernelNormalize(e.target.checked)} | |
| className="rounded border-gray-300" | |
| /> | |
| Normalize | |
| </label> | |
| </div> | |
| <div className="grid gap-1 mb-3" style={{ gridTemplateColumns: `repeat(${customKernelSize}, 1fr)` }}> | |
| {Array.from({ length: customKernelSize }, (_, row) => | |
| Array.from({ length: customKernelSize }, (_, col) => ( | |
| <input | |
| key={`${row}-${col}`} | |
| type="number" | |
| step="0.1" | |
| value={customKernelMatrix[row]?.[col] ?? 0} | |
| onChange={(e) => updateCustomKernelCell(row, col, parseFloat(e.target.value) || 0)} | |
| className="w-full h-8 text-center text-sm border border-gray-300 rounded dark:border-gray-600 dark:bg-gray-700" | |
| /> | |
| )) | |
| )} | |
| </div> | |
| <Button onClick={handleCustomKernelSubmit} className="w-full"> | |
| Apply Custom Kernel | |
| </Button> | |
| </div> | |
| )} | |
| {/* Kernel List */} | |
| <div className="space-y-3 max-h-96 overflow-y-auto"> | |
| {kernelsByCategory | |
| .filter((cat) => cat.kernels.length > 0) | |
| .map((cat) => ( | |
| <div key={cat.value} className="space-y-2"> | |
| <h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide"> | |
| {cat.label} | |
| </h4> | |
| <div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))' }}> | |
| {cat.kernels.map((kernel) => ( | |
| <button | |
| key={kernel.id} | |
| onClick={() => handleKernelSelect(kernel)} | |
| className={cn( | |
| 'p-3 text-left rounded-lg border transition-all', | |
| 'hover:border-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20', | |
| selectedKernel?.id === kernel.id | |
| ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' | |
| : 'border-gray-200 dark:border-gray-700' | |
| )} | |
| > | |
| <div className="font-medium text-sm">{kernel.name}</div> | |
| <div className="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2"> | |
| {kernel.description} | |
| </div> | |
| <div className="flex items-center gap-1 mt-2"> | |
| <Badge variant="outline" className="text-xs"> | |
| {kernel.size}×{kernel.size} | |
| </Badge> | |
| {kernel.normalize && ( | |
| <Badge variant="secondary" className="text-xs"> | |
| Normalized | |
| </Badge> | |
| )} | |
| </div> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| ))} | |
| {filteredKernels.length === 0 && ( | |
| <div className="text-center py-8 text-gray-500"> | |
| No kernels found matching “{searchQuery}” | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ) | |
| } |