Ë
    Õ½Ùj¯  ã                   ó´   — d Z ddlZddlZddlZddlZddlZddlmZ ddl	Z	 G d„ de
«      Z G d„ de«      Z G d„ d	«      Z G d
„ d«      Z G d„ d«      Zdd„Zy)u¯  
AI Room Designer â€” generation backends (Stage 3).

One contract for every backend:  edit(before_path, prompt, out_path) -> out_path
(raises BackendError on failure). The pipeline and QC retry loop don't care
which backend produced the image.

  GeminiBackend   LIVE â€” Google Gemini image-edit API (generateContent REST,
                  x-goog-api-key header, key from env via config.api_key(),
                  never hardcoded, never logged).
  MockBackend     RIG/DRY-RUN â€” deterministic local transforms standing in for
                  the API so the full request/response + QC path can be proven
                  with no key and $0 spend. Behaviours: "grade" (warm colour
                  grade, structure untouched -> should PASS the gate) and
                  "reframe" (crop/shift, the known Budget Refresh v1 failure
                  pattern -> should FAIL the gate). Scriptable via
                  ROOM_DESIGNER_MOCK_SCRIPT or constructor arg.
  StandinBackend  The Stage 1/2 operator-run Muse media pipeline: writes a
                  PENDING marker and raises NeedsOperator â€” job stays queued
                  for the operator exactly as before (fallback preserved).
é    N)ÚPathc                   ó   — e Zd Zy)ÚBackendErrorN©Ú__name__Ú
__module__Ú__qualname__© ó    ú+/var/www/room-designer/pipeline/backends.pyr   r       ó   „ Ør   r   c                   ó   — e Zd Zy)ÚNeedsOperatorNr   r
   r   r   r   r   $   r   r   r   c                   ó4   — e Zd ZdZdd„Zed„ «       Zd„ Zd„ Zy)ÚGeminiBackendÚgeminiNc                 óê   — |�|nt        j                  «       | _        |xs t        j                  «       | _        |xs t        j                  «       | _        |xs | j
                  | _        d | _        y ©N)ÚconfigÚapi_keyÚkeyÚmodelÚ
image_sizeÚ_http_transportÚ	transportÚ
last_usage)Úselfr   r   r   r   s        r   Ú__init__zGeminiBackend.__init__,   sV   € Ø˜/‘3¬v¯~©~Ó/?ˆŒØÒ,œfŸl™l›nˆŒ
Ø$Ò;¬×(9Ñ(9Ó(;ˆŒØ"Ò: d×&:Ñ&:ˆŒØˆ�r   c           	      óV  — t         j                  j                  | t        j                  |«      j                  «       ddi|¥d¬«      }	 t         j                  j                  |d¬«      5 }t        j                  |j                  «       j                  «       «      cd d d «       S # 1 sw Y   y xY w# t         j                  j                  $ r?}t        d|j                  › d|j                  «       j                  «       d d	 › �«      ‚d }~wt        $ r}t        d
|› �«      ‚d }~ww xY w)NzContent-Typezapplication/jsonÚPOST)ÚdataÚheadersÚmethodé´   )ÚtimeoutzGemini API HTTP z: i,  zGemini API request failed: )ÚurllibÚrequestÚRequestÚjsonÚdumpsÚencodeÚurlopenÚloadsÚreadÚdecodeÚerrorÚ	HTTPErrorr   ÚcodeÚ	Exception)Úurlr"   ÚpayloadÚreqÚrÚes         r   r   zGeminiBackend._http_transport3   sú   € ä�n‰n×$Ñ$Ø”d—j‘j Ó)×0Ñ0Ó2Ø#Ð%7ÐC¸7ÐCÈFð %ó Tˆð	BÜ—‘×'Ñ'¨°SÐ'Ó9ð 5¸QÜ—z‘z !§&¡&£(§/¡/Ó"3Ó4÷5÷ 5ò 5ûä�|‰|×%Ñ%ò 	WÜÐ!1°!·&±&°¸¸A¿F¹F»H¿O¹OÓ<MÈdÈsÐ<SÐ;TÐUÓVÐVûÜò 	BÜÐ!<¸Q¸CÐ@ÓAÐAûð	BúsB   Á
!B2 Á+1B&Â	B2 Â&B/Â+B2 Â/B2 Â2D(Ã:D	Ä	D(ÄD#Ä#D(c                 ó2  — t        |«      j                  «       }dddt        j                  |«      j	                  «       dœid|igdœgddgd	| j
                  id
œdœ}t        j                  j                  | j                  ¬«      }|d| j                  i|fS )NÚuserÚinline_dataz
image/jpeg)Ú	mime_typer!   Útext)ÚroleÚpartsÚTEXTÚIMAGEÚ	imageSize)ÚresponseModalitiesÚimageConfig)ÚcontentsÚgenerationConfig)r   zx-goog-api-key)r   Ú
read_bytesÚbase64Ú	b64encoder/   r   r   ÚGEMINI_ENDPOINTÚformatr   r   )r   Úbefore_pathÚpromptÚimgr5   r4   s         r   Úbuild_requestzGeminiBackend.build_request@   s±   € Ü�;Ó×*Ñ*Ó,ˆð à"°,Ü-3×-=Ñ-=¸cÓ-B×-IÑ-IÓ-Kñ%Mð Nà˜VÐ$ðñð ð (.¨wÐ&7Ø +¨T¯_©_Ð=ñ!ñ
ˆô ×$Ñ$×+Ñ+°$·*±*Ð+Ó=ˆØÐ% t§x¡xÐ0°'Ð9Ð9r   c                 ó  — | j                   st        d«      ‚| j                  ||«      \  }}}| j                  |||«      }|j	                  d«      | _        |j	                  dg «      D ]•  }|j	                  di «      j	                  dg «      D ]n  }	|	j	                  d«      xs |	j	                  d«      xs i j	                  d«      }
|
sŒ=t        |«      j                  t        j                  |
«      «       |c c S  Œ— t        d	«      ‚)
Nz-no API key configured (ROOM_DESIGNER_API_KEY)ÚusageMetadataÚ
candidatesÚcontentr?   r;   Ú
inlineDatar!   z'Gemini response contained no image part)
r   r   rO   r   Úgetr   r   Úwrite_bytesrH   Ú	b64decode)r   rL   rM   Úout_pathr4   r"   r5   ÚrespÚcandÚpartr!   s              r   ÚeditzGeminiBackend.editS   sñ   € Ø�xŠxÜÐNÓOÐOØ $× 2Ñ 2°;ÀÓ GÑˆˆW�gØ�~‰~˜c 7¨GÓ4ˆØŸ(™( ?Ó3ˆŒØ—H‘H˜\¨2Ó.ò 	$ˆDØŸ™ ¨BÓ/×3Ñ3°G¸RÓ@ò $�ØŸ™ Ó/ÒO°4·8±8¸LÓ3IÒOÈR×TÑTÐU[Ó\�ÚÜ˜“N×.Ñ.¬v×/?Ñ/?ÀÓ/EÔFØ#”Oñ	$ð	$ô ÐDÓEÐEr   )NNNN)	r   r   r	   Únamer   Ústaticmethodr   rO   r\   r
   r   r   r   r   )   s-   „ Ø€Dóð ñ
Bó ð
Bò:ó&Fr   r   c                   ó"   — e Zd ZdZdZdd„Zd„ Zy)ÚMockBackendzDDeterministic local stand-in. `script` = behaviours cycled per call.ÚmockNc                 óð   — |€ t         j                  j                  dd«      }|j                  d«      D �cg c]#  }|j	                  «       sŒ|j	                  «       ‘Œ% c}xs dg| _        d| _        y c c}w )NÚROOM_DESIGNER_MOCK_SCRIPTÚgradeú,r   )ÚosÚenvironrU   ÚsplitÚstripÚscriptÚcalls)r   rj   Úss      r   r   zMockBackend.__init__g   sY   € Øˆ>Ü—Z‘Z—^‘^Ð$?ÀÓIˆFØ*0¯,©,°sÓ*;ÖI Q¸q¿w¹w½y�q—w‘w•yÒIÒVÈgÈYˆŒØˆ�
ùò Js   ¶A3ÁA3c           	      óâ  — ddl m}m} | j                  t	        | j
                  t        | j                  «      dz
  «         }| xj
                  dz  c_        |j                  |«      j                  d«      }|dk(  rg|j                  \  }}	|j                  t        |dz  «      t        |	dz  «      t        |dz  «      t        |	d	z  «      f«      }|j                  ||	f«      }|j                  |«      j                  d
«      }|j                  |«      j                  d«      }|j!                  «       \  }
}}|
j#                  d„ «      }
|j%                  d|
||f«      }|j'                  |«       |S )Nr   )ÚImageÚImageEnhanceé   ÚRGBÚreframegš™™™™™¹?g¸…ëQ¸®?g®Gázî?gÍÌÌÌÌÌì?gáz®Gáò?g¤p=
×£ð?c                 ó2   — t        dt        | dz  «      «      S )Néÿ   gÍÌÌÌÌÌð?)ÚminÚint)Úvs    r   ú<lambda>z"MockBackend.edit.<locals>.<lambda>|   s   € œc #¤s¨1¨t©8£}Ó5€ r   )ÚPILrn   ro   rj   ru   rk   ÚlenÚopenÚconvertÚsizeÚcroprv   ÚresizeÚColorÚenhanceÚ
Brightnessrh   ÚpointÚmergeÚsave)r   rL   rM   rX   rn   ro   Ú	behaviourrN   ÚwÚhr7   ÚgÚbs                r   r\   zMockBackend.editm   s0  € ß+Ø—K‘K¤ D§J¡J´°D·K±KÓ0@À1Ñ0DÓ EÑFˆ	Ø�
Š
�a‰�
Ø�j‰j˜Ó%×-Ñ-¨eÓ4ˆØ˜	Ò!ð —8‘8‰DˆAˆqØ—(‘(œC  D¡›M¬3¨q°4©x«=¼#¸aÀ$¹h»-ÌÈQÐQUÉXËÐWÓXˆCØ—*‘*˜a ˜VÓ$ˆCà× Ñ  Ó%×-Ñ-¨dÓ3ˆØ×%Ñ% cÓ*×2Ñ2°4Ó8ˆØ—)‘)“+‰ˆˆ1ˆaØ�G‰GÑ5Ó6ˆØ�k‰k˜% ! Q¨ Ó+ˆØ�‰�ÔØˆr   r   )r   r   r	   Ú__doc__r]   r   r\   r
   r   r   r`   r`   c   s   „ ÙNØ€Dóór   r`   c                   ó   — e Zd ZdZdZd„ Zy)ÚStandinBackendzCStage 1/2 fallback: operator-run media pipeline (job stays queued).Ústandinc                 ó   — t        d«      ‚)NzDstandin backend: generation is operator-run; job stays queued-on-rig)r   )r   rL   rM   rX   s       r   r\   zStandinBackend.edit‡   s   € ÜØRóTð 	Tr   N)r   r   r	   r‹   r]   r\   r
   r   r   r�   r�   ƒ   s   „ ÙMØ€DóTr   r�   c           
      ó
  — | xs t        j                  «       }|dk(  rt        di |¤ŽS |dk(  r3t        di |j	                  «       D ��ci c]  \  }}|dk(  sŒ||“Œ c}}¤ŽS |dk(  r
t        «       S t        d|› d�«      ‚c c}}w )Nr   ra   rj   rŽ   zunknown provider 'ú'r
   )r   Úproviderr   r`   Úitemsr�   r   )r’   ÚkwÚpÚkrw   s        r   Úget_backendr—   Œ   s‡   € ØÒ%”F—O‘OÓ%€AØˆH‚}ÜÑ"˜rÑ"Ð"ØˆF‚{ÜÑL¨r¯x©x«z×K¡t q¨!¸QÀ(»]˜a ™dÓKÑLÐLØˆI‚~ÜÓÐÜ
Ð+¨A¨3¨aÐ0Ó
1Ð1ùó Ls   ÁA?ÁA?r   )r‹   rH   r)   rf   Úurllib.requestr&   Úurllib.errorÚpathlibr   r   r3   r   r   r   r`   r�   r—   r
   r   r   ú<module>r›      sb   ðñó* Û Û 	Û Û Ý ã ô	�9ô 	ô	�Lô 	÷
6Fñ 6F÷tñ ÷@Tñ Tô2r   