feat: allow reference images for AI layer edits and relax stylistic constraints to allow for realism
Build and Deploy / build-and-push (push) Successful in 2m9s

This commit is contained in:
AI Bot
2026-10-05 15:27:59 +05:30
parent f44a07b1a9
commit 527dfba535
3 changed files with 112 additions and 21 deletions
+75 -9
View File
@@ -46,6 +46,7 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
const containerRef = useRef<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const refImageInputRef = useRef<HTMLInputElement>(null);
const dragRef = useRef<{ handle: Handle; startX: number; startY: number; layer: Layer } | null>(null);
const token = localStorage.getItem('auth_token') || '';
@@ -53,6 +54,8 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
const [isEditingLayer, setIsEditingLayer] = useState(false);
const [aiPreviewUrl, setAiPreviewUrl] = useState<string | null>(null);
const [originalLayerUrl, setOriginalLayerUrl] = useState<string | null>(null);
const [aiReferenceImage, setAiReferenceImage] = useState<File | null>(null);
const [aiReferencePreview, setAiReferencePreview] = useState<string | null>(null);
const handleAiEdit = async (regenerate = false) => {
if (!selectedId || !aiPrompt.trim()) return;
@@ -77,11 +80,40 @@ export function UVEditor({ onClose, onSave, targetObject, loadedModel, sceneObje
// Use webp to preserve transparency while significantly reducing payload size
const base64 = tempCanvas.toDataURL('image/webp', 0.8);
const fullPrompt = `The user has provided an image layer. Please apply the following edit exactly as requested, while strictly maintaining the EXACT same aspect ratio, resolution, and general structure of the original image.
let referenceBase64 = undefined;
if (aiReferenceImage) {
const reader = new FileReader();
referenceBase64 = await new Promise<string>((resolve, reject) => {
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const refCanvas = document.createElement('canvas');
let rw = img.width;
let rh = img.height;
if (rw > 1024 || rh > 1024) {
const rRatio = Math.min(1024 / rw, 1024 / rh);
rw = Math.round(rw * rRatio);
rh = Math.round(rh * rRatio);
}
refCanvas.width = rw;
refCanvas.height = rh;
const rctx = refCanvas.getContext('2d');
rctx?.drawImage(img, 0, 0, rw, rh);
resolve(refCanvas.toDataURL('image/webp', 0.8));
};
img.onerror = reject;
img.src = e.target?.result as string;
};
reader.onerror = reject;
reader.readAsDataURL(aiReferenceImage);
});
}
const fullPrompt = `The user has provided an image layer. Please apply the following edit exactly as requested, while strictly maintaining the EXACT same aspect ratio and resolution of the original image. You may change the style, aesthetic, or realism.
User Prompt: "${aiPrompt}"
Negative constraints: Do NOT change the aspect ratio. Do NOT add completely unrelated elements. Do NOT crop the image boundaries.`;
const newUrl = await editUVLayer(fullPrompt, base64, token || '');
const newUrl = await editUVLayer(fullPrompt, base64, token || '', referenceBase64);
if (!originalLayerUrl && !regenerate) {
setOriginalLayerUrl(layer.url);
@@ -108,6 +140,8 @@ Negative constraints: Do NOT change the aspect ratio. Do NOT add completely unre
setAiPreviewUrl(null);
setOriginalLayerUrl(null);
setAiPrompt("");
setAiReferenceImage(null);
setAiReferencePreview(null);
};
const handleRejectEdit = () => {
@@ -118,11 +152,23 @@ Negative constraints: Do NOT change the aspect ratio. Do NOT add completely unre
setLayers(ls => ls.map(l => l.id === selectedId ? { ...l, url: originalLayerUrl, img } : l));
setAiPreviewUrl(null);
setOriginalLayerUrl(null);
setAiReferenceImage(null);
setAiReferencePreview(null);
};
img.src = originalLayerUrl;
} else {
setAiPreviewUrl(null);
setOriginalLayerUrl(null);
setAiReferenceImage(null);
setAiReferencePreview(null);
}
};
const handleRefImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setAiReferenceImage(file);
setAiReferencePreview(URL.createObjectURL(file));
}
};
@@ -526,13 +572,33 @@ Negative constraints: Do NOT change the aspect ratio. Do NOT add completely unre
</button>
</div>
) : (
<button
onClick={() => handleAiEdit(false)}
disabled={!aiPrompt.trim()}
className="w-full py-1.5 bg-blue-500/10 hover:bg-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed rounded text-[11px] font-semibold text-blue-400 border border-blue-500/30 transition-colors flex items-center justify-center gap-1.5"
>
<span className="text-[14px] leading-none">✨</span> Generate Edit
</button>
<>
<div className="flex items-center gap-2">
<input type="file" ref={refImageInputRef} className="hidden" accept=".jpg,.jpeg,.png,.webp" onChange={handleRefImageUpload} />
<button
onClick={() => refImageInputRef.current?.click()}
className="flex-1 py-1.5 bg-zinc-800 hover:bg-zinc-700 rounded text-[10px] font-semibold text-zinc-300 border border-zinc-700 transition-colors flex items-center justify-center gap-1.5"
>
<Upload size={12} /> {aiReferencePreview ? 'Change Ref' : 'Optional Reference Image'}
</button>
{aiReferencePreview && (
<div className="relative w-7 h-7 rounded overflow-hidden border border-zinc-600 flex-shrink-0 group">
<img src={aiReferencePreview} alt="Ref" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/60 flex items-center justify-center opacity-0 group-hover:opacity-100 cursor-pointer"
onClick={() => { setAiReferenceImage(null); setAiReferencePreview(null); }}>
<Trash2 size={10} className="text-red-400" />
</div>
</div>
)}
</div>
<button
onClick={() => handleAiEdit(false)}
disabled={!aiPrompt.trim()}
className="w-full py-1.5 bg-blue-500/10 hover:bg-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed rounded text-[11px] font-semibold text-blue-400 border border-blue-500/30 transition-colors flex items-center justify-center gap-1.5"
>
<span className="text-[14px] leading-none">✨</span> Generate Edit
</button>
</>
)}
</div>
</div>