Ë
    ÿ�j8  ã                   ó�  — d dl mZ d dlZd dlZd dlmZmZ d dlZd dlm	Z	m
Z
mZmZmZ d dlmZ d dlZd dlmZ d dlmZ d dlmZ d d	lmZ  ej4                  e«      Zd
edefd„Zdedede fd„Z! ejD                  dejF                  «      Z$dede fd„Z%dedefd„Z&dedede'fd„Z(	 	 	 ddede)de)dee   dee   dede
eef   fd„Z*y)é    )Úget_browserN)ÚurljoinÚurlparse)ÚListÚDictÚSetÚAnyÚOptional)ÚBeautifulSoup)Úsettings)Úclient)Ú"PRODUCT_LINK_CLASSIFICATION_PROMPT)Úinsert_llm_usageÚhtmlÚreturnc                 ó   — g }t        | d«      }|j                  dddi¬«      D ]³  }|j                  xs |j                  «       xs d}	 t	        j
                  |«      }t        |t        «      r|n|g}|D ]_  }t        |t        «      sŒt        |j                  d«      t        «      r|j                  d	„ |d   D «       «       ŒO|j                  |«       Œa Œµ |S # t        j                  t        f$ r t        j                  d«       Y Œèw xY w)
z½Every JSON-LD object on a page, with @graph wrappers flattened.

    Must run before the crawler decomposes <script> tags, which would otherwise
    delete exactly the data this reads.
    úhtml.parserÚscriptÚtypezapplication/ld+json©ÚattrsÚ z Skipping malformed JSON-LD blockz@graphc              3   óB   K  — | ]  }t        |t        «      sŒ|–— Œ y ­w©N)Ú
isinstanceÚdict)Ú.0Úgs     ú;/var/www/html/strategist-ai/app/services/content/website.pyú	<genexpr>z(extract_jsonld_blocks.<locals>.<genexpr>'   s   è ø€ ÒO A¼:ÀaÌÕ;NœaÑOùs   ‚˜)r   Úfind_allÚstringÚget_textÚjsonÚloadsÚJSONDecodeErrorÚ	TypeErrorÚloggerÚdebugr   Úlistr   ÚgetÚextendÚappend)r   ÚblocksÚsoupÚtagÚrawÚparsedÚ
candidatesÚitems           r   Úextract_jsonld_blocksr5      sú   € ð €FÜ˜˜}Ó-€DØ�}‰}˜X¨fÐ6KÐ-Lˆ}ÓMò $ˆØ�j‰jÒ0˜CŸL™L›NÒ0¨bˆð	Ü—Z‘Z “_ˆFô  *¨&´$Ô7‘V¸f¸Xˆ
Øò 	$ˆDÜ˜d¤DÔ)ØÜ˜$Ÿ(™( 8Ó,¬dÔ3Ø—‘ÑO¨¨h©ÔOÕOà—‘˜dÕ#ñ	$ð$ð" €Møô ×$Ñ$¤iÐ0ò 	ä�L‰LÐ;Ô<Ùð	ús   Á
CÃ.DÄDÚbase_urlÚ
target_urlc                 óš  — 	 t        | «      }t        |«      }|j                  |j                  k7  ry|j                  j                  d«      r|j                  n|j                  dz   }|j                  j                  d«      r|j                  n|j                  dz   }|j                  dv ry|j	                  |«      S # t
        $ r Y yw xY w)z9
    Checks if target_url is a sub-path of base_url.
    Fú/)r   r9   T)r   ÚnetlocÚpathÚendswithÚ
startswithÚ	Exception)r6   r7   ÚbaseÚtargetÚb_pathÚt_paths         r   Ú
is_subpathrC   ,   s­   € ðÜ˜Ó!ˆÜ˜*Ó%ˆà�;‰;˜&Ÿ-™-Ò'Øð #Ÿi™i×0Ñ0°Ô5�—’¸4¿9¹9Às¹?ˆØ &§¡× 4Ñ 4°SÔ 9�—’¸v¿{¹{ÈSÑ?Pˆð �9‰9˜	Ñ!Øà× Ñ  Ó(Ð(øÜò Ùðús   ‚/B> ²A:B> Â-B> Â>	C
Ã	C
z/(products?|item|p)/[^/?#]+Úurlc                 ód   — t        t        j                  t        | «      j                  «      «      S )zÖFree heuristic for the common case. Returns False for anything that
    doesn't match a known pattern -- callers fall back to the LLM classifier
    for sites this misses, they don't treat False as "not a product".)ÚboolÚ_PRODUCT_URL_PATTERNÚsearchr   r;   )rD   s    r   Ú_looks_like_product_urlrI   F   s%   € ô Ô$×+Ñ+¬H°S«M×,>Ñ,>Ó?Ó@Ð@ó    c                 óŒ   — t        | «      }t        | «      rdn|j                  }|j                  |d¬«      j	                  «       S )a  The identity a URL is deduped on while crawling -- deliberately looser
    than the URL itself.

    The fragment is always dropped: it never reaches the server, so two URLs
    differing only by fragment (.../shirt and .../shirt#main-content) are
    always the same page. The query string is dropped too, but only for
    product-detail URLs -- a storefront's "related products" widget appends a
    unique tracking query string (pr_rec_id, pr_ref_pid, ...) to every link to
    a product, which without this would make the same product page look like
    a new one on every referring page. Non-product URLs keep their query
    string, since this function is shared with the general site-scrape path
    where a query string can mean genuinely different content (pagination).
    r   )ÚqueryÚfragment)r   rI   rL   Ú_replaceÚgeturl)rD   r2   rL   s      r   Ú
_dedup_keyrP   M   s;   € ô �c‹]€FÜ)¨#Ô.‰B°F·L±L€EØ�?‰? °ˆ?Ó4×;Ñ;Ó=Ð=rJ   r3   Ú	tenant_idc              ƒ   ó†  ‡
K  — | s
t        «       S dj                  d„ t        | «      D «       «      }t        j                  |¬«      Š
ˆ
fd„}	 t        j                  |«      ƒ d{  –—† }t        j                  |j                  d   j                  j                  «      }	 t        |dd«      }|r<t!        |d	t"        j$                  |j&                  |j(                  |j*                  «       t-        |t.        «      r|j1                  d«      nd}|xs g }|D �	ch c]3  }	t-        |	t2        «      sŒd|	cxk  rt5        | «      k  sŒ)n n| |	   d   ’Œ5 c}	S 7 Œî# t        $ r,}t        j                  d|› �«       t        «       cY d}~S d}~ww xY w# t        $ r"}t        j                  d
|› �«       Y d}~ŒÈd}~ww xY wc c}	w ­w)z÷One LLM call, run in a thread since the OpenAI client here is sync
    and this must not block the event loop mid-crawl. Never raises -- a
    failed classification just means the crawl falls back to insertion
    order, which is today's behavior.Ú
c              3   ó>   K  — | ]  \  }\  }}|› d |› d|› d�–— Œ y­w)z. z -- "ú"N© )r   ÚirD   Útexts       r   r    z*_classify_product_links.<locals>.<genexpr>h   s0   è ø€ ò Ù(6¨©;¨C°ˆ1ˆ#ˆR�ˆu�F˜4˜& Ô#ñùs   ‚)Úlinksc                  óˆ   •— t         j                  j                  j                  t        j
                  d‰ dœgddddi¬«      S )NÚuser)ÚroleÚcontentiô  g      >@r   Újson_object)ÚmodelÚmessagesÚmax_completion_tokensÚtimeoutÚresponse_format)Úopenai_clientÚchatÚcompletionsÚcreater   Ú	LLM_MODEL)Úprompts   €r   Ú_callz&_classify_product_links.<locals>._callm   sI   ø€ Ü×!Ñ!×-Ñ-×4Ñ4Ü×$Ñ$Ø%°&Ñ9Ð:Ø"%ØØ# ]Ð3ð 5ó 
ð 	
rJ   Nr   z$Product-link classification failed: ÚusagezProduct Link Classificationz1Could not log product-link classification usage: Úproduct_indices)ÚsetÚjoinÚ	enumerater   ÚformatÚasyncioÚ	to_threadr$   r%   ÚchoicesÚmessager]   r>   r(   ÚwarningÚgetattrr   r   rh   Úprompt_tokensÚcompletion_tokensÚtotal_tokensr   r   r+   ÚintÚlen)r3   rQ   Únumberedrj   ÚresponseÚpayloadÚexrk   ÚindicesrW   ri   s             @r   Ú_classify_product_linksr�   `   s‡  øè ø€ ñ
 Ü‹uˆà�y‰yñ Ü:CÀJÓ:Oôó €Hô 0×6Ñ6¸XÔF€Fô
ðÜ ×*Ñ*¨5Ó1×1ˆÜ—*‘*˜X×-Ñ-¨aÑ0×8Ñ8×@Ñ@ÓAˆð
QÜ˜ '¨4Ó0ˆÙÜ˜YÐ(EÄx×GYÑGYØ"×0Ñ0°%×2IÑ2IÈ5×K]ÑK]ô_ô
 1;¸7ÄDÔ0Iˆg�k‰kÐ+Ô,Èt€GØŠm˜€GØ&-Öa ´¸A¼sÕ1CÈÈQÔH`ÔQTÐU_ÓQ`ÖH`ˆJ�q‰M˜!ÓÒaÐað! 2ùäò Ü�‰Ð=¸b¸TÐBÔCÜ‹u�ûðûô ò QÜ�‰ÐJÈ2È$ÐO×PÑPûðQüò
 bùs…   ƒA	GÁE Á%EÁ&:E Â!AF Ã,-GÄF<Ä/F<ÅF<ÅGÅE Å	FÅ!FÆ FÆGÆFÆGÆ	F9ÆF4Æ/GÆ4F9Æ9GÚ	max_depthÚ	max_pagesÚvisitedÚ	seed_urlsc           
   ƒ   ó’  ‡ ‡‡‡"‡#‡$‡%K  — ‰€
t        «       Št        ‰ «      j                  }dg g t        «       i g dœŠ$t        «       ƒ d{  –—† Š"|r|D �cg c]  }|df‘Œ c}n‰ dfg}	|	D ��
ch c]  \  }}
t	        |«      ’Œ }}}
t        «       Š%t        «       Š#d}d}ˆ"ˆˆ$ˆ ˆˆ%fd„}i }|	�r?t        ‰«      |k  �r0|	j                  ˆ#fd„¬«       g }|	rÄt        |«      t        j                  k  r¨t        ‰«      t        |«      z   |k  rŽ|	j                  d«      \  }}t	        |«      }|‰%v s||D �cg c]  }t	        |d   «      ‘Œ c}v rŒz|j                  ||f«       |	r7t        |«      t        j                  k  rt        ‰«      t        |«      z   |k  rŒŽ|s�nOt        j                  |D ��cg c]  \  }} |||«      ‘Œ c}}Ž ƒ d{  –—† }|�B	 |r$t        d	t        t        ‰«      d
z  |z  «      «      nd} ||dt        ‰«      › d|› d�«       |D ]Y  }|D ]R  \  }}}t	        |«      }|‰%vsŒ||vsŒ|	j                  ||f«       |j%                  |«       |||<   t'        |«      sŒQd}ŒT Œ[ |se|sct        ‰«      t        j(                  k\  rG|	rEd}|	dd
 D ��
cg c]  \  }}
||j+                  |d«      f‘Œ }}}
	 t-        ||«      ƒ d{  –—† }‰#|z  Š#|	rt        ‰«      |k  r�Œ0t/        t1        ‰$d   «      «      ‰$d<   t1        ‰$d   j3                  «       «      dd D ��!cg c]
  \  }}!||!dœ‘Œ c}!}‰$d<   ‰$S 7 �Œõc c}w c c}
}w c c}w c c}}w 7 �Œ�# t        $ r t         j#                  dd¬«       Y �Œjw xY wc c}
}w 7 Œ¸# t        $ r,} t         j#                  d| › �«       t        «       }Y d} ~ Œæd} ~ ww xY wc c}!}w ­w)až  
    Crawls the website starting from URL up to max_depth and max_pages using a headless browser.
    Returns a dictionary containing aggregated text, metadata, and color palette.

    Visits product-shaped URLs before plain nav/collection links -- a strict
    depth-by-depth BFS can exhaust the whole page budget on a site's nav
    surface before ever reaching a product page (see the crawl-prioritization
    spec). A regex heuristic (_looks_like_product_url) handles the common
    case for free; if it finds nothing after CRAWL_PRODUCT_FALLBACK_AFTER_PAGES
    pages, one LLM call (_classify_product_links) re-ranks the frontier for
    sites with non-standard URL conventions.

    seed_urls, when given, replaces the single-URL frontier with these --
    e.g. a sitemap's already-known product URLs.

    progress, if given, is called as progress(percent, step) after every
    batch -- best-effort, never raises.
    Nr   )rX   ÚpagesÚmetadataÚcolorsÚimagesÚurls_visitedr   Fc              “   ó	  •‡ ‡!K  — ‰&j                  | «       ‰'j                  t        | «      «       t        j                  d| › d|› dt	        ‰&«      › d�«       g }	 ‰"j                  | «      ƒ d {  –—† \  }}}|st        j                  d| › �«       |S t        |d«      }‰$d   j                  | «       | |j                  d«      xs$ |j                  r|j                  j                  nd	d	d
œ}|j                  dddi¬«      }|r|j                  dd	«      |d<   ‰$d   j                  |«       |j                  d«      }	|	D ]r  }
t        j                  d|
j                  xs d	«      }‰$d   j!                  |«       t        j                  d|
j                  xs d	«      }‰$d   j!                  |«       Œt t#        |«      } |g d¢«      D ]  }|j%                  «        Œ i }|j                  d«      }g d¢}|D ]ý  }|j                  d«      }|sŒt'        | |«      }|j)                  «       Š!t+        ˆ!fd„|D «       «      rŒH|j                  d«      xs |j                  d«      }|si|j-                  d«      d   j-                  d«      d   }|j/                  dd «      d   j1                  d!d"«      j1                  d#d"«      j3                  «       }|j5                  «       d d$ }|‰$d%   vr|‰$d%   |<   |||<   Œÿ |j7                  «       }d&„ |j9                  «       D «       }d'„ |D «       }d(j;                  d)„ |D «       «      }‰$d*   j                  | ||||j                  d«      xs$ |j                  r|j                  j                  nd	t=        |j?                  «       «      d d+ D ��cg c]
  \  }}||d,œ‘Œ c}}d-œ«       ‰$d.xx   d/| › d0|› d(�z  cc<   |‰#k  re|D ]`  }|d1   Š |j                  d.d	«      }tA        ‰%‰ «      sŒ't        ‰ «      ‰'vsŒ5t+        ˆ fd2„d3D «       «      rŒJ|j                  ‰ ||d z   f«       Œb |S 7 �ŒÓc c}}w # tB        $ r(}t        jE                  d4| › d5|› �d6¬7«       Y d }~|S d }~ww xY w­w)8NzCrawling URL (Headless): z (depth z, count ú)zNo content rendered for r   r‹   Útitler   )rD   rŽ   ÚdescriptionÚmetaÚnamer�   r   r]   rˆ   Ústylez#[0-9a-fA-F]{3,6}r‰   z
rgb\(.*?\))r   r’   ÚnavÚfooterÚheaderÚasideÚimg)	ÚfacebookÚtwitterÚlinkedinÚ	instagramÚtiktokÚsocialÚiconÚarrowÚchevronÚsrcc              3   ó&   •K  — | ]  }|‰v –— Œ
 y ­wr   rV   )r   ÚkeyÚ	url_lowers     €r   r    z;extract_text_from_url.<locals>.crawl_one.<locals>.<genexpr>î   s   øè ø€ ÒE°˜3 )Ô+ÑEùs   ƒÚaltr9   éÿÿÿÿú?r   ú.é   Ú-Ú Ú_éd   rŠ   c              3   ó<   K  — | ]  }|j                  «       –— Œ y ­wr   )Ústrip)r   Úlines     r   r    z;extract_text_from_url.<locals>.crawl_one.<locals>.<genexpr>ý   s   è ø€ ÒD d�T—Z‘Z—\ÑDùs   ‚c              3   óh   K  — | ]*  }|j                  d «      D ]  }|j                  «       –— Œ Œ, y­w)z  N)Úsplitr¯   )r   r°   Úphrases      r   r    z;extract_text_from_url.<locals>.crawl_one.<locals>.<genexpr>þ   s,   è ø€ ÒV¨ÀTÇZÁZÐPTÓEUÒV¸6�f—l‘l—nÐV�nÑVùs   ‚02rS   c              3   ó&   K  — | ]	  }|sŒ|–— Œ y ­wr   rV   )r   Úchunks     r   r    z;extract_text_from_url.<locals>.crawl_one.<locals>.<genexpr>ÿ   s   è ø€ Ò$H¨uÂ%¤UÑ$Hùs   ‚Šr‡   é
   ©rD   Úlabel)rD   rX   Újsonldr   rŽ   rŠ   rX   z
--- Content from z ---
Úhrefc              3   ó\   •K  — | ]#  }‰j                  «       j                  |«      –— Œ% y ­wr   )Úlowerr<   )r   ÚextÚnext_urls     €r   r    z;extract_text_from_url.<locals>.crawl_one.<locals>.<genexpr>  s#   øè ø€ Ò"vÀc 8§>¡>Ó#3×#<Ñ#<¸S×#AÑ"vùs   ƒ),)z.pdfz.jpgz.pngz.zipz.gifzFailed to crawl z with Playwright: T©Úexc_info)#ÚaddrP   r(   Úinfor{   Úget_rendered_contentru   r   r-   r+   rŽ   r"   Úfindr!   ÚreÚfindallÚupdater5   Ú	decomposer   r¼   Úanyr²   ÚrsplitÚreplaceÚ
capitalizer¯   r#   Ú
splitlinesrn   r*   ÚitemsrC   r>   Úerror)(Úcurrent_urlÚdepthÚ	new_linksr]   rY   Úpw_metar/   Ú	page_metaÚdesc_tagÚstylesr’   Ú
hex_colorsÚ
rgb_colorsÚpage_jsonldÚelementÚpage_imagesÚimg_tagsÚjunk_keywordsr—   Úimg_urlÚfull_img_urlr¸   ÚfilenameÚraw_textÚlinesÚchunksÚcleaned_textÚuÚlÚlinkÚ	next_textÚer¾   r¤   Úbrowserr‚   ÚresultsrD   r„   Úvisited_keyss(                                   @@€€€€€€r   Ú	crawl_onez(extract_text_from_url.<locals>.crawl_one¿   s~  úè ø€ Ø�‰�KÔ Ø×Ñœ KÓ0Ô1Ü�‰Ð/°¨}¸HÀUÀGÈ8ÔTWÐX_ÓT`ÐSaÐabÐcÔdàˆ	ðP	_Ø,3×,HÑ,HÈÓ,U×&UÑ#ˆG�U˜GáÜ—‘Ð!9¸+¸ÐGÔHØ Ð ä  ¨-Ó8ˆDØ�NÑ#×*Ñ*¨;Ô7ð #Ø Ÿ™ WÓ-ÒZÀtÇzÂz°$·*±*×2CÒ2CÐWYØ!ñˆIð
 —y‘y °¸Ð/F�yÓGˆHÙØ+3¯<©<¸	À2Ó+F�	˜-Ñ(Ø�JÑ×&Ñ& yÔ1à—]‘] 7Ó+ˆFØò 5�ÜŸZ™ZÐ(<¸e¿l¹lÒ>PÈbÓQ�
Ø˜Ñ!×(Ñ(¨Ô4ÜŸZ™Z¨°u·|±|Ò7IÀrÓJ�
Ø˜Ñ!×(Ñ(¨Õ4ð	5ô 0°Ó8ˆKáÒ WÓXò $�Ø×!Ñ!Õ#ð$ð ˆKØ—}‘} UÓ+ˆHÚ|ˆMàò 6�ØŸ'™' %›.�ÚÜ#*¨;¸Ó#@�LØ ,× 2Ñ 2Ó 4�IÜÓE°}ÔEÔEØ àŸG™G E›NÒ>¨c¯g©g°gÓ.>�EÙ Ø#/×#5Ñ#5°cÓ#:¸2Ñ#>×#DÑ#DÀSÓ#IÈ!Ñ#L˜Ø (§¡°°QÓ 7¸Ñ :× BÑ BÀ3ÈÓ L× TÑ TÐUXÐZ]Ó ^× iÑ iÓ k˜à!ŸK™K›M¨$¨3Ð/�Eà#¨7°8Ñ+<Ñ<Ø:?˜ Ñ)¨,Ñ7Ø05�K Ò-ð#6ð& —}‘}“ˆHÙD¨h×.AÑ.AÓ.CÔDˆEÙV°ÔVˆFØŸ9™9Ñ$H¸Ô$HÓHˆLà�GÑ×#Ñ#Ø"Ø$Ø%ØØ Ÿ™ WÓ-ÒZÀtÇzÂz°$·*±*×2CÒ2CÐWYÜ>BÀ;×CTÑCTÓCVÓ>WÐX[ÐY[Ð>\×]±d°a¸ 1¨qÓ1Ó]ñ%ô ð �F‹OÐ!4°[°MÀÈÀ~ÐUWÐXÑX‹Oà�yÒ Ø!ò O�DØ# F™|�HØ $§¡¨°Ó 4�IÜ! # xÕ0´ZÀÓ5IÐQ]Ò5]Ü"Ó"vÐMuÔ"vÕvØ%×,Ñ,¨h¸	À5È1Á9Ð-MÕNðOð Ððc 'VûóB ^øô ò 	_Ü�L‰LÐ+¨K¨=Ð8JÈ1È#ÐNÐY]ˆL×^Ð^àÐûð	_üs{   …AR ÁQ Á0QÁ1#Q ÂR ÂE<Q ÈFQ Î*QÎ9AQ ÐQ ÐQ Ð(Q ÑR ÑQ ÑQ Ñ	Q=ÑQ8Ñ2R Ñ8Q=Ñ=R c                 ó6   •— | d   ‰v st        | d   «      rdS dS )Nr   r©   )rI   )ÚentryÚproduct_prioritys    €r   ú<lambda>z'extract_text_from_url.<locals>.<lambda>  s'   ø€ ¨U°1©XÐ9IÑ-IÔMdÐejÐklÑemÔMn¨€ Ðtu€ rJ   )r£   éc   r­   zcrawled z of z pageszCrawl progress callback failedTr¿   z-Product-link classification fallback failed: r‰   rŠ   é   r·   )rm   r   r:   r   rP   r{   Úsortr   ÚCRAWL_BATCH_SIZEÚpopr-   rq   ÚgatherÚminrz   r>   r(   ru   rÁ   rI   Ú"CRAWL_PRODUCT_FALLBACK_AFTER_PAGESr+   r�   Úsortedr*   rÎ   )&rD   r‚   rƒ   r„   rQ   r…   ÚprogressÚbase_domainrå   Úfrontierr¬   ÚqueuedÚfallback_firedÚany_product_pattern_seenrí   Úpending_text_by_urlÚbatchÚcandidate_urlrÑ   Úcandidate_keyÚbÚdÚresults_batchÚpctrÒ   r¾   rè   Ú
next_depthÚnext_keyÚf_urlr3   Úverdictr   ræ   rê   rð   rë   rì   s&   `` `                              @@@@r   Úextract_text_from_urlr  ‰   sÕ  þè ø€ ð* €Ü“%ˆä˜3“-×&Ñ&€KàØØÜ“%ØØñ€Gô  “M×!€Gñ
 /8 	Ö*˜1��A’Ó*¸sÀA¸h¸Z€Hð
 )1×1¡  1Œj˜�mÐ1€FÑ1ô
 “5€LÜ“uÐØ€NØ$Ð÷Xñ Xðx Ðâ
”s˜7“| iÓ/Ø�‰ÓuˆÔvàˆÙœC ›J¬×)BÑ)BÒBÜ�w“<¤# e£*Ñ,¨yÒ8Ø#+§<¡<°£?Ñ ˆM˜5Ü& }Ó5ˆMØ Ñ,°Ð\aÖAbÐWXÄ*ÈQÈqÉTÕBRÒAbÑ0bØØ�L‰L˜-¨Ð/Ô0ñ œC ›J¬×)BÑ)BÒBÜ�w“<¤# e£*Ñ,¨yÓ8ñ Ùä%Ÿn™nÈ5×.QÁ4À1Àa©y¸¸A­Ó.QÐR×RˆàÐðPÙFO”c˜"œc¤# g£,°Ñ"4°yÑ"@ÓAÔBÐUV�Ù˜ ¬¨W«¨°d¸9¸+ÀVÐLÔMð 'ò 	8ˆIØ3<ò 8Ñ/�˜) ZÜ% hÓ/�Ø <Ò/°HÀFÒ4JØ—O‘O X¨zÐ$:Ô;Ø—J‘J˜xÔ(Ø4=Ð'¨Ñ1Ü.¨xÕ8Ø37Ñ0ñ8ð	8ñ Ñ'?Ü˜“L¤H×$OÑ$OÒOÙØ!ˆNØV^Ð_cÐ`cÐVd×eÉ(È%ÐQR˜5Ð"5×"9Ñ"9¸%ÀÓ"DÒEÐeˆJÑeð Ü 7¸
ÀIÓ N×N�ð  Ñ'ÐñY ”s˜7“| iÔ/ô\ œt G¨HÑ$5Ó6Ó7€GˆHÑÜ<@ÀÈÑAR×AXÑAXÓAZÓ<[Ð\_Ð]_Ð<`×a±D°A°q ¨QÓ/Óa€GˆHÑà€NðG "ûò
 +ùó
 2ùò` Bcùó /RÐRúô ò PÜ—‘Ð?È$�×OðPüó" fàNùÜò  Ü—‘Ð!NÈrÈdÐSÔTÜ›%•ûð üó bùsí   ‰A OÁ	L>Á

OÁMÁOÁ,MÂB0OÄ1MÅAOÆOÆ/M
ÇOÇ
MÇOÇAM ÈOÈ1OÈ64OÉ+2OÊNÊ;OÊ?N	 ËNËN	 ËOË*<OÌ&OÌ5
OÍOÍ M>Í:OÍ=M>Í>	OÎN	 Î		N>Î"N9Î4OÎ9N>Î>	O)r©   ró   NNNN)+Úapp.services.content.browserr   Úloggingr$   Úurllib.parser   r   rÅ   Útypingr   r   r   r	   r
   Úbs4r   rq   Úapp.core.configr   Úapp.core.llm_clientr   rd   Úapp.core.promptsr   Úapp.services.infra.databaser   Ú	getLoggerÚ__name__r(   Ústrr*   r5   rF   rC   ÚcompileÚ
IGNORECASErG   rI   rP   rm   r�   rz   r  rV   rJ   r   ú<module>r     s0  ðÝ 4Û Û ß *Û 	ß 1Õ 1Ý Û å $Ý 7Ý ?Ý 8à	ˆ×	Ñ	˜8Ó	$€ð ð ¨ó ð6˜ð ¨#ð °$ó ð. "�r—z‘zÐ"@À"Ç-Á-ÓPÐ ðA ð A¨ó Að>�Cð >˜Có >ð&'b¨dð 'b¸sð 'bÀsó 'bðR PRØW[ØCGñE Sð E°Sð EÈð EØ+.¨s©8ðEØGOÐPSÁ}ðEà-1ðEàLPÐQTÐVYÐQYÉNôErJ   