/**
* IdentityTools - Optional identity-aware edit buttons for Edit Studio.
*
* Additive + non-destructive:
* - Only rendered when avatar identity models are installed
* - Does NOT replace or modify existing Quick Enhance / Background tools
* - Controlled by parent via `show` / capability booleans
*
* Basic Pack (InsightFace + InstantID):
* - Fix Faces+ (identity-aware face restoration)
* - Inpaint (Preserve Person)
* - Change BG (Preserve Person)
*
* Full Pack (+ InSwapper):
* - Face Swap (NOW FUNCTIONAL — uses ReActor/InSwapper workflow)
*/
import React, { useState } from 'react';
import { Loader2, UserCheck, Scan, ImageOff, Repeat, Shield } from 'lucide-react';
import { applyIdentityTool, IDENTITY_TOOLS, } from '../enhance/identityApi';
import { FaceSwapRefInput } from './FaceSwapRefInput';
// ---------------------------------------------------------------------------
// Icon map
// ---------------------------------------------------------------------------
function getIcon(id) {
switch (id) {
case 'fix_faces_identity':
return ;
case 'inpaint_identity':
return ;
case 'change_bg_identity':
return ;
case 'face_swap':
return ;
default:
return ;
}
}
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
export function IdentityTools({ backendUrl, apiKey, imageUrl, onResult, onError, disabled = false, hasBasicIdentity, hasFaceSwap, maskDataUrl, }) {
const [loading, setLoading] = useState(null);
// Face Swap reference input state (additive)
const [showFaceSwapRef, setShowFaceSwapRef] = useState(false);
const [faceSwapRefUrl, setFaceSwapRefUrl] = useState(null);
// Don't render at all if no identity models installed
if (!hasBasicIdentity)
return null;
const anyLoading = loading !== null;
const isDisabled = disabled || !imageUrl;
const handleTool = async (toolType, referenceUrl) => {
if (!imageUrl || anyLoading || isDisabled)
return;
// For face_swap, require a reference image — show picker if not set
if (toolType === 'face_swap' && !referenceUrl && !faceSwapRefUrl) {
setShowFaceSwapRef(true);
return;
}
const effectiveRefUrl = referenceUrl || faceSwapRefUrl;
setLoading(toolType);
try {
const result = await applyIdentityTool({
backendUrl,
apiKey,
imageUrl,
toolType,
referenceImageUrl: toolType === 'face_swap' ? effectiveRefUrl || undefined : undefined,
maskDataUrl: toolType === 'inpaint_identity' && maskDataUrl ? maskDataUrl : undefined,
});
const resultUrl = result?.media?.images?.[0];
if (resultUrl) {
onResult(resultUrl, toolType);
// Reset face swap state after successful swap
if (toolType === 'face_swap') {
setFaceSwapRefUrl(null);
setShowFaceSwapRef(false);
}
}
else {
onError('Identity tool completed but no image was returned.');
}
}
catch (e) {
onError(e instanceof Error ? e.message : 'Identity tool failed');
}
finally {
setLoading(null);
}
};
// Filter visible tools based on installed models
const visibleTools = IDENTITY_TOOLS.filter((tool) => {
if (tool.pack === 'full')
return hasFaceSwap;
return true; // basic pack tools always visible when hasBasicIdentity
});
return (
{/* Section header */}
Identity Tools
Beta
Edit while preserving facial identity. Uses installed Avatar & Identity models.
{/* Tool buttons */}
{visibleTools.map((tool) => (
{/* Face Swap reference input — shown inline below the Face Swap button */}
{tool.id === 'face_swap' && showFaceSwapRef && (
{
setFaceSwapRefUrl(url);
setShowFaceSwapRef(false);
// Auto-trigger the swap immediately
handleTool('face_swap', url);
}} onCancel={() => setShowFaceSwapRef(false)}/>)}
))}
);
}