Download frontend/src/ui/edit/IdentityTools.jsx from ruslanmv/HomePilot: direct link, hf CLI and curl.
- Browser
- Download file 6.81 kB
-
https://huggingface.co/spaces/ruslanmv/HomePilot/resolve/main/frontend/src/ui/edit/IdentityTools.jsx
- Command line
-
hf download hf://spaces/ruslanmv/HomePilot/frontend/src/ui/edit/IdentityTools.jsx
-
curl -L -o IdentityTools.jsx https://huggingface.co/spaces/ruslanmv/HomePilot/resolve/main/frontend/src/ui/edit/IdentityTools.jsx
6.81 kB
| /** | |
| * 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 <UserCheck size={18} className="text-cyan-400"/>; | |
| case 'inpaint_identity': | |
| return <Scan size={18} className="text-cyan-400"/>; | |
| case 'change_bg_identity': | |
| return <ImageOff size={18} className="text-cyan-400"/>; | |
| case 'face_swap': | |
| return <Repeat size={18} className="text-cyan-400"/>; | |
| default: | |
| return <Shield size={18} className="text-cyan-400"/>; | |
| } | |
| } | |
| // --------------------------------------------------------------------------- | |
| // 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 (<div className="space-y-3"> | |
| {/* Section header */} | |
| <div className="text-xs uppercase tracking-wider text-white/40 font-semibold flex items-center gap-2"> | |
| <Shield size={14}/> | |
| Identity Tools | |
| <span className="text-[8px] px-1.5 py-0.5 rounded-full bg-cyan-500/15 border border-cyan-500/20 text-cyan-300/60 font-medium normal-case tracking-normal"> | |
| Beta | |
| </span> | |
| </div> | |
| <p className="text-[10px] text-white/25 leading-relaxed -mt-1"> | |
| Edit while preserving facial identity. Uses installed Avatar & Identity models. | |
| </p> | |
| {/* Tool buttons */} | |
| <div className="grid grid-cols-1 gap-2"> | |
| {visibleTools.map((tool) => (<div key={tool.id}> | |
| <button onClick={() => handleTool(tool.id)} disabled={isDisabled || anyLoading} className={` | |
| w-full flex items-center gap-3 p-3 rounded-xl border transition-all text-left | |
| ${loading === tool.id | |
| ? 'bg-cyan-500/20 border-cyan-500/40 text-cyan-300' | |
| : isDisabled || anyLoading | |
| ? 'bg-white/5 border-white/5 text-white/30 cursor-not-allowed' | |
| : 'bg-white/5 border-white/10 text-white/80 hover:bg-cyan-500/10 hover:border-cyan-500/30 hover:text-cyan-200'} | |
| `}> | |
| <div className={` | |
| w-9 h-9 rounded-lg flex items-center justify-center | |
| ${loading === tool.id ? 'bg-cyan-500/30' : 'bg-white/10'} | |
| `}> | |
| {loading === tool.id ? (<Loader2 size={18} className="animate-spin text-cyan-400"/>) : (getIcon(tool.id))} | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="text-sm font-medium flex items-center gap-2"> | |
| {tool.label} | |
| <span className="text-[9px] px-1.5 py-0.5 rounded bg-cyan-500/10 text-cyan-300/40"> | |
| {tool.pack === 'full' ? 'Full Pack' : 'Identity'} | |
| </span> | |
| </div> | |
| <div className="text-[10px] text-white/40 truncate"> | |
| {tool.description} | |
| </div> | |
| </div> | |
| </button> | |
| {/* Face Swap reference input — shown inline below the Face Swap button */} | |
| {tool.id === 'face_swap' && showFaceSwapRef && (<FaceSwapRefInput backendUrl={backendUrl} apiKey={apiKey} onReferenceReady={(url) => { | |
| setFaceSwapRefUrl(url); | |
| setShowFaceSwapRef(false); | |
| // Auto-trigger the swap immediately | |
| handleTool('face_swap', url); | |
| }} onCancel={() => setShowFaceSwapRef(false)}/>)} | |
| </div>))} | |
| </div> | |
| </div>); | |
| } | |