done
This commit is contained in:
parent
c2f57a5193
commit
aedb21ad75
344
main.py
344
main.py
@ -3,7 +3,7 @@ import json
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Request
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
@ -28,6 +28,8 @@ if not GEMINI_API_KEY:
|
|||||||
client = genai.Client(api_key=GEMINI_API_KEY)
|
client = genai.Client(api_key=GEMINI_API_KEY)
|
||||||
IMAGE_MODEL = "gemini-2.5-flash-image"
|
IMAGE_MODEL = "gemini-2.5-flash-image"
|
||||||
|
|
||||||
|
AUTH_KEY = os.getenv("AIFIT_AUTH_KEY")
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# (Optional) CORS if you call it from frontend directly
|
# (Optional) CORS if you call it from frontend directly
|
||||||
@ -63,6 +65,7 @@ def health():
|
|||||||
|
|
||||||
@app.post("/api/try-on/", summary="Try-on (user_photo + outfit_photo + metadata)")
|
@app.post("/api/try-on/", summary="Try-on (user_photo + outfit_photo + metadata)")
|
||||||
async def try_on(
|
async def try_on(
|
||||||
|
request: Request,
|
||||||
user_photo: UploadFile = File(...),
|
user_photo: UploadFile = File(...),
|
||||||
outfit_photo: UploadFile = File(...),
|
outfit_photo: UploadFile = File(...),
|
||||||
metadata: str = Form("", description="JSON or text describing the outfit items"),
|
metadata: str = Form("", description="JSON or text describing the outfit items"),
|
||||||
@ -70,10 +73,16 @@ async def try_on(
|
|||||||
"""
|
"""
|
||||||
metadata can be:
|
metadata can be:
|
||||||
- free text: 'black hoodie, patterned shirt, white shorts'
|
- free text: 'black hoodie, patterned shirt, white shorts'
|
||||||
- JSON: '{"outer": "hoodie", "inner": "shirt", "bottom": "shorts"}'
|
- JSON: '{"request_id": "uuid", "outer_layer": {...}, "bottom": {"type": "shorts"}}'
|
||||||
- any additional instructions
|
- any additional instructions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# ---- AUTH CHECK (simple shared key) ----
|
||||||
|
if AUTH_KEY:
|
||||||
|
client_key = request.headers.get("X-AIFIT-Key")
|
||||||
|
if not client_key or client_key != AUTH_KEY:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid or missing authentication key")
|
||||||
|
|
||||||
# ---- Read files ----
|
# ---- Read files ----
|
||||||
user_bytes = await user_photo.read()
|
user_bytes = await user_photo.read()
|
||||||
outfit_bytes = await outfit_photo.read()
|
outfit_bytes = await outfit_photo.read()
|
||||||
@ -83,91 +92,248 @@ async def try_on(
|
|||||||
if not outfit_bytes:
|
if not outfit_bytes:
|
||||||
raise HTTPException(status_code=400, detail="outfit_photo is empty")
|
raise HTTPException(status_code=400, detail="outfit_photo is empty")
|
||||||
|
|
||||||
# ---- Parse metadata if JSON ----
|
# ---- Parse metadata (JSON or plain text) + extract request_id + dynamic rules ----
|
||||||
metadata_text = ""
|
metadata_text = ""
|
||||||
try:
|
request_id = None
|
||||||
meta = json.loads(metadata)
|
extra_rules = ""
|
||||||
metadata_text = json.dumps(meta, indent=2)
|
|
||||||
except:
|
|
||||||
metadata_text = metadata.strip()
|
|
||||||
|
|
||||||
# ---- Build Prompt ----
|
if metadata:
|
||||||
|
try:
|
||||||
|
meta = json.loads(metadata)
|
||||||
|
metadata_text = json.dumps(meta, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Try common key names for the UUID
|
||||||
|
request_id = meta.get("request_id") or meta.get("id") or meta.get("uuid")
|
||||||
|
|
||||||
|
# Dynamic behavior based on bottom type
|
||||||
|
bottom = meta.get("bottom") or {}
|
||||||
|
bottom_type = (bottom.get("type") or "").lower()
|
||||||
|
|
||||||
|
if bottom_type == "shorts":
|
||||||
|
# Special rule to force removal of long pants and show legs
|
||||||
|
extra_rules = """
|
||||||
|
SPECIAL RULE FOR SHORTS (INSANELY STRICT, DO NOT IGNORE):
|
||||||
|
|
||||||
|
- Metadata says the bottom garment is SHORTS.
|
||||||
|
- You MUST COMPLETELY REMOVE ANY LONG PANTS OR TROUSERS from the FIRST image.
|
||||||
|
- ZERO pants are allowed to remain. No black fabric, no shadows shaped like pants,
|
||||||
|
no ghost textures. If any trace of the old pants is left, the result is WRONG.
|
||||||
|
- From the hem of the shorts down, the legs must be rendered as NATURAL, BARE LEGS
|
||||||
|
(with socks/shoes if present) with correct skin tone, shading, lighting, and anatomy.
|
||||||
|
- Only the shorts, socks and shoes may cover the legs.
|
||||||
|
- You are FORBIDDEN to stack shorts on top of pants. Shorts must fully replace pants.
|
||||||
|
- Shorts must match the SECOND image in color, length, silhouette and fit.
|
||||||
|
"""
|
||||||
|
except Exception:
|
||||||
|
metadata_text = metadata.strip()
|
||||||
|
|
||||||
|
# ---- Build Prompt (new, strict, merged version) ----
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
EDIT TASK — FULL OUTFIT REPLACEMENT
|
You are performing a **strict outfit replacement operation** on the FIRST image using the clothing and accessories from the SECOND image and the metadata provided.
|
||||||
|
|
||||||
FIRST image = user body/mirror selfie
|
⚠️ IMPORTANT SAFETY CONTEXT
|
||||||
SECOND image = outfit reference
|
- The individuals in the images are **not public figures**.
|
||||||
|
- The task is standard, allowed **fashion editing / virtual try-on** only.
|
||||||
|
- No face, body, or identity manipulation is permitted.
|
||||||
|
|
||||||
Follow these rules:
|
-----------------------------------------------------
|
||||||
|
🚨 ULTRA-HARD IDENTITY LOCK (ABSOLUTE RULES, DO NOT BREAK)
|
||||||
|
|
||||||
1. Edit ONLY the body/clothing of the FIRST image.
|
These identity rules are STRONGER than all other instructions:
|
||||||
|
|
||||||
2. Replace ALL clothing with the items visible in the SECOND image (outfit photo).
|
- The person in the FIRST image is the ONLY person you are allowed to show in the final image.
|
||||||
|
- You MUST keep the FIRST image person's:
|
||||||
|
• face
|
||||||
|
• identity
|
||||||
|
• skin tone
|
||||||
|
• facial structure
|
||||||
|
• hair style and color
|
||||||
|
• body proportions
|
||||||
|
• pose
|
||||||
|
EXACTLY as they are.
|
||||||
|
|
||||||
3. Keep the user's:
|
- You are **FORBIDDEN** to:
|
||||||
- face
|
• change the face or body of the person in the FIRST image,
|
||||||
- skin
|
• beautify, smooth, reshape, de-age, re-gender, or re-style the face,
|
||||||
- hands
|
• swap the person with someone else,
|
||||||
- phone
|
• blend or mix the FIRST and SECOND person.
|
||||||
- pose
|
|
||||||
- environment
|
|
||||||
EXACTLY the same.
|
|
||||||
|
|
||||||
4. OUTFIT PHOTO AND METADATA CONSIDERATION (CRITICAL):
|
If there is ANY conflict between clothing instructions and identity protection:
|
||||||
|
YOU MUST PROTECT THE FIRST PERSON'S IDENTITY and only adjust clothing areas.
|
||||||
You MUST consider BOTH sources:
|
|
||||||
|
|
||||||
A) SECOND IMAGE (OUTFIT PHOTO):
|
|
||||||
- Carefully examine ALL clothing items visible in the outfit photo
|
|
||||||
- Transfer EVERY piece of clothing from the outfit photo to the user photo
|
|
||||||
- Pay attention to the visual details: colors, patterns, textures, styles, fit, and layering
|
|
||||||
- Ensure all visible items (tops, bottoms, outerwear, accessories, etc.) are transferred
|
|
||||||
|
|
||||||
B) METADATA:
|
|
||||||
The following metadata provides additional information about the outfit:
|
|
||||||
|
|
||||||
Metadata:
|
|
||||||
{metadata_text}
|
|
||||||
|
|
||||||
Use the metadata to:
|
|
||||||
- Identify and verify ALL items mentioned in the metadata are present in the outfit photo
|
|
||||||
- Understand the complete outfit composition and layering structure
|
|
||||||
- Ensure no items are missed - cross-reference metadata items with the outfit photo
|
|
||||||
- Apply any specific details from metadata (colors, patterns, materials, styles) that match the outfit photo
|
|
||||||
- If metadata mentions items not clearly visible in the photo, use metadata details to enhance accuracy
|
|
||||||
|
|
||||||
C) COMBINE BOTH SOURCES:
|
|
||||||
- Use the outfit photo as the primary visual reference
|
|
||||||
- Use metadata to ensure completeness and accuracy
|
|
||||||
- Transfer ALL items that appear in the outfit photo AND are mentioned in metadata
|
|
||||||
- The final result should include every clothing item visible in the outfit photo, verified against the metadata
|
|
||||||
|
|
||||||
5. Ensure:
|
-----------------------------------------------------
|
||||||
- realistic lighting
|
👤 SECOND IMAGE PERSON HANDLING — CLOTHES ONLY, NEVER THEIR IDENTITY
|
||||||
- correct fabric physics
|
|
||||||
- correct drape
|
|
||||||
- realism
|
|
||||||
- no blending of people
|
|
||||||
- no copying faces from the second image
|
|
||||||
- all items from both the outfit photo and metadata are properly integrated into the user photo
|
|
||||||
|
|
||||||
6. Output ONE final edited image with ALL items from the outfit photo transferred to the user, ensuring all metadata items are also included.
|
- The person in the SECOND image (outfit photo) is **NOT** allowed to appear in the final result.
|
||||||
|
- You must treat the SECOND person as a "clothing mannequin" ONLY.
|
||||||
|
- You are **STRICTLY FORBIDDEN** from copying:
|
||||||
|
• their face
|
||||||
|
• their skin tone
|
||||||
|
• their hair
|
||||||
|
• their body shape
|
||||||
|
• their pose
|
||||||
|
• their tattoos or body details
|
||||||
|
|
||||||
IMPORTANT:
|
You may ONLY use the SECOND image to extract:
|
||||||
- FIRST = user_photo
|
• clothing items,
|
||||||
- SECOND = outfit_photo
|
• shoes,
|
||||||
- You must use BOTH the outfit photo (visual reference) AND metadata (descriptive reference)
|
• accessories (including the glasses holder strap around the neck),
|
||||||
- ALL items visible in the outfit photo AND mentioned in metadata must appear in the final image
|
• their colors, patterns, materials, and shapes.
|
||||||
|
|
||||||
|
Under NO circumstances may the final image look like the SECOND person.
|
||||||
|
It must always clearly be the person from the FIRST image wearing those clothes.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
🎯 PRIMARY OBJECTIVE
|
||||||
|
Replace ALL clothing and accessories in the FIRST IMAGE with the outfit shown in the SECOND IMAGE, cross-verified with the metadata below.
|
||||||
|
|
||||||
|
The replacement must be:
|
||||||
|
- Complete (all garments changed)
|
||||||
|
- Exact (copy the outfit items exactly)
|
||||||
|
- Hyper-realistic
|
||||||
|
- Seamlessly integrated
|
||||||
|
- Physically correct
|
||||||
|
|
||||||
|
Do NOT leave ANY part of the original outfit visible.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
👤 DO NOT CHANGE ANYTHING ABOUT THE USER (FIRST IMAGE)
|
||||||
|
Strictly preserve:
|
||||||
|
- Face and identity
|
||||||
|
- Skin tone
|
||||||
|
- Hair
|
||||||
|
- Expression
|
||||||
|
- Body proportions
|
||||||
|
- Pose
|
||||||
|
- Hands
|
||||||
|
- Phone
|
||||||
|
- Non-clothing jewelry
|
||||||
|
- Background and environment
|
||||||
|
- Lighting and shadows
|
||||||
|
- Camera angle, framing, and composition
|
||||||
|
- Depth of field and bokeh
|
||||||
|
|
||||||
|
The edited image must look like the same person photographed in the same moment, but wearing the uploaded outfit.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
👕 OUTFIT SOURCES YOU MUST FOLLOW
|
||||||
|
|
||||||
|
You MUST use BOTH sources:
|
||||||
|
|
||||||
|
1) SECOND IMAGE (OUTFIT PHOTO) – PRIMARY VISUAL SOURCE
|
||||||
|
- Carefully inspect ALL clothing and accessories.
|
||||||
|
- Transfer EVERY clothing item visible in the outfit photo to the user:
|
||||||
|
• Outer layers (jackets, hoodies, coats)
|
||||||
|
• Inner layers (shirts, t-shirts, tops)
|
||||||
|
• Bottoms (pants, shorts, skirts)
|
||||||
|
• Footwear
|
||||||
|
• Accessories (hats, glasses, glasses holder strap around the neck, necklaces, etc.)
|
||||||
|
- Copy exactly:
|
||||||
|
• Colors and hues (no tinting)
|
||||||
|
• Patterns and their scale (no warping or stretching)
|
||||||
|
• Textures, weave, and material type
|
||||||
|
• Stitching, seams, hems, cuffs, collars, zippers, buttons, pockets, labels, prints, logos, trims
|
||||||
|
• Shape, silhouette, and fabric density
|
||||||
|
|
||||||
|
2) METADATA (DESCRIPTIVE SOURCE)
|
||||||
|
The following metadata describes the outfit and items:
|
||||||
|
|
||||||
|
{metadata_text}
|
||||||
|
|
||||||
|
{extra_rules}
|
||||||
|
|
||||||
|
Use metadata to:
|
||||||
|
- Confirm all clothing layers and components.
|
||||||
|
- Ensure no item mentioned in metadata is forgotten.
|
||||||
|
- Validate colors, patterns, and materials.
|
||||||
|
- Resolve ambiguous details from the outfit photo.
|
||||||
|
- If metadata mentions items not clearly visible, use it to refine, but do NOT invent new styles.
|
||||||
|
|
||||||
|
3) COMBINED BEHAVIOR
|
||||||
|
- Outfit photo is the main visual truth.
|
||||||
|
- Metadata is the checklist and description to avoid missing items.
|
||||||
|
- The final result MUST:
|
||||||
|
• Include ALL clothing and accessories visible in the outfit photo.
|
||||||
|
• Include ALL relevant items mentioned in metadata that are consistent with the outfit photo.
|
||||||
|
• Remove the original garments completely.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
🧵 STRICT CLOTHING REPLACEMENT RULES
|
||||||
|
|
||||||
|
1. REMOVE original clothing completely from the FIRST image before placing replacements.
|
||||||
|
- No underlayers or ghost fabrics.
|
||||||
|
- No visible seams, colors, or textures from the old outfit.
|
||||||
|
|
||||||
|
2. APPLY the new outfit exactly as shown in the SECOND image:
|
||||||
|
- Perfect color match (no hue or saturation shift).
|
||||||
|
- Original pattern scale and orientation.
|
||||||
|
- Correct sleeve length, collar height, garment length, and silhouette.
|
||||||
|
- Only scale garments enough to fit the body while preserving pattern proportions.
|
||||||
|
|
||||||
|
3. NATURAL, PHYSICS-AWARE FIT:
|
||||||
|
- Fabric drapes according to gravity and the model’s pose.
|
||||||
|
- Folds and creases look realistic but do NOT distort patterns or design.
|
||||||
|
|
||||||
|
4. PERFECT INTEGRATION:
|
||||||
|
- Lighting and reflections must match the original scene.
|
||||||
|
- Contact shadows and occlusion must be consistent.
|
||||||
|
- No haloing, floating edges, or mismatched shadow direction.
|
||||||
|
|
||||||
|
5. ACCESSORIES:
|
||||||
|
- Apply all visible accessories from the outfit photo.
|
||||||
|
- If metadata mentions a glasses holder strap around the neck, it must appear naturally placed according to anatomy and perspective.
|
||||||
|
|
||||||
|
6. NO NEW DESIGN ELEMENTS:
|
||||||
|
- Do NOT invent new zippers, seams, logos, or prints.
|
||||||
|
- Do NOT remove real logos or prints that exist on the uploaded outfit unless clearly obstructed.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
📸 PHOTOREALISM REQUIREMENTS
|
||||||
|
|
||||||
|
- Maintain original exposure, contrast, and white balance.
|
||||||
|
- Preserve original depth of field (DOF) and perspective.
|
||||||
|
- Do NOT stylize, cartoonize, or apply filters.
|
||||||
|
- Avoid blurring, smoothing, or “AI look”.
|
||||||
|
- The final render must look like a real photo from the same camera and setup.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
🚫 FORBIDDEN ACTIONS
|
||||||
|
|
||||||
|
- Do NOT alter the user’s face, identity, or body.
|
||||||
|
- Do NOT change background, environment, camera angle, or lighting direction.
|
||||||
|
- Do NOT add or remove body parts or tattoos.
|
||||||
|
- Do NOT generate a different model.
|
||||||
|
- Do NOT copy the second person’s face or body.
|
||||||
|
- Do NOT output multiple images or side-by-side comparisons.
|
||||||
|
- Do NOT output any text in the image.
|
||||||
|
- Do NOT ignore the outfit photo or metadata.
|
||||||
|
|
||||||
|
-----------------------------------------------------
|
||||||
|
✅ FINAL OUTPUT REQUIREMENT
|
||||||
|
|
||||||
|
Generate ONE single high-resolution, hyper-realistic edited image where:
|
||||||
|
- The person from the FIRST image is wearing the outfit from the SECOND image.
|
||||||
|
- All clothing and accessories are replaced according to the outfit photo and metadata.
|
||||||
|
- The person, pose, background, and scene remain identical except for clothes and accessories.
|
||||||
|
|
||||||
|
FIRST image = user_photo (to edit).
|
||||||
|
SECOND image = outfit_photo (outfit reference).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ---- Wrap image files ----
|
# ---- Wrap image files ----
|
||||||
user_part = Part.from_bytes(data=user_bytes, mime_type=user_photo.content_type or "image/jpeg")
|
user_part = Part.from_bytes(
|
||||||
outfit_part = Part.from_bytes(data=outfit_bytes, mime_type=outfit_photo.content_type or "image/jpeg")
|
data=user_bytes,
|
||||||
|
mime_type=user_photo.content_type or "image/jpeg",
|
||||||
|
)
|
||||||
|
outfit_part = Part.from_bytes(
|
||||||
|
data=outfit_bytes,
|
||||||
|
mime_type=outfit_photo.content_type or "image/jpeg",
|
||||||
|
)
|
||||||
|
|
||||||
# ---- Call Gemini ----
|
# ---- Call Gemini ----
|
||||||
print(f"\n[DEBUG] Using model: {IMAGE_MODEL}")
|
print(f"\n[DEBUG] Using model: {IMAGE_MODEL}")
|
||||||
print(f"[DEBUG] Metadata received: {metadata[:100] if metadata else '(empty)'}")
|
print(f"[DEBUG] Metadata received: {metadata[:200] if metadata else '(empty)'}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = client.models.generate_content(
|
response = client.models.generate_content(
|
||||||
model=IMAGE_MODEL,
|
model=IMAGE_MODEL,
|
||||||
@ -180,16 +346,14 @@ IMPORTANT:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
# Check if it's an API key error
|
|
||||||
if "API key" in error_msg or "INVALID_ARGUMENT" in error_msg or "API_KEY" in error_msg:
|
if "API key" in error_msg or "INVALID_ARGUMENT" in error_msg or "API_KEY" in error_msg:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=401,
|
status_code=401,
|
||||||
detail=(
|
detail=(
|
||||||
f"Gemini API key error: {error_msg}. "
|
f"Gemini API key error: {error_msg}. "
|
||||||
"Please verify your GEMINI_API_KEY in the .env file is correct."
|
"Please verify your GEMINI_API_KEY in the .env file is correct."
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
# Log the full error for debugging
|
|
||||||
import traceback
|
import traceback
|
||||||
print(f"[ERROR] Gemini API error: {error_msg}")
|
print(f"[ERROR] Gemini API error: {error_msg}")
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
@ -205,27 +369,24 @@ IMPORTANT:
|
|||||||
finish_reason = getattr(cand, "finish_reason", None)
|
finish_reason = getattr(cand, "finish_reason", None)
|
||||||
print(f"[DEBUG] Finish reason: {finish_reason}")
|
print(f"[DEBUG] Finish reason: {finish_reason}")
|
||||||
print(f"[DEBUG] Safety ratings: {getattr(cand, 'safety_ratings', None)}")
|
print(f"[DEBUG] Safety ratings: {getattr(cand, 'safety_ratings', None)}")
|
||||||
|
|
||||||
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
|
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
|
||||||
print(f"[DEBUG] Parts in first candidate ({len(cand.content.parts)} total):")
|
print(f"[DEBUG] Parts in first candidate ({len(cand.content.parts)} total):")
|
||||||
for i, part in enumerate(cand.content.parts):
|
for i, part in enumerate(cand.content.parts):
|
||||||
part_type = type(part).__name__
|
part_type = type(part).__name__
|
||||||
print(f" Part {i}: type={part_type}")
|
print(f" Part {i}: type={part_type}")
|
||||||
|
|
||||||
# Check for text
|
|
||||||
text = getattr(part, "text", None)
|
text = getattr(part, "text", None)
|
||||||
if text:
|
if text:
|
||||||
print(f" TEXT: {text[:300]}")
|
print(f" TEXT: {text[:300]}")
|
||||||
|
|
||||||
# Check for inline_data
|
|
||||||
if hasattr(part, "inline_data"):
|
if hasattr(part, "inline_data"):
|
||||||
inline = part.inline_data
|
inline = part.inline_data
|
||||||
if inline:
|
if inline:
|
||||||
data_size = len(getattr(inline, "data", b""))
|
data_size = len(getattr(inline, "data", b""))
|
||||||
mime = getattr(inline, "mime_type", "unknown")
|
mime = getattr(inline, "mime_type", "unknown")
|
||||||
print(f" INLINE_DATA: mime={mime}, size={data_size} bytes")
|
print(f" INLINE_DATA: mime={mime}, size={data_size} bytes")
|
||||||
|
|
||||||
# Check for blob
|
|
||||||
if hasattr(part, "blob"):
|
if hasattr(part, "blob"):
|
||||||
blob = part.blob
|
blob = part.blob
|
||||||
if blob:
|
if blob:
|
||||||
@ -237,33 +398,30 @@ IMPORTANT:
|
|||||||
# ---- Extract output image ----
|
# ---- Extract output image ----
|
||||||
img_part = find_first_image_part(response)
|
img_part = find_first_image_part(response)
|
||||||
if not img_part or not getattr(img_part, "data", None):
|
if not img_part or not getattr(img_part, "data", None):
|
||||||
# Try to extract any text message from Gemini
|
|
||||||
message = "No image returned from Gemini."
|
message = "No image returned from Gemini."
|
||||||
|
|
||||||
if getattr(response, "candidates", None) and len(response.candidates) > 0:
|
if getattr(response, "candidates", None) and len(response.candidates) > 0:
|
||||||
cand = response.candidates[0]
|
cand = response.candidates[0]
|
||||||
finish_reason = getattr(cand, "finish_reason", "unknown")
|
finish_reason = getattr(cand, "finish_reason", "unknown")
|
||||||
|
|
||||||
# Extract text explanation from Gemini
|
|
||||||
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
|
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
|
||||||
for part in cand.content.parts:
|
for part in cand.content.parts:
|
||||||
txt = getattr(part, "text", None)
|
txt = getattr(part, "text", None)
|
||||||
if txt:
|
if txt:
|
||||||
message += f" Gemini says: {txt[:500]}"
|
message += f" Gemini says: {txt[:500]}"
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check safety ratings
|
|
||||||
if hasattr(cand, "safety_ratings"):
|
if hasattr(cand, "safety_ratings"):
|
||||||
safety_ratings = cand.safety_ratings
|
safety_ratings = cand.safety_ratings
|
||||||
if safety_ratings:
|
if safety_ratings:
|
||||||
blocked = [
|
blocked = [
|
||||||
f"{getattr(r, 'category', 'unknown')}"
|
f"{getattr(r, 'category', 'unknown')}"
|
||||||
for r in safety_ratings
|
for r in safety_ratings
|
||||||
if hasattr(r, "blocked") and getattr(r, "blocked", False)
|
if hasattr(r, "blocked") and getattr(r, "blocked", False)
|
||||||
]
|
]
|
||||||
if blocked:
|
if blocked:
|
||||||
message += f" Safety blocked: {', '.join(blocked)}"
|
message += f" Safety blocked: {', '.join(blocked)}"
|
||||||
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=502,
|
status_code=502,
|
||||||
detail=f"{message} Finish reason: {finish_reason}",
|
detail=f"{message} Finish reason: {finish_reason}",
|
||||||
@ -281,8 +439,12 @@ IMPORTANT:
|
|||||||
Image.open(BytesIO(raw))
|
Image.open(BytesIO(raw))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail=f"Returned image is invalid: {e}. Image size: {len(raw)} bytes"
|
detail=f"Returned image is invalid: {e}. Image size: {len(raw)} bytes",
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(content=raw, media_type="image/png")
|
headers = {}
|
||||||
|
if request_id:
|
||||||
|
headers["X-TryOn-Request-Id"] = str(request_id)
|
||||||
|
|
||||||
|
return Response(content=raw, media_type="image/png", headers=headers)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user