├── App ├── .bolt │ ├── config.json │ ├── ignore │ └── prompt ├── .eslintrc.json ├── .gitignore ├── app │ ├── globals.css │ ├── layout.tsx │ ├── metadata.ts │ └── page.tsx ├── components.json ├── components │ ├── FileUploader.tsx │ ├── FolderList.tsx │ ├── ImageGrid.tsx │ ├── ImageLibrary.tsx │ ├── ImageMetadata.tsx │ ├── Pagination.tsx │ ├── SearchBar.tsx │ ├── SocialLinks.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ └── tooltip.tsx ├── hooks │ ├── use-folder-store.ts │ └── use-toast.ts ├── lib │ ├── utils.ts │ └── utils │ │ └── metadata.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── tailwind.config.ts └── tsconfig.json ├── LICENSE ├── README.md └── Start.bat /App/.bolt/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "nextjs-shadcn" 3 | } 4 | -------------------------------------------------------------------------------- /App/.bolt/ignore: -------------------------------------------------------------------------------- 1 | components/ui/* 2 | hooks/use-toast.ts 3 | -------------------------------------------------------------------------------- /App/.bolt/prompt: -------------------------------------------------------------------------------- 1 | For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. 2 | 3 | When using client-side hooks (useState and useEffect) in a component that's being treated as a Server Component by Next.js, always add the "use client" directive at the top of the file. 4 | 5 | Do not write code that will trigger this error: "Warning: Extra attributes from the server: %s%s""class,style" 6 | 7 | By default, this template supports JSX syntax with Tailwind CSS classes, the shadcn/ui library, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. 8 | 9 | Use icons from lucide-react for logos. 10 | 11 | Use stock photos from unsplash where appropriate, only valid URLs you know exist. 12 | -------------------------------------------------------------------------------- /App/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /App/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /App/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | @layer base { 20 | :root { 21 | --background: 0 0% 100%; 22 | --foreground: 0 0% 3.9%; 23 | --card: 0 0% 100%; 24 | --card-foreground: 0 0% 3.9%; 25 | --popover: 0 0% 100%; 26 | --popover-foreground: 0 0% 3.9%; 27 | --primary: 0 0% 9%; 28 | --primary-foreground: 0 0% 98%; 29 | --secondary: 0 0% 96.1%; 30 | --secondary-foreground: 0 0% 9%; 31 | --muted: 0 0% 96.1%; 32 | --muted-foreground: 0 0% 45.1%; 33 | --accent: 0 0% 96.1%; 34 | --accent-foreground: 0 0% 9%; 35 | --destructive: 0 84.2% 60.2%; 36 | --destructive-foreground: 0 0% 98%; 37 | --border: 0 0% 89.8%; 38 | --input: 0 0% 89.8%; 39 | --ring: 0 0% 3.9%; 40 | --chart-1: 12 76% 61%; 41 | --chart-2: 173 58% 39%; 42 | --chart-3: 197 37% 24%; 43 | --chart-4: 43 74% 66%; 44 | --chart-5: 27 87% 67%; 45 | --radius: 0.5rem; 46 | } 47 | .dark { 48 | --background: 0 0% 3.9%; 49 | --foreground: 0 0% 98%; 50 | --card: 0 0% 3.9%; 51 | --card-foreground: 0 0% 98%; 52 | --popover: 0 0% 3.9%; 53 | --popover-foreground: 0 0% 98%; 54 | --primary: 0 0% 98%; 55 | --primary-foreground: 0 0% 9%; 56 | --secondary: 0 0% 14.9%; 57 | --secondary-foreground: 0 0% 98%; 58 | --muted: 0 0% 14.9%; 59 | --muted-foreground: 0 0% 63.9%; 60 | --accent: 0 0% 14.9%; 61 | --accent-foreground: 0 0% 98%; 62 | --destructive: 0 62.8% 30.6%; 63 | --destructive-foreground: 0 0% 98%; 64 | --border: 0 0% 14.9%; 65 | --input: 0 0% 14.9%; 66 | --ring: 0 0% 83.1%; 67 | --chart-1: 220 70% 50%; 68 | --chart-2: 160 60% 45%; 69 | --chart-3: 30 80% 55%; 70 | --chart-4: 280 65% 60%; 71 | --chart-5: 340 75% 55%; 72 | } 73 | } 74 | 75 | @layer base { 76 | * { 77 | @apply border-border; 78 | } 79 | body { 80 | @apply bg-background text-foreground; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /App/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | import { Inter } from 'next/font/google'; 3 | import { ThemeProvider } from "next-themes"; 4 | import { metadata } from './metadata'; 5 | 6 | const inter = Inter({ subsets: ['latin'] }); 7 | 8 | export { metadata }; 9 | 10 | export default function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return ( 16 | 17 | 18 | 24 | {children} 25 | 26 | 27 | 28 | ); 29 | } -------------------------------------------------------------------------------- /App/app/metadata.ts: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | 3 | export const metadata: Metadata = { 4 | title: 'ComfyUI Image Library', 5 | description: 'A library for ComfyUI generated images', 6 | }; -------------------------------------------------------------------------------- /App/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import dynamic from 'next/dynamic'; 4 | 5 | const ImageLibrary = dynamic(() => import('@/components/ImageLibrary'), { 6 | ssr: false 7 | }); 8 | 9 | export default function Home() { 10 | return ; 11 | } -------------------------------------------------------------------------------- /App/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /App/components/FileUploader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FolderOpen } from "lucide-react"; 4 | import { Button } from "@/components/ui/button"; 5 | 6 | interface FileUploaderProps { 7 | onFolderSelect: (folder: any) => void; 8 | } 9 | 10 | export function FileUploader({ onFolderSelect }: FileUploaderProps) { 11 | const handleFolderSelect = async () => { 12 | try { 13 | if (typeof window === 'undefined') return; 14 | 15 | // Check if the API is available 16 | if (!('showDirectoryPicker' in window)) { 17 | throw new Error('File System Access API is not supported in this environment'); 18 | } 19 | 20 | // @ts-ignore - FileSystemDirectoryHandle is experimental 21 | const dirHandle = await window.showDirectoryPicker({ 22 | mode: 'read' 23 | }); 24 | onFolderSelect(dirHandle); 25 | } catch (err) { 26 | console.error("Error selecting folder:", err); 27 | // Handle the error gracefully 28 | if (err instanceof Error && err.name === 'SecurityError') { 29 | alert('Unable to access files. This feature may not be available in the current environment.'); 30 | } 31 | } 32 | }; 33 | 34 | return ( 35 | 42 | ); 43 | } -------------------------------------------------------------------------------- /App/components/FolderList.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ScrollArea } from "@/components/ui/scroll-area"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Folder, Trash2 } from "lucide-react"; 6 | import { cn } from "@/lib/utils"; 7 | 8 | interface FolderListProps { 9 | folders: Array<{ name: string; path: string }>; 10 | currentFolder: string | null; 11 | onFolderSelect: (path: string) => void; 12 | onFolderDelete: (path: string) => void; 13 | } 14 | 15 | export function FolderList({ 16 | folders, 17 | currentFolder, 18 | onFolderSelect, 19 | onFolderDelete 20 | }: FolderListProps) { 21 | return ( 22 | 23 |
24 | {folders.map((folder) => ( 25 |
26 | 37 | 45 |
46 | ))} 47 |
48 |
49 | ); 50 | } -------------------------------------------------------------------------------- /App/components/ImageGrid.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AspectRatio } from "@/components/ui/aspect-ratio"; 4 | import { ScrollArea } from "@/components/ui/scroll-area"; 5 | import Image from "next/image"; 6 | import { useState } from "react"; 7 | import { Skeleton } from "@/components/ui/skeleton"; 8 | 9 | interface ImageGridProps { 10 | images: Array<{ 11 | path: string; 12 | name: string; 13 | }>; 14 | onImageSelect: (image: any) => void; 15 | } 16 | 17 | export function ImageGrid({ images, onImageSelect }: ImageGridProps) { 18 | const [loadingStates, setLoadingStates] = useState<{ [key: string]: boolean }>({}); 19 | const [errorStates, setErrorStates] = useState<{ [key: string]: boolean }>({}); 20 | 21 | const handleImageLoad = (imagePath: string) => { 22 | setLoadingStates(prev => ({ ...prev, [imagePath]: false })); 23 | }; 24 | 25 | const handleImageError = (imagePath: string) => { 26 | setLoadingStates(prev => ({ ...prev, [imagePath]: false })); 27 | setErrorStates(prev => ({ ...prev, [imagePath]: true })); 28 | }; 29 | 30 | return ( 31 | 32 |
33 | {images.map((image, index) => ( 34 |
!errorStates[image.path] && onImageSelect(image)} 38 | > 39 | 40 | {!errorStates[image.path] ? ( 41 | <> 42 | {(loadingStates[image.path] !== false) && ( 43 | 44 | )} 45 | {image.name} handleImageLoad(image.path)} 53 | onError={() => handleImageError(image.path)} 54 | /> 55 | 56 | ) : ( 57 |
58 |

Failed to load

59 |
60 | )} 61 |
62 |
63 | ))} 64 |
65 |
66 | ); 67 | } -------------------------------------------------------------------------------- /App/components/ImageLibrary.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import { FolderList } from './FolderList'; 5 | import { ImageGrid } from './ImageGrid'; 6 | import { ImageMetadata } from './ImageMetadata'; 7 | import { SocialLinks } from './SocialLinks'; 8 | import { Button } from '@/components/ui/button'; 9 | import { Input } from '@/components/ui/input'; 10 | import { FolderOpen, Search, SortAsc, SortDesc } from 'lucide-react'; 11 | import { useFolderStore } from '@/hooks/use-folder-store'; 12 | import { readImageMetadata } from '@/lib/utils'; 13 | import { Pagination } from './Pagination'; 14 | 15 | const IMAGES_PER_PAGE = 50; 16 | 17 | export default function ImageLibrary() { 18 | const [currentFolder, setCurrentFolder] = useState(null); 19 | const [images, setImages] = useState([]); 20 | const [selectedImage, setSelectedImage] = useState(null); 21 | const [searchQuery, setSearchQuery] = useState(''); 22 | const [sortAscending, setSortAscending] = useState(false); 23 | const [currentPage, setCurrentPage] = useState(1); 24 | const [isLoading, setIsLoading] = useState(false); 25 | const { 26 | folders, 27 | addFolder, 28 | removeFolder, 29 | updateFolderHandle, 30 | updateLastAccessed, 31 | getLastAccessedFolder 32 | } = useFolderStore(); 33 | 34 | const handleFolderSelect = async (folderHandle: FileSystemDirectoryHandle) => { 35 | try { 36 | setIsLoading(true); 37 | const images = []; 38 | for await (const entry of folderHandle.values()) { 39 | if (entry.kind === 'file' && entry.name.toLowerCase().endsWith('.png')) { 40 | const metadata = await readImageMetadata(entry); 41 | const file = await entry.getFile(); 42 | images.push({ 43 | path: URL.createObjectURL(file), 44 | metadata, 45 | name: entry.name, 46 | lastModified: file.lastModified, 47 | }); 48 | } 49 | } 50 | setImages(images); 51 | setCurrentPage(1); 52 | } catch (error) { 53 | console.error('Error reading folder:', error); 54 | } finally { 55 | setIsLoading(false); 56 | } 57 | }; 58 | 59 | const openFolder = async () => { 60 | try { 61 | const handle = await window.showDirectoryPicker(); 62 | const folder = { 63 | name: handle.name, 64 | path: handle.name, 65 | handle, 66 | }; 67 | addFolder(folder); 68 | setCurrentFolder(folder.path); 69 | await handleFolderSelect(handle); 70 | } catch (error) { 71 | console.error('Error selecting folder:', error); 72 | } 73 | }; 74 | 75 | const handleFolderDelete = (path: string) => { 76 | if (currentFolder === path) { 77 | setImages([]); 78 | setCurrentFolder(null); 79 | } 80 | removeFolder(path); 81 | }; 82 | 83 | const handleStoredFolderSelect = async (path: string) => { 84 | try { 85 | const folder = folders.find((f) => f.path === path); 86 | if (!folder) return; 87 | 88 | setCurrentFolder(path); 89 | updateLastAccessed(path); 90 | let handle = folder.handle; 91 | 92 | if (!handle) { 93 | try { 94 | handle = await window.showDirectoryPicker({ 95 | id: path, 96 | startIn: 'desktop', 97 | }); 98 | updateFolderHandle(path, handle); 99 | } catch (error) { 100 | console.error('Error getting folder handle:', error); 101 | return; 102 | } 103 | } 104 | 105 | // Verify permission 106 | try { 107 | await handle.requestPermission({ mode: 'read' }); 108 | await handleFolderSelect(handle); 109 | } catch (error) { 110 | console.error('Permission denied, requesting new handle:', error); 111 | try { 112 | handle = await window.showDirectoryPicker({ 113 | id: path, 114 | startIn: 'desktop', 115 | }); 116 | updateFolderHandle(path, handle); 117 | await handleFolderSelect(handle); 118 | } catch (innerError) { 119 | console.error('Error selecting folder:', innerError); 120 | } 121 | } 122 | } catch (error) { 123 | console.error('Error accessing folder:', error); 124 | } 125 | }; 126 | 127 | // Auto-load last accessed folder on mount 128 | useEffect(() => { 129 | const loadLastFolder = async () => { 130 | const lastFolder = getLastAccessedFolder(); 131 | if (lastFolder?.path) { 132 | await handleStoredFolderSelect(lastFolder.path); 133 | } 134 | }; 135 | loadLastFolder(); 136 | }, []); 137 | 138 | const filteredAndSortedImages = images 139 | .filter((image) => { 140 | if (!searchQuery) return true; 141 | const query = searchQuery.toLowerCase(); 142 | return ( 143 | image.metadata?.toLowerCase().includes(query) || 144 | image.name.toLowerCase().includes(query) 145 | ); 146 | }) 147 | .sort((a, b) => { 148 | const modifier = sortAscending ? 1 : -1; 149 | return (a.lastModified - b.lastModified) * modifier; 150 | }); 151 | 152 | const totalPages = Math.ceil(filteredAndSortedImages.length / IMAGES_PER_PAGE); 153 | const paginatedImages = filteredAndSortedImages.slice( 154 | (currentPage - 1) * IMAGES_PER_PAGE, 155 | currentPage * IMAGES_PER_PAGE 156 | ); 157 | 158 | useEffect(() => { 159 | // Reset to first page when search query changes 160 | setCurrentPage(1); 161 | }, [searchQuery]); 162 | 163 | return ( 164 |
165 | 182 |
183 |
184 |
185 |
186 |
194 |
195 | 208 |
209 |
210 |
211 |
212 | 216 |
217 | {totalPages > 1 && ( 218 | 223 | )} 224 |
225 | {selectedImage && ( 226 | setSelectedImage(null)} 229 | /> 230 | )} 231 |
232 | ); 233 | } -------------------------------------------------------------------------------- /App/components/ImageMetadata.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { ScrollArea } from "@/components/ui/scroll-area"; 5 | import { Copy, FileImage, X } from "lucide-react"; 6 | import Image from "next/image"; 7 | import { useEffect, useState } from "react"; 8 | 9 | interface ImageMetadataProps { 10 | image: { 11 | path: string; 12 | name: string; 13 | metadata: any; 14 | }; 15 | onClose: () => void; 16 | } 17 | 18 | interface ImageDimensions { 19 | width: number; 20 | height: number; 21 | } 22 | 23 | export function ImageMetadata({ image, onClose }: ImageMetadataProps) { 24 | const [imageDimensions, setImageDimensions] = useState(null); 25 | 26 | useEffect(() => { 27 | const img = new window.Image(); 28 | img.onload = () => { 29 | setImageDimensions({ 30 | width: img.naturalWidth, 31 | height: img.naturalHeight 32 | }); 33 | }; 34 | img.src = image.path; 35 | }, [image.path]); 36 | 37 | const copyToClipboard = (text: string) => { 38 | navigator.clipboard.writeText(text); 39 | }; 40 | 41 | const parseMetadata = (metadata: any) => { 42 | try { 43 | if (!metadata) return null; 44 | 45 | const data = typeof metadata === 'string' ? 46 | JSON.parse(metadata.trim()) : 47 | metadata; 48 | 49 | // Find prompt from different possible locations 50 | let prompt = ""; 51 | 52 | // Check for direct text input (new format) 53 | if (data["113"]?.inputs?.text) { 54 | prompt = data["113"].inputs.text; 55 | } else { 56 | // Find prompt node (CLIPTextEncode with non-empty text) 57 | const promptNode = Object.entries(data).find(([, value]: [string, any]) => 58 | value?.class_type === "CLIPTextEncode" && 59 | value?.inputs?.text && 60 | !value?.inputs?.text?.includes("negative") && 61 | value?.inputs?.text?.length > 0 62 | ); 63 | if (promptNode) { 64 | prompt = promptNode[1].inputs.text; 65 | } 66 | } 67 | 68 | // Find model node 69 | const modelNode = Object.entries(data).find(([, value]: [string, any]) => 70 | value?.class_type === "UnetLoaderGGUF" || 71 | value?.class_type === "CheckpointLoaderSimple" || 72 | value?.class_type === "UNETLoader" 73 | ); 74 | 75 | // Find LoRA nodes 76 | const loraNames = []; 77 | Object.entries(data).forEach(([, value]: [string, any]) => { 78 | if (value?.class_type === "Power Lora Loader (rgthree)") { 79 | Object.entries(value.inputs).forEach(([key, val]: [string, any]) => { 80 | if (key.startsWith('lora_') && val?.on && val?.lora) { 81 | loraNames.push(val.lora); 82 | } 83 | }); 84 | } else if (value?.class_type === "LoraLoader|pysssss") { 85 | if (value.inputs?.lora_name?.content) { 86 | loraNames.push(value.inputs.lora_name.content); 87 | } 88 | } else if (value?.inputs?.lora_name && typeof value.inputs.lora_name === 'string') { 89 | loraNames.push(value.inputs.lora_name); 90 | } 91 | }); 92 | 93 | // Find seed 94 | const samplerNode = Object.entries(data).find(([, value]: [string, any]) => 95 | value?.class_type === "KSampler" && 96 | value?.inputs?.seed 97 | ); 98 | 99 | return { 100 | prompt, 101 | modelName: 102 | modelNode?.[1]?.inputs?.unet_name || 103 | modelNode?.[1]?.inputs?.ckpt_name || 104 | "", 105 | loraNames: [...new Set(loraNames)], // Remove duplicates 106 | seed: samplerNode?.[1]?.inputs?.seed || "" 107 | }; 108 | } catch (error) { 109 | console.error('Error parsing metadata:', error); 110 | return null; 111 | } 112 | }; 113 | 114 | const metadata = parseMetadata(image.metadata); 115 | 116 | return ( 117 |
118 |
119 |

Image Details

120 | 123 |
124 | 125 |
126 |
127 | {image.name} 133 |
134 | 135 |
136 |
137 |
147 | 148 | {metadata ? ( 149 | <> 150 |
151 |
152 |

Prompt

153 | {metadata.prompt && ( 154 | 161 | )} 162 |
163 |

164 | {metadata.prompt || "No prompt available"} 165 |

166 |
167 | 168 |
169 |

Model

170 |

171 | {metadata.modelName || "No model information available"} 172 |

173 |
174 | 175 |
176 |

LoRA Models

177 |
178 | {metadata.loraNames?.length > 0 ? ( 179 | metadata.loraNames.map((lora, index) => ( 180 |

181 | {lora} 182 |

183 | )) 184 | ) : ( 185 |

No LoRA models used

186 | )} 187 |
188 |
189 | 190 |
191 |

Seed

192 |

193 | {metadata.seed || "Seed not available"} 194 |

195 |
196 | 197 | ) : ( 198 |

No metadata available for this image

199 | )} 200 |
201 |
202 |
203 |
204 | ); 205 | } -------------------------------------------------------------------------------- /App/components/Pagination.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { ChevronLeft, ChevronRight } from "lucide-react"; 5 | 6 | interface PaginationProps { 7 | currentPage: number; 8 | totalPages: number; 9 | onPageChange: (page: number) => void; 10 | } 11 | 12 | export function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) { 13 | return ( 14 |
15 |
16 | 25 |
26 | Page {currentPage} of {totalPages} 27 |
28 | 37 |
38 |
39 | ); 40 | } -------------------------------------------------------------------------------- /App/components/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Search } from "lucide-react"; 4 | import { Input } from "@/components/ui/input"; 5 | 6 | interface SearchBarProps { 7 | value: string; 8 | onChange: (value: string) => void; 9 | } 10 | 11 | export function SearchBar({ value, onChange }: SearchBarProps) { 12 | return ( 13 |
14 |
15 | 16 | onChange(e.target.value)} 21 | className="flex-1" 22 | /> 23 |
24 |
25 | ); 26 | } -------------------------------------------------------------------------------- /App/components/SocialLinks.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Instagram, Youtube, Globe, Twitter } from "lucide-react"; 5 | 6 | export function SocialLinks() { 7 | return ( 8 |
9 |

10 | ComfyGallery made by PixelAILabs.com. For more information, visit the links below. 11 |

12 | 13 | 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /App/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as AccordionPrimitive from '@radix-ui/react-accordion'; 5 | import { ChevronDown } from 'lucide-react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | const Accordion = AccordionPrimitive.Root; 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )); 21 | AccordionItem.displayName = 'AccordionItem'; 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180', 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )); 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )); 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName; 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; 59 | -------------------------------------------------------------------------------- /App/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | import { buttonVariants } from '@/components/ui/button'; 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root; 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger; 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal; 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )); 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )); 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ); 60 | AlertDialogHeader.displayName = 'AlertDialogHeader'; 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ); 74 | AlertDialogFooter.displayName = 'AlertDialogFooter'; 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )); 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )); 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName; 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )); 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )); 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | }; 142 | -------------------------------------------------------------------------------- /App/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cva, type VariantProps } from 'class-variance-authority'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | const alertVariants = cva( 7 | 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', 8 | { 9 | variants: { 10 | variant: { 11 | default: 'bg-background text-foreground', 12 | destructive: 13 | 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: 'default', 18 | }, 19 | } 20 | ); 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )); 33 | Alert.displayName = 'Alert'; 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )); 45 | AlertTitle.displayName = 'AlertTitle'; 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )); 57 | AlertDescription.displayName = 'AlertDescription'; 58 | 59 | export { Alert, AlertTitle, AlertDescription }; 60 | -------------------------------------------------------------------------------- /App/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'; 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root; 6 | 7 | export { AspectRatio }; 8 | -------------------------------------------------------------------------------- /App/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as AvatarPrimitive from '@radix-ui/react-avatar'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )); 21 | Avatar.displayName = AvatarPrimitive.Root.displayName; 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )); 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )); 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 49 | 50 | export { Avatar, AvatarImage, AvatarFallback }; 51 | -------------------------------------------------------------------------------- /App/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cva, type VariantProps } from 'class-variance-authority'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | const badgeVariants = cva( 7 | 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', 13 | secondary: 14 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 15 | destructive: 16 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', 17 | outline: 'text-foreground', 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: 'default', 22 | }, 23 | } 24 | ); 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ); 34 | } 35 | 36 | export { Badge, badgeVariants }; 37 | -------------------------------------------------------------------------------- /App/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { ChevronRight, MoreHorizontal } from 'lucide-react'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<'nav'> & { 10 | separator?: React.ReactNode; 11 | } 12 | >(({ ...props }, ref) =>