imagine / components /Gallery /ImageBatch.tsx
github-actions[bot]
GitHub deploy: 8ac466cec7cb18a3cdc40223ab11ee9b5f5f569b
e6ce630
import { useState, useRef, useEffect } from 'react';
import ImageCard from './ImageCard';
import BatchMenu from './BatchMenu';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import { Copy, Check } from 'lucide-react';
import { Button } from '../ui/button';
const formatTimestamp = (timestamp: string | undefined) => {
if (!timestamp) return '';
const date = new Date(timestamp);
if (isNaN(date.getTime())) return '';
const now = new Date();
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
const timeString = `${hours}:${minutes}`;
const dateString = date.getFullYear() === now.getFullYear()
? `${day}-${month}`
: `${day}-${month}-${year}`;
return `${timeString} ${dateString}`;
};
interface Image {
url: string;
}
interface Batch {
id: number;
prompt: string;
width: number;
height: number;
model: string;
images: Image[];
createdAt?: string;
status?: string;
}
const modelNames: { [key: string]: string } = {
'runware:100@1': 'FLUX SCHNELL',
'runware:101@1': 'FLUX DEV'
};
interface ImageBatchProps {
batch: Batch;
onDelete: (id: number) => void;
onRemix: (batch: Batch) => void;
}
export default function ImageBatch({ batch, onDelete, onRemix }: ImageBatchProps) {
const [copied, setCopied] = useState(false);
const [isPromptTruncated, setIsPromptTruncated] = useState(false);
const promptRef = useRef<HTMLParagraphElement>(null);
const modelName = modelNames[batch.model] || batch.model;
const [elapsedTime, setElapsedTime] = useState(0);
useEffect(() => {
const checkTruncation = () => {
if (promptRef.current) {
setIsPromptTruncated(
promptRef.current.scrollWidth > promptRef.current.clientWidth
);
}
};
checkTruncation();
window.addEventListener('resize', checkTruncation);
return () => {
window.removeEventListener('resize', checkTruncation);
};
}, [batch.prompt]);
useEffect(() => {
let interval: NodeJS.Timeout;
if (batch.status === 'pending') {
const startTime = new Date(batch.createdAt || Date.now()).getTime();
interval = setInterval(() => {
const now = Date.now();
setElapsedTime((now - startTime) / 1000);
}, 100);
}
return () => clearInterval(interval);
}, [batch.status, batch.createdAt]);
const handleDelete = () => {
onDelete(batch.id);
};
const handleRemix = () => {
onRemix(batch);
};
const copyToClipboard = () => {
navigator.clipboard.writeText(batch.prompt).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
};
return (
<div className="mb-4 rounded-xl p-3 bg-[#ededed] dark:bg-gray-900">
<div className="flex flex-col">
<div className="flex items-center justify-between">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<p ref={promptRef} className="text-[#141414] dark:text-white text-xs font-medium truncate cursor-default max-w-[95%]">{batch.prompt}</p>
</TooltipTrigger>
{isPromptTruncated && (
<TooltipContent side="bottom" align="center" className="max-w-md">
<p className="text-sm">{batch.prompt}</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 flex-shrink-0"
onClick={copyToClipboard}
>
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
</Button>
<BatchMenu onDelete={handleDelete} onRemix={handleRemix} />
</div>
</div>
<p className="text-[#141414] dark:text-white text-[10px] mt-0">
{modelName} | {batch.width}x{batch.height}{formatTimestamp(batch.createdAt) && ` • ${formatTimestamp(batch.createdAt)}`}
</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 mt-2">
{batch.images.map((image, index) => (
<ImageCard
key={index}
image={image}
batchImages={batch.images}
batchId={batch.id}
status={batch.status}
width={batch.width}
height={batch.height}
elapsedTime={elapsedTime}
/>
))}
</div>
{batch.status === 'error' && (
<p className="text-red-500 mt-2">Error generating images. Please try again.</p>
)}
</div>
);
}