@koda-sl/baker-cli 0.457.0 → 0.458.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -10
- package/dist/account-CZWMPCRJ.js +79 -0
- package/dist/account-F2D257QB.js +49 -0
- package/dist/account-VGIKDAK3.js +80 -0
- package/dist/account-reads-OS6RLJFJ.js +218 -0
- package/dist/account-writes-7HZJWI5U.js +66 -0
- package/dist/accounts-25JDCWKR.js +75 -0
- package/dist/accounts-4IIILFLV.js +115 -0
- package/dist/accounts-6GNGCHU7.js +77 -0
- package/dist/accounts-X5MPAWZX.js +114 -0
- package/dist/accounts-Z45QINO4.js +611 -0
- package/dist/actions-CLFWCFFA.js +55 -0
- package/dist/active-entities-V2K2S3UY.js +103 -0
- package/dist/activities-CUVZGTGA.js +74 -0
- package/dist/ad-group-writes-GG3BUQQY.js +170 -0
- package/dist/ad-groups-KRZ6YX7O.js +68 -0
- package/dist/ad-writes-YDMS7NPG.js +174 -0
- package/dist/add-7P4VYS7R.js +302 -0
- package/dist/add-BHLEFGCG.js +97 -0
- package/dist/add-LHAZ34ZF.js +105 -0
- package/dist/add-PC7YGO2Z.js +104 -0
- package/dist/ads-5SGFPSQL.js +113 -0
- package/dist/ads-CP4VTPKO.js +77 -0
- package/dist/ads-FYSAF7HG.js +130 -0
- package/dist/ads-FYSAF7HG.js.map +1 -0
- package/dist/ads-Q54IPFAS.js +40 -0
- package/dist/adsets-4RXF4YXR.js +176 -0
- package/dist/advertisers-JDUAIACQ.js +125 -0
- package/dist/advertisers-JDUAIACQ.js.map +1 -0
- package/dist/advertisers-LAIOXSCF.js +120 -0
- package/dist/analytics-7M6JSC2C.js +1976 -0
- package/dist/analytics-7RGPJKUD.js +433 -0
- package/dist/animate-PYYWDHQ2.js +480 -0
- package/dist/assign-4UP6F6UX.js +77 -0
- package/dist/audience-size-AYNVGVBR.js +94 -0
- package/dist/audiences-6QF625VZ.js +268 -0
- package/dist/audiences-B2ZSJSOJ.js +78 -0
- package/dist/audiences-S6IHCG3G.js +85 -0
- package/dist/audit-43GSHWBO.js +151 -0
- package/dist/audit-IJUKCS2K.js +88 -0
- package/dist/autocomplete-YVP3WOFQ.js +111 -0
- package/dist/avatars-O64BDZHC.js +38 -0
- package/dist/bid-pricing-GMEWVIQP.js +99 -0
- package/dist/brand-W55FHWU6.js +22 -0
- package/dist/brief-QH7Z6QBP.js +491 -0
- package/dist/brief-QH7Z6QBP.js.map +1 -0
- package/dist/businesses-ZFP263RY.js +53 -0
- package/dist/campaign-groups-XPYTQFNQ.js +95 -0
- package/dist/campaign-writes-7MFHQAAI.js +165 -0
- package/dist/campaign-writes-APYVRCNH.js +170 -0
- package/dist/campaigns-ANQW3YDH.js +76 -0
- package/dist/campaigns-LXSVJEXI.js +109 -0
- package/dist/campaigns-UN3XB6TJ.js +107 -0
- package/dist/campaigns-YPI4KS7U.js +110 -0
- package/dist/canvas-GPXU2ZSC.js +46 -0
- package/dist/capabilities-HD2T6H24.js +144 -0
- package/dist/cards-QRX43TGI.js +85 -0
- package/dist/catalog-CRIYIXOJ.js +65 -0
- package/dist/catalogs-D677AN33.js +128 -0
- package/dist/changes-PWBOKKNL.js +135 -0
- package/dist/chatgpt-RYL44ASK.js +47 -0
- package/dist/chats-OQ6JOASV.js +262 -0
- package/dist/chunk-2QLNGCFZ.js +175 -0
- package/dist/chunk-2V2M2CHS.js +225 -0
- package/dist/chunk-35QYDREI.js +160 -0
- package/dist/chunk-3BG2BVWN.js +622 -0
- package/dist/chunk-3FOE4P6A.js +28 -0
- package/dist/chunk-3JX4NUGF.js +45 -0
- package/dist/chunk-3KN3VCS3.js +1711 -0
- package/dist/chunk-3QLACKNX.js +310 -0
- package/dist/chunk-54VIXBLO.js +46 -0
- package/dist/chunk-5IQL24NI.js +76 -0
- package/dist/chunk-5TOKIRCD.js +1441 -0
- package/dist/chunk-5URIFBIL.js +75 -0
- package/dist/chunk-5Z6SSOLG.js +188 -0
- package/dist/chunk-6C4S466W.js +486 -0
- package/dist/chunk-6HLPM7U3.js +191 -0
- package/dist/chunk-6R7CMUIZ.js +74 -0
- package/dist/chunk-6XPBHM5P.js +84 -0
- package/dist/chunk-6ZEFXZRD.js +274 -0
- package/dist/chunk-73JXX4LJ.js +136 -0
- package/dist/chunk-A2PTD6FL.js +166 -0
- package/dist/chunk-AGEJ7IJG.js +592 -0
- package/dist/chunk-APY53DKV.js +304 -0
- package/dist/chunk-BJ4SHBS2.js +207 -0
- package/dist/chunk-BXKJZBBJ.js +125 -0
- package/dist/chunk-C26QSQXP.js +57 -0
- package/dist/chunk-C2DAPBYB.js +4410 -0
- package/dist/chunk-D2JOBGBD.js +541 -0
- package/dist/chunk-DE64GHEP.js +74 -0
- package/dist/chunk-E4ICSNSW.js +215 -0
- package/dist/chunk-EIAA3XTT.js +275 -0
- package/dist/chunk-EMLM2TKM.js +6667 -0
- package/dist/chunk-EOWMPN3Q.js +51 -0
- package/dist/chunk-F66W4QQL.js +92 -0
- package/dist/chunk-FCADB4RG.js +535 -0
- package/dist/chunk-FCADB4RG.js.map +1 -0
- package/dist/chunk-FIBL3EZN.js +197 -0
- package/dist/chunk-FJDTR2IN.js +363 -0
- package/dist/chunk-FNKOQQ2U.js +42 -0
- package/dist/chunk-FZKC65TR.js +455 -0
- package/dist/chunk-G4PE563Y.js +309 -0
- package/dist/chunk-G6UBBPH3.js +106 -0
- package/dist/chunk-GKUIGEY6.js +56 -0
- package/dist/chunk-GQPGUJZ2.js +76 -0
- package/dist/chunk-GQPGUJZ2.js.map +1 -0
- package/dist/chunk-GWFJAVWD.js +97 -0
- package/dist/chunk-HHLEPWHB.js +433 -0
- package/dist/chunk-HMKVVPWY.js +333 -0
- package/dist/chunk-HMVVNB23.js +76 -0
- package/dist/chunk-ICFITD5H.js +31 -0
- package/dist/chunk-IDEFDDTX.js +819 -0
- package/dist/chunk-IFZD2MDE.js +474 -0
- package/dist/chunk-IFZD2MDE.js.map +1 -0
- package/dist/chunk-IH4NS3NT.js +172 -0
- package/dist/chunk-ITYFVNLA.js +846 -0
- package/dist/chunk-IX6PPPOS.js +346 -0
- package/dist/chunk-JM6XT33N.js +1079 -0
- package/dist/chunk-JTSYEIHY.js +103 -0
- package/dist/chunk-K63BDJYR.js +136 -0
- package/dist/chunk-KNX2UBLI.js +610 -0
- package/dist/chunk-KPHBHRJY.js +149 -0
- package/dist/chunk-KWMCE3Q2.js +23 -0
- package/dist/chunk-KYQZM5FB.js +1830 -0
- package/dist/chunk-LAAJ3V67.js +159 -0
- package/dist/chunk-LCMGWIEQ.js +296 -0
- package/dist/chunk-MNCMAJKB.js +337 -0
- package/dist/chunk-N3DLCXMB.js +98 -0
- package/dist/chunk-NKU7RLAI.js +169 -0
- package/dist/chunk-NWAFH7K3.js +98 -0
- package/dist/chunk-NYVO4TND.js +26 -0
- package/dist/chunk-O5MNUL7C.js +509 -0
- package/dist/chunk-O5RBFETA.js +665 -0
- package/dist/chunk-ODMKCAAQ.js +111 -0
- package/dist/chunk-OIS3WG2U.js +65 -0
- package/dist/chunk-OIS3WG2U.js.map +1 -0
- package/dist/chunk-OL4RPZ64.js +96 -0
- package/dist/chunk-OP2OMNVN.js +106 -0
- package/dist/chunk-P5V7NXMY.js +25 -0
- package/dist/chunk-Q3PB5ZUT.js +172 -0
- package/dist/chunk-QT7FKFNS.js +60 -0
- package/dist/chunk-R5VW2ACF.js +494 -0
- package/dist/chunk-RGE7STV4.js +3153 -0
- package/dist/chunk-RIXGQIMZ.js +84 -0
- package/dist/chunk-RWEVT5SO.js +133 -0
- package/dist/chunk-S3TSJQOJ.js +96 -0
- package/dist/chunk-SMVHUBXE.js +66 -0
- package/dist/chunk-SZX5LUHK.js +94 -0
- package/dist/chunk-TG63NMMW.js +58 -0
- package/dist/chunk-TOYLG3ZY.js +214 -0
- package/dist/chunk-TZKJNSZH.js +55 -0
- package/dist/chunk-U57KAPAJ.js +74 -0
- package/dist/chunk-VAOMHPEX.js +936 -0
- package/dist/chunk-VAOMHPEX.js.map +1 -0
- package/dist/chunk-VFME5MT5.js +297 -0
- package/dist/chunk-WCHPZD64.js +5189 -0
- package/dist/chunk-WHOS7L4N.js +19 -0
- package/dist/chunk-WOB3ESBQ.js +67 -0
- package/dist/chunk-XM75RTFZ.js +1599 -0
- package/dist/chunk-XUGTYYXY.js +174 -0
- package/dist/chunk-XX5DXKEI.js +206 -0
- package/dist/chunk-Y4V55WFQ.js +23992 -0
- package/dist/chunk-YKSIHGIL.js +454 -0
- package/dist/chunk-YOLNNZ77.js +382 -0
- package/dist/chunk-ZJVHZHYP.js +6188 -0
- package/dist/chunk-ZJVHZHYP.js.map +1 -0
- package/dist/chunk-ZW6EMA2U.js +139 -0
- package/dist/chunk-ZZFYDAJQ.js +981 -0
- package/dist/chunk-ZZFYDAJQ.js.map +1 -0
- package/dist/claim-B6LWKULS.js +197 -0
- package/dist/cli.js +36 -36
- package/dist/client-DUZXHJAT.js +19 -0
- package/dist/code-BQCH5ARZ.js +76 -0
- package/dist/companies-BC6TCRXY.js +195 -0
- package/dist/complete-Y7ZAKY62.js +81 -0
- package/dist/config-2NKM5GSZ.js +88 -0
- package/dist/connect-4WXSJAIK.js +42 -0
- package/dist/content-MJ3W6FKW.js +85 -0
- package/dist/content-MJ3W6FKW.js.map +1 -0
- package/dist/conversation-SEVRAH5J.js +93 -0
- package/dist/conversion-report-KLP2PJKJ.js +125 -0
- package/dist/conversion-upload-command-KB6ME34E.js +438 -0
- package/dist/conversion-writes-LO24U7NW.js +120 -0
- package/dist/conversions-OC2UHXG7.js +135 -0
- package/dist/conversions-QBWE4C5T.js +76 -0
- package/dist/countries-F67IA6DF.js +82 -0
- package/dist/create-BPHBGMFM.js +59 -0
- package/dist/create-IGZH5OFP.js +235 -0
- package/dist/create-NLNOXZ2T.js +130 -0
- package/dist/create-YX3YN4SU.js +207 -0
- package/dist/creatives-4N6RRVBP.js +87 -0
- package/dist/creatives-ESTFGCVQ.js +182 -0
- package/dist/creatives-ZDWQ7AP3.js +51 -0
- package/dist/credentials-GA4ZW4QL.js +287 -0
- package/dist/critique-IJG6WBDW.js +1449 -0
- package/dist/critique-SYBZXBHJ.js +213 -0
- package/dist/crm-2P7WHZBW.js +224 -0
- package/dist/crm-SVDB2XHW.js +318 -0
- package/dist/crop-ZAYOFRSS.js +113 -0
- package/dist/currency-UWW5QJGL.js +88 -0
- package/dist/custom-conversions-2BG2VUOY.js +125 -0
- package/dist/customers-2LZ3HG3V.js +56 -0
- package/dist/delete-A3JJJHC6.js +63 -0
- package/dist/delete-ER3LIA3F.js +71 -0
- package/dist/delete-HVCQQSG4.js +71 -0
- package/dist/delete-QPPPLHS4.js +54 -0
- package/dist/delete-T22GD7FG.js +69 -0
- package/dist/delete-VZUF57KL.js +66 -0
- package/dist/demographics-3KAK7JGZ.js +138 -0
- package/dist/describe-ADQWM55K.js +97 -0
- package/dist/describe-XEGTSEUI.js +97 -0
- package/dist/dimensions-WQZBBY7I.js +101 -0
- package/dist/discard-VU72HO4B.js +66 -0
- package/dist/discover-HXKIQWH2.js +172 -0
- package/dist/domain-ERWROFZC.js +98 -0
- package/dist/download-7Z3BWAHR.js +299 -0
- package/dist/draft-3V3HOB5N.js +217 -0
- package/dist/draft-4TCT6UQD.js +103 -0
- package/dist/draft-5YJY6YT5.js +328 -0
- package/dist/draft-7LIGULA2.js +100 -0
- package/dist/draft-C2DDLJH2.js +307 -0
- package/dist/draft-FCCYMFKW.js +281 -0
- package/dist/draft-FELTG6LD.js +306 -0
- package/dist/draft-JIFFSL5U.js +233 -0
- package/dist/engine/index.js +13 -13
- package/dist/env-IUF4DTG2.js +43 -0
- package/dist/experiment-T3H6LFLK.js +1397 -0
- package/dist/export-V7WQJD4L.js +168 -0
- package/dist/extract-WW7WLLRB.js +113 -0
- package/dist/facets-J4JLHESD.js +114 -0
- package/dist/favorites-XLCT5S6E.js +178 -0
- package/dist/feed-PG6AXUYA.js +179 -0
- package/dist/feed-PG6AXUYA.js.map +1 -0
- package/dist/feedback-J6FSCCWF.js +66 -0
- package/dist/feedback-record-4AY3MG2E.js +175 -0
- package/dist/fetch-C4JYRA53.js +139 -0
- package/dist/fetch-QIHGIATQ.js +176 -0
- package/dist/find-SLZO4FBR.js +134 -0
- package/dist/fit-SSR3JDDI.js +45 -0
- package/dist/flows-IRFHGBUR.js +247 -0
- package/dist/follow-EKNBOS7Z.js +82 -0
- package/dist/follow-EKNBOS7Z.js.map +1 -0
- package/dist/follow-competitors-OYQXYSI5.js +131 -0
- package/dist/follow-competitors-OYQXYSI5.js.map +1 -0
- package/dist/following-M3C3XSHY.js +113 -0
- package/dist/following-M3C3XSHY.js.map +1 -0
- package/dist/fonts-ZU5Q4A6U.js +34 -0
- package/dist/forecast-KVPAU7OT.js +104 -0
- package/dist/forecast-XGBT722Z.js +168 -0
- package/dist/funding-BWSGYQPP.js +85 -0
- package/dist/funnel-JXC5WXLR.js +394 -0
- package/dist/ga4-XWK5H7HN.js +55 -0
- package/dist/generate-E6YDVJ23.js +262 -0
- package/dist/get-23E7ZEKP.js +56 -0
- package/dist/get-HXJSMHDC.js +61 -0
- package/dist/get-IWYF6PA4.js +78 -0
- package/dist/get-JGCMBA7Y.js +63 -0
- package/dist/get-QLFMRSLN.js +59 -0
- package/dist/get-XWIA6BEO.js +63 -0
- package/dist/get-ZLSZTGXA.js +92 -0
- package/dist/gif-KHF452GC.js +94 -0
- package/dist/google-5GESCEKC.js +136 -0
- package/dist/google-KTDHD75U.js +3190 -0
- package/dist/google-ads-VW5UGOGO.js +195 -0
- package/dist/group-JYNKEOPG.js +43 -0
- package/dist/group-ZMCHUQMI.js +43 -0
- package/dist/gsc-I6RIFE7I.js +30 -0
- package/dist/history-F3N6M4JM.js +67 -0
- package/dist/history-IAKV2LJN.js +133 -0
- package/dist/hubspot-QVDLE3BF.js +779 -0
- package/dist/icon-4C2JOBPE.js +137 -0
- package/dist/ideas-7COQX7C3.js +32 -0
- package/dist/images-5XNXXZLM.js +90 -0
- package/dist/improve-6VC6ORAG.js +86 -0
- package/dist/ingest-L6MMXL6R.js +23 -0
- package/dist/ingest-UP42M3WU.js +107 -0
- package/dist/insights-3H736QZH.js +122 -0
- package/dist/insights-3TEVP4B2.js +423 -0
- package/dist/inspect-2WOLOPTV.js +189 -0
- package/dist/inspiration-I2XMXXEW.js +53 -0
- package/dist/intent-IYUKLNST.js +95 -0
- package/dist/job-MTHK2PPN.js +202 -0
- package/dist/job-create-AD3L6A6F.js +109 -0
- package/dist/job-status-MCFOA5V3.js +74 -0
- package/dist/keep-NNATO2B6.js +78 -0
- package/dist/keyword-gap-RSITTJ56.js +130 -0
- package/dist/keyword-metrics-E5D2ESIH.js +197 -0
- package/dist/keywords-for-site-SRUKRBCR.js +128 -0
- package/dist/landing-ZFZ4LYCB.js +28 -0
- package/dist/landings-JJELYF4P.js +143 -0
- package/dist/landings-JJELYF4P.js.map +1 -0
- package/dist/languages-3FETIHSB.js +55 -0
- package/dist/languages-SJ6ZWALA.js +40 -0
- package/dist/launches-ZZ7ST4SY.js +108 -0
- package/dist/launches-ZZ7ST4SY.js.map +1 -0
- package/dist/layerize-NSF6NEWY.js +190 -0
- package/dist/leads-EVFLPNG6.js +293 -0
- package/dist/leads-P46ND2LT.js +90 -0
- package/dist/learn-EJSMXKSE.js +203 -0
- package/dist/library-UKGHGGOG.js +109 -0
- package/dist/lighthouse-B2IBNWLX.js +75 -0
- package/dist/line-item-writes-EM6I5KIH.js +208 -0
- package/dist/line-items-GLNMIKM5.js +103 -0
- package/dist/link-CZ4XFU2D.js +64 -0
- package/dist/linkedin-ES3KPPHF.js +1008 -0
- package/dist/list-3IJMAQHX.js +51 -0
- package/dist/list-B7GN44R3.js +63 -0
- package/dist/list-BDPUPCQZ.js +57 -0
- package/dist/list-HGQY4OHN.js +151 -0
- package/dist/list-JD7EIBPU.js +303 -0
- package/dist/list-NZZ75O6C.js +65 -0
- package/dist/list-XBYKFXS3.js +76 -0
- package/dist/list-XRON5I6M.js +50 -0
- package/dist/locations-M2665NGJ.js +40 -0
- package/dist/locations-TKPILKLO.js +67 -0
- package/dist/log-KUHIZL53.js +92 -0
- package/dist/logo-EVE3TAL5.js +18 -0
- package/dist/lookup-Z5TFHYDU.js +22 -0
- package/dist/make-ISUBJILH.js +634 -0
- package/dist/make-ISUBJILH.js.map +1 -0
- package/dist/map-NNMEQE6E.js +404 -0
- package/dist/mcp-AJDXG3NV.js +463 -0
- package/dist/media-DGNFOT7M.js +89 -0
- package/dist/media-FRNTELKI.js +15 -0
- package/dist/media-sources-V452RJ6B.js +32 -0
- package/dist/members-VUJ62KJQ.js +44 -0
- package/dist/meta-NGBLUNVU.js +69 -0
- package/dist/metrics-QRRK473C.js +152 -0
- package/dist/models-4BTFIZZZ.js +155 -0
- package/dist/normalize-O47OIEUO.js +806 -0
- package/dist/normalize-OTNIBYUO.js +141 -0
- package/dist/options-FTXE6HFT.js +121 -0
- package/dist/output-W473ZLGR.js +24 -0
- package/dist/page-7TEJ5NA5.js +105 -0
- package/dist/pages-S2E3FHDH.js +66 -0
- package/dist/performance-IMD2N7OD.js +24 -0
- package/dist/pinterest-DQC3JXQT.js +96 -0
- package/dist/pixels-M5GS7DIH.js +60 -0
- package/dist/pixels-MHH4N35F.js +108 -0
- package/dist/plan-GGLBOLIU.js +207 -0
- package/dist/preview-AHDEJO2X.js +169 -0
- package/dist/profile-2TPKFD4Y.js +131 -0
- package/dist/profile-2TPKFD4Y.js.map +1 -0
- package/dist/promoted-tweet-writes-XUM4WGZH.js +180 -0
- package/dist/promoted-tweets-TIHMJ5PU.js +119 -0
- package/dist/properties-KOY2GE45.js +101 -0
- package/dist/publish-Q7MHUVVZ.js +322 -0
- package/dist/query-EO7BX4MH.js +236 -0
- package/dist/query-QZWMKAJZ.js +247 -0
- package/dist/query-XLTRAAQO.js +61 -0
- package/dist/questionnaire-BL3G6UYL.js +93 -0
- package/dist/read-ZMNYD3PA.js +141 -0
- package/dist/reddit-RO3VKMJ7.js +26 -0
- package/dist/refresh-OER4JT3Q.js +94 -0
- package/dist/release-4DLGFSYY.js +60 -0
- package/dist/relevant-pages-ZGGWFDLL.js +99 -0
- package/dist/remove-MQ3JY36P.js +48 -0
- package/dist/remove-WENY6GQU.js +50 -0
- package/dist/reopen-VSVK5EZI.js +63 -0
- package/dist/rerun-B32CIKVH.js +85 -0
- package/dist/research-2XAQRMC3.js +62 -0
- package/dist/research-ZEIW667A.js +262 -0
- package/dist/reset-DXBESBL7.js +54 -0
- package/dist/reset-G4A52FMR.js +54 -0
- package/dist/resolve-FA6CFVX4.js +121 -0
- package/dist/retry-CXTOGFZT.js +60 -0
- package/dist/retry-THMD27CA.js +62 -0
- package/dist/retry-failed-3R35LZ6I.js +45 -0
- package/dist/run-HRSNUGWJ.js +60 -0
- package/dist/run-NYJUDS32.js +65 -0
- package/dist/scaffold-ad-RUJQIYQH.js +1549 -0
- package/dist/scaffold-static-ad-36KRZNWU.js +973 -0
- package/dist/scaffold-static-ad-36KRZNWU.js.map +1 -0
- package/dist/scaffold-video-RYVG4IFA.js +873 -0
- package/dist/schedule-ENH4N66R.js +61 -0
- package/dist/schedule-KSIMVG5U.js +66 -0
- package/dist/scheduled-agents-RM24BRGD.js +49 -0
- package/dist/schema-JJVSWKGP.js +132 -0
- package/dist/schema-MT76C7XK.js +63 -0
- package/dist/scrape-ICS5UA62.js +2241 -0
- package/dist/screenshot-YPUFPY5W.js +102 -0
- package/dist/search-6BHODVNN.js +27 -0
- package/dist/search-HOQZ3XIK.js +92 -0
- package/dist/search-KFP4GPMJ.js +174 -0
- package/dist/search-TCDI77HS.js +91 -0
- package/dist/search-W7E5S347.js +224 -0
- package/dist/search-copy-LXBPFQC3.js +115 -0
- package/dist/search-copy-LXBPFQC3.js.map +1 -0
- package/dist/seeds-Q6VS27LT.js +101 -0
- package/dist/seeds-Q6VS27LT.js.map +1 -0
- package/dist/select-CB5BBKHR.js +94 -0
- package/dist/sends-HDTG7XRL.js +309 -0
- package/dist/sequences-SISO5JGS.js +146 -0
- package/dist/set-prompt-IS7JSFYY.js +186 -0
- package/dist/setup-links-NBO7B64S.js +141 -0
- package/dist/shared-MJEF5Z6R.js +31 -0
- package/dist/site-5MPVICS2.js +177 -0
- package/dist/sitemaps-G3YCZDEM.js +87 -0
- package/dist/sites-YH2ZIMCP.js +101 -0
- package/dist/skills-SQTM676S.js +47 -0
- package/dist/sources-FSYJ2EQL.js +421 -0
- package/dist/sources-OES5YJY6.js +30 -0
- package/dist/stale-YT3CGHAI.js +63 -0
- package/dist/stats-IDL2KWGM.js +34 -0
- package/dist/status-ZDKRO7TH.js +62 -0
- package/dist/sticker-2R5LODHJ.js +94 -0
- package/dist/stock-5DWDTNFB.js +179 -0
- package/dist/studio-PPFFAUWO.js +49 -0
- package/dist/sync-7DWOHCVF.js +265 -0
- package/dist/sync-JRRGJWFH.js +54 -0
- package/dist/sync-RNKJFPDI.js +54 -0
- package/dist/sync-definition-SDJM567M.js +53 -0
- package/dist/tag-manager-MBS6YCZM.js +43 -0
- package/dist/tags-5EVJ73IX.js +26 -0
- package/dist/tags-5THBYIHN.js +20 -0
- package/dist/tags-D74DMYCW.js +20 -0
- package/dist/tags-KFDRX4H7.js +20 -0
- package/dist/tags-YEWHOSC5.js +224 -0
- package/dist/targeting-constants-KIMGCL2N.js +118 -0
- package/dist/targeting-criteria-5BN4EELG.js +113 -0
- package/dist/targets-DBQNYE34.js +102 -0
- package/dist/templates-J2NZIOY2.js +178 -0
- package/dist/testimonials-2IJE6K2L.js +32 -0
- package/dist/themes-6BRONMJ2.js +94 -0
- package/dist/top-companies-TAA7HKTA.js +108 -0
- package/dist/translate-ZVQQJJQT.js +598 -0
- package/dist/triggers-2VPLSNP7.js +141 -0
- package/dist/tweets-YEMIGLP7.js +123 -0
- package/dist/unfollow-S6XQVQIA.js +46 -0
- package/dist/unlink-DDTHG25I.js +67 -0
- package/dist/unstage-ABSCDW43.js +84 -0
- package/dist/unstage-VDLTHNHY.js +95 -0
- package/dist/update-C2QWKWXB.js +98 -0
- package/dist/update-COMDOEYG.js +142 -0
- package/dist/update-FMROPHXQ.js +155 -0
- package/dist/update-SY54UP5O.js +63 -0
- package/dist/upload-7QBWAQLM.js +126 -0
- package/dist/upload-PTXQ7IEE.js +150 -0
- package/dist/upscale-YQ2KZRCI.js +109 -0
- package/dist/url-options-QRGN4Q34.js +130 -0
- package/dist/url-params-ZBOCO4CG.js +98 -0
- package/dist/use-BFD6WCDB.js +95 -0
- package/dist/validate-M7B2SBDH.js +89 -0
- package/dist/variant-U77I2UDY.js +338 -0
- package/dist/videos-2EPKRCW6.js +39 -0
- package/dist/view-BDLUTKMK.js +121 -0
- package/dist/viewer-check-YF6ENNNL.js +69 -0
- package/dist/web-JRDXE2GL.js +140 -0
- package/dist/winners-AWRCSFAC.js +113 -0
- package/dist/winners-AWRCSFAC.js.map +1 -0
- package/dist/winning-ads-QQ6Y5R7T.js +79 -0
- package/dist/winning-ads-QQ6Y5R7T.js.map +1 -0
- package/dist/write-commands-3UXPJJ75.js +1015 -0
- package/dist/write-commands-HZI5YF52.js +737 -0
- package/dist/write-commands-MYA273DT.js +206 -0
- package/dist/write-commands-OQPBPQ77.js +713 -0
- package/dist/x-WA3Q46K6.js +47 -0
- package/package.json +1 -1
- package/dist/account-AO6PNNPO.js +0 -79
- package/dist/account-F6MHQ64I.js +0 -80
- package/dist/account-IEVEOFIK.js +0 -49
- package/dist/account-reads-W3XO7CIW.js +0 -218
- package/dist/account-writes-ISE4U2FQ.js +0 -66
- package/dist/accounts-4CDRK4S4.js +0 -75
- package/dist/accounts-4TM4UZEJ.js +0 -77
- package/dist/accounts-5QDKBZMH.js +0 -115
- package/dist/accounts-KYJO3JC6.js +0 -114
- package/dist/accounts-XDC26KK6.js +0 -611
- package/dist/actions-CJB4GZYA.js +0 -55
- package/dist/active-entities-YUSOUR5M.js +0 -103
- package/dist/activities-ZVQOBHZH.js +0 -74
- package/dist/ad-group-writes-UU5NNKSO.js +0 -170
- package/dist/ad-groups-CGFXZNIA.js +0 -68
- package/dist/ad-writes-NALDYWOR.js +0 -174
- package/dist/add-577QWMCF.js +0 -97
- package/dist/add-BP4CUUKB.js +0 -104
- package/dist/add-CUWAW7KG.js +0 -105
- package/dist/add-RYR3QGOP.js +0 -302
- package/dist/ads-GII5LEOH.js +0 -40
- package/dist/ads-PV7GZXPR.js +0 -77
- package/dist/ads-SATXH4OD.js +0 -113
- package/dist/adsets-PMXNW3EG.js +0 -176
- package/dist/advertisers-GM5UFI44.js +0 -120
- package/dist/advertisers-YMHPZYNF.js +0 -111
- package/dist/advertisers-YMHPZYNF.js.map +0 -1
- package/dist/analytics-4CD7CH7H.js +0 -433
- package/dist/analytics-JDPRLCHQ.js +0 -1976
- package/dist/animate-FWWHSYPL.js +0 -480
- package/dist/assign-XI4HIIIM.js +0 -77
- package/dist/audience-size-DHZ65PGA.js +0 -94
- package/dist/audiences-D7CXP3ZF.js +0 -78
- package/dist/audiences-HFRVIE2X.js +0 -85
- package/dist/audiences-VEKGLWBG.js +0 -268
- package/dist/audit-AD4ZZNJN.js +0 -88
- package/dist/audit-M7BZYBIS.js +0 -151
- package/dist/autocomplete-B45I26R4.js +0 -111
- package/dist/avatars-75BALNB7.js +0 -38
- package/dist/bid-pricing-4RCP24BE.js +0 -99
- package/dist/brand-2TOLKYDY.js +0 -22
- package/dist/brief-5G4NEZ4O.js +0 -477
- package/dist/brief-5G4NEZ4O.js.map +0 -1
- package/dist/businesses-JBDQZZIG.js +0 -53
- package/dist/campaign-groups-G7B26V7K.js +0 -95
- package/dist/campaign-writes-HZ7ART7W.js +0 -165
- package/dist/campaign-writes-SX52CJYW.js +0 -170
- package/dist/campaigns-MEEM3CNC.js +0 -110
- package/dist/campaigns-OIJGTO7W.js +0 -107
- package/dist/campaigns-RW3QJQ2F.js +0 -109
- package/dist/campaigns-X3DPCCSN.js +0 -76
- package/dist/canvas-LXRLDE5G.js +0 -46
- package/dist/capabilities-BWSB7IFT.js +0 -144
- package/dist/cards-4477BFEV.js +0 -85
- package/dist/catalog-BKQOAVR2.js +0 -65
- package/dist/catalogs-G3YEVG6S.js +0 -128
- package/dist/changes-AYLNY6KN.js +0 -135
- package/dist/chatgpt-TNSRA3L3.js +0 -47
- package/dist/chats-MMHBJQFM.js +0 -262
- package/dist/chunk-24GELJP3.js +0 -622
- package/dist/chunk-2BKVVCCV.js +0 -133
- package/dist/chunk-2D4ASTGN.js +0 -159
- package/dist/chunk-2K4LMCV2.js +0 -197
- package/dist/chunk-2TK4DEOE.js +0 -74
- package/dist/chunk-2YJL6MEB.js +0 -51
- package/dist/chunk-2ZY7HON7.js +0 -174
- package/dist/chunk-35XWJEWH.js +0 -274
- package/dist/chunk-3EUFAONS.js +0 -5189
- package/dist/chunk-45OQOUJA.js +0 -6176
- package/dist/chunk-45OQOUJA.js.map +0 -1
- package/dist/chunk-4KCKNAVW.js +0 -665
- package/dist/chunk-4NDCTLI5.js +0 -191
- package/dist/chunk-4QEY2L6U.js +0 -486
- package/dist/chunk-54UVGFX3.js +0 -440
- package/dist/chunk-54UVGFX3.js.map +0 -1
- package/dist/chunk-5FWMRTNB.js +0 -846
- package/dist/chunk-5YGBRHO6.js +0 -96
- package/dist/chunk-5Z2KSVIV.js +0 -67
- package/dist/chunk-6B7VKFS4.js +0 -172
- package/dist/chunk-6G5K6ZC6.js +0 -46
- package/dist/chunk-75FDTDLH.js +0 -96
- package/dist/chunk-7OFNN4CT.js +0 -58
- package/dist/chunk-7TNPEURJ.js +0 -1079
- package/dist/chunk-7ZHMGXE2.js +0 -819
- package/dist/chunk-BNCW67EM.js +0 -534
- package/dist/chunk-BNCW67EM.js.map +0 -1
- package/dist/chunk-BSOWFWEU.js +0 -509
- package/dist/chunk-C6U6UDHJ.js +0 -106
- package/dist/chunk-D23EKXOP.js +0 -275
- package/dist/chunk-D7PFF6UC.js +0 -346
- package/dist/chunk-DT2XXF6O.js +0 -455
- package/dist/chunk-DTD2IRDA.js +0 -149
- package/dist/chunk-DYOI4FQ7.js +0 -169
- package/dist/chunk-DZSIUEGH.js +0 -136
- package/dist/chunk-EFM7OODQ.js +0 -433
- package/dist/chunk-EFSYTROP.js +0 -94
- package/dist/chunk-EIIANSBX.js +0 -310
- package/dist/chunk-EYK6G4XE.js +0 -57
- package/dist/chunk-EZWYX343.js +0 -6667
- package/dist/chunk-F2S5BLPE.js +0 -97
- package/dist/chunk-GP7QUF5A.js +0 -363
- package/dist/chunk-H26L3OQY.js +0 -42
- package/dist/chunk-HOXZOOVA.js +0 -3153
- package/dist/chunk-HRYOZUSK.js +0 -1711
- package/dist/chunk-HTW7FTP4.js +0 -45
- package/dist/chunk-IFWP3ZDO.js +0 -215
- package/dist/chunk-JERXV5A3.js +0 -26
- package/dist/chunk-JU7FIHQ2.js +0 -103
- package/dist/chunk-KFE3S63W.js +0 -1441
- package/dist/chunk-KLBTXETO.js +0 -23992
- package/dist/chunk-KLNKEVIY.js +0 -136
- package/dist/chunk-KN7DFVKW.js +0 -92
- package/dist/chunk-KNY76UGC.js +0 -84
- package/dist/chunk-KQTYTCKQ.js +0 -98
- package/dist/chunk-KS2QY6MK.js +0 -139
- package/dist/chunk-LERTEZST.js +0 -28
- package/dist/chunk-LI6PGY3L.js +0 -337
- package/dist/chunk-LS5MTRFI.js +0 -106
- package/dist/chunk-LYBXCXJB.js +0 -160
- package/dist/chunk-MCWYIEWI.js +0 -111
- package/dist/chunk-MFQ5SUJT.js +0 -207
- package/dist/chunk-MZL4H5PY.js +0 -225
- package/dist/chunk-NAWWC5I3.js +0 -454
- package/dist/chunk-NU7JSWVA.js +0 -946
- package/dist/chunk-NU7JSWVA.js.map +0 -1
- package/dist/chunk-OW4XDJV2.js +0 -494
- package/dist/chunk-PBA3YCZA.js +0 -76
- package/dist/chunk-PHEEJUFL.js +0 -19
- package/dist/chunk-PWGWMIIJ.js +0 -25
- package/dist/chunk-PZ7EPJRF.js +0 -60
- package/dist/chunk-QM6T4BFA.js +0 -76
- package/dist/chunk-QNDLOCAI.js +0 -382
- package/dist/chunk-QSN5MZGS.js +0 -74
- package/dist/chunk-RX6F3GEA.js +0 -610
- package/dist/chunk-STIB5DR5.js +0 -56
- package/dist/chunk-TD2P4HBP.js +0 -55
- package/dist/chunk-TEINLN64.js +0 -649
- package/dist/chunk-TEINLN64.js.map +0 -1
- package/dist/chunk-ULJ5RFOV.js +0 -76
- package/dist/chunk-ULJ5RFOV.js.map +0 -1
- package/dist/chunk-UZUHXN7H.js +0 -23
- package/dist/chunk-V5QJBDBC.js +0 -175
- package/dist/chunk-VAJBWA2U.js +0 -75
- package/dist/chunk-VB6WJJR3.js +0 -1830
- package/dist/chunk-VFIOJD5M.js +0 -309
- package/dist/chunk-VVZBBKRC.js +0 -296
- package/dist/chunk-W3B4BHQU.js +0 -31
- package/dist/chunk-WBUMUELD.js +0 -541
- package/dist/chunk-WEU6GKUQ.js +0 -297
- package/dist/chunk-WNFIJAUO.js +0 -98
- package/dist/chunk-WS2GMOSG.js +0 -74
- package/dist/chunk-WTOYCUKH.js +0 -84
- package/dist/chunk-X5VHGXFB.js +0 -206
- package/dist/chunk-XBOK23HI.js +0 -214
- package/dist/chunk-XJRBFLBD.js +0 -304
- package/dist/chunk-XL3BMSYG.js +0 -125
- package/dist/chunk-XPOIIQCI.js +0 -1599
- package/dist/chunk-XY7BZSDQ.js +0 -166
- package/dist/chunk-Y63Y4QSY.js +0 -592
- package/dist/chunk-YBMVHGY3.js +0 -333
- package/dist/chunk-YKFEHWHC.js +0 -188
- package/dist/chunk-YRVDCIJ2.js +0 -172
- package/dist/chunk-ZIHEIVJC.js +0 -4410
- package/dist/chunk-ZZQXVYUY.js +0 -66
- package/dist/claim-UDNXCJIU.js +0 -197
- package/dist/client-VHN32ZRE.js +0 -19
- package/dist/code-YS6ZMGX3.js +0 -76
- package/dist/companies-RPZN4JTR.js +0 -195
- package/dist/complete-HLSRQE3N.js +0 -81
- package/dist/config-CQN5FHMZ.js +0 -88
- package/dist/connect-TIJZO7OV.js +0 -42
- package/dist/content-3ZPP4SW2.js +0 -81
- package/dist/content-3ZPP4SW2.js.map +0 -1
- package/dist/conversation-BS7TF6LZ.js +0 -93
- package/dist/conversion-report-N4HANYOB.js +0 -125
- package/dist/conversion-upload-command-CSD344BY.js +0 -438
- package/dist/conversion-writes-LW4HQKK5.js +0 -120
- package/dist/conversions-FEWAX64H.js +0 -135
- package/dist/conversions-TS723AZV.js +0 -76
- package/dist/countries-ESAKTQAE.js +0 -82
- package/dist/create-4SH4BZXP.js +0 -235
- package/dist/create-B4UVIJZI.js +0 -207
- package/dist/create-F5FNHJKE.js +0 -130
- package/dist/create-L3JHP6J4.js +0 -59
- package/dist/creatives-LK2NZHRX.js +0 -51
- package/dist/creatives-RQ2DFHGI.js +0 -87
- package/dist/creatives-UILBPOKO.js +0 -182
- package/dist/credentials-GC34ZPYP.js +0 -287
- package/dist/critique-FAOH4FCP.js +0 -213
- package/dist/critique-X7HYYEJ4.js +0 -1449
- package/dist/crm-RB5TVV3M.js +0 -318
- package/dist/crm-XEQDSU2U.js +0 -224
- package/dist/crop-2J4RKJ6B.js +0 -113
- package/dist/currency-47QHFHDA.js +0 -88
- package/dist/custom-conversions-7HOWR6X6.js +0 -125
- package/dist/customers-P5WOUY6G.js +0 -56
- package/dist/delete-3AV3ARMX.js +0 -71
- package/dist/delete-A5XD52K4.js +0 -69
- package/dist/delete-DLQ2ZZDA.js +0 -66
- package/dist/delete-GZKGPBBD.js +0 -54
- package/dist/delete-MRM2S4Z5.js +0 -71
- package/dist/delete-SW5OSIGN.js +0 -63
- package/dist/demographics-ZLZOIWUE.js +0 -138
- package/dist/describe-6NVOWIBE.js +0 -97
- package/dist/describe-P3PPIQME.js +0 -97
- package/dist/dimensions-EYAYO27I.js +0 -101
- package/dist/discard-VNVNJWNX.js +0 -66
- package/dist/discover-P6TUGZ45.js +0 -172
- package/dist/domain-OBICRJP3.js +0 -98
- package/dist/download-7T6HIJKD.js +0 -299
- package/dist/draft-3ZVCQSAG.js +0 -103
- package/dist/draft-666YKA3P.js +0 -307
- package/dist/draft-KIBMVKSR.js +0 -100
- package/dist/draft-THPZFASM.js +0 -281
- package/dist/draft-UECMJ6B4.js +0 -217
- package/dist/draft-V75RBORX.js +0 -328
- package/dist/draft-YDY2Q2K6.js +0 -233
- package/dist/draft-Z3VUQ4LJ.js +0 -306
- package/dist/env-KIP2J2UB.js +0 -43
- package/dist/experiment-PYQPAZZN.js +0 -1397
- package/dist/export-JQRMBJ7D.js +0 -168
- package/dist/extract-7QKLME7Z.js +0 -113
- package/dist/facets-7J66L3NK.js +0 -114
- package/dist/favorites-NSGF4OV5.js +0 -178
- package/dist/feed-RV4KPPXM.js +0 -157
- package/dist/feed-RV4KPPXM.js.map +0 -1
- package/dist/feedback-3LDG4WVJ.js +0 -66
- package/dist/feedback-record-CTAUHLMF.js +0 -175
- package/dist/fetch-4RXXZGO4.js +0 -176
- package/dist/fetch-XF76JABV.js +0 -139
- package/dist/find-FRRHAPWX.js +0 -134
- package/dist/fit-7237F5EB.js +0 -45
- package/dist/flows-GGB46DLH.js +0 -247
- package/dist/follow-FN7LV3WV.js +0 -82
- package/dist/follow-FN7LV3WV.js.map +0 -1
- package/dist/follow-competitors-MKXFHLXB.js +0 -131
- package/dist/follow-competitors-MKXFHLXB.js.map +0 -1
- package/dist/following-RKC2FLHI.js +0 -109
- package/dist/following-RKC2FLHI.js.map +0 -1
- package/dist/fonts-S3GWKOUJ.js +0 -34
- package/dist/forecast-6JVKXNFB.js +0 -104
- package/dist/forecast-SVD3LMMY.js +0 -168
- package/dist/funding-NYO65IMT.js +0 -85
- package/dist/funnel-RCQOIQZ5.js +0 -394
- package/dist/ga4-U6DMJ6K4.js +0 -55
- package/dist/generate-MMBYB44G.js +0 -262
- package/dist/get-3L6W3E3T.js +0 -59
- package/dist/get-6WEYKOY6.js +0 -63
- package/dist/get-KQSM2MXM.js +0 -78
- package/dist/get-MN2GRXGZ.js +0 -56
- package/dist/get-P743RPSI.js +0 -63
- package/dist/get-XN5ZGDIK.js +0 -61
- package/dist/get-YIHTAMRT.js +0 -92
- package/dist/gif-53ULFVLR.js +0 -94
- package/dist/google-CI2AMJKN.js +0 -3190
- package/dist/google-QYYYSSXI.js +0 -136
- package/dist/google-ads-YAJ33LTW.js +0 -195
- package/dist/group-EFZ3AJIE.js +0 -43
- package/dist/group-GKWCCX66.js +0 -43
- package/dist/gsc-TAKOF7J5.js +0 -30
- package/dist/history-DBIEYQHY.js +0 -67
- package/dist/history-QWYLRQBO.js +0 -133
- package/dist/hubspot-DDONLJ7R.js +0 -779
- package/dist/icon-L4RSS6PZ.js +0 -137
- package/dist/ideas-N44Z2SIT.js +0 -32
- package/dist/images-BKHCBCE7.js +0 -90
- package/dist/improve-BCCU5AS6.js +0 -86
- package/dist/ingest-HBECWLOB.js +0 -107
- package/dist/ingest-VNRTFD4Y.js +0 -23
- package/dist/insights-NZWSJKU4.js +0 -423
- package/dist/insights-ZIU6FJMS.js +0 -122
- package/dist/inspect-7GGJDJBJ.js +0 -189
- package/dist/inspiration-CGM3NK6X.js +0 -53
- package/dist/intent-HKNBXEDS.js +0 -95
- package/dist/job-L7RJZMSG.js +0 -202
- package/dist/job-create-JUDAN52O.js +0 -109
- package/dist/job-status-U43WE3RJ.js +0 -74
- package/dist/keep-55CPC7DG.js +0 -78
- package/dist/keyword-gap-HUSP6MOT.js +0 -130
- package/dist/keyword-metrics-2C3VFGDQ.js +0 -197
- package/dist/keywords-for-site-4GV4KS4X.js +0 -128
- package/dist/landing-V2FMPQOK.js +0 -28
- package/dist/languages-75YJZAHL.js +0 -40
- package/dist/languages-JSGWCC7Z.js +0 -55
- package/dist/layerize-RPD6SNP6.js +0 -190
- package/dist/leads-DTULNGMU.js +0 -90
- package/dist/leads-OPRVJBQG.js +0 -293
- package/dist/learn-EW2CKOFS.js +0 -203
- package/dist/library-ARKRLCNJ.js +0 -109
- package/dist/lighthouse-6ZC45E53.js +0 -75
- package/dist/line-item-writes-EF3BJ5KV.js +0 -208
- package/dist/line-items-G467XOZF.js +0 -103
- package/dist/link-5SU4MR6M.js +0 -64
- package/dist/linkedin-OFM3VVOB.js +0 -1008
- package/dist/list-7MVBXOJT.js +0 -51
- package/dist/list-A5UCIR2J.js +0 -65
- package/dist/list-AL7N6X2P.js +0 -50
- package/dist/list-HEWXAVCL.js +0 -151
- package/dist/list-LI4OGQFW.js +0 -57
- package/dist/list-P7NAFN6J.js +0 -76
- package/dist/list-SUTCQLGI.js +0 -63
- package/dist/list-VFZPXFMA.js +0 -303
- package/dist/locations-6XADFU3V.js +0 -40
- package/dist/locations-74TPQWAT.js +0 -67
- package/dist/log-I3MAU44R.js +0 -92
- package/dist/logo-Q5D7PZY6.js +0 -18
- package/dist/lookup-TCU2ZEZW.js +0 -22
- package/dist/make-ALBJZKJZ.js +0 -596
- package/dist/make-ALBJZKJZ.js.map +0 -1
- package/dist/map-OT6F33VF.js +0 -404
- package/dist/mcp-LC3L2JQO.js +0 -463
- package/dist/media-6CQYG4UY.js +0 -89
- package/dist/media-MYZ5I7WK.js +0 -15
- package/dist/media-sources-RK6EJYBQ.js +0 -32
- package/dist/members-7CHDHW7H.js +0 -44
- package/dist/meta-7XP5AWYU.js +0 -69
- package/dist/metrics-73LI743C.js +0 -152
- package/dist/models-7U3GRYHF.js +0 -155
- package/dist/normalize-CXMYHNSA.js +0 -141
- package/dist/normalize-ICQF2AST.js +0 -806
- package/dist/options-E4IO5SR5.js +0 -121
- package/dist/output-RAH3TSIL.js +0 -24
- package/dist/page-GS3WLT2C.js +0 -105
- package/dist/pages-FXYQLHSJ.js +0 -66
- package/dist/performance-UQLSBPYN.js +0 -24
- package/dist/pinterest-AUH7WBA7.js +0 -96
- package/dist/pixels-45A55MOP.js +0 -60
- package/dist/pixels-ZFF4BGUH.js +0 -108
- package/dist/plan-NBFLYAOW.js +0 -207
- package/dist/preview-AD4GKYIA.js +0 -169
- package/dist/promoted-tweet-writes-ZW3FUPFG.js +0 -180
- package/dist/promoted-tweets-ZLQ6GZD5.js +0 -119
- package/dist/properties-L2XIYQDS.js +0 -101
- package/dist/publish-H2PBR5XN.js +0 -322
- package/dist/query-GDUTZ4R7.js +0 -61
- package/dist/query-SGGBCHTF.js +0 -236
- package/dist/query-VTGVZQIO.js +0 -247
- package/dist/questionnaire-J56AATOX.js +0 -93
- package/dist/read-G5AC332V.js +0 -141
- package/dist/reddit-TGQYQUNZ.js +0 -26
- package/dist/refresh-HVBCC2ZI.js +0 -94
- package/dist/release-VI3JT6O6.js +0 -60
- package/dist/relevant-pages-2FW3E5KD.js +0 -99
- package/dist/remove-4EE7STMS.js +0 -50
- package/dist/remove-6LGGKMLJ.js +0 -48
- package/dist/reopen-LEGLAOMA.js +0 -63
- package/dist/rerun-A5DPZFOO.js +0 -85
- package/dist/research-O6YMV2TG.js +0 -62
- package/dist/research-WOJELJ4K.js +0 -262
- package/dist/reset-PQHURJDC.js +0 -54
- package/dist/reset-V2X7DFHD.js +0 -54
- package/dist/resolve-Q4AACDIC.js +0 -121
- package/dist/retry-BB6NJBC4.js +0 -60
- package/dist/retry-WMGEUJPB.js +0 -62
- package/dist/retry-failed-6HXPUVXJ.js +0 -45
- package/dist/run-OBYA2F3E.js +0 -60
- package/dist/run-V6E36GXW.js +0 -65
- package/dist/scaffold-ad-KKYI7FZJ.js +0 -1549
- package/dist/scaffold-static-ad-R5UE4YIZ.js +0 -966
- package/dist/scaffold-static-ad-R5UE4YIZ.js.map +0 -1
- package/dist/scaffold-video-Y2BWXZSX.js +0 -873
- package/dist/schedule-EO27AZTT.js +0 -61
- package/dist/schedule-O2K6M43V.js +0 -66
- package/dist/scheduled-agents-OIJBX7OO.js +0 -49
- package/dist/schema-6GR77VJG.js +0 -63
- package/dist/schema-7GSCTQIC.js +0 -132
- package/dist/scrape-ETAMW43A.js +0 -2241
- package/dist/screenshot-YCQF25ZB.js +0 -102
- package/dist/search-742XSULP.js +0 -224
- package/dist/search-MEEM4D3W.js +0 -92
- package/dist/search-OQNGSMUH.js +0 -91
- package/dist/search-Q7PAV7NY.js +0 -174
- package/dist/search-UO5KJTYS.js +0 -26
- package/dist/seeds-N6FF5S4T.js +0 -101
- package/dist/seeds-N6FF5S4T.js.map +0 -1
- package/dist/select-ASR5SBYE.js +0 -94
- package/dist/sends-WGJVQUMF.js +0 -309
- package/dist/sequences-OIPVR7JL.js +0 -146
- package/dist/set-prompt-I32464V4.js +0 -186
- package/dist/setup-links-NP2HULRC.js +0 -141
- package/dist/shared-HJDFNID4.js +0 -31
- package/dist/site-PTIYQQ3Z.js +0 -177
- package/dist/sitemaps-POPZJTAX.js +0 -87
- package/dist/sites-262H6LM4.js +0 -101
- package/dist/skills-6TQZLIMP.js +0 -47
- package/dist/sources-5FRMFKDA.js +0 -30
- package/dist/sources-FJLCIG6X.js +0 -421
- package/dist/stale-YUYMETFC.js +0 -63
- package/dist/stats-F5JZVPYG.js +0 -34
- package/dist/status-UYIQFC7L.js +0 -62
- package/dist/sticker-4KMBSMP5.js +0 -94
- package/dist/stock-KXJ26BY4.js +0 -179
- package/dist/studio-6ZL76TZR.js +0 -49
- package/dist/sync-527ZCA4A.js +0 -54
- package/dist/sync-CF7FVUQJ.js +0 -54
- package/dist/sync-ZSNUBYPM.js +0 -265
- package/dist/sync-definition-M4KRPGTN.js +0 -53
- package/dist/tag-manager-JAAMOUGB.js +0 -43
- package/dist/tags-2MX3332V.js +0 -20
- package/dist/tags-7767I2D6.js +0 -224
- package/dist/tags-J3BGX3E6.js +0 -20
- package/dist/tags-O27SK3YX.js +0 -26
- package/dist/tags-VAYVSQM6.js +0 -20
- package/dist/targeting-constants-XJ2VOW7K.js +0 -118
- package/dist/targeting-criteria-GOTI75Y4.js +0 -113
- package/dist/targets-FC7CGFRD.js +0 -102
- package/dist/templates-EF5LOWP5.js +0 -178
- package/dist/testimonials-MRMRWF2T.js +0 -32
- package/dist/themes-2ZV7OCJ3.js +0 -94
- package/dist/top-companies-VTLR6GTW.js +0 -108
- package/dist/translate-UMTKAUJ3.js +0 -598
- package/dist/triggers-Y7ZMMY3C.js +0 -141
- package/dist/tweets-ATP3FQAC.js +0 -123
- package/dist/unfollow-CBVPGPF7.js +0 -46
- package/dist/unlink-HZCFLXH4.js +0 -67
- package/dist/unstage-562HOOE7.js +0 -84
- package/dist/unstage-V7GMMOWR.js +0 -95
- package/dist/update-ATSXY5UN.js +0 -142
- package/dist/update-DKU2UDFA.js +0 -155
- package/dist/update-I5A5TOZJ.js +0 -98
- package/dist/update-ZF3VFYLB.js +0 -63
- package/dist/upload-L5SYZB3X.js +0 -150
- package/dist/upload-SIAI6NMH.js +0 -126
- package/dist/upscale-YWRLFU22.js +0 -109
- package/dist/url-options-JSX6HBZL.js +0 -130
- package/dist/url-params-AZUI3CW3.js +0 -98
- package/dist/use-UT2DIZOG.js +0 -95
- package/dist/validate-MC4EIQI5.js +0 -89
- package/dist/variant-FLZJYGYL.js +0 -338
- package/dist/videos-S7BYFSU3.js +0 -39
- package/dist/view-NPAG66QK.js +0 -121
- package/dist/viewer-check-45T2HLAH.js +0 -69
- package/dist/web-5R7LHKIT.js +0 -140
- package/dist/winners-I5BMATJZ.js +0 -79
- package/dist/winners-I5BMATJZ.js.map +0 -1
- package/dist/winning-ads-PDCY4YW5.js +0 -62
- package/dist/winning-ads-PDCY4YW5.js.map +0 -1
- package/dist/write-commands-MFRZD6Q6.js +0 -737
- package/dist/write-commands-VWSLDBPT.js +0 -206
- package/dist/write-commands-VXL5QKMT.js +0 -1015
- package/dist/write-commands-WTRSOSO3.js +0 -713
- package/dist/x-22PKVRYX.js +0 -47
- /package/dist/{account-AO6PNNPO.js.map → account-CZWMPCRJ.js.map} +0 -0
- /package/dist/{account-IEVEOFIK.js.map → account-F2D257QB.js.map} +0 -0
- /package/dist/{account-F6MHQ64I.js.map → account-VGIKDAK3.js.map} +0 -0
- /package/dist/{account-reads-W3XO7CIW.js.map → account-reads-OS6RLJFJ.js.map} +0 -0
- /package/dist/{account-writes-ISE4U2FQ.js.map → account-writes-7HZJWI5U.js.map} +0 -0
- /package/dist/{accounts-4CDRK4S4.js.map → accounts-25JDCWKR.js.map} +0 -0
- /package/dist/{accounts-5QDKBZMH.js.map → accounts-4IIILFLV.js.map} +0 -0
- /package/dist/{accounts-4TM4UZEJ.js.map → accounts-6GNGCHU7.js.map} +0 -0
- /package/dist/{accounts-KYJO3JC6.js.map → accounts-X5MPAWZX.js.map} +0 -0
- /package/dist/{accounts-XDC26KK6.js.map → accounts-Z45QINO4.js.map} +0 -0
- /package/dist/{actions-CJB4GZYA.js.map → actions-CLFWCFFA.js.map} +0 -0
- /package/dist/{active-entities-YUSOUR5M.js.map → active-entities-V2K2S3UY.js.map} +0 -0
- /package/dist/{activities-ZVQOBHZH.js.map → activities-CUVZGTGA.js.map} +0 -0
- /package/dist/{ad-group-writes-UU5NNKSO.js.map → ad-group-writes-GG3BUQQY.js.map} +0 -0
- /package/dist/{ad-groups-CGFXZNIA.js.map → ad-groups-KRZ6YX7O.js.map} +0 -0
- /package/dist/{ad-writes-NALDYWOR.js.map → ad-writes-YDMS7NPG.js.map} +0 -0
- /package/dist/{add-RYR3QGOP.js.map → add-7P4VYS7R.js.map} +0 -0
- /package/dist/{add-577QWMCF.js.map → add-BHLEFGCG.js.map} +0 -0
- /package/dist/{add-CUWAW7KG.js.map → add-LHAZ34ZF.js.map} +0 -0
- /package/dist/{add-BP4CUUKB.js.map → add-PC7YGO2Z.js.map} +0 -0
- /package/dist/{ads-SATXH4OD.js.map → ads-5SGFPSQL.js.map} +0 -0
- /package/dist/{ads-PV7GZXPR.js.map → ads-CP4VTPKO.js.map} +0 -0
- /package/dist/{ads-GII5LEOH.js.map → ads-Q54IPFAS.js.map} +0 -0
- /package/dist/{adsets-PMXNW3EG.js.map → adsets-4RXF4YXR.js.map} +0 -0
- /package/dist/{advertisers-GM5UFI44.js.map → advertisers-LAIOXSCF.js.map} +0 -0
- /package/dist/{analytics-JDPRLCHQ.js.map → analytics-7M6JSC2C.js.map} +0 -0
- /package/dist/{analytics-4CD7CH7H.js.map → analytics-7RGPJKUD.js.map} +0 -0
- /package/dist/{animate-FWWHSYPL.js.map → animate-PYYWDHQ2.js.map} +0 -0
- /package/dist/{assign-XI4HIIIM.js.map → assign-4UP6F6UX.js.map} +0 -0
- /package/dist/{audience-size-DHZ65PGA.js.map → audience-size-AYNVGVBR.js.map} +0 -0
- /package/dist/{audiences-VEKGLWBG.js.map → audiences-6QF625VZ.js.map} +0 -0
- /package/dist/{audiences-D7CXP3ZF.js.map → audiences-B2ZSJSOJ.js.map} +0 -0
- /package/dist/{audiences-HFRVIE2X.js.map → audiences-S6IHCG3G.js.map} +0 -0
- /package/dist/{audit-M7BZYBIS.js.map → audit-43GSHWBO.js.map} +0 -0
- /package/dist/{audit-AD4ZZNJN.js.map → audit-IJUKCS2K.js.map} +0 -0
- /package/dist/{autocomplete-B45I26R4.js.map → autocomplete-YVP3WOFQ.js.map} +0 -0
- /package/dist/{avatars-75BALNB7.js.map → avatars-O64BDZHC.js.map} +0 -0
- /package/dist/{bid-pricing-4RCP24BE.js.map → bid-pricing-GMEWVIQP.js.map} +0 -0
- /package/dist/{brand-2TOLKYDY.js.map → brand-W55FHWU6.js.map} +0 -0
- /package/dist/{businesses-JBDQZZIG.js.map → businesses-ZFP263RY.js.map} +0 -0
- /package/dist/{campaign-groups-G7B26V7K.js.map → campaign-groups-XPYTQFNQ.js.map} +0 -0
- /package/dist/{campaign-writes-HZ7ART7W.js.map → campaign-writes-7MFHQAAI.js.map} +0 -0
- /package/dist/{campaign-writes-SX52CJYW.js.map → campaign-writes-APYVRCNH.js.map} +0 -0
- /package/dist/{campaigns-X3DPCCSN.js.map → campaigns-ANQW3YDH.js.map} +0 -0
- /package/dist/{campaigns-RW3QJQ2F.js.map → campaigns-LXSVJEXI.js.map} +0 -0
- /package/dist/{campaigns-OIJGTO7W.js.map → campaigns-UN3XB6TJ.js.map} +0 -0
- /package/dist/{campaigns-MEEM3CNC.js.map → campaigns-YPI4KS7U.js.map} +0 -0
- /package/dist/{canvas-LXRLDE5G.js.map → canvas-GPXU2ZSC.js.map} +0 -0
- /package/dist/{capabilities-BWSB7IFT.js.map → capabilities-HD2T6H24.js.map} +0 -0
- /package/dist/{cards-4477BFEV.js.map → cards-QRX43TGI.js.map} +0 -0
- /package/dist/{catalog-BKQOAVR2.js.map → catalog-CRIYIXOJ.js.map} +0 -0
- /package/dist/{catalogs-G3YEVG6S.js.map → catalogs-D677AN33.js.map} +0 -0
- /package/dist/{changes-AYLNY6KN.js.map → changes-PWBOKKNL.js.map} +0 -0
- /package/dist/{chatgpt-TNSRA3L3.js.map → chatgpt-RYL44ASK.js.map} +0 -0
- /package/dist/{chats-MMHBJQFM.js.map → chats-OQ6JOASV.js.map} +0 -0
- /package/dist/{chunk-V5QJBDBC.js.map → chunk-2QLNGCFZ.js.map} +0 -0
- /package/dist/{chunk-MZL4H5PY.js.map → chunk-2V2M2CHS.js.map} +0 -0
- /package/dist/{chunk-LYBXCXJB.js.map → chunk-35QYDREI.js.map} +0 -0
- /package/dist/{chunk-24GELJP3.js.map → chunk-3BG2BVWN.js.map} +0 -0
- /package/dist/{chunk-LERTEZST.js.map → chunk-3FOE4P6A.js.map} +0 -0
- /package/dist/{chunk-HTW7FTP4.js.map → chunk-3JX4NUGF.js.map} +0 -0
- /package/dist/{chunk-HRYOZUSK.js.map → chunk-3KN3VCS3.js.map} +0 -0
- /package/dist/{chunk-EIIANSBX.js.map → chunk-3QLACKNX.js.map} +0 -0
- /package/dist/{chunk-6G5K6ZC6.js.map → chunk-54VIXBLO.js.map} +0 -0
- /package/dist/{chunk-PBA3YCZA.js.map → chunk-5IQL24NI.js.map} +0 -0
- /package/dist/{chunk-KFE3S63W.js.map → chunk-5TOKIRCD.js.map} +0 -0
- /package/dist/{chunk-VAJBWA2U.js.map → chunk-5URIFBIL.js.map} +0 -0
- /package/dist/{chunk-YKFEHWHC.js.map → chunk-5Z6SSOLG.js.map} +0 -0
- /package/dist/{chunk-4QEY2L6U.js.map → chunk-6C4S466W.js.map} +0 -0
- /package/dist/{chunk-4NDCTLI5.js.map → chunk-6HLPM7U3.js.map} +0 -0
- /package/dist/{chunk-WS2GMOSG.js.map → chunk-6R7CMUIZ.js.map} +0 -0
- /package/dist/{chunk-KNY76UGC.js.map → chunk-6XPBHM5P.js.map} +0 -0
- /package/dist/{chunk-35XWJEWH.js.map → chunk-6ZEFXZRD.js.map} +0 -0
- /package/dist/{chunk-DZSIUEGH.js.map → chunk-73JXX4LJ.js.map} +0 -0
- /package/dist/{chunk-XY7BZSDQ.js.map → chunk-A2PTD6FL.js.map} +0 -0
- /package/dist/{chunk-Y63Y4QSY.js.map → chunk-AGEJ7IJG.js.map} +0 -0
- /package/dist/{chunk-XJRBFLBD.js.map → chunk-APY53DKV.js.map} +0 -0
- /package/dist/{chunk-MFQ5SUJT.js.map → chunk-BJ4SHBS2.js.map} +0 -0
- /package/dist/{chunk-XL3BMSYG.js.map → chunk-BXKJZBBJ.js.map} +0 -0
- /package/dist/{chunk-EYK6G4XE.js.map → chunk-C26QSQXP.js.map} +0 -0
- /package/dist/{chunk-ZIHEIVJC.js.map → chunk-C2DAPBYB.js.map} +0 -0
- /package/dist/{chunk-WBUMUELD.js.map → chunk-D2JOBGBD.js.map} +0 -0
- /package/dist/{chunk-2TK4DEOE.js.map → chunk-DE64GHEP.js.map} +0 -0
- /package/dist/{chunk-IFWP3ZDO.js.map → chunk-E4ICSNSW.js.map} +0 -0
- /package/dist/{chunk-D23EKXOP.js.map → chunk-EIAA3XTT.js.map} +0 -0
- /package/dist/{chunk-EZWYX343.js.map → chunk-EMLM2TKM.js.map} +0 -0
- /package/dist/{chunk-2YJL6MEB.js.map → chunk-EOWMPN3Q.js.map} +0 -0
- /package/dist/{chunk-KN7DFVKW.js.map → chunk-F66W4QQL.js.map} +0 -0
- /package/dist/{chunk-2K4LMCV2.js.map → chunk-FIBL3EZN.js.map} +0 -0
- /package/dist/{chunk-GP7QUF5A.js.map → chunk-FJDTR2IN.js.map} +0 -0
- /package/dist/{chunk-H26L3OQY.js.map → chunk-FNKOQQ2U.js.map} +0 -0
- /package/dist/{chunk-DT2XXF6O.js.map → chunk-FZKC65TR.js.map} +0 -0
- /package/dist/{chunk-VFIOJD5M.js.map → chunk-G4PE563Y.js.map} +0 -0
- /package/dist/{chunk-C6U6UDHJ.js.map → chunk-G6UBBPH3.js.map} +0 -0
- /package/dist/{chunk-STIB5DR5.js.map → chunk-GKUIGEY6.js.map} +0 -0
- /package/dist/{chunk-F2S5BLPE.js.map → chunk-GWFJAVWD.js.map} +0 -0
- /package/dist/{chunk-EFM7OODQ.js.map → chunk-HHLEPWHB.js.map} +0 -0
- /package/dist/{chunk-YBMVHGY3.js.map → chunk-HMKVVPWY.js.map} +0 -0
- /package/dist/{chunk-QM6T4BFA.js.map → chunk-HMVVNB23.js.map} +0 -0
- /package/dist/{chunk-W3B4BHQU.js.map → chunk-ICFITD5H.js.map} +0 -0
- /package/dist/{chunk-7ZHMGXE2.js.map → chunk-IDEFDDTX.js.map} +0 -0
- /package/dist/{chunk-YRVDCIJ2.js.map → chunk-IH4NS3NT.js.map} +0 -0
- /package/dist/{chunk-5FWMRTNB.js.map → chunk-ITYFVNLA.js.map} +0 -0
- /package/dist/{chunk-D7PFF6UC.js.map → chunk-IX6PPPOS.js.map} +0 -0
- /package/dist/{chunk-7TNPEURJ.js.map → chunk-JM6XT33N.js.map} +0 -0
- /package/dist/{chunk-JU7FIHQ2.js.map → chunk-JTSYEIHY.js.map} +0 -0
- /package/dist/{chunk-KLNKEVIY.js.map → chunk-K63BDJYR.js.map} +0 -0
- /package/dist/{chunk-RX6F3GEA.js.map → chunk-KNX2UBLI.js.map} +0 -0
- /package/dist/{chunk-DTD2IRDA.js.map → chunk-KPHBHRJY.js.map} +0 -0
- /package/dist/{chunk-UZUHXN7H.js.map → chunk-KWMCE3Q2.js.map} +0 -0
- /package/dist/{chunk-VB6WJJR3.js.map → chunk-KYQZM5FB.js.map} +0 -0
- /package/dist/{chunk-2D4ASTGN.js.map → chunk-LAAJ3V67.js.map} +0 -0
- /package/dist/{chunk-VVZBBKRC.js.map → chunk-LCMGWIEQ.js.map} +0 -0
- /package/dist/{chunk-LI6PGY3L.js.map → chunk-MNCMAJKB.js.map} +0 -0
- /package/dist/{chunk-WNFIJAUO.js.map → chunk-N3DLCXMB.js.map} +0 -0
- /package/dist/{chunk-DYOI4FQ7.js.map → chunk-NKU7RLAI.js.map} +0 -0
- /package/dist/{chunk-KQTYTCKQ.js.map → chunk-NWAFH7K3.js.map} +0 -0
- /package/dist/{chunk-JERXV5A3.js.map → chunk-NYVO4TND.js.map} +0 -0
- /package/dist/{chunk-BSOWFWEU.js.map → chunk-O5MNUL7C.js.map} +0 -0
- /package/dist/{chunk-4KCKNAVW.js.map → chunk-O5RBFETA.js.map} +0 -0
- /package/dist/{chunk-MCWYIEWI.js.map → chunk-ODMKCAAQ.js.map} +0 -0
- /package/dist/{chunk-5YGBRHO6.js.map → chunk-OL4RPZ64.js.map} +0 -0
- /package/dist/{chunk-LS5MTRFI.js.map → chunk-OP2OMNVN.js.map} +0 -0
- /package/dist/{chunk-PWGWMIIJ.js.map → chunk-P5V7NXMY.js.map} +0 -0
- /package/dist/{chunk-6B7VKFS4.js.map → chunk-Q3PB5ZUT.js.map} +0 -0
- /package/dist/{chunk-PZ7EPJRF.js.map → chunk-QT7FKFNS.js.map} +0 -0
- /package/dist/{chunk-OW4XDJV2.js.map → chunk-R5VW2ACF.js.map} +0 -0
- /package/dist/{chunk-HOXZOOVA.js.map → chunk-RGE7STV4.js.map} +0 -0
- /package/dist/{chunk-WTOYCUKH.js.map → chunk-RIXGQIMZ.js.map} +0 -0
- /package/dist/{chunk-2BKVVCCV.js.map → chunk-RWEVT5SO.js.map} +0 -0
- /package/dist/{chunk-75FDTDLH.js.map → chunk-S3TSJQOJ.js.map} +0 -0
- /package/dist/{chunk-ZZQXVYUY.js.map → chunk-SMVHUBXE.js.map} +0 -0
- /package/dist/{chunk-EFSYTROP.js.map → chunk-SZX5LUHK.js.map} +0 -0
- /package/dist/{chunk-7OFNN4CT.js.map → chunk-TG63NMMW.js.map} +0 -0
- /package/dist/{chunk-XBOK23HI.js.map → chunk-TOYLG3ZY.js.map} +0 -0
- /package/dist/{chunk-TD2P4HBP.js.map → chunk-TZKJNSZH.js.map} +0 -0
- /package/dist/{chunk-QSN5MZGS.js.map → chunk-U57KAPAJ.js.map} +0 -0
- /package/dist/{chunk-WEU6GKUQ.js.map → chunk-VFME5MT5.js.map} +0 -0
- /package/dist/{chunk-3EUFAONS.js.map → chunk-WCHPZD64.js.map} +0 -0
- /package/dist/{chunk-PHEEJUFL.js.map → chunk-WHOS7L4N.js.map} +0 -0
- /package/dist/{chunk-5Z2KSVIV.js.map → chunk-WOB3ESBQ.js.map} +0 -0
- /package/dist/{chunk-XPOIIQCI.js.map → chunk-XM75RTFZ.js.map} +0 -0
- /package/dist/{chunk-2ZY7HON7.js.map → chunk-XUGTYYXY.js.map} +0 -0
- /package/dist/{chunk-X5VHGXFB.js.map → chunk-XX5DXKEI.js.map} +0 -0
- /package/dist/{chunk-KLBTXETO.js.map → chunk-Y4V55WFQ.js.map} +0 -0
- /package/dist/{chunk-NAWWC5I3.js.map → chunk-YKSIHGIL.js.map} +0 -0
- /package/dist/{chunk-QNDLOCAI.js.map → chunk-YOLNNZ77.js.map} +0 -0
- /package/dist/{chunk-KS2QY6MK.js.map → chunk-ZW6EMA2U.js.map} +0 -0
- /package/dist/{claim-UDNXCJIU.js.map → claim-B6LWKULS.js.map} +0 -0
- /package/dist/{client-VHN32ZRE.js.map → client-DUZXHJAT.js.map} +0 -0
- /package/dist/{code-YS6ZMGX3.js.map → code-BQCH5ARZ.js.map} +0 -0
- /package/dist/{companies-RPZN4JTR.js.map → companies-BC6TCRXY.js.map} +0 -0
- /package/dist/{complete-HLSRQE3N.js.map → complete-Y7ZAKY62.js.map} +0 -0
- /package/dist/{config-CQN5FHMZ.js.map → config-2NKM5GSZ.js.map} +0 -0
- /package/dist/{connect-TIJZO7OV.js.map → connect-4WXSJAIK.js.map} +0 -0
- /package/dist/{conversation-BS7TF6LZ.js.map → conversation-SEVRAH5J.js.map} +0 -0
- /package/dist/{conversion-report-N4HANYOB.js.map → conversion-report-KLP2PJKJ.js.map} +0 -0
- /package/dist/{conversion-upload-command-CSD344BY.js.map → conversion-upload-command-KB6ME34E.js.map} +0 -0
- /package/dist/{conversion-writes-LW4HQKK5.js.map → conversion-writes-LO24U7NW.js.map} +0 -0
- /package/dist/{conversions-FEWAX64H.js.map → conversions-OC2UHXG7.js.map} +0 -0
- /package/dist/{conversions-TS723AZV.js.map → conversions-QBWE4C5T.js.map} +0 -0
- /package/dist/{countries-ESAKTQAE.js.map → countries-F67IA6DF.js.map} +0 -0
- /package/dist/{create-L3JHP6J4.js.map → create-BPHBGMFM.js.map} +0 -0
- /package/dist/{create-4SH4BZXP.js.map → create-IGZH5OFP.js.map} +0 -0
- /package/dist/{create-F5FNHJKE.js.map → create-NLNOXZ2T.js.map} +0 -0
- /package/dist/{create-B4UVIJZI.js.map → create-YX3YN4SU.js.map} +0 -0
- /package/dist/{creatives-RQ2DFHGI.js.map → creatives-4N6RRVBP.js.map} +0 -0
- /package/dist/{creatives-UILBPOKO.js.map → creatives-ESTFGCVQ.js.map} +0 -0
- /package/dist/{creatives-LK2NZHRX.js.map → creatives-ZDWQ7AP3.js.map} +0 -0
- /package/dist/{credentials-GC34ZPYP.js.map → credentials-GA4ZW4QL.js.map} +0 -0
- /package/dist/{critique-X7HYYEJ4.js.map → critique-IJG6WBDW.js.map} +0 -0
- /package/dist/{critique-FAOH4FCP.js.map → critique-SYBZXBHJ.js.map} +0 -0
- /package/dist/{crm-XEQDSU2U.js.map → crm-2P7WHZBW.js.map} +0 -0
- /package/dist/{crm-RB5TVV3M.js.map → crm-SVDB2XHW.js.map} +0 -0
- /package/dist/{crop-2J4RKJ6B.js.map → crop-ZAYOFRSS.js.map} +0 -0
- /package/dist/{currency-47QHFHDA.js.map → currency-UWW5QJGL.js.map} +0 -0
- /package/dist/{custom-conversions-7HOWR6X6.js.map → custom-conversions-2BG2VUOY.js.map} +0 -0
- /package/dist/{customers-P5WOUY6G.js.map → customers-2LZ3HG3V.js.map} +0 -0
- /package/dist/{delete-SW5OSIGN.js.map → delete-A3JJJHC6.js.map} +0 -0
- /package/dist/{delete-3AV3ARMX.js.map → delete-ER3LIA3F.js.map} +0 -0
- /package/dist/{delete-MRM2S4Z5.js.map → delete-HVCQQSG4.js.map} +0 -0
- /package/dist/{delete-GZKGPBBD.js.map → delete-QPPPLHS4.js.map} +0 -0
- /package/dist/{delete-A5XD52K4.js.map → delete-T22GD7FG.js.map} +0 -0
- /package/dist/{delete-DLQ2ZZDA.js.map → delete-VZUF57KL.js.map} +0 -0
- /package/dist/{demographics-ZLZOIWUE.js.map → demographics-3KAK7JGZ.js.map} +0 -0
- /package/dist/{describe-P3PPIQME.js.map → describe-ADQWM55K.js.map} +0 -0
- /package/dist/{describe-6NVOWIBE.js.map → describe-XEGTSEUI.js.map} +0 -0
- /package/dist/{dimensions-EYAYO27I.js.map → dimensions-WQZBBY7I.js.map} +0 -0
- /package/dist/{discard-VNVNJWNX.js.map → discard-VU72HO4B.js.map} +0 -0
- /package/dist/{discover-P6TUGZ45.js.map → discover-HXKIQWH2.js.map} +0 -0
- /package/dist/{domain-OBICRJP3.js.map → domain-ERWROFZC.js.map} +0 -0
- /package/dist/{download-7T6HIJKD.js.map → download-7Z3BWAHR.js.map} +0 -0
- /package/dist/{draft-UECMJ6B4.js.map → draft-3V3HOB5N.js.map} +0 -0
- /package/dist/{draft-3ZVCQSAG.js.map → draft-4TCT6UQD.js.map} +0 -0
- /package/dist/{draft-V75RBORX.js.map → draft-5YJY6YT5.js.map} +0 -0
- /package/dist/{draft-KIBMVKSR.js.map → draft-7LIGULA2.js.map} +0 -0
- /package/dist/{draft-666YKA3P.js.map → draft-C2DDLJH2.js.map} +0 -0
- /package/dist/{draft-THPZFASM.js.map → draft-FCCYMFKW.js.map} +0 -0
- /package/dist/{draft-Z3VUQ4LJ.js.map → draft-FELTG6LD.js.map} +0 -0
- /package/dist/{draft-YDY2Q2K6.js.map → draft-JIFFSL5U.js.map} +0 -0
- /package/dist/{env-KIP2J2UB.js.map → env-IUF4DTG2.js.map} +0 -0
- /package/dist/{experiment-PYQPAZZN.js.map → experiment-T3H6LFLK.js.map} +0 -0
- /package/dist/{export-JQRMBJ7D.js.map → export-V7WQJD4L.js.map} +0 -0
- /package/dist/{extract-7QKLME7Z.js.map → extract-WW7WLLRB.js.map} +0 -0
- /package/dist/{facets-7J66L3NK.js.map → facets-J4JLHESD.js.map} +0 -0
- /package/dist/{favorites-NSGF4OV5.js.map → favorites-XLCT5S6E.js.map} +0 -0
- /package/dist/{feedback-3LDG4WVJ.js.map → feedback-J6FSCCWF.js.map} +0 -0
- /package/dist/{feedback-record-CTAUHLMF.js.map → feedback-record-4AY3MG2E.js.map} +0 -0
- /package/dist/{fetch-XF76JABV.js.map → fetch-C4JYRA53.js.map} +0 -0
- /package/dist/{fetch-4RXXZGO4.js.map → fetch-QIHGIATQ.js.map} +0 -0
- /package/dist/{find-FRRHAPWX.js.map → find-SLZO4FBR.js.map} +0 -0
- /package/dist/{fit-7237F5EB.js.map → fit-SSR3JDDI.js.map} +0 -0
- /package/dist/{flows-GGB46DLH.js.map → flows-IRFHGBUR.js.map} +0 -0
- /package/dist/{fonts-S3GWKOUJ.js.map → fonts-ZU5Q4A6U.js.map} +0 -0
- /package/dist/{forecast-6JVKXNFB.js.map → forecast-KVPAU7OT.js.map} +0 -0
- /package/dist/{forecast-SVD3LMMY.js.map → forecast-XGBT722Z.js.map} +0 -0
- /package/dist/{funding-NYO65IMT.js.map → funding-BWSGYQPP.js.map} +0 -0
- /package/dist/{funnel-RCQOIQZ5.js.map → funnel-JXC5WXLR.js.map} +0 -0
- /package/dist/{ga4-U6DMJ6K4.js.map → ga4-XWK5H7HN.js.map} +0 -0
- /package/dist/{generate-MMBYB44G.js.map → generate-E6YDVJ23.js.map} +0 -0
- /package/dist/{get-MN2GRXGZ.js.map → get-23E7ZEKP.js.map} +0 -0
- /package/dist/{get-XN5ZGDIK.js.map → get-HXJSMHDC.js.map} +0 -0
- /package/dist/{get-KQSM2MXM.js.map → get-IWYF6PA4.js.map} +0 -0
- /package/dist/{get-6WEYKOY6.js.map → get-JGCMBA7Y.js.map} +0 -0
- /package/dist/{get-3L6W3E3T.js.map → get-QLFMRSLN.js.map} +0 -0
- /package/dist/{get-P743RPSI.js.map → get-XWIA6BEO.js.map} +0 -0
- /package/dist/{get-YIHTAMRT.js.map → get-ZLSZTGXA.js.map} +0 -0
- /package/dist/{gif-53ULFVLR.js.map → gif-KHF452GC.js.map} +0 -0
- /package/dist/{google-QYYYSSXI.js.map → google-5GESCEKC.js.map} +0 -0
- /package/dist/{google-CI2AMJKN.js.map → google-KTDHD75U.js.map} +0 -0
- /package/dist/{google-ads-YAJ33LTW.js.map → google-ads-VW5UGOGO.js.map} +0 -0
- /package/dist/{group-EFZ3AJIE.js.map → group-JYNKEOPG.js.map} +0 -0
- /package/dist/{group-GKWCCX66.js.map → group-ZMCHUQMI.js.map} +0 -0
- /package/dist/{gsc-TAKOF7J5.js.map → gsc-I6RIFE7I.js.map} +0 -0
- /package/dist/{history-DBIEYQHY.js.map → history-F3N6M4JM.js.map} +0 -0
- /package/dist/{history-QWYLRQBO.js.map → history-IAKV2LJN.js.map} +0 -0
- /package/dist/{hubspot-DDONLJ7R.js.map → hubspot-QVDLE3BF.js.map} +0 -0
- /package/dist/{icon-L4RSS6PZ.js.map → icon-4C2JOBPE.js.map} +0 -0
- /package/dist/{ideas-N44Z2SIT.js.map → ideas-7COQX7C3.js.map} +0 -0
- /package/dist/{images-BKHCBCE7.js.map → images-5XNXXZLM.js.map} +0 -0
- /package/dist/{improve-BCCU5AS6.js.map → improve-6VC6ORAG.js.map} +0 -0
- /package/dist/{ingest-VNRTFD4Y.js.map → ingest-L6MMXL6R.js.map} +0 -0
- /package/dist/{ingest-HBECWLOB.js.map → ingest-UP42M3WU.js.map} +0 -0
- /package/dist/{insights-ZIU6FJMS.js.map → insights-3H736QZH.js.map} +0 -0
- /package/dist/{insights-NZWSJKU4.js.map → insights-3TEVP4B2.js.map} +0 -0
- /package/dist/{inspect-7GGJDJBJ.js.map → inspect-2WOLOPTV.js.map} +0 -0
- /package/dist/{inspiration-CGM3NK6X.js.map → inspiration-I2XMXXEW.js.map} +0 -0
- /package/dist/{intent-HKNBXEDS.js.map → intent-IYUKLNST.js.map} +0 -0
- /package/dist/{job-L7RJZMSG.js.map → job-MTHK2PPN.js.map} +0 -0
- /package/dist/{job-create-JUDAN52O.js.map → job-create-AD3L6A6F.js.map} +0 -0
- /package/dist/{job-status-U43WE3RJ.js.map → job-status-MCFOA5V3.js.map} +0 -0
- /package/dist/{keep-55CPC7DG.js.map → keep-NNATO2B6.js.map} +0 -0
- /package/dist/{keyword-gap-HUSP6MOT.js.map → keyword-gap-RSITTJ56.js.map} +0 -0
- /package/dist/{keyword-metrics-2C3VFGDQ.js.map → keyword-metrics-E5D2ESIH.js.map} +0 -0
- /package/dist/{keywords-for-site-4GV4KS4X.js.map → keywords-for-site-SRUKRBCR.js.map} +0 -0
- /package/dist/{landing-V2FMPQOK.js.map → landing-ZFZ4LYCB.js.map} +0 -0
- /package/dist/{languages-JSGWCC7Z.js.map → languages-3FETIHSB.js.map} +0 -0
- /package/dist/{languages-75YJZAHL.js.map → languages-SJ6ZWALA.js.map} +0 -0
- /package/dist/{layerize-RPD6SNP6.js.map → layerize-NSF6NEWY.js.map} +0 -0
- /package/dist/{leads-OPRVJBQG.js.map → leads-EVFLPNG6.js.map} +0 -0
- /package/dist/{leads-DTULNGMU.js.map → leads-P46ND2LT.js.map} +0 -0
- /package/dist/{learn-EW2CKOFS.js.map → learn-EJSMXKSE.js.map} +0 -0
- /package/dist/{library-ARKRLCNJ.js.map → library-UKGHGGOG.js.map} +0 -0
- /package/dist/{lighthouse-6ZC45E53.js.map → lighthouse-B2IBNWLX.js.map} +0 -0
- /package/dist/{line-item-writes-EF3BJ5KV.js.map → line-item-writes-EM6I5KIH.js.map} +0 -0
- /package/dist/{line-items-G467XOZF.js.map → line-items-GLNMIKM5.js.map} +0 -0
- /package/dist/{link-5SU4MR6M.js.map → link-CZ4XFU2D.js.map} +0 -0
- /package/dist/{linkedin-OFM3VVOB.js.map → linkedin-ES3KPPHF.js.map} +0 -0
- /package/dist/{list-7MVBXOJT.js.map → list-3IJMAQHX.js.map} +0 -0
- /package/dist/{list-SUTCQLGI.js.map → list-B7GN44R3.js.map} +0 -0
- /package/dist/{list-LI4OGQFW.js.map → list-BDPUPCQZ.js.map} +0 -0
- /package/dist/{list-HEWXAVCL.js.map → list-HGQY4OHN.js.map} +0 -0
- /package/dist/{list-VFZPXFMA.js.map → list-JD7EIBPU.js.map} +0 -0
- /package/dist/{list-A5UCIR2J.js.map → list-NZZ75O6C.js.map} +0 -0
- /package/dist/{list-P7NAFN6J.js.map → list-XBYKFXS3.js.map} +0 -0
- /package/dist/{list-AL7N6X2P.js.map → list-XRON5I6M.js.map} +0 -0
- /package/dist/{locations-6XADFU3V.js.map → locations-M2665NGJ.js.map} +0 -0
- /package/dist/{locations-74TPQWAT.js.map → locations-TKPILKLO.js.map} +0 -0
- /package/dist/{log-I3MAU44R.js.map → log-KUHIZL53.js.map} +0 -0
- /package/dist/{logo-Q5D7PZY6.js.map → logo-EVE3TAL5.js.map} +0 -0
- /package/dist/{lookup-TCU2ZEZW.js.map → lookup-Z5TFHYDU.js.map} +0 -0
- /package/dist/{map-OT6F33VF.js.map → map-NNMEQE6E.js.map} +0 -0
- /package/dist/{mcp-LC3L2JQO.js.map → mcp-AJDXG3NV.js.map} +0 -0
- /package/dist/{media-6CQYG4UY.js.map → media-DGNFOT7M.js.map} +0 -0
- /package/dist/{media-MYZ5I7WK.js.map → media-FRNTELKI.js.map} +0 -0
- /package/dist/{media-sources-RK6EJYBQ.js.map → media-sources-V452RJ6B.js.map} +0 -0
- /package/dist/{members-7CHDHW7H.js.map → members-VUJ62KJQ.js.map} +0 -0
- /package/dist/{meta-7XP5AWYU.js.map → meta-NGBLUNVU.js.map} +0 -0
- /package/dist/{metrics-73LI743C.js.map → metrics-QRRK473C.js.map} +0 -0
- /package/dist/{models-7U3GRYHF.js.map → models-4BTFIZZZ.js.map} +0 -0
- /package/dist/{normalize-ICQF2AST.js.map → normalize-O47OIEUO.js.map} +0 -0
- /package/dist/{normalize-CXMYHNSA.js.map → normalize-OTNIBYUO.js.map} +0 -0
- /package/dist/{options-E4IO5SR5.js.map → options-FTXE6HFT.js.map} +0 -0
- /package/dist/{output-RAH3TSIL.js.map → output-W473ZLGR.js.map} +0 -0
- /package/dist/{page-GS3WLT2C.js.map → page-7TEJ5NA5.js.map} +0 -0
- /package/dist/{pages-FXYQLHSJ.js.map → pages-S2E3FHDH.js.map} +0 -0
- /package/dist/{performance-UQLSBPYN.js.map → performance-IMD2N7OD.js.map} +0 -0
- /package/dist/{pinterest-AUH7WBA7.js.map → pinterest-DQC3JXQT.js.map} +0 -0
- /package/dist/{pixels-45A55MOP.js.map → pixels-M5GS7DIH.js.map} +0 -0
- /package/dist/{pixels-ZFF4BGUH.js.map → pixels-MHH4N35F.js.map} +0 -0
- /package/dist/{plan-NBFLYAOW.js.map → plan-GGLBOLIU.js.map} +0 -0
- /package/dist/{preview-AD4GKYIA.js.map → preview-AHDEJO2X.js.map} +0 -0
- /package/dist/{promoted-tweet-writes-ZW3FUPFG.js.map → promoted-tweet-writes-XUM4WGZH.js.map} +0 -0
- /package/dist/{promoted-tweets-ZLQ6GZD5.js.map → promoted-tweets-TIHMJ5PU.js.map} +0 -0
- /package/dist/{properties-L2XIYQDS.js.map → properties-KOY2GE45.js.map} +0 -0
- /package/dist/{publish-H2PBR5XN.js.map → publish-Q7MHUVVZ.js.map} +0 -0
- /package/dist/{query-SGGBCHTF.js.map → query-EO7BX4MH.js.map} +0 -0
- /package/dist/{query-VTGVZQIO.js.map → query-QZWMKAJZ.js.map} +0 -0
- /package/dist/{query-GDUTZ4R7.js.map → query-XLTRAAQO.js.map} +0 -0
- /package/dist/{questionnaire-J56AATOX.js.map → questionnaire-BL3G6UYL.js.map} +0 -0
- /package/dist/{read-G5AC332V.js.map → read-ZMNYD3PA.js.map} +0 -0
- /package/dist/{reddit-TGQYQUNZ.js.map → reddit-RO3VKMJ7.js.map} +0 -0
- /package/dist/{refresh-HVBCC2ZI.js.map → refresh-OER4JT3Q.js.map} +0 -0
- /package/dist/{release-VI3JT6O6.js.map → release-4DLGFSYY.js.map} +0 -0
- /package/dist/{relevant-pages-2FW3E5KD.js.map → relevant-pages-ZGGWFDLL.js.map} +0 -0
- /package/dist/{remove-6LGGKMLJ.js.map → remove-MQ3JY36P.js.map} +0 -0
- /package/dist/{remove-4EE7STMS.js.map → remove-WENY6GQU.js.map} +0 -0
- /package/dist/{reopen-LEGLAOMA.js.map → reopen-VSVK5EZI.js.map} +0 -0
- /package/dist/{rerun-A5DPZFOO.js.map → rerun-B32CIKVH.js.map} +0 -0
- /package/dist/{research-O6YMV2TG.js.map → research-2XAQRMC3.js.map} +0 -0
- /package/dist/{research-WOJELJ4K.js.map → research-ZEIW667A.js.map} +0 -0
- /package/dist/{reset-PQHURJDC.js.map → reset-DXBESBL7.js.map} +0 -0
- /package/dist/{reset-V2X7DFHD.js.map → reset-G4A52FMR.js.map} +0 -0
- /package/dist/{resolve-Q4AACDIC.js.map → resolve-FA6CFVX4.js.map} +0 -0
- /package/dist/{retry-BB6NJBC4.js.map → retry-CXTOGFZT.js.map} +0 -0
- /package/dist/{retry-WMGEUJPB.js.map → retry-THMD27CA.js.map} +0 -0
- /package/dist/{retry-failed-6HXPUVXJ.js.map → retry-failed-3R35LZ6I.js.map} +0 -0
- /package/dist/{run-OBYA2F3E.js.map → run-HRSNUGWJ.js.map} +0 -0
- /package/dist/{run-V6E36GXW.js.map → run-NYJUDS32.js.map} +0 -0
- /package/dist/{scaffold-ad-KKYI7FZJ.js.map → scaffold-ad-RUJQIYQH.js.map} +0 -0
- /package/dist/{scaffold-video-Y2BWXZSX.js.map → scaffold-video-RYVG4IFA.js.map} +0 -0
- /package/dist/{schedule-EO27AZTT.js.map → schedule-ENH4N66R.js.map} +0 -0
- /package/dist/{schedule-O2K6M43V.js.map → schedule-KSIMVG5U.js.map} +0 -0
- /package/dist/{scheduled-agents-OIJBX7OO.js.map → scheduled-agents-RM24BRGD.js.map} +0 -0
- /package/dist/{schema-7GSCTQIC.js.map → schema-JJVSWKGP.js.map} +0 -0
- /package/dist/{schema-6GR77VJG.js.map → schema-MT76C7XK.js.map} +0 -0
- /package/dist/{scrape-ETAMW43A.js.map → scrape-ICS5UA62.js.map} +0 -0
- /package/dist/{screenshot-YCQF25ZB.js.map → screenshot-YPUFPY5W.js.map} +0 -0
- /package/dist/{search-UO5KJTYS.js.map → search-6BHODVNN.js.map} +0 -0
- /package/dist/{search-MEEM4D3W.js.map → search-HOQZ3XIK.js.map} +0 -0
- /package/dist/{search-Q7PAV7NY.js.map → search-KFP4GPMJ.js.map} +0 -0
- /package/dist/{search-OQNGSMUH.js.map → search-TCDI77HS.js.map} +0 -0
- /package/dist/{search-742XSULP.js.map → search-W7E5S347.js.map} +0 -0
- /package/dist/{select-ASR5SBYE.js.map → select-CB5BBKHR.js.map} +0 -0
- /package/dist/{sends-WGJVQUMF.js.map → sends-HDTG7XRL.js.map} +0 -0
- /package/dist/{sequences-OIPVR7JL.js.map → sequences-SISO5JGS.js.map} +0 -0
- /package/dist/{set-prompt-I32464V4.js.map → set-prompt-IS7JSFYY.js.map} +0 -0
- /package/dist/{setup-links-NP2HULRC.js.map → setup-links-NBO7B64S.js.map} +0 -0
- /package/dist/{shared-HJDFNID4.js.map → shared-MJEF5Z6R.js.map} +0 -0
- /package/dist/{site-PTIYQQ3Z.js.map → site-5MPVICS2.js.map} +0 -0
- /package/dist/{sitemaps-POPZJTAX.js.map → sitemaps-G3YCZDEM.js.map} +0 -0
- /package/dist/{sites-262H6LM4.js.map → sites-YH2ZIMCP.js.map} +0 -0
- /package/dist/{skills-6TQZLIMP.js.map → skills-SQTM676S.js.map} +0 -0
- /package/dist/{sources-FJLCIG6X.js.map → sources-FSYJ2EQL.js.map} +0 -0
- /package/dist/{sources-5FRMFKDA.js.map → sources-OES5YJY6.js.map} +0 -0
- /package/dist/{stale-YUYMETFC.js.map → stale-YT3CGHAI.js.map} +0 -0
- /package/dist/{stats-F5JZVPYG.js.map → stats-IDL2KWGM.js.map} +0 -0
- /package/dist/{status-UYIQFC7L.js.map → status-ZDKRO7TH.js.map} +0 -0
- /package/dist/{sticker-4KMBSMP5.js.map → sticker-2R5LODHJ.js.map} +0 -0
- /package/dist/{stock-KXJ26BY4.js.map → stock-5DWDTNFB.js.map} +0 -0
- /package/dist/{studio-6ZL76TZR.js.map → studio-PPFFAUWO.js.map} +0 -0
- /package/dist/{sync-ZSNUBYPM.js.map → sync-7DWOHCVF.js.map} +0 -0
- /package/dist/{sync-CF7FVUQJ.js.map → sync-JRRGJWFH.js.map} +0 -0
- /package/dist/{sync-527ZCA4A.js.map → sync-RNKJFPDI.js.map} +0 -0
- /package/dist/{sync-definition-M4KRPGTN.js.map → sync-definition-SDJM567M.js.map} +0 -0
- /package/dist/{tag-manager-JAAMOUGB.js.map → tag-manager-MBS6YCZM.js.map} +0 -0
- /package/dist/{tags-O27SK3YX.js.map → tags-5EVJ73IX.js.map} +0 -0
- /package/dist/{tags-VAYVSQM6.js.map → tags-5THBYIHN.js.map} +0 -0
- /package/dist/{tags-J3BGX3E6.js.map → tags-D74DMYCW.js.map} +0 -0
- /package/dist/{tags-2MX3332V.js.map → tags-KFDRX4H7.js.map} +0 -0
- /package/dist/{tags-7767I2D6.js.map → tags-YEWHOSC5.js.map} +0 -0
- /package/dist/{targeting-constants-XJ2VOW7K.js.map → targeting-constants-KIMGCL2N.js.map} +0 -0
- /package/dist/{targeting-criteria-GOTI75Y4.js.map → targeting-criteria-5BN4EELG.js.map} +0 -0
- /package/dist/{targets-FC7CGFRD.js.map → targets-DBQNYE34.js.map} +0 -0
- /package/dist/{templates-EF5LOWP5.js.map → templates-J2NZIOY2.js.map} +0 -0
- /package/dist/{testimonials-MRMRWF2T.js.map → testimonials-2IJE6K2L.js.map} +0 -0
- /package/dist/{themes-2ZV7OCJ3.js.map → themes-6BRONMJ2.js.map} +0 -0
- /package/dist/{top-companies-VTLR6GTW.js.map → top-companies-TAA7HKTA.js.map} +0 -0
- /package/dist/{translate-UMTKAUJ3.js.map → translate-ZVQQJJQT.js.map} +0 -0
- /package/dist/{triggers-Y7ZMMY3C.js.map → triggers-2VPLSNP7.js.map} +0 -0
- /package/dist/{tweets-ATP3FQAC.js.map → tweets-YEMIGLP7.js.map} +0 -0
- /package/dist/{unfollow-CBVPGPF7.js.map → unfollow-S6XQVQIA.js.map} +0 -0
- /package/dist/{unlink-HZCFLXH4.js.map → unlink-DDTHG25I.js.map} +0 -0
- /package/dist/{unstage-562HOOE7.js.map → unstage-ABSCDW43.js.map} +0 -0
- /package/dist/{unstage-V7GMMOWR.js.map → unstage-VDLTHNHY.js.map} +0 -0
- /package/dist/{update-I5A5TOZJ.js.map → update-C2QWKWXB.js.map} +0 -0
- /package/dist/{update-ATSXY5UN.js.map → update-COMDOEYG.js.map} +0 -0
- /package/dist/{update-DKU2UDFA.js.map → update-FMROPHXQ.js.map} +0 -0
- /package/dist/{update-ZF3VFYLB.js.map → update-SY54UP5O.js.map} +0 -0
- /package/dist/{upload-SIAI6NMH.js.map → upload-7QBWAQLM.js.map} +0 -0
- /package/dist/{upload-L5SYZB3X.js.map → upload-PTXQ7IEE.js.map} +0 -0
- /package/dist/{upscale-YWRLFU22.js.map → upscale-YQ2KZRCI.js.map} +0 -0
- /package/dist/{url-options-JSX6HBZL.js.map → url-options-QRGN4Q34.js.map} +0 -0
- /package/dist/{url-params-AZUI3CW3.js.map → url-params-ZBOCO4CG.js.map} +0 -0
- /package/dist/{use-UT2DIZOG.js.map → use-BFD6WCDB.js.map} +0 -0
- /package/dist/{validate-MC4EIQI5.js.map → validate-M7B2SBDH.js.map} +0 -0
- /package/dist/{variant-FLZJYGYL.js.map → variant-U77I2UDY.js.map} +0 -0
- /package/dist/{videos-S7BYFSU3.js.map → videos-2EPKRCW6.js.map} +0 -0
- /package/dist/{view-NPAG66QK.js.map → view-BDLUTKMK.js.map} +0 -0
- /package/dist/{viewer-check-45T2HLAH.js.map → viewer-check-YF6ENNNL.js.map} +0 -0
- /package/dist/{web-5R7LHKIT.js.map → web-JRDXE2GL.js.map} +0 -0
- /package/dist/{write-commands-VXL5QKMT.js.map → write-commands-3UXPJJ75.js.map} +0 -0
- /package/dist/{write-commands-MFRZD6Q6.js.map → write-commands-HZI5YF52.js.map} +0 -0
- /package/dist/{write-commands-VWSLDBPT.js.map → write-commands-MYA273DT.js.map} +0 -0
- /package/dist/{write-commands-WTRSOSO3.js.map → write-commands-OQPBPQ77.js.map} +0 -0
- /package/dist/{x-22PKVRYX.js.map → x-WA3Q46K6.js.map} +0 -0
package/dist/chunk-ZIHEIVJC.js
DELETED
|
@@ -1,4410 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
bandViolations,
|
|
3
|
-
blockingArea,
|
|
4
|
-
bytesOf,
|
|
5
|
-
checkStaticImage,
|
|
6
|
-
normalizeCopy,
|
|
7
|
-
safeRectPx,
|
|
8
|
-
staticSafeArea,
|
|
9
|
-
textMismatch
|
|
10
|
-
} from "./chunk-KFE3S63W.js";
|
|
11
|
-
import {
|
|
12
|
-
HAND_LETTERED,
|
|
13
|
-
conceptSummary,
|
|
14
|
-
plainText,
|
|
15
|
-
readAdSpec,
|
|
16
|
-
seesReferenceStructure,
|
|
17
|
-
withRenderedLettering
|
|
18
|
-
} from "./chunk-VB6WJJR3.js";
|
|
19
|
-
import {
|
|
20
|
-
AD_IMAGE_MODEL,
|
|
21
|
-
MODEL_REGISTRY,
|
|
22
|
-
bestSupportedQuality,
|
|
23
|
-
isManagedNodeKind,
|
|
24
|
-
maxClipSeconds,
|
|
25
|
-
nearestSupportedAspectRatio,
|
|
26
|
-
nearestSupportedImageSize,
|
|
27
|
-
supportsLastFrame,
|
|
28
|
-
supportsParam,
|
|
29
|
-
validateModelInputs,
|
|
30
|
-
validateModelParams
|
|
31
|
-
} from "./chunk-CHSNHVYF.js";
|
|
32
|
-
import {
|
|
33
|
-
askAboutImages
|
|
34
|
-
} from "./chunk-RX6F3GEA.js";
|
|
35
|
-
import {
|
|
36
|
-
marketCountryName
|
|
37
|
-
} from "./chunk-5HYSKH77.js";
|
|
38
|
-
import {
|
|
39
|
-
ASSET_KINDS,
|
|
40
|
-
BackendHttpError,
|
|
41
|
-
HttpClient,
|
|
42
|
-
classifyBackendError,
|
|
43
|
-
forEachString,
|
|
44
|
-
isBlocking,
|
|
45
|
-
looksLikeHttpUrl,
|
|
46
|
-
resolveOutputKinds
|
|
47
|
-
} from "./chunk-HRYOZUSK.js";
|
|
48
|
-
|
|
49
|
-
// src/engine/scaffold/static-brief.ts
|
|
50
|
-
var wordCount = (s) => s.split(/\s+/).filter(Boolean).length;
|
|
51
|
-
var sentence = (s) => {
|
|
52
|
-
const t = s.trim();
|
|
53
|
-
return t && !/[.!?]$/.test(t) ? `${t}.` : t;
|
|
54
|
-
};
|
|
55
|
-
var capitalize = (s) => s ? s[0]?.toUpperCase() + s.slice(1) : s;
|
|
56
|
-
function tidy(s) {
|
|
57
|
-
return s.replace(/\.{3,}/g, "\u2026").replace(
|
|
58
|
-
/([.!?,;:])(?:\s*[.,;:])+/g,
|
|
59
|
-
(run, first) => /[!?]/.test(first) ? first : run.trim().at(-1) ?? ""
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
var EMPTY_CLAUSE = /^(none|null|nil|n a|na|not applicable)$/;
|
|
63
|
-
function keepClauses(text, drop = () => false) {
|
|
64
|
-
const kept = [];
|
|
65
|
-
const seen = /* @__PURE__ */ new Set();
|
|
66
|
-
for (const part of text.split(/(?<=[,;.!?])\s+/)) {
|
|
67
|
-
const body = normalized(part);
|
|
68
|
-
if (body && !EMPTY_CLAUSE.test(body) && !seen.has(body) && !drop(part)) {
|
|
69
|
-
seen.add(body);
|
|
70
|
-
kept.push(part.trim());
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const end = part.trim().match(/[.!?]$/)?.[0];
|
|
74
|
-
const last = kept.length - 1;
|
|
75
|
-
if (end && last >= 0)
|
|
76
|
-
kept[last] = (kept[last] ?? "").replace(/[,;:]?$/, (m) => /[.!?]$/.test(kept[last] ?? "") ? m : end);
|
|
77
|
-
}
|
|
78
|
-
return kept.join(" ");
|
|
79
|
-
}
|
|
80
|
-
var MIN_COPY_LETTERS = 3;
|
|
81
|
-
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82
|
-
var wordEdge = (ch, look) => ch && /[\p{L}\p{N}]/u.test(ch) ? look === "behind" ? "(?<![\\p{L}\\p{N}])" : "(?![\\p{L}\\p{N}])" : "";
|
|
83
|
-
var phrase = (text, wrap = (core) => core) => new RegExp(`${wordEdge(text[0], "behind")}${wrap(escapeRegExp(text))}${wordEdge(text.at(-1), "ahead")}`, "giu");
|
|
84
|
-
var longEnough = (copy) => (copy.match(new RegExp("\\p{L}", "gu")) ?? []).length >= MIN_COPY_LETTERS;
|
|
85
|
-
function copyAsLineRefs(text, copy) {
|
|
86
|
-
const byLength = copy.map((t, i) => ({ t, n: i + 1 })).sort((a, b) => b.t.length - a.t.length);
|
|
87
|
-
return byLength.reduce(
|
|
88
|
-
(out, { t, n }) => longEnough(t) ? out.replace(
|
|
89
|
-
phrase(t, (core) => `['"\u201C\u2018]?${core}['"\u201D\u2019]?`),
|
|
90
|
-
`line ${n}`
|
|
91
|
-
) : out,
|
|
92
|
-
text
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
function unwanted(clause, k) {
|
|
96
|
-
if (k.logo && /\bno (logos?|wordmarks?|branding)\b/i.test(clause)) return true;
|
|
97
|
-
if (HAND_LETTERED.test(clause)) return true;
|
|
98
|
-
if (/\bsource (ad|composition)\b|\bORIGINAL\b|\bcompetitor|\breference ad\b/.test(clause)) return true;
|
|
99
|
-
return k.positive && NEGATION.test(clause.replace(/"[^"\n]*"/g, ""));
|
|
100
|
-
}
|
|
101
|
-
var NEGATION = /\b(no|not|never|don't|do not|avoid|without)\b/i;
|
|
102
|
-
function plain(text, k) {
|
|
103
|
-
let out = plainText(text).replace(new RegExp("(?<=\\p{L})_(?=\\p{L})", "gu"), " ");
|
|
104
|
-
if (k) out = copyAsLineRefs(out, k.copy).replace(/\bfaithful(ly)?\b/gi, (_m, ly) => `exact${ly ?? ""}`);
|
|
105
|
-
return tidy(keepClauses(out, k ? (clause) => unwanted(clause, k) : void 0)).replace(/\s*[,;:]+$/, "");
|
|
106
|
-
}
|
|
107
|
-
var normalized = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
108
|
-
function unsaid(text, said) {
|
|
109
|
-
const known = ` ${normalized(said)} `;
|
|
110
|
-
return (text.match(/[^.!?]+[.!?]*/g) ?? []).map((s) => s.trim()).filter((s) => normalized(s) && !known.includes(` ${normalized(s)} `)).join(" ");
|
|
111
|
-
}
|
|
112
|
-
function clip(text, max) {
|
|
113
|
-
if (wordCount(text) <= max) return text;
|
|
114
|
-
const sentences = text.match(/[^.!?]+[.!?]*/g) ?? [text];
|
|
115
|
-
let out = "";
|
|
116
|
-
for (const s of sentences) {
|
|
117
|
-
if (wordCount(out + s) > max) break;
|
|
118
|
-
out += s;
|
|
119
|
-
}
|
|
120
|
-
if (out.trim()) return sentence(out.trim().replace(/[\s,;:]+$/, ""));
|
|
121
|
-
const head = text.split(/\s+/).slice(0, max).join(" ");
|
|
122
|
-
const clause = head.match(/^(.*?)[,;:—–](?:\s[^,;:—–]*)?$/)?.[1];
|
|
123
|
-
return sentence((clause?.trim() || head).replace(/[\s,;:]+$/, ""));
|
|
124
|
-
}
|
|
125
|
-
function neutralName(l) {
|
|
126
|
-
if (l > 0.95) return "white";
|
|
127
|
-
if (l > 0.8) return "off-white";
|
|
128
|
-
if (l > 0.6) return "light grey";
|
|
129
|
-
if (l > 0.35) return "grey";
|
|
130
|
-
return l > 0.12 ? "dark grey" : "black";
|
|
131
|
-
}
|
|
132
|
-
var HUES = [
|
|
133
|
-
[15, "red"],
|
|
134
|
-
[40, "orange"],
|
|
135
|
-
[65, "yellow"],
|
|
136
|
-
[165, "green"],
|
|
137
|
-
[185, "teal"],
|
|
138
|
-
[250, "blue"],
|
|
139
|
-
[290, "purple"],
|
|
140
|
-
[340, "pink"],
|
|
141
|
-
[360, "red"]
|
|
142
|
-
];
|
|
143
|
-
function hueOf(r, g, b, max, d) {
|
|
144
|
-
const raw = max === r ? (g - b) / d % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
145
|
-
return (raw * 60 + 360) % 360;
|
|
146
|
-
}
|
|
147
|
-
function colourName(hex) {
|
|
148
|
-
const n = Number.parseInt(hex.slice(1), 16);
|
|
149
|
-
const r = (n >> 16 & 255) / 255;
|
|
150
|
-
const g = (n >> 8 & 255) / 255;
|
|
151
|
-
const b = (n & 255) / 255;
|
|
152
|
-
const max = Math.max(r, g, b);
|
|
153
|
-
const min = Math.min(r, g, b);
|
|
154
|
-
const l = (max + min) / 2;
|
|
155
|
-
const d = max - min;
|
|
156
|
-
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
157
|
-
if (s < 0.15 || l > 0.95 || l < 0.07) return neutralName(l);
|
|
158
|
-
const h = hueOf(r, g, b, max, d);
|
|
159
|
-
const band = HUES.find(([edge]) => h < edge)?.[1] ?? "red";
|
|
160
|
-
const hue = band === "orange" && l < 0.35 ? "brown" : band;
|
|
161
|
-
const shade = l < 0.3 ? "dark " : l > 0.8 ? "pale " : "";
|
|
162
|
-
return `${shade}${hue}`;
|
|
163
|
-
}
|
|
164
|
-
var VERTICAL_NOUN = {
|
|
165
|
-
reels: "full-screen vertical ad for Stories and Reels",
|
|
166
|
-
stories: "full-screen vertical Stories ad",
|
|
167
|
-
feed: "full-screen vertical ad"
|
|
168
|
-
};
|
|
169
|
-
function deliverableNoun(format, placements) {
|
|
170
|
-
if (format === "9:16") {
|
|
171
|
-
const safe = staticSafeArea(format, placements);
|
|
172
|
-
return safe.placement === "vertical" ? `full-screen vertical ${safe.surface} ad` : VERTICAL_NOUN[safe.placement] ?? "full-screen vertical ad";
|
|
173
|
-
}
|
|
174
|
-
const [w, h] = format.split(":").map(Number);
|
|
175
|
-
if (!w || !h) return "ad";
|
|
176
|
-
return w < h ? "vertical feed ad" : w === h ? "square feed ad" : "landscape ad";
|
|
177
|
-
}
|
|
178
|
-
function keptInside(logo, button) {
|
|
179
|
-
const parts = ["every word", ...logo ? ["the logo"] : [], ...button ? ["the button"] : []];
|
|
180
|
-
return parts.length === 1 ? parts[0] ?? "" : `${parts.slice(0, -1).join(", ")} and ${parts.at(-1)}`;
|
|
181
|
-
}
|
|
182
|
-
function safeAreaText(format, placements, items, structure) {
|
|
183
|
-
const safe = blockingArea(format, placements);
|
|
184
|
-
const pct2 = (f) => `${Math.round(f * 100)}%`;
|
|
185
|
-
const side = pct2(Math.max(safe.left, safe.right));
|
|
186
|
-
const sits = items.includes(" and ") ? "sit" : "sits";
|
|
187
|
-
if (safe.placement !== "feed") {
|
|
188
|
-
const surface = safe.placement === "reels" ? "Stories and Reels" : safe.surface;
|
|
189
|
-
return structure === "labelled_sections" ? `Keep ${items} between the top ${pct2(safe.top)} and the bottom ${pct2(safe.bottom)} of the frame (the app's interface covers them; scene only there), at least ${side} in from each side.` : `In ${surface} the app's interface covers the top ${pct2(safe.top)} and the bottom ${pct2(safe.bottom)} of the screen, so those bands hold only the scene; ${items} ${sits} between them, at least ${side} in from each side.`;
|
|
190
|
-
}
|
|
191
|
-
return edgeText(safe, items, structure);
|
|
192
|
-
}
|
|
193
|
-
function edgeText(area, items, structure) {
|
|
194
|
-
const pct2 = (f) => `${Math.round(f * 100)}%`;
|
|
195
|
-
const side = pct2(Math.max(area.left, area.right));
|
|
196
|
-
const sits = items.includes(" and ") ? "sit" : "sits";
|
|
197
|
-
const edges = pct2(area.top) === side && pct2(area.bottom) === side ? `at least ${side} in from every edge` : `at least ${pct2(Math.max(area.top, area.bottom))} in from the top and bottom edges and ${side} from the sides`;
|
|
198
|
-
return structure === "labelled_sections" ? `Keep ${items} ${edges}.` : `${capitalize(items)} ${sits} ${edges}, clear of the frame.`;
|
|
199
|
-
}
|
|
200
|
-
var MARGIN_HEADROOM = 0.03;
|
|
201
|
-
function marginText(planned, items, structure) {
|
|
202
|
-
const scene = structure === "labelled_sections" ? "The scene runs to every edge of the frame, with no border or frame around the ad." : "The scene runs to every edge of the frame and continues beyond it.";
|
|
203
|
-
const { top, bottom, left, right } = planned.area;
|
|
204
|
-
const asked = {
|
|
205
|
-
top: top + MARGIN_HEADROOM,
|
|
206
|
-
bottom: bottom + MARGIN_HEADROOM,
|
|
207
|
-
left: left + MARGIN_HEADROOM,
|
|
208
|
-
right: right + MARGIN_HEADROOM
|
|
209
|
-
};
|
|
210
|
-
return `${edgeText(asked, items, structure)} ${scene}`;
|
|
211
|
-
}
|
|
212
|
-
function bandText(planned, items, flowing, appChrome) {
|
|
213
|
-
const band = planned.area;
|
|
214
|
-
const pct2 = (f) => `${Math.round(f * 100)}%`;
|
|
215
|
-
const region = `from ${pct2(band.top)} to ${pct2(1 - band.bottom)} of the height and from ${pct2(band.left)} to ${pct2(1 - band.right)} of the width`;
|
|
216
|
-
const sits = items.includes(" and ") ? "sit" : "sits";
|
|
217
|
-
const reasons = [
|
|
218
|
-
...appChrome ? [
|
|
219
|
-
flowing ? "the app draws its interface over the top and bottom of the screen" : "the app's interface covers the top and bottom"
|
|
220
|
-
] : [],
|
|
221
|
-
...planned.cut ? ["the other sizes are cut from this picture"] : []
|
|
222
|
-
];
|
|
223
|
-
if (reasons.length === 0)
|
|
224
|
-
reasons.push(flowing ? "the edges of a feed crop tightly" : "a feed crops tightly at the edges");
|
|
225
|
-
if (flowing) {
|
|
226
|
-
return `${capitalize(items)} ${sits} inside one region, ${region}, because ${reasons.join(" and ")}; the rest of the frame carries the scene only.`;
|
|
227
|
-
}
|
|
228
|
-
return `${items} ${sits} inside one region: ${region}. Outside it the frame carries the scene only: ${reasons.join(", and ")}.`;
|
|
229
|
-
}
|
|
230
|
-
var isAnimal = (r) => r.id.startsWith("ANIMAL_") || !r.id.startsWith("PERSON_") && ANIMALS.test(r.what);
|
|
231
|
-
var personOrAnimal = (r) => isAnimal(r) ? "animal" : "person";
|
|
232
|
-
function thirdPartyLogo(brand, k) {
|
|
233
|
-
const where = brand.what ? plain(brand.what, k) : "";
|
|
234
|
-
return `the ${plain(brand.name)} logo${where ? `, on ${where.replace(/^on /i, "")}` : ""}`;
|
|
235
|
-
}
|
|
236
|
-
var thirdPartyNames = (spec) => spec.third_party_brands.map((b) => `the ${plain(b.name)} logo`).join(" and ");
|
|
237
|
-
function otherBrandsText(c) {
|
|
238
|
-
const brands = c.spec.third_party_brands;
|
|
239
|
-
if (brands.length === 0 || c.job !== "generate") return "";
|
|
240
|
-
const marks = brands.map((b) => thirdPartyLogo(b, c.clean)).join("; ");
|
|
241
|
-
return c.flowing ? ` ${sentence(capitalize(marks))} Each appears exactly as that brand draws it.` : `
|
|
242
|
-
Other brands: ${marks}, exactly as ${brands.length === 1 ? "that brand draws it" : "each brand draws its own"}.`;
|
|
243
|
-
}
|
|
244
|
-
var LOGO_SIZE_WORDS = /\b(small|smaller|tiny|little|subtle|discreet|faint|minimal)\b/i;
|
|
245
|
-
function logoInstruction(p, ctx) {
|
|
246
|
-
if (ctx.native) {
|
|
247
|
-
return ctx.flowing ? `the brand's real logo, as the sender's picture or signature where the disguise puts it, small as such a mark is. Reproduce it once, exactly as drawn in Image ${p.index}.` : "the brand's real logo, as the sender's picture or signature where the disguise puts it, small as such a mark is. Reproduce it once, exactly as drawn.";
|
|
248
|
-
}
|
|
249
|
-
const position = ctx.logoPosition ? keepClauses(plain(ctx.logoPosition, ctx.clean), (clause) => LOGO_SIZE_WORDS.test(clause)) : "";
|
|
250
|
-
const at = position ? `, ${position.replace(/[\s,;:.!?]+$/, "")}` : "";
|
|
251
|
-
const plate = ctx.plates.length > 0 ? ctx.plates.map((u) => `${u.name} (${u.hex})`).join(" or ") : "white or black";
|
|
252
|
-
const where = `at least a fifth of the frame wide, on an area it contrasts with or a small ${plate} plate`;
|
|
253
|
-
if (ctx.short)
|
|
254
|
-
return "the brand's real logo. Reproduce it once, apart from the product, exactly as drawn, at least a fifth of the frame wide.";
|
|
255
|
-
return ctx.flowing ? `the brand's real logo. Reproduce it once, apart from the product${at}, exactly as drawn in Image ${p.index}, ${where}.` : `the brand's real logo. Reproduce it once, never on the product${at}, exactly as drawn, ${where}.`;
|
|
256
|
-
}
|
|
257
|
-
function identityInstruction(p, what, keep, flowing, short) {
|
|
258
|
-
if (p.spec && personOrAnimal(p.spec) === "person") {
|
|
259
|
-
if (short) return `${what} Same face, hair, skin tone and build; one person, once.${keep}`;
|
|
260
|
-
return flowing ? `${what} Keep their facial features, hair, skin tone and build exactly the same as in Image ${p.index}, and show them once; several views in that picture are all this one person.${keep}` : `${what} Keep this exact face, hair, skin tone and build, and show them once; several views in the picture are all this one person.${keep}`;
|
|
261
|
-
}
|
|
262
|
-
return flowing ? `${what} Keep its face, markings and colours exactly the same as in Image ${p.index}, and show it once; several views in that picture are all this one animal.${keep}` : `${what} Keep its exact face, markings and colours, and show it once; several views in the picture are all this one animal.${keep}`;
|
|
263
|
-
}
|
|
264
|
-
function typeInstruction(ctx) {
|
|
265
|
-
if (ctx.flowing) return "a sample of the brand typeface; its words are only a sample.";
|
|
266
|
-
if (ctx.allDesigned) return "a sample of the brand typeface for every line; its words are only a sample.";
|
|
267
|
-
const lines = `line${ctx.designedLines.length === 1 ? "" : "s"} ${ctx.designedLines.join(", ")}`;
|
|
268
|
-
return `a sample of the brand typeface for ${lines}; its words are only a sample.`;
|
|
269
|
-
}
|
|
270
|
-
function pictureInstruction(p, ctx) {
|
|
271
|
-
const what = p.spec ? sentence(plain(p.spec.what, ctx.clean)) : "";
|
|
272
|
-
const keep = p.spec?.keep ? ` Keep ${plain(p.spec.keep, ctx.clean)}.` : "";
|
|
273
|
-
switch (p.role) {
|
|
274
|
-
case "base":
|
|
275
|
-
return "screen content takes priority over other references unless explicitly changed.";
|
|
276
|
-
case "identity":
|
|
277
|
-
return identityInstruction(p, what, keep, ctx.flowing, ctx.short);
|
|
278
|
-
// The user saw one label come back two ways (E4 gallery, BENCH.md § "E1 v2"): the label is the product's.
|
|
279
|
-
case "product":
|
|
280
|
-
return ctx.flowing ? `${what} Keep it and its label, text and design, exactly as in Image ${p.index}.${keep}` : `${what} Reproduce this exact product and its label, text and design unchanged.${keep}`;
|
|
281
|
-
case "logo":
|
|
282
|
-
return logoInstruction(p, ctx);
|
|
283
|
-
case "prop": {
|
|
284
|
-
const brand = ctx.thirdParty.find((b) => b.logo === p.label);
|
|
285
|
-
if (brand) return `${thirdPartyLogo(brand, ctx.clean)}. Reproduce it exactly as drawn, readable.`;
|
|
286
|
-
return `${what} Show it as it looks here.${keep}`;
|
|
287
|
-
}
|
|
288
|
-
case "type":
|
|
289
|
-
return typeInstruction(ctx);
|
|
290
|
-
case "layout":
|
|
291
|
-
return "the ad this one is built on, for its structure only: how it is built (its container, its layout, its pacing); never its words, brand, logo, people, products or colours, which belong to another advertiser.";
|
|
292
|
-
default:
|
|
293
|
-
return `${what} Show it as it looks here.${keep}`;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
function wiredPictures(spec, opts, job, designed) {
|
|
297
|
-
const images = opts.images;
|
|
298
|
-
const refs = spec.references.flatMap((r) => {
|
|
299
|
-
const ref = images ? images.refs?.[r.id] : r.id;
|
|
300
|
-
return ref ? [{ role: r.role, label: r.id, ref, spec: r }] : [];
|
|
301
|
-
});
|
|
302
|
-
const optional = [
|
|
303
|
-
[job !== "generate", "base", "AD", images?.base],
|
|
304
|
-
[spec.logo, "logo", "LOGO", images?.logo],
|
|
305
|
-
[designed, "type", "TYPE", images?.type],
|
|
306
|
-
// The reference, for its structure only, when the ad is built on its concept (C5: the image
|
|
307
|
-
// model never saw the reference, 0/127 renders); `sourceLayout` decides when given.
|
|
308
|
-
[job === "generate" && (opts.sourceLayout ?? seesReferenceStructure(spec)), "layout", "LAYOUT", images?.layout]
|
|
309
|
-
];
|
|
310
|
-
const extras = optional.flatMap(([wanted, role, label, ref]) => wanted && ref ? [{ role, label, ref }] : []);
|
|
311
|
-
return [...refs, ...extras];
|
|
312
|
-
}
|
|
313
|
-
function pictures(spec, opts, job, profile, designed) {
|
|
314
|
-
const order = profile.brief.referenceOrder;
|
|
315
|
-
const sorted = wiredPictures(spec, opts, job, designed).map((p, i) => ({ p, i })).sort((a, b) => order.indexOf(a.p.role) - order.indexOf(b.p.role) || a.i - b.i).map(({ p }) => p);
|
|
316
|
-
const seen = /* @__PURE__ */ new Set();
|
|
317
|
-
const unique = [];
|
|
318
|
-
for (const p of sorted) {
|
|
319
|
-
if (seen.has(p.ref)) continue;
|
|
320
|
-
seen.add(p.ref);
|
|
321
|
-
unique.push(p);
|
|
322
|
-
}
|
|
323
|
-
return unique.map((p, i) => ({ ...p, index: i + 1 }));
|
|
324
|
-
}
|
|
325
|
-
var SYMBOL_NAMES = {
|
|
326
|
-
"\xAE": "a small raised registered-trademark sign",
|
|
327
|
-
"\u2122": "a small raised trademark sign",
|
|
328
|
-
"\xA9": "a copyright sign",
|
|
329
|
-
"\u20AC": "the euro sign",
|
|
330
|
-
"\xA3": "the pound sign"
|
|
331
|
-
};
|
|
332
|
-
function quotedCopy(text) {
|
|
333
|
-
return text.split(/\s*\n\s*/).filter(Boolean).join(" ");
|
|
334
|
-
}
|
|
335
|
-
function lineBreaks(text) {
|
|
336
|
-
const lines = text.split(/\s*\n\s*/).filter(Boolean);
|
|
337
|
-
if (lines.length < 2) return "";
|
|
338
|
-
const ends = lines.slice(0, -1).map((l) => `\u201C${l.split(/\s+/).at(-1)}\u201D`);
|
|
339
|
-
return `, on ${lines.length} lines, breaking after ${ends.join(" and ")}`;
|
|
340
|
-
}
|
|
341
|
-
function clipNote(text, max) {
|
|
342
|
-
if (wordCount(text) <= max) return text;
|
|
343
|
-
return text.split(/\s+/).slice(0, max).join(" ").replace(/[,;:.]+$/, "");
|
|
344
|
-
}
|
|
345
|
-
var ROLE_WORDS = {
|
|
346
|
-
headline: "the headline",
|
|
347
|
-
title: "the headline",
|
|
348
|
-
subhead: "the subhead",
|
|
349
|
-
subheadline: "the subhead",
|
|
350
|
-
subtitle: "the subhead",
|
|
351
|
-
price: "the price",
|
|
352
|
-
stat: "the key figure",
|
|
353
|
-
figure: "the key figure",
|
|
354
|
-
number: "the key figure",
|
|
355
|
-
discount: "the discount",
|
|
356
|
-
cta: "the button",
|
|
357
|
-
button: "the button",
|
|
358
|
-
call_to_action: "the button",
|
|
359
|
-
caption: "a caption",
|
|
360
|
-
fine_print: "the fine print",
|
|
361
|
-
legal: "the fine print",
|
|
362
|
-
disclaimer: "the fine print",
|
|
363
|
-
footnote: "the fine print",
|
|
364
|
-
label: "a label",
|
|
365
|
-
list_item: "a list item",
|
|
366
|
-
bullet: "a list item",
|
|
367
|
-
badge: "a badge",
|
|
368
|
-
quote: "the quote",
|
|
369
|
-
testimonial: "the quote",
|
|
370
|
-
attribution: "who said it",
|
|
371
|
-
rating: "the rating",
|
|
372
|
-
tagline: "the tagline"
|
|
373
|
-
};
|
|
374
|
-
var roleKey = (role) => (role ?? "").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
375
|
-
var roleWords = (line) => ROLE_WORDS[roleKey(line.role)] ?? "";
|
|
376
|
-
function copyLineText(line, n, face, k, level) {
|
|
377
|
-
const cap = NOTE_CAP[level] ?? 0;
|
|
378
|
-
const look = line.look ? clipNote(plain(line.look, k), cap) : "";
|
|
379
|
-
const note = line.position ? clipNote(plain(line.position, k), cap) : "";
|
|
380
|
-
const position = note ? `; ${note}` : "";
|
|
381
|
-
const text = quotedCopy(line.text);
|
|
382
|
-
const detail = `${look ? line.lettering === "in_scene" ? `: ${look}` : `, ${look}` : ""}${lineBreaks(line.text)}${position}`;
|
|
383
|
-
const lettered = line.lettering === "in_scene" ? line.on ? `lettered on ${plain(line.on, k)}` : "lettered on its object in the scene" : face ? `set in ${face}` : "";
|
|
384
|
-
const lead = [roleWords(line), lettered].filter(Boolean).join(", ") || "designed";
|
|
385
|
-
return sentence(`${n}. "${text}" \u2014 ${lead}${detail}`);
|
|
386
|
-
}
|
|
387
|
-
function textNotes(copy, profile) {
|
|
388
|
-
const notes = Object.entries(SYMBOL_NAMES).flatMap(([symbol, name]) => {
|
|
389
|
-
const lines = copy.flatMap((line, i) => line.text.includes(symbol) ? [i + 1] : []);
|
|
390
|
-
if (lines.length === 0) return [];
|
|
391
|
-
const which = lines.length === 1 ? `line ${lines[0]}` : `lines ${lines.slice(0, -1).join(", ")} and ${lines.at(-1)}`;
|
|
392
|
-
return [`The ${symbol} in ${which} is ${name}.`];
|
|
393
|
-
});
|
|
394
|
-
const marks = [...new Set(copy.flatMap((l) => l.text.match(/[À-ÖØ-öø-ÿ¿¡]/g) ?? []))];
|
|
395
|
-
if (profile.brief.text.keepAccents && marks.length > 0)
|
|
396
|
-
notes.push(`Keep every accent and mark exactly: ${marks.join(" ")}.`);
|
|
397
|
-
return notes;
|
|
398
|
-
}
|
|
399
|
-
var BUTTON_ROLES = /* @__PURE__ */ new Set(["cta", "button", "call_to_action"]);
|
|
400
|
-
var isButton = (l) => BUTTON_ROLES.has(roleKey(l.role)) || /button/i.test(l.look ?? "");
|
|
401
|
-
var FIGURE_ROLES = /* @__PURE__ */ new Set(["price", "stat", "figure", "number", "discount"]);
|
|
402
|
-
var HEADLINE_ROLES = /* @__PURE__ */ new Set(["headline", "title"]);
|
|
403
|
-
var FINE_PRINT_ROLES = /* @__PURE__ */ new Set(["fine_print", "legal", "disclaimer", "footnote"]);
|
|
404
|
-
var BUTTON_SIZE = "a solid button at least a third of the frame wide, its words large and bold";
|
|
405
|
-
var designedLines = (copy) => copy.flatMap((line, i) => line.lettering === "designed" ? [{ line, n: i + 1 }] : []);
|
|
406
|
-
var finePrint = (ranked) => ranked.designed.filter(({ line }) => FINE_PRINT_ROLES.has(roleKey(line.role)));
|
|
407
|
-
var NATIVE_BUTTON = "as the native element would carry it (a link, a reply, a small button of its own), never a big ad button";
|
|
408
|
-
function fullHierarchy(ranked, lead, native) {
|
|
409
|
-
const { designed, figure, headline, button } = ranked;
|
|
410
|
-
const size = lead === figure ? "very large" : "at least half the frame wide";
|
|
411
|
-
return [
|
|
412
|
-
...lead ? [`line ${lead.n} largest (${size}), read first`] : [],
|
|
413
|
-
...lead && figure && headline ? [`then line ${headline.n}`] : [],
|
|
414
|
-
...button ? [`line ${button.n} ${native ? NATIVE_BUTTON : BUTTON_SIZE}`] : [],
|
|
415
|
-
...designed.length > 1 ? ["every line readable at a glance"] : [],
|
|
416
|
-
...finePrint(ranked).map(({ n }) => `line ${n} smallest, still legible`)
|
|
417
|
-
];
|
|
418
|
-
}
|
|
419
|
-
function shortHierarchy(ranked, lead, native) {
|
|
420
|
-
return [
|
|
421
|
-
...lead ? [`line ${lead.n} largest, read first`] : [],
|
|
422
|
-
...ranked.button ? [
|
|
423
|
-
native ? `line ${ranked.button.n} as the native element would carry it` : `line ${ranked.button.n} a solid button a third of the frame wide`
|
|
424
|
-
] : [],
|
|
425
|
-
...finePrint(ranked).map(({ n }) => `line ${n} smallest`)
|
|
426
|
-
];
|
|
427
|
-
}
|
|
428
|
-
function rankedLines(copy) {
|
|
429
|
-
const designed = designedLines(copy);
|
|
430
|
-
const numbered = copy.map((line, i) => ({ line, n: i + 1 }));
|
|
431
|
-
const byRole = (roles) => numbered.find(({ line }) => roles.has(roleKey(line.role)));
|
|
432
|
-
const figure = byRole(FIGURE_ROLES);
|
|
433
|
-
const headline = byRole(HEADLINE_ROLES);
|
|
434
|
-
const lead = figure ?? headline ?? designed.find(({ line }) => !isButton(line) && !FINE_PRINT_ROLES.has(roleKey(line.role)));
|
|
435
|
-
return { designed, figure, headline, lead, button: designed.find(({ line }) => isButton(line)) };
|
|
436
|
-
}
|
|
437
|
-
function hierarchyText(c) {
|
|
438
|
-
const ranked = rankedLines(c.spec.copy);
|
|
439
|
-
const lead = c.opts.variant === 2 ? void 0 : ranked.lead;
|
|
440
|
-
const native = c.spec.brand_presence === "native";
|
|
441
|
-
const parts = c.level >= SHORT_DIRECTION_LEVEL ? shortHierarchy(ranked, lead, native) : fullHierarchy(ranked, lead, native);
|
|
442
|
-
if (parts.length === 0) return "";
|
|
443
|
-
return c.flowing ? sentence(capitalize(parts.join("; "))) : `Hierarchy: ${sentence(parts.join("; "))}`;
|
|
444
|
-
}
|
|
445
|
-
function brandRoleColours(spec, brand) {
|
|
446
|
-
const designed = designedLines(spec.copy);
|
|
447
|
-
const figure = designed.find(({ line }) => FIGURE_ROLES.has(roleKey(line.role)));
|
|
448
|
-
const panel = spec.scene.look === "photo" && !isNativeCapture(spec);
|
|
449
|
-
const roles = [
|
|
450
|
-
[spec.scene.look === "designed", "the background", brand.ground],
|
|
451
|
-
[panel && designed.length > 0, "any band or panel behind the words", brand.ground],
|
|
452
|
-
[designed.length > 0, "the designed type", brand.ink],
|
|
453
|
-
[spec.copy.some(isButton), "the button", brand.cta],
|
|
454
|
-
[Boolean(figure), figure ? `line ${figure.n} (or the shape it sits on)` : "", brand.accent],
|
|
455
|
-
[
|
|
456
|
-
!figure && spec.copy.some((l) => /underline|highlight|arrow/i.test(l.look ?? "")),
|
|
457
|
-
"underlines and highlights",
|
|
458
|
-
brand.accent
|
|
459
|
-
]
|
|
460
|
-
];
|
|
461
|
-
return roles.flatMap(([applies, object, hex]) => applies && hex ? [{ object, name: colourName(hex), hex }] : []);
|
|
462
|
-
}
|
|
463
|
-
var NATIVE_CAPTURE = /\b(selfie|phone[- ]camera|phone photo|shot on (?:a )?phone|taken on (?:a )?phone|smartphone photo|ugc|user[- ]generated|webcam|front[- ]camera|snapshot|native caption)\b/i;
|
|
464
|
-
var PHONE_CAMERA = /\b(phone|smartphone|iphone)\b/i;
|
|
465
|
-
function isNativeCapture(spec) {
|
|
466
|
-
if (spec.scene.look !== "photo") return false;
|
|
467
|
-
const capture = spec.scene.capture ?? "";
|
|
468
|
-
return NATIVE_CAPTURE.test(`${spec.scene.description} ${capture}`) || PHONE_CAMERA.test(capture);
|
|
469
|
-
}
|
|
470
|
-
function colourUses(spec, brand, k) {
|
|
471
|
-
const allowed = brand ? new Set(
|
|
472
|
-
[...brand.palette, brand.ground, brand.ink, brand.accent, brand.cta].filter((h) => Boolean(h))
|
|
473
|
-
) : null;
|
|
474
|
-
const own = spec.colors.flatMap((c) => {
|
|
475
|
-
if (c.hex && allowed && !allowed.has(c.hex)) return [];
|
|
476
|
-
const tied = { object: plain(c.object, k), name: plain(c.name, k) };
|
|
477
|
-
return [c.hex && allowed ? { ...tied, hex: c.hex } : tied];
|
|
478
|
-
});
|
|
479
|
-
if (!brand) return own;
|
|
480
|
-
const said = new Set(own.flatMap((u) => u.hex ? [u.hex] : []));
|
|
481
|
-
return [...own, ...brandRoleColours(spec, brand).filter((u) => !u.hex || !said.has(u.hex))];
|
|
482
|
-
}
|
|
483
|
-
function luminance(hex) {
|
|
484
|
-
const n = Number.parseInt(hex.slice(1), 16);
|
|
485
|
-
return (0.2126 * (n >> 16 & 255) + 0.7152 * (n >> 8 & 255) + 0.0722 * (n & 255)) / 255;
|
|
486
|
-
}
|
|
487
|
-
function plateColours(spec, brand, k) {
|
|
488
|
-
if (!brand) return [];
|
|
489
|
-
const hexes = [.../* @__PURE__ */ new Set([brand.ink, brand.ground, brand.accent, brand.cta, ...brand.palette])].filter(
|
|
490
|
-
(h) => Boolean(h)
|
|
491
|
-
);
|
|
492
|
-
if (hexes.length === 0) return [];
|
|
493
|
-
const sorted = [...hexes].sort((a, b) => luminance(a) - luminance(b));
|
|
494
|
-
const ends = [.../* @__PURE__ */ new Set([sorted[0], sorted.at(-1)])].filter((h) => Boolean(h));
|
|
495
|
-
const named = (hex) => spec.colors.find((c) => c.hex === hex)?.name;
|
|
496
|
-
return ends.map((hex) => ({ object: "the logo plate", name: plain(named(hex) ?? colourName(hex), k), hex }));
|
|
497
|
-
}
|
|
498
|
-
function heroOf(pics) {
|
|
499
|
-
const product = pics.find((p) => p.role === "product");
|
|
500
|
-
const identity = pics.find((p) => p.role === "identity" && p.spec);
|
|
501
|
-
if (product) return `the product in Image ${product.index}`;
|
|
502
|
-
if (identity?.spec) return `the ${personOrAnimal(identity.spec)} in Image ${identity.index}`;
|
|
503
|
-
return MAIN_SUBJECT;
|
|
504
|
-
}
|
|
505
|
-
var MAIN_SUBJECT = "the main subject of the scene";
|
|
506
|
-
function variantDirection(c) {
|
|
507
|
-
const [long, short] = c.opts.variant === 1 ? pictureDirection(c) : hookDirection(c);
|
|
508
|
-
const keep = mustKeep(c);
|
|
509
|
-
const kept = keep.length > 0 ? ` Keep ${keep.join(" and ")} as it is.` : "";
|
|
510
|
-
return c.level >= SHORT_DIRECTION_LEVEL ? `${short} ${DEPARTS_SHORT}${kept}` : `${long} ${DEPARTS}${kept}`;
|
|
511
|
-
}
|
|
512
|
-
var DEPARTS = "It leads where it differs from the scene and line notes above.";
|
|
513
|
-
var DEPARTS_SHORT = "It leads over the notes above.";
|
|
514
|
-
var isSupport = (l) => isButton(l) || FINE_PRINT_ROLES.has(roleKey(l.role)) || roleKey(l.role) === "attribution";
|
|
515
|
-
function hookDirection(c) {
|
|
516
|
-
const hooks = c.spec.copy.flatMap(
|
|
517
|
-
(l, i) => l.lettering === "designed" && !isSupport(l) ? [{ n: i + 1, text: l.text }] : []
|
|
518
|
-
);
|
|
519
|
-
const [first, ...rest] = hooks;
|
|
520
|
-
const lead = rest[0];
|
|
521
|
-
if (first && lead)
|
|
522
|
-
return [
|
|
523
|
-
`a type-led layout with another hook: line ${lead.n} leads, set as the largest words in the ad and read first; line ${first.n} follows it, smaller; the picture sits behind or beside the words as support, with the same words.`,
|
|
524
|
-
`line ${lead.n} leads, largest; line ${first.n} smaller.`
|
|
525
|
-
];
|
|
526
|
-
if (first)
|
|
527
|
-
return [
|
|
528
|
-
`a type-led layout: line ${first.n} set very large, across most of the frame's width, at least three times the size of every other line; the picture sits small behind or beside it as support, with the same words.`,
|
|
529
|
-
`line ${first.n} very large, every other line small.`
|
|
530
|
-
];
|
|
531
|
-
const lettered = c.spec.copy.findIndex((l) => l.lettering === "in_scene");
|
|
532
|
-
if (lettered >= 0) {
|
|
533
|
-
const on = c.spec.copy[lettered]?.on;
|
|
534
|
-
const object = on ? plain(on, c.clean) : `the object carrying line ${lettered + 1}`;
|
|
535
|
-
return [
|
|
536
|
-
`a closer framing of the words: ${object} seen close and large in the frame, so its words are the first thing read, with the same words.`,
|
|
537
|
-
`${object} close and large.`
|
|
538
|
-
];
|
|
539
|
-
}
|
|
540
|
-
const hero = heroOf(c.pics);
|
|
541
|
-
return [
|
|
542
|
-
`another arrangement: ${hero} on the other side of the frame, seen from another angle, with the same words.`,
|
|
543
|
-
`${hero} on the other side, from another angle.`
|
|
544
|
-
];
|
|
545
|
-
}
|
|
546
|
-
var CLOSE_FRAMING = /\b(?:close[- ]?up|macro|detail shot|close(?: [a-z-]+){0,2} shot|tight(?:ly)? (?:crop|cropped|framed|shot))\b/i;
|
|
547
|
-
function pictureDirection(c) {
|
|
548
|
-
const hero = heroOf(c.pics);
|
|
549
|
-
if (c.spec.scene.look === "designed") {
|
|
550
|
-
const shown = hero === MAIN_SUBJECT ? "the thing the message is about" : hero;
|
|
551
|
-
return [
|
|
552
|
-
`a picture-led layout: ${shown} shown as one large picture filling most of the frame, the words set compactly over a calm part of it, with the same words.`,
|
|
553
|
-
`a picture-led layout: ${shown} large, the words compact.`
|
|
554
|
-
];
|
|
555
|
-
}
|
|
556
|
-
if (c.spec.scene.look === "illustration")
|
|
557
|
-
return [
|
|
558
|
-
`a close, simple drawing: ${hero} drawn large, filling most of the frame, on one flat background colour, with the same words.`,
|
|
559
|
-
`a close, simple drawing of ${hero} on one flat colour.`
|
|
560
|
-
];
|
|
561
|
-
const scene = `${c.spec.scene.description} ${c.spec.scene.capture ?? ""}`;
|
|
562
|
-
if (CLOSE_FRAMING.test(scene))
|
|
563
|
-
return [
|
|
564
|
-
`a wide shot: ${hero} smaller in the frame, in the full place around it, so the setting tells the story, with the same words.`,
|
|
565
|
-
`a wide shot of ${hero} in its full setting.`
|
|
566
|
-
];
|
|
567
|
-
return [
|
|
568
|
-
`a close-up: ${hero} fills most of the frame, seen from close, and the setting shrinks to a soft hint of its colour and light, with the same words.`,
|
|
569
|
-
`a close-up of ${hero}, filling the frame.`
|
|
570
|
-
];
|
|
571
|
-
}
|
|
572
|
-
function compileStaticBrief(written, brandOverride, format, viewerOverride, profile, opts = {}) {
|
|
573
|
-
const spec = withRenderedLettering(written);
|
|
574
|
-
const job = opts.job ?? "generate";
|
|
575
|
-
const designedLines2 = spec.copy.flatMap((l, i) => l.lettering === "designed" ? [i + 1] : []);
|
|
576
|
-
const pics = pictures(spec, opts, job, profile, designedLines2.length > 0);
|
|
577
|
-
const base = {
|
|
578
|
-
spec,
|
|
579
|
-
brand: brandOverride ?? spec.brand,
|
|
580
|
-
viewer: viewerOverride ?? spec.viewer,
|
|
581
|
-
format,
|
|
582
|
-
profile,
|
|
583
|
-
opts,
|
|
584
|
-
job,
|
|
585
|
-
flowing: profile.brief.structure === "flowing_sentences",
|
|
586
|
-
pics,
|
|
587
|
-
designedLines: designedLines2,
|
|
588
|
-
level: 0,
|
|
589
|
-
clean: {
|
|
590
|
-
copy: spec.copy.map((l) => quotedCopy(l.text)),
|
|
591
|
-
logo: pics.some((p2) => p2.role === "logo"),
|
|
592
|
-
positive: profile.brief.constraints === "positive_last"
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
let prompt = assemble(base);
|
|
596
|
-
for (let level = 1; level <= MAX_TRIM && wordCount(prompt) > profile.brief.wordBudget; level++) {
|
|
597
|
-
prompt = assemble({ ...base, level });
|
|
598
|
-
}
|
|
599
|
-
const p = profile.brief.params;
|
|
600
|
-
return {
|
|
601
|
-
prompt,
|
|
602
|
-
references: base.pics.map(({ index, role, label, ref }) => ({ index, role, label, ref })),
|
|
603
|
-
params: {
|
|
604
|
-
n: job === "generate" ? p.n : 1,
|
|
605
|
-
...p.quality ? { quality: p.quality } : {},
|
|
606
|
-
...p.imageSize ? { image_size: p.imageSize } : {}
|
|
607
|
-
},
|
|
608
|
-
words: wordCount(prompt)
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
var MAX_TRIM = 16;
|
|
612
|
-
var LAST_RESORT_LEVEL = 16;
|
|
613
|
-
var PRODUCT_NOTE_WORDS = 10;
|
|
614
|
-
var EXTRAS_SHORT_LEVEL = 15;
|
|
615
|
-
var CAPTURE_LEVEL = 9;
|
|
616
|
-
var SHORT_DIRECTION_LEVEL = 10;
|
|
617
|
-
var SHORT_PICTURES_LEVEL = 14;
|
|
618
|
-
var NOTE_CAP = [...Array(10).fill(Number.POSITIVE_INFINITY), 12, 8, 4, 0];
|
|
619
|
-
var SCENE_CAP = [
|
|
620
|
-
Number.POSITIVE_INFINITY,
|
|
621
|
-
Number.POSITIVE_INFINITY,
|
|
622
|
-
Number.POSITIVE_INFINITY,
|
|
623
|
-
Number.POSITIVE_INFINITY,
|
|
624
|
-
80,
|
|
625
|
-
50,
|
|
626
|
-
35,
|
|
627
|
-
25,
|
|
628
|
-
15
|
|
629
|
-
];
|
|
630
|
-
function audienceText(viewer, level, k) {
|
|
631
|
-
if (!viewer || level >= 3) return "";
|
|
632
|
-
const warmth = {
|
|
633
|
-
cold: ", who are new to the brand",
|
|
634
|
-
warm: ", who already know the brand",
|
|
635
|
-
hot: ", who are ready to act"
|
|
636
|
-
};
|
|
637
|
-
const knows = viewer.knows ? ` (what they already know: ${plain(viewer.knows, k)})` : warmth[viewer.temperature ?? ""] ?? "";
|
|
638
|
-
return `, made for ${plain(viewer.who, k)}${knows}`;
|
|
639
|
-
}
|
|
640
|
-
function ideaText(spec, level, k) {
|
|
641
|
-
const message = spec.message;
|
|
642
|
-
if (!message || level >= 3) return "";
|
|
643
|
-
const m = message.toLowerCase();
|
|
644
|
-
const repeats = spec.copy.some((l) => m.includes(l.text.toLowerCase()) || l.text.toLowerCase().includes(m));
|
|
645
|
-
return repeats ? "" : ` Its one idea: ${sentence(plain(message, k))}`;
|
|
646
|
-
}
|
|
647
|
-
function deliverableSection(c) {
|
|
648
|
-
const noun = deliverableNoun(c.format, c.opts.placements);
|
|
649
|
-
const forBrand = c.brand?.name ? ` for ${plain(c.brand.name)}` : "";
|
|
650
|
-
if (c.job === "fix") {
|
|
651
|
-
const change = sentence(c.opts.instruction?.trim() || "correct what is wrong");
|
|
652
|
-
return c.flowing ? `Image 1 is the current version of this ad. ${capitalize(change)} Keep everything else in Image 1 exactly the same, preserving its picture, layout, colours, lettering and every word.` : `Edit: Image 1 is the current version of this ad. ${capitalize(change)} Keep everything else exactly as it is in Image 1: the picture, the layout, the colours, the lettering and every word.`;
|
|
653
|
-
}
|
|
654
|
-
if (c.job === "series") {
|
|
655
|
-
const change = sentence(c.opts.instruction?.trim() || "set the new words");
|
|
656
|
-
return c.flowing ? `Image 1 is a finished ad of this series. Make the next ad of the same series${forBrand}: ${change} Keep everything else in Image 1 exactly the same, preserving its picture, layout, background, shapes, colours, lettering style and logo, each word in the place and style of the one it replaces.` : `Edit: Image 1 is a finished ad of this series. Make the next ad of the same series${forBrand}. ${capitalize(change)} Keep everything else exactly as it is in Image 1: the picture, the layout, the background, the shapes, the colours, the lettering style and the logo, each new line in the place, size and style of the line it replaces.`;
|
|
657
|
-
}
|
|
658
|
-
if (c.job === "relay") {
|
|
659
|
-
const ui = "Keep any depicted app interface exact, with marketing overlays visibly separate from its native controls.";
|
|
660
|
-
return c.flowing ? `Image 1 is a finished ad. Adapt this same ad to a ${noun}${forBrand}: move and scale its existing elements, keeping its picture, background, colours, lettering and every word. Extend only its existing background where needed. ${ui}` : `Deliverable: the same ad as Image 1, re-laid as a finished ${noun}${forBrand}. Move and scale its existing elements; keep its picture, background, colours, lettering and every word. Extend only its existing background where needed. ${ui}`;
|
|
661
|
-
}
|
|
662
|
-
const lead = c.flowing ? "Create a finished" : "Deliverable: a finished";
|
|
663
|
-
return `${lead} ${noun}${forBrand}${audienceText(c.viewer, c.level, c.clean)}.${marketText(c)}${recognitionText(c)}${ideaText(c.spec, c.level, c.clean)}${conceptText(c)}${deviceText(c)}`;
|
|
664
|
-
}
|
|
665
|
-
function marketText(c) {
|
|
666
|
-
const market = c.viewer?.market;
|
|
667
|
-
if (!market) return "";
|
|
668
|
-
if (market.country === "global") {
|
|
669
|
-
return c.flowing ? " It is set in no one country: no flags, landmarks, money or shop signs of any country, and people who could live in any of them." : "\nSet in no one country: no flags, landmarks, money or shop signs of any country.";
|
|
670
|
-
}
|
|
671
|
-
const where = marketCountryName(market) ?? market.country;
|
|
672
|
-
const money = market.currency ? ` (${market.currency})` : "";
|
|
673
|
-
return c.flowing ? ` It is set in ${where}: its places, shops, money${money} and people are ${where}'s, in everyday details, never a flag or a landmark added to say where.` : `
|
|
674
|
-
Set in ${where}: its places, shops, money${money} and people are ${where}'s, in everyday details, never a flag or a landmark added to say where.`;
|
|
675
|
-
}
|
|
676
|
-
function recognitionText(c) {
|
|
677
|
-
const what = c.spec.recognition ? plain(c.spec.recognition, c.clean) : "";
|
|
678
|
-
if (!what) return "";
|
|
679
|
-
return c.flowing ? ` A stranger knows at a glance it is ${sentence(what)}` : ` A stranger knows at a glance it is ${sentence(what)}`;
|
|
680
|
-
}
|
|
681
|
-
var keptConcept = (spec) => spec.concept && spec.concept.verdict !== "reject" ? spec.concept : void 0;
|
|
682
|
-
function mustKeep(c) {
|
|
683
|
-
if (!conceptSummary(c.spec)) return [];
|
|
684
|
-
return (c.spec.concept?.load_bearing ?? []).map((l) => {
|
|
685
|
-
const ours = l.ours ? plain(l.ours, c.clean) : "";
|
|
686
|
-
return `${plain(l.element, c.clean)}${ours ? ` (${ours})` : ""}`;
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
var CARRIED = {
|
|
690
|
-
whole_ad: "Built on a proven ad as a whole: its disguise, its arrangement and its opening, in our words, brand and pictures.",
|
|
691
|
-
layout: "Built on a proven ad's layout: the same arrangement, with our words, brand and pictures.",
|
|
692
|
-
hook: "Built on a proven ad's hook: it opens on the same pattern, in our words; the look is ours.",
|
|
693
|
-
angle: "Built on a proven ad's angle: the same argument, in our words; the look is ours."
|
|
694
|
-
};
|
|
695
|
-
function conceptText(c) {
|
|
696
|
-
const kept = conceptSummary(c.spec) ? keptConcept(c.spec) : void 0;
|
|
697
|
-
if (!kept) return "";
|
|
698
|
-
const w = kept.why_it_wins;
|
|
699
|
-
const layers = [w.disguise, w.voice, w.trigger, w.attention, w.logic].flatMap(
|
|
700
|
-
(t) => t?.trim() ? [plain(t, c.clean)] : []
|
|
701
|
-
);
|
|
702
|
-
const continuing = c.job !== "generate";
|
|
703
|
-
const idea = layers.length > 0 && (continuing || c.level < LAST_RESORT_LEVEL) ? sentence(layers.join("; ")) : "";
|
|
704
|
-
const keep = mustKeep(c);
|
|
705
|
-
const keepLine = keep.length > 0 ? sentence(keep.join("; ")) : "";
|
|
706
|
-
const carry = kept.carry && (continuing || c.level < LAST_RESORT_LEVEL) ? CARRIED[kept.carry] : "";
|
|
707
|
-
const parts = [
|
|
708
|
-
carry,
|
|
709
|
-
idea ? `The idea: ${idea}` : "",
|
|
710
|
-
keepLine ? `${c.flowing ? "It must keep" : "Must keep:"} ${keepLine}` : ""
|
|
711
|
-
];
|
|
712
|
-
const separator = c.flowing ? " " : "\n";
|
|
713
|
-
return parts.flatMap((part) => part ? [`${separator}${part}`] : []).join("");
|
|
714
|
-
}
|
|
715
|
-
function deviceText(c) {
|
|
716
|
-
if (c.job !== "generate") {
|
|
717
|
-
const mechanisms = c.spec.mechanism.map(
|
|
718
|
-
(m) => [plain(m.device, c.clean), m.why_it_works ? plain(m.why_it_works, c.clean) : ""].filter(Boolean).join(": ")
|
|
719
|
-
);
|
|
720
|
-
return mechanisms.length > 0 ? `Keep the persuasion mechanism: ${sentence(mechanisms.join("; "))}` : "";
|
|
721
|
-
}
|
|
722
|
-
const device = c.spec.mechanism[0]?.device;
|
|
723
|
-
if (!device || c.level >= 1) return "";
|
|
724
|
-
const text = plain(device, c.clean);
|
|
725
|
-
if (!text) return "";
|
|
726
|
-
if (!c.flowing) return `
|
|
727
|
-
Idea: ${sentence(capitalize(text))}`;
|
|
728
|
-
const lower = new RegExp("^\\p{Lu}\\p{Ll}", "u").test(text) ? `${text[0]?.toLowerCase()}${text.slice(1)}` : text;
|
|
729
|
-
return ` The idea it is built on: ${sentence(lower)}`;
|
|
730
|
-
}
|
|
731
|
-
function pictureNoun(p) {
|
|
732
|
-
if (p.role === "identity" && p.spec) return personOrAnimal(p.spec);
|
|
733
|
-
return p.role === "product" ? "product" : "object";
|
|
734
|
-
}
|
|
735
|
-
function explainedBefore(p, pics) {
|
|
736
|
-
if (!p.spec) return void 0;
|
|
737
|
-
return pics.find((q) => q.index < p.index && q.spec && q.role === p.role && pictureNoun(q) === pictureNoun(p));
|
|
738
|
-
}
|
|
739
|
-
function picturesSection(c) {
|
|
740
|
-
if (c.pics.length === 0) return [];
|
|
741
|
-
const ctx = {
|
|
742
|
-
clean: c.clean,
|
|
743
|
-
job: c.job,
|
|
744
|
-
designedLines: c.designedLines,
|
|
745
|
-
allDesigned: c.designedLines.length === c.spec.copy.length,
|
|
746
|
-
plates: plateColours(c.spec, c.brand, c.clean),
|
|
747
|
-
thirdParty: c.spec.third_party_brands,
|
|
748
|
-
flowing: c.flowing,
|
|
749
|
-
short: c.level >= LAST_RESORT_LEVEL,
|
|
750
|
-
native: c.spec.brand_presence === "native",
|
|
751
|
-
...c.spec.logo_position ? { logoPosition: c.spec.logo_position } : {}
|
|
752
|
-
};
|
|
753
|
-
const lines = c.pics.map((p) => {
|
|
754
|
-
const label = p.label ? ` (${p.label})` : "";
|
|
755
|
-
const same = c.level >= SHORT_PICTURES_LEVEL ? explainedBefore(p, c.pics) : void 0;
|
|
756
|
-
const instruction = same ? `another ${pictureNoun(p)}${c.flowing ? ", under" : ";"} the same rules as Image ${same.index}.` : pictureInstruction(p, ctx);
|
|
757
|
-
return c.flowing ? `Image ${p.index}${label} is ${instruction}` : `Image ${p.index}${label}: ${instruction}`;
|
|
758
|
-
});
|
|
759
|
-
return [c.flowing ? lines.join("\n") : `References:
|
|
760
|
-
${lines.join("\n")}`];
|
|
761
|
-
}
|
|
762
|
-
var DESIGNED_LOOK = {
|
|
763
|
-
flowing: "It is a clean designed ad graphic with one dominant element and a clear reading order, and its type reads at a glance on a phone.",
|
|
764
|
-
labelled: "Look: a clean designed ad graphic: one dominant element, a clear reading order, crisp type that reads on a phone."
|
|
765
|
-
};
|
|
766
|
-
var ILLUSTRATION_LOOK = {
|
|
767
|
-
flowing: "It is an illustration with one dominant element and a clear reading order.",
|
|
768
|
-
labelled: "Look: an illustration: one dominant element, a clear reading order."
|
|
769
|
-
};
|
|
770
|
-
function photoLook(c) {
|
|
771
|
-
const r = isNativeCapture(c.spec) ? c.profile.brief.realism.native : c.profile.brief.realism.produced;
|
|
772
|
-
const capture = c.spec.scene.capture && c.level < CAPTURE_LEVEL ? plain(c.spec.scene.capture, c.clean) : "";
|
|
773
|
-
if (!c.flowing) return `Look: ${r.photo}${capture ? ` \u2014 ${capture}` : ""}; ${r.texture}.`;
|
|
774
|
-
const purpose = r.purpose ? `, ${r.purpose}` : "";
|
|
775
|
-
return `It is ${r.photo}${capture ? `, ${capture}` : ""}, with ${r.texture}${purpose}.`;
|
|
776
|
-
}
|
|
777
|
-
function animalsText(c) {
|
|
778
|
-
if (!showsAnimals(c.spec) || c.level >= EXTRAS_SHORT_LEVEL) return "";
|
|
779
|
-
return c.flowing ? " The animals look real, with real fur, natural light and a candid moment." : " Animals look real: real fur, natural light, a candid moment.";
|
|
780
|
-
}
|
|
781
|
-
function lookText(c) {
|
|
782
|
-
const people = `${showsPeople(c.spec) ? peopleText(c) : ""}${animalsText(c)}`;
|
|
783
|
-
if (c.spec.scene.look === "photo") return `${photoLook(c)}${people}`;
|
|
784
|
-
const look = c.spec.scene.look === "illustration" ? ILLUSTRATION_LOOK : DESIGNED_LOOK;
|
|
785
|
-
return `${c.flowing ? look.flowing : look.labelled}${people}`;
|
|
786
|
-
}
|
|
787
|
-
var PEOPLE = /\b(person|people|wom[ae]n|m[ae]n|girls?|boys?|child(?:ren)?|kids?|baby|parents?|mother|father|mum|mom|dad|family|couple|customers?|founder|presenter|technician|nurse|doctor|workers?|students?|friends|athlete|chef|faces?|selfie|portrait|plumbers?|electricians?|drivers?|owners?|managers?|employees?|engineers?|developers?|teachers?|mechanics?|carpenters?|tradesm[ae]n|advis[oe]rs?|agents?|riders?|couriers?)\b/i;
|
|
788
|
-
function showsPeople(spec) {
|
|
789
|
-
if (spec.scene.look === "illustration") return false;
|
|
790
|
-
if (spec.references.some((r) => r.role === "identity" && !isAnimal(r))) return true;
|
|
791
|
-
return spec.scene.look === "photo" && PEOPLE.test(withoutAbsences(`${spec.scene.description} ${spec.scene.layout ?? ""}`));
|
|
792
|
-
}
|
|
793
|
-
var CLAUSE_REST = String.raw`(?:(?!\b(?:and|but|while|with|as|where|whereas)\b)[^.;,])*`;
|
|
794
|
-
var ABSENCE = new RegExp(
|
|
795
|
-
String.raw`\b(?:no|without|nor|never any|not a single)\s+(?:\w+\s+){0,2}?(?:people|persons?|humans?|one|hands?|faces?|figures?)\b${CLAUSE_REST}|\b(?:nobody|no-one|no one)\b${CLAUSE_REST}`,
|
|
796
|
-
"gi"
|
|
797
|
-
);
|
|
798
|
-
var SOFTWARE_ROLE = /\b(?:password|file|task|project|package|window|device|usb|printer|graphics|gpu|ai|software|browser|download|display|audio|print)\s+(?:managers?|agents?|drivers?)\b|\b(?:managers?|agents?|drivers?)\s+(?:apps?|software|tools?|dashboards?|platforms?|programs?|updates?|panels?|screens?|extensions?|plugins?)\b/gi;
|
|
799
|
-
function withoutAbsences(text) {
|
|
800
|
-
return text.replace(/\b(?:first|second|third)[- ]person\b/gi, " ").replace(/\b\w+['’]s\b/g, " ").replace(SOFTWARE_ROLE, " ").replace(ABSENCE, " ");
|
|
801
|
-
}
|
|
802
|
-
function peopleText(c) {
|
|
803
|
-
const cue = c.profile.brief.realism.people;
|
|
804
|
-
if (!cue) return "";
|
|
805
|
-
const { button, lead } = rankedLines(c.spec.copy);
|
|
806
|
-
const target = button ?? lead;
|
|
807
|
-
const said = c.level >= EXTRAS_SHORT_LEVEL ? "People look real, faces well lit." : sentence(capitalize(cue));
|
|
808
|
-
const who = c.viewer?.who ? " Ordinary people the viewer's age: real faces with lines, everyday clothes." : "";
|
|
809
|
-
return ` ${said}${who}${target ? ` Their gaze or pose leads toward line ${target.n}.` : ""}`;
|
|
810
|
-
}
|
|
811
|
-
var looksRendered = (spec) => showsPeople(spec) || showsAnimals(spec) || showsScreen(spec);
|
|
812
|
-
var ANIMALS = /\b(dogs?|puppy|puppies|cats?|kittens?|pets?|horses?|birds?|rabbits?|animals?)\b/i;
|
|
813
|
-
function showsAnimals(spec) {
|
|
814
|
-
if (spec.scene.look === "illustration") return false;
|
|
815
|
-
if (spec.references.some((r) => r.role === "identity" && isAnimal(r))) return true;
|
|
816
|
-
return spec.scene.look === "photo" && ANIMALS.test(`${spec.scene.description} ${spec.scene.layout ?? ""}`);
|
|
817
|
-
}
|
|
818
|
-
var SCREEN = /\b(apps?|screens?|lock[- ]screen|dashboard|interface|mockup|notification|laptop|tablet|browser|website|chat bubbles?|ui)\b/i;
|
|
819
|
-
function showsScreen(spec) {
|
|
820
|
-
const ons = spec.copy.map((l) => l.on ?? "").join(" ");
|
|
821
|
-
return SCREEN.test(`${spec.scene.description} ${spec.scene.layout ?? ""} ${ons}`);
|
|
822
|
-
}
|
|
823
|
-
var SCREENS = {
|
|
824
|
-
labelled: "Screens: a real phone photographed, or a real screenshot: real app density, a real status bar, flat true-to-life colours; any words on them from the quoted lines.",
|
|
825
|
-
flowing: "Every phone or app screen looks like a real phone photographed or a real screenshot, with real app density, a real status bar and flat true-to-life colours, any words on it from the quoted lines."
|
|
826
|
-
};
|
|
827
|
-
var SCREENSHOT = /\b(screenshots?|screen ?grabs?|screens?)\b/i;
|
|
828
|
-
var screenshotOf = (pics) => pics.find((p) => p.spec && p.role !== "identity" && SCREENSHOT.test(p.spec.what));
|
|
829
|
-
function screensText(c) {
|
|
830
|
-
const laptop = /\b(laptops?|notebook computers?|macbooks?)\b/i.test(c.spec.scene.description);
|
|
831
|
-
const device = laptop ? "laptop" : "phone";
|
|
832
|
-
const shot = screenshotOf(c.pics);
|
|
833
|
-
if (shot) {
|
|
834
|
-
return c.flowing ? ` Every screen shows Image ${shot.index} exactly as it is, on a real ${device}, photographed.` : `
|
|
835
|
-
Screens: every screen shows Image ${shot.index} exactly as it is, on a real ${device}, photographed.`;
|
|
836
|
-
}
|
|
837
|
-
if (!showsScreen(c.spec)) return "";
|
|
838
|
-
if (c.level >= EXTRAS_SHORT_LEVEL)
|
|
839
|
-
return c.flowing ? ` Every ${device} or app screen looks like a real ${device} photographed or a real screenshot.` : `
|
|
840
|
-
Screens: a real ${device} photographed, or a real screenshot.`;
|
|
841
|
-
if (laptop) {
|
|
842
|
-
const screen = "a real laptop photographed, or a real screenshot; display, keyboard and hinge in consistent perspective; any words on it from the quoted lines.";
|
|
843
|
-
return c.flowing ? ` Every laptop screen looks like ${screen}` : `
|
|
844
|
-
Screens: ${screen}`;
|
|
845
|
-
}
|
|
846
|
-
return c.flowing ? ` ${SCREENS.flowing}` : `
|
|
847
|
-
${SCREENS.labelled}`;
|
|
848
|
-
}
|
|
849
|
-
function unwiredProductsText(c) {
|
|
850
|
-
const wired = new Set(c.pics.map((p) => p.label));
|
|
851
|
-
return c.spec.references.filter((r) => r.role === "product" && !wired.has(r.id)).map((r) => {
|
|
852
|
-
const what = plain(r.what, c.clean).replace(/[.!?]+$/, "");
|
|
853
|
-
const told = c.level >= LAST_RESORT_LEVEL ? clipNote(what.split(/[,;]/)[0] ?? what, PRODUCT_NOTE_WORDS) : what;
|
|
854
|
-
return ` The product, ${told}, is in the frame, whole and clearly visible.`;
|
|
855
|
-
}).join("");
|
|
856
|
-
}
|
|
857
|
-
function unwiredPeopleText(c) {
|
|
858
|
-
const wired = new Set(c.pics.map((p) => p.label));
|
|
859
|
-
return c.spec.references.filter((r) => r.role === "identity" && r.id.startsWith("PERSON_") && !wired.has(r.id)).map((r) => {
|
|
860
|
-
const what = plain(r.what, c.clean).replace(/[.!?]+$/, "");
|
|
861
|
-
const told = c.level >= LAST_RESORT_LEVEL ? clipNote(what.split(/[,;]/)[0] ?? what, PRODUCT_NOTE_WORDS) : what;
|
|
862
|
-
return ` The person, ${told}, is an invented, ordinary real person \u2014 real skin, unposed, never a model.`;
|
|
863
|
-
}).join("");
|
|
864
|
-
}
|
|
865
|
-
var BEFORE_AFTER = /\bbefore\b[^.]{0,40}\bafter\b|\bantes\b[^.]{0,30}\bdespu[eé]s\b|\bavant\b[^.]{0,30}\bapr[eè]s\b|\bvorher\b[^.]{0,30}\bnachher\b|\bprima\b[^.]{0,30}\bdopo\b/i;
|
|
866
|
-
function isBeforeAfter(spec) {
|
|
867
|
-
const told = [spec.scene.description, spec.scene.layout, spec.direction, ...spec.mechanism.map((m) => m.device)];
|
|
868
|
-
return BEFORE_AFTER.test(told.filter(Boolean).join(" \n "));
|
|
869
|
-
}
|
|
870
|
-
var BEFORE_AFTER_TEXT = " The two states it compares differ clearly at a glance, even as a thumbnail; only the result changes between them.";
|
|
871
|
-
function sceneSection(c) {
|
|
872
|
-
if (c.job !== "generate") return [];
|
|
873
|
-
const scene = c.spec.scene;
|
|
874
|
-
const products = `${unwiredProductsText(c)}${unwiredPeopleText(c)}${isBeforeAfter(c.spec) ? BEFORE_AFTER_TEXT : ""}`;
|
|
875
|
-
const told = normalized(products);
|
|
876
|
-
const own = (sentence(plain(scene.description, c.clean)).match(/[^.!?]+[.!?]*/g) ?? []).filter((s) => !products || !told.includes(normalized(s.replace(/^\s*in the frame:?/i, "")))).join("").trim();
|
|
877
|
-
const description = capitalize(clip(own || sentence(plain(scene.description, c.clean)), SCENE_CAP[c.level] ?? 15));
|
|
878
|
-
const mood = scene.mood && c.level < 1 ? ` The mood is ${sentence(plain(scene.mood, c.clean))}` : "";
|
|
879
|
-
const out = [
|
|
880
|
-
c.flowing ? `${description}${products}${mood} ${lookText(c)}${screensText(c)}${otherBrandsText(c)}` : `Scene: ${description}${products}${mood}
|
|
881
|
-
${lookText(c)}${screensText(c)}${otherBrandsText(c)}`
|
|
882
|
-
];
|
|
883
|
-
const layout = scene.layout && c.level < 2 ? unsaid(plain(scene.layout, c.clean), plain(scene.description, c.clean)) : "";
|
|
884
|
-
if (layout) out.push(c.flowing ? sentence(capitalize(layout)) : `Arrangement: ${sentence(layout)}`);
|
|
885
|
-
return out;
|
|
886
|
-
}
|
|
887
|
-
function textSection(c) {
|
|
888
|
-
const copy = c.spec.copy;
|
|
889
|
-
if (copy.length === 0)
|
|
890
|
-
return c.flowing ? "The picture carries the whole ad on its own." : "Text: none; the picture is the ad.";
|
|
891
|
-
const typePic = c.pics.find((p) => p.role === "type");
|
|
892
|
-
const face = typePic ? `the brand typeface (Image ${typePic.index})` : c.brand?.typeface ? plain(c.brand.typeface, c.clean) : "a clean, bold sans-serif";
|
|
893
|
-
const faceOnce = c.designedLines.length > 1;
|
|
894
|
-
const named = faceOnce && (!typePic || c.flowing);
|
|
895
|
-
const label = productLabel(c);
|
|
896
|
-
const header = c.flowing ? `The ad carries exactly these ${copy.length} lines of text, each once and spelled exactly as quoted${named ? `, the designed type set in ${face}` : ""}:` : `Text: exactly these ${copy.length} lines, each once, spelled exactly as quoted; they are the only words in the image${label}${named ? `; designed type in ${face}` : ""}:`;
|
|
897
|
-
return [
|
|
898
|
-
header,
|
|
899
|
-
...copy.map((l, i) => copyLineText(l, i + 1, faceOnce ? null : face, c.clean, c.level)),
|
|
900
|
-
...[hierarchyText(c)].filter(Boolean),
|
|
901
|
-
...textNotes(copy, c.profile)
|
|
902
|
-
].join("\n");
|
|
903
|
-
}
|
|
904
|
-
function productLabel(c) {
|
|
905
|
-
const shot = screenshotOf(c.pics);
|
|
906
|
-
if (shot) return ` besides the words on Image ${shot.index}'s screen`;
|
|
907
|
-
return c.pics.some((p) => p.role === "product") ? " besides the product's own label" : "";
|
|
908
|
-
}
|
|
909
|
-
function colourSection(c) {
|
|
910
|
-
if (c.job !== "generate") return [];
|
|
911
|
-
const uses = colourUses(c.spec, c.brand, c.clean);
|
|
912
|
-
if (uses.length === 0) return [];
|
|
913
|
-
const list = uses.map((u) => `${u.object} in ${u.name}${u.hex ? ` (${u.hex})` : ""}`).join("; ");
|
|
914
|
-
return [c.flowing ? sentence(capitalize(list)) : `Colour: ${sentence(list)}`];
|
|
915
|
-
}
|
|
916
|
-
function layoutSection(c) {
|
|
917
|
-
const items = keptInside(
|
|
918
|
-
c.pics.some((p) => p.role === "logo"),
|
|
919
|
-
c.spec.copy.some(isButton)
|
|
920
|
-
);
|
|
921
|
-
const safe = c.opts.zoned ? "" : c.opts.band?.margin ? marginText(c.opts.band, items, c.profile.brief.structure) : c.opts.band ? bandText(c.opts.band, items, c.flowing, staticSafeArea(c.format, c.opts.placements).placement !== "feed") : safeAreaText(c.format, c.opts.placements, items, c.profile.brief.structure);
|
|
922
|
-
const out = [safe, legibilityText(c), fillText(c)].filter(Boolean).join(" ");
|
|
923
|
-
return c.flowing ? out : `Layout: ${out}`;
|
|
924
|
-
}
|
|
925
|
-
function legibilityText(c) {
|
|
926
|
-
const logo = c.pics.some((p) => p.role === "logo");
|
|
927
|
-
if (c.spec.copy.length === 0) return logo ? "The logo sits on a ground it reads against." : "";
|
|
928
|
-
return logo ? "Every line and the logo sit on a ground they read against." : "Every line sits on a ground it reads against.";
|
|
929
|
-
}
|
|
930
|
-
var MINIMAL = /\b(minimal(?:ist)?|negative space|white space|empty space|sparse|airy)\b/i;
|
|
931
|
-
function fillText(c) {
|
|
932
|
-
if (c.job !== "generate" || c.level >= EXTRAS_SHORT_LEVEL || c.opts.variant === 1 || c.opts.variant === 2) return "";
|
|
933
|
-
const { scene, direction } = c.spec;
|
|
934
|
-
if (MINIMAL.test([scene.description, scene.mood, scene.layout, direction].join(" "))) return "";
|
|
935
|
-
return c.flowing ? "The subject and the words fill the frame with purpose." : "Fill the frame; no large empty areas.";
|
|
936
|
-
}
|
|
937
|
-
function directionSection(c) {
|
|
938
|
-
if (c.job !== "generate") return [];
|
|
939
|
-
const note = c.spec.direction && c.level < 2 ? sentence(plain(c.spec.direction, c.clean)) : "";
|
|
940
|
-
const variant = c.opts.variant ?? 0;
|
|
941
|
-
if (variant !== 1 && variant !== 2) return note ? [c.flowing ? capitalize(note) : `Direction: ${note}`] : [];
|
|
942
|
-
const direction = variantDirection(c);
|
|
943
|
-
return [c.flowing ? `This version is ${direction} ${note}`.trim() : `Direction: ${direction} ${note}`.trim()];
|
|
944
|
-
}
|
|
945
|
-
function positiveConstraints(c) {
|
|
946
|
-
const n = c.spec.copy.length;
|
|
947
|
-
const logo = c.pics.find((p) => p.role === "logo");
|
|
948
|
-
const others = thirdPartyNames(c.spec);
|
|
949
|
-
if (n === 0)
|
|
950
|
-
return `The only words in the finished image are none at all: it is picture only${logo ? `, with the one logo from Image ${logo.index}` : ""}.`;
|
|
951
|
-
const label = productLabel(c) ? `,${productLabel(c)}` : "";
|
|
952
|
-
const onlyLogo = !logo ? "" : others ? `, and the logos are the one in Image ${logo.index} and ${others}, the only brand marks anywhere, vehicles, packaging and clothing included` : `, and the one logo is the one in Image ${logo.index}, the only brand mark anywhere, vehicles, packaging and clothing included`;
|
|
953
|
-
return `The only words in the finished image are the ${n} quoted lines above, each exactly once; every other surface carries picture only${label}${onlyLogo}.`;
|
|
954
|
-
}
|
|
955
|
-
function constraintsSection(c) {
|
|
956
|
-
if (c.profile.brief.constraints === "positive_last") return positiveConstraints(c);
|
|
957
|
-
const n = c.spec.copy.length;
|
|
958
|
-
const logo = c.pics.find((p) => p.role === "logo");
|
|
959
|
-
const logos = [
|
|
960
|
-
...logo ? [`Image ${logo.index}`] : [],
|
|
961
|
-
...thirdPartyNames(c.spec) ? [thirdPartyNames(c.spec)] : []
|
|
962
|
-
];
|
|
963
|
-
const repeatedWordLimit = n > 0 && c.level >= LAST_RESORT_LEVEL;
|
|
964
|
-
const wordLimit = n > 0 ? `any words other than the ${n} quoted lines` : "any words or letters";
|
|
965
|
-
const avoid = [
|
|
966
|
-
// At the final trim level the Text section already states the complete word allow-list.
|
|
967
|
-
...repeatedWordLimit ? [] : [wordLimit],
|
|
968
|
-
// A Toyota badge appeared on a car-loan ad's SUV (BENCH.md § "Final"): the allow-list covers the scene's things too.
|
|
969
|
-
// The words and the identity's "show them once" are said above; brief v2 spends the words on the logo and the button.
|
|
970
|
-
`${logos.length > 0 ? `any logo, badge, icon, seal or sticker other than ${logos.join(" and ")}` : "any logo or wordmark, badge, icon, seal or sticker"}, on vehicles, packaging and clothing too`,
|
|
971
|
-
"watermarks",
|
|
972
|
-
...c.job === "generate" && isNativeCapture(c.spec) ? ["a staged, retouched or studio-lit look"] : [],
|
|
973
|
-
// The user found the people, a dog and a phone screen "very AI" (BENCH.md § "Fix round").
|
|
974
|
-
...c.job !== "generate" ? [] : showsPeople(c.spec) ? ["glossy, CGI or airbrushed skin; faces in shadow"] : looksRendered(c.spec) ? ["a glossy CGI look"] : []
|
|
975
|
-
];
|
|
976
|
-
return `Avoid: ${avoid.join("; ")}.`;
|
|
977
|
-
}
|
|
978
|
-
function assemble(c) {
|
|
979
|
-
const continuity = c.job !== "generate" ? (conceptText(c) || deviceText(c)).trim() : "";
|
|
980
|
-
return [
|
|
981
|
-
deliverableSection(c),
|
|
982
|
-
"Real-world anatomy, devices, scale, perspective and supports stay plausible. Scale proportionally; reflow text; preserve letter shapes.",
|
|
983
|
-
// A continuation needs the original idea as well as the previous picture. Keep this
|
|
984
|
-
// separate: the per-format adapter replaces the deliverable paragraph.
|
|
985
|
-
...continuity ? [continuity] : [],
|
|
986
|
-
...c.job === "fix" || c.job === "series" ? ["Keep any depicted app interface exact, with marketing overlays visibly separate from its native controls."] : [],
|
|
987
|
-
...picturesSection(c),
|
|
988
|
-
...sceneSection(c),
|
|
989
|
-
textSection(c),
|
|
990
|
-
...colourSection(c),
|
|
991
|
-
layoutSection(c),
|
|
992
|
-
...directionSection(c),
|
|
993
|
-
constraintsSection(c)
|
|
994
|
-
].join("\n\n");
|
|
995
|
-
}
|
|
996
|
-
function briefCopyLines(prompt) {
|
|
997
|
-
return [...prompt.matchAll(/^\d+\. "(.*)" — /gm)].map((m) => m[1] ?? "");
|
|
998
|
-
}
|
|
999
|
-
function judgeBrief(brief, opts = {}) {
|
|
1000
|
-
const whose = opts.brand ? ` for ${opts.brand}` : "";
|
|
1001
|
-
const forWhom = opts.viewer ? `, made for ${opts.viewer}` : "";
|
|
1002
|
-
const mechanism = opts.mechanisms && opts.mechanisms.length > 0 ? `; the ad still rests on ${opts.mechanisms.join("; ")}` : "";
|
|
1003
|
-
return [
|
|
1004
|
-
`You compare candidate versions of ONE static ad${whose}${forWhom}, all rendered from the BRIEF below. Compare the candidates with each other and pick the one that best delivers the brief; a candidate that fails an earlier check loses to one that passes it:`,
|
|
1005
|
-
"1. Words: every quoted line of the brief is printed exactly once, spelled exactly (accents and symbols included), and no other words appear.",
|
|
1006
|
-
"2. Picture: no AI artifacts on people, animals or products (hands, faces, mouths, warped objects), no seams or smears.",
|
|
1007
|
-
"3. Space: every word, the logo and the button sit where the brief's Layout keeps them; nothing is clipped or overlapping.",
|
|
1008
|
-
"4. One idea: one dominant element carries the idea and still reads at a glance shrunk to a phone-feed thumbnail.",
|
|
1009
|
-
`5. Brief: the scene, the lettering of each line, the colours and the pictures are the ones the brief asks for${mechanism}.`,
|
|
1010
|
-
`6. Brand: with any logo covered, the ad still reads as ${opts.brand ? `${opts.brand}'s` : "the brand's"}.`,
|
|
1011
|
-
'Compare every pair both ways before deciding. Begin `reasoning` with one line per criterion, exactly "<criterion>: Image X > Image Y > \u2026" (best first), then one sentence on why the winner won.',
|
|
1012
|
-
"",
|
|
1013
|
-
"BRIEF:",
|
|
1014
|
-
brief
|
|
1015
|
-
].join("\n");
|
|
1016
|
-
}
|
|
1017
|
-
var NUMBERED_COPY = /^(\d+\. )"(.*)"( — )/gm;
|
|
1018
|
-
var withoutCopy = (prompt) => prompt.replace(NUMBERED_COPY, '$1""$3');
|
|
1019
|
-
var briefWords = (prompt) => wordCount(withoutCopy(prompt));
|
|
1020
|
-
|
|
1021
|
-
// src/engine/scaffold/static-sizes.ts
|
|
1022
|
-
function ratioValue(ratio) {
|
|
1023
|
-
const [w, h] = ratio.split(":").map(Number);
|
|
1024
|
-
if (!w || !h || !Number.isFinite(w) || !Number.isFinite(h)) throw new Error(`not a W:H ratio: "${ratio}"`);
|
|
1025
|
-
return w / h;
|
|
1026
|
-
}
|
|
1027
|
-
function place(full, length, band, margin) {
|
|
1028
|
-
const lo = Math.max(0, Math.ceil(band.end - length * (1 - margin.end) - 1e-6));
|
|
1029
|
-
const hi = Math.min(full - length, Math.floor(band.start - length * margin.start + 1e-6));
|
|
1030
|
-
const centred = Math.round((band.start + band.end) / 2 - length * (margin.start + 1 - margin.end) / 2);
|
|
1031
|
-
if (lo > hi) return { at: Math.min(full - length, Math.max(0, centred)), fits: false };
|
|
1032
|
-
return { at: Math.min(hi, Math.max(lo, centred)), fits: true };
|
|
1033
|
-
}
|
|
1034
|
-
function bandCropGeometry(master, target, band, targetSafe = staticSafeArea(target)) {
|
|
1035
|
-
const r = ratioValue(target);
|
|
1036
|
-
let width = master.width;
|
|
1037
|
-
let height = Math.round(master.width / r);
|
|
1038
|
-
if (height > master.height) {
|
|
1039
|
-
height = master.height;
|
|
1040
|
-
width = Math.round(master.height * r);
|
|
1041
|
-
}
|
|
1042
|
-
const y = place(
|
|
1043
|
-
master.height,
|
|
1044
|
-
height,
|
|
1045
|
-
{ start: band.top * master.height, end: (1 - band.bottom) * master.height },
|
|
1046
|
-
{ start: targetSafe.top, end: targetSafe.bottom }
|
|
1047
|
-
);
|
|
1048
|
-
const x = place(
|
|
1049
|
-
master.width,
|
|
1050
|
-
width,
|
|
1051
|
-
{ start: band.left * master.width, end: (1 - band.right) * master.width },
|
|
1052
|
-
{ start: targetSafe.left, end: targetSafe.right }
|
|
1053
|
-
);
|
|
1054
|
-
return { x: x.at, y: y.at, width, height, fits: x.fits && y.fits };
|
|
1055
|
-
}
|
|
1056
|
-
var MASTER_SHAPES = ["relayout", "9x16_band", "4x5_adapt"];
|
|
1057
|
-
var DEFAULT_MASTER_SHAPE = "relayout";
|
|
1058
|
-
var META_PIXELS = {
|
|
1059
|
-
"9:16": { width: 1080, height: 1920 },
|
|
1060
|
-
"4:5": { width: 1080, height: 1350 },
|
|
1061
|
-
"1:1": { width: 1080, height: 1080 },
|
|
1062
|
-
"16:9": { width: 1920, height: 1080 },
|
|
1063
|
-
"1.91:1": { width: 1200, height: 628 }
|
|
1064
|
-
};
|
|
1065
|
-
function deliveredSize(format) {
|
|
1066
|
-
const known = META_PIXELS[format];
|
|
1067
|
-
if (known) return known;
|
|
1068
|
-
const r = ratioValue(format);
|
|
1069
|
-
return r <= 1 ? { width: 1080, height: Math.round(1080 / r) } : { width: Math.round(1080 * r), height: 1080 };
|
|
1070
|
-
}
|
|
1071
|
-
var ASPECT_ADAPT_FORMATS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
1072
|
-
function nearestAdaptFormat(format) {
|
|
1073
|
-
if (ASPECT_ADAPT_FORMATS.includes(format)) return format;
|
|
1074
|
-
const wanted = ratioValue(format);
|
|
1075
|
-
return [...ASPECT_ADAPT_FORMATS].sort(
|
|
1076
|
-
(a, b) => Math.abs(Math.log(ratioValue(a) / wanted)) - Math.abs(Math.log(ratioValue(b) / wanted))
|
|
1077
|
-
)[0];
|
|
1078
|
-
}
|
|
1079
|
-
function masterFormat(formats, shape) {
|
|
1080
|
-
const portrait = formats.filter((f) => ratioValue(f) <= 1);
|
|
1081
|
-
if (portrait.length === 0) return formats[0];
|
|
1082
|
-
if (shape === "4x5_adapt" && portrait.includes("4:5")) return "4:5";
|
|
1083
|
-
return [...portrait].sort((a, b) => ratioValue(a) - ratioValue(b))[0];
|
|
1084
|
-
}
|
|
1085
|
-
function masterContentBand(master, cuts, placements) {
|
|
1086
|
-
const own = staticSafeArea(master, placements);
|
|
1087
|
-
const band = { top: own.top, bottom: own.bottom, left: own.left, right: own.right };
|
|
1088
|
-
const m = ratioValue(master);
|
|
1089
|
-
for (const cut of cuts) {
|
|
1090
|
-
const safe = staticSafeArea(cut, placements);
|
|
1091
|
-
band.left = Math.max(band.left, safe.left);
|
|
1092
|
-
band.right = Math.max(band.right, safe.right);
|
|
1093
|
-
const share = m / ratioValue(cut);
|
|
1094
|
-
const room = share * (1 - safe.top - safe.bottom) - 2e-3;
|
|
1095
|
-
const height = 1 - band.top - band.bottom;
|
|
1096
|
-
if (height > room) {
|
|
1097
|
-
const shrink = (height - room) / 2;
|
|
1098
|
-
band.top += shrink;
|
|
1099
|
-
band.bottom += shrink;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
return band;
|
|
1103
|
-
}
|
|
1104
|
-
function planStaticFormats(genModel, formats, opts = {}) {
|
|
1105
|
-
if (formats.length === 0) throw new Error("a static ad needs at least one format");
|
|
1106
|
-
const masterShape = opts.masterShape ?? DEFAULT_MASTER_SHAPE;
|
|
1107
|
-
if (masterShape === "relayout") return planRelayout(genModel, formats, opts.placements);
|
|
1108
|
-
const format = masterFormat(formats, masterShape);
|
|
1109
|
-
const size = deliveredSize(format);
|
|
1110
|
-
const m = ratioValue(format);
|
|
1111
|
-
const cuts = formats.filter((f) => f !== format && ratioValue(f) >= m && ratioValue(f) <= Math.max(1, m));
|
|
1112
|
-
const band = masterContentBand(format, cuts, opts.placements);
|
|
1113
|
-
const sizes = formats.map((f) => {
|
|
1114
|
-
if (f === format) return { format: f, size, from: "master" };
|
|
1115
|
-
if (cuts.includes(f)) {
|
|
1116
|
-
const crop = bandCropGeometry(size, f, band, staticSafeArea(f, opts.placements));
|
|
1117
|
-
if (crop.fits) return { format: f, size: deliveredSize(f), from: "crop", crop };
|
|
1118
|
-
}
|
|
1119
|
-
return { format: f, size: deliveredSize(f), from: "adapt", adaptFormat: nearestAdaptFormat(f) };
|
|
1120
|
-
});
|
|
1121
|
-
return {
|
|
1122
|
-
masterShape,
|
|
1123
|
-
...opts.placements && opts.placements.length > 0 ? { placements: [...opts.placements] } : {},
|
|
1124
|
-
master: { format, renderRatio: nearestSupportedAspectRatio("image_generate", genModel, format), size },
|
|
1125
|
-
band,
|
|
1126
|
-
sizes
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
function leadFormat(formats) {
|
|
1130
|
-
for (const lead of ["4:5", "1:1"]) if (formats.includes(lead)) return lead;
|
|
1131
|
-
return formats.find((f) => ratioValue(f) <= 1) ?? formats[0];
|
|
1132
|
-
}
|
|
1133
|
-
function planRelayout(genModel, formats, placements) {
|
|
1134
|
-
const format = leadFormat(formats);
|
|
1135
|
-
const size = deliveredSize(format);
|
|
1136
|
-
const { top, bottom, left, right } = staticSafeArea(format, placements);
|
|
1137
|
-
return {
|
|
1138
|
-
masterShape: "relayout",
|
|
1139
|
-
...placements && placements.length > 0 ? { placements: [...placements] } : {},
|
|
1140
|
-
master: { format, renderRatio: nearestSupportedAspectRatio("image_generate", genModel, format), size },
|
|
1141
|
-
band: { top, bottom, left, right },
|
|
1142
|
-
sizes: formats.map(
|
|
1143
|
-
(f) => f === format ? { format: f, size, from: "master" } : { format: f, size: deliveredSize(f), from: "relayout" }
|
|
1144
|
-
)
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
var LOGO_WIDTH_SHARE = 0.15;
|
|
1148
|
-
var LOGO_ASPECT = 3;
|
|
1149
|
-
var MAX_TEXT_OVERLAP = 0.02;
|
|
1150
|
-
function overlapArea(a, b) {
|
|
1151
|
-
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
1152
|
-
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
1153
|
-
return w > 0 && h > 0 ? w * h : 0;
|
|
1154
|
-
}
|
|
1155
|
-
function logoSlot(frame, safe, opts = {}) {
|
|
1156
|
-
const area = safeRectPx(safe, frame.width, frame.height);
|
|
1157
|
-
const width = Math.round(frame.width * LOGO_WIDTH_SHARE);
|
|
1158
|
-
const height = Math.round(width / (opts.aspect ?? LOGO_ASPECT));
|
|
1159
|
-
const left = area.x;
|
|
1160
|
-
const right = area.x + area.width - width;
|
|
1161
|
-
const centre = Math.round(area.x + (area.width - width) / 2);
|
|
1162
|
-
const top = area.y;
|
|
1163
|
-
const bottom = area.y + area.height - height;
|
|
1164
|
-
const candidates = [
|
|
1165
|
-
[left, top],
|
|
1166
|
-
[right, top],
|
|
1167
|
-
[left, bottom],
|
|
1168
|
-
[right, bottom],
|
|
1169
|
-
[centre, top],
|
|
1170
|
-
[centre, bottom]
|
|
1171
|
-
].map(([x, y]) => ({ x, y, width, height }));
|
|
1172
|
-
const boxes = opts.textBoxes ?? [];
|
|
1173
|
-
const clear = (r) => boxes.reduce((sum, b) => sum + overlapArea(r, b), 0) <= MAX_TEXT_OVERLAP * r.width * r.height;
|
|
1174
|
-
const rect = candidates.find(clear) ?? candidates[0];
|
|
1175
|
-
const ground = opts.luminance?.(rect) ?? 1;
|
|
1176
|
-
return { rect, variant: ground >= 0.5 ? "dark" : "light" };
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// src/engine/scaffold/static-adapt.ts
|
|
1180
|
-
var STATIC_ADAPT_FORMAT_ROLE = "static_adapt_format";
|
|
1181
|
-
function adaptPlan(model, format, placements) {
|
|
1182
|
-
const renderRatio = nearestSupportedAspectRatio("image_generate", model, format);
|
|
1183
|
-
return {
|
|
1184
|
-
format,
|
|
1185
|
-
...placements && placements.length > 0 ? { placements: [...placements] } : {},
|
|
1186
|
-
renderRatio,
|
|
1187
|
-
size: deliveredSize(format),
|
|
1188
|
-
safe: safeAreaOnFrame(renderRatio, format, placements)
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
function safeAreaOnFrame(frame, format, placements) {
|
|
1192
|
-
const own = blockingArea(format, placements);
|
|
1193
|
-
const rendered = ratioValue(frame);
|
|
1194
|
-
const delivered = ratioValue(format);
|
|
1195
|
-
const keepH = rendered < delivered ? rendered / delivered : 1;
|
|
1196
|
-
const keepW = rendered > delivered ? delivered / rendered : 1;
|
|
1197
|
-
return {
|
|
1198
|
-
top: (1 - keepH) / 2 + own.top * keepH,
|
|
1199
|
-
bottom: (1 - keepH) / 2 + own.bottom * keepH,
|
|
1200
|
-
left: (1 - keepW) / 2 + own.left * keepW,
|
|
1201
|
-
right: (1 - keepW) / 2 + own.right * keepW
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
function trimmedRenderBand(plan) {
|
|
1205
|
-
const own = blockingArea(plan.format, plan.placements);
|
|
1206
|
-
const trimmed = ["top", "bottom", "left", "right"].some(
|
|
1207
|
-
(edge) => Math.abs(own[edge] - plan.safe[edge]) > 1e-9
|
|
1208
|
-
);
|
|
1209
|
-
return trimmed ? { area: plan.safe, cut: false, margin: true } : void 0;
|
|
1210
|
-
}
|
|
1211
|
-
function fillArgs(size) {
|
|
1212
|
-
const px = `${size.width}x${size.height}`;
|
|
1213
|
-
return [
|
|
1214
|
-
"{{in.image}}",
|
|
1215
|
-
"-filter",
|
|
1216
|
-
"Lanczos",
|
|
1217
|
-
"-resize",
|
|
1218
|
-
`${px}^`,
|
|
1219
|
-
"-gravity",
|
|
1220
|
-
"center",
|
|
1221
|
-
"-extent",
|
|
1222
|
-
px,
|
|
1223
|
-
"+repage",
|
|
1224
|
-
"{{out.image}}"
|
|
1225
|
-
];
|
|
1226
|
-
}
|
|
1227
|
-
function adaptNoun(plan) {
|
|
1228
|
-
const r = ratioValue(plan.format);
|
|
1229
|
-
if (plan.format === "9:16") {
|
|
1230
|
-
const safe = staticSafeArea(plan.format, plan.placements);
|
|
1231
|
-
return safe.placement === "stories" ? "full-screen vertical ad for Stories" : safe.placement === "vertical" ? `full-screen vertical ad for ${safe.surface}` : "full-screen vertical ad for Stories and Reels";
|
|
1232
|
-
}
|
|
1233
|
-
if (r === 1) return "square feed ad";
|
|
1234
|
-
return r < 1 ? "vertical feed ad" : "landscape ad";
|
|
1235
|
-
}
|
|
1236
|
-
function adaptDeliverable(plan, spec, flowing, ground, short) {
|
|
1237
|
-
const brand = spec.brand?.name;
|
|
1238
|
-
const noun = `${adaptNoun(plan)}${brand ? ` for ${brand}` : ""}`;
|
|
1239
|
-
const background = "Extend only the background at Image 1's outer edges; textures stay on their existing surfaces.";
|
|
1240
|
-
if (short && !flowing) {
|
|
1241
|
-
const keep = [
|
|
1242
|
-
...showsPeople2(spec) ? ["each person the same face and clothes"] : [],
|
|
1243
|
-
...isBeforeAfter(spec) ? ["the two states as different"] : []
|
|
1244
|
-
];
|
|
1245
|
-
return `Deliverable: adapt Image 1 to a ${noun}. Move and scale existing elements first; reflow the contents for this frame, keeping its words, people, product, logo, colours and typeface. ${background} Keep any depicted app interface exact, with marketing overlays visibly separate from its native controls. Every person, product and line appears whole and once${keep.length > 0 ? `, ${keep.join(", ")}` : ""}; add nothing new; never Image 1 shrunk onto a background.`;
|
|
1246
|
-
}
|
|
1247
|
-
const cast = [
|
|
1248
|
-
SAME_CAST,
|
|
1249
|
-
...showsPeople2(spec) ? [SAME_PERSON] : [],
|
|
1250
|
-
...isBeforeAfter(spec) ? [SAME_CHANGE] : [],
|
|
1251
|
-
...ground ? [GROUND_ONLY] : []
|
|
1252
|
-
].join(" ");
|
|
1253
|
-
const continuity = `Move and scale existing elements first; reflow the contents for this frame instead of pasting a smaller copy on a backdrop. Keep the visual hierarchy. ${background} Keep any depicted app interface exact, with marketing overlays visibly separate from its native controls.`;
|
|
1254
|
-
return flowing ? `Image 1 is a finished ad. Adapt this same ad to a ${noun}, keeping its message, words, people, product, logo, colours and typeface. ${continuity} ${cast}` : `Deliverable: the ad in Image 1, re-laid out as a finished ${noun}, keeping its message, words, people, product, logo, colours and typeface; never Image 1 shrunk onto a background. ${continuity} ${cast}`;
|
|
1255
|
-
}
|
|
1256
|
-
var SAME_CAST = "Every person, product and line appears whole and once, the same individuals as in Image 1; add no person, animal, product or object that Image 1 does not show.";
|
|
1257
|
-
var SAME_PERSON = "Each person is the same person as in Image 1: same face, same hair, same clothes.";
|
|
1258
|
-
var SAME_CHANGE = "The two states it compares stay as clearly different as in Image 1.";
|
|
1259
|
-
var showsPeople2 = (spec) => spec.references.some((r) => r.id.startsWith("PERSON_")) || spec.scene.look !== "illustration" && PEOPLE.test(`${spec.scene.description} ${spec.scene.layout ?? ""}`);
|
|
1260
|
-
var GROUND_ONLY = "Image 1 shows no people and no product, so the room this frame adds carries only Image 1's own background and the brand's colours already present there, never a new scene.";
|
|
1261
|
-
function wordsOnly(spec) {
|
|
1262
|
-
return spec.references.length === 0 && spec.scene.look !== "photo" && !PEOPLE.test(`${spec.scene.description} ${spec.scene.layout ?? ""}`);
|
|
1263
|
-
}
|
|
1264
|
-
var isTall = (plan) => ratioValue(plan.format) < 0.7;
|
|
1265
|
-
var LIST_ROLES = /^(list_item|bullet|row|item)$/i;
|
|
1266
|
-
var TABLE = /\b(tables?|columns?|rows?|checklists?|lists?)\b/i;
|
|
1267
|
-
var hasRows = (spec) => spec.copy.some((l) => LIST_ROLES.test(l.role ?? "")) || TABLE.test(`${spec.scene.description} ${spec.scene.layout ?? ""}`);
|
|
1268
|
-
function scaleText(plan, spec, flowing, zoned, short) {
|
|
1269
|
-
if (short && !flowing) {
|
|
1270
|
-
return isTall(plan) && zoned ? "Scale: larger than in Image 1, the headline across most of the band's width, the words spread through the band." : "Scale: larger than in Image 1, the largest line across most of the width.";
|
|
1271
|
-
}
|
|
1272
|
-
if (!(isTall(plan) && zoned)) {
|
|
1273
|
-
return flowing ? "Every line is set at the size this frame needs rather than at its size in Image 1: the largest line spans most of the width between the margins, and the words fill their space." : "Scale: set every line for this frame, not at its size in Image 1: the largest line spans most of the width between the margins; no small text in a large empty area.";
|
|
1274
|
-
}
|
|
1275
|
-
const rows = hasRows(spec) ? flowing ? " Rows, columns and lists are restacked for the tall frame, one item per line, each larger." : " Rows, columns and lists are restacked for the tall frame, one item per line, each larger, rather than shrunk." : "";
|
|
1276
|
-
return flowing ? `The words are set for this frame, larger than in Image 1: the headline large enough to fill most of the band's width, and every line, the logo and the button spread through the band's height, inside its edges.${rows}` : `Scale: larger than in Image 1: the headline set large enough to fill most of the band's width; every line, the logo and the button spread through the band's height, inside its edges, never a small block in its middle.${rows}`;
|
|
1277
|
-
}
|
|
1278
|
-
var COVERED_HEADROOM = { top: 0.06, bottom: 0.08, side: 0.02 };
|
|
1279
|
-
var NO_HEADROOM = { top: 0, bottom: 0, side: 0 };
|
|
1280
|
-
var pct = (share) => `${Math.round(share * 100)}%`;
|
|
1281
|
-
function zonesText(plan, flowing, short) {
|
|
1282
|
-
const covered = staticSafeArea(plan.format, plan.placements).placement !== "feed";
|
|
1283
|
-
const room = isTall(plan) && covered ? COVERED_HEADROOM : NO_HEADROOM;
|
|
1284
|
-
const top = plan.safe.top + room.top;
|
|
1285
|
-
const bottom = plan.safe.bottom + room.bottom;
|
|
1286
|
-
const side = Math.max(plan.safe.left, plan.safe.right) + room.side;
|
|
1287
|
-
const band = `from ${pct(top)} to ${pct(1 - bottom)} of the height, at least ${pct(side)} in from each side`;
|
|
1288
|
-
if (short && !flowing) {
|
|
1289
|
-
const around = "Image 1's existing background treatment, including any intentional flat colour";
|
|
1290
|
-
return `Zones: every word, the logo and the button sit ${band}; around them, ${around}.`;
|
|
1291
|
-
}
|
|
1292
|
-
if (!(isTall(plan) && covered)) {
|
|
1293
|
-
return flowing ? `The logo, every line and the button sit ${band}; around them the frame shows only the ad's own picture.` : `Zones: the logo, every line and the button sit ${band}; around them, only the ad's own picture.`;
|
|
1294
|
-
}
|
|
1295
|
-
const picture = "Image 1's existing background treatment, including any intentional flat colour";
|
|
1296
|
-
return flowing ? `The words' band runs ${band}, holding the logo, every line and the button. Above and below it the placement's interface covers the frame: continue only ${picture}.` : `Zones: the app's buttons and captions cover the top and the bottom of this frame, hiding any word there. The logo, every line and the button sit in one band, ${band}: the headline starts below the top fifth, and the button ends directly under the last line, never at the bottom edge where a feed ad puts it. Above and below it, where the placement's interface covers the frame, continue only ${picture}.`;
|
|
1297
|
-
}
|
|
1298
|
-
function adaptBrief(spec, profile, opts) {
|
|
1299
|
-
const { plan, images } = opts;
|
|
1300
|
-
const flowing = profile.brief.structure === "flowing_sentences";
|
|
1301
|
-
const { logo_position: _logoPosition, ...rest } = spec;
|
|
1302
|
-
const unplaced = { ...rest, copy: spec.copy.map(({ position: _position, ...line }) => line) };
|
|
1303
|
-
const band = trimmedRenderBand(plan);
|
|
1304
|
-
const zoned = opts.zones !== false;
|
|
1305
|
-
const ground = wordsOnly(spec);
|
|
1306
|
-
const compile = (wordBudget) => compileStaticBrief(
|
|
1307
|
-
unplaced,
|
|
1308
|
-
void 0,
|
|
1309
|
-
plan.format,
|
|
1310
|
-
void 0,
|
|
1311
|
-
{ ...profile, brief: { ...profile.brief, wordBudget } },
|
|
1312
|
-
{
|
|
1313
|
-
job: "relay",
|
|
1314
|
-
images: {
|
|
1315
|
-
base: images.base,
|
|
1316
|
-
...images.logo ? { logo: images.logo } : {},
|
|
1317
|
-
...images.type ? { type: images.type } : {},
|
|
1318
|
-
refs: images.refs ?? {}
|
|
1319
|
-
},
|
|
1320
|
-
...plan.placements ? { placements: plan.placements } : {},
|
|
1321
|
-
...band ? { band } : {},
|
|
1322
|
-
...zoned ? { zoned } : {}
|
|
1323
|
-
}
|
|
1324
|
-
);
|
|
1325
|
-
const relaid = (compiled2, short = false) => {
|
|
1326
|
-
const paragraphs = compiled2.prompt.split("\n\n");
|
|
1327
|
-
paragraphs[0] = adaptDeliverable(plan, spec, flowing, ground && !zoned, short);
|
|
1328
|
-
const first = spec.copy[0] ? quotedCopy(spec.copy[0].text) : void 0;
|
|
1329
|
-
const textAt = first ? paragraphs.findIndex((p) => p.includes(first)) : -1;
|
|
1330
|
-
if (textAt >= 0) {
|
|
1331
|
-
const added = [...zoned ? [zonesText(plan, flowing, short)] : [], scaleText(plan, spec, flowing, zoned, short)];
|
|
1332
|
-
paragraphs.splice(textAt + 1, 0, ...added);
|
|
1333
|
-
} else if (zoned) {
|
|
1334
|
-
const layoutAt = paragraphs.findIndex((p) => p.startsWith("Layout:"));
|
|
1335
|
-
paragraphs.splice(layoutAt > 0 ? layoutAt : 1, 0, zonesText(plan, flowing, short));
|
|
1336
|
-
}
|
|
1337
|
-
return paragraphs.join("\n\n");
|
|
1338
|
-
};
|
|
1339
|
-
const budget = profile.brief.wordBudget;
|
|
1340
|
-
let compiled = compile(budget);
|
|
1341
|
-
let prompt = relaid(compiled);
|
|
1342
|
-
for (let pass = 0; pass < 3 && briefWords(prompt) > budget; pass++) {
|
|
1343
|
-
compiled = compile(compiled.words - (briefWords(prompt) - budget));
|
|
1344
|
-
prompt = relaid(compiled);
|
|
1345
|
-
}
|
|
1346
|
-
if (briefWords(prompt) > budget) prompt = relaid(compiled, true);
|
|
1347
|
-
return { ...compiled, prompt, words: wordsIn(prompt) };
|
|
1348
|
-
}
|
|
1349
|
-
var wordsIn = (text) => text.split(/\s+/).filter(Boolean).length;
|
|
1350
|
-
|
|
1351
|
-
// src/engine/lib/prompt-profiles.ts
|
|
1352
|
-
var VEO_PERSON_GENERATION = "allow_adult";
|
|
1353
|
-
var VEO_NEGATIVE_PROMPT = "subtitles, captions, on-screen text, watermark, logo, warped face, distorted hands, extra fingers, low quality";
|
|
1354
|
-
var KLING_NEGATIVE_PROMPT = "warped face, distorted hands, extra fingers, morphing, flicker, on-screen text, watermark, low quality";
|
|
1355
|
-
var KLING_CFG_SCALE = 0.7;
|
|
1356
|
-
var SPEAKS_PROSE = (line, accent) => `The person speaks to camera; lip-sync follows the dialogue verbatim, with delivery and emotion carried in the wording itself (no bracketed cues).${accent ? ` The speech is in ${accent}.` : ""} Dialogue: "${line}"`;
|
|
1357
|
-
var SEEDANCE_PROFILE = {
|
|
1358
|
-
id: "seedance",
|
|
1359
|
-
dialogueDirective: SPEAKS_PROSE,
|
|
1360
|
-
extraDirectives: [],
|
|
1361
|
-
// Seedance has no negative_prompt field. Phrase the anti-artifact intent as the POSITIVE
|
|
1362
|
-
// state we want (crisp hands, one stable face, smooth coherent motion, locked identity) —
|
|
1363
|
-
// an "Avoid: warped fingers, morphing faces" list tends to plant those very artifacts.
|
|
1364
|
-
stabilityDirectives: [
|
|
1365
|
-
"hands and fingers crisp and anatomically correct",
|
|
1366
|
-
"one consistent face and identity across every frame",
|
|
1367
|
-
"smooth, temporally coherent motion that holds steady frame to frame"
|
|
1368
|
-
],
|
|
1369
|
-
keyframeInstruction: "Preserve the composition and colors of the first frame; change only the motion described.",
|
|
1370
|
-
// ~120 words is the Seedance quality sweet spot: past it the model starts dropping beats and
|
|
1371
|
-
// the attention budget dilutes. The validator warns above this; composite scenes that stack
|
|
1372
|
-
// many brief `parts` are the usual offenders.
|
|
1373
|
-
wordBudget: 120,
|
|
1374
|
-
paramDefaults: {}
|
|
1375
|
-
};
|
|
1376
|
-
var VEO_PROFILE = {
|
|
1377
|
-
id: "veo",
|
|
1378
|
-
dialogueDirective: SPEAKS_PROSE,
|
|
1379
|
-
// Veo's known failure mode: quoted dialogue triggers burned-in subtitles. Belt
|
|
1380
|
-
// (inline) and braces (the negative_prompt param, below) — the param is the real lever.
|
|
1381
|
-
extraDirectives: ["No subtitles, no captions, no on-screen text of any kind."],
|
|
1382
|
-
stabilityDirectives: [],
|
|
1383
|
-
// Veo takes a negative_prompt PARAM instead (paramDefaults).
|
|
1384
|
-
keyframeInstruction: "Describe the transition and camera move between the frames; the frames fix the content.",
|
|
1385
|
-
wordBudget: 200,
|
|
1386
|
-
paramDefaults: { person_generation: VEO_PERSON_GENERATION, negative_prompt: VEO_NEGATIVE_PROMPT }
|
|
1387
|
-
};
|
|
1388
|
-
var KLING_PROFILE = {
|
|
1389
|
-
id: "kling",
|
|
1390
|
-
dialogueDirective: SPEAKS_PROSE,
|
|
1391
|
-
// Belt (inline) and braces (the negative_prompt param) — the param is the real lever.
|
|
1392
|
-
extraDirectives: ["Single, deliberate camera move \u2014 no whip pans or rapid cuts within the clip."],
|
|
1393
|
-
stabilityDirectives: [],
|
|
1394
|
-
// Kling takes a negative_prompt PARAM instead (paramDefaults).
|
|
1395
|
-
keyframeInstruction: "Preserve the composition and colors of the first frame; animate the motion described.",
|
|
1396
|
-
wordBudget: 200,
|
|
1397
|
-
paramDefaults: { negative_prompt: KLING_NEGATIVE_PROMPT, cfg_scale: KLING_CFG_SCALE }
|
|
1398
|
-
};
|
|
1399
|
-
var OMNI_PROFILE = {
|
|
1400
|
-
id: "omni",
|
|
1401
|
-
dialogueDirective: SPEAKS_PROSE,
|
|
1402
|
-
// Omni renders speech and ambience natively in the same pass, so the prompt
|
|
1403
|
-
// carries the audio direction; there is no separate audio toggle to set.
|
|
1404
|
-
extraDirectives: ["No subtitles, no captions, no on-screen text of any kind."],
|
|
1405
|
-
// Omni exposes NO negative_prompt param — the API rejects the field outright —
|
|
1406
|
-
// so the anti-artifact intent has to be carried positively, Seedance-style,
|
|
1407
|
-
// rather than as an "Avoid:" list Veo would have taken as a param.
|
|
1408
|
-
stabilityDirectives: [
|
|
1409
|
-
"hands and fingers crisp and anatomically correct",
|
|
1410
|
-
"one consistent face and identity across every frame",
|
|
1411
|
-
"smooth, temporally coherent motion that holds steady frame to frame"
|
|
1412
|
-
],
|
|
1413
|
-
keyframeInstruction: "Preserve the composition and colors of the first frame; change only the motion described.",
|
|
1414
|
-
// Omni reasons over a 1M-token context and rewards a fuller brief than the
|
|
1415
|
-
// diffusion-style models, which dilute past their sweet spot.
|
|
1416
|
-
wordBudget: 200,
|
|
1417
|
-
// Nothing to set. Every knob the other profiles use (negative_prompt,
|
|
1418
|
-
// person_generation, cfg_scale) is a pre-dispatch 400 on this model.
|
|
1419
|
-
paramDefaults: {}
|
|
1420
|
-
};
|
|
1421
|
-
var CLIP_PROFILES = {
|
|
1422
|
-
"bytedance/seedance-2.0": SEEDANCE_PROFILE,
|
|
1423
|
-
"bytedance/seedance-2.5": SEEDANCE_PROFILE,
|
|
1424
|
-
"google/gemini-omni-1.1-flash": OMNI_PROFILE,
|
|
1425
|
-
"google/gemini-omni-flash": OMNI_PROFILE,
|
|
1426
|
-
"google/veo-3.1": VEO_PROFILE,
|
|
1427
|
-
"google/veo-3.1-fast": VEO_PROFILE,
|
|
1428
|
-
"google/veo-3.1-lite": VEO_PROFILE,
|
|
1429
|
-
"kwaivgi/kling-v3.0-pro": KLING_PROFILE,
|
|
1430
|
-
"x-ai/grok-imagine-video": null,
|
|
1431
|
-
"x-ai/grok-imagine-video-1.5": null,
|
|
1432
|
-
"alibaba/wan-2.7": null,
|
|
1433
|
-
"alibaba/wan-3.0": null,
|
|
1434
|
-
"runway/gen-4.5": null,
|
|
1435
|
-
"alibaba/happyhorse-1.1": null
|
|
1436
|
-
};
|
|
1437
|
-
function clipProfileFor(modelId) {
|
|
1438
|
-
return CLIP_PROFILES[modelId] ?? void 0;
|
|
1439
|
-
}
|
|
1440
|
-
function clipParamRecipe(model, profile, intent) {
|
|
1441
|
-
const out = {};
|
|
1442
|
-
if (intent === "hero" && videoModelRenders(model, "1080p")) {
|
|
1443
|
-
out.resolution = "1080p";
|
|
1444
|
-
}
|
|
1445
|
-
if (intent === "hook" && profile.id === "kling") {
|
|
1446
|
-
out.cfg_scale = 0.85;
|
|
1447
|
-
}
|
|
1448
|
-
return out;
|
|
1449
|
-
}
|
|
1450
|
-
function videoModelRenders(model, resolution) {
|
|
1451
|
-
const schema = MODEL_REGISTRY.video_generate[model]?.params.resolution;
|
|
1452
|
-
return schema?.kind === "string" && (schema.enum ?? []).includes(resolution);
|
|
1453
|
-
}
|
|
1454
|
-
function nativeDialogueOf(prompt) {
|
|
1455
|
-
if (typeof prompt !== "string") return void 0;
|
|
1456
|
-
const m = prompt.match(/Dialogue: "(.*)"/);
|
|
1457
|
-
return m?.[1]?.trim() || void 0;
|
|
1458
|
-
}
|
|
1459
|
-
var REFERENCE_ORDER = ["base", "identity", "product", "prop", "logo", "type", "layout"];
|
|
1460
|
-
var GPT_IMAGE_BRIEF = {
|
|
1461
|
-
// "organize the prompt as scene, subject, details, and constraints, using labeled sections";
|
|
1462
|
-
// "written like a creative brief" (openai §1, S1/S5 §4.6).
|
|
1463
|
-
structure: "labelled_sections",
|
|
1464
|
-
// Bench E1: this structure vs the pre-rebuild wrapper + blueprint JSON on 181 paired ads:
|
|
1465
|
-
// shippable 8.3% vs 4.4%, near-clone of the source 7% vs 17%, 0 lint contradictions vs 11 codes on
|
|
1466
|
-
// every legacy prompt, 15% cheaper (BENCH.md#E1; pairwise pending).
|
|
1467
|
-
// Official examples run 40-150 words; the synthesis caps briefs at 150-400
|
|
1468
|
-
// (image-prompting-research-2026.md §6, openai §1). Ours carried 1,615 (prompt-audit.md §1).
|
|
1469
|
-
wordBudget: 400,
|
|
1470
|
-
referenceOrder: REFERENCE_ORDER,
|
|
1471
|
-
// Quote each line, say how many times it appears, allow-list it, spell symbols out, keep
|
|
1472
|
-
// accents: openai §2 ("Render the tagline exactly once", "Include ONLY this card text").
|
|
1473
|
-
text: { numberedQuotes: true, nameSymbols: true, keepAccents: true },
|
|
1474
|
-
// Official prompts close on a Constraints block or a "No…" sentence (openai §7).
|
|
1475
|
-
constraints: "avoid_list_last",
|
|
1476
|
-
realism: {
|
|
1477
|
-
// "include the word 'photorealistic' directly in the prompt" (openai §5, S5).
|
|
1478
|
-
// "real texture (pores, wrinkles, fabric wear, imperfections)"; camera specs are "cues,
|
|
1479
|
-
// not a guarantee" (openai §5), so capture is described in words, never figures.
|
|
1480
|
-
native: {
|
|
1481
|
-
photo: "photorealistic: a real, unedited phone photo",
|
|
1482
|
-
texture: "real texture and wear on every surface, natural light, light sensor noise, unposed and unretouched",
|
|
1483
|
-
purpose: ""
|
|
1484
|
-
},
|
|
1485
|
-
// Brief v2: a studio or hero shot keeps its own light; the phone cues made them candid and busy
|
|
1486
|
-
// (BENCH.md § E1, the judge's remaining reasons), and the user found the old prompt's more polished.
|
|
1487
|
-
produced: {
|
|
1488
|
-
photo: "photorealistic: a real photograph",
|
|
1489
|
-
texture: "real textures, true-to-life light and colour",
|
|
1490
|
-
purpose: ""
|
|
1491
|
-
},
|
|
1492
|
-
// The user: people look "too AI", faces too dark; wants natural, real-looking people (BENCH.md § "E1 v2"),
|
|
1493
|
-
// and again on the final sets, "more natural please" (§ "Fix round"); and real people's photos are
|
|
1494
|
-
// taken on a phone, not a professional camera (AV2, scripts/creative-evals/avatar-realism.mjs).
|
|
1495
|
-
people: "candid people shot on a phone: real skin, natural light, relaxed expressions"
|
|
1496
|
-
},
|
|
1497
|
-
// n 1: the variants are one call per direction, not one n=3 call. Same cost (n is billed per
|
|
1498
|
-
// image, E0) and returns 3/3 either way, but one n=3 call gave >=2 distinct options in 25% of
|
|
1499
|
-
// ads vs 39% for three calls (n=99/100; n=2 + 1 call: 31% vs 36% on 39 shared ads)
|
|
1500
|
-
// (.context/static-review/BENCH.md#E3).
|
|
1501
|
-
// quality max: the deterministic checks tie (shippable 9-12% on every step, n=100), but on the
|
|
1502
|
-
// E4 gallery (30 rated pairs) the user's win rate is max 69%, xhigh 54%, high 47%, Flare 34%,
|
|
1503
|
-
// and the judge's on the same pairs 73%, 65%, 39%, 31%. Max costs $0.166 per image vs $0.085
|
|
1504
|
-
// for xhigh and 80 s vs 44 s (BENCH.md#E4, § "E1 v2"); a round renders twice in a row (the
|
|
1505
|
-
// options, then each size), so three ads took most of an hour. Oct 2026 the user chose xhigh.
|
|
1506
|
-
params: { n: 1, quality: "xhigh", imageSize: "2K" }
|
|
1507
|
-
};
|
|
1508
|
-
var GEMINI_IMAGE_BRIEF = {
|
|
1509
|
-
// "Use Natural Language & Full Sentences… as if you were briefing a human artist", and
|
|
1510
|
-
// give the why (prompting-google-gemini-image.md §2). Bench E1 on Gemini 3 Pro Image, 182 paired
|
|
1511
|
-
// ads: shippable 6.0% vs 7.1% for the legacy prompt (within the -2pp gate), near-clone 6% vs 15%
|
|
1512
|
-
// (BENCH.md#E1; pairwise pending).
|
|
1513
|
-
structure: "flowing_sentences",
|
|
1514
|
-
// Google gives no limit; the synthesis budget holds (research §6).
|
|
1515
|
-
wordBudget: 400,
|
|
1516
|
-
referenceOrder: REFERENCE_ORDER,
|
|
1517
|
-
// Quote each string and name the font per line (gemini §3, Cloud guide per-line pattern).
|
|
1518
|
-
text: { numberedQuotes: true, nameSymbols: true, keepAccents: true },
|
|
1519
|
-
// "Use positive framing … 'empty street' instead of 'no cars'" (gemini §2).
|
|
1520
|
-
constraints: "positive_last",
|
|
1521
|
-
realism: {
|
|
1522
|
-
// Capture-device language, never "hyperrealistic"/"8k" tag soup (gemini §6).
|
|
1523
|
-
native: {
|
|
1524
|
-
photo: "a candid photo taken on a phone",
|
|
1525
|
-
texture: "real texture and wear on every surface, natural light, a little sensor grain, unposed and unretouched",
|
|
1526
|
-
// Pro reasons from purpose: say why it is lo-fi (gemini §2, §6).
|
|
1527
|
-
purpose: "because a polished picture reads as an ad and this one has to read as a real post"
|
|
1528
|
-
},
|
|
1529
|
-
produced: { photo: "a real photograph", texture: "real textures and true-to-life light and colour", purpose: "" },
|
|
1530
|
-
people: "the people look real and candid, shot on a phone, with natural skin, natural light and relaxed expressions"
|
|
1531
|
-
},
|
|
1532
|
-
// No `n` on Gemini image (gemini §0.4); Pro 2K costs the same as 1K and small text blurs
|
|
1533
|
-
// at 1K (gemini §0.3).
|
|
1534
|
-
params: { n: 1, imageSize: "2K" }
|
|
1535
|
-
};
|
|
1536
|
-
var RECRAFT_BRIEF = {
|
|
1537
|
-
structure: "labelled_sections",
|
|
1538
|
-
wordBudget: 400,
|
|
1539
|
-
referenceOrder: REFERENCE_ORDER,
|
|
1540
|
-
text: { numberedQuotes: true, nameSymbols: true, keepAccents: true },
|
|
1541
|
-
constraints: "positive_last",
|
|
1542
|
-
realism: {
|
|
1543
|
-
native: { photo: "a flat vector illustration", texture: "clean shapes and even fills", purpose: "" },
|
|
1544
|
-
produced: { photo: "a flat vector illustration", texture: "clean shapes and even fills", purpose: "" },
|
|
1545
|
-
people: ""
|
|
1546
|
-
},
|
|
1547
|
-
params: { n: 1 }
|
|
1548
|
-
};
|
|
1549
|
-
var GPT_IMAGE_PROFILE = {
|
|
1550
|
-
id: "gpt-image",
|
|
1551
|
-
constraintPlacement: "last",
|
|
1552
|
-
photorealCue: true,
|
|
1553
|
-
// OpenRouter forwards `quality`; gpt-image already processes inputs at high fidelity
|
|
1554
|
-
// automatically, so we deliberately do NOT send `input_fidelity`.
|
|
1555
|
-
//
|
|
1556
|
-
// No `quality` here any more: it is the one default that depends on WHICH gpt-image is
|
|
1557
|
-
// pinned. 2.5 added `xhigh` and `max` above `high`, and the older entries would refuse
|
|
1558
|
-
// them at validate — so callers ask for the step they want through
|
|
1559
|
-
// `bestSupportedQuality` and get the best the chosen model actually has.
|
|
1560
|
-
paramDefaults: {},
|
|
1561
|
-
// gpt-image-2 has no `xhigh` (openai §0.2): the brief asks for `high`, its top step.
|
|
1562
|
-
brief: { ...GPT_IMAGE_BRIEF, params: { n: 1, quality: "high" } }
|
|
1563
|
-
};
|
|
1564
|
-
var GPT_IMAGE_25_PROFILE = {
|
|
1565
|
-
...GPT_IMAGE_PROFILE,
|
|
1566
|
-
brief: GPT_IMAGE_BRIEF
|
|
1567
|
-
};
|
|
1568
|
-
var GEMINI_IMAGE_PROFILE = {
|
|
1569
|
-
id: "gemini",
|
|
1570
|
-
constraintPlacement: "inline",
|
|
1571
|
-
photorealCue: true,
|
|
1572
|
-
// NO `quality`. OpenRouter accepts the field on a Gemini image call and returns
|
|
1573
|
-
// 200 — but its per-endpoint schema does not list it and the cost is identical
|
|
1574
|
-
// with and without, so Google drops it: an inert knob. Worse, the registry
|
|
1575
|
-
// (correctly) does not declare it for Gemini, so emitting it fails `baker canvas
|
|
1576
|
-
// validate` with `unknown_param` before the call is ever made.
|
|
1577
|
-
paramDefaults: {},
|
|
1578
|
-
brief: GEMINI_IMAGE_BRIEF
|
|
1579
|
-
};
|
|
1580
|
-
var RECRAFT_IMAGE_PROFILE = {
|
|
1581
|
-
id: "recraft",
|
|
1582
|
-
constraintPlacement: "inline",
|
|
1583
|
-
photorealCue: false,
|
|
1584
|
-
paramDefaults: {},
|
|
1585
|
-
brief: RECRAFT_BRIEF
|
|
1586
|
-
};
|
|
1587
|
-
var IMAGE_PROFILES = {
|
|
1588
|
-
"openai/gpt-image-2.5-sunburst": GPT_IMAGE_25_PROFILE,
|
|
1589
|
-
"openai/gpt-image-2": GPT_IMAGE_PROFILE,
|
|
1590
|
-
"openai/gpt-5.4-image-2": GPT_IMAGE_PROFILE,
|
|
1591
|
-
"google/gemini-3.1-flash-image": GEMINI_IMAGE_PROFILE,
|
|
1592
|
-
"google/gemini-3.1-flash-lite-image": GEMINI_IMAGE_PROFILE,
|
|
1593
|
-
"google/gemini-3-pro-image": GEMINI_IMAGE_PROFILE,
|
|
1594
|
-
"google/gemini-3.1-flash-image-preview": GEMINI_IMAGE_PROFILE,
|
|
1595
|
-
"google/gemini-3-pro-image-preview": GEMINI_IMAGE_PROFILE,
|
|
1596
|
-
"recraft/recraft-v4.1-pro-vector": RECRAFT_IMAGE_PROFILE
|
|
1597
|
-
};
|
|
1598
|
-
function imageProfileFor(modelId) {
|
|
1599
|
-
return IMAGE_PROFILES[modelId] ?? void 0;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
// src/engine/scaffold/lib/image-params.ts
|
|
1603
|
-
function imageRenderParams(model, want) {
|
|
1604
|
-
const quality = bestSupportedQuality("image_generate", model, want.quality);
|
|
1605
|
-
return {
|
|
1606
|
-
...supportsParam("image_generate", model, "image_size") ? { image_size: nearestSupportedImageSize("image_generate", model, want.size) } : {},
|
|
1607
|
-
...imageProfileFor(model)?.paramDefaults ?? {},
|
|
1608
|
-
...quality ? { quality } : {}
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// src/engine/scaffold/static-topology.ts
|
|
1613
|
-
var STATIC_VARIANTS_ROLE = "static_variants";
|
|
1614
|
-
var STATIC_VARIANT_CALL_ROLE = "static_variant_call";
|
|
1615
|
-
var VARIANT_PREFILTER_ROLE = "variant_prefilter";
|
|
1616
|
-
var STATIC_PICK_ROLE = "pick";
|
|
1617
|
-
var STATIC_MASTER_ROLE = "static_master";
|
|
1618
|
-
var STATIC_SIZE_ROLE = "static_size";
|
|
1619
|
-
var STATIC_ADAPT_ROLE = "static_adapt";
|
|
1620
|
-
var STATIC_SAFE_FIT_ROLE = "static_safe_fit";
|
|
1621
|
-
var STATIC_PLACE_ROLE = "static_place";
|
|
1622
|
-
var LOGO_COMPOSITE_ROLE = "logo_composite";
|
|
1623
|
-
var SERIES_LEAD_ROLE = "series_lead";
|
|
1624
|
-
var STATIC_FIX_ROLE = "static_fix";
|
|
1625
|
-
var LOGO_COMPOSITE_DEFAULT = false;
|
|
1626
|
-
var ADAPT_IMAGE_SIZE = "2K";
|
|
1627
|
-
function nodesOf(canvas) {
|
|
1628
|
-
const nodes = canvas?.nodes;
|
|
1629
|
-
return Array.isArray(nodes) ? nodes : [];
|
|
1630
|
-
}
|
|
1631
|
-
function nodesWithRole(canvas, role) {
|
|
1632
|
-
return nodesOf(canvas).filter(
|
|
1633
|
-
(n) => n?.tags?.role === role && typeof n.id === "string" && typeof n.type === "string"
|
|
1634
|
-
);
|
|
1635
|
-
}
|
|
1636
|
-
function rolesById(canvas) {
|
|
1637
|
-
const out = {};
|
|
1638
|
-
for (const n of nodesOf(canvas)) {
|
|
1639
|
-
if (typeof n?.id === "string" && typeof n.tags?.role === "string") out[n.id] = n.tags.role;
|
|
1640
|
-
}
|
|
1641
|
-
return out;
|
|
1642
|
-
}
|
|
1643
|
-
function relaidPictureRole(canvas) {
|
|
1644
|
-
const roles = rolesById(canvas);
|
|
1645
|
-
const final = nodesOf(canvas).find((n) => n.id === "final" && n.type === "collect");
|
|
1646
|
-
const labels = final?.params?.labels;
|
|
1647
|
-
const images = final?.inputs?.images;
|
|
1648
|
-
if (!Array.isArray(labels) || !Array.isArray(images)) return {};
|
|
1649
|
-
const at = images.findIndex((ref) => {
|
|
1650
|
-
const from = typeof ref === "string" ? /^\$ref:([^.]+)\./.exec(ref)?.[1] : void 0;
|
|
1651
|
-
return from !== void 0 && roles[from] === STATIC_MASTER_ROLE;
|
|
1652
|
-
});
|
|
1653
|
-
const format = labels[at];
|
|
1654
|
-
return typeof format === "string" ? { [`last_${formatSlug(format)}`]: STATIC_MASTER_ROLE } : {};
|
|
1655
|
-
}
|
|
1656
|
-
function formatSlug(format) {
|
|
1657
|
-
return format.replace(":", "x").replace(".", "_");
|
|
1658
|
-
}
|
|
1659
|
-
var EXTENT = /^(\d+)x(\d+)$/;
|
|
1660
|
-
function staticMasterFormat(canvas) {
|
|
1661
|
-
const master = nodesWithRole(canvas, STATIC_MASTER_ROLE)[0];
|
|
1662
|
-
const args = master?.params?.args;
|
|
1663
|
-
if (!Array.isArray(args)) return null;
|
|
1664
|
-
const at = args.indexOf("-extent");
|
|
1665
|
-
const match = typeof args[at + 1] === "string" ? args[at + 1].match(EXTENT) : null;
|
|
1666
|
-
if (at < 0 || !match) return null;
|
|
1667
|
-
return formatOfSize({ width: Number(match[1]), height: Number(match[2]) });
|
|
1668
|
-
}
|
|
1669
|
-
var KNOWN_FORMATS = ["9:16", "4:5", "1:1", "16:9", "1.91:1", ...ASPECT_ADAPT_FORMATS];
|
|
1670
|
-
function formatOfSize(size) {
|
|
1671
|
-
for (const format of KNOWN_FORMATS) {
|
|
1672
|
-
const d = deliveredSize(format);
|
|
1673
|
-
if (d.width === size.width && d.height === size.height) return format;
|
|
1674
|
-
}
|
|
1675
|
-
const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
|
|
1676
|
-
const g = gcd(size.width, size.height);
|
|
1677
|
-
return `${size.width / g}:${size.height / g}`;
|
|
1678
|
-
}
|
|
1679
|
-
function safeAreaOf(adaptFormat, delivered) {
|
|
1680
|
-
const { top, bottom, left, right } = staticSafeArea(adaptFormat);
|
|
1681
|
-
return delivered.map((f) => safeAreaOnFrame(adaptFormat, f)).reduce(
|
|
1682
|
-
(a, b) => ({
|
|
1683
|
-
top: Math.max(a.top, b.top),
|
|
1684
|
-
bottom: Math.max(a.bottom, b.bottom),
|
|
1685
|
-
left: Math.max(a.left, b.left),
|
|
1686
|
-
right: Math.max(a.right, b.right)
|
|
1687
|
-
}),
|
|
1688
|
-
{ top, bottom, left, right }
|
|
1689
|
-
);
|
|
1690
|
-
}
|
|
1691
|
-
function magick(id, role, image, args, extra = {}) {
|
|
1692
|
-
return {
|
|
1693
|
-
id,
|
|
1694
|
-
type: "imagemagick",
|
|
1695
|
-
inputs: { image, ...extra },
|
|
1696
|
-
params: { args, outputs: { image: { kind: "image", ext: "png" } } },
|
|
1697
|
-
tags: { role }
|
|
1698
|
-
};
|
|
1699
|
-
}
|
|
1700
|
-
function variantNodes(g) {
|
|
1701
|
-
const render = (id, extra, tags) => ({
|
|
1702
|
-
id,
|
|
1703
|
-
type: "image_generate",
|
|
1704
|
-
...g.reference.length > 0 ? { inputs: { reference: [...g.reference] } } : {},
|
|
1705
|
-
params: { model: g.genModel, aspect_ratio: g.plan.master.renderRatio, ...g.genParams, ...extra, prompt: g.prompt },
|
|
1706
|
-
tags
|
|
1707
|
-
});
|
|
1708
|
-
if (g.takes === 1) return [render("gen", {}, { role: STATIC_VARIANTS_ROLE })];
|
|
1709
|
-
const calls = Array.from(
|
|
1710
|
-
{ length: g.takes },
|
|
1711
|
-
(_, i) => render(`gen_${i + 1}`, {}, { role: STATIC_VARIANT_CALL_ROLE, region: i })
|
|
1712
|
-
);
|
|
1713
|
-
return [
|
|
1714
|
-
...calls,
|
|
1715
|
-
{
|
|
1716
|
-
id: "gen",
|
|
1717
|
-
type: "collect",
|
|
1718
|
-
inputs: { images: calls.map((c) => `$ref:${c.id}.images#0`) },
|
|
1719
|
-
tags: { role: STATIC_VARIANTS_ROLE }
|
|
1720
|
-
}
|
|
1721
|
-
];
|
|
1722
|
-
}
|
|
1723
|
-
function staticRenderNodes(g) {
|
|
1724
|
-
const { plan } = g;
|
|
1725
|
-
const nodes = [
|
|
1726
|
-
...variantNodes(g),
|
|
1727
|
-
{
|
|
1728
|
-
id: "prefilter",
|
|
1729
|
-
type: "static_prefilter",
|
|
1730
|
-
inputs: { images: "$ref:gen.images", spec: g.specRef },
|
|
1731
|
-
params: {
|
|
1732
|
-
formats: plan.sizes.map((s) => s.format),
|
|
1733
|
-
master_shape: plan.masterShape,
|
|
1734
|
-
...plan.placements ? { placements: [...plan.placements] } : {},
|
|
1735
|
-
// The judge breaks a real tie only; one variant never asks it.
|
|
1736
|
-
...g.takes > 1 ? { judge_model: g.judgeModel, judge_prompt: g.prompt } : {}
|
|
1737
|
-
},
|
|
1738
|
-
tags: { role: VARIANT_PREFILTER_ROLE }
|
|
1739
|
-
},
|
|
1740
|
-
magick("master", STATIC_MASTER_ROLE, "$ref:prefilter.image", fillArgs(plan.master.size))
|
|
1741
|
-
];
|
|
1742
|
-
const adapted = plan.sizes.filter((s) => s.from === "adapt");
|
|
1743
|
-
const adaptFormats = [...new Set(adapted.map((s) => s.adaptFormat ?? s.format))];
|
|
1744
|
-
if (adaptFormats.length > 0) {
|
|
1745
|
-
nodes.push({
|
|
1746
|
-
id: "adapt",
|
|
1747
|
-
type: "image_aspect_adapt",
|
|
1748
|
-
inputs: { source: "$ref:master.image" },
|
|
1749
|
-
params: {
|
|
1750
|
-
model: g.adaptModel,
|
|
1751
|
-
formats: adaptFormats,
|
|
1752
|
-
image_size: ADAPT_IMAGE_SIZE,
|
|
1753
|
-
safe_areas: Object.fromEntries(
|
|
1754
|
-
adaptFormats.map((f) => [
|
|
1755
|
-
f,
|
|
1756
|
-
safeAreaOf(
|
|
1757
|
-
f,
|
|
1758
|
-
adapted.filter((s) => (s.adaptFormat ?? s.format) === f).map((s) => s.format)
|
|
1759
|
-
)
|
|
1760
|
-
])
|
|
1761
|
-
)
|
|
1762
|
-
},
|
|
1763
|
-
tags: { role: STATIC_ADAPT_ROLE }
|
|
1764
|
-
});
|
|
1765
|
-
}
|
|
1766
|
-
nodes.push(...relayoutNodes(g));
|
|
1767
|
-
const refs = plan.sizes.map((s) => {
|
|
1768
|
-
const slug = formatSlug(s.format);
|
|
1769
|
-
const made = sizeNodes(s, plan, adaptFormats);
|
|
1770
|
-
nodes.push(...made);
|
|
1771
|
-
const last = made[made.length - 1];
|
|
1772
|
-
const ref = last ? `$ref:${last.id}.image` : "$ref:master.image";
|
|
1773
|
-
if (!g.compositeLogo) return ref;
|
|
1774
|
-
const slot = logoSlot(s.size, staticSafeArea(s.format)).rect;
|
|
1775
|
-
nodes.push(
|
|
1776
|
-
magick(
|
|
1777
|
-
`logo_${slug}`,
|
|
1778
|
-
LOGO_COMPOSITE_ROLE,
|
|
1779
|
-
ref,
|
|
1780
|
-
[
|
|
1781
|
-
"{{in.image}}",
|
|
1782
|
-
"(",
|
|
1783
|
-
"{{in.logo}}",
|
|
1784
|
-
"-resize",
|
|
1785
|
-
`${slot.width}x${slot.height}`,
|
|
1786
|
-
")",
|
|
1787
|
-
"-gravity",
|
|
1788
|
-
"NorthWest",
|
|
1789
|
-
"-geometry",
|
|
1790
|
-
`+${slot.x}+${slot.y}`,
|
|
1791
|
-
"-composite",
|
|
1792
|
-
"{{out.image}}"
|
|
1793
|
-
],
|
|
1794
|
-
{ logo: g.compositeLogo }
|
|
1795
|
-
)
|
|
1796
|
-
);
|
|
1797
|
-
return `$ref:logo_${slug}.image`;
|
|
1798
|
-
});
|
|
1799
|
-
nodes.push({
|
|
1800
|
-
id: "final",
|
|
1801
|
-
type: "collect",
|
|
1802
|
-
inputs: { images: refs },
|
|
1803
|
-
params: { labels: plan.sizes.map((s) => s.format) }
|
|
1804
|
-
});
|
|
1805
|
-
return nodes;
|
|
1806
|
-
}
|
|
1807
|
-
function sizeNodes(s, plan, adaptFormats) {
|
|
1808
|
-
const size = sizeNode(s, adaptFormats);
|
|
1809
|
-
const fit = safeFitNode(s.format, size ? `$ref:${size.id}.image` : "$ref:master.image", plan.placements);
|
|
1810
|
-
return [...size ? [size] : [], ...fit ? [fit] : []];
|
|
1811
|
-
}
|
|
1812
|
-
function safeFitNode(format, image, placements) {
|
|
1813
|
-
if (staticSafeArea(format, placements).placement === "feed") return null;
|
|
1814
|
-
return {
|
|
1815
|
-
id: `safe_${formatSlug(format)}`,
|
|
1816
|
-
type: "static_safe_fit",
|
|
1817
|
-
inputs: { image },
|
|
1818
|
-
params: { format, ...placements ? { placements: [...placements] } : {} },
|
|
1819
|
-
tags: { role: STATIC_SAFE_FIT_ROLE }
|
|
1820
|
-
};
|
|
1821
|
-
}
|
|
1822
|
-
function sizeNode(s, adaptFormats) {
|
|
1823
|
-
const id = `size_${formatSlug(s.format)}`;
|
|
1824
|
-
if (s.from === "crop" && s.crop) {
|
|
1825
|
-
const c = s.crop;
|
|
1826
|
-
const exact = c.width === s.size.width && c.height === s.size.height;
|
|
1827
|
-
return magick(id, STATIC_SIZE_ROLE, "$ref:master.image", [
|
|
1828
|
-
"{{in.image}}",
|
|
1829
|
-
"-crop",
|
|
1830
|
-
`${c.width}x${c.height}+${c.x}+${c.y}`,
|
|
1831
|
-
"+repage",
|
|
1832
|
-
...exact ? [] : ["-filter", "Lanczos", "-resize", `${s.size.width}x${s.size.height}!`],
|
|
1833
|
-
"{{out.image}}"
|
|
1834
|
-
]);
|
|
1835
|
-
}
|
|
1836
|
-
if (s.from === "relayout") {
|
|
1837
|
-
return magick(id, STATIC_SIZE_ROLE, `$ref:adapt_${formatSlug(s.format)}.images#0`, fillArgs(s.size));
|
|
1838
|
-
}
|
|
1839
|
-
if (s.from !== "adapt") return null;
|
|
1840
|
-
const index = adaptFormats.indexOf(s.adaptFormat ?? s.format);
|
|
1841
|
-
return magick(id, STATIC_SIZE_ROLE, `$ref:adapt.images#${index}`, fillArgs(s.size));
|
|
1842
|
-
}
|
|
1843
|
-
var RELAYOUT_QUALITY = "xhigh";
|
|
1844
|
-
function relayoutNodes(g) {
|
|
1845
|
-
return g.plan.sizes.filter((s) => s.from === "relayout").map((s) => relayoutNode(s, g));
|
|
1846
|
-
}
|
|
1847
|
-
function relayoutNode(s, g) {
|
|
1848
|
-
return {
|
|
1849
|
-
id: `adapt_${formatSlug(s.format)}`,
|
|
1850
|
-
type: "image_generate",
|
|
1851
|
-
inputs: { reference: ["$ref:master.image", ...g.logoRef ? [g.logoRef] : []] },
|
|
1852
|
-
params: {
|
|
1853
|
-
model: g.relayoutModel,
|
|
1854
|
-
aspect_ratio: adaptPlan(g.relayoutModel, s.format, g.plan.placements).renderRatio,
|
|
1855
|
-
...imageRenderParams(g.relayoutModel, { size: ADAPT_IMAGE_SIZE, quality: RELAYOUT_QUALITY }),
|
|
1856
|
-
prompt: g.prompt
|
|
1857
|
-
},
|
|
1858
|
-
tags: { role: STATIC_ADAPT_FORMAT_ROLE }
|
|
1859
|
-
};
|
|
1860
|
-
}
|
|
1861
|
-
var RENDER_GRAPH_IDS = ["gen", "prefilter", "pick", "master", "adapt", "final", "fix", "previous"];
|
|
1862
|
-
var LEGACY_RENDER_ID = /^(?:ad(?:_[a-c])?|ad_\d+x\d+|fit_\d+x\d+)$/;
|
|
1863
|
-
function variantRenders(canvas) {
|
|
1864
|
-
const fanned = nodesWithRole(canvas, STATIC_VARIANT_CALL_ROLE).map((n) => n.id);
|
|
1865
|
-
if (fanned.length > 0) return fanned;
|
|
1866
|
-
return nodesWithRole(canvas, STATIC_VARIANTS_ROLE).filter((n) => n.type === "image_generate").map((n) => n.id);
|
|
1867
|
-
}
|
|
1868
|
-
function regenerateTargets(canvas, requested) {
|
|
1869
|
-
const known = new Set(nodesOf(canvas).flatMap((n) => typeof n?.id === "string" ? [n.id] : []));
|
|
1870
|
-
const gathered = new Set(
|
|
1871
|
-
nodesWithRole(canvas, STATIC_VARIANTS_ROLE).filter((n) => n.type === "collect").map((n) => n.id)
|
|
1872
|
-
);
|
|
1873
|
-
const ids = [];
|
|
1874
|
-
const unknown = [];
|
|
1875
|
-
for (const name of requested) {
|
|
1876
|
-
const variants = name === STATIC_VARIANTS_ROLE || gathered.has(name) || !known.has(name) && LEGACY_RENDER_ID.test(name);
|
|
1877
|
-
const found = variants ? variantRenders(canvas) : known.has(name) ? [name] : nodesWithRole(canvas, name).map((n) => n.id);
|
|
1878
|
-
if (found.length === 0) unknown.push(name);
|
|
1879
|
-
for (const id of found) if (!ids.includes(id)) ids.push(id);
|
|
1880
|
-
}
|
|
1881
|
-
return { ids, unknown };
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
// src/engine/client/backend-client.ts
|
|
1885
|
-
function isAsyncJob(res) {
|
|
1886
|
-
return typeof res.job_id === "string";
|
|
1887
|
-
}
|
|
1888
|
-
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1889
|
-
function failedJobError(error) {
|
|
1890
|
-
const message = error.message ?? "the node failed on the server without saying why";
|
|
1891
|
-
return new BackendHttpError(
|
|
1892
|
-
classifyBackendError(error.status ?? 502, { ...error, retryable: error.retryable ?? false }, message)
|
|
1893
|
-
);
|
|
1894
|
-
}
|
|
1895
|
-
function pollInterval(attempt) {
|
|
1896
|
-
return attempt < 15 ? 1e3 : 3e3;
|
|
1897
|
-
}
|
|
1898
|
-
var JOB_POLL_MAX_MS = 20 * 60 * 1e3;
|
|
1899
|
-
var BackendClient = class {
|
|
1900
|
-
http;
|
|
1901
|
-
constructor(opts) {
|
|
1902
|
-
this.http = new HttpClient(opts);
|
|
1903
|
-
}
|
|
1904
|
-
async exec(req, signal) {
|
|
1905
|
-
const res = await this.http.postJson("/api/canvas/nodes/exec", req, signal);
|
|
1906
|
-
if (isAsyncJob(res)) {
|
|
1907
|
-
return await this.pollJob(res.job_id, signal);
|
|
1908
|
-
}
|
|
1909
|
-
return res;
|
|
1910
|
-
}
|
|
1911
|
-
async pollJob(jobId, signal) {
|
|
1912
|
-
const deadline = Date.now() + JOB_POLL_MAX_MS;
|
|
1913
|
-
const path4 = `/api/canvas/jobs/${encodeURIComponent(jobId)}`;
|
|
1914
|
-
for (let attempt = 0; ; attempt++) {
|
|
1915
|
-
if (signal?.aborted) {
|
|
1916
|
-
throw new BackendHttpError({ kind: "network", cause: signal.reason ?? new Error("aborted") });
|
|
1917
|
-
}
|
|
1918
|
-
const job = await this.http.getJson(path4, signal);
|
|
1919
|
-
if (job.status === "completed") return job.result;
|
|
1920
|
-
if (job.status === "failed") throw failedJobError(job.error);
|
|
1921
|
-
if (Date.now() > deadline) {
|
|
1922
|
-
throw new BackendHttpError({ kind: "timeout", message: `job ${jobId} did not finish in time` });
|
|
1923
|
-
}
|
|
1924
|
-
await sleep(pollInterval(attempt));
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
presignAssetUpload(sha256, mime, signal, purpose, conditional) {
|
|
1928
|
-
return this.http.postJson(
|
|
1929
|
-
"/api/canvas/assets/presign",
|
|
1930
|
-
{ sha256, mime, purpose, ...conditional ? { conditional: true } : {} },
|
|
1931
|
-
signal
|
|
1932
|
-
);
|
|
1933
|
-
}
|
|
1934
|
-
/** Remote cache lookup. A miss (404) — or an old backend without the route — returns null. */
|
|
1935
|
-
async getCacheEntry(cacheKey, signal) {
|
|
1936
|
-
try {
|
|
1937
|
-
const res = await this.http.getJson(`/api/canvas/cache/${encodeURIComponent(cacheKey)}`, signal);
|
|
1938
|
-
return res.entry;
|
|
1939
|
-
} catch (e) {
|
|
1940
|
-
if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
|
|
1941
|
-
throw e;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
async putCacheEntry(entry, signal) {
|
|
1945
|
-
await this.http.putJson(
|
|
1946
|
-
`/api/canvas/cache/${encodeURIComponent(entry.cacheKey)}`,
|
|
1947
|
-
entry,
|
|
1948
|
-
signal
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
|
-
/** Durable run-history record — POST /api/canvas/runs (idempotent server-side on runId). */
|
|
1952
|
-
async recordRun(payload, signal) {
|
|
1953
|
-
await this.http.postJson("/api/canvas/runs", payload, signal);
|
|
1954
|
-
}
|
|
1955
|
-
/**
|
|
1956
|
-
* Resumable-run lookup — GET /api/canvas/runs/active. Returns the newest
|
|
1957
|
-
* still-running/interrupted run for the creative (pinned to the exact canvas
|
|
1958
|
-
* sha), so a fresh sandbox can adopt its run id and re-attach billed
|
|
1959
|
-
* in-flight jobs. Null on no active run — or an older backend without the
|
|
1960
|
-
* route (both 404).
|
|
1961
|
-
*/
|
|
1962
|
-
async getActiveRun(creativeSlug, canvasSha, signal) {
|
|
1963
|
-
const params = new URLSearchParams({ slug: creativeSlug });
|
|
1964
|
-
if (canvasSha) params.set("sha", canvasSha);
|
|
1965
|
-
try {
|
|
1966
|
-
const res = await this.http.getJson(`/api/canvas/runs/active?${params}`, signal);
|
|
1967
|
-
return res.run;
|
|
1968
|
-
} catch (e) {
|
|
1969
|
-
if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
|
|
1970
|
-
throw e;
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
/**
|
|
1974
|
-
* Portable-rerun lookup — GET /api/canvas/runs/latest. The newest run for
|
|
1975
|
-
* the creative that recorded a canvas snapshot manifest; null when none
|
|
1976
|
-
* exists (or the backend predates the route — both 404).
|
|
1977
|
-
*/
|
|
1978
|
-
async getLatestSnapshotRun(creativeSlug, signal) {
|
|
1979
|
-
const params = new URLSearchParams({ slug: creativeSlug });
|
|
1980
|
-
try {
|
|
1981
|
-
const res = await this.http.getJson(`/api/canvas/runs/latest?${params}`, signal);
|
|
1982
|
-
return res.run;
|
|
1983
|
-
} catch (e) {
|
|
1984
|
-
if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
|
|
1985
|
-
throw e;
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Chat-scoped blueprint sync — POST /api/creatives/definition. Lets the
|
|
1990
|
-
* dashboard draw a scaffolded creative's workflow graph BEFORE the first run.
|
|
1991
|
-
* Additive on the backend (never archives siblings, never sets definitionPath).
|
|
1992
|
-
*/
|
|
1993
|
-
async syncCreativeDefinition(payload, signal) {
|
|
1994
|
-
await this.http.postJson("/api/creatives/definition", payload, signal);
|
|
1995
|
-
}
|
|
1996
|
-
getArtifact(kind, name, version, signal) {
|
|
1997
|
-
const path4 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
|
|
1998
|
-
return this.http.getJson(path4, signal);
|
|
1999
|
-
}
|
|
2000
|
-
};
|
|
2001
|
-
|
|
2002
|
-
// src/engine/engine/validator.ts
|
|
2003
|
-
import path3 from "path";
|
|
2004
|
-
import { z as z2 } from "zod";
|
|
2005
|
-
|
|
2006
|
-
// src/engine/schema/canvas.ts
|
|
2007
|
-
import { z } from "zod";
|
|
2008
|
-
var REF_PREFIX = "$ref:";
|
|
2009
|
-
function parseRefExpr(input) {
|
|
2010
|
-
if (!input.startsWith(REF_PREFIX)) return null;
|
|
2011
|
-
const rest = input.slice(REF_PREFIX.length);
|
|
2012
|
-
const hashIdx = rest.indexOf("#");
|
|
2013
|
-
const head = hashIdx === -1 ? rest : rest.slice(0, hashIdx);
|
|
2014
|
-
const tail = hashIdx === -1 ? null : rest.slice(hashIdx + 1);
|
|
2015
|
-
const dot = head.indexOf(".");
|
|
2016
|
-
if (dot <= 0) return null;
|
|
2017
|
-
const nodeId = head.slice(0, dot);
|
|
2018
|
-
const output = head.slice(dot + 1);
|
|
2019
|
-
if (!nodeId || !output) return null;
|
|
2020
|
-
let index;
|
|
2021
|
-
if (tail !== null) {
|
|
2022
|
-
const n = Number(tail);
|
|
2023
|
-
if (tail === "" || !Number.isInteger(n) || n < 0) return null;
|
|
2024
|
-
index = n;
|
|
2025
|
-
}
|
|
2026
|
-
return { kind: "ref", nodeId, output, index };
|
|
2027
|
-
}
|
|
2028
|
-
var NodeIdRe = /^[a-z][a-z0-9_]*$/;
|
|
2029
|
-
var NodeTags = z.object({
|
|
2030
|
-
/** The scene (0-based, in blueprint order) this node renders or voices. */
|
|
2031
|
-
scene: z.number().int().min(0).optional(),
|
|
2032
|
-
/** The node's job in the assembly, e.g. `clip`, `frame`, `composite`, `spine`, `overlay`, `mux`. */
|
|
2033
|
-
role: z.string().min(1).optional(),
|
|
2034
|
-
/** The region index inside a composited (split / picture-in-picture) scene. */
|
|
2035
|
-
region: z.number().int().min(0).optional(),
|
|
2036
|
-
/** True for a speaker's own on-camera voice (lip-synced), false for a voice read over the picture. */
|
|
2037
|
-
on_camera: z.boolean().optional()
|
|
2038
|
-
}).strict();
|
|
2039
|
-
var NodeDecl = z.object({
|
|
2040
|
-
id: z.string().regex(NodeIdRe, "node id must be lower_snake_case"),
|
|
2041
|
-
type: z.string().min(1),
|
|
2042
|
-
version: z.string().min(1).optional(),
|
|
2043
|
-
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
2044
|
-
params: z.record(z.string(), z.unknown()).optional(),
|
|
2045
|
-
// Regenerate knob. The engine is content-addressed: identical params + inputs
|
|
2046
|
-
// return the cached render, so re-running an unchanged node NEVER re-bills or
|
|
2047
|
-
// produces a new result. Bump this token (any string/number — a `2`, a `"v3"`,
|
|
2048
|
-
// a note) and re-run to force THIS node to render fresh; because its new output
|
|
2049
|
-
// changes downstream input hashes, everything depending on it regenerates too.
|
|
2050
|
-
// This is the declarative "change a value, re-run, get a new render" affordance.
|
|
2051
|
-
regenerate: z.union([z.string(), z.number()]).optional(),
|
|
2052
|
-
tags: NodeTags.optional()
|
|
2053
|
-
}).strict();
|
|
2054
|
-
var OutputRef = z.object({
|
|
2055
|
-
node: z.string(),
|
|
2056
|
-
output: z.string()
|
|
2057
|
-
}).strict();
|
|
2058
|
-
var VideoMeta = z.object({
|
|
2059
|
-
duration_s: z.number(),
|
|
2060
|
-
// The one-clock contract: the spine's summed clip lengths and every audio
|
|
2061
|
-
// timeline reachable from the final mux must equal `expected_total_s` — the
|
|
2062
|
-
// validator recomputes both from the LIVE graph (`-shortest` at the final mux
|
|
2063
|
-
// silently truncates the longer stream on any mismatch). Emitted by
|
|
2064
|
-
// scaffold-video; optional so hand-authored canvases still parse.
|
|
2065
|
-
timeline: z.object({
|
|
2066
|
-
spine_node: z.string(),
|
|
2067
|
-
expected_total_s: z.number(),
|
|
2068
|
-
audio_mix_node: z.string().optional()
|
|
2069
|
-
}).strict().optional(),
|
|
2070
|
-
// Each sequenced voiceover turn on the absolute timeline.
|
|
2071
|
-
vo_segments: z.array(
|
|
2072
|
-
z.object({
|
|
2073
|
-
slot: z.string(),
|
|
2074
|
-
start_s: z.number(),
|
|
2075
|
-
end_s: z.number(),
|
|
2076
|
-
scene: z.number().optional(),
|
|
2077
|
-
speaker: z.string().optional()
|
|
2078
|
-
})
|
|
2079
|
-
).default([]),
|
|
2080
|
-
// Scenes with a single on-camera speaker, voiced NATIVELY by the video model
|
|
2081
|
-
// (Seedance generate_audio) then re-voiced to one brand voice. Each entry names
|
|
2082
|
-
// the `audio_voice_convert` node the validator confirms is present — the
|
|
2083
|
-
// native-audio replacement for the retired post-hoc lipsync check. A bare scene
|
|
2084
|
-
// index (legacy) or `{ scene, lipsync_node }` (legacy veed canvases) still parse.
|
|
2085
|
-
talking_scenes: z.array(
|
|
2086
|
-
z.union([
|
|
2087
|
-
z.number(),
|
|
2088
|
-
z.object({
|
|
2089
|
-
scene: z.number(),
|
|
2090
|
-
voice_convert_node: z.string(),
|
|
2091
|
-
// Advisory: the scene's visual length vs the estimated spoken length, so
|
|
2092
|
-
// a reviewer can see a native line that may run past its cut. Not gated.
|
|
2093
|
-
scene_s: z.number().optional(),
|
|
2094
|
-
est_speech_s: z.number().optional(),
|
|
2095
|
-
// Word count of the line est_speech_s was measured for. Together they carry
|
|
2096
|
-
// the speaker's OBSERVED pace (from the deconstruct's word timings), so the
|
|
2097
|
-
// overrun check budgets re-authored lines at the real rate, not a wps guess.
|
|
2098
|
-
speech_words: z.number().optional()
|
|
2099
|
-
}),
|
|
2100
|
-
z.object({ scene: z.number(), lipsync_node: z.string() })
|
|
2101
|
-
])
|
|
2102
|
-
).default([]),
|
|
2103
|
-
// Advisory, NOT gated: scenes where a presenter's later same-shot turns play as
|
|
2104
|
-
// `tts` over their one native clip (a held shot can hold only ONE lip-synced clip,
|
|
2105
|
-
// and voiceover turns can't overlap), so the lips may not match those words. The
|
|
2106
|
-
// scaffold surfaces it so the agent can cut away or rely on the burned-in captions.
|
|
2107
|
-
lip_sync_caution: z.array(z.object({ scene: z.number(), speaker: z.string(), tts_over_native: z.array(z.string()) })).optional(),
|
|
2108
|
-
// Advisory, NOT gated by the validator: the reviewable "which graphic fires
|
|
2109
|
-
// on which spoken beat" map emitted by scaffold-video (per-scene window,
|
|
2110
|
-
// spoken line, storyboard frames, scheduled graphics). Free-form rows so the
|
|
2111
|
-
// schema stays decoupled from the scaffold's exact shape.
|
|
2112
|
-
motion_board: z.array(z.unknown()).optional(),
|
|
2113
|
-
// The recurring identity elements the scaffold wired (one shared reference slot
|
|
2114
|
-
// per cast member / pet / product / logo). The reference-completeness check reads
|
|
2115
|
-
// this to warn when a frame's description mentions an element whose ref isn't
|
|
2116
|
-
// wired on that frame node (the wrong-identity-animal failure).
|
|
2117
|
-
elements: z.array(
|
|
2118
|
-
z.object({
|
|
2119
|
-
ref: z.string(),
|
|
2120
|
-
label: z.string(),
|
|
2121
|
-
type: z.string(),
|
|
2122
|
-
description: z.string().optional()
|
|
2123
|
-
})
|
|
2124
|
-
).optional(),
|
|
2125
|
-
// Per video_generate node: the scene's natural visual span. The validator warns
|
|
2126
|
-
// when a clip's span exceeds the assigned model's max clip duration (e.g. a 9.2s
|
|
2127
|
-
// scene on Veo, which caps at 8s) — the clip would truncate.
|
|
2128
|
-
clip_spans: z.array(z.object({ node: z.string(), span_s: z.number() })).optional(),
|
|
2129
|
-
// WHY the graph has the nodes it has: one route per scene (clip / still_hold /
|
|
2130
|
-
// screen_still / graphic_plate / brand_card / phrase_slice / phrase_run /
|
|
2131
|
-
// composite) + a node-type histogram, stamped by scaffold-video. Read this first
|
|
2132
|
-
// when a canvas looks big.
|
|
2133
|
-
graph_stats: z.object({
|
|
2134
|
-
nodes_total: z.number(),
|
|
2135
|
-
nodes_billable: z.number(),
|
|
2136
|
-
node_types: z.record(z.string(), z.number()),
|
|
2137
|
-
scene_routes: z.array(z.object({ scene: z.number(), route: z.string(), window_s: z.number() }))
|
|
2138
|
-
}).strict().optional()
|
|
2139
|
-
}).strict().optional();
|
|
2140
|
-
var CanvasMetadata = z.object({
|
|
2141
|
-
name: z.string().optional(),
|
|
2142
|
-
description: z.string().optional(),
|
|
2143
|
-
// Free-form, human-facing next-steps guide embedded by scaffolds (e.g.
|
|
2144
|
-
// `scaffold-video`) so the editable checklist travels inside the canvas
|
|
2145
|
-
// instead of only printing to stdout. Ignored by the engine.
|
|
2146
|
-
todo: z.unknown().optional(),
|
|
2147
|
-
video: VideoMeta,
|
|
2148
|
-
// A static ad of a series (`seriesMemberAd`): the lead ad its picture is an edit of, the
|
|
2149
|
-
// lead's version, and the lead's words as that version drew them, which this ad's replace.
|
|
2150
|
-
// Ignored by the engine; the brief projection reads it.
|
|
2151
|
-
series: z.object({
|
|
2152
|
-
lead: z.string().min(1),
|
|
2153
|
-
run_id: z.string().min(1),
|
|
2154
|
-
copy: z.array(z.object({ text: z.string(), role: z.string().optional() }).strict()),
|
|
2155
|
-
design: z.string().optional(),
|
|
2156
|
-
// A translation of the lead: the language (BCP-47) its words are in.
|
|
2157
|
-
language: z.string().min(2).optional()
|
|
2158
|
-
}).strict().optional()
|
|
2159
|
-
}).strict().optional();
|
|
2160
|
-
var CanvasSchema = z.object({
|
|
2161
|
-
schema: z.literal("baker-canvas/1"),
|
|
2162
|
-
cache_salt: z.string().optional(),
|
|
2163
|
-
metadata: CanvasMetadata,
|
|
2164
|
-
nodes: z.array(NodeDecl).min(1),
|
|
2165
|
-
output: OutputRef.optional()
|
|
2166
|
-
}).strict();
|
|
2167
|
-
|
|
2168
|
-
// src/engine/schema/prompts.ts
|
|
2169
|
-
var SLOT_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
2170
|
-
function extractSlots(template) {
|
|
2171
|
-
const out = /* @__PURE__ */ new Set();
|
|
2172
|
-
for (const match of template.matchAll(SLOT_RE)) {
|
|
2173
|
-
const name = match[1];
|
|
2174
|
-
if (name) out.add(name);
|
|
2175
|
-
}
|
|
2176
|
-
return [...out];
|
|
2177
|
-
}
|
|
2178
|
-
function renderTemplate(template, values) {
|
|
2179
|
-
return template.replace(SLOT_RE, (_full, name) => {
|
|
2180
|
-
if (Object.hasOwn(values, name)) {
|
|
2181
|
-
return values[name] ?? "";
|
|
2182
|
-
}
|
|
2183
|
-
return "";
|
|
2184
|
-
});
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
// src/engine/engine/credits.ts
|
|
2188
|
-
function priceNode(def, params) {
|
|
2189
|
-
if (!def.cost) return null;
|
|
2190
|
-
try {
|
|
2191
|
-
const estimate = def.cost({ params });
|
|
2192
|
-
return Number.isFinite(estimate.credits) ? estimate : null;
|
|
2193
|
-
} catch {
|
|
2194
|
-
return null;
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
function estimateNodeCredits(def, params) {
|
|
2198
|
-
if (!def?.cost) return 0;
|
|
2199
|
-
const parsed = def.params.safeParse(params ?? {});
|
|
2200
|
-
if (!parsed.success) return 0;
|
|
2201
|
-
return priceNode(def, parsed.data)?.credits ?? 0;
|
|
2202
|
-
}
|
|
2203
|
-
function estimateCanvasCredits(canvas, registry) {
|
|
2204
|
-
return canvas.nodes.map((n) => ({
|
|
2205
|
-
node_id: n.id,
|
|
2206
|
-
node_type: n.type,
|
|
2207
|
-
credits: estimateNodeCredits(registry.get(n.type), n.params)
|
|
2208
|
-
}));
|
|
2209
|
-
}
|
|
2210
|
-
function sumCredits(perNode) {
|
|
2211
|
-
return perNode.reduce((total, n) => total + n.credits, 0);
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
// src/engine/engine/didyoumean.ts
|
|
2215
|
-
function distance(a, b) {
|
|
2216
|
-
const m = a.length;
|
|
2217
|
-
const n = b.length;
|
|
2218
|
-
if (m === 0) return n;
|
|
2219
|
-
if (n === 0) return m;
|
|
2220
|
-
const dp = makeDp(m, n);
|
|
2221
|
-
for (let i = 1; i <= m; i++) fillRow(dp, a, b, i, n);
|
|
2222
|
-
return dp[m][n] ?? 0;
|
|
2223
|
-
}
|
|
2224
|
-
function makeDp(m, n) {
|
|
2225
|
-
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
2226
|
-
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
2227
|
-
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
2228
|
-
return dp;
|
|
2229
|
-
}
|
|
2230
|
-
function fillRow(dp, a, b, i, n) {
|
|
2231
|
-
const rowI = dp[i];
|
|
2232
|
-
const rowPrev = dp[i - 1];
|
|
2233
|
-
const rowPrev2 = i >= 2 ? dp[i - 2] : void 0;
|
|
2234
|
-
for (let j = 1; j <= n; j++) {
|
|
2235
|
-
rowI[j] = stepCost(a, b, i, j, rowI, rowPrev, rowPrev2);
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
function stepCost(a, b, i, j, rowI, rowPrev, rowPrev2) {
|
|
2239
|
-
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
2240
|
-
const del = (rowPrev[j] ?? 0) + 1;
|
|
2241
|
-
const ins = (rowI[j - 1] ?? 0) + 1;
|
|
2242
|
-
const sub = (rowPrev[j - 1] ?? 0) + cost;
|
|
2243
|
-
let best = Math.min(del, ins, sub);
|
|
2244
|
-
if (i > 1 && j > 1 && rowPrev2 && a.charCodeAt(i - 1) === b.charCodeAt(j - 2) && a.charCodeAt(i - 2) === b.charCodeAt(j - 1)) {
|
|
2245
|
-
best = Math.min(best, (rowPrev2[j - 2] ?? 0) + 1);
|
|
2246
|
-
}
|
|
2247
|
-
return best;
|
|
2248
|
-
}
|
|
2249
|
-
function didYouMean(query, candidates, max = 3) {
|
|
2250
|
-
if (candidates.length === 0 || !query) return [];
|
|
2251
|
-
const threshold = Math.max(1, Math.floor(query.length / 3) + 1);
|
|
2252
|
-
const scored = [];
|
|
2253
|
-
for (const c of candidates) {
|
|
2254
|
-
const d = distance(query.toLowerCase(), c.toLowerCase());
|
|
2255
|
-
if (d <= threshold) scored.push({ c, d });
|
|
2256
|
-
}
|
|
2257
|
-
scored.sort((a, b) => a.d - b.d || a.c.localeCompare(b.c));
|
|
2258
|
-
return scored.slice(0, max).map((s) => s.c);
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
// src/engine/engine/graph.ts
|
|
2262
|
-
function nodeDependencies(node, known) {
|
|
2263
|
-
const deps = /* @__PURE__ */ new Set();
|
|
2264
|
-
const collect = (s) => {
|
|
2265
|
-
const expr = parseRefExpr(s);
|
|
2266
|
-
if (expr && known.has(expr.nodeId)) deps.add(expr.nodeId);
|
|
2267
|
-
};
|
|
2268
|
-
forEachString(node.inputs, collect);
|
|
2269
|
-
forEachString(node.params, collect);
|
|
2270
|
-
return deps;
|
|
2271
|
-
}
|
|
2272
|
-
function buildDependencyGraph(canvas) {
|
|
2273
|
-
const known = new Set(canvas.nodes.map((n) => n.id));
|
|
2274
|
-
const graph = /* @__PURE__ */ new Map();
|
|
2275
|
-
for (const n of canvas.nodes) graph.set(n.id, /* @__PURE__ */ new Set());
|
|
2276
|
-
for (const n of canvas.nodes) {
|
|
2277
|
-
const deps = graph.get(n.id);
|
|
2278
|
-
if (deps) for (const dep of nodeDependencies(n, known)) deps.add(dep);
|
|
2279
|
-
}
|
|
2280
|
-
return graph;
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
// src/engine/engine/lint/context.ts
|
|
2284
|
-
function refNodeOf(ctx, value) {
|
|
2285
|
-
if (typeof value !== "string" || !value.startsWith(REF_PREFIX)) return null;
|
|
2286
|
-
const parsed = parseRefExpr(value);
|
|
2287
|
-
if (!parsed) return null;
|
|
2288
|
-
const idx = ctx.idToIndex.get(parsed.nodeId);
|
|
2289
|
-
return idx === void 0 ? null : ctx.canvas.nodes[idx];
|
|
2290
|
-
}
|
|
2291
|
-
function isTagged(ctx) {
|
|
2292
|
-
return ctx.canvas.nodes.some((n) => n.tags !== void 0);
|
|
2293
|
-
}
|
|
2294
|
-
function onVideoCanvas(check) {
|
|
2295
|
-
return (ctx) => {
|
|
2296
|
-
const meta = ctx.canvas.metadata?.video;
|
|
2297
|
-
if (meta) check(ctx, meta);
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
// src/engine/engine/lint/rules/audio.ts
|
|
2302
|
-
var CODES = {
|
|
2303
|
-
DUCK_UNITS: "AUDIO_DUCK_UNITS",
|
|
2304
|
-
MUSIC_BED_TOO_LOUD: "AUDIO_MUSIC_BED_TOO_LOUD",
|
|
2305
|
-
MUSIC_BED_NOT_DUCKED: "AUDIO_MUSIC_BED_NOT_DUCKED",
|
|
2306
|
-
MUSIC_BED_SCORED_TO_ARC: "AUDIO_MUSIC_BED_SCORED_TO_ARC"
|
|
2307
|
-
};
|
|
2308
|
-
var MUSIC_BED_MAX_GAIN_DB = -18;
|
|
2309
|
-
var DUCK_MIN_ATTACK_MS = 0.5;
|
|
2310
|
-
var DUCK_MIN_RELEASE_MS = 10;
|
|
2311
|
-
var SPEECH_NODE_TYPES = /* @__PURE__ */ new Set(["tts", "dialogue", "audio_voice_convert", "video_lipsync"]);
|
|
2312
|
-
var SPEECH_SLOT_RE = /voice|speech|\bvo\b|^vo|_vo|dialog|narration/i;
|
|
2313
|
-
var MUSIC_SLOT_RE = /music|score|\bbed\b/i;
|
|
2314
|
-
function audioTimelineTracks(node) {
|
|
2315
|
-
const tracks = node.params?.tracks;
|
|
2316
|
-
if (!Array.isArray(tracks)) return [];
|
|
2317
|
-
return tracks.filter(
|
|
2318
|
-
(t) => typeof t === "object" && t !== null && typeof t.slot === "string"
|
|
2319
|
-
);
|
|
2320
|
-
}
|
|
2321
|
-
function checkAudioBedMix(ctx) {
|
|
2322
|
-
for (const n of ctx.canvas.nodes) {
|
|
2323
|
-
if (n.type === "audio_timeline") checkOneAudioMix(ctx, n);
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
function checkOneAudioMix(ctx, node) {
|
|
2327
|
-
const tracks = audioTimelineTracks(node);
|
|
2328
|
-
if (tracks.length === 0) return;
|
|
2329
|
-
const duck = node.params?.duck;
|
|
2330
|
-
checkDuckUnits(ctx, node, duck);
|
|
2331
|
-
const sourceType = (slot) => refNodeOf(ctx, node.inputs?.[slot])?.type ?? null;
|
|
2332
|
-
const speech = tracks.filter((t) => {
|
|
2333
|
-
const type = sourceType(t.slot);
|
|
2334
|
-
return type !== null && SPEECH_NODE_TYPES.has(type) || SPEECH_SLOT_RE.test(t.slot);
|
|
2335
|
-
});
|
|
2336
|
-
if (speech.length === 0) return;
|
|
2337
|
-
const beds = tracks.filter(
|
|
2338
|
-
(t) => (sourceType(t.slot) === "music" || MUSIC_SLOT_RE.test(t.slot)) && t.duration_s === void 0
|
|
2339
|
-
);
|
|
2340
|
-
if (beds.length === 0) return;
|
|
2341
|
-
checkBedLevels(ctx, node, beds);
|
|
2342
|
-
checkBedDucked(ctx, node, beds, speech, duck);
|
|
2343
|
-
for (const bed of beds) checkBedNotScored(ctx, node, refNodeOf(ctx, node.inputs?.[bed.slot]));
|
|
2344
|
-
}
|
|
2345
|
-
var MUSIC_ARC_RE = /\b(?:emotional arc|follows? (?:her|his|their|the) arc|swell(?:s|ing)?|crescendo|climax|build(?:s|ing) (?:to|into)|resolv(?:e|es|ing)|dramatic|epic|tense|tension)\b/i;
|
|
2346
|
-
function checkBedNotScored(ctx, node, source) {
|
|
2347
|
-
if (source?.type !== "music") return;
|
|
2348
|
-
const params = source.params;
|
|
2349
|
-
if (params?.model !== "elevenlabs/video-background-music-v1") return;
|
|
2350
|
-
const brief = [params?.description, params?.prompt].filter((v) => typeof v === "string").join(" ");
|
|
2351
|
-
const found = MUSIC_ARC_RE.exec(brief);
|
|
2352
|
-
if (!found) return;
|
|
2353
|
-
ctx.issues.push({
|
|
2354
|
-
path: `nodes[${ctx.idToIndex.get(source.id) ?? -1}].params.description`,
|
|
2355
|
-
code: CODES.MUSIC_BED_SCORED_TO_ARC,
|
|
2356
|
-
severity: "warning",
|
|
2357
|
-
node_id: source.id,
|
|
2358
|
-
node_type: source.type,
|
|
2359
|
-
message: `"${source.id}" briefs the bed with "${found[0]}", and "${node.id}" mixes it under speech \u2014 this model watches the cut, so movement asked for here comes back as a film score (a testimonial briefed that way was rejected as "muy dram\xE1tica"). Under a voice, name ONE steady character and forbid movement: no build, no drop, no climax, nothing that follows the arc`
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2362
|
-
function checkDuckUnits(ctx, node, duck) {
|
|
2363
|
-
const index = ctx.idToIndex.get(node.id) ?? -1;
|
|
2364
|
-
if (typeof duck?.attack === "number" && duck.attack < DUCK_MIN_ATTACK_MS) {
|
|
2365
|
-
ctx.issues.push({
|
|
2366
|
-
path: `nodes[${index}].params.duck.attack`,
|
|
2367
|
-
code: CODES.DUCK_UNITS,
|
|
2368
|
-
node_id: node.id,
|
|
2369
|
-
node_type: node.type,
|
|
2370
|
-
message: `"${node.id}" ducks with attack ${duck.attack} \u2014 this parameter is MILLISECONDS, so that is instantaneous. Use 5-50 (default 5)`
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
if (typeof duck?.release === "number" && duck.release < DUCK_MIN_RELEASE_MS) {
|
|
2374
|
-
ctx.issues.push({
|
|
2375
|
-
path: `nodes[${index}].params.duck.release`,
|
|
2376
|
-
code: CODES.DUCK_UNITS,
|
|
2377
|
-
node_id: node.id,
|
|
2378
|
-
node_type: node.type,
|
|
2379
|
-
message: `"${node.id}" ducks with release ${duck.release} \u2014 this parameter is MILLISECONDS, so the bed jumps back to full level between syllables and the mix sounds like the music is too loud. Use 200-800 (default 300); 0.45 is a seconds value`
|
|
2380
|
-
});
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
function checkBedLevels(ctx, node, beds) {
|
|
2384
|
-
const index = ctx.idToIndex.get(node.id) ?? -1;
|
|
2385
|
-
for (const bed of beds) {
|
|
2386
|
-
if ((bed.gain_db ?? 0) <= MUSIC_BED_MAX_GAIN_DB) continue;
|
|
2387
|
-
const level = bed.gain_db === void 0 ? "full level (no gain_db)" : `${bed.gain_db} dB`;
|
|
2388
|
-
ctx.issues.push({
|
|
2389
|
-
path: `nodes[${index}].params.tracks`,
|
|
2390
|
-
code: CODES.MUSIC_BED_TOO_LOUD,
|
|
2391
|
-
severity: "warning",
|
|
2392
|
-
node_id: node.id,
|
|
2393
|
-
node_type: node.type,
|
|
2394
|
-
message: `"${node.id}" runs the music bed "${bed.slot}" at ${level} under speech \u2014 a bed belongs at ${MUSIC_BED_MAX_GAIN_DB} dB or below (the scaffolder pins -20; -13 was rejected by a client as too loud). If a silent tail sounds empty, add the same bed as a SECOND windowed track (start_s + duration_s) with its own higher gain instead of raising this one`
|
|
2395
|
-
});
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
function checkBedDucked(ctx, node, beds, speech, duck) {
|
|
2399
|
-
if (beds.some((bed) => bed.slot === duck?.track)) return;
|
|
2400
|
-
const quoted = (list) => list.map((t) => `"${t.slot}"`).join(", ");
|
|
2401
|
-
ctx.issues.push({
|
|
2402
|
-
path: `nodes[${ctx.idToIndex.get(node.id) ?? -1}].params.duck`,
|
|
2403
|
-
code: CODES.MUSIC_BED_NOT_DUCKED,
|
|
2404
|
-
severity: "warning",
|
|
2405
|
-
node_id: node.id,
|
|
2406
|
-
node_type: node.type,
|
|
2407
|
-
message: `"${node.id}" mixes a music bed (${quoted(beds)}) under speech (${quoted(speech)}) with no sidechain duck on it \u2014 a flat bed fights the voice in every gap. Add params.duck { track: "${beds[0]?.slot}", against: [${quoted(speech)}] }`
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
|
-
var audioBedMixRule = {
|
|
2411
|
-
name: "audio-bed-mix",
|
|
2412
|
-
codes: {
|
|
2413
|
-
[CODES.DUCK_UNITS]: "error",
|
|
2414
|
-
[CODES.MUSIC_BED_TOO_LOUD]: "warning",
|
|
2415
|
-
[CODES.MUSIC_BED_NOT_DUCKED]: "warning",
|
|
2416
|
-
[CODES.MUSIC_BED_SCORED_TO_ARC]: "warning"
|
|
2417
|
-
},
|
|
2418
|
-
check: checkAudioBedMix
|
|
2419
|
-
};
|
|
2420
|
-
|
|
2421
|
-
// src/engine/engine/lint/rules/image.ts
|
|
2422
|
-
var CODES2 = {
|
|
2423
|
-
IMAGE_PROFILE_MISSING: "IMAGE_PROMPT_PROFILE_MISSING",
|
|
2424
|
-
PARALLEL_FORMAT_RENDERS: "IMAGE_PARALLEL_FORMAT_RENDERS"
|
|
2425
|
-
};
|
|
2426
|
-
function checkImageProfile(ctx) {
|
|
2427
|
-
for (const n of ctx.canvas.nodes) {
|
|
2428
|
-
if (n.type !== "image_generate") continue;
|
|
2429
|
-
const model = n.params?.model;
|
|
2430
|
-
if (typeof model !== "string" || imageProfileFor(model)) continue;
|
|
2431
|
-
ctx.issues.push({
|
|
2432
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.model`,
|
|
2433
|
-
code: CODES2.IMAGE_PROFILE_MISSING,
|
|
2434
|
-
severity: "warning",
|
|
2435
|
-
node_id: n.id,
|
|
2436
|
-
message: `"${n.id}" renders on "${model}", an image model with no frame-prompt profile \u2014 it got the generic template (constraint placement / photoreal cue not tuned). Add an ImageModelProfile for this model`
|
|
2437
|
-
});
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
var SAME_CREATIVE_PROMPT_SIMILARITY = 0.6;
|
|
2441
|
-
function promptWordSet(text) {
|
|
2442
|
-
return new Set(
|
|
2443
|
-
text.toLowerCase().split(/[^a-z0-9']+/).filter((w) => w.length >= 3)
|
|
2444
|
-
);
|
|
2445
|
-
}
|
|
2446
|
-
function jaccard(a, b) {
|
|
2447
|
-
if (a.size === 0 || b.size === 0) return 0;
|
|
2448
|
-
let shared = 0;
|
|
2449
|
-
for (const w of a) if (b.has(w)) shared++;
|
|
2450
|
-
return shared / (a.size + b.size - shared);
|
|
2451
|
-
}
|
|
2452
|
-
function parallelFormatRenderGroups(nodes) {
|
|
2453
|
-
const parent = nodes.map((_, i) => i);
|
|
2454
|
-
const find = (i) => {
|
|
2455
|
-
let root = i;
|
|
2456
|
-
while (parent[root] !== root) root = parent[root];
|
|
2457
|
-
return root;
|
|
2458
|
-
};
|
|
2459
|
-
const words = nodes.map((n) => promptWordSet(n.prompt));
|
|
2460
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
2461
|
-
for (let j = i + 1; j < nodes.length; j++) {
|
|
2462
|
-
const a = nodes[i];
|
|
2463
|
-
const b = nodes[j];
|
|
2464
|
-
if (a.ratio === b.ratio) continue;
|
|
2465
|
-
if (jaccard(words[i], words[j]) < SAME_CREATIVE_PROMPT_SIMILARITY) continue;
|
|
2466
|
-
parent[find(i)] = find(j);
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2470
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
2471
|
-
const root = find(i);
|
|
2472
|
-
const group = groups.get(root) ?? [];
|
|
2473
|
-
group.push(nodes[i]);
|
|
2474
|
-
groups.set(root, group);
|
|
2475
|
-
}
|
|
2476
|
-
return [...groups.values()].filter((g) => g.length > 1);
|
|
2477
|
-
}
|
|
2478
|
-
var STATIC_RENDER_ROLES = /* @__PURE__ */ new Set([
|
|
2479
|
-
STATIC_VARIANTS_ROLE,
|
|
2480
|
-
STATIC_VARIANT_CALL_ROLE,
|
|
2481
|
-
STATIC_FIX_ROLE,
|
|
2482
|
-
STATIC_ADAPT_FORMAT_ROLE
|
|
2483
|
-
]);
|
|
2484
|
-
function checkParallelFormatRenders(ctx) {
|
|
2485
|
-
const candidates = [];
|
|
2486
|
-
for (const n of ctx.canvas.nodes) {
|
|
2487
|
-
if (n.type !== "image_generate" || STATIC_RENDER_ROLES.has(n.tags?.role ?? "")) continue;
|
|
2488
|
-
const params = n.params;
|
|
2489
|
-
const prompt = params?.prompt;
|
|
2490
|
-
if (typeof prompt !== "string" || prompt.length === 0) continue;
|
|
2491
|
-
candidates.push({ id: n.id, prompt, ratio: String(params?.aspect_ratio ?? "(unset)") });
|
|
2492
|
-
}
|
|
2493
|
-
if (candidates.length < 2) return;
|
|
2494
|
-
for (const group of parallelFormatRenderGroups(candidates)) {
|
|
2495
|
-
const ids = group.map((g) => g.id);
|
|
2496
|
-
const ratios = [...new Set(group.map((g) => g.ratio))].sort();
|
|
2497
|
-
ctx.issues.push({
|
|
2498
|
-
path: "nodes",
|
|
2499
|
-
code: CODES2.PARALLEL_FORMAT_RENDERS,
|
|
2500
|
-
severity: "warning",
|
|
2501
|
-
message: `${ids.join(", ")} render the same creative once per aspect ratio (${ratios.join(", ")}) \u2014 each is an independent generation, so the formats come back with different type, crop, and color and stop reading as one ad. Keep ONE hero render and add a terminal image_aspect_adapt node fed from it (params.formats or params.platform) so every ratio is re-framed from the same image`
|
|
2502
|
-
});
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
var parallelFormatRendersRule = {
|
|
2506
|
-
name: "parallel-format-renders",
|
|
2507
|
-
codes: { [CODES2.PARALLEL_FORMAT_RENDERS]: "warning" },
|
|
2508
|
-
check: checkParallelFormatRenders
|
|
2509
|
-
};
|
|
2510
|
-
var imageProfileRule = {
|
|
2511
|
-
name: "image-profile",
|
|
2512
|
-
codes: { [CODES2.IMAGE_PROFILE_MISSING]: "warning" },
|
|
2513
|
-
check: checkImageProfile
|
|
2514
|
-
};
|
|
2515
|
-
|
|
2516
|
-
// src/engine/engine/lint/rules/video-prompt.ts
|
|
2517
|
-
var CODES3 = {
|
|
2518
|
-
PROMPT_PROFILE_MISSING: "VIDEO_PROMPT_PROFILE_MISSING",
|
|
2519
|
-
PROMPT_DECISION_MISSING: "VIDEO_PROMPT_DECISION_MISSING",
|
|
2520
|
-
HOOK_LAYER_MISSING: "VIDEO_HOOK_LAYER_MISSING",
|
|
2521
|
-
PERSON_GENERATION_MISSING: "VIDEO_PERSON_GENERATION_MISSING",
|
|
2522
|
-
PROMPT_OVER_BUDGET: "VIDEO_PROMPT_OVER_BUDGET",
|
|
2523
|
-
RAW_FACE_KEYFRAME: "VIDEO_RAW_FACE_KEYFRAME",
|
|
2524
|
-
REFERENCE_MISSING: "VIDEO_REFERENCE_MISSING",
|
|
2525
|
-
MODEL_IGNORES_INPUT: "VIDEO_MODEL_IGNORES_INPUT",
|
|
2526
|
-
UI_IN_PROMPT: "VIDEO_UI_IN_PROMPT",
|
|
2527
|
-
BRANDMARK_IN_PROMPT: "VIDEO_BRANDMARK_IN_PROMPT"
|
|
2528
|
-
};
|
|
2529
|
-
function checkPromptProfile(ctx) {
|
|
2530
|
-
for (const n of ctx.canvas.nodes) {
|
|
2531
|
-
if (n.type !== "video_generate") continue;
|
|
2532
|
-
const model = n.params?.model;
|
|
2533
|
-
if (typeof model !== "string" || clipProfileFor(model)) continue;
|
|
2534
|
-
ctx.issues.push({
|
|
2535
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.model`,
|
|
2536
|
-
code: CODES3.PROMPT_PROFILE_MISSING,
|
|
2537
|
-
severity: "warning",
|
|
2538
|
-
node_id: n.id,
|
|
2539
|
-
message: `"${n.id}" runs on "${model}", a model with no clip-prompt profile \u2014 its prompt was authored with the default Seedance syntax. Review the dialogue/emotion markup for this model before billing`
|
|
2540
|
-
});
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
var TECHNIQUE_CUE = /\b(camera|push[- ]?in|pull[- ]?out|pan|dolly|zoom|tilt|track|handheld|locked|orbit|crane|whip|motion|move)\b/i;
|
|
2544
|
-
var NEGATIVES_CUE = /\bavoid:|\bno subtitles|\bnegative\b|keep it clean and stable/i;
|
|
2545
|
-
var VIBE_CUE = /\b(light|lighting|golden|moody|warm|cool|tone|grade|contrast|shadow|glow|rim|backlit|neon|soft|harsh)\b/i;
|
|
2546
|
-
function checkPromptDecisions(ctx) {
|
|
2547
|
-
for (const n of ctx.canvas.nodes) {
|
|
2548
|
-
if (n.type !== "video_generate") continue;
|
|
2549
|
-
const params = n.params;
|
|
2550
|
-
const prompt = typeof params?.prompt === "string" ? params.prompt : "";
|
|
2551
|
-
if (!prompt) continue;
|
|
2552
|
-
const hasTechnique = TECHNIQUE_CUE.test(prompt);
|
|
2553
|
-
const hasNegatives = NEGATIVES_CUE.test(prompt) || typeof params?.negative_prompt === "string";
|
|
2554
|
-
if (hasTechnique || hasNegatives) continue;
|
|
2555
|
-
ctx.issues.push({
|
|
2556
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
2557
|
-
code: CODES3.PROMPT_DECISION_MISSING,
|
|
2558
|
-
severity: "warning",
|
|
2559
|
-
node_id: n.id,
|
|
2560
|
-
message: `"${n.id}" is a thin clip prompt \u2014 it names no camera/motion move (TECHNIQUE) and no constraints (NEGATIVES: an affirmative "Keep it clean and stable" line, a negative_prompt param, or an "Avoid:" tail). Make the six decisions (route / spec / beats / copy / technique / negatives) so the model doesn't fill the gaps with drift. See prompt-anatomy.md`
|
|
2561
|
-
});
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
function checkHookLayers(ctx) {
|
|
2565
|
-
const scene0 = isTagged(ctx) ? ctx.canvas.nodes.filter((n) => n.tags?.scene === 0) : ctx.canvas.nodes.filter((n) => /^s0(?:[_a-z0-9])*_/.test(n.id) || n.id === "s0");
|
|
2566
|
-
const hookClip = scene0.find((n) => n.type === "video_generate");
|
|
2567
|
-
if (!hookClip) return;
|
|
2568
|
-
const scene0Text = scene0.map((n) => {
|
|
2569
|
-
const p = n.params?.prompt;
|
|
2570
|
-
return typeof p === "string" ? p : "";
|
|
2571
|
-
}).join("\n");
|
|
2572
|
-
const hookParams = hookClip.params;
|
|
2573
|
-
const hasOverlayLayer = ctx.canvas.nodes.some((n) => /hyperframe/.test(n.type));
|
|
2574
|
-
const hasSound = hookParams?.generate_audio === true || /Dialogue:|Audio:/.test(scene0Text) || ctx.canvas.nodes.some((n) => n.type === "tts" || n.type === "dialogue" || n.type === "music");
|
|
2575
|
-
const hasVisual = scene0Text.trim().length > 40;
|
|
2576
|
-
const hasVibe = VIBE_CUE.test(scene0Text);
|
|
2577
|
-
const missing = [];
|
|
2578
|
-
if (!hasOverlayLayer) missing.push("text (overlay)");
|
|
2579
|
-
if (!hasSound) missing.push("sound (line/SFX)");
|
|
2580
|
-
if (!hasVisual) missing.push("visual (the frame)");
|
|
2581
|
-
if (!hasVibe) missing.push("vibe (lighting/tone)");
|
|
2582
|
-
if (missing.length < 2) return;
|
|
2583
|
-
ctx.issues.push({
|
|
2584
|
-
path: `nodes[${ctx.idToIndex.get(hookClip.id) ?? -1}]`,
|
|
2585
|
-
code: CODES3.HOOK_LAYER_MISSING,
|
|
2586
|
-
severity: "warning",
|
|
2587
|
-
node_id: hookClip.id,
|
|
2588
|
-
message: `the hook (scene 0) is thin across layers \u2014 missing ${missing.join(", ")}. A scroll-stopping hook works on all four layers (text / sound / visual / vibe); strengthen the missing ones. See hook-craft.md`
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
function checkPersonGeneration(ctx) {
|
|
2592
|
-
for (const n of ctx.canvas.nodes) {
|
|
2593
|
-
if (n.type !== "video_generate") continue;
|
|
2594
|
-
const params = n.params;
|
|
2595
|
-
const model = params?.model;
|
|
2596
|
-
if (clipProfileFor(typeof model === "string" ? model : "")?.id !== "veo") continue;
|
|
2597
|
-
const hasKeyframe = Boolean(n.inputs?.first_frame ?? n.inputs?.reference);
|
|
2598
|
-
if (!hasKeyframe || params?.person_generation) continue;
|
|
2599
|
-
ctx.issues.push({
|
|
2600
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params`,
|
|
2601
|
-
code: CODES3.PERSON_GENERATION_MISSING,
|
|
2602
|
-
severity: "warning",
|
|
2603
|
-
node_id: n.id,
|
|
2604
|
-
message: `Veo clip "${n.id}" drives from a keyframe but sets no person_generation \u2014 set "allow_adult" (the only legal value for image-to-video, and the only one allowed in the EU/UK)`
|
|
2605
|
-
});
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
function checkPromptBudget(ctx) {
|
|
2609
|
-
for (const n of ctx.canvas.nodes) {
|
|
2610
|
-
if (n.type !== "video_generate") continue;
|
|
2611
|
-
const params = n.params;
|
|
2612
|
-
const model = params?.model;
|
|
2613
|
-
const prompt = params?.prompt;
|
|
2614
|
-
const profile = typeof model === "string" ? clipProfileFor(model) : void 0;
|
|
2615
|
-
if (!profile || typeof prompt !== "string") continue;
|
|
2616
|
-
const words = prompt.trim().split(/\s+/).filter(Boolean).length;
|
|
2617
|
-
if (words <= profile.wordBudget) continue;
|
|
2618
|
-
ctx.issues.push({
|
|
2619
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
2620
|
-
code: CODES3.PROMPT_OVER_BUDGET,
|
|
2621
|
-
severity: "warning",
|
|
2622
|
-
node_id: n.id,
|
|
2623
|
-
message: `"${n.id}" prompt is ${words} words \u2014 over ${model}'s ~${profile.wordBudget}-word budget; trim it (the frames carry the content, so keep the clip prompt to motion + audio)`
|
|
2624
|
-
});
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
|
-
function checkRawFaceKeyframe(ctx) {
|
|
2628
|
-
for (const n of ctx.canvas.nodes) {
|
|
2629
|
-
if (n.type !== "video_generate") continue;
|
|
2630
|
-
const model = n.params?.model;
|
|
2631
|
-
if (clipProfileFor(typeof model === "string" ? model : "")?.id !== "seedance") continue;
|
|
2632
|
-
const src = refNodeOf(ctx, n.inputs?.first_frame ?? n.inputs?.reference);
|
|
2633
|
-
if (!src || src.type !== "ingest") continue;
|
|
2634
|
-
ctx.issues.push({
|
|
2635
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].inputs.first_frame`,
|
|
2636
|
-
code: CODES3.RAW_FACE_KEYFRAME,
|
|
2637
|
-
severity: "warning",
|
|
2638
|
-
node_id: n.id,
|
|
2639
|
-
message: `Seedance clip "${n.id}" animates a RAW ingested image ("${src.id}"), not a generated frame \u2014 if it shows a real human face, ByteDance's real-person filter rejects it (422). Anchor on an AI-generated portrait (recast), or route this clip to Veo`
|
|
2640
|
-
});
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
var ELEMENT_TYPE_KEYWORDS = {
|
|
2644
|
-
animal: ["dog", "puppy", "pup", "cat", "kitten", "kitty", "pet", "canine", "feline"],
|
|
2645
|
-
logo: ["logo", "wordmark"],
|
|
2646
|
-
badge: ["badge", "seal"],
|
|
2647
|
-
product: []
|
|
2648
|
-
};
|
|
2649
|
-
var CHECKED_ELEMENT_TYPES = new Set(Object.keys(ELEMENT_TYPE_KEYWORDS));
|
|
2650
|
-
var KEYWORD_STOPWORDS = /* @__PURE__ */ new Set([
|
|
2651
|
-
"the",
|
|
2652
|
-
"and",
|
|
2653
|
-
"with",
|
|
2654
|
-
"for",
|
|
2655
|
-
"brand",
|
|
2656
|
-
"logo",
|
|
2657
|
-
"image",
|
|
2658
|
-
"shot",
|
|
2659
|
-
"main",
|
|
2660
|
-
"hero",
|
|
2661
|
-
"element",
|
|
2662
|
-
"product",
|
|
2663
|
-
"reference"
|
|
2664
|
-
]);
|
|
2665
|
-
function keywordTokens(text) {
|
|
2666
|
-
if (!text) return [];
|
|
2667
|
-
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !KEYWORD_STOPWORDS.has(t));
|
|
2668
|
-
}
|
|
2669
|
-
function elementMentionKeywords(el) {
|
|
2670
|
-
const typeWords = ELEMENT_TYPE_KEYWORDS[el.type.toLowerCase()] ?? [];
|
|
2671
|
-
return [.../* @__PURE__ */ new Set([...typeWords, ...keywordTokens(el.label), ...keywordTokens(el.description)])];
|
|
2672
|
-
}
|
|
2673
|
-
function keywordsForElement(el) {
|
|
2674
|
-
return elementMentionKeywords(el);
|
|
2675
|
-
}
|
|
2676
|
-
function containsWord(text, word) {
|
|
2677
|
-
const esc = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2678
|
-
return new RegExp(`\\b${esc}\\b`, "i").test(text);
|
|
2679
|
-
}
|
|
2680
|
-
var FRAME_DESCRIPTION_START = "FRAME DESCRIPTION (this frame's editable prompt):";
|
|
2681
|
-
var FRAME_DESCRIPTION_END = "Render exactly what the FRAME DESCRIPTION";
|
|
2682
|
-
function frameDescriptionOf(prompt) {
|
|
2683
|
-
const i = prompt.indexOf(FRAME_DESCRIPTION_START);
|
|
2684
|
-
if (i < 0) return prompt;
|
|
2685
|
-
const rest = prompt.slice(i + FRAME_DESCRIPTION_START.length);
|
|
2686
|
-
const j = rest.indexOf(FRAME_DESCRIPTION_END);
|
|
2687
|
-
return j < 0 ? rest : rest.slice(0, j);
|
|
2688
|
-
}
|
|
2689
|
-
function checkFrameReferences(ctx, node, index, keyworded) {
|
|
2690
|
-
const rawPrompt = node.params?.prompt;
|
|
2691
|
-
if (typeof rawPrompt !== "string" || rawPrompt.length === 0) return;
|
|
2692
|
-
const prompt = frameDescriptionOf(rawPrompt);
|
|
2693
|
-
const inputsBlob = JSON.stringify(node.inputs ?? {});
|
|
2694
|
-
for (const { el, keywords } of keyworded) {
|
|
2695
|
-
if (inputsBlob.includes(el.ref)) continue;
|
|
2696
|
-
const hit = keywords.find((kw) => containsWord(prompt, kw));
|
|
2697
|
-
if (!hit) continue;
|
|
2698
|
-
ctx.issues.push({
|
|
2699
|
-
path: `nodes[${index}].inputs.reference`,
|
|
2700
|
-
code: CODES3.REFERENCE_MISSING,
|
|
2701
|
-
severity: "warning",
|
|
2702
|
-
message: `frame "${node.id}" describes a "${hit}" but the "${el.label}" reference (${el.ref}) isn't wired on this node \u2014 the generator will invent a wrong-identity ${el.type} from prose. Add ${el.ref} to inputs.reference`,
|
|
2703
|
-
node_id: node.id,
|
|
2704
|
-
node_type: node.type
|
|
2705
|
-
});
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
function checkReferenceCompleteness(ctx, meta) {
|
|
2709
|
-
const elements = (meta.elements ?? []).filter((el) => CHECKED_ELEMENT_TYPES.has(el.type.toLowerCase()));
|
|
2710
|
-
const keyworded = elements.map((el) => ({ el, keywords: keywordsForElement(el) })).filter((e) => e.keywords.length > 0);
|
|
2711
|
-
if (keyworded.length === 0) return;
|
|
2712
|
-
const sheetSources = JSON.stringify(
|
|
2713
|
-
ctx.canvas.nodes.filter((n) => n.type === "image_reference_sheet").map((n) => n.inputs ?? {})
|
|
2714
|
-
);
|
|
2715
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
2716
|
-
const n = ctx.canvas.nodes[i];
|
|
2717
|
-
if (n?.type !== "image_generate" || sheetSources.includes(`"$ref:${n.id}.`)) continue;
|
|
2718
|
-
checkFrameReferences(ctx, n, i, keyworded);
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
function checkUnusableLastFrame(ctx) {
|
|
2722
|
-
for (const n of ctx.canvas.nodes) {
|
|
2723
|
-
if (n.type !== "video_generate") continue;
|
|
2724
|
-
const model = n.params?.model;
|
|
2725
|
-
if (typeof model !== "string" || supportsLastFrame(model)) continue;
|
|
2726
|
-
const inputs = n.inputs;
|
|
2727
|
-
if (!inputs?.last_frame) continue;
|
|
2728
|
-
ctx.issues.push({
|
|
2729
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].inputs.last_frame`,
|
|
2730
|
-
code: CODES3.MODEL_IGNORES_INPUT,
|
|
2731
|
-
severity: "warning",
|
|
2732
|
-
node_id: n.id,
|
|
2733
|
-
node_type: "video_generate",
|
|
2734
|
-
message: `"${n.id}" wires a last_frame but runs on ${model}, which takes only ONE conditioning image \u2014 the end keyframe is dropped and the image generated for it is wasted. Drop the last_frame, or move this clip to Veo/Seedance, which honour both frames`
|
|
2735
|
-
});
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
var UI_IN_PROMPT_RE = /\bscreen[- ]?(?:recording|capture|grab|share)\b|\bapp (?:interface|screen)\b|\bphone screen overlay\b/i;
|
|
2739
|
-
function checkUiInPrompt(ctx) {
|
|
2740
|
-
for (const n of ctx.canvas.nodes) {
|
|
2741
|
-
if (n.type !== "video_generate") continue;
|
|
2742
|
-
const prompt = n.params?.prompt;
|
|
2743
|
-
if (typeof prompt !== "string" || !UI_IN_PROMPT_RE.test(prompt)) continue;
|
|
2744
|
-
ctx.issues.push({
|
|
2745
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
2746
|
-
code: CODES3.UI_IN_PROMPT,
|
|
2747
|
-
severity: "warning",
|
|
2748
|
-
message: `"${n.id}" asks the video model to render a screen/UI surface \u2014 generative video garbles UI text and chrome. Composite the real screen on the overlay layer (screenshot / brand HTML) and keep this prompt to the background plate`,
|
|
2749
|
-
node_id: n.id,
|
|
2750
|
-
node_type: "video_generate"
|
|
2751
|
-
});
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
var BRANDMARK_IN_PROMPT_RE = /\b(?:logo|wordmark) (?:overlay|animation|sting|card|reveal)\b|\b(?:google|facebook|instagram|tiktok|youtube|amazon|apple|microsoft|whatsapp|netflix|spotify|excel|trustpilot) (?:logo|wordmark|branding)\b/i;
|
|
2755
|
-
function checkBrandmarkInPrompt(ctx) {
|
|
2756
|
-
for (const n of ctx.canvas.nodes) {
|
|
2757
|
-
if (n.type !== "video_generate" && n.type !== "image_generate") continue;
|
|
2758
|
-
const prompt = n.params?.prompt;
|
|
2759
|
-
if (typeof prompt !== "string" || !BRANDMARK_IN_PROMPT_RE.test(prompt)) continue;
|
|
2760
|
-
ctx.issues.push({
|
|
2761
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
2762
|
-
code: CODES3.BRANDMARK_IN_PROMPT,
|
|
2763
|
-
severity: "warning",
|
|
2764
|
-
message: `"${n.id}" asks the model to render a brand logo/wordmark \u2014 generation garbles marks and third-party logos carry IP exposure. Source the real mark (baker images logo <domain>) and composite it on the overlay layer`,
|
|
2765
|
-
node_id: n.id,
|
|
2766
|
-
node_type: n.type
|
|
2767
|
-
});
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
var promptProfileRule = {
|
|
2771
|
-
name: "video-prompt-profile",
|
|
2772
|
-
codes: { [CODES3.PROMPT_PROFILE_MISSING]: "warning" },
|
|
2773
|
-
check: checkPromptProfile
|
|
2774
|
-
};
|
|
2775
|
-
var personGenerationRule = {
|
|
2776
|
-
name: "video-person-generation",
|
|
2777
|
-
codes: { [CODES3.PERSON_GENERATION_MISSING]: "warning" },
|
|
2778
|
-
check: checkPersonGeneration
|
|
2779
|
-
};
|
|
2780
|
-
var promptBudgetRule = {
|
|
2781
|
-
name: "video-prompt-budget",
|
|
2782
|
-
codes: { [CODES3.PROMPT_OVER_BUDGET]: "warning" },
|
|
2783
|
-
check: checkPromptBudget
|
|
2784
|
-
};
|
|
2785
|
-
var rawFaceKeyframeRule = {
|
|
2786
|
-
name: "video-raw-face-keyframe",
|
|
2787
|
-
codes: { [CODES3.RAW_FACE_KEYFRAME]: "warning" },
|
|
2788
|
-
check: checkRawFaceKeyframe
|
|
2789
|
-
};
|
|
2790
|
-
var promptDecisionsRule = {
|
|
2791
|
-
name: "video-prompt-decisions",
|
|
2792
|
-
codes: { [CODES3.PROMPT_DECISION_MISSING]: "warning" },
|
|
2793
|
-
check: checkPromptDecisions
|
|
2794
|
-
};
|
|
2795
|
-
var hookLayersRule = {
|
|
2796
|
-
name: "video-hook-layers",
|
|
2797
|
-
codes: { [CODES3.HOOK_LAYER_MISSING]: "warning" },
|
|
2798
|
-
check: checkHookLayers
|
|
2799
|
-
};
|
|
2800
|
-
var uiInPromptRule = {
|
|
2801
|
-
name: "video-ui-in-prompt",
|
|
2802
|
-
codes: { [CODES3.UI_IN_PROMPT]: "warning" },
|
|
2803
|
-
check: onVideoCanvas((ctx) => checkUiInPrompt(ctx))
|
|
2804
|
-
};
|
|
2805
|
-
var brandmarkInPromptRule = {
|
|
2806
|
-
name: "video-brandmark-in-prompt",
|
|
2807
|
-
codes: { [CODES3.BRANDMARK_IN_PROMPT]: "warning" },
|
|
2808
|
-
check: onVideoCanvas((ctx) => checkBrandmarkInPrompt(ctx))
|
|
2809
|
-
};
|
|
2810
|
-
var referenceCompletenessRule = {
|
|
2811
|
-
name: "video-reference-completeness",
|
|
2812
|
-
codes: { [CODES3.REFERENCE_MISSING]: "warning" },
|
|
2813
|
-
check: onVideoCanvas(checkReferenceCompleteness)
|
|
2814
|
-
};
|
|
2815
|
-
var unusableLastFrameRule = {
|
|
2816
|
-
name: "video-unusable-last-frame",
|
|
2817
|
-
codes: { [CODES3.MODEL_IGNORES_INPUT]: "warning" },
|
|
2818
|
-
check: onVideoCanvas((ctx) => checkUnusableLastFrame(ctx))
|
|
2819
|
-
};
|
|
2820
|
-
|
|
2821
|
-
// src/engine/engine/lint/rules/video-timeline.ts
|
|
2822
|
-
import { readFile } from "fs/promises";
|
|
2823
|
-
import path from "path";
|
|
2824
|
-
|
|
2825
|
-
// src/engine/lib/speech.ts
|
|
2826
|
-
var NATIVE_CLIP_WORDS_PER_SECOND = 2.5;
|
|
2827
|
-
var READ_WORDS_PER_SECOND = 3.06;
|
|
2828
|
-
function wordCount2(text) {
|
|
2829
|
-
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
2830
|
-
}
|
|
2831
|
-
function estSpeechS(text) {
|
|
2832
|
-
return wordCount2(text) / NATIVE_CLIP_WORDS_PER_SECOND;
|
|
2833
|
-
}
|
|
2834
|
-
var OBSERVED_WPS_MIN = 1;
|
|
2835
|
-
var OBSERVED_WPS_MAX = 6;
|
|
2836
|
-
function estSpeechWindowS(text, startS, endS) {
|
|
2837
|
-
const words = wordCount2(text);
|
|
2838
|
-
const window = (endS ?? 0) - (startS ?? 0);
|
|
2839
|
-
if (words > 0 && window > 0.3) {
|
|
2840
|
-
const wps = words / window;
|
|
2841
|
-
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
|
|
2842
|
-
}
|
|
2843
|
-
return estSpeechS(text);
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
// src/engine/lib/spine-input.ts
|
|
2847
|
-
function parseSpineInputLens(args) {
|
|
2848
|
-
const lens = /* @__PURE__ */ new Map();
|
|
2849
|
-
let inputIndex = 0;
|
|
2850
|
-
for (let i = 0; i < args.length; i++) {
|
|
2851
|
-
if (args[i] !== "-i") continue;
|
|
2852
|
-
if (args[i - 2] === "-t") {
|
|
2853
|
-
const v = Number(args[i - 1]);
|
|
2854
|
-
if (Number.isFinite(v)) lens.set(inputIndex, v);
|
|
2855
|
-
}
|
|
2856
|
-
inputIndex++;
|
|
2857
|
-
}
|
|
2858
|
-
const graph = args.join(" ");
|
|
2859
|
-
for (const m of graph.matchAll(/\[(\d+):v\][^;[]*?trim=(?:start=[\d.]+:)?duration=([\d.]+)/g)) {
|
|
2860
|
-
const idx = Number(m[1]);
|
|
2861
|
-
const v = Number(m[2]);
|
|
2862
|
-
if (Number.isFinite(idx) && Number.isFinite(v)) lens.set(idx, v);
|
|
2863
|
-
}
|
|
2864
|
-
return lens;
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
// src/engine/engine/lint/rules/video-timeline.ts
|
|
2868
|
-
var CODES4 = {
|
|
2869
|
-
VO_OVERLAP: "VIDEO_VO_OVERLAP",
|
|
2870
|
-
AUDIO_DURATION: "VIDEO_AUDIO_DURATION",
|
|
2871
|
-
LIPSYNC_MISSING: "VIDEO_LIPSYNC_MISSING",
|
|
2872
|
-
SPEECH_OVERRUN: "VIDEO_SPEECH_OVERRUN",
|
|
2873
|
-
SPEECH_EXCEEDS_EXTRACT: "VIDEO_SPEECH_EXCEEDS_EXTRACT",
|
|
2874
|
-
ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH",
|
|
2875
|
-
SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL",
|
|
2876
|
-
TIMELINE_TOTAL: "VIDEO_TIMELINE_TOTAL_MISMATCH",
|
|
2877
|
-
NATIVE_SEG_OVERLAP: "VIDEO_NATIVE_SEG_OVERLAP",
|
|
2878
|
-
SPINE_UNNORMALIZED: "VIDEO_SPINE_UNNORMALIZED",
|
|
2879
|
-
ODD_DIMENSIONS: "VIDEO_ODD_DIMENSIONS",
|
|
2880
|
-
REGION_DROPPED: "VIDEO_REGION_DROPPED",
|
|
2881
|
-
OVERLAY_OUT_OF_BOUNDS: "VIDEO_OVERLAY_OUT_OF_BOUNDS"
|
|
2882
|
-
};
|
|
2883
|
-
var SPAN_MODEL_SLACK_S = 0.25;
|
|
2884
|
-
var VIDEO_TIME_SLACK_S = 0.75;
|
|
2885
|
-
var SPEECH_OVERRUN_RATIO = 1.6;
|
|
2886
|
-
function ffmpegTrimSeconds(node) {
|
|
2887
|
-
const args = node.params?.args;
|
|
2888
|
-
if (!Array.isArray(args)) return null;
|
|
2889
|
-
let t = null;
|
|
2890
|
-
for (let i = 0; i < args.length - 1; i++) {
|
|
2891
|
-
if (args[i] === "-t") {
|
|
2892
|
-
const v = Number(args[i + 1]);
|
|
2893
|
-
if (Number.isFinite(v)) t = v;
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
return t;
|
|
2897
|
-
}
|
|
2898
|
-
function liveNodeDurationS(ctx, node, depth = 0) {
|
|
2899
|
-
if (!node || depth > 3) return null;
|
|
2900
|
-
if (node.type === "video_generate") {
|
|
2901
|
-
const d = node.params?.duration;
|
|
2902
|
-
return typeof d === "number" ? d : null;
|
|
2903
|
-
}
|
|
2904
|
-
if (node.type === "ffmpeg") {
|
|
2905
|
-
const t = ffmpegTrimSeconds(node);
|
|
2906
|
-
if (t !== null) return t;
|
|
2907
|
-
for (const v of Object.values(node.inputs ?? {})) {
|
|
2908
|
-
const upstream = refNodeOf(ctx, v);
|
|
2909
|
-
const d = liveNodeDurationS(ctx, upstream, depth + 1);
|
|
2910
|
-
if (d !== null) return d;
|
|
2911
|
-
}
|
|
2912
|
-
return null;
|
|
2913
|
-
}
|
|
2914
|
-
return null;
|
|
2915
|
-
}
|
|
2916
|
-
function overlayTimingIssues(html, durationS) {
|
|
2917
|
-
const out = [];
|
|
2918
|
-
for (const m of html.matchAll(/<[^>]*\bdata-start="([\d.]+)"[^>]*\bdata-dur="([\d.]+)"[^>]*>/g)) {
|
|
2919
|
-
const start = Number(m[1]);
|
|
2920
|
-
const dur = Number(m[2]);
|
|
2921
|
-
if (!Number.isFinite(start) || !Number.isFinite(dur)) continue;
|
|
2922
|
-
if (start >= durationS || start + dur > durationS + 0.25) out.push({ start, dur });
|
|
2923
|
-
}
|
|
2924
|
-
return out;
|
|
2925
|
-
}
|
|
2926
|
-
async function checkOverlayTimingBounds(canvas, issues) {
|
|
2927
|
-
const durationS = canvas.metadata?.video?.duration_s;
|
|
2928
|
-
if (typeof durationS !== "number") return;
|
|
2929
|
-
for (let i = 0; i < canvas.nodes.length; i++) {
|
|
2930
|
-
const n = canvas.nodes[i];
|
|
2931
|
-
if (n?.type !== "hyperframe_render") continue;
|
|
2932
|
-
const composition = n.params?.composition;
|
|
2933
|
-
if (typeof composition !== "string" || !path.isAbsolute(composition)) continue;
|
|
2934
|
-
const html = await readFile(path.join(composition, "index.html"), "utf8").catch(() => null);
|
|
2935
|
-
if (!html) continue;
|
|
2936
|
-
for (const w of overlayTimingIssues(html, durationS)) {
|
|
2937
|
-
issues.push({
|
|
2938
|
-
path: `nodes[${i}].params.composition`,
|
|
2939
|
-
code: CODES4.OVERLAY_OUT_OF_BOUNDS,
|
|
2940
|
-
severity: "warning",
|
|
2941
|
-
node_id: n.id,
|
|
2942
|
-
node_type: n.type,
|
|
2943
|
-
message: `an overlay in ${path.basename(composition)}/index.html runs [${w.start}s, ${w.start + w.dur}s] but the video ends at ${durationS}s \u2014 it never hides on screen. Re-time its data-start/data-dur to fit the video`
|
|
2944
|
-
});
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
}
|
|
2948
|
-
function sceneClips(ctx, scene) {
|
|
2949
|
-
if (isTagged(ctx)) return ctx.canvas.nodes.filter((n) => n.type === "video_generate" && n.tags?.scene === scene);
|
|
2950
|
-
const nativeClipRe = new RegExp(`^s${scene}(_r\\d+)?_clip$`);
|
|
2951
|
-
return ctx.canvas.nodes.filter((n) => nativeClipRe.test(n.id) && n.type === "video_generate");
|
|
2952
|
-
}
|
|
2953
|
-
function nativeAudioReachesMix(ctx, scene) {
|
|
2954
|
-
if (isTagged(ctx)) {
|
|
2955
|
-
return ctx.canvas.nodes.some(
|
|
2956
|
-
(n) => n.type === "audio_timeline" && Object.values(n.inputs ?? {}).some((v) => {
|
|
2957
|
-
const source = refNodeOf(ctx, v);
|
|
2958
|
-
return source?.tags?.scene === scene && (source.tags.role === "voice_extract" || source.tags.role === "clip");
|
|
2959
|
-
})
|
|
2960
|
-
);
|
|
2961
|
-
}
|
|
2962
|
-
const wanted = [
|
|
2963
|
-
`$ref:s${scene}_voextract.audio`,
|
|
2964
|
-
`$ref:s${scene}_clip.audio`,
|
|
2965
|
-
new RegExp(`\\$ref:s${scene}_r\\d+_(voextract|clip)\\.audio`)
|
|
2966
|
-
];
|
|
2967
|
-
return ctx.canvas.nodes.some((n) => {
|
|
2968
|
-
if (n.type !== "audio_timeline") return false;
|
|
2969
|
-
const blob = JSON.stringify(n.inputs ?? {});
|
|
2970
|
-
return wanted.some((w) => typeof w === "string" ? blob.includes(w) : w.test(blob));
|
|
2971
|
-
});
|
|
2972
|
-
}
|
|
2973
|
-
function talkingSceneSatisfied(ctx, entry, scene) {
|
|
2974
|
-
const nodes = ctx.canvas.nodes;
|
|
2975
|
-
if (typeof entry === "object" && "voice_convert_node" in entry) {
|
|
2976
|
-
const clipNativeAudio = sceneClips(ctx, scene).some((n) => {
|
|
2977
|
-
const params = n.params;
|
|
2978
|
-
if (typeof params?.model === "string" && !supportsParam("video_generate", params.model, "generate_audio")) {
|
|
2979
|
-
return true;
|
|
2980
|
-
}
|
|
2981
|
-
return params?.generate_audio === true;
|
|
2982
|
-
});
|
|
2983
|
-
const converted = nodes.some((n) => n.id === entry.voice_convert_node && n.type === "audio_voice_convert");
|
|
2984
|
-
return clipNativeAudio && (converted || nativeAudioReachesMix(ctx, scene));
|
|
2985
|
-
}
|
|
2986
|
-
if (typeof entry === "object") {
|
|
2987
|
-
return nodes.some((n) => n.id === entry.lipsync_node && n.type === "video_lipsync");
|
|
2988
|
-
}
|
|
2989
|
-
return nodes.some((n) => {
|
|
2990
|
-
if (n.type !== "video_lipsync") return false;
|
|
2991
|
-
const video = n.inputs?.video;
|
|
2992
|
-
return video === `$ref:s${scene}_trim.video` || video === `$ref:s${scene}_clip.video`;
|
|
2993
|
-
});
|
|
2994
|
-
}
|
|
2995
|
-
var TIMING_MAX_DEPTH = 4;
|
|
2996
|
-
function typedTiming(ctx, node, depth = 0) {
|
|
2997
|
-
const def = ctx.registry.get(node.type);
|
|
2998
|
-
if (!def?.staticTiming || depth > TIMING_MAX_DEPTH) return null;
|
|
2999
|
-
const parsed = def.params.safeParse(node.params ?? {});
|
|
3000
|
-
if (!parsed.success) return null;
|
|
3001
|
-
const inputs = {};
|
|
3002
|
-
for (const [slot, value] of Object.entries(node.inputs ?? {})) {
|
|
3003
|
-
const upstream = refNodeOf(ctx, value);
|
|
3004
|
-
if (!upstream) continue;
|
|
3005
|
-
const timing = typedTiming(ctx, upstream, depth + 1) ?? legacyTiming(ctx, upstream);
|
|
3006
|
-
if (timing) inputs[slot] = timing;
|
|
3007
|
-
}
|
|
3008
|
-
return def.staticTiming({ params: parsed.data, inputs });
|
|
3009
|
-
}
|
|
3010
|
-
function legacyTiming(ctx, node) {
|
|
3011
|
-
const d = liveNodeDurationS(ctx, node);
|
|
3012
|
-
return d === null ? null : { duration_s: d };
|
|
3013
|
-
}
|
|
3014
|
-
function spineIdOf(ctx, meta) {
|
|
3015
|
-
if (meta.timeline?.spine_node) return meta.timeline.spine_node;
|
|
3016
|
-
const tagged = ctx.canvas.nodes.find((n) => n.tags?.role === "spine");
|
|
3017
|
-
if (tagged) return tagged.id;
|
|
3018
|
-
return ctx.idToIndex.has("spine") ? "spine" : null;
|
|
3019
|
-
}
|
|
3020
|
-
function spineTotalS(ctx, spine) {
|
|
3021
|
-
let total = 0;
|
|
3022
|
-
const inputs = Object.entries(spine.inputs ?? {}).filter(([k]) => /^c\d+$/.test(k));
|
|
3023
|
-
if (inputs.length === 0) return null;
|
|
3024
|
-
const rawArgs = spine.params.args;
|
|
3025
|
-
const absorbed = Array.isArray(rawArgs) ? parseSpineInputLens(rawArgs.map(String)) : /* @__PURE__ */ new Map();
|
|
3026
|
-
for (const [k, v] of inputs) {
|
|
3027
|
-
const inputIdx = Number(k.slice(1));
|
|
3028
|
-
const d = absorbed.get(inputIdx) ?? liveNodeDurationS(ctx, refNodeOf(ctx, v));
|
|
3029
|
-
if (d === null) return null;
|
|
3030
|
-
total += d;
|
|
3031
|
-
}
|
|
3032
|
-
const graph = spine.params.args?.join?.(" ") ?? "";
|
|
3033
|
-
for (const m of String(graph).matchAll(/xfade=transition=[^:]+:duration=([\d.]+)/g)) {
|
|
3034
|
-
total -= Number(m[1]);
|
|
3035
|
-
}
|
|
3036
|
-
return total;
|
|
3037
|
-
}
|
|
3038
|
-
function checkTimelineContract(ctx, meta) {
|
|
3039
|
-
const stamp = meta.timeline;
|
|
3040
|
-
const spineId = spineIdOf(ctx, meta);
|
|
3041
|
-
if (!spineId) return;
|
|
3042
|
-
const expected = stamp?.expected_total_s ?? meta.duration_s;
|
|
3043
|
-
checkSpineTotal(ctx, spineId, expected);
|
|
3044
|
-
checkAudioTimelineTotals(ctx, stamp?.audio_mix_node, expected);
|
|
3045
|
-
}
|
|
3046
|
-
function checkSpineTotal(ctx, spineId, expected) {
|
|
3047
|
-
const spineIdx = ctx.idToIndex.get(spineId);
|
|
3048
|
-
const spine = spineIdx === void 0 ? void 0 : ctx.canvas.nodes[spineIdx];
|
|
3049
|
-
if (!spine || spineIdx === void 0) return;
|
|
3050
|
-
if (ctx.registry.get(spine.type)?.staticTiming) {
|
|
3051
|
-
checkTypedSpineTotal(ctx, spine, spineIdx, expected);
|
|
3052
|
-
return;
|
|
3053
|
-
}
|
|
3054
|
-
const spineLen = spineTotalS(ctx, spine);
|
|
3055
|
-
if (spineLen === null || Math.abs(spineLen - expected) <= VIDEO_TIME_SLACK_S) return;
|
|
3056
|
-
ctx.issues.push({
|
|
3057
|
-
path: `nodes[${spineIdx}].params.args`,
|
|
3058
|
-
code: CODES4.TIMELINE_TOTAL,
|
|
3059
|
-
node_id: spineId,
|
|
3060
|
-
message: `the picture sums to ${round2(spineLen)}s but the timeline is pinned to ${expected}s \u2014 \`-shortest\` at the final mux will silently cut the longer stream. Re-time the seg trims (or the audio total_ms) so both match`
|
|
3061
|
-
});
|
|
3062
|
-
}
|
|
3063
|
-
function checkTypedSpineTotal(ctx, spine, spineIdx, expected) {
|
|
3064
|
-
const spineLen = typedTiming(ctx, spine)?.duration_s;
|
|
3065
|
-
if (spineLen === void 0 || Math.abs(spineLen - expected) <= VIDEO_TIME_SLACK_S) return;
|
|
3066
|
-
ctx.issues.push({
|
|
3067
|
-
path: `nodes[${spineIdx}].params.clips`,
|
|
3068
|
-
code: CODES4.TIMELINE_TOTAL,
|
|
3069
|
-
node_id: spine.id,
|
|
3070
|
-
message: `the picture sums to ${round2(spineLen)}s but the timeline is pinned to ${expected}s. The final mux ends at the shorter stream and silently cuts the longer one. Re-time the clips' duration_s (or the audio total_ms) so both match`
|
|
3071
|
-
});
|
|
3072
|
-
}
|
|
3073
|
-
function checkAudioTimelineTotals(ctx, audioMixNode, expected) {
|
|
3074
|
-
const mustBeFullLength = /* @__PURE__ */ new Set();
|
|
3075
|
-
if (audioMixNode) mustBeFullLength.add(audioMixNode);
|
|
3076
|
-
ctx.canvas.nodes.forEach((n) => {
|
|
3077
|
-
if (n.type !== "audio_voice_convert") return;
|
|
3078
|
-
const track = refNodeOf(ctx, n.inputs?.audio);
|
|
3079
|
-
if (track?.type === "audio_timeline") mustBeFullLength.add(track.id);
|
|
3080
|
-
});
|
|
3081
|
-
for (const id of mustBeFullLength) {
|
|
3082
|
-
const idx = ctx.idToIndex.get(id);
|
|
3083
|
-
const node = idx === void 0 ? null : ctx.canvas.nodes[idx];
|
|
3084
|
-
const totalMs = node?.params?.total_ms;
|
|
3085
|
-
if (typeof totalMs !== "number") continue;
|
|
3086
|
-
if (Math.abs(totalMs / 1e3 - expected) > VIDEO_TIME_SLACK_S) {
|
|
3087
|
-
ctx.issues.push({
|
|
3088
|
-
path: `nodes[${idx}].params.total_ms`,
|
|
3089
|
-
code: CODES4.TIMELINE_TOTAL,
|
|
3090
|
-
node_id: id,
|
|
3091
|
-
message: `audio timeline "${id}" is pinned to ${totalMs}ms but the picture timeline is ${expected}s \u2014 \`-shortest\` at the final mux will silently cut the longer stream. Pin total_ms to ${Math.round(expected * 1e3)}`
|
|
3092
|
-
});
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
function checkNativeSegOverlap(ctx) {
|
|
3097
|
-
for (const conv of ctx.canvas.nodes) {
|
|
3098
|
-
if (conv.type !== "audio_voice_convert") continue;
|
|
3099
|
-
const track = refNodeOf(ctx, conv.inputs?.audio);
|
|
3100
|
-
if (track?.type !== "audio_timeline") continue;
|
|
3101
|
-
const params = track.params;
|
|
3102
|
-
const windows = (params.tracks ?? []).map((t) => {
|
|
3103
|
-
const extract = refNodeOf(ctx, track.inputs?.[t.slot]);
|
|
3104
|
-
const len = t.duration_s ?? (extract ? ffmpegTrimSeconds(extract) : null);
|
|
3105
|
-
return len === null ? null : { slot: t.slot, start: t.start_s, end: t.start_s + len };
|
|
3106
|
-
}).filter((w) => w !== null).sort((a, b) => a.start - b.start);
|
|
3107
|
-
for (let i = 1; i < windows.length; i++) {
|
|
3108
|
-
const prev = windows[i - 1];
|
|
3109
|
-
const cur = windows[i];
|
|
3110
|
-
if (!prev || !cur || cur.start >= prev.end - 0.01) continue;
|
|
3111
|
-
const trackIdx = ctx.idToIndex.get(track.id);
|
|
3112
|
-
ctx.issues.push({
|
|
3113
|
-
path: `nodes[${trackIdx}].params.tracks`,
|
|
3114
|
-
code: CODES4.NATIVE_SEG_OVERLAP,
|
|
3115
|
-
node_id: track.id,
|
|
3116
|
-
message: `voice track "${track.id}": "${prev.slot}" runs to ${round2(prev.end)}s but "${cur.slot}" starts at ${cur.start}s \u2014 both play at once (echo). Cap the first with duration_s: ${round2(cur.start - prev.start)} or re-time the windows`
|
|
3117
|
-
});
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
function checkSpineNormalized(ctx, meta) {
|
|
3122
|
-
const spineId = spineIdOf(ctx, meta);
|
|
3123
|
-
const idx = spineId === null ? void 0 : ctx.idToIndex.get(spineId);
|
|
3124
|
-
const spine = idx === void 0 ? void 0 : ctx.canvas.nodes[idx];
|
|
3125
|
-
if (!spine || spine.type !== "ffmpeg") return;
|
|
3126
|
-
const args = spine.params.args;
|
|
3127
|
-
const graph = Array.isArray(args) ? args.join(" ") : "";
|
|
3128
|
-
if (!graph.includes("concat=n=")) return;
|
|
3129
|
-
if (/\[\d+:v\](?:\[|concat)/.test(graph)) {
|
|
3130
|
-
ctx.issues.push({
|
|
3131
|
-
path: `nodes[${idx}].params.args`,
|
|
3132
|
-
code: CODES4.SPINE_UNNORMALIZED,
|
|
3133
|
-
severity: "warning",
|
|
3134
|
-
node_id: spine.id,
|
|
3135
|
-
message: `the spine concat feeds raw input labels \u2014 generated clips carry no fps/SAR guarantee, and one 24fps clip silently stretches the picture off the audio/overlay clock. Chain \`format=yuv420p,fps=30,setsar=1,settb=AVTB\` on every input before the concat`
|
|
3136
|
-
});
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
function checkOddDimensions(ctx) {
|
|
3140
|
-
ctx.canvas.nodes.forEach((node, idx) => {
|
|
3141
|
-
if (node.type !== "ffmpeg") return;
|
|
3142
|
-
const args = node.params.args;
|
|
3143
|
-
const blob = Array.isArray(args) ? args.join(" ") : "";
|
|
3144
|
-
for (const m of blob.matchAll(/(?:scale|crop|pad)=(\d+):(\d+)/g)) {
|
|
3145
|
-
const w = Number(m[1]);
|
|
3146
|
-
const h = Number(m[2]);
|
|
3147
|
-
if (w % 2 === 0 && h % 2 === 0) continue;
|
|
3148
|
-
ctx.issues.push({
|
|
3149
|
-
path: `nodes[${idx}].params.args`,
|
|
3150
|
-
code: CODES4.ODD_DIMENSIONS,
|
|
3151
|
-
severity: "warning",
|
|
3152
|
-
node_id: node.id,
|
|
3153
|
-
message: `"${m[0]}" produces an odd dimension \u2014 libx264 yuv420p rejects odd sizes, so this node fails at render time. Round both to even numbers`
|
|
3154
|
-
});
|
|
3155
|
-
return;
|
|
3156
|
-
}
|
|
3157
|
-
});
|
|
3158
|
-
}
|
|
3159
|
-
function checkRegionDropped(ctx) {
|
|
3160
|
-
if (isTagged(ctx)) {
|
|
3161
|
-
checkTaggedRegionDropped(ctx);
|
|
3162
|
-
return;
|
|
3163
|
-
}
|
|
3164
|
-
const regionClips = /* @__PURE__ */ new Map();
|
|
3165
|
-
for (const node of ctx.canvas.nodes) {
|
|
3166
|
-
const m = /^s(\d+)_r\d+_clip$/.exec(node.id);
|
|
3167
|
-
if (m && node.type === "video_generate")
|
|
3168
|
-
regionClips.set(m[1], (regionClips.get(m[1]) ?? 0) + 1);
|
|
3169
|
-
}
|
|
3170
|
-
for (const [scene, billed] of regionClips) {
|
|
3171
|
-
const idx = ctx.idToIndex.get(`s${scene}_composite`);
|
|
3172
|
-
if (idx === void 0) continue;
|
|
3173
|
-
const composite = ctx.canvas.nodes[idx];
|
|
3174
|
-
const consumed = Object.keys(composite.inputs ?? {}).filter((k) => /^c\d+$/.test(k)).length;
|
|
3175
|
-
if (billed > consumed) {
|
|
3176
|
-
ctx.issues.push({
|
|
3177
|
-
path: `nodes[${idx}].inputs`,
|
|
3178
|
-
code: CODES4.REGION_DROPPED,
|
|
3179
|
-
severity: "warning",
|
|
3180
|
-
node_id: composite.id,
|
|
3181
|
-
message: `scene s${scene} bills ${billed} region clips but its composite consumes ${consumed} \u2014 the extra generation is paid for and never reaches the frame. Wire every region into the composite or delete the unused clip node`
|
|
3182
|
-
});
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
function checkTaggedRegionDropped(ctx) {
|
|
3187
|
-
for (const composite of ctx.canvas.nodes) {
|
|
3188
|
-
const scene = composite.tags?.scene;
|
|
3189
|
-
if (composite.tags?.role !== "composite" || scene === void 0) continue;
|
|
3190
|
-
const billed = ctx.canvas.nodes.filter(
|
|
3191
|
-
(n) => n.type === "video_generate" && n.tags?.scene === scene && n.tags.region !== void 0
|
|
3192
|
-
);
|
|
3193
|
-
const reached = upstreamIds(ctx, composite);
|
|
3194
|
-
const consumed = billed.filter((n) => reached.has(n.id)).length;
|
|
3195
|
-
if (billed.length <= consumed) continue;
|
|
3196
|
-
ctx.issues.push({
|
|
3197
|
-
path: `nodes[${ctx.idToIndex.get(composite.id) ?? -1}].inputs`,
|
|
3198
|
-
code: CODES4.REGION_DROPPED,
|
|
3199
|
-
severity: "warning",
|
|
3200
|
-
node_id: composite.id,
|
|
3201
|
-
message: `scene s${scene} bills ${billed.length} region clips but its composite consumes ${consumed} \u2014 the extra generation is paid for and never reaches the frame. Wire every region into the composite or delete the unused clip node`
|
|
3202
|
-
});
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
function wiredNodes(ctx, node) {
|
|
3206
|
-
return Object.values(node.inputs ?? {}).flatMap((v) => Array.isArray(v) ? v : [v]).map((ref) => refNodeOf(ctx, ref)).filter((up) => up !== null);
|
|
3207
|
-
}
|
|
3208
|
-
function upstreamIds(ctx, node) {
|
|
3209
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3210
|
-
const stack = [node];
|
|
3211
|
-
for (let cur = stack.pop(); cur; cur = stack.pop()) {
|
|
3212
|
-
for (const up of wiredNodes(ctx, cur)) {
|
|
3213
|
-
if (seen.has(up.id)) continue;
|
|
3214
|
-
seen.add(up.id);
|
|
3215
|
-
stack.push(up);
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
return seen;
|
|
3219
|
-
}
|
|
3220
|
-
function round2(n) {
|
|
3221
|
-
return Math.round(n * 100) / 100;
|
|
3222
|
-
}
|
|
3223
|
-
function checkClipSpanFitsModel(ctx, meta) {
|
|
3224
|
-
for (const { node, span_s } of meta.clip_spans ?? []) {
|
|
3225
|
-
const target = ctx.canvas.nodes.find((n) => n.id === node && n.type === "video_generate");
|
|
3226
|
-
if (!target) continue;
|
|
3227
|
-
const model = target.params?.model;
|
|
3228
|
-
if (typeof model !== "string") continue;
|
|
3229
|
-
const max = maxClipSeconds(model);
|
|
3230
|
-
if (max === void 0 || span_s <= max + SPAN_MODEL_SLACK_S) continue;
|
|
3231
|
-
ctx.issues.push({
|
|
3232
|
-
path: `nodes[${ctx.idToIndex.get(node) ?? -1}].params.duration`,
|
|
3233
|
-
code: CODES4.SPAN_EXCEEDS_MODEL,
|
|
3234
|
-
severity: "warning",
|
|
3235
|
-
message: `scene for clip "${node}" needs ~${span_s}s but ${model} caps clips at ${max}s \u2014 the clip will truncate. Split the scene, or keep it on a model whose max duration covers the span`,
|
|
3236
|
-
node_id: node,
|
|
3237
|
-
node_type: "video_generate"
|
|
3238
|
-
});
|
|
3239
|
-
}
|
|
3240
|
-
}
|
|
3241
|
-
function secondsPerWord(stamped) {
|
|
3242
|
-
const est = stamped?.est_speech_s;
|
|
3243
|
-
const words = stamped?.speech_words;
|
|
3244
|
-
if (est && words && est > 0 && words > 0) {
|
|
3245
|
-
const wps = words / est;
|
|
3246
|
-
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return est / words;
|
|
3247
|
-
}
|
|
3248
|
-
return 1 / NATIVE_CLIP_WORDS_PER_SECOND;
|
|
3249
|
-
}
|
|
3250
|
-
function speechOverrunOf(node, secPerWord) {
|
|
3251
|
-
const params = node.params;
|
|
3252
|
-
if (params?.generate_audio !== true) return null;
|
|
3253
|
-
const line = nativeDialogueOf(params.prompt);
|
|
3254
|
-
const duration = typeof params.duration === "number" ? params.duration : void 0;
|
|
3255
|
-
if (!line || !duration) return null;
|
|
3256
|
-
const estSpeechS2 = wordCount2(line) * secPerWord;
|
|
3257
|
-
return estSpeechS2 > duration * SPEECH_OVERRUN_RATIO ? { estSpeechS: estSpeechS2, duration } : null;
|
|
3258
|
-
}
|
|
3259
|
-
function checkSpeechOverrun(ctx, talkingScenes) {
|
|
3260
|
-
for (const entry of talkingScenes) {
|
|
3261
|
-
if (typeof entry !== "object" || !("voice_convert_node" in entry)) continue;
|
|
3262
|
-
for (const n of sceneClips(ctx, entry.scene)) {
|
|
3263
|
-
const overrun = speechOverrunOf(n, secondsPerWord(entry));
|
|
3264
|
-
if (overrun) {
|
|
3265
|
-
ctx.issues.push({
|
|
3266
|
-
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
3267
|
-
code: CODES4.SPEECH_OVERRUN,
|
|
3268
|
-
message: `"${n.id}" asks Seedance to speak ~${Math.round(overrun.estSpeechS * 10) / 10}s of dialogue inside a ${overrun.duration}s clip \u2014 the line cannot fit (>${SPEECH_OVERRUN_RATIO}\xD7 the clip). Shorten the line, split the scene, or lengthen the clip duration`
|
|
3269
|
-
});
|
|
3270
|
-
continue;
|
|
3271
|
-
}
|
|
3272
|
-
checkSpeechExceedsExtract(ctx, n, entry);
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
}
|
|
3276
|
-
function sceneExtract(ctx, clip2, scene) {
|
|
3277
|
-
if (isTagged(ctx)) {
|
|
3278
|
-
const extracts = ctx.canvas.nodes.filter((n) => n.tags?.role === "voice_extract" && n.tags.scene === scene);
|
|
3279
|
-
const extract2 = extracts.find((n) => n.tags?.region === clip2.tags?.region) ?? extracts[0];
|
|
3280
|
-
return extract2 ? { extract: extract2, label: extract2.id } : null;
|
|
3281
|
-
}
|
|
3282
|
-
const regionTag = /^s\d+(_r\d+)?_clip$/.exec(clip2.id)?.[1] ?? "";
|
|
3283
|
-
const extractIdx = ctx.idToIndex.get(`s${scene}${regionTag}_voextract`) ?? ctx.idToIndex.get(`s${scene}_voextract`);
|
|
3284
|
-
const extract = extractIdx === void 0 ? void 0 : ctx.canvas.nodes[extractIdx];
|
|
3285
|
-
return extract ? { extract, label: `s${scene}_voextract` } : null;
|
|
3286
|
-
}
|
|
3287
|
-
function checkSpeechExceedsExtract(ctx, clip2, entry) {
|
|
3288
|
-
const params = clip2.params;
|
|
3289
|
-
if (params?.generate_audio !== true) return;
|
|
3290
|
-
const line = nativeDialogueOf(params.prompt);
|
|
3291
|
-
if (!line) return;
|
|
3292
|
-
const found = sceneExtract(ctx, clip2, entry.scene);
|
|
3293
|
-
const windowS = found ? ffmpegTrimSeconds(found.extract) : null;
|
|
3294
|
-
if (!found || windowS === null) return;
|
|
3295
|
-
const estSpeechS2 = wordCount2(line) * secondsPerWord(entry);
|
|
3296
|
-
if (estSpeechS2 <= windowS * SPEECH_OVERRUN_RATIO) return;
|
|
3297
|
-
ctx.issues.push({
|
|
3298
|
-
path: `nodes[${ctx.idToIndex.get(clip2.id) ?? -1}].params.prompt`,
|
|
3299
|
-
code: CODES4.SPEECH_EXCEEDS_EXTRACT,
|
|
3300
|
-
node_id: clip2.id,
|
|
3301
|
-
message: `"${clip2.id}"'s line is ~${Math.round(estSpeechS2 * 10) / 10}s of speech but its extract window (${found.label}) is ${windowS}s \u2014 the read gets cut mid-word on the spine. Shorten the Dialogue line, lengthen the scene in prompt.json and re-scaffold, or raise the voextract \`-t\` when the read should carry over the next cutaway`
|
|
3302
|
-
});
|
|
3303
|
-
}
|
|
3304
|
-
function checkAspectConsistency(ctx) {
|
|
3305
|
-
const clips = ctx.canvas.nodes.filter((n) => n.type === "video_generate");
|
|
3306
|
-
if (clips.length < 2) return;
|
|
3307
|
-
const ratios = new Set(clips.map((n) => String(n.params?.aspect_ratio ?? "(unset)")));
|
|
3308
|
-
if (ratios.size <= 1) return;
|
|
3309
|
-
ctx.issues.push({
|
|
3310
|
-
path: "nodes",
|
|
3311
|
-
code: CODES4.ASPECT_MISMATCH,
|
|
3312
|
-
message: `video_generate nodes disagree on aspect_ratio (${[...ratios].sort().join(", ")}) \u2014 the spine's segments would render at different shapes and the composite silently crops. Pin every clip to the same aspect_ratio`
|
|
3313
|
-
});
|
|
3314
|
-
}
|
|
3315
|
-
function sortedVoSegments(meta) {
|
|
3316
|
-
return [...meta.vo_segments].sort((a, b) => a.start_s - b.start_s);
|
|
3317
|
-
}
|
|
3318
|
-
function checkVoOverlap(ctx, meta) {
|
|
3319
|
-
const segments = sortedVoSegments(meta);
|
|
3320
|
-
for (let i = 1; i < segments.length; i++) {
|
|
3321
|
-
const prev = segments[i - 1];
|
|
3322
|
-
const cur = segments[i];
|
|
3323
|
-
if (!prev || !cur) continue;
|
|
3324
|
-
if (cur.start_s < prev.end_s - VIDEO_TIME_SLACK_S) {
|
|
3325
|
-
ctx.issues.push({
|
|
3326
|
-
path: "metadata.video.vo_segments",
|
|
3327
|
-
code: CODES4.VO_OVERLAP,
|
|
3328
|
-
message: `voiceover turns overlap: "${prev.slot}" runs to ${prev.end_s}s but "${cur.slot}" starts at ${cur.start_s}s \u2014 sequence them so dialogue alternates instead of stacking`
|
|
3329
|
-
});
|
|
3330
|
-
}
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
function checkAudioDuration(ctx, meta) {
|
|
3334
|
-
const audioEnd = sortedVoSegments(meta).reduce((m, s) => Math.max(m, s.end_s), 0);
|
|
3335
|
-
if (audioEnd > meta.duration_s + VIDEO_TIME_SLACK_S) {
|
|
3336
|
-
ctx.issues.push({
|
|
3337
|
-
path: "metadata.video.duration_s",
|
|
3338
|
-
code: CODES4.AUDIO_DURATION,
|
|
3339
|
-
message: `audio runs to ${audioEnd}s but the video is ${meta.duration_s}s \u2014 trim the voiceover or extend the video so audio \u2248 video length`
|
|
3340
|
-
});
|
|
3341
|
-
}
|
|
3342
|
-
}
|
|
3343
|
-
function checkTalkingScenes(ctx, meta) {
|
|
3344
|
-
for (const entry of meta.talking_scenes) {
|
|
3345
|
-
const scene = typeof entry === "number" ? entry : entry.scene;
|
|
3346
|
-
if (!talkingSceneSatisfied(ctx, entry, scene)) {
|
|
3347
|
-
ctx.issues.push({
|
|
3348
|
-
path: "metadata.video.talking_scenes",
|
|
3349
|
-
code: CODES4.LIPSYNC_MISSING,
|
|
3350
|
-
message: `scene ${scene} is a single-on-camera-speaker talking head but its clip lacks native audio (generate_audio: true) \u2014 the lips would drift. Generate the clip with native audio, then route it to the mix via either the audio_voice_convert node (one brand voice) or directly (s${scene}_voextract \u2192 audio_mix)`
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
var voOverlapRule = {
|
|
3356
|
-
name: "video-vo-overlap",
|
|
3357
|
-
codes: { [CODES4.VO_OVERLAP]: "error" },
|
|
3358
|
-
check: onVideoCanvas(checkVoOverlap)
|
|
3359
|
-
};
|
|
3360
|
-
var audioDurationRule = {
|
|
3361
|
-
name: "video-audio-duration",
|
|
3362
|
-
codes: { [CODES4.AUDIO_DURATION]: "error" },
|
|
3363
|
-
check: onVideoCanvas(checkAudioDuration)
|
|
3364
|
-
};
|
|
3365
|
-
var talkingScenesRule = {
|
|
3366
|
-
name: "video-talking-scenes",
|
|
3367
|
-
codes: { [CODES4.LIPSYNC_MISSING]: "error" },
|
|
3368
|
-
check: onVideoCanvas(checkTalkingScenes)
|
|
3369
|
-
};
|
|
3370
|
-
var speechOverrunRule = {
|
|
3371
|
-
name: "video-speech-overrun",
|
|
3372
|
-
codes: { [CODES4.SPEECH_OVERRUN]: "error", [CODES4.SPEECH_EXCEEDS_EXTRACT]: "error" },
|
|
3373
|
-
check: onVideoCanvas((ctx, meta) => checkSpeechOverrun(ctx, meta.talking_scenes))
|
|
3374
|
-
};
|
|
3375
|
-
var aspectConsistencyRule = {
|
|
3376
|
-
name: "video-aspect-consistency",
|
|
3377
|
-
codes: { [CODES4.ASPECT_MISMATCH]: "error" },
|
|
3378
|
-
check: onVideoCanvas((ctx) => checkAspectConsistency(ctx))
|
|
3379
|
-
};
|
|
3380
|
-
var clipSpanRule = {
|
|
3381
|
-
name: "video-clip-span",
|
|
3382
|
-
codes: { [CODES4.SPAN_EXCEEDS_MODEL]: "warning" },
|
|
3383
|
-
check: onVideoCanvas(checkClipSpanFitsModel)
|
|
3384
|
-
};
|
|
3385
|
-
var timelineContractRule = {
|
|
3386
|
-
name: "video-timeline-contract",
|
|
3387
|
-
codes: { [CODES4.TIMELINE_TOTAL]: "error" },
|
|
3388
|
-
check: onVideoCanvas(checkTimelineContract)
|
|
3389
|
-
};
|
|
3390
|
-
var nativeSegOverlapRule = {
|
|
3391
|
-
name: "video-native-seg-overlap",
|
|
3392
|
-
codes: { [CODES4.NATIVE_SEG_OVERLAP]: "error" },
|
|
3393
|
-
check: onVideoCanvas((ctx) => checkNativeSegOverlap(ctx))
|
|
3394
|
-
};
|
|
3395
|
-
var spineNormalizedRule = {
|
|
3396
|
-
name: "video-spine-normalized",
|
|
3397
|
-
codes: { [CODES4.SPINE_UNNORMALIZED]: "warning" },
|
|
3398
|
-
check: onVideoCanvas(checkSpineNormalized)
|
|
3399
|
-
};
|
|
3400
|
-
var oddDimensionsRule = {
|
|
3401
|
-
name: "video-odd-dimensions",
|
|
3402
|
-
codes: { [CODES4.ODD_DIMENSIONS]: "warning" },
|
|
3403
|
-
check: onVideoCanvas((ctx) => checkOddDimensions(ctx))
|
|
3404
|
-
};
|
|
3405
|
-
var regionDroppedRule = {
|
|
3406
|
-
name: "video-region-dropped",
|
|
3407
|
-
codes: { [CODES4.REGION_DROPPED]: "warning" },
|
|
3408
|
-
check: onVideoCanvas((ctx) => checkRegionDropped(ctx))
|
|
3409
|
-
};
|
|
3410
|
-
var overlayTimingRule = {
|
|
3411
|
-
name: "video-overlay-timing",
|
|
3412
|
-
codes: { [CODES4.OVERLAY_OUT_OF_BOUNDS]: "warning" },
|
|
3413
|
-
check: checkOverlayTimingBounds
|
|
3414
|
-
};
|
|
3415
|
-
|
|
3416
|
-
// src/engine/engine/lint/registry.ts
|
|
3417
|
-
var LINT_RULES = [
|
|
3418
|
-
parallelFormatRendersRule,
|
|
3419
|
-
audioBedMixRule,
|
|
3420
|
-
promptProfileRule,
|
|
3421
|
-
imageProfileRule,
|
|
3422
|
-
personGenerationRule,
|
|
3423
|
-
promptBudgetRule,
|
|
3424
|
-
rawFaceKeyframeRule,
|
|
3425
|
-
promptDecisionsRule,
|
|
3426
|
-
hookLayersRule,
|
|
3427
|
-
voOverlapRule,
|
|
3428
|
-
audioDurationRule,
|
|
3429
|
-
talkingScenesRule,
|
|
3430
|
-
speechOverrunRule,
|
|
3431
|
-
aspectConsistencyRule,
|
|
3432
|
-
uiInPromptRule,
|
|
3433
|
-
brandmarkInPromptRule,
|
|
3434
|
-
referenceCompletenessRule,
|
|
3435
|
-
clipSpanRule,
|
|
3436
|
-
timelineContractRule,
|
|
3437
|
-
unusableLastFrameRule,
|
|
3438
|
-
nativeSegOverlapRule,
|
|
3439
|
-
spineNormalizedRule,
|
|
3440
|
-
oddDimensionsRule,
|
|
3441
|
-
regionDroppedRule
|
|
3442
|
-
];
|
|
3443
|
-
var DEEP_LINT_RULES = [overlayTimingRule];
|
|
3444
|
-
function assertDeclared(rule, raised) {
|
|
3445
|
-
for (const issue of raised) {
|
|
3446
|
-
const declared = rule.codes[issue.code];
|
|
3447
|
-
const severity = issue.severity ?? "error";
|
|
3448
|
-
if (declared !== severity) {
|
|
3449
|
-
throw new Error(
|
|
3450
|
-
`lint rule "${rule.name}" raised ${issue.code} as ${severity}, but declares ${declared ?? "no such code"}`
|
|
3451
|
-
);
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
function runLintRules(ctx) {
|
|
3456
|
-
for (const rule of LINT_RULES) {
|
|
3457
|
-
const before = ctx.issues.length;
|
|
3458
|
-
rule.check(ctx);
|
|
3459
|
-
assertDeclared(rule, ctx.issues.slice(before));
|
|
3460
|
-
}
|
|
3461
|
-
}
|
|
3462
|
-
async function runDeepLintRules(canvas, issues) {
|
|
3463
|
-
for (const rule of DEEP_LINT_RULES) {
|
|
3464
|
-
const before = issues.length;
|
|
3465
|
-
await rule.check(canvas, issues);
|
|
3466
|
-
assertDeclared(rule, issues.slice(before));
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
// src/engine/engine/prompt-limit.ts
|
|
3471
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
3472
|
-
import path2 from "path";
|
|
3473
|
-
var PROMPT_OVER_MODEL_LIMIT = "PROMPT_OVER_MODEL_LIMIT";
|
|
3474
|
-
async function staticTextOf(value, nodes) {
|
|
3475
|
-
if (typeof value !== "string") return void 0;
|
|
3476
|
-
if (!value.startsWith(REF_PREFIX)) return value;
|
|
3477
|
-
const expr = parseRefExpr(value);
|
|
3478
|
-
if (!expr || expr.kind !== "ref" || expr.index !== void 0) return void 0;
|
|
3479
|
-
const target = nodes.get(expr.nodeId);
|
|
3480
|
-
const params = target?.params ?? {};
|
|
3481
|
-
if (target?.type === "text" && expr.output === "text" && typeof params.value === "string") return params.value;
|
|
3482
|
-
if (target?.type === "ingest" && expr.output === "asset" && params.source === "path" && (params.expect === "json" || params.expect === "text") && typeof params.path === "string" && path2.isAbsolute(params.path)) {
|
|
3483
|
-
try {
|
|
3484
|
-
return await readFile2(params.path, "utf8");
|
|
3485
|
-
} catch {
|
|
3486
|
-
return void 0;
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
return void 0;
|
|
3490
|
-
}
|
|
3491
|
-
function modelStringCaps(nodeType, params) {
|
|
3492
|
-
if (!isManagedNodeKind(nodeType)) return [];
|
|
3493
|
-
const model = params?.model;
|
|
3494
|
-
if (typeof model !== "string") return [];
|
|
3495
|
-
const spec = MODEL_REGISTRY[nodeType][model];
|
|
3496
|
-
if (!spec) return [];
|
|
3497
|
-
return Object.entries(spec.params).flatMap(
|
|
3498
|
-
([key, schema]) => schema.kind === "string" && schema.maxLength !== void 0 ? [{ key, max: schema.maxLength, model }] : []
|
|
3499
|
-
);
|
|
3500
|
-
}
|
|
3501
|
-
function renderedPromptOverLimit(nodeId, nodeType, renderedParams) {
|
|
3502
|
-
const params = renderedParams ?? {};
|
|
3503
|
-
return modelStringCaps(nodeType, params).flatMap(({ key, max, model }) => {
|
|
3504
|
-
const value = params[key];
|
|
3505
|
-
if (typeof value !== "string" || value.length <= max) return [];
|
|
3506
|
-
return [
|
|
3507
|
-
`"${nodeId}" ${key} is ${value.length} characters once its slots are filled, ${value.length - max} over the ${max} that "${model}" accepts. Shorten what feeds its {{slots}} (or the prompt around them) and run again; nothing was sent to the model.`
|
|
3508
|
-
];
|
|
3509
|
-
});
|
|
3510
|
-
}
|
|
3511
|
-
async function filledParam(n, raw, nodes) {
|
|
3512
|
-
const template = await staticTextOf(raw, nodes);
|
|
3513
|
-
if (template === void 0) return void 0;
|
|
3514
|
-
const names = extractSlots(template);
|
|
3515
|
-
if (names.length === 0 && template === raw) return void 0;
|
|
3516
|
-
const inputs = n.inputs ?? {};
|
|
3517
|
-
const slots = {};
|
|
3518
|
-
for (const name of names) {
|
|
3519
|
-
const text = await staticTextOf(inputs[name], nodes);
|
|
3520
|
-
if (text !== void 0) slots[name] = text;
|
|
3521
|
-
}
|
|
3522
|
-
return { length: renderTemplate(template, slots).length, slots };
|
|
3523
|
-
}
|
|
3524
|
-
function largestSlotNote(slots) {
|
|
3525
|
-
const largest = Object.entries(slots).sort((a, b) => b[1].length - a[1].length)[0];
|
|
3526
|
-
return largest ? `; {{${largest[0]}}} alone is ${largest[1].length}` : "";
|
|
3527
|
-
}
|
|
3528
|
-
async function filledPromptLimitIssues(canvas) {
|
|
3529
|
-
const nodes = new Map(canvas.nodes.map((n) => [n.id, n]));
|
|
3530
|
-
const issues = [];
|
|
3531
|
-
for (const [i, n] of canvas.nodes.entries()) {
|
|
3532
|
-
for (const { key, max, model } of modelStringCaps(n.type, n.params)) {
|
|
3533
|
-
const filled = await filledParam(n, n.params?.[key], nodes);
|
|
3534
|
-
if (!filled || filled.length <= max) continue;
|
|
3535
|
-
issues.push({
|
|
3536
|
-
path: `nodes[${i}].params.${key}`,
|
|
3537
|
-
code: PROMPT_OVER_MODEL_LIMIT,
|
|
3538
|
-
message: `"${n.id}" ${key} is ${filled.length} characters once its slots are filled \u2014 ${filled.length - max} over the ${max} that "${model}" accepts` + largestSlotNote(filled.slots) + `. Shorten it before running: the model refuses it, after every node before "${n.id}" has already been paid for.`,
|
|
3539
|
-
received: filled.length,
|
|
3540
|
-
expected: `<= ${max} characters`,
|
|
3541
|
-
node_id: n.id,
|
|
3542
|
-
node_type: n.type
|
|
3543
|
-
});
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
return issues;
|
|
3547
|
-
}
|
|
3548
|
-
|
|
3549
|
-
// src/engine/engine/scheduler.ts
|
|
3550
|
-
function topologicalLayers(graph) {
|
|
3551
|
-
const remaining = cloneGraph(graph);
|
|
3552
|
-
const layers = [];
|
|
3553
|
-
while (remaining.size > 0) {
|
|
3554
|
-
const ready = collectReady(remaining);
|
|
3555
|
-
if (ready.length === 0) {
|
|
3556
|
-
throw new Error(`scheduler: cycle detected among ${[...remaining.keys()].join(", ")}`);
|
|
3557
|
-
}
|
|
3558
|
-
layers.push(ready);
|
|
3559
|
-
removeNodes(remaining, ready);
|
|
3560
|
-
}
|
|
3561
|
-
return layers;
|
|
3562
|
-
}
|
|
3563
|
-
function reachableNodes(graph, roots) {
|
|
3564
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
3565
|
-
const stack = [...roots];
|
|
3566
|
-
while (stack.length > 0) {
|
|
3567
|
-
const id = stack.pop();
|
|
3568
|
-
if (id === void 0 || reachable.has(id) || !graph.has(id)) continue;
|
|
3569
|
-
reachable.add(id);
|
|
3570
|
-
for (const dep of graph.get(id) ?? []) stack.push(dep);
|
|
3571
|
-
}
|
|
3572
|
-
return reachable;
|
|
3573
|
-
}
|
|
3574
|
-
function subgraph(graph, keep) {
|
|
3575
|
-
const out = /* @__PURE__ */ new Map();
|
|
3576
|
-
for (const [id, deps] of graph) {
|
|
3577
|
-
if (!keep.has(id)) continue;
|
|
3578
|
-
out.set(id, new Set([...deps].filter((d) => keep.has(d))));
|
|
3579
|
-
}
|
|
3580
|
-
return out;
|
|
3581
|
-
}
|
|
3582
|
-
function cloneGraph(graph) {
|
|
3583
|
-
const out = /* @__PURE__ */ new Map();
|
|
3584
|
-
for (const [k, deps] of graph) out.set(k, new Set(deps));
|
|
3585
|
-
return out;
|
|
3586
|
-
}
|
|
3587
|
-
function collectReady(remaining) {
|
|
3588
|
-
const ready = [];
|
|
3589
|
-
for (const [id, deps] of remaining) if (deps.size === 0) ready.push(id);
|
|
3590
|
-
ready.sort();
|
|
3591
|
-
return ready;
|
|
3592
|
-
}
|
|
3593
|
-
function removeNodes(remaining, ready) {
|
|
3594
|
-
for (const id of ready) remaining.delete(id);
|
|
3595
|
-
for (const deps of remaining.values()) {
|
|
3596
|
-
for (const id of ready) deps.delete(id);
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3599
|
-
function findCycle(graph) {
|
|
3600
|
-
const reverseAdj = buildReverseAdj(graph);
|
|
3601
|
-
const color = /* @__PURE__ */ new Map();
|
|
3602
|
-
for (const k of graph.keys()) color.set(k, 0);
|
|
3603
|
-
const stack = [];
|
|
3604
|
-
for (const id of graph.keys()) {
|
|
3605
|
-
if ((color.get(id) ?? 0) !== 0) continue;
|
|
3606
|
-
const found = dfsCycle(id, color, stack, reverseAdj);
|
|
3607
|
-
if (found) return found;
|
|
3608
|
-
}
|
|
3609
|
-
return null;
|
|
3610
|
-
}
|
|
3611
|
-
function buildReverseAdj(graph) {
|
|
3612
|
-
const reverseAdj = /* @__PURE__ */ new Map();
|
|
3613
|
-
for (const [node, deps] of graph) {
|
|
3614
|
-
for (const dep of deps) {
|
|
3615
|
-
const arr = reverseAdj.get(dep) ?? [];
|
|
3616
|
-
arr.push(node);
|
|
3617
|
-
reverseAdj.set(dep, arr);
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
return reverseAdj;
|
|
3621
|
-
}
|
|
3622
|
-
function dfsCycle(u, color, stack, reverseAdj) {
|
|
3623
|
-
color.set(u, 1);
|
|
3624
|
-
stack.push(u);
|
|
3625
|
-
const children = reverseAdj.get(u) ?? [];
|
|
3626
|
-
for (const v of children) {
|
|
3627
|
-
const c = color.get(v) ?? 0;
|
|
3628
|
-
if (c === 1) {
|
|
3629
|
-
const idx = stack.indexOf(v);
|
|
3630
|
-
return [...stack.slice(idx), v];
|
|
3631
|
-
}
|
|
3632
|
-
if (c === 0) {
|
|
3633
|
-
const r = dfsCycle(v, color, stack, reverseAdj);
|
|
3634
|
-
if (r) return r;
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
color.set(u, 2);
|
|
3638
|
-
stack.pop();
|
|
3639
|
-
return null;
|
|
3640
|
-
}
|
|
3641
|
-
|
|
3642
|
-
// src/engine/engine/validator.ts
|
|
3643
|
-
var MODEL_INPUT_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio"]);
|
|
3644
|
-
var STAGE_CODES = {
|
|
3645
|
-
PARSE: "PARSE_ERROR",
|
|
3646
|
-
SHAPE: "SHAPE_ERROR",
|
|
3647
|
-
UNKNOWN_NODE: "UNKNOWN_NODE_TYPE",
|
|
3648
|
-
PARAMS: "INVALID_PARAMS",
|
|
3649
|
-
PARAMS_EXTRA: "INVALID_PARAMS_EXTRA",
|
|
3650
|
-
MODEL_PARAMS: "MODEL_INVALID_PARAMS",
|
|
3651
|
-
MODEL_INPUTS: "MODEL_INVALID_INPUTS",
|
|
3652
|
-
REF_UNRESOLVED: "UNRESOLVED_REF",
|
|
3653
|
-
REF_OUT_OF_BOUNDS: "REF_INDEX_OUT_OF_BOUNDS",
|
|
3654
|
-
REF_TYPE: "REF_TYPE_MISMATCH",
|
|
3655
|
-
DUPLICATE_ID: "DUPLICATE_NODE_ID",
|
|
3656
|
-
CYCLE: "GRAPH_CYCLE",
|
|
3657
|
-
SLOT: "PROMPT_SLOT_UNRESOLVED",
|
|
3658
|
-
OUTPUT: "OUTPUT_REF_INVALID",
|
|
3659
|
-
ORPHAN_NODE: "ORPHAN_NODE",
|
|
3660
|
-
INGEST_PATH_URL_HEALED: "INGEST_PATH_URL_HEALED"
|
|
3661
|
-
};
|
|
3662
|
-
function validateCanvas(input, registry) {
|
|
3663
|
-
const issues = [];
|
|
3664
|
-
const shape = CanvasSchema.safeParse(input);
|
|
3665
|
-
if (!shape.success) {
|
|
3666
|
-
pushZodIssues(issues, shape.error, "", STAGE_CODES.SHAPE);
|
|
3667
|
-
return { ok: false, issues };
|
|
3668
|
-
}
|
|
3669
|
-
const canvas = shape.data;
|
|
3670
|
-
const idToIndex = buildIdToIndex(canvas);
|
|
3671
|
-
const ctx = { canvas, registry, issues, idToIndex };
|
|
3672
|
-
checkDuplicateIds(ctx);
|
|
3673
|
-
checkUnknownNodeTypes(ctx);
|
|
3674
|
-
if (issues.some((i) => i.code === STAGE_CODES.UNKNOWN_NODE)) return { ok: false, issues };
|
|
3675
|
-
checkAllParams(ctx);
|
|
3676
|
-
checkIngestPathUrlHeals(ctx);
|
|
3677
|
-
checkAllModelParams(ctx);
|
|
3678
|
-
checkAllRefs(ctx);
|
|
3679
|
-
checkAllModelInputs(ctx);
|
|
3680
|
-
checkCycle(ctx);
|
|
3681
|
-
checkAllSlots(ctx);
|
|
3682
|
-
const estimatedCredits = sumCredits(estimateCanvasCredits(canvas, registry));
|
|
3683
|
-
checkOutputRef(ctx);
|
|
3684
|
-
checkOrphanNodes(ctx);
|
|
3685
|
-
runLintRules(ctx);
|
|
3686
|
-
const hasBlocking = issues.some(isBlocking);
|
|
3687
|
-
if (hasBlocking) return { ok: false, issues };
|
|
3688
|
-
const warnings = issues.filter((i) => !isBlocking(i));
|
|
3689
|
-
return { ok: true, canvas, estimatedCredits, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
3690
|
-
}
|
|
3691
|
-
async function validateCanvasDeep(input, registry) {
|
|
3692
|
-
const shallow = validateCanvas(input, registry);
|
|
3693
|
-
if (!shallow.ok) return shallow;
|
|
3694
|
-
const canvas = shallow.canvas;
|
|
3695
|
-
const issues = [];
|
|
3696
|
-
const ctx = { canvas, registry, issues, idToIndex: buildIdToIndex(canvas) };
|
|
3697
|
-
const inputKindOf = (value) => staticInputKind(ctx, value);
|
|
3698
|
-
const inputSourceOf = (value) => staticInputSource(ctx, value);
|
|
3699
|
-
for (let i = 0; i < canvas.nodes.length; i++) {
|
|
3700
|
-
const n = canvas.nodes[i];
|
|
3701
|
-
if (!n) continue;
|
|
3702
|
-
const def = registry.get(n.type);
|
|
3703
|
-
if (!def?.validateExtra) continue;
|
|
3704
|
-
let extra;
|
|
3705
|
-
try {
|
|
3706
|
-
extra = await def.validateExtra({
|
|
3707
|
-
rawParams: n.params,
|
|
3708
|
-
rawInputs: n.inputs,
|
|
3709
|
-
nodeId: n.id,
|
|
3710
|
-
inputKindOf,
|
|
3711
|
-
inputSourceOf
|
|
3712
|
-
});
|
|
3713
|
-
} catch (e) {
|
|
3714
|
-
issues.push({
|
|
3715
|
-
path: `nodes[${i}].params`,
|
|
3716
|
-
code: STAGE_CODES.PARAMS_EXTRA,
|
|
3717
|
-
message: e.message,
|
|
3718
|
-
node_id: n.id,
|
|
3719
|
-
node_type: n.type
|
|
3720
|
-
});
|
|
3721
|
-
continue;
|
|
3722
|
-
}
|
|
3723
|
-
for (const item of extra) {
|
|
3724
|
-
issues.push({
|
|
3725
|
-
path: `nodes[${i}].${item.path}`,
|
|
3726
|
-
code: STAGE_CODES.PARAMS_EXTRA,
|
|
3727
|
-
message: item.message,
|
|
3728
|
-
node_id: n.id,
|
|
3729
|
-
node_type: n.type
|
|
3730
|
-
});
|
|
3731
|
-
}
|
|
3732
|
-
}
|
|
3733
|
-
await runDeepLintRules(canvas, issues);
|
|
3734
|
-
issues.push(...await filledPromptLimitIssues(canvas));
|
|
3735
|
-
const hasBlocking = issues.some(isBlocking);
|
|
3736
|
-
if (hasBlocking) return { ok: false, issues };
|
|
3737
|
-
const warnings = [...shallow.warnings ?? [], ...issues.filter((i) => !isBlocking(i))];
|
|
3738
|
-
const perNodeCredits = estimateCanvasCredits(canvas, registry);
|
|
3739
|
-
return {
|
|
3740
|
-
ok: true,
|
|
3741
|
-
canvas,
|
|
3742
|
-
estimatedCredits: shallow.estimatedCredits,
|
|
3743
|
-
perNodeCredits,
|
|
3744
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
3745
|
-
};
|
|
3746
|
-
}
|
|
3747
|
-
function buildIdToIndex(canvas) {
|
|
3748
|
-
const m = /* @__PURE__ */ new Map();
|
|
3749
|
-
for (let i = 0; i < canvas.nodes.length; i++) {
|
|
3750
|
-
const n = canvas.nodes[i];
|
|
3751
|
-
if (n) m.set(n.id, i);
|
|
3752
|
-
}
|
|
3753
|
-
return m;
|
|
3754
|
-
}
|
|
3755
|
-
function checkDuplicateIds(ctx) {
|
|
3756
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3757
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3758
|
-
const n = ctx.canvas.nodes[i];
|
|
3759
|
-
if (!n) continue;
|
|
3760
|
-
if (seen.has(n.id)) {
|
|
3761
|
-
ctx.issues.push({
|
|
3762
|
-
path: `nodes[${i}].id`,
|
|
3763
|
-
code: STAGE_CODES.DUPLICATE_ID,
|
|
3764
|
-
message: `duplicate node id "${n.id}"`,
|
|
3765
|
-
node_id: n.id
|
|
3766
|
-
});
|
|
3767
|
-
}
|
|
3768
|
-
seen.add(n.id);
|
|
3769
|
-
}
|
|
3770
|
-
}
|
|
3771
|
-
function checkUnknownNodeTypes(ctx) {
|
|
3772
|
-
const known = ctx.registry.ids();
|
|
3773
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3774
|
-
const n = ctx.canvas.nodes[i];
|
|
3775
|
-
if (!n || ctx.registry.has(n.type)) continue;
|
|
3776
|
-
ctx.issues.push({
|
|
3777
|
-
path: `nodes[${i}].type`,
|
|
3778
|
-
code: STAGE_CODES.UNKNOWN_NODE,
|
|
3779
|
-
message: `unknown node type "${n.type}"`,
|
|
3780
|
-
received: n.type,
|
|
3781
|
-
did_you_mean: didYouMean(n.type, known),
|
|
3782
|
-
node_id: n.id,
|
|
3783
|
-
node_type: n.type
|
|
3784
|
-
});
|
|
3785
|
-
}
|
|
3786
|
-
}
|
|
3787
|
-
function checkAllParams(ctx) {
|
|
3788
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3789
|
-
const n = ctx.canvas.nodes[i];
|
|
3790
|
-
if (!n) continue;
|
|
3791
|
-
const def = ctx.registry.get(n.type);
|
|
3792
|
-
if (!def) continue;
|
|
3793
|
-
const parsed = def.params.safeParse(n.params ?? {});
|
|
3794
|
-
if (parsed.success) continue;
|
|
3795
|
-
pushZodIssues(ctx.issues, parsed.error, `nodes[${i}].params`, STAGE_CODES.PARAMS, n.id, n.type);
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
function checkIngestPathUrlHeals(ctx) {
|
|
3799
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3800
|
-
const n = ctx.canvas.nodes[i];
|
|
3801
|
-
if (!n || n.type !== "ingest") continue;
|
|
3802
|
-
const params = n.params ?? {};
|
|
3803
|
-
if (params.source !== "path" || !looksLikeHttpUrl(params.path)) continue;
|
|
3804
|
-
ctx.issues.push({
|
|
3805
|
-
path: `nodes[${i}].params.source`,
|
|
3806
|
-
code: STAGE_CODES.INGEST_PATH_URL_HEALED,
|
|
3807
|
-
message: `ingest "${n.id}" declares source:"path" but its value is a URL \u2014 running it as source:"url". Update the canvas JSON to source:"url" so it matches what baker runs.`,
|
|
3808
|
-
severity: "warning",
|
|
3809
|
-
node_id: n.id,
|
|
3810
|
-
node_type: n.type
|
|
3811
|
-
});
|
|
3812
|
-
}
|
|
3813
|
-
}
|
|
3814
|
-
function checkAllModelParams(ctx) {
|
|
3815
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3816
|
-
const n = ctx.canvas.nodes[i];
|
|
3817
|
-
if (!n || !isManagedNodeKind(n.type)) continue;
|
|
3818
|
-
const params = n.params ?? {};
|
|
3819
|
-
const model = params.model;
|
|
3820
|
-
if (typeof model !== "string" || model.length === 0) continue;
|
|
3821
|
-
const r = validateModelParams(n.type, model, params);
|
|
3822
|
-
if (r.ok) continue;
|
|
3823
|
-
for (const err of r.errors) {
|
|
3824
|
-
ctx.issues.push({
|
|
3825
|
-
path: `nodes[${i}].params${"param" in err ? `.${err.param}` : ""}`,
|
|
3826
|
-
code: STAGE_CODES.MODEL_PARAMS,
|
|
3827
|
-
message: err.message,
|
|
3828
|
-
node_id: n.id,
|
|
3829
|
-
node_type: n.type
|
|
3830
|
-
});
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
}
|
|
3834
|
-
function checkAllRefs(ctx) {
|
|
3835
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3836
|
-
const n = ctx.canvas.nodes[i];
|
|
3837
|
-
if (!n) continue;
|
|
3838
|
-
walkRefs(n.inputs, (refStr, jsonPath) => {
|
|
3839
|
-
checkOneRef(ctx, n, i, refStr, jsonPath, "inputs");
|
|
3840
|
-
});
|
|
3841
|
-
walkRefs(n.params, (refStr, jsonPath) => {
|
|
3842
|
-
checkOneRef(ctx, n, i, refStr, jsonPath, "params");
|
|
3843
|
-
});
|
|
3844
|
-
}
|
|
3845
|
-
}
|
|
3846
|
-
function checkAllModelInputs(ctx) {
|
|
3847
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3848
|
-
const n = ctx.canvas.nodes[i];
|
|
3849
|
-
if (!n || !isManagedNodeKind(n.type)) continue;
|
|
3850
|
-
const params = n.params ?? {};
|
|
3851
|
-
const model = params.model;
|
|
3852
|
-
if (typeof model !== "string" || model.length === 0) continue;
|
|
3853
|
-
const providedKinds = collectProvidedKinds(ctx, n);
|
|
3854
|
-
const r = validateModelInputs(n.type, model, providedKinds);
|
|
3855
|
-
if (r.ok) continue;
|
|
3856
|
-
for (const err of r.errors) {
|
|
3857
|
-
ctx.issues.push({
|
|
3858
|
-
path: `nodes[${i}].inputs`,
|
|
3859
|
-
code: STAGE_CODES.MODEL_INPUTS,
|
|
3860
|
-
message: err.message,
|
|
3861
|
-
node_id: n.id,
|
|
3862
|
-
node_type: n.type
|
|
3863
|
-
});
|
|
3864
|
-
}
|
|
3865
|
-
}
|
|
3866
|
-
}
|
|
3867
|
-
function collectProvidedKinds(ctx, n) {
|
|
3868
|
-
const kinds = /* @__PURE__ */ new Set();
|
|
3869
|
-
walkRefs(n.inputs, (refStr) => {
|
|
3870
|
-
const kind = resolveRefKind(ctx, refStr);
|
|
3871
|
-
if (kind) kinds.add(kind);
|
|
3872
|
-
});
|
|
3873
|
-
return [...kinds];
|
|
3874
|
-
}
|
|
3875
|
-
function resolveRefKind(ctx, refStr) {
|
|
3876
|
-
const target = refTarget(ctx, refStr);
|
|
3877
|
-
const kind = target ? refOutputKind(target) : void 0;
|
|
3878
|
-
return kind && MODEL_INPUT_KINDS.has(kind) ? kind : null;
|
|
3879
|
-
}
|
|
3880
|
-
function refTarget(ctx, refStr) {
|
|
3881
|
-
const expr = parseRefExpr(refStr);
|
|
3882
|
-
if (!expr) return null;
|
|
3883
|
-
const targetIdx = ctx.idToIndex.get(expr.nodeId);
|
|
3884
|
-
const node = targetIdx === void 0 ? void 0 : ctx.canvas.nodes[targetIdx];
|
|
3885
|
-
const def = node ? ctx.registry.get(node.type) : void 0;
|
|
3886
|
-
return node && def ? { expr, node, def } : null;
|
|
3887
|
-
}
|
|
3888
|
-
function refOutputKind({ expr, node, def }) {
|
|
3889
|
-
const targetParams = def.params.safeParse(node.params ?? {});
|
|
3890
|
-
const resolvedKinds = resolveOutputKinds(def.outputKinds, targetParams.success ? targetParams.data : {});
|
|
3891
|
-
const declaredKind = node.params?.outputs?.[expr.output]?.kind;
|
|
3892
|
-
return resolvedKinds[expr.output] ?? declaredKind;
|
|
3893
|
-
}
|
|
3894
|
-
function staticInputKind(ctx, value) {
|
|
3895
|
-
if (Array.isArray(value)) return "list";
|
|
3896
|
-
if (typeof value !== "string") return value && typeof value === "object" ? "unknown" : "value";
|
|
3897
|
-
if (!value.startsWith(REF_PREFIX)) return "value";
|
|
3898
|
-
const target = refTarget(ctx, value);
|
|
3899
|
-
if (!target) return "unknown";
|
|
3900
|
-
const outputSchema = unwrap(shapeOf(target.def.outputs)?.[target.expr.output] ?? z2.unknown());
|
|
3901
|
-
if (outputSchema instanceof z2.ZodArray && target.expr.index === void 0) return "list";
|
|
3902
|
-
const kind = refOutputKind(target);
|
|
3903
|
-
if (kind && ASSET_KINDS.has(kind)) return kind;
|
|
3904
|
-
const plain2 = outputSchema instanceof z2.ZodString || outputSchema instanceof z2.ZodNumber;
|
|
3905
|
-
return plain2 || outputSchema instanceof z2.ZodBoolean ? "value" : "unknown";
|
|
3906
|
-
}
|
|
3907
|
-
function staticInputSource(ctx, value) {
|
|
3908
|
-
if (typeof value !== "string" || !value.startsWith(REF_PREFIX)) return void 0;
|
|
3909
|
-
const target = refTarget(ctx, value);
|
|
3910
|
-
if (!target || target.node.type !== "ingest" || target.expr.output !== "asset" || target.expr.index !== void 0)
|
|
3911
|
-
return void 0;
|
|
3912
|
-
const parsed = target.def.params.safeParse(target.node.params ?? {});
|
|
3913
|
-
if (!parsed.success) return void 0;
|
|
3914
|
-
const params = parsed.data;
|
|
3915
|
-
if (params.source === "path" && typeof params.path === "string") return { path: path3.resolve(params.path) };
|
|
3916
|
-
if (params.source === "url" && typeof params.url === "string") return { url: params.url };
|
|
3917
|
-
return void 0;
|
|
3918
|
-
}
|
|
3919
|
-
function checkOneRef(ctx, n, i, refStr, jsonPath, field) {
|
|
3920
|
-
if (!refStr.startsWith(REF_PREFIX)) return;
|
|
3921
|
-
const expr = parseRefExpr(refStr);
|
|
3922
|
-
if (!expr || expr.kind !== "ref") {
|
|
3923
|
-
ctx.issues.push({
|
|
3924
|
-
path: `nodes[${i}].${field}.${jsonPath}`,
|
|
3925
|
-
code: STAGE_CODES.REF_UNRESOLVED,
|
|
3926
|
-
message: `malformed ref "${refStr}"`,
|
|
3927
|
-
node_id: n.id,
|
|
3928
|
-
node_type: n.type
|
|
3929
|
-
});
|
|
3930
|
-
return;
|
|
3931
|
-
}
|
|
3932
|
-
const targetIdx = ctx.idToIndex.get(expr.nodeId);
|
|
3933
|
-
if (targetIdx === void 0) {
|
|
3934
|
-
ctx.issues.push({
|
|
3935
|
-
path: `nodes[${i}].${field}.${jsonPath}`,
|
|
3936
|
-
code: STAGE_CODES.REF_UNRESOLVED,
|
|
3937
|
-
message: `ref "${refStr}" \u2014 no node with id "${expr.nodeId}"`,
|
|
3938
|
-
did_you_mean: didYouMean(expr.nodeId, [...ctx.idToIndex.keys()]),
|
|
3939
|
-
node_id: n.id,
|
|
3940
|
-
node_type: n.type
|
|
3941
|
-
});
|
|
3942
|
-
return;
|
|
3943
|
-
}
|
|
3944
|
-
checkRefOutput(ctx, n, i, jsonPath, field, refStr, expr.nodeId, expr.output, targetIdx);
|
|
3945
|
-
}
|
|
3946
|
-
function checkRefOutput(ctx, n, i, jsonPath, field, refStr, nodeId, output, targetIdx) {
|
|
3947
|
-
const target = ctx.canvas.nodes[targetIdx];
|
|
3948
|
-
if (!target) return;
|
|
3949
|
-
const targetDef = ctx.registry.get(target.type);
|
|
3950
|
-
if (!targetDef) return;
|
|
3951
|
-
const outputNames = listShapeKeys(targetDef.outputs);
|
|
3952
|
-
if (!outputNames || outputNames.includes(output)) return;
|
|
3953
|
-
ctx.issues.push({
|
|
3954
|
-
path: `nodes[${i}].${field}.${jsonPath}`,
|
|
3955
|
-
code: STAGE_CODES.REF_UNRESOLVED,
|
|
3956
|
-
message: `ref "${refStr}" \u2014 node "${nodeId}" (${target.type}) has no output "${output}"`,
|
|
3957
|
-
did_you_mean: didYouMean(output, outputNames),
|
|
3958
|
-
node_id: n.id,
|
|
3959
|
-
node_type: n.type
|
|
3960
|
-
});
|
|
3961
|
-
}
|
|
3962
|
-
function checkCycle(ctx) {
|
|
3963
|
-
const graph = buildDependencyGraph(ctx.canvas);
|
|
3964
|
-
const cycle = findCycle(graph);
|
|
3965
|
-
if (cycle) {
|
|
3966
|
-
ctx.issues.push({ path: "nodes", code: STAGE_CODES.CYCLE, message: `cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
function checkAllSlots(ctx) {
|
|
3970
|
-
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
3971
|
-
const n = ctx.canvas.nodes[i];
|
|
3972
|
-
if (!n) continue;
|
|
3973
|
-
if (!ctx.registry.get(n.type)) continue;
|
|
3974
|
-
checkSlotsForNode(ctx, n, i);
|
|
3975
|
-
}
|
|
3976
|
-
}
|
|
3977
|
-
function checkSlotsForNode(ctx, n, _i) {
|
|
3978
|
-
const slots = /* @__PURE__ */ new Set();
|
|
3979
|
-
forEachString(n.params, (text) => {
|
|
3980
|
-
for (const s of extractSlots(text)) slots.add(s);
|
|
3981
|
-
});
|
|
3982
|
-
if (slots.size === 0) return;
|
|
3983
|
-
const inputKeys = Object.keys(n.inputs ?? {});
|
|
3984
|
-
for (const slot of slots) {
|
|
3985
|
-
if (inputKeys.includes(slot)) continue;
|
|
3986
|
-
ctx.issues.push({
|
|
3987
|
-
path: `nodes[${_i}].params`,
|
|
3988
|
-
code: STAGE_CODES.SLOT,
|
|
3989
|
-
message: `prompt slot "{{${slot}}}" has no matching input on node "${n.id}"`,
|
|
3990
|
-
did_you_mean: didYouMean(slot, inputKeys),
|
|
3991
|
-
node_id: n.id,
|
|
3992
|
-
node_type: n.type
|
|
3993
|
-
});
|
|
3994
|
-
}
|
|
3995
|
-
}
|
|
3996
|
-
function checkOrphanNodes(ctx) {
|
|
3997
|
-
const out = ctx.canvas.output;
|
|
3998
|
-
if (!out || !ctx.idToIndex.has(out.node)) return;
|
|
3999
|
-
const graph = buildDependencyGraph(ctx.canvas);
|
|
4000
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
4001
|
-
const stack = [out.node];
|
|
4002
|
-
while (stack.length > 0) {
|
|
4003
|
-
const id = stack.pop();
|
|
4004
|
-
if (id === void 0 || reachable.has(id)) continue;
|
|
4005
|
-
reachable.add(id);
|
|
4006
|
-
for (const dep of graph.get(id) ?? []) stack.push(dep);
|
|
4007
|
-
}
|
|
4008
|
-
for (const n of ctx.canvas.nodes) {
|
|
4009
|
-
if (reachable.has(n.id) || n.type === "ingest") continue;
|
|
4010
|
-
ctx.issues.push({
|
|
4011
|
-
path: `nodes[${ctx.idToIndex.get(n.id)}]`,
|
|
4012
|
-
code: STAGE_CODES.ORPHAN_NODE,
|
|
4013
|
-
severity: "warning",
|
|
4014
|
-
message: `node "${n.id}" (${n.type}) is not reachable from output "${out.node}": a run skips it, so nothing it would make is used; wire it in or remove it`
|
|
4015
|
-
});
|
|
4016
|
-
}
|
|
4017
|
-
}
|
|
4018
|
-
function checkOutputRef(ctx) {
|
|
4019
|
-
const out = ctx.canvas.output;
|
|
4020
|
-
if (!out) return;
|
|
4021
|
-
const targetIdx = ctx.idToIndex.get(out.node);
|
|
4022
|
-
if (targetIdx === void 0) {
|
|
4023
|
-
ctx.issues.push({
|
|
4024
|
-
path: "output.node",
|
|
4025
|
-
code: STAGE_CODES.OUTPUT,
|
|
4026
|
-
message: `output references unknown node "${out.node}"`,
|
|
4027
|
-
did_you_mean: didYouMean(out.node, [...ctx.idToIndex.keys()])
|
|
4028
|
-
});
|
|
4029
|
-
return;
|
|
4030
|
-
}
|
|
4031
|
-
const target = ctx.canvas.nodes[targetIdx];
|
|
4032
|
-
if (!target) return;
|
|
4033
|
-
const targetDef = ctx.registry.get(target.type);
|
|
4034
|
-
if (!targetDef) return;
|
|
4035
|
-
const outs = listShapeKeys(targetDef.outputs);
|
|
4036
|
-
if (!outs || outs.includes(out.output)) return;
|
|
4037
|
-
ctx.issues.push({
|
|
4038
|
-
path: "output.output",
|
|
4039
|
-
code: STAGE_CODES.OUTPUT,
|
|
4040
|
-
message: `node "${out.node}" (${target.type}) has no output "${out.output}"`,
|
|
4041
|
-
did_you_mean: didYouMean(out.output, outs)
|
|
4042
|
-
});
|
|
4043
|
-
}
|
|
4044
|
-
function pushZodIssues(issues, err, pathPrefix, code, nodeId, nodeType) {
|
|
4045
|
-
for (const issue of err.issues) {
|
|
4046
|
-
const tail = pathToString(issue.path);
|
|
4047
|
-
const path4 = pathPrefix ? tail ? `${pathPrefix}.${tail}` : pathPrefix : tail;
|
|
4048
|
-
issues.push({
|
|
4049
|
-
path: path4,
|
|
4050
|
-
code,
|
|
4051
|
-
message: issue.message,
|
|
4052
|
-
received: issue.code === "invalid_type" ? issue.received : void 0,
|
|
4053
|
-
node_id: nodeId,
|
|
4054
|
-
node_type: nodeType
|
|
4055
|
-
});
|
|
4056
|
-
}
|
|
4057
|
-
}
|
|
4058
|
-
function pathToString(path4) {
|
|
4059
|
-
return path4.map((p) => typeof p === "number" ? `[${p}]` : `.${String(p)}`).join("").replace(/^\./, "");
|
|
4060
|
-
}
|
|
4061
|
-
function walkRefs(value, cb) {
|
|
4062
|
-
forEachString(value, (s, path4) => {
|
|
4063
|
-
if (s.startsWith(REF_PREFIX)) cb(s, path4);
|
|
4064
|
-
});
|
|
4065
|
-
}
|
|
4066
|
-
function listShapeKeys(schema) {
|
|
4067
|
-
const shape = shapeOf(schema);
|
|
4068
|
-
return shape ? Object.keys(shape) : void 0;
|
|
4069
|
-
}
|
|
4070
|
-
function shapeOf(schema) {
|
|
4071
|
-
const obj = unwrap(schema);
|
|
4072
|
-
return obj instanceof z2.ZodObject ? obj.shape : void 0;
|
|
4073
|
-
}
|
|
4074
|
-
function unwrap(schema) {
|
|
4075
|
-
let cur = schema;
|
|
4076
|
-
for (let i = 0; i < 8; i++) {
|
|
4077
|
-
const inner = cur._def?.innerType;
|
|
4078
|
-
if (!inner) return cur;
|
|
4079
|
-
cur = inner;
|
|
4080
|
-
}
|
|
4081
|
-
return cur;
|
|
4082
|
-
}
|
|
4083
|
-
|
|
4084
|
-
// src/engine/nodes/local/lib/variant-prefilter.ts
|
|
4085
|
-
import { createHash } from "crypto";
|
|
4086
|
-
import sharp from "sharp";
|
|
4087
|
-
function coverOf(from, to) {
|
|
4088
|
-
const scale = Math.max(to.width / from.width, to.height / from.height);
|
|
4089
|
-
const width = from.width * scale;
|
|
4090
|
-
const height = from.height * scale;
|
|
4091
|
-
return { scale, width, height, centre: { left: (width - to.width) / 2, top: (height - to.height) / 2 } };
|
|
4092
|
-
}
|
|
4093
|
-
function framingOf(from, to) {
|
|
4094
|
-
let frame;
|
|
4095
|
-
return {
|
|
4096
|
-
place: (boxes) => {
|
|
4097
|
-
frame = frameKeeping(boxes, from, to);
|
|
4098
|
-
return coverFitBoxes(boxes, from, to, frame);
|
|
4099
|
-
},
|
|
4100
|
-
moved: () => {
|
|
4101
|
-
const { centre } = coverOf(from, to);
|
|
4102
|
-
return frame && (Math.abs(frame.left - centre.left) >= 1 || Math.abs(frame.top - centre.top) >= 1) ? frame : void 0;
|
|
4103
|
-
}
|
|
4104
|
-
};
|
|
4105
|
-
}
|
|
4106
|
-
async function cutToFrame(bytes, frame, to) {
|
|
4107
|
-
const { width = 0, height = 0 } = await sharp(bytes).metadata();
|
|
4108
|
-
const { scale } = coverOf({ width, height }, to);
|
|
4109
|
-
const w = Math.min(width, Math.round(to.width / scale));
|
|
4110
|
-
const h = Math.min(height, Math.round(to.height / scale));
|
|
4111
|
-
const left = Math.min(width - w, Math.max(0, Math.round(frame.left / scale)));
|
|
4112
|
-
const top = Math.min(height - h, Math.max(0, Math.round(frame.top / scale)));
|
|
4113
|
-
return sharp(bytes).extract({ left, top, width: w, height: h }).png().toBuffer();
|
|
4114
|
-
}
|
|
4115
|
-
var FRAME_MARGIN = 0.02;
|
|
4116
|
-
function frameKeeping(boxes, from, to) {
|
|
4117
|
-
const cover = coverOf(from, to);
|
|
4118
|
-
if (boxes.length === 0) return cover.centre;
|
|
4119
|
-
const along = (offset, extent, frame, lo, hi) => {
|
|
4120
|
-
const low = lo * extent;
|
|
4121
|
-
const high = hi * extent;
|
|
4122
|
-
const room = extent - frame;
|
|
4123
|
-
const margin = FRAME_MARGIN * frame;
|
|
4124
|
-
for (const m of [margin, 0]) {
|
|
4125
|
-
const min = Math.max(0, high + m - frame);
|
|
4126
|
-
const max = Math.min(room, low - m);
|
|
4127
|
-
if (min <= max) return Math.min(max, Math.max(min, offset));
|
|
4128
|
-
}
|
|
4129
|
-
return offset;
|
|
4130
|
-
};
|
|
4131
|
-
const x0 = Math.min(...boxes.map((b) => b.x0));
|
|
4132
|
-
const x1 = Math.max(...boxes.map((b) => b.x1));
|
|
4133
|
-
const y0 = Math.min(...boxes.map((b) => b.y0));
|
|
4134
|
-
const y1 = Math.max(...boxes.map((b) => b.y1));
|
|
4135
|
-
return {
|
|
4136
|
-
left: along(cover.centre.left, cover.width, to.width, x0, x1),
|
|
4137
|
-
top: along(cover.centre.top, cover.height, to.height, y0, y1)
|
|
4138
|
-
};
|
|
4139
|
-
}
|
|
4140
|
-
function coverFitBoxes(boxes, from, to, frame) {
|
|
4141
|
-
const cover = coverOf(from, to);
|
|
4142
|
-
const { left: offX, top: offY } = frame ?? cover.centre;
|
|
4143
|
-
const x = (f) => (f * cover.width - offX) / to.width;
|
|
4144
|
-
const y = (f) => (f * cover.height - offY) / to.height;
|
|
4145
|
-
return boxes.map((b) => ({ ...b, x0: x(b.x0), y0: y(b.y0), x1: x(b.x1), y1: y(b.y1) }));
|
|
4146
|
-
}
|
|
4147
|
-
var NO_LOGO = "The brand's logo was not found on it.";
|
|
4148
|
-
function rankOf(boxes, blocking, want) {
|
|
4149
|
-
const allowed = want.brand ? [want.brand] : [];
|
|
4150
|
-
const off = textMismatch(want.lines, boxes, { allowed }).lines;
|
|
4151
|
-
const brand = want.brand ? normalizeCopy(want.brand) : null;
|
|
4152
|
-
const logoFound = boxes.some((b) => b.kind === "logo" || brand !== null && normalizeCopy(b.text) === brand);
|
|
4153
|
-
const noLogo = want.logo && !logoFound ? 1 : 0;
|
|
4154
|
-
const noButton = off.some((l) => want.buttons.has(l.text)) ? 1 : 0;
|
|
4155
|
-
const outOfBand = bandViolations(boxes, want.format, want.grade).length;
|
|
4156
|
-
return {
|
|
4157
|
-
rank: [blocking.length, off.length, noLogo, noButton, outOfBand],
|
|
4158
|
-
shortfalls: noLogo ? [NO_LOGO] : []
|
|
4159
|
-
};
|
|
4160
|
-
}
|
|
4161
|
-
var isButton2 = (line) => /cta|button/i.test(`${line.role ?? ""} ${line.look ?? ""}`);
|
|
4162
|
-
function staticChecksOf(spec) {
|
|
4163
|
-
if (spec && typeof spec === "object" && spec.kind === "static_checks")
|
|
4164
|
-
return spec;
|
|
4165
|
-
try {
|
|
4166
|
-
const parsed = readAdSpec(spec);
|
|
4167
|
-
const native = parsed.brand_presence === "native";
|
|
4168
|
-
return {
|
|
4169
|
-
kind: "static_checks",
|
|
4170
|
-
lines: parsed.copy.map((l) => ({ text: l.text, lettering: l.lettering, ...l.role ? { role: l.role } : {} })),
|
|
4171
|
-
buttons: native ? [] : parsed.copy.filter(isButton2).map((l) => l.text),
|
|
4172
|
-
...parsed.brand?.name ? { brand: parsed.brand.name } : {},
|
|
4173
|
-
logo: parsed.logo === true && !native
|
|
4174
|
-
};
|
|
4175
|
-
} catch {
|
|
4176
|
-
return null;
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
|
-
async function prefilterVariants(variants, spec, formats, opts = {}) {
|
|
4180
|
-
const everything = {
|
|
4181
|
-
kept: seededOrder(
|
|
4182
|
-
variants.map((v) => v.index),
|
|
4183
|
-
variants
|
|
4184
|
-
),
|
|
4185
|
-
dropped: []
|
|
4186
|
-
};
|
|
4187
|
-
const checks = spec === null || spec === void 0 ? null : staticChecksOf(spec);
|
|
4188
|
-
if (!checks) return everything;
|
|
4189
|
-
const { lines, brand, logo } = checks;
|
|
4190
|
-
const buttons = new Set(checks.buttons);
|
|
4191
|
-
if (formats.length === 0 || variants.length === 0) return everything;
|
|
4192
|
-
const plan = planStaticFormats(AD_IMAGE_MODEL, formats, {
|
|
4193
|
-
masterShape: opts.masterShape ?? DEFAULT_MASTER_SHAPE,
|
|
4194
|
-
...opts.placements ? { placements: opts.placements } : {}
|
|
4195
|
-
});
|
|
4196
|
-
const format = plan.master.format;
|
|
4197
|
-
const grade = plan.masterShape === "relayout" ? plan.placements ? { placements: plan.placements } : {} : { band: plan.band };
|
|
4198
|
-
const want = { lines, buttons, ...brand ? { brand } : {}, logo, format, grade };
|
|
4199
|
-
const checked = await Promise.all(
|
|
4200
|
-
variants.map(
|
|
4201
|
-
(variant) => checkVariant(variant, { format, size: plan.master.size, lines, brand, grade, want, opts })
|
|
4202
|
-
)
|
|
4203
|
-
);
|
|
4204
|
-
const outcome = prefilterOutcome(checked);
|
|
4205
|
-
const frames = Object.fromEntries(checked.flatMap((c) => "frame" in c && c.frame ? [[c.index, c.frame]] : []));
|
|
4206
|
-
return {
|
|
4207
|
-
...outcome,
|
|
4208
|
-
kept: seededOrder(outcome.kept, variants),
|
|
4209
|
-
...Object.keys(frames).length > 0 ? { frames, masterSize: plan.master.size } : {}
|
|
4210
|
-
};
|
|
4211
|
-
}
|
|
4212
|
-
async function checkVariant(variant, c) {
|
|
4213
|
-
const { format, lines, brand, grade, want, opts } = c;
|
|
4214
|
-
const source = variant.path ?? variant.url;
|
|
4215
|
-
if (!source) return { index: variant.index, blocking: null };
|
|
4216
|
-
try {
|
|
4217
|
-
const bytes = await bytesOf(source);
|
|
4218
|
-
const { width = 0, height = 0 } = await sharp(bytes).metadata();
|
|
4219
|
-
const framing = framingOf({ width, height }, c.size);
|
|
4220
|
-
const check = await checkStaticImage(bytes, {
|
|
4221
|
-
format,
|
|
4222
|
-
lines,
|
|
4223
|
-
allowed: brand ? [brand] : [],
|
|
4224
|
-
...grade,
|
|
4225
|
-
toFrame: framing.place,
|
|
4226
|
-
...opts.read ? { read: opts.read } : {},
|
|
4227
|
-
...opts.onSpend ? { onSpend: opts.onSpend } : {}
|
|
4228
|
-
});
|
|
4229
|
-
if (check.status === "not_checked") return { index: variant.index, blocking: null };
|
|
4230
|
-
const prefix = `${format}: `;
|
|
4231
|
-
const blocking = check.findings.filter((f) => f.severity === "blocking").map((f) => f.what.startsWith(prefix) ? f.what.slice(prefix.length) : f.what);
|
|
4232
|
-
const frame = framing.moved();
|
|
4233
|
-
return { index: variant.index, blocking, ...rankOf(check.boxes, blocking, want), ...frame ? { frame } : {} };
|
|
4234
|
-
} catch {
|
|
4235
|
-
return { index: variant.index, blocking: null };
|
|
4236
|
-
}
|
|
4237
|
-
}
|
|
4238
|
-
function seededOrder(indexes, variants) {
|
|
4239
|
-
const shaOf = new Map(variants.map((v) => [v.index, v.sha256]));
|
|
4240
|
-
const seed = [...variants.map((v) => v.sha256)].sort().join(":");
|
|
4241
|
-
const key = (index) => createHash("sha256").update(`${seed}|${shaOf.get(index) ?? index}`).digest("hex");
|
|
4242
|
-
return [...indexes].sort((a, b) => key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : a - b);
|
|
4243
|
-
}
|
|
4244
|
-
var compareRanks = (a, b) => {
|
|
4245
|
-
for (let i = 0; i < a.length; i++) {
|
|
4246
|
-
const d = (a[i] ?? 0) - (b[i] ?? 0);
|
|
4247
|
-
if (d !== 0) return d;
|
|
4248
|
-
}
|
|
4249
|
-
return 0;
|
|
4250
|
-
};
|
|
4251
|
-
function prefilterOutcome(checked) {
|
|
4252
|
-
const unchecked = checked.filter((c) => c.blocking === null).map((c) => c.index);
|
|
4253
|
-
const read = checked.flatMap((c) => c.blocking === null ? [] : [c]);
|
|
4254
|
-
const extra = unchecked.length > 0 ? { unchecked } : {};
|
|
4255
|
-
const clean = read.filter((c) => c.blocking.length === 0);
|
|
4256
|
-
if (clean.length === 0 && unchecked.length === 0) {
|
|
4257
|
-
const best = [...read].sort((a, b) => compareRanks(a.rank, b.rank) || a.index - b.index)[0];
|
|
4258
|
-
if (!best) return { kept: [], dropped: [], ...extra };
|
|
4259
|
-
const dropped2 = read.filter((c) => c.index !== best.index).map((c) => ({ index: c.index, reasons: c.blocking }));
|
|
4260
|
-
return { kept: [best.index], dropped: dropped2, flagged: { index: best.index, reasons: best.blocking }, ...extra };
|
|
4261
|
-
}
|
|
4262
|
-
const top = [...clean].sort((a, b) => compareRanks(a.rank, b.rank))[0]?.rank;
|
|
4263
|
-
const kept = checked.filter((c) => c.blocking === null || top !== void 0 && compareRanks(c.rank, top) === 0).map((c) => c.index);
|
|
4264
|
-
const dropped = read.filter((c) => !kept.includes(c.index)).map((c) => ({ index: c.index, reasons: c.blocking.length > 0 ? c.blocking : c.shortfalls }));
|
|
4265
|
-
return { kept, dropped, ...extra };
|
|
4266
|
-
}
|
|
4267
|
-
function judgeAnswerOf(raw) {
|
|
4268
|
-
const answer = raw;
|
|
4269
|
-
const best = Number(answer?.best);
|
|
4270
|
-
if (typeof answer?.reasoning !== "string" || !Number.isInteger(best)) return null;
|
|
4271
|
-
return { reasoning: answer.reasoning, best };
|
|
4272
|
-
}
|
|
4273
|
-
async function judgeTie(tied, opts) {
|
|
4274
|
-
const jpegs = [];
|
|
4275
|
-
for (const variant of tied) {
|
|
4276
|
-
const source = variant.path ?? variant.url;
|
|
4277
|
-
if (!source) return null;
|
|
4278
|
-
try {
|
|
4279
|
-
const bytes = await bytesOf(source);
|
|
4280
|
-
jpegs.push(
|
|
4281
|
-
(await sharp(bytes).resize(1024, 1024, { fit: "inside" }).jpeg({ quality: 85 }).toBuffer()).toString("base64")
|
|
4282
|
-
);
|
|
4283
|
-
} catch {
|
|
4284
|
-
return null;
|
|
4285
|
-
}
|
|
4286
|
-
}
|
|
4287
|
-
const prompt = `${opts.prompt}
|
|
4288
|
-
|
|
4289
|
-
The candidates are Image 1 to Image ${jpegs.length}, in the order shown. Reply with only this JSON: {"reasoning":"<the ranking lines, then one sentence>","best":<the number of the image that wins>}`;
|
|
4290
|
-
const ask = opts.ask ?? ((model, images, text) => askAboutImages(model, images, text, opts.onSpend));
|
|
4291
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4292
|
-
const answer = judgeAnswerOf(await ask(opts.model, jpegs, prompt));
|
|
4293
|
-
const winner = answer ? tied[answer.best - 1] : void 0;
|
|
4294
|
-
if (answer && winner) return { chosen: winner.index, reasoning: answer.reasoning };
|
|
4295
|
-
}
|
|
4296
|
-
return null;
|
|
4297
|
-
}
|
|
4298
|
-
|
|
4299
|
-
// src/engine/lib/spell-number.ts
|
|
4300
|
-
import { toCardinal as nwAr } from "n2words/ar-SA";
|
|
4301
|
-
import { toCardinal as nwDe } from "n2words/de-DE";
|
|
4302
|
-
import { toCardinal as nwEn } from "n2words/en-US";
|
|
4303
|
-
import { toCardinal as nwEs } from "n2words/es-ES";
|
|
4304
|
-
import { toCardinal as nwFr } from "n2words/fr-FR";
|
|
4305
|
-
import { toCardinal as nwHi } from "n2words/hi-IN";
|
|
4306
|
-
import { toCardinal as nwIt } from "n2words/it-IT";
|
|
4307
|
-
import { toCardinal as nwJa } from "n2words/ja-JP";
|
|
4308
|
-
import { toCardinal as nwKo } from "n2words/ko-KR";
|
|
4309
|
-
import { toCardinal as nwNl } from "n2words/nl-NL";
|
|
4310
|
-
import { toCardinal as nwPl } from "n2words/pl-PL";
|
|
4311
|
-
import { toCardinal as nwPt } from "n2words/pt-PT";
|
|
4312
|
-
var INTEGER_SPELLERS = {
|
|
4313
|
-
fr: nwFr,
|
|
4314
|
-
es: nwEs,
|
|
4315
|
-
en: nwEn,
|
|
4316
|
-
de: nwDe,
|
|
4317
|
-
it: nwIt,
|
|
4318
|
-
pt: nwPt,
|
|
4319
|
-
nl: nwNl,
|
|
4320
|
-
pl: nwPl,
|
|
4321
|
-
ar: nwAr,
|
|
4322
|
-
ja: nwJa,
|
|
4323
|
-
ko: nwKo,
|
|
4324
|
-
hi: nwHi
|
|
4325
|
-
};
|
|
4326
|
-
function canSpellNumbers(langCode) {
|
|
4327
|
-
return langCode in INTEGER_SPELLERS;
|
|
4328
|
-
}
|
|
4329
|
-
function spellNumber(langCode, n) {
|
|
4330
|
-
const spell = langCode ? INTEGER_SPELLERS[langCode] : void 0;
|
|
4331
|
-
if (!spell || !Number.isFinite(n)) return String(n);
|
|
4332
|
-
try {
|
|
4333
|
-
return spell(n);
|
|
4334
|
-
} catch {
|
|
4335
|
-
return String(n);
|
|
4336
|
-
}
|
|
4337
|
-
}
|
|
4338
|
-
|
|
4339
|
-
export {
|
|
4340
|
-
SEEDANCE_PROFILE,
|
|
4341
|
-
clipProfileFor,
|
|
4342
|
-
clipParamRecipe,
|
|
4343
|
-
nativeDialogueOf,
|
|
4344
|
-
GPT_IMAGE_25_PROFILE,
|
|
4345
|
-
imageProfileFor,
|
|
4346
|
-
MASTER_SHAPES,
|
|
4347
|
-
DEFAULT_MASTER_SHAPE,
|
|
4348
|
-
deliveredSize,
|
|
4349
|
-
ASPECT_ADAPT_FORMATS,
|
|
4350
|
-
planStaticFormats,
|
|
4351
|
-
cutToFrame,
|
|
4352
|
-
staticChecksOf,
|
|
4353
|
-
prefilterVariants,
|
|
4354
|
-
judgeTie,
|
|
4355
|
-
compileStaticBrief,
|
|
4356
|
-
briefCopyLines,
|
|
4357
|
-
judgeBrief,
|
|
4358
|
-
STATIC_ADAPT_FORMAT_ROLE,
|
|
4359
|
-
adaptPlan,
|
|
4360
|
-
trimmedRenderBand,
|
|
4361
|
-
adaptBrief,
|
|
4362
|
-
imageRenderParams,
|
|
4363
|
-
STATIC_VARIANTS_ROLE,
|
|
4364
|
-
STATIC_VARIANT_CALL_ROLE,
|
|
4365
|
-
VARIANT_PREFILTER_ROLE,
|
|
4366
|
-
STATIC_PICK_ROLE,
|
|
4367
|
-
STATIC_MASTER_ROLE,
|
|
4368
|
-
STATIC_SIZE_ROLE,
|
|
4369
|
-
STATIC_ADAPT_ROLE,
|
|
4370
|
-
STATIC_SAFE_FIT_ROLE,
|
|
4371
|
-
STATIC_PLACE_ROLE,
|
|
4372
|
-
LOGO_COMPOSITE_ROLE,
|
|
4373
|
-
SERIES_LEAD_ROLE,
|
|
4374
|
-
STATIC_FIX_ROLE,
|
|
4375
|
-
LOGO_COMPOSITE_DEFAULT,
|
|
4376
|
-
nodesWithRole,
|
|
4377
|
-
rolesById,
|
|
4378
|
-
relaidPictureRole,
|
|
4379
|
-
formatSlug,
|
|
4380
|
-
staticMasterFormat,
|
|
4381
|
-
staticRenderNodes,
|
|
4382
|
-
safeFitNode,
|
|
4383
|
-
RENDER_GRAPH_IDS,
|
|
4384
|
-
regenerateTargets,
|
|
4385
|
-
BackendClient,
|
|
4386
|
-
REF_PREFIX,
|
|
4387
|
-
parseRefExpr,
|
|
4388
|
-
NodeDecl,
|
|
4389
|
-
renderTemplate,
|
|
4390
|
-
priceNode,
|
|
4391
|
-
estimateNodeCredits,
|
|
4392
|
-
sumCredits,
|
|
4393
|
-
nodeDependencies,
|
|
4394
|
-
buildDependencyGraph,
|
|
4395
|
-
elementMentionKeywords,
|
|
4396
|
-
frameDescriptionOf,
|
|
4397
|
-
READ_WORDS_PER_SECOND,
|
|
4398
|
-
wordCount2 as wordCount,
|
|
4399
|
-
estSpeechS,
|
|
4400
|
-
estSpeechWindowS,
|
|
4401
|
-
renderedPromptOverLimit,
|
|
4402
|
-
topologicalLayers,
|
|
4403
|
-
reachableNodes,
|
|
4404
|
-
subgraph,
|
|
4405
|
-
validateCanvas,
|
|
4406
|
-
validateCanvasDeep,
|
|
4407
|
-
canSpellNumbers,
|
|
4408
|
-
spellNumber
|
|
4409
|
-
};
|
|
4410
|
-
//# sourceMappingURL=chunk-ZIHEIVJC.js.map
|