
    aj1%                        d Z ddlmZ ddlZddlmZmZmZmZ  ej                  e
      ZdZdZeZdZdZd	Zdd
ZdddZddZ	 ddd	 	 	 	 	 ddZ G d d      Zy)uF  최종 i2i 변환 provider 선택 (2026-08-25 사용자 지시 "grok, reve 등으로
교환 가능하게").

호출부는 **어느 제공자인지 몰라도 된다** — `build_cine_client()` 가 주는
것은 `generate_image(prompt, labeled_references=[(label, png)])` 슬롯 하나뿐
이고, 두 client 가 같은 모양을 낸다.

★신원을 값으로 돌려주는 자리가 여기다(`cine_provider_identity`). 지문이
 provider·endpoint·model 을 봐야 하기 때문이다 — **같은 문안·같은 원본이라도
 다른 제공자가 만든 그림은 다른 산출**이다. 신원을 지문에 안 넣으면 제공자를
 바꿔도 옛 산출이 「지금 계약의 것」으로 통한다(cine_verify 가 판정 문안을
 지문에 안 넣어 같은 자리에서 걸렸던 것과 같은 결함).

★endpoint 는 **변동하는 host URL 이 아니라 논리 ID** 로 적는다. host 는
 제공자 사정으로 바뀌는데 그때마다 지문이 흔들리면 그림을 다시 사게 된다.

값(2026-08-25 공식 안내): Grok 약 $0.03/장 · Reve $0.25/장 = **8.3배**.
    )annotationsN)AnyCallableDictOptionalgrokzopenrouter/chat-completionszopenrouter/images+aspect_ratiozxai/images/edits)r   revemaiseedreamxaic                     ddl m}  t        t        | dt              xs d      }|t
        vrt        d|dt        t
               d      |S )u  설정이 고른 제공자 — 모르는 값은 **되돌리지 않고 크게 실패**한다.

    설정 검증(`config._still_cine_provider_known`)이 startup 에서 이미
    막지만, 실행 중 설정 API 로 바뀌는 갈래가 있어 여기서도 본다. 조용히
    기본값으로 되돌리면 운영자는 reve 로 도는 줄 아는데 지문·기록은 grok
    으로 남는다.
    r   settingsstill_cine_provider zstill_cine_provider unknown: z	 (known: ))app.core.configr   strgetattrLEGACY_PROVIDERKNOWN_PROVIDERS
ValueErrorlist)r   vs     X/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/cine_provider.pyresolve_cine_providerr   9   sZ     )GH3_EKLA+A5 1O,-Q01 	1 H    c           	        ddl m} | xs
 t               }|dk(  r t        t	        |dd      xs d      }d||dS |dk(  r"dt
        t        t	        |dd      xs d      dS |d	k(  r"d	t        t        t	        |d
d      xs d      dS |dk(  r"dt        t        t	        |dd      xs d      dS dt        t        t	        |dd      xs d      dS )u	  지문에 접을 신원 — (provider, endpoint, model).

    model 은 alias 가 아니라 **물리 모델 문자열**이다. alias 만 넣으면 판
    교체가 캐시를 못 뚫는다(cine_verify 의 `resolve_verify_model_physical`
    과 같은 이유).
    r   r   r	   reve_image_modelr   )providerendpointmodelr   seedream_image_modelr
   mai_image_modelr   xai_image_modelr   grok_image_model)	r   r   r   r   r   SEEDREAM_ENDPOINTMAI_ENDPOINTXAI_ENDPOINTGROK_ENDPOINT)r    r   pr"   s       r   cine_provider_identityr,   K   s     )+)+AF{GH&8"=CD"FFJ")+A2FL"M
 	

 	Ez$+<bAGRH
 	

 	Ez$+<bAGRH
 	
 !WX'92>D"E r   c                    ddl m} | j                  d      t        k(  xrF | j                  d      t        k(  xr, | j                  d      t        t        |dd      xs d      k(  S )u  이 신원이 **지문 v1 이 뜻하던 바로 그것**인가.

    지문 v1 은 provider·endpoint 칸이 없어 model 만 적었다. 그 시절 변환은
    전부 Grok 이었으므로, 지금 선택이 **정확히 그 Grok** 일 때만 옛 산출을
    지금 것으로 읽을 수 있다. 조금이라도 다르면 재생성이 맞다 — 제공자
    교체는 산출 계약이 실제로 다르다(소급 동등 처리 금지).
    r   r   r    r!   r"   r&   r   )r   r   getr   r*   r   r   )identityr   s     r   is_legacy_identityr0   q   sd     ) 	Z O3 	=LL$5	=LL!SH0"5;&= =r   	on_submitc          	     8   | xs
 t               }|dk(  rddlm}  |       S |dk(  rddlm}  ||      S ddlm} |dk(  r)dd	lm} t        t        t        |d
d      xs d            S |dk(  r'dd	lm}  |t        t        |dd      xs d            S  |       S )uP  제공자에 맞는 client — 호출부는 슬롯만 안다.

    `on_submit` 은 **접수 기반 제공자에만** 뜻이 있다(fal queue). 동기
    왕복인 Grok 에는 접수라는 단계가 없어 넘겨도 쓰이지 않는다 — 넘긴
    쪽이 「기록이 남는다」고 오해하지 않도록 여기 적어 둔다.
    r   r   )XaiImageClientr	   )ReveImageClientr1   )GrokImageClientr   r   r#   r   r
   r$   )r"   )r    app.modules.llm.xai_image_clientr4   !app.modules.llm.reve_image_clientr5   !app.modules.llm.grok_image_clientr6   r   r   _SeedreamCineClientr   r   )r    r2   r+   r4   r5   r6   r   s          r   build_cine_clientr;      s     	+)+AEzCF{E33AJ,"3H4b9?R$A B 	BEz 	-SH/4:&< = 	=r   c                  ,    e Zd ZdZddZddZ	 	 ddZy)	r:   uI  Seedream 을 cine 슬롯 모양(`generate_image`)으로 감싼다.

    ★운반은 **이미 있는 것**을 쓴다 — `marker_map_engine` 의 OpenRouter 전용
     이미지 경로다. 새로 짜면 같은 실수를 또 한다(chat/completions 로 보내
     500 을 받고 「모델이 못 그린다」로 읽는 것).
    c                :    |st        d      || _        i | _        y )Nu"   seedream_image_model 이 비었다)r   _model_tls_ctx)selfr"   s     r   __init__z_SeedreamCineClient.__init__   s    ABB(*r   c                    | j                   j                  |j                         D ci c]  \  }}|	|| c}}       | S c c}}w )u,  다른 cine client 와 **같은 슬롯**. 호출부는 제공자를 몰라야 한다.

        ★없으면 `AttributeError` 로 변환이 통째로 죽는다 — 실측으로 잡았다.
         새 제공자를 더할 때 이 계약을 안 맞추면 조용히 fallback 으로 떨어진다.
        )r?   updateitems)r@   kwargskr   s       r   set_contextz_SeedreamCineClient.set_context   sB     	v||~O~tq!ad~OP Ps
   
A
A
Nc           
        dd l }dd l}ddlm} ddlm} |xs g D 	
cg c]  \  }	}
|
	 c}
}	xs t        |xs g       }|j                         }|j                         5 }g }t        |      D ]6  \  }}
 ||      d| dz  }|j                  |
       |j                  |       8  || j                  ||dt        | j                        |      }d d d        t        |j                         |z
  dz        fS c c}
}	w # 1 sw Y   0xY w)	Nr   )Path)draw_marker_map_via_modelref_z.pngstill_cine_transform)r"   prompt	ref_pathsrolecapture_contextaspect_ratioi  )tempfiletimepathlibrI   &app.modules.pipeline.marker_map_enginerJ   r   	monotonicTemporaryDirectory	enumeratewrite_bytesappendr>   dictr?   int)r@   rM   reference_imagesrQ   labeled_referencesrR   rS   _PrJ   _lblbrefst0tdpathsifpngs                     r   generate_imagez"_SeedreamCineClient.generate_image   s   &	' #5":":<":gdA":< $"A$^^((*bE!$1rFtA3d^+a Q ( ,kk&E+ $T]] 3)	+C + C)B.$6777! = +*s   C:A2D  D	)r"   r   returnNone)rE   r   rj   z'_SeedreamCineClient')Nz16:9N)__name__
__module____qualname____doc__rA   rG   ri    r   r   r:   r:      s    + 7;?C8r   r:   )rj   r   )r   )r    r   rj   Dict[str, str])r/   rq   rj   bool)r    r   r2   z*Optional[Callable[[Dict[str, Any]], None]]rj   r   )ro   
__future__r   loggingtypingr   r   r   r   	getLoggerrl   loggerr   r*   r(   r'   r)   r   r   r,   r0   r;   r:   rp   r   r   <module>rx      s   $ #  0 0			8	$ -  5  "<$#L& " =A"" :" 		"J08 08r   