Files
ai-toolkit/ui/src/components/SampleImageCard.tsx
Jaret Burkett 5b5aadadb8 Add LTX-2 Support (#644)
* WIP, adding support for LTX2

* Training on images working

* Fix loading comfy models

* Handle converting and deconverting lora so it matches original format

* Reworked ui to habdle ltx and propert dataset default overwriting.

* Update the way lokr saves to it is more compatable with comfy

* Audio loading and synchronization/resampling is working

* Add audio to training. Does it work? Maybe, still testing.

* Fixed fps default issue for sound

* Have ui set fps for accurate audio mapping on ltx

* Added audio procession options to the ui for ltx

* Clean up requirements
2026-01-13 04:55:30 -07:00

101 lines
2.8 KiB
TypeScript

import React, { useRef, useEffect, useState, ReactNode } from 'react';
import { isVideo } from '@/utils/basic';
interface SampleImageCardProps {
imageUrl: string;
alt: string;
numSamples: number;
sampleImages: string[];
children?: ReactNode;
className?: string;
onDelete?: () => void;
onClick?: () => void;
/** pass your scroll container element (e.g. containerRef.current) */
observerRoot?: Element | null;
/** optional: tweak pre-load buffer */
rootMargin?: string; // default '200px 0px'
}
const SampleImageCard: React.FC<SampleImageCardProps> = ({
imageUrl,
alt,
numSamples,
sampleImages,
children,
className = '',
onClick = () => {},
observerRoot = null,
rootMargin = '200px 0px',
}) => {
const cardRef = useRef<HTMLDivElement>(null);
const videoRef = useRef<HTMLVideoElement | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [loaded, setLoaded] = useState(false);
// Observe both enter and exit
useEffect(() => {
const el = cardRef.current;
if (!el) return;
const observer = new IntersectionObserver(
entries => {
for (const entry of entries) {
if (entry.target === el) {
setIsVisible(entry.isIntersecting);
}
}
},
{
root: observerRoot ?? null,
threshold: 0.01,
rootMargin,
},
);
observer.observe(el);
return () => observer.disconnect();
}, [observerRoot, rootMargin]);
const handleLoad = () => setLoaded(true);
return (
<div className={`flex flex-col ${className}`}>
<div ref={cardRef} className="relative w-full cursor-pointer" style={{ paddingBottom: '100%' }} onClick={onClick}>
<div className="absolute inset-0 rounded-t-lg shadow-md">
{isVisible ? (
isVideo(imageUrl) ? (
<video
ref={videoRef}
src={`/api/img/${encodeURIComponent(imageUrl)}`}
className="w-full h-full object-cover"
preload="none"
onLoad={handleLoad}
playsInline
muted
loop
autoPlay
controls={false}
/>
) : (
<img
src={`/api/img/${encodeURIComponent(imageUrl)}`}
alt={alt}
onLoad={handleLoad}
loading="lazy"
decoding="async"
className={`w-full h-full object-cover transition-opacity duration-300 ${
loaded ? 'opacity-100' : 'opacity-0'
}`}
/>
)
) : null}
{children && isVisible && <div className="absolute inset-0 flex items-center justify-center">{children}</div>}
</div>
</div>
</div>
);
};
export default SampleImageCard;