[object Object]

← back to Whatsmystyle

fix: surface Gemini refusal reason; honest verdict on identity preservation

6ace428587643cc348330cdbc0180216ff089174 · 2026-05-11 23:04:47 -0700 · Steve Abrams

Worker now logs Gemini finishReason + safetyRatings + text refusal when
'returned no image' fires, instead of silently re-raising. Helps diagnose
the SAFETY refusal mode that's currently breaking avatar try-on.

Verdict from 3 live Gemini calls on Steve's portrait + Loro Piana cashmere:
  - Garment rendered correctly (camel cashmere crewneck, every call)
  - Identity preservation FAILED 3/3 — generic stock-photo man each time
  - 2 of 3 charged (7.8¢ total); the 3rd failed (no image), no charge
  - Pattern: Gemini's safety refuses real-person identity-clone on this
    prompt shape (clean studio background + 'keep face/hair/skin')

Path forward per Steve: Dopple. Awaiting DOPPLE_API_KEY paste. If Dopple
also fails identity preservation, fallback is local IDM-VTON (free,
self-hosted, ~3GB model).

Files touched

Diff

commit 6ace428587643cc348330cdbc0180216ff089174
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 11 23:04:47 2026 -0700

    fix: surface Gemini refusal reason; honest verdict on identity preservation
    
    Worker now logs Gemini finishReason + safetyRatings + text refusal when
    'returned no image' fires, instead of silently re-raising. Helps diagnose
    the SAFETY refusal mode that's currently breaking avatar try-on.
    
    Verdict from 3 live Gemini calls on Steve's portrait + Loro Piana cashmere:
      - Garment rendered correctly (camel cashmere crewneck, every call)
      - Identity preservation FAILED 3/3 — generic stock-photo man each time
      - 2 of 3 charged (7.8¢ total); the 3rd failed (no image), no charge
      - Pattern: Gemini's safety refuses real-person identity-clone on this
        prompt shape (clean studio background + 'keep face/hair/skin')
    
    Path forward per Steve: Dopple. Awaiting DOPPLE_API_KEY paste. If Dopple
    also fails identity preservation, fallback is local IDM-VTON (free,
    self-hosted, ~3GB model).
---
 data/whatsmystyle.db-shm | Bin 32768 -> 32768 bytes
 data/whatsmystyle.db-wal | Bin 2867552 -> 2929352 bytes
 scripts/tryon-worker.js  |  10 +++++++++-
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/data/whatsmystyle.db-shm b/data/whatsmystyle.db-shm
index 2a60945..ff57dc8 100644
Binary files a/data/whatsmystyle.db-shm and b/data/whatsmystyle.db-shm differ
diff --git a/data/whatsmystyle.db-wal b/data/whatsmystyle.db-wal
index 7b0a5e4..227ff6e 100644
Binary files a/data/whatsmystyle.db-wal and b/data/whatsmystyle.db-wal differ
diff --git a/scripts/tryon-worker.js b/scripts/tryon-worker.js
index 1fead3f..af12b75 100644
--- a/scripts/tryon-worker.js
+++ b/scripts/tryon-worker.js
@@ -51,7 +51,15 @@ async function gemini(personImgBuf, garmentImgBuf, instruction) {
   const j = await r.json();
   const part = j.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
   const data = part?.inline_data?.data || part?.inlineData?.data;
-  if (!data) throw new Error('Gemini returned no image');
+  if (!data) {
+    // Log the actual response so we can see WHY (safety refusal, quota, etc.)
+    const text = j.candidates?.[0]?.content?.parts?.find(p => p.text)?.text;
+    const finish = j.candidates?.[0]?.finishReason;
+    const safety = j.candidates?.[0]?.safetyRatings;
+    const err = new Error(`Gemini returned no image (finishReason=${finish}, text="${(text || '').slice(0, 200)}", safety=${JSON.stringify(safety || [])})`);
+    err.gemini_response = j;
+    throw err;
+  }
   return Buffer.from(data, 'base64');
 }
 

← ac1cfa6 feat: paid Gemini 2.5 Flash Image try-on + budget HUD  ·  back to Whatsmystyle  ·  feat: FASHN AI + OpenAI gpt-image-1 try-on providers (per dr 302dac8 →