
    `gj?                       d Z ddlmZ ddlZddlZddlmZmZ ddlm	Z	 ddl
mZ ddlmZ ddlmZmZmZ dd	lmZmZ  ej*                  e      Zeeegdf   Zd
ZdZdZ eh d      Z e	d       G d d             ZddZ d
dddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ!ddZ"ddZ#dddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ$y) u  Pet generation orchestration — the base-draft → hatch flow.

Two steps, mirroring the UX across every surface:

1. :func:`generate_base_drafts` — a handful of prompt-only "what should this pet
   look like" variants. Cheap; the user picks one (or retries for a fresh set).
2. :func:`hatch_pet` — takes the chosen base and generates one grounded row
   strip per Hermes state, slices each into frames, composes the atlas, validates
   it, and writes the pet into the store.

Splitting it this way bounds cost (4 cheap base calls per round; the ~6 row
calls happen once, on the pet you actually keep) and gives each UI a natural
preview/loading point.
    )annotationsN)ThreadPoolExecutoras_completed)	dataclass)Path)Callable)atlasimagegenprompts)GenerationErrorSpriteProvider         >   idlewavingrunning-rightT)frozenc                  D    e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   y)HatchResultz*Outcome of a successful :func:`hatch_pet`.strslugdisplay_namer   spritesheet	list[str]statesdict
validationN)__name__
__module____qualname____doc____annotations__     Q/root/.hermes/venv/lib/python3.12/site-packages/agent/pet/generate/orchestrate.pyr   r   .   s"    4
Ir%   r   c                x   ddl m} 	 |j                  |       5 }t        j                  |j                  d            }ddd       t        j                        }| j                  d      }|j                  |d       |S # 1 sw Y   DxY w# t        $ r#}t        j                  d| |       | cY d}~S d}~ww xY w)	u  Key out any solid backdrop the provider painted; save as an RGBA PNG.

    ``background=transparent`` is requested on every call, but image models honor
    it inconsistently — some still paint a flat (often near-white) backdrop. We
    run the same chroma-key pass the row extractor uses so every base draft the
    user picks between (and the reference the rows are grounded on) is a clean
    cutout. Best-effort: a decode failure leaves the original untouched.
    r   )ImageRGBANz.pngPNG)formatz3base draft transparency hardening failed for %s: %s)PILr(   openr	   remove_backgroundconvert_clear_transparent_rgbwith_suffixsave	Exceptionloggerdebug)pathr(   openedkeyedoutexcs         r&   _harden_transparencyr;   9   s     ZZ 	D++FNN6,BCE	D ,,U3v&

3u
%
	D 	D  JDRUVs4   B %BAB B
B 	B9B4.B94B9auto)nstylereference_imagesprovideron_draftis_cancelledc               `    |xs d|xs t        j                  t                    |xs d t        j	                  d|        d
 fd}t        dt        |t                    }i }	g }
t        |      5 }t        |      D cg c]  }|j                  ||       }}t        |      D ]s  }        r.t        j	                  d       |D ]  }|j                            n>|j                         \  }}}||r|
j                  |       b||	|<   |j	  |||       u ddd       t#        |	      D cg c]  }|	|   	 }}|s        st%        t'        |
            |S c c}w # t        $ r }t        j!                  d	|       Y d}~d}~ww xY w# 1 sw Y   txY wc c}w )a  Generate *n* candidate base looks for *concept*; returns image paths.

    Each draft is hardened to a transparent cutout (see :func:`_harden_transparency`).
    Drafts are generated concurrently and *on_draft(index, path)* fires as each
    one finishes (not at the end) so callers can stream previews to the UI
    instead of leaving it blank until the whole batch is done.

    *is_cancelled*, when supplied, is polled cooperatively: a draft that hasn't
    started yet is skipped, and once it trips we stop staging/streaming further
    drafts and cancel any queued work (already-in-flight provider calls can't be
    hard-killed, but their results are dropped).
    Nrequire_referencesc                      yNFr$   r$   r%   r&   <lambda>z&generate_base_drafts.<locals>.<lambda>l       r%   z6pet generate: drafting %d base looks for %r (style=%s)c                Z           r| d d fS t        j                         }t        j                  | t	        t        j                        z     }t        j
                  
|      }	 t        j                  |d	d      }|st        j                  d|        | d dfS t        j                  d| t        j                         |z
         | t        |d	         d fS # t        $ rE}t        j                  d| t        j                         |z
  |       | d t        |      fcY d }~S d }~ww xY w)
N)r>   	variation   pet_base)r=   r?   r@   prefixz-pet generate: draft %d failed after %.1fs: %sz(pet generate: draft %d produced no imagez$the image provider returned no imagez%pet generate: draft %d ready in %.1fsr   )time	monotonicr   BASE_VARIATIONSlenbuild_base_promptr
   generater3   r4   warningr   infor;   )indext0rK   promptr9   r:   	cancelledconceptrefsspriter>   s         r&   _onez"generate_base_drafts.<locals>._ones   s   ;$$$^^++EC8O8O4P,PQ	**7%9U	)##Fa$QW`jkC NNEuM$ FFF;UDNNDTWYDYZ*3q62D88  	)NNJESWSaSaScfhShjmn$C((	)s   (C 	D*%:D%D*%D*rL   max_workersu5   pet generate: cancelled — dropping remaining draftszon_draft callback failed: %s)rW   intreturnz#tuple[int, Path | None, str | None])r
   resolve_providerboolr4   rV   maxmin_MAX_PARALLEL_GENERATIONSr   rangesubmitr   cancelresultappendr3   r5   sortedr   _drafts_failed_reason)r[   r=   r>   r?   r@   rA   rB   r^   workersresultserrorspoolifuturesfutpendingrW   r6   errr:   draftsrZ   r\   r]   s   ` `                  @@@r&   generate_base_draftsry   R   s   0 #tDQ22d4jQF/I
 KKH!WV[\9 9" !S567G!GF		0 FD16q:A4;;tQ'::  ( 	FC{ST& %GNN$%"zz|E4|MM#&!GEN#FUD)	FF. #)/2Qgaj2F2)+ 3F;<<M9 ;& ! FLL!?EEF)F F. 3sO   FE.)A6F!	E3*FF+.F3	F<FFFFF(c                `    | syddl m} t         ||       j                  d      d   d         S )zDThe representative reason a draft round produced nothing, humanized.z*image generation produced no usable draftsr   )CounterrL   )collectionsr{   _humanize_image_errormost_common)rq   r{   s     r&   rn   rn      s/    ;# !<!<Q!?!B1!EFFr%   c                    | j                         t        fddD              r	 yt        fddD              rydv sdv ry	| j                         d
   j                         dd S )zTurn a raw provider error into a friendly, actionable sentence.

    The big one is moderation: image models refuse trademarked characters and
    real people (e.g. "minion"), which reads as an opaque 400 otherwise.
    c              3  &   K   | ]  }|v  
 y wNr$   .0slows     r&   	<genexpr>z(_humanize_image_error.<locals>.<genexpr>   s     
i18
i   )moderation_blockedzsafety systemzcontent policycontent_policyu   The image provider blocked this prompt — its safety filter rejects trademarked characters and real people. Try an original description.c              3  &   K   | ]  }|v  
 y wr   r$   r   s     r&   r   z(_humanize_image_error.<locals>.<genexpr>   s     
H18
Hr   )zapi keyunauthorized401authuY   The image provider rejected the request — check your API key in Settings → Providers.z
rate limit429uD   The image provider is rate-limiting — wait a moment and try again.r   N   )lowerany
splitlinesstrip)errorr   s    @r&   r}   r}      ss     ++-C

ih
iiS	
 
HG
HHjseslUa &&(#..r%    )r   descriptionr[   r>   on_progressr@   rB   c        	   
        ! t        |       j                         st        d       |xs t        j                  d      !|xs d }	|xs d |xs |xs  i }
t        t        j                        }t        j                  d|       d+ !fd}t        j                  D cg c]  }|d   d	k7  s| }}t        d
t        t        |      t                    }d}t        |      5 }|D cg c]  }|j                  ||       }}t        |      D ]i  }        r/t        j                  d       |D ]  }|j!                            n3|j#                         \  }}|d
z  } |	d| d| d|        |se||
|<   k ddd               rt        d      |
j%                  d      }|rC|d
z  } |	dd| d|        t        j&                  |      |
d	<   t        j                  d       nt        j)                  d       |
j%                  d      s$ |	dd       t        j*                  d      g|
d<    |	dd       t        j                  dt        |
             t        j,                  t        j.                  |
            }t        j0                  |      }|d   s!t        dj3                  |d         xs d      t5        |d          }t7        t8        |z
        }|rt        d!d"j3                  |             t        |      t:        k  r2t        d#t        |       d$t        t        j                         d%      dd&lm}  |	d'       t        j                  d(       |jA                  ||xs |)      }tC        |jD                  |jF                  |jH                  |d    |*      S c c}w c c}w # 1 sw Y   ExY w),aI  Turn an approved base image into a full, installed Hermes pet.

    Generates a grounded row strip per state, extracts frames, composes +
    validates the atlas, and registers it. The idle row falls back to the base
    look so the pet always renders. Raises :class:`GenerationError` on failure.

    *is_cancelled*, when supplied, is polled cooperatively: rows that haven't
    started are skipped, queued rows are cancelled, and once every row is done we
    abort (raising :class:`GenerationError`) before composing/saving so a stopped
    hatch never writes a half-built pet.
    zbase image not found: TrD   c                      y r   r$   )_s    r&   rH   zhatch_pet.<locals>.<lambda>   rI   r%   c                      yrG   r$   r$   r%   r&   rH   zhatch_pet.<locals>.<lambda>   rI   r%   z*pet hatch %r: generating %d animation rowsc           
        | \  }}}        r|d fS t        j                         }d }t        t              D ]  }        r|d fc S |t        dz
  k  }	 t	        j
                  t        j                  ||      dgd| d      }|rdnd}	t        j                  |d   ||	d	
      }
t        j                  d|t        j                         |z
  |dz          ||
fc S  t        j                  d|t        j                         |z
  |       |d fS # t        $ r.}|}t        j                  d||dz   t        |       Y d }~d }~ww xY w)NrL   )r>   pet_row_	landscape)r=   r?   r@   rN   aspect_ratio
componentsr<   r   F)methodfitz0pet hatch %r: row %r ready in %.1fs (attempt %d)z-pet hatch %r: row %r attempt %d/%d failed: %sz,pet hatch %r: row %r gave up after %.1fs: %s)rO   rP   rh   _ROW_GEN_ATTEMPTSr
   rT   r   build_row_promptr	   extract_strip_framesr4   rV   r3   rU   )specstate_rowcountrX   last_excattemptstrictstripsr   framesr:   baserZ   labelr   r]   r>   s               r&   _gen_rowzhatch_pet.<locals>._gen_row   sp   !tU;$;^^%)
 ./ 	G{d{"0144F!**,,UE5N&*V#%eW- "-	 *0V33F1IuVY^_F%!1B!6! f}$3	@ 	:%)B.	
 d{  C%1.? s   BD	E #E		Er   zrunning-leftrL   r_   u3   pet hatch %r: cancelled — dropping remaining rowsrow:Nzhatch cancelledr   zrunning-left:z<pet hatch %r: row 'running-left' mirrored from running-rightz>pet hatch %r: no running-right to mirror; left walk left emptyr   zidle-fallbackF)r   composer   z,pet hatch %r: composing atlas from %d statesokz; rq   zatlas validation failedfilled_statesz#missing required animation row(s): z, zonly /z' animation rows were usable; regenerate)storer2   zpet hatch %r: saving pet)r   r   r   )r   r   r   r   r   )r   ztuple[str, int, int]rb   ztuple[str, list | None])%r   is_filer   r
   rc   rR   r	   	ROW_SPECSr4   rV   re   rf   rg   r   ri   r   rj   rk   getmirror_framesrU   single_framecompose_atlasnormalize_cellsvalidate_atlasjoinsetrm   _REQUIRED_STATES_MIN_FILLED_STATES	agent.petr   register_local_petr   r   r   r   )"
base_imager   r   r   r[   r>   r   r@   rB   progressframes_by_state
total_rowsr   r   generated_specsro   donerr   rt   ru   rv   r   r   rightsheetr   r   missing_requiredr   petr   rZ   r   r]   s"    `   `                        @@@@r&   	hatch_petr      s   . 
D<<> 6tf=>>K22dKF/H/I+|+tE')OU__%J
KK<dJO
. .d ).U47n;TtUOU!S_-/HIJGD		0 0D;JK44;;x.KK  ( 
	0C{QSWX& %GNN$%JJLME6AIDUugQtfAj\:;)/&
	0	0  {/00
 0E	-vQzl;<*/*=*=e*D'RTXYWY]^ v&(#(#5#5d#F"GY
KK>c/FZ[  5 5o FGE%%e,Jddii
8(<=ZAZ[[
?34M.>? CDIIN^D_C`abb
=..C&'qU__)=(>>ef
 	
  VT
KK*D1

"
"!)T	 # C XX%%OO/* G V
 L0 0s1   1O?O6O;OA1OOOO)r6   r   rb   r   )r[   r   r=   ra   r>   r   r?   zlist[Path] | Noner@   SpriteProvider | NonerA   z"Callable[[int, Path], None] | NonerB   Callable[[], bool] | Nonerb   z
list[Path])rq   r   rb   r   )r   r   rb   r   )r   z
str | Pathr   r   r   r   r   r   r[   r   r>   r   r   zProgressFn | Noner@   r   rB   r   rb   r   )%r"   
__future__r   loggingrO   concurrent.futuresr   r   dataclassesr   pathlibr   typingr   agent.pet.generater	   r
   r   agent.pet.generate.imagegenr   r   	getLoggerr   r4   r   
ProgressFnrg   r   r   	frozensetr   r   r;   ry   rn   r}   r   r$   r%   r&   <module>r      s   #   ? !   7 7 G			8	$ sCj$&'
     @A  $  8 *.&*37.2RR R 	R
 (R $R 1R ,R RjG/0 %)&*.2bb b 	b
 b b b #b $b ,b br%   