feat: Add Clean Image (AI) button to pre-process images before Meshy 3D generation
Build and Deploy / build-and-push (push) Successful in 1m11s
Build and Deploy / build-and-push (push) Successful in 1m11s
This commit is contained in:
@@ -5,6 +5,7 @@ import { fal } from '@fal-ai/client';
|
||||
import { cn } from '../lib/utils';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { api } from '../lib/api';
|
||||
import { editUVLayer } from '../lib/api/apiUtils';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { OrbitControls, Stage } from '@react-three/drei';
|
||||
import { GLTFLoader } from 'three-stdlib';
|
||||
@@ -46,7 +47,7 @@ const ModelPreview = ({ url }: { url: string }) => {
|
||||
};
|
||||
|
||||
export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose, onPushToApp }) => {
|
||||
const { profile, setProfile } = useAuthStore();
|
||||
const { profile, setProfile, token } = useAuthStore();
|
||||
const [apiProvider, setApiProvider] = useState<'fal' | 'tripo' | 'meshy'>('fal');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [tripoApiKey, setTripoApiKey] = useState('');
|
||||
@@ -72,6 +73,7 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
const [progressMessage, setProgressMessage] = useState('');
|
||||
const [generatedModelUrl, setGeneratedModelUrl] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isCleaningImage, setIsCleaningImage] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isValidatingKey, setIsValidatingKey] = useState(false);
|
||||
const [keyValidationStatus, setKeyValidationStatus] = useState<'idle' | 'valid' | 'invalid' | 'error'>('idle');
|
||||
@@ -137,6 +139,29 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
}
|
||||
};
|
||||
|
||||
const handleCleanImage = async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!imageFile || !token) return;
|
||||
|
||||
setIsCleaningImage(true);
|
||||
try {
|
||||
const base64 = await fileToDataUrl(imageFile);
|
||||
const newUrl = await editUVLayer("Remove all text, labels, and logos from this object, leaving a clean plain surface of the exact same shape.", base64, token);
|
||||
|
||||
const res = await fetch(newUrl);
|
||||
const blob = await res.blob();
|
||||
const newFile = new File([blob], "cleaned-image.png", { type: "image/png" });
|
||||
|
||||
setImageFile(newFile);
|
||||
setImagePreview(newUrl);
|
||||
} catch (err: any) {
|
||||
alert("Failed to clean image: " + err.message);
|
||||
} finally {
|
||||
setIsCleaningImage(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Utility to convert file to data URL for Fal API if needed
|
||||
const fileToDataUrl = (file: File): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -453,8 +478,16 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
{imagePreview ? (
|
||||
<>
|
||||
<img src={imagePreview} alt="Preview" className="absolute inset-0 w-full h-full object-contain p-2" />
|
||||
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity">
|
||||
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 flex flex-col items-center justify-center gap-3 transition-opacity">
|
||||
<span className="text-white text-xs font-medium flex items-center gap-2"><Upload size={14}/> Change Image</span>
|
||||
<button
|
||||
onClick={handleCleanImage}
|
||||
disabled={isCleaningImage}
|
||||
className="px-3 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white rounded text-xs font-bold flex items-center gap-2 transition-colors shadow-lg shadow-black/50"
|
||||
>
|
||||
{isCleaningImage ? <Loader2 size={12} className="animate-spin" /> : null}
|
||||
Clean Image (AI)
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
|
||||
Reference in New Issue
Block a user