Skip to content

Commit e1b4cab

Browse files
committed
feat: add gradio api integration for instantid face swap uploads
1 parent 5e8f5cf commit e1b4cab

4 files changed

Lines changed: 91 additions & 0 deletions

File tree

app.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
from fastapi.middleware.cors import CORSMiddleware
33
from pydantic import BaseModel
44
from huggingface_hub import InferenceClient
5+
from gradio_client import Client, handle_file
56
import os
67
import io
8+
import base64
9+
import tempfile
710

811
app = FastAPI()
912

@@ -28,6 +31,7 @@ class GenerateRequest(BaseModel):
2831
prompt: str
2932
negative_prompt: str = ""
3033
seed: int = None
34+
face_image_base64: str = None
3135

3236
@app.get("/")
3337
def read_root():
@@ -36,6 +40,52 @@ def read_root():
3640
@app.post("/api/generate")
3741
async def generate_image(req: GenerateRequest):
3842
try:
43+
# Branch 1: Face Swap using InstantID (Gradio API Hack)
44+
if req.face_image_base64:
45+
base64_data = req.face_image_base64
46+
if "," in base64_data:
47+
base64_data = base64_data.split(",")[1]
48+
49+
img_data = base64.b64decode(base64_data)
50+
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
51+
tmp.write(img_data)
52+
tmp_path = tmp.name
53+
54+
gradio_client = Client("InstantX/InstantID")
55+
result = gradio_client.predict(
56+
face_image_path=handle_file(tmp_path),
57+
pose_image_path=None,
58+
prompt=req.prompt,
59+
negative_prompt=req.negative_prompt,
60+
style_name="(No style)",
61+
num_steps=30,
62+
identitynet_strength_ratio=0.8,
63+
adapter_strength_ratio=0.8,
64+
canny_strength=0.4,
65+
depth_strength=0.4,
66+
controlnet_selection=["depth"],
67+
guidance_scale=5.0,
68+
seed=req.seed if req.seed is not None else 42,
69+
scheduler="EulerDiscreteScheduler",
70+
enable_LCM=False,
71+
enhance_face_region=True,
72+
api_name="/generate_image"
73+
)
74+
75+
# Extract image path from Gradio tuple response
76+
image_list = result[0] if isinstance(result, tuple) else result
77+
if isinstance(image_list, list) and len(image_list) > 0:
78+
final_img_path = image_list[0].get("path")
79+
else:
80+
final_img_path = image_list
81+
82+
with open(final_img_path, "rb") as f:
83+
img_byte_arr = f.read()
84+
85+
os.remove(tmp_path)
86+
return Response(content=img_byte_arr, media_type="image/jpeg")
87+
88+
# Branch 2: Blazing Fast Text-to-Image (FLUX.1-schnell)
3989
parameters = {}
4090
if req.negative_prompt:
4191
parameters["negative_prompt"] = req.negative_prompt

app/page.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,21 @@ export default function Home() {
66
const [prompt, setPrompt] = useState('');
77
const [negativePrompt, setNegativePrompt] = useState('ugly, bad anatomy, deformed, blurry');
88
const [seed, setSeed] = useState('');
9+
const [faceImage, setFaceImage] = useState(null);
910
const [isGenerating, setIsGenerating] = useState(false);
1011
const [generatedImage, setGeneratedImage] = useState(null);
1112

13+
const handleFileUpload = (e) => {
14+
const file = e.target.files[0];
15+
if (file) {
16+
const reader = new FileReader();
17+
reader.onloadend = () => setFaceImage(reader.result);
18+
reader.readAsDataURL(file);
19+
} else {
20+
setFaceImage(null);
21+
}
22+
};
23+
1224
const handleGenerate = async (e) => {
1325
e.preventDefault();
1426
setIsGenerating(true);
@@ -18,6 +30,7 @@ export default function Home() {
1830
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'https://purvansh01-persona-studio.hf.space';
1931
const bodyPayload = { prompt, negativePrompt };
2032
if (seed) bodyPayload.seed = parseInt(seed, 10);
33+
if (faceImage) bodyPayload.face_image_base64 = faceImage;
2134

2235
const response = await fetch(`${backendUrl}/api/generate`, {
2336
method: 'POST',
@@ -81,6 +94,25 @@ export default function Home() {
8194
{/* Controls Panel */}
8295
<div className="glass-panel">
8396
<form onSubmit={handleGenerate}>
97+
<div style={{ marginBottom: '24px' }}>
98+
<label className="input-label">Upload Reference Face (Optional)</label>
99+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
100+
<input
101+
type="file"
102+
accept="image/*"
103+
onChange={handleFileUpload}
104+
className="input-field"
105+
style={{ padding: '8px' }}
106+
/>
107+
{faceImage && (
108+
<img src={faceImage} alt="Face Reference" style={{ width: '40px', height: '40px', borderRadius: '50%', objectFit: 'cover' }} />
109+
)}
110+
</div>
111+
<p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '8px' }}>
112+
Pro Tip: Uploading a face triggers the massive InstantID GPUs. It is incredibly powerful but may take up to 60 seconds.
113+
</p>
114+
</div>
115+
84116
<div style={{ marginBottom: '24px' }}>
85117
<label className="input-label">Positive Prompt</label>
86118
<textarea

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ fastapi
22
uvicorn
33
huggingface_hub
44
Pillow
5+
gradio_client

test_gradio.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from gradio_client import Client
2+
3+
try:
4+
print("Connecting to InstantX/InstantID...")
5+
client = Client("InstantX/InstantID")
6+
print(client.view_api(return_format="dict"))
7+
except Exception as e:
8+
print("Error:", e)

0 commit comments

Comments
 (0)