
    5i}                    .   U d Z ddlmZ ddl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mZ ddlZ ej                  e      ZdZddd	d
ZdZdZ G d d      Zdaded<    ej0                         Z	 d	 	 	 	 	 ddZd Z	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)u  
FlowBrowserEngine — Headless Playwright engine for Google Labs Flow.

Reverse-engineered API (Feb 2026):
  Endpoint: POST aisandbox-pa.googleapis.com/v1/projects/{project_id}/flowMedia:batchGenerateImages
  Model:    GEM_PIX_2 (displayed as "Nano Banana Pro" in UI)
  Auth:     Bearer token (from session endpoint) + reCAPTCHA (browser-generated)
  Response: Signed GCS URLs for each generated image

Strategy:
1. Keep headless Chromium alive on a Flow project page
2. Install JS fetch() interceptor to capture reCAPTCHA + auth from real requests
3. For each generation: fill prompt → click submit → wait for API response
4. Download images from the returned signed URLs
    )annotationsN)Path)Optionalz!https://labs.google/fx/tools/flowIMAGE_ASPECT_RATIO_PORTRAITIMAGE_ASPECT_RATIO_LANDSCAPEIMAGE_ASPECT_RATIO_SQUARE)portrait	landscapesquareax	  
// 1. Hide navigator.webdriver
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });

// 2. Override Permissions API to look like real Chrome
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
    parameters.name === 'notifications'
        ? Promise.resolve({ state: Notification.permission })
        : originalQuery(parameters)
);

// 3. Fake plugins (Chrome always has at least PDF plugins)
Object.defineProperty(navigator, 'plugins', {
    get: () => {
        const plugins = [
            { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
            { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
            { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
        ];
        plugins.length = 3;
        plugins.refresh = () => {};
        plugins.item = (i) => plugins[i] || null;
        plugins.namedItem = (n) => plugins.find(p => p.name === n) || null;
        return plugins;
    }
});

// 4. Fake languages
Object.defineProperty(navigator, 'languages', { get: () => ['pt-BR', 'pt', 'en-US', 'en'] });

// 5. Fake Chrome runtime (reCAPTCHA checks window.chrome)
if (!window.chrome) {
    window.chrome = {};
}
if (!window.chrome.runtime) {
    window.chrome.runtime = {
        connect: () => {},
        sendMessage: () => {},
        id: undefined,
    };
}

// 6. Override connection info to hide automation
Object.defineProperty(navigator, 'connection', {
    get: () => ({
        effectiveType: '4g',
        rtt: 50,
        downlink: 10,
        saveData: false,
    })
});

// 7. Fake WebGL vendor/renderer (headless often has different values)
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) {
    if (param === 37445) return 'Google Inc. (NVIDIA)';
    if (param === 37446) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Direct3D11 vs_5_0 ps_5_0, D3D11)';
    return getParameter.call(this, param);
};

// 8. Fake platform
Object.defineProperty(navigator, 'platform', { get: () => 'Linux x86_64' });

// 9. Fake hardware concurrency
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });

// 10. Fake device memory
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
a  
(() => {
    if (window.__flowInterceptorReady) return;
    window.__flowCaptured = [];
    window.__flowLastGeneration = null;
    window.__flowAspectRatio = null;  // set from Python to override aspect ratio
    window.__flowReferenceMediaId = null;  // set from Python for coverage shot reference
    window.__flowReferenceWeight = 0.85;   // reference image influence weight
    window.__flowLastMediaId = null;       // last captured mediaGenerationId
    const origFetch = window.fetch;
    window.fetch = async function(...args) {
        const [url, opts] = args;
        const urlStr = typeof url === 'string' ? url : url?.url || '';
        if (urlStr.includes('aisandbox-pa') && opts?.method?.toUpperCase() === 'POST') {
            const entry = {url: urlStr, headers: {}, body: null, ts: Date.now()};
            if (opts.headers) {
                if (opts.headers instanceof Headers)
                    opts.headers.forEach((v, k) => { entry.headers[k] = v; });
                else if (typeof opts.headers === 'object')
                    entry.headers = {...opts.headers};
            }
            // Override aspect ratio + limit to 1 image in batchGenerateImages requests
            if (urlStr.includes('batchGenerateImages') && opts.body) {
                try {
                    const body = JSON.parse(opts.body);
                    if (body.requests && body.requests.length > 0) {
                        // Keep only the first request to generate 1 image
                        body.requests = [body.requests[0]];
                        if (window.__flowAspectRatio) {
                            body.requests[0].imageAspectRatio = window.__flowAspectRatio;
                        }
                        // Inject reference image for coverage shot consistency
                        if (window.__flowReferenceMediaId) {
                            body.requests[0].imageInputs = [{
                                mediaId: window.__flowReferenceMediaId,
                                weight: window.__flowReferenceWeight || 0.85
                            }];
                        } else {
                            body.requests[0].imageInputs = [];
                        }
                    }
                    opts.body = JSON.stringify(body);
                } catch {}
            }
            if (opts.body) {
                try { entry.body = JSON.parse(opts.body); } catch {}
            }
            window.__flowCaptured.push(entry);
        }
        const response = await origFetch.apply(this, args);
        if (urlStr.includes('batchGenerateImages')) {
            try {
                const clone = response.clone();
                const data = await clone.json();
                window.__flowLastGeneration = {url: urlStr, response: data, ts: Date.now()};
                // Capture mediaGenerationId for coverage shot reference
                try {
                    const mediaId = data?.media?.[0]?.image?.generatedImage?.mediaGenerationId;
                    if (mediaId) window.__flowLastMediaId = mediaId;
                } catch {}
            } catch {}
        }
        return response;
    };
    window.__flowInterceptorReady = true;
})();
c                  0   e Zd ZdZddZedd       Zedd       Zedd       ZdddZ	ddZ
d Zd	 Zd
 Zd Zedd       Zdd dZd!dZ e ej*                  dd            ZdZg dZ	 	 d"	 	 	 	 	 	 	 	 	 d#dZd$dZed%d       Z	 	 	 	 	 	 	 	 	 	 d&dZy)'FlowBrowserEnginea  
    Headless browser engine for Flow image generation with Nano Banana Pro.

    Usage (async):
        engine = FlowBrowserEngine(session_token, csrf_token)
        await engine.start()
        path = await engine.generate("a cat", Path("out.png"))
        await engine.stop()
    c                    || _         || _        d | _        d | _        d | _        d | _        d| _        t        j                         | _	        d | _
        d | _        d | _        y )NF)session_token
csrf_token_browser_context_page_pw_readyasyncioLock_lock_project_id_http_client_last_media_id)selfr   r   s      F/root/.openclaw/workspace/visionaryfx/providers/flow_browser_engine.py__init__zFlowBrowserEngine.__init__   sY    *$
\\^
*.6:*.    c                :    | j                   xr | j                  d uS N)r   r   r   s    r   
is_runningzFlowBrowserEngine.is_running   s    {{8t}}D88r   c                    | j                   S r!   )r   r"   s    r   
project_idzFlowBrowserEngine.project_id   s    r   c                    | j                   S )z@The mediaGenerationId from the last successful image generation.)r   r"   s    r   last_media_idzFlowBrowserEngine.last_media_id   s     """r   c                *  K   | j                   ryddlm} 	  |       j                          d{   | _        | j                  j
                  j                  dg d       d{   | _        | j                  j                  ddd	d
d       d{   | _	        | j                  j                  t               d{    | j                  j                  d| j                  ddddddd| j                  ddddddddddddddg       d{    | j                  j                          d{   | _        t"        j%                  dt&               | j                   j)                  t&        d|dz         d{    t+        j,                  d       d{    | j/                          d{   }|st1        d      | j                   j3                  d      }|j5                          d{   dkD  r$|j6                  j9                  d       d{   s"t1        d| j                   j:                         | j                   j=                  t>               d{    tA        jB                  dd       | _"        d| _         t"        j%                  d!| jF                         y7 ~7 K7  7 7 7 7 @7 &7 7 7 7 g# tH        $ r9}t"        jK                  d"|       | jM                          d{  7   Y d}~y#d}~ww xY ww)$z@Launch browser, navigate to Flow, enter a project in image mode.Tr   )async_playwrightN)
z--no-sandboxz---disable-blink-features=AutomationControlledz--disable-dev-shm-usagez--disable-infobarsz--window-size=1280,900z--start-maximizedz--disable-extensionsz4--disable-component-extensions-with-background-pagesz--disable-background-networkingz--disable-default-apps)headlessargszeMozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36i   i  )widthheightzpt-BR)
user_agentviewportlocalez __Secure-next-auth.session-tokenzlabs.google/Lax)namevaluedomainpathsecurehttpOnlysameSitez__Host-next-auth.csrf-tokenz__Secure-next-auth.callback-urlzhttps%3A%2F%2Flabs.googlez%FlowBrowserEngine: Loading gallery %sdomcontentloaded  
wait_untiltimeout   z Could not enter any Flow project#PINHOLE_TEXT_AREA_ELEMENT_ID  r>   z$Textarea not found after setup. URL=g      N@)r>   follow_redirectsu'   FlowBrowserEngine: Ready — project=%su&   FlowBrowserEngine: Start failed — %sF)'r   playwright.async_apir)   startr   chromiumlaunchr   new_contextr   add_init_script_STEALTH_JSadd_cookiesr   r   new_pager   loggerinfoFLOW_GALLERYgotor   sleep_enter_projectRuntimeErrorlocatorcountfirst
is_visibleurlevaluate_FETCH_INTERCEPTORhttpxAsyncClientr   r   	Exceptionerrorstop)r   	timeout_sr)   enteredtaes         r   rE   zFlowBrowserEngine.start   s    ;;9[	-/5577DH"&(("3"3":": #; # DM #'--";";I $(37 #< # DM --//<<<--++ !C!%!3!3"/ #"&$($) !>!%"/ #"&$($) !B!<"/ #"&$($)'  @  $}}5577DJ KK?N**//);YQUEU "    --""" !//11G"#EFF ##$CDB((*$q(2883F3Ft3F3T-T-T":4::>>:JK 
 **%%&8999 % 1 1$QU VDDKKKA4CSCSTk 8 =@ 8 # 2 %-T :  	LLA1E))+	s  LK J-5K %J0&-K J3+K ?J6 AK J9!K 3J<4AK ?J? K KK 4K5?K 4K5&K K
AK $K%AK ,L-K 0K 3K 6K 9K <K ?K K K K 
K K 	L)L LLLLLc                4	  K   t        d      D ]H  }t        j                  d|dz          | j                  j	                  d       d{   }|r5t        j                  d|       t        j                  d       d{    nt        j                  d       | j                  j                  d	      }|j                          d{   d
kD  rd|j                  j                  d       d{   r@|j                  j                          d{    t        j                  d       d{    n`t        j                  d       | j                  j                  j                  dd       d{    t        j                  d       d{    | j                  j                  }d|v rD|j                  d      d   j                  d      d
   j                  d      d
   | _        t        j                  d| j                         | j                  j                  d      }|j                          d{   d
kD  rx|j                  j                  d       d{   rT|j                  j                          d{    t        j                  d       d{    t        j                  d       | j                  j	                  d       d{   }|dk(  r| j                  j                  d      }|j                          d{   d
kD  r|j                  j                  d       d{   r|j                  j                          d{    t        j                  d       d{    | j                  j                  d      }|j                          d{   d
kD  r?|j                  j                          d{    t        j                  d       d{     yt        j!                  d|       |dk  s| j                  j#                  t$        dd !       d{    t        j                  d"       d{    K y#7 7 7 7 u7 V7 <7 7 7 97 7 7 7 7 q7 M7 .7 7 7 7 7 ^7 Cw)$z8Enter a Flow project using robust selectors, with retry.   u3   FlowBrowserEngine: Entering project (attempt %d)…   a`  
                () => {
                    // Find all links that go to a project
                    const links = document.querySelectorAll('a[href*="/project/"]');
                    if (links.length > 0) {
                        links[0].click();
                        return 'link';
                    }
                    // Find clickable project cards (divs with project thumbnails)
                    const cards = document.querySelectorAll(
                        '[class*="project"], [class*="card"], [role="link"], [role="button"]');
                    for (const c of cards) {
                        const a = c.querySelector('a[href*="/project/"]') || c.closest('a[href*="/project/"]');
                        if (a) { a.click(); return 'card-link'; }
                    }
                    return null;
                }
            Nz!FlowBrowserEngine: Clicked via %s   uB   FlowBrowserEngine: No project links found, creating new project…zWbutton:has-text("Novo projeto"), button:has-text("New project"), button:has-text("add")r   i  rB   z,FlowBrowserEngine: Fallback coordinate click      z	/project/?#z%FlowBrowserEngine: Entered project %szbutton:has-text("Imagens")   z)FlowBrowserEngine: Switched to Images taba  
                    () => {
                        for (const b of document.querySelectorAll('button'))
                            if (b.textContent.includes('Criar imagens')) return 'ok';
                        return 'need_switch';
                    }
                need_switchzbutton:has-text("Texto para")i  ztext="Criar imagens"Tu5   FlowBrowserEngine: Still on gallery (%s), retrying…r:   0u  r<   r?   F)rangerM   rN   r   rY   r   rQ   rT   rU   rV   rW   clickmouserX   splitr   warningrP   rO   )	r   attemptclickednew_btnrX   img_tabmodemode_btncis	            r   rR   z FlowBrowserEngine._enter_projectB  s    Qx ^	+GKKEwQR{
 !JJ// 1  G& ?ImmA&&& X **,,m !(1,w}}7O7O  8P 8 2 2 "----///!--*** KK NO****00c:::!--*** **..Cc!IIk*2.44S9!<BB3GJ   CTEUEUV **,,-IJ (1,w}}7O7O  8P 8 2 2 "----///!--***KK KL "ZZ00 2   =(#zz112QRH%^^--1HNN<U<U $ =V = 7 7 'nn22444%mmA...!ZZ//0FG!#+a/"$((.."222")--"222KS Q;**//$1CU *    "--***}^	+@ u* ' ) 2 0* ;* ) 2 0* . 7 5.+22 +s  AR	Q
4R>Q?A	RQ!	&R/Q$0!RQ'R.Q*/AR2Q-3RQ0B*R:Q3;&R!Q6"!RQ9R Q<!7RQ?8RR&R8R9!RRR7R82R*R+$RRR,R-!R%R5R6RR	RR!R$R'R*R-R0R3R6R9R<R?RRRRRRRRRRc                
  K   d| _         	 | j                  r"| j                  j                          d{    d| _        	 | j                  r"| j                  j                          d{    	 | j                  r"| j                  j                          d{    dx| _        x| _        | _	        d| _        t        j                  d       y7 # t        $ r Y w xY w7 v# t        $ r Y w xY w7 X# t        $ r Y aw xY ww)zShutdown browser.FNzFlowBrowserEngine: Stopped)r   r   acloser]   r   closer   r_   r   r   rM   rN   r"   s    r   r_   zFlowBrowserEngine.stop  s     	  ''..000 !	}}mm))+++	xxhhmmo%% 6:99
01! 1 		
 , 		 & 		s   D)C CC D)C# *C!+C# 0)C4 C2C4 2DC 	CDCD!C# #	C/,D.C//D2C4 4	D =D?D  Dc                h  K   t         j                  d       | j                  j                  t        dd       d{    t        j                  d       d{    | j                          d{   }|st        d      | j                  j                  d      }|j                          d{   d	kD  r$|j                  j                  d
       d{   st        d      | j                  j                  t               d{    t         j                  d       y7 7 7 7 y7 T7 $w)zPRefresh reCAPTCHA context by navigating back to gallery and re-entering project.u6   FlowBrowserEngine: Refreshing session (page reload)…r:   ro   r<   Nr?   z0Failed to re-enter project after session refreshr@   r   rA   rB   z(Textarea not found after session refreshu(   FlowBrowserEngine: Session refreshed ✓)rM   rN   r   rP   rO   r   rQ   rR   rS   rT   rU   rV   rW   rY   rZ   )r   ra   rb   s      r   _refresh_sessionz"FlowBrowserEngine._refresh_session  s     LMjjoo%7  
 	
 	
 mmA++--QRRZZ ?@hhj 1$rxx/B/B4/B/P)P)PIJJjj!!"4555>?	
 	- !)P5si   ;D2D&D2D(D22D*3?D22D,3&D2D.1D2D0D2(D2*D2,D2.D20D2c                   K   t         j                  d       d| _        | j                          d{    | j	                          d{   }|st        d      t         j                  d       y7 ?7 )w)z<Full browser restart for a completely clean reCAPTCHA state.u*   FlowBrowserEngine: Full browser restart…FNz#Failed to restart FlowBrowserEngineu,   FlowBrowserEngine: Full restart complete ✓)rM   rN   r   r_   rE   rS   )r   oks     r   _full_restartzFlowBrowserEngine._full_restart  s\     @Aiik::<DEEBC	 	s!   0A6A2A6
A4(A64A6c           	       K   	 t        t        j                  dd            D ]  }t        j                  dd      }t        j                  dd      }| j                  j                  j                  ||t        j                  dd      	       d
{    t        j                  t        j                  dd             d
{     t        j                  dd      }| j                  j                  j                  d|       d
{    t        j                  t        j                  dd             d
{    y
7 7 ~7 :7 # t        $ r Y y
w xY ww)zFSimulate human-like mouse/scroll behavior to maintain reCAPTCHA score.rm      ri   r;   d   i        )stepsNg?g333333?ij   r   皙?      ?)rp   randomrandintr   rr   mover   rQ   uniformwheelr]   )r   _xyscroll_ys        r   _human_like_behaviorz&FlowBrowserEngine._human_like_behavior  s	    
	6>>!Q/0 >NN3-NN3,jj&&++Aqq"8M+NNNmmFNN3$<===	>
 ~~dC0H**""((H555--sC 8999	 O=59 		sr   EBE D?0E EAE E	0E 9E:E >E?E E E E 	EEEEc                4     g d}t         fd|D              S )z@Check if an error is reCAPTCHA-related (needs session recovery).)	recaptchaPERMISSION_DENIEDcaptchac              3  ^   K   | ]$  }|j                         j                         v  & y wr!   )lower).0ind	error_msgs     r   	<genexpr>z8FlowBrowserEngine._is_recaptcha_error.<locals>.<genexpr>  s#     J399;)//"33Js   *-)any)r   
indicatorss   ` r   _is_recaptcha_errorz%FlowBrowserEngine._is_recaptcha_error  s     C
JzJJJr   c                   K   | j                   j                  d||d       d{    t        j                  d|dd |       y7 w)u  Set a reference image (by mediaGenerationId) for the next generation.

        For Coverage Shots: generate the master angle first, then call this with
        its media_id before generating subsequent angles to maintain scene fidelity.

        Args:
            media_id: The mediaGenerationId from a previous generation response.
            weight: Influence weight of the reference image (0.0–1.0). Default 0.85.
        zg(args) => { window.__flowReferenceMediaId = args.mediaId; window.__flowReferenceWeight = args.weight; })mediaIdweightNuE   FlowBrowserEngine: Reference image set — media_id=%s… weight=%.2f   r   rY   rM   rN   )r   media_idr   s      r   set_reference_imagez%FlowBrowserEngine.set_reference_image  sQ      jj!!u F3
 	
 	
 	SSbM	
		
s   #AA Ac                   K   | j                   j                  d       d{    t        j                  d       y7 w)z>Clear the reference image so the next generation is text-only.z/() => { window.__flowReferenceMediaId = null; }Nz*FlowBrowserEngine: Reference image clearedr   r"   s    r   clear_reference_imagez'FlowBrowserEngine.clear_reference_image  s2     jj!!"STTT@A 	Us   ><>FLOW_GENERATION_COOLDOWN4r   )r   
   r      Nc                   K   | j                   4 d{    | j                  |t        |      ||       d{   cddd      d{    S 7 :7 7 	# 1 d{  7  sw Y   yxY ww)z;Generate an image using Flow (Nano Banana Pro / GEM_PIX_2).N)r   _generate_implr   )r   promptoutput_pathaspect_ratioseeds        r   generatezFlowBrowserEngine.generate  se      :: 	 	,,[)< 	 	 		 	 	 	sS   A)AA)!AAAA)A	A)AA)A&AA&"A)c                  K   | j                   j                  t               d{    | j                   j                  d|       d{    | j                   j                  d      }|j	                          d{   dkD  r$|j
                  j                  d       d{   st        d      |j
                  j                          d{    t        j                  t        j                  dd	             d{    |j
                  j                  d
       d{    t        j                  t        j                  dd             d{    |j
                  j                  |       d{    t        j                  t        j                  dd             d{    t        j                  dt!        |             | j                   j                  d       d{   }|s#|j
                  j#                  d       d{    t        j                  d|       t%        d      D ]]  }t        j                  d       d{    | j                   j                  d       d{   }|sF|j'                  d      sX|d   c S  t        d      7 |7 [7 +7 7 7 7 7 a7 A7 7 7 7 q7 Pw)zNFill prompt, submit, and wait for the API response. Returns the response dict.NzN(ar) => { window.__flowLastGeneration = null; window.__flowAspectRatio = ar; }r@   r   rA   rB   z&Prompt textarea not found on Flow pager   r    g333333?gffffff?g?g      ?z+FlowBrowserEngine: Prompt filled (%d chars)a0  
            () => {
                const btns = document.querySelectorAll('button');
                for (const btn of btns) {
                    if (!btn.offsetParent) continue;
                    const text = btn.textContent.trim();
                    const rect = btn.getBoundingClientRect();
                    if (text.includes('arrow_forward') && rect.y > 550 && rect.width < 80) {
                        btn.click();
                        return true;
                    }
                }
                return false;
            }
        Enterz0FlowBrowserEngine: Submit triggered (clicked=%s)Z   rf   z!() => window.__flowLastGenerationresponsez2Timeout waiting for Flow generation response (90s))r   rY   rZ   rT   rU   rV   rW   rS   rq   r   rQ   r   r   fillrM   rN   lenpressrp   get)r   r   r   rb   rv   r   gens          r   _submit_and_waitz"FlowBrowserEngine._submit_and_wait  s.     jj!!"4555jj!!\
 	
 	
 ZZ ?@hhj 1$rxx/B/B4/B/P)P)PGHHhhnnmmFNN34555hhmmBmmFNN46777hhmmF###mmFNN34555A3v;O 

++ -   ((..)))FP r 	'A--"""

++,OPPCswwz*:&		' OPP] 	6	
 !)P57#5  *
 #Ps  #KJ!#K	J$
2K<J'=&K#J*$,KJ-0KJ0"K$J3%0KJ6"K8J990K)J<*AK+J?,$KKA KK"K4K5K<KK$K'K*K-K0K3K6K9K<K?KKKKc                    | j                  di       }t        |t              rN|j                  dd      }|j                  dd      }|j                  dd      }|rd| d| d| S t        |      S t        |      S )	z5Extract human-readable error from API error response.r^   coder   messagestatus[z] z: )r   
isinstancedictstr)r   errr   msgr   s        r   _extract_errorz FlowBrowserEngine._extract_errorL  s{     ll7B'c4 7762&D'')R(CWWXr*F26QtfBvhb.DCHD3xr   c                 	  K   | j                   st        d      |j                  j                  dd       t        j                  d|j                  |       d }t        | j                  dz         D ]  }|dkD  r| j                  t        |dz
  t        | j                        dz
           }|r| j                  |      rz	 |dk  r:t        j                  d|| j                         | j                          d {    n9t        j                  d	|| j                         | j                          d {    n#t        j                  d|| j                  ||       t%        j&                  |       d {    | j)                  ||       d {   }	t        j                  dt+        |	j-                                      d|	v rS| j/                  |	      }t        j#                  d|       || j                  k  rt        d| j                   d|       |	j1                  dg       }
|
sNdt+        |	j-                                }t        j#                  d|       || j                  k  rt        |      |
d   }|j1                  di       j1                  di       }|j1                  dd      }|sNdt+        |j-                                }t        j#                  d|       || j                  k  rt        |      | j2                  j1                  |       d {   }|j4                  dk7  rt        d|j4                         |j7                  |j8                         |j1                  di       j1                  di       j1                  d      }|r || _        t        j=                  d|d d        |j?                         j@                  }t        j                  d|j                  |       | jC                          d {    t%        j&                  | jD                         d {    |c S  t        d       7 K7 # t         $ r*}t        j#                  d
|       t        d|       |d }~ww xY w7 7 7 f7 7 [w)!NzFlowBrowserEngine not started.T)parentsexist_oku0   FlowBrowserEngine: Generating → %s (aspect=%s)rf   r   rm   uI   FlowBrowserEngine: reCAPTCHA recovery L1 — page refresh (attempt %d/%d)uI   FlowBrowserEngine: reCAPTCHA recovery L2 — full restart (attempt %d/%d)u)   FlowBrowserEngine: Recovery failed — %szSession recovery failed: zAFlowBrowserEngine: Retry %d/%d after %ds backoff (last error: %s)z$FlowBrowserEngine: Response keys: %sr^   u#   FlowBrowserEngine: API error — %szFlow API error after z
 retries: mediazNo media in response: zFlowBrowserEngine: %simagegeneratedImagefifeUrlr   zNo fifeUrl in image: ri   zDownload failed: HTTP mediaGenerationIdu*   FlowBrowserEngine: Captured media_id=%s…r   u0   FlowBrowserEngine: Image saved → %s (%d bytes)Unreachable)#r   rS   parentmkdirrM   rN   r3   rp   MAX_RETRIESRETRY_BACKOFFminr   r   rt   r   r   r]   r^   r   rQ   r   listkeysr   r   r   status_codewrite_bytescontentr   debugstatst_sizer   GENERATION_COOLDOWN)r   r   r   r   r   
last_errorru   backoffrecovery_errr   
media_listfirst_media
image_datafife_urldl_respr   sizes                    r   r   z FlowBrowserEngine._generate_implW  s;     {{?@@   =>	
 
T--12 b	G{,,!S););%<q%@A
 $":"::"F,"a<"NN k ' $ 0 0
 #'"7"7"999"NN k ' $ 0 0
 #'"4"4"666 NN[((" mmG,,,!226<HHHKK>X]]_@UV ("!00:
BJOT---"+D,<,<+=Z
|T 
 "gr2J5d8==?6K5LM
4jAT---":..$Q-K$"599:JBOJ!~~i4H4T*//:K5L4MN
4jAT---":.. !--11(;;G""c)"%;G<O<O;P#QRR##GOO4 ,%r*() 
 &.#I8TWUW=Y##%--DKKBKDTDTVZ
 ++----- 8 8999Eb	H =))k : 7$ ,G +7~F+,	,  -HD <* .9s   B>R9Q:Q;9Q4Q5Q9<R5R6RRFR!R"C'R	R

&R0R1RQQ	Q>%Q99Q>>RRR
RR)r   r   r   r   )returnbool)r   zOptional[str])r   
str | None)<   )r`   intr   r   )r   r   r   r   )333333?)r   r   r   floatr   None)r   r   )r   N)
r   r   r   
Path | strr   r   r   
int | Noner   r   )r   r   r   r   r   r   )r   r   r   r   )
r   r   r   r   r   r   r   r   r   r   )__name__
__module____qualname____doc__r   propertyr#   r%   r'   rE   rR   r_   r   r   r   staticmethodr   r   r   r   osgetenvr   r   r   r   r   r   r    r   r   r   r      sE   / 9 9     # #bHbH22@ D K K
(B
 ibii(BCHIK#M :   	
  
1Qf  v*v* v* 	v*
 v* 
v*r   r   zOptional[FlowBrowserEngine]_enginec                  K   t         4 d{    t        r't        j                  rt        cddd      d{    S | xs t        j                  dd      }|xs t        j                  dd      }|r|st        d      t        ||      at        j                          d{   st        d      t        cddd      d{    S 7 7 7 *7 # 1 d{  7  sw Y   yxY ww)zGet or create the global FlowBrowserEngine singleton.
    
    Credentials can be passed explicitly (from DB) or fall back to env vars.
    NGOOGLE_FLOW_SESSION_TOKENr   GOOGLE_FLOW_CSRF_TOKENz#Google Flow cookies not configured.z!Failed to start FlowBrowserEngine)_engine_lockr  r#   r   r   rS   r   rE   )r   r   sessioncsrfs       r   
get_enginer    s       
 
w))
 
 
  M299-H"#MDRYY'?DdDEE#GT2]]_$$BCC
 
 
 
 %
 
 
 
sq   C)CC)CC)CC)A&C%C&C:C)CC)C)CC)C&CC&"C)c                 Z   K   t         rt         j                          d{    da yy7 w)zStop the global engine.N)r  r_   r   r   r   stop_enginer
    s'      lln s   +)	+c                (  K   t                d{   }|r|j                  ||       d{    	 |j                  | |||       d{   |r|j                          d{    S S 7 X7 >7 #7 # |r|j                          d{  7   w w xY ww)ut  High-level: generate an image using the Flow browser engine.

    Args:
        prompt: Text prompt for image generation.
        output_path: Where to save the generated image.
        aspect_ratio: IMAGE_ASPECT_RATIO_PORTRAIT / LANDSCAPE / SQUARE.
        seed: Optional fixed seed for reproducibility.
        reference_media_id: If set, uses this mediaGenerationId as imageInputs
            reference (image-to-image). Use for Coverage Shot angles after the
            master frame — maintains scene environment consistency across angles.
        reference_weight: Influence strength of reference image (0.0–1.0).
    N)r  r   r   r   )r   r   r   r   reference_media_idreference_weightengines          r   generate_imager    s     ( <F(();=MNNN1__V[,MM..000   NM 1 ..000 sf   BA)BA+BA1 	A-
A1 B"A/#B+B-A1 /B1BB
BB)NN)r   r   r   r   r   r   )r   NNr   )r   r   r   r   r   r   r   r   r  r   r  r   r   r   )r   
__future__r   r   jsonloggingr   r   timepathlibr   typingr   r[   	getLoggerr   rM   rO   ASPECT_RATIOSrJ   rZ   r   r  __annotations__r   r  r  r
  r  r   r   r   <module>r     s	    #    	     			8	$2-/)ETB JV* V*v (,	$ +w||~ @D2<* 6%)"111 1 	1
 #1 1 
1r   