import { Link } from 'react-router'; import { useSearchParams } from 'react-router'; import { Button } from '@/components/ui/button'; import { fileUrl } from '../lib/api'; import type { GenerationRow } from '../lib/api'; import { Lightbox } from './Lightbox'; import { useLightboxManager } from './useLightboxManager'; import { useGalleryManager } from './useGalleryManager'; export const Gallery = () => { const manager = useGalleryManager(); // Hooks run before any early return, or the order changes between renders. const lightbox = useLightboxManager(manager.rows); const [searchParams] = useSearchParams(); if (manager.loading) return

Loading…

; if (manager.error) return

{manager.error}

; if (manager.rows.length === 0) { return

Nothing generated yet.

; } return (

{manager.succeeded.length} image{manager.succeeded.length === 1 ? '' : 's'} {manager.totalCost > 0 && ` · $${manager.totalCost.toFixed(4)} total`}

{manager.rows.map((row) => ( ))}
); }; const Tile = ({ row, searchParams }: { row: GenerationRow; searchParams: URLSearchParams }) => { const src = fileUrl(row.url); const to = (() => { const next = new URLSearchParams(searchParams); next.set('selected', row.id); return { search: `?${next.toString()}` }; })(); return (
{src ? ( // A real link, not a button with an onClick — the selection lives in the URL // (`?selected=`), so this is a navigation and it earns cmd-click, middle-click and // focus-visible for free. {row.prompt} ) : ( {row.error ?? row.status} )}

{row.prompt}

{row.model} {row.cost_usd && ` · $${Number(row.cost_usd).toFixed(4)}`} {row.duration_ms != null && ` · ${(row.duration_ms / 1000).toFixed(1)}s`}

); };