@aglyn/plugins-ai 1.0.0-beta.266 → 1.0.0-beta.268

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.
Files changed (40) hide show
  1. package/package.json +18 -18
  2. package/src/lib/components/ai-job-site.d.ts +8 -1
  3. package/src/lib/components/ai-job-site.js +7 -0
  4. package/src/lib/components/ai-job-site.js.map +1 -1
  5. package/src/lib/components/ai-site-build-page.component.js +37 -0
  6. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +25 -6
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/components/ai-site-start-steps.component.d.ts +8 -2
  10. package/src/lib/components/ai-site-start-steps.component.js +55 -26
  11. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-tracking.d.ts +11 -3
  13. package/src/lib/components/ai-site-start-tracking.js +28 -9
  14. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  15. package/src/lib/components/assist-jobs-drawer.component.js +24 -2
  16. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  17. package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
  18. package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
  19. package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
  20. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
  21. package/src/lib/jobs/ai-jobs-beat.js +23 -6
  22. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  23. package/src/lib/jobs/ai-jobs.js +46 -7
  24. package/src/lib/jobs/ai-jobs.js.map +1 -1
  25. package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
  26. package/src/lib/jobs/ai-layout-stock-photos.js +321 -19
  27. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  28. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
  29. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
  30. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
  31. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +31 -0
  32. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +208 -0
  33. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
  34. package/src/lib/layout-language/ai-layout-design.js +5 -0
  35. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  36. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  37. package/src/lib/model/ai-jobs.types.js.map +1 -1
  38. package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
  39. package/src/lib/model/ai-site-start-handoff.js +88 -0
  40. package/src/lib/model/ai-site-start-handoff.js.map +1 -0
@@ -158,6 +158,15 @@ export declare const AI_LAYOUT_STOCK_SEARCHES_PER_PAGE = 18;
158
158
  export declare const AI_LAYOUT_STOCK_PICK_WINDOW = 40;
159
159
  /** The largest photo copied: inside one direct upload. */
160
160
  export declare const AI_LAYOUT_STOCK_MAX_BYTES: number;
161
+ /**
162
+ * The media library's refusals that are about ONE photo's own bytes, as the
163
+ * console's `ingestMedia` returns them (AGL-3923): 415 for a type or contents
164
+ * it does not accept, 413 for a photo too large to store, 451 for a file
165
+ * held in quarantine. The slot then tries its next hit, and the page goes on
166
+ * storing. Every other refusal (403 from the storage band, the site's scope
167
+ * or a lockdown; 500) holds for the whole page.
168
+ */
169
+ export declare const AI_STOCK_PHOTO_OWN_REFUSALS: ReadonlySet<number>;
161
170
  /**
162
171
  * The unit job input naming the screen drafts the job's other pages were
163
172
  * written to, whose photos a page does not place again (AGL-3660).
@@ -709,6 +718,32 @@ export declare function aiStockIsAbbreviation(businessType: string): boolean;
709
718
  export declare function aiStockNamedKind(text: string): string;
710
719
  /** What a kind of site's pictures are searched and judged by ({@link KIND_SUBJECTS}); `null` for a kind not listed. */
711
720
  export declare function aiStockKindTerms(kind: string): AiStockSiteTerms | null;
721
+ /**
722
+ * Whether a site's world is only its own words (AGL-3923): no craft this
723
+ * module lists and no kind of site named it, so its broad searches are its
724
+ * business and its craft read back ("freelencing"), which a library may not
725
+ * know at all.
726
+ */
727
+ export declare function aiStockWorldIsUnlisted(terms: Pick<AiStockSiteTerms, 'business' | 'craft' | 'broad'>): boolean;
728
+ /**
729
+ * What a picture is searched by once nothing narrower found it a photo
730
+ * (AGL-3923), widest last: the kind of site the person picked, the kind its
731
+ * starter is the shape of, and the starter's own common photos
732
+ * ({@link STARTER_GENERIC}). Only a site whose world is its own words alone
733
+ * ({@link aiStockWorldIsUnlisted}) widens: a craft this module lists, or a
734
+ * world read from the kind of site, keeps its slots to that world, and a slot
735
+ * nothing in it fits keeps no photo rather than an office (AGL-3831). A rung
736
+ * that would search what an earlier one did is left out.
737
+ *
738
+ * The beta.265 Kasvora Marketing Solutions start (job BK3r4I8bF1) typed its
739
+ * business as "Freelencing site offer services": its world was the one word
740
+ * "freelencing", which no library tags, so its cover hero searched it, found
741
+ * nothing, and shipped a flat dark band with no photo.
742
+ */
743
+ export declare function aiStockWiderTerms(terms: Pick<AiStockSiteTerms, 'business' | 'craft' | 'broad'>, kind: string): Array<{
744
+ rung: 'kind' | 'starter' | 'generic';
745
+ terms: AiStockSiteTerms;
746
+ }>;
712
747
  /**
713
748
  * The first word of a text that names a craft {@link CRAFT_KIN} lists, as
714
749
  * its table writes it: "gym" of "Synchronicity Gym", "law" of "Hartwell
@@ -19,7 +19,7 @@ import { STOCK_PHOTO_QUERY_MAX_CHARS, stockPhotoProvider, stockPhotoSourceKey }
19
19
  import { AI_LAYOUT_NO_PHOTO, aiLayoutSeedNumber } from "../layout-language/ai-layout-pictures.js";
20
20
  import { aiStorefrontSays } from "../layout-language/ai-layout-storefront.js";
21
21
  import { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from "../layout-language/ai-layout-labels.js";
22
- import { aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
22
+ import { aiSiteKind, aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
23
23
  import { aiStockBusinessNoun } from "./ai-stock-business-nouns.js";
24
24
  /**
25
25
  * STOCK PHOTOS FOR A LANGUAGE PAGE'S PICTURE SLOTS (AGL-3660).
@@ -157,6 +157,18 @@ import { aiStockBusinessNoun } from "./ai-stock-business-nouns.js";
157
157
  /** The accepted hits a slot keeps in hand, in case a copy fails. */ const AI_LAYOUT_STOCK_TRIES_PER_SLOT = 3;
158
158
  /** Copies made side by side. */ const AI_LAYOUT_STOCK_COPIES_AT_ONCE = 4;
159
159
  /** The largest photo copied: inside one direct upload. */ export const AI_LAYOUT_STOCK_MAX_BYTES = 3 * 1024 * 1024;
160
+ /**
161
+ * The media library's refusals that are about ONE photo's own bytes, as the
162
+ * console's `ingestMedia` returns them (AGL-3923): 415 for a type or contents
163
+ * it does not accept, 413 for a photo too large to store, 451 for a file
164
+ * held in quarantine. The slot then tries its next hit, and the page goes on
165
+ * storing. Every other refusal (403 from the storage band, the site's scope
166
+ * or a lockdown; 500) holds for the whole page.
167
+ */ export const AI_STOCK_PHOTO_OWN_REFUSALS = new Set([
168
+ 413,
169
+ 415,
170
+ 451
171
+ ]);
160
172
  /**
161
173
  * The unit job input naming the screen drafts the job's other pages were
162
174
  * written to, whose photos a page does not place again (AGL-3660).
@@ -2812,6 +2824,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2812
2824
  * later does not place what an earlier one did, while a pass repeated over
2813
2825
  * its own page keeps its own photos. Bounded; the oldest job goes first.
2814
2826
  */ const JOB_PHOTOS = new Map();
2827
+ /** What a job's photos map holds for a photo the media library refused for its own bytes: no page's scope (AGL-3923). */ const REFUSED_BY_THE_LIBRARY = '\u0000refused';
2815
2828
  const JOB_PHOTOS_MAX = 200;
2816
2829
  function jobPhotos(key) {
2817
2830
  if (!key) return null;
@@ -3801,6 +3814,188 @@ const EXTENSIONS = {
3801
3814
  ]
3802
3815
  });
3803
3816
  }
3817
+ /**
3818
+ * The kind of site each starter is the shape of (`AiSiteKind.starter`): what
3819
+ * a picture is searched by, widened past the kind the person picked, where
3820
+ * nothing narrower found one (AGL-3923).
3821
+ */ const STARTER_KINDS = {
3822
+ business: 'business',
3823
+ 'physical-shop': 'store',
3824
+ portfolio: 'portfolio',
3825
+ landing: 'landing'
3826
+ };
3827
+ /**
3828
+ * The last searches a picture tries, for each starter (AGL-3923): plain,
3829
+ * common photos of where such a site's work is done, which every library
3830
+ * holds many of, held only to their own words. Never a person's face, a
3831
+ * child, a product or a place a business could be mistaken for.
3832
+ */ const STARTER_GENERIC = {
3833
+ business: {
3834
+ subject: 'office',
3835
+ kin: [
3836
+ 'office',
3837
+ 'workspace',
3838
+ 'desk',
3839
+ 'laptop',
3840
+ 'computer',
3841
+ 'meeting',
3842
+ 'business',
3843
+ 'work',
3844
+ 'workplace',
3845
+ 'team',
3846
+ 'notebook',
3847
+ 'coffee',
3848
+ 'teamwork',
3849
+ 'conference'
3850
+ ],
3851
+ broad: [
3852
+ 'office workspace',
3853
+ 'business meeting',
3854
+ 'workspace desk laptop'
3855
+ ]
3856
+ },
3857
+ 'physical-shop': {
3858
+ subject: 'shop',
3859
+ kin: [
3860
+ 'shop',
3861
+ 'store',
3862
+ 'retail',
3863
+ 'shelf',
3864
+ 'shelves',
3865
+ 'display',
3866
+ 'interior',
3867
+ 'boutique',
3868
+ 'storefront',
3869
+ 'counter'
3870
+ ],
3871
+ broad: [
3872
+ 'shop interior',
3873
+ 'retail store'
3874
+ ]
3875
+ },
3876
+ portfolio: {
3877
+ subject: 'studio',
3878
+ kin: [
3879
+ 'studio',
3880
+ 'workspace',
3881
+ 'desk',
3882
+ 'art',
3883
+ 'design',
3884
+ 'creative',
3885
+ 'sketch',
3886
+ 'drawing',
3887
+ 'notebook',
3888
+ 'paper'
3889
+ ],
3890
+ broad: [
3891
+ 'creative workspace',
3892
+ 'art studio',
3893
+ 'workspace desk laptop'
3894
+ ]
3895
+ },
3896
+ landing: {
3897
+ subject: 'office',
3898
+ kin: [
3899
+ 'office',
3900
+ 'workspace',
3901
+ 'desk',
3902
+ 'laptop',
3903
+ 'computer',
3904
+ 'meeting',
3905
+ 'business',
3906
+ 'work',
3907
+ 'workplace',
3908
+ 'team',
3909
+ 'technology',
3910
+ 'startup'
3911
+ ],
3912
+ broad: [
3913
+ 'modern office',
3914
+ 'workspace desk laptop',
3915
+ 'business meeting'
3916
+ ]
3917
+ }
3918
+ };
3919
+ /** The starter a kind of site follows ({@link STARTER_KINDS}); a kind not listed follows the business starter. */ function starterOfKind(kind) {
3920
+ var _ref;
3921
+ var _aiSiteKind;
3922
+ const starter = (_ref = (_aiSiteKind = aiSiteKind(kind)) == null ? void 0 : _aiSiteKind.starter) != null ? _ref : 'business';
3923
+ return STARTER_GENERIC[starter] ? starter : 'business';
3924
+ }
3925
+ /**
3926
+ * Whether a site's world is only its own words (AGL-3923): no craft this
3927
+ * module lists and no kind of site named it, so its broad searches are its
3928
+ * business and its craft read back ("freelencing"), which a library may not
3929
+ * know at all.
3930
+ */ export function aiStockWorldIsUnlisted(terms) {
3931
+ const own = [
3932
+ ...new Set([
3933
+ terms.business,
3934
+ terms.craft
3935
+ ].map((word)=>word.trim()).filter(Boolean))
3936
+ ];
3937
+ return terms.broad.length > 0 && terms.broad.length === own.length && terms.broad.every((query, index)=>query === own[index]);
3938
+ }
3939
+ /**
3940
+ * What a picture is searched by once nothing narrower found it a photo
3941
+ * (AGL-3923), widest last: the kind of site the person picked, the kind its
3942
+ * starter is the shape of, and the starter's own common photos
3943
+ * ({@link STARTER_GENERIC}). Only a site whose world is its own words alone
3944
+ * ({@link aiStockWorldIsUnlisted}) widens: a craft this module lists, or a
3945
+ * world read from the kind of site, keeps its slots to that world, and a slot
3946
+ * nothing in it fits keeps no photo rather than an office (AGL-3831). A rung
3947
+ * that would search what an earlier one did is left out.
3948
+ *
3949
+ * The beta.265 Kasvora Marketing Solutions start (job BK3r4I8bF1) typed its
3950
+ * business as "Freelencing site offer services": its world was the one word
3951
+ * "freelencing", which no library tags, so its cover hero searched it, found
3952
+ * nothing, and shipped a flat dark band with no photo.
3953
+ */ export function aiStockWiderTerms(terms, kind) {
3954
+ var _STARTER_KINDS_starter;
3955
+ const rungs = [];
3956
+ if (!aiStockWorldIsUnlisted(terms)) return rungs;
3957
+ const seen = new Set([
3958
+ JSON.stringify([
3959
+ terms.craft,
3960
+ [
3961
+ ...terms.broad
3962
+ ]
3963
+ ])
3964
+ ]);
3965
+ const add = (rung, found)=>{
3966
+ if (!found || !found.broad.length) return;
3967
+ const key = JSON.stringify([
3968
+ found.craft,
3969
+ found.broad
3970
+ ]);
3971
+ if (seen.has(key)) return;
3972
+ seen.add(key);
3973
+ rungs.push({
3974
+ rung,
3975
+ terms: found
3976
+ });
3977
+ };
3978
+ const starter = starterOfKind(kind);
3979
+ if (kind) add('kind', aiStockKindTerms(kind));
3980
+ add('starter', aiStockKindTerms((_STARTER_KINDS_starter = STARTER_KINDS[starter]) != null ? _STARTER_KINDS_starter : 'business'));
3981
+ const generic = STARTER_GENERIC[starter];
3982
+ const kin = generic.kin.map(aiStockStem);
3983
+ add('generic', {
3984
+ business: '',
3985
+ craft: '',
3986
+ domain: kin,
3987
+ broad: [
3988
+ ...generic.broad
3989
+ ],
3990
+ making: [],
3991
+ named: [],
3992
+ rivals: [],
3993
+ core: kin,
3994
+ shun: [],
3995
+ shunLead: []
3996
+ });
3997
+ return rungs;
3998
+ }
3804
3999
  /**
3805
4000
  * The first word of a text that names a craft {@link CRAFT_KIN} lists, as
3806
4001
  * its table writes it: "gym" of "Synchronicity Gym", "law" of "Hartwell
@@ -3841,7 +4036,7 @@ const EXTENSIONS = {
3841
4036
  * The source for one page, or `null` when this deployment has no stock
3842
4037
  * photo library or no media door, which leaves every slot to the starters.
3843
4038
  */ export function aiLayoutStockPhotoSource(input, deps = {}) {
3844
- var _deps_provider, _deps_ingest;
4039
+ var _deps_provider, _deps_ingest, _input_kind;
3845
4040
  const provider = ((_deps_provider = deps.provider) != null ? _deps_provider : stockPhotoProvider)();
3846
4041
  const ingest = ((_deps_ingest = deps.ingest) != null ? _deps_ingest : pluginMediaIngest)();
3847
4042
  if (!provider || !ingest || !input.hostId || !input.uid) return null;
@@ -3853,6 +4048,8 @@ const EXTENSIONS = {
3853
4048
  // alt texts, in its own language, name no subject a search could use.
3854
4049
  const english = aiStockSourceSubject(input);
3855
4050
  const terms = aiStockSourceTerms(input);
4051
+ // Where a picture's own world finds nothing, the wider worlds it tries (AGL-3923).
4052
+ const wider = aiStockWiderTerms(terms, (_input_kind = input.kind) != null ? _input_kind : '');
3856
4053
  const proper = aiStockProperWords(input.business);
3857
4054
  return async (slots)=>{
3858
4055
  var _deps_budgetMs, _input_avoid;
@@ -3869,8 +4066,10 @@ const EXTENSIONS = {
3869
4066
  /** The photos this page's slots have claimed, first choices, by slot order. */ const shown = [];
3870
4067
  /** Asset srcs this page has placed. */ const placedSrcs = new Set();
3871
4068
  const answers = new Map();
4069
+ /** The wider world a widened search is judged by (AGL-3923); a search not listed is judged by the site's. */ const worldOf = new WeakMap();
3872
4070
  let searches = 0;
3873
4071
  let storing = true;
4072
+ /** Slots a photo was chosen for and could not be copied into the library (AGL-3923). */ const uncopied = new Set();
3874
4073
  /** Whether the library answered any search of this page: a slot left empty then found nothing fitting. */ let answered = false;
3875
4074
  const remember = (sourceKey, src)=>{
3876
4075
  placedSrcs.add(src);
@@ -3953,7 +4152,17 @@ const EXTENSIONS = {
3953
4152
  maxBytes: AI_LAYOUT_STOCK_MAX_BYTES,
3954
4153
  signal
3955
4154
  });
3956
- if (!bytes || signal.aborted || !storing) return null;
4155
+ if (!bytes || signal.aborted || !storing) {
4156
+ // A copy the library would not hand over is otherwise silent (AGL-3923).
4157
+ if (!bytes && !signal.aborted) {
4158
+ uncopied.add(slot);
4159
+ console.warn('ai layout pictures: a stock photo could not be downloaded', {
4160
+ provider: photo.provider,
4161
+ id: photo.id
4162
+ });
4163
+ }
4164
+ return null;
4165
+ }
3957
4166
  const credit = provider.credit(photo);
3958
4167
  const stored = await ingest.ingest({
3959
4168
  hostId: input.hostId,
@@ -3988,13 +4197,28 @@ const EXTENSIONS = {
3988
4197
  height: (_stored_height = stored.height) != null ? _stored_height : photo.height
3989
4198
  }, described);
3990
4199
  }
3991
- storing = false;
3992
4200
  const refusal = stored;
4201
+ // One photo's own bytes refused (AGL-3923): the beta.267 Brightvale
4202
+ // start's first pick, Pixabay 10157445, came labeled image/png and was
4203
+ // not PNG, and that one 415 stopped every copy on all four of its
4204
+ // pages, which then showed the starters' pottery bench and coffee
4205
+ // shop. Such a refusal is this photo's: the slot tries its next hit.
4206
+ const ofThisPhoto = AI_STOCK_PHOTO_OWN_REFUSALS.has(refusal.status);
4207
+ if (ofThisPhoto) {
4208
+ uncopied.add(slot);
4209
+ // Never tried again by this page's other slots, nor by the job's other pages.
4210
+ claimed.add(sourceKey);
4211
+ job == null ? void 0 : job.set(sourceKey, REFUSED_BY_THE_LIBRARY);
4212
+ } else storing = false;
3993
4213
  console.warn('ai layout pictures: the media library refused a stock photo', {
3994
4214
  status: refusal.status,
3995
- reason: refusal.reason
4215
+ reason: refusal.reason,
4216
+ provider: photo.provider,
4217
+ id: photo.id,
4218
+ holds: ofThisPhoto ? 'this photo' : 'the page'
3996
4219
  });
3997
4220
  } catch (error) {
4221
+ uncopied.add(slot);
3998
4222
  console.warn('ai layout pictures: a stock photo could not be kept', {
3999
4223
  error: String(error)
4000
4224
  });
@@ -4006,14 +4230,14 @@ const EXTENSIONS = {
4006
4230
  * hit names a child; a portrait of one of the business's people names an
4007
4231
  * adult, never leads with a child, and is preferred in its role (AGL-3856).
4008
4232
  */ const judgementFor = (slot, request, subject)=>{
4009
- var _request_refuse, _input_sectionNames_slot_sectionIndex, _request_refuseLead;
4233
+ var _request_refuse, _input_sectionNames_slot_sectionIndex, _worldOf_get, _request_refuseLead;
4010
4234
  var _request_needs, _role_scene, _role_refuse;
4011
4235
  const refuse = [
4012
4236
  ...(_request_refuse = request.refuse) != null ? _request_refuse : [],
4013
4237
  ...terms.adults ? CHILD_STEMS : []
4014
4238
  ];
4015
4239
  const role = slot.people ? aiStockPeopleRole(slot.alt, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '') : null;
4016
- return _extends({}, judgementOf(slot, request, subject), refuse.length ? {
4240
+ return _extends({}, judgementOf(slot, request, subject, (_worldOf_get = worldOf.get(request)) != null ? _worldOf_get : terms), refuse.length ? {
4017
4241
  refuse: [
4018
4242
  ...new Set(refuse)
4019
4243
  ]
@@ -4033,12 +4257,16 @@ const EXTENSIONS = {
4033
4257
  roleRefuse: aiStockRoleRefusals(role, slot.alt)
4034
4258
  } : {}) : {});
4035
4259
  };
4036
- /** A search's own judgement, as its words set it. */ const judgementOf = (slot, request, subject)=>{
4260
+ /**
4261
+ * A search's own judgement, as its words set it, held to the world it was
4262
+ * asked for: the site's own, or a wider one where the site's found nothing
4263
+ * (AGL-3923). What lets a sensitive topic in is always the site's own words.
4264
+ */ const judgementOf = (slot, request, subject, world)=>{
4037
4265
  var _request_subject, _request_shun;
4038
4266
  var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead;
4039
4267
  return _extends({
4040
4268
  subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
4041
- domain: terms.domain,
4269
+ domain: world.domain,
4042
4270
  strict: slot.role === 'gallery' && !request.broad || !!request.requires || !!request.strict,
4043
4271
  named: ((_request_allow = request.allow) == null ? void 0 : _request_allow.length) ? [
4044
4272
  ...new Set([
@@ -4046,16 +4274,16 @@ const EXTENSIONS = {
4046
4274
  ...request.allow
4047
4275
  ])
4048
4276
  ] : terms.named,
4049
- rivals: terms.rivals
4050
- }, terms.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
4277
+ rivals: world.rivals
4278
+ }, world.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
4051
4279
  shun: [
4052
4280
  ...new Set([
4053
- ...terms.shun,
4281
+ ...world.shun,
4054
4282
  ...(_request_shun = request.shun) != null ? _request_shun : []
4055
4283
  ])
4056
4284
  ]
4057
- } : {}, terms.shunLead.length ? {
4058
- shunLead: terms.shunLead
4285
+ } : {}, world.shunLead.length ? {
4286
+ shunLead: world.shunLead
4059
4287
  } : {}, request.product ? {
4060
4288
  product: true
4061
4289
  } : {}, ((_request_leadAny = request.leadAny) == null ? void 0 : _request_leadAny.length) ? {
@@ -4089,9 +4317,9 @@ const EXTENSIONS = {
4089
4317
  * The craft's own photos, the last thing a slot of a craft's site is
4090
4318
  * filled from (AGL-3660): its broad searches, any hit naming the craft
4091
4319
  * itself. Never a desk, a laptop or a living room for a soap shop.
4092
- */ const craftSearches = (slot)=>{
4320
+ */ const craftSearches = (slot, world = terms)=>{
4093
4321
  // A portfolio's record of the work is never filled by the craft's own photos (AGL-3856).
4094
- if (!terms.core.length || aiStockShowsTheWork(slot, terms)) return [];
4322
+ if (!world.core.length || aiStockShowsTheWork(slot, world)) return [];
4095
4323
  const orientation = aiStockOrientation(slot.aspect);
4096
4324
  const size = slot.role === 'hero' ? {
4097
4325
  minWidth: 1600
@@ -4100,12 +4328,12 @@ const EXTENSIONS = {
4100
4328
  } : {
4101
4329
  minWidth: 900
4102
4330
  };
4103
- return terms.broad.map((query)=>_extends({
4331
+ return world.broad.map((query)=>_extends({
4104
4332
  query: clip(query),
4105
4333
  orientation
4106
4334
  }, size, {
4107
4335
  broad: true,
4108
- category: terms.core
4336
+ category: world.core
4109
4337
  }, slot.role === 'hero' ? {
4110
4338
  first: true
4111
4339
  } : {}, slot.product ? {
@@ -4113,6 +4341,57 @@ const EXTENSIONS = {
4113
4341
  tier: 'weak'
4114
4342
  } : {}));
4115
4343
  };
4344
+ /**
4345
+ * A slot's searches once its own world found it nothing (AGL-3923), in
4346
+ * order: the kind of site's world, the starter's, the slot's own words
4347
+ * alone held to no world, and last the starter's common photos. Each is
4348
+ * judged by the world it was asked for. A product, a portrait of the
4349
+ * business's people and a record's cover keep their own fallbacks.
4350
+ */ const widerSearches = (slot, subject, sectionName)=>{
4351
+ if (!wider.length || slot.product || slot.people || slot.record) return [];
4352
+ const part = aiStockSlotPart(slot, sectionName);
4353
+ const asked = [];
4354
+ const ask = (world, requests)=>{
4355
+ for (const request of requests){
4356
+ worldOf.set(request, world);
4357
+ asked.push(request);
4358
+ }
4359
+ };
4360
+ for (const { rung, terms: world } of wider){
4361
+ if (rung === 'generic') continue;
4362
+ ask(world, [
4363
+ ...aiStockSearchesFor(slot, world, subject, part, sectionName),
4364
+ ...craftSearches(slot, world)
4365
+ ]);
4366
+ }
4367
+ if (subject) {
4368
+ const orientation = aiStockOrientation(slot.aspect);
4369
+ const size = slot.role === 'hero' ? {
4370
+ minWidth: 1600
4371
+ } : orientation === 'vertical' ? {
4372
+ minHeight: 900
4373
+ } : {
4374
+ minWidth: 900
4375
+ };
4376
+ const object = aiStockObjectWord(subject);
4377
+ const own = [
4378
+ ...new Set([
4379
+ subject,
4380
+ object
4381
+ ].map(clip).filter(Boolean))
4382
+ ];
4383
+ ask(terms, own.map((query)=>_extends({
4384
+ query,
4385
+ orientation
4386
+ }, size, {
4387
+ subject,
4388
+ strict: true,
4389
+ worldless: true
4390
+ })));
4391
+ }
4392
+ for (const { rung, terms: world } of wider)if (rung === 'generic') ask(world, craftSearches(slot, world));
4393
+ return asked;
4394
+ };
4116
4395
  // 1. Choose, slot by slot, so the page's choices never collide: each
4117
4396
  // slot claims its best accepted hit before the next slot looks.
4118
4397
  const choices = [];
@@ -4143,6 +4422,11 @@ const EXTENSIONS = {
4143
4422
  if (elsewhere(key)) blocked.add(key);
4144
4423
  }
4145
4424
  }
4425
+ // A photo the library refused for its own bytes is never a repeat either (AGL-3923).
4426
+ for (const hit of found){
4427
+ const key = stockPhotoSourceKey(hit);
4428
+ if ((job == null ? void 0 : job.get(key)) === REFUSED_BY_THE_LIBRARY) blocked.add(key);
4429
+ }
4146
4430
  const judgement = judgementFor(slot, request, subject);
4147
4431
  const ranked = aiStockRank(found, blocked, `${input.seed}:${index}:${request.query}`, judgement);
4148
4432
  // A team's portraits are of different people where the hits allow (AGL-3856).
@@ -4177,6 +4461,9 @@ const EXTENSIONS = {
4177
4461
  const repeats = Boolean(slot.product) || slot.role === 'hero';
4178
4462
  const sure = slot.product ? requests.filter((request)=>request.tier !== 'weak') : requests;
4179
4463
  const unsure = slot.product ? requests.filter((request)=>request.tier === 'weak') : [];
4464
+ // 3. Nothing of its own world at all: the wider worlds (AGL-3923), new
4465
+ // and then, for a hero, one the job already shows.
4466
+ const widened = widerSearches(slot, subject, sectionName);
4180
4467
  const phases = [
4181
4468
  [
4182
4469
  sure,
@@ -4197,6 +4484,18 @@ const EXTENSIONS = {
4197
4484
  unsure,
4198
4485
  true
4199
4486
  ]
4487
+ ] : [],
4488
+ ...widened.length ? [
4489
+ [
4490
+ widened,
4491
+ false
4492
+ ]
4493
+ ] : [],
4494
+ ...widened.length && repeats ? [
4495
+ [
4496
+ widened,
4497
+ true
4498
+ ]
4200
4499
  ] : []
4201
4500
  ];
4202
4501
  for (const [tried, repeat] of phases){
@@ -4265,7 +4564,10 @@ const EXTENSIONS = {
4265
4564
  // The library answered and nothing in it fits: no photo rather than a
4266
4565
  // starter that shows another business (AGL-3831). A library that never
4267
4566
  // answered, or a site that refused to store, leaves the starters.
4268
- return answered && storing ? photos.map((photo)=>photo != null ? photo : AI_LAYOUT_NO_PHOTO) : photos;
4567
+ // A slot whose fitting photo could not be copied was failed by the
4568
+ // library, as one that never answered is, and takes a starter rather
4569
+ // than lose its picture (AGL-3923).
4570
+ return answered && storing ? photos.map((photo, index)=>photo != null ? photo : uncopied.has(slots[index]) ? null : AI_LAYOUT_NO_PHOTO) : photos;
4269
4571
  };
4270
4572
  }
4271
4573