@aglyn/plugins-ai 1.0.0-beta.272 → 1.0.0-beta.274

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 (52) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-site-start-card.component.js +237 -234
  3. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  4. package/src/lib/jobs/ai-empty-site-sweep.d.ts +12 -1
  5. package/src/lib/jobs/ai-empty-site-sweep.js +15 -3
  6. package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -1
  7. package/src/lib/jobs/ai-job-page-step.js +21 -2
  8. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  9. package/src/lib/jobs/ai-job-seo-step.js +8 -4
  10. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  11. package/src/lib/jobs/ai-job-site-datasets.d.ts +39 -1
  12. package/src/lib/jobs/ai-job-site-datasets.js +111 -15
  13. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  14. package/src/lib/jobs/ai-job-site-step.d.ts +12 -3
  15. package/src/lib/jobs/ai-job-site-step.js +85 -11
  16. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  17. package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
  18. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  19. package/src/lib/jobs/ai-jobs.d.ts +3 -0
  20. package/src/lib/jobs/ai-jobs.js +4 -0
  21. package/src/lib/jobs/ai-jobs.js.map +1 -1
  22. package/src/lib/jobs/ai-layout-owner-photos.js +4 -4
  23. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  24. package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
  25. package/src/lib/jobs/ai-layout-stock-photos.js +114 -43
  26. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  27. package/src/lib/jobs/ai-site-late-photos.d.ts +143 -0
  28. package/src/lib/jobs/ai-site-late-photos.js +486 -0
  29. package/src/lib/jobs/ai-site-late-photos.js.map +1 -0
  30. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  31. package/src/lib/jobs/ai-site-publish.js +123 -5
  32. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  33. package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.d.ts +83 -0
  34. package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js +1553 -0
  35. package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js.map +1 -0
  36. package/src/lib/layout-language/ai-layout-pictures.d.ts +50 -13
  37. package/src/lib/layout-language/ai-layout-pictures.js +81 -19
  38. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  39. package/src/lib/model/ai-jobs.types.d.ts +10 -0
  40. package/src/lib/model/ai-jobs.types.js.map +1 -1
  41. package/src/lib/model/ai-site-photos.d.ts +119 -0
  42. package/src/lib/model/ai-site-photos.js +128 -0
  43. package/src/lib/model/ai-site-photos.js.map +1 -0
  44. package/src/lib/runtime/ai-doctrine-validators.d.ts +3 -1
  45. package/src/lib/runtime/ai-doctrine-validators.js +3 -1
  46. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  47. package/src/lib/runtime/seo-fields.d.ts +29 -4
  48. package/src/lib/runtime/seo-fields.js +58 -11
  49. package/src/lib/runtime/seo-fields.js.map +1 -1
  50. package/src/lib/tools/ai-seo-tool.d.ts +25 -1
  51. package/src/lib/tools/ai-seo-tool.js +61 -0
  52. package/src/lib/tools/ai-seo-tool.js.map +1 -1
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ /**
18
+ * THE PICTURES A SITE START LEFT ON A STARTER PHOTO (AGL-3988).
19
+ *
20
+ * A guided start's pages and its datasets' records are each built in a pass
21
+ * of their own, minutes apart. A picture the stock library could not fill in
22
+ * its pass took a starter photo there: its picks' downloads refused by the
23
+ * library's image hosts in a burst, or no photo of the job's own to take
24
+ * yet. The v1.0.0-beta.271 Laurel Fork Photography start (2026-10-19 23:05Z)
25
+ * filed all twelve "Gallery collections" covers and its home hero under an
26
+ * office team and a desk: pixabay.com's Cloudflare answered 429 to 22
27
+ * downloads while the covers were built, before the job had copied any
28
+ * photo, and to 11 more on the home page, the first page built.
29
+ *
30
+ * Each such picture is written down here, with the picks the hosts refused,
31
+ * on the job, by the machine, in the write that records its pass. The site
32
+ * step's late photo pass (`jobs/ai-site-late-photos.ts`) reads it once every
33
+ * page is built and before the site is published: it asks the refused picks
34
+ * for again, a minute after the refusal, and gives every picture still on a
35
+ * starter one of the photos the job copied. Pure.
36
+ */ /** Where a starter photo is served from: the platform's own files, on every site's origin. */ export const AI_SITE_STARTER_PREFIX = '/_static/starter/';
37
+ /** Whether an address is a starter photo's. */ export function aiSiteIsStarterSrc(src) {
38
+ return typeof src === 'string' && src.startsWith(AI_SITE_STARTER_PREFIX);
39
+ }
40
+ /** The most of each a job keeps: a site's pages, each one's pictures, and its lists' records. */ const PAGES_MAX = 12;
41
+ const SLOTS_MAX = 16;
42
+ const LISTS_MAX = 8;
43
+ const RECORDS_MAX = 24;
44
+ const KEYS_MAX = 3;
45
+ /** An entry with its refused picks bounded, and none where it has none. */ function withPicks(entry) {
46
+ var _ref;
47
+ var _entry_refused;
48
+ const copy = _extends({}, entry);
49
+ delete copy.refused;
50
+ const keys = (_ref = (_entry_refused = entry.refused) == null ? void 0 : _entry_refused.keys.slice(0, KEYS_MAX)) != null ? _ref : [];
51
+ return entry.refused && keys.length ? _extends({}, copy, {
52
+ refused: _extends({}, entry.refused, {
53
+ keys
54
+ })
55
+ }) : copy;
56
+ }
57
+ /** A page's starters as a job keeps them: bounded, and none without a picture. */ function pageOf(page) {
58
+ const slots = page.slots.slice(0, SLOTS_MAX).map(withPicks);
59
+ return page.screen && slots.length ? {
60
+ screen: page.screen,
61
+ slots
62
+ } : null;
63
+ }
64
+ /** Whether a list still holds a starter on any record. */ function listHoldsStarters(list) {
65
+ return list.records.some((record)=>aiSiteIsStarterSrc(record.value));
66
+ }
67
+ /**
68
+ * The job's photos state once a unit reported its own (AGL-3988): a page's
69
+ * or a list's entry replaces the one it had, by its draft or its dataset;
70
+ * every other entry stays. `null` where neither holds anything.
71
+ */ export function aiSiteMergePhotos(previous, next) {
72
+ var _ref, _ref1, _ref2, _ref3, _ref4;
73
+ const pages = new Map(((_ref = previous == null ? void 0 : previous.pages) != null ? _ref : []).map((page)=>[
74
+ page.screen,
75
+ page
76
+ ]));
77
+ for (const page of (_ref1 = next == null ? void 0 : next.pages) != null ? _ref1 : []){
78
+ const kept = pageOf(page);
79
+ if (kept) pages.set(kept.screen, kept);
80
+ else pages.delete(page.screen);
81
+ }
82
+ const lists = new Map(((_ref2 = previous == null ? void 0 : previous.lists) != null ? _ref2 : []).map((list)=>[
83
+ list.dataset,
84
+ list
85
+ ]));
86
+ for (const list of (_ref3 = next == null ? void 0 : next.lists) != null ? _ref3 : []){
87
+ if (list.dataset && list.imageField && listHoldsStarters(list)) {
88
+ lists.set(list.dataset, _extends({}, list, {
89
+ records: list.records.slice(0, RECORDS_MAX).map(withPicks)
90
+ }));
91
+ } else lists.delete(list.dataset);
92
+ }
93
+ const merged = {
94
+ pages: [
95
+ ...pages.values()
96
+ ].slice(0, PAGES_MAX),
97
+ lists: [
98
+ ...lists.values()
99
+ ].slice(0, LISTS_MAX)
100
+ };
101
+ const latePasses = (_ref4 = next == null ? void 0 : next.latePasses) != null ? _ref4 : previous == null ? void 0 : previous.latePasses;
102
+ if (latePasses) merged.latePasses = latePasses;
103
+ return merged.pages.length || merged.lists.length ? merged : null;
104
+ }
105
+ /**
106
+ * Whether a site job owes its late photo pass: a page picture or a list
107
+ * record it left on a starter. With `pageIds`, only a picture of one of
108
+ * those pages counts: a page that was not built is never published.
109
+ */ export function aiSitePhotosOwed(photos, pageIds) {
110
+ const built = pageIds ? new Set(pageIds) : null;
111
+ return Boolean((photos == null ? void 0 : photos.pages.some((page)=>page.slots.length && (!built || built.has(page.screen)))) || (photos == null ? void 0 : photos.lists.some(listHoldsStarters)));
112
+ }
113
+ /** When the newest refusal the state holds was made, in ms; `null` where none was. */ export function aiSitePhotosLastRefusalMs(photos) {
114
+ var _ref, _ref1;
115
+ const times = [
116
+ ...((_ref = photos == null ? void 0 : photos.pages) != null ? _ref : []).flatMap((page)=>page.slots.map((slot)=>{
117
+ var _slot_refused;
118
+ return (_slot_refused = slot.refused) == null ? void 0 : _slot_refused.at;
119
+ })),
120
+ ...((_ref1 = photos == null ? void 0 : photos.lists) != null ? _ref1 : []).flatMap((list)=>list.records.map((record)=>{
121
+ var _record_refused;
122
+ return (_record_refused = record.refused) == null ? void 0 : _record_refused.at;
123
+ }))
124
+ ].map((at)=>at ? Date.parse(at) : NaN).filter((ms)=>Number.isFinite(ms));
125
+ return times.length ? Math.max(...times) : null;
126
+ }
127
+
128
+ //# sourceMappingURL=ai-site-photos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-photos.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { StockPhotoSearchRequest } from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\nimport type { AiLayoutPicturePhoto, AiLayoutPictureRole } from '../layout-language/ai-layout-pictures'\n\n/**\n * THE PICTURES A SITE START LEFT ON A STARTER PHOTO (AGL-3988).\n *\n * A guided start's pages and its datasets' records are each built in a pass\n * of their own, minutes apart. A picture the stock library could not fill in\n * its pass took a starter photo there: its picks' downloads refused by the\n * library's image hosts in a burst, or no photo of the job's own to take\n * yet. The v1.0.0-beta.271 Laurel Fork Photography start (2026-10-19 23:05Z)\n * filed all twelve \"Gallery collections\" covers and its home hero under an\n * office team and a desk: pixabay.com's Cloudflare answered 429 to 22\n * downloads while the covers were built, before the job had copied any\n * photo, and to 11 more on the home page, the first page built.\n *\n * Each such picture is written down here, with the picks the hosts refused,\n * on the job, by the machine, in the write that records its pass. The site\n * step's late photo pass (`jobs/ai-site-late-photos.ts`) reads it once every\n * page is built and before the site is published: it asks the refused picks\n * for again, a minute after the refusal, and gives every picture still on a\n * starter one of the photos the job copied. Pure.\n */\n\n/** Where a starter photo is served from: the platform's own files, on every site's origin. */\nexport const AI_SITE_STARTER_PREFIX = '/_static/starter/'\n\n/** Whether an address is a starter photo's. */\nexport function aiSiteIsStarterSrc(src: unknown): boolean {\n return typeof src === 'string' && src.startsWith(AI_SITE_STARTER_PREFIX)\n}\n\n/** The picks a slot kept in hand whose downloads the library's image hosts refused, to ask for again. */\nexport interface AiStockRefusedPicks {\n /** The search they came from, as it was sent: asked again, the provider's cache answers it. */\n request: StockPhotoSearchRequest\n /** The picks' source keys (`pixabay:5401602`), best first. */\n keys: string[]\n /** When the last of them was refused, ISO. */\n at: string\n}\n\n/** A picture of one of the start's pages that holds a starter photo. */\nexport interface AiSiteStarterSlot {\n /** The `image` node. */\n image: string\n /** What the picture was to show, in the slot's own words: a starter's alt text is the starter's. */\n words: string\n role: AiLayoutPictureRole\n /** The frame's shape, width over height. */\n aspect: number\n refused?: AiStockRefusedPicks\n}\n\n/** A page of the start with the pictures it left on a starter. */\nexport interface AiSiteStarterPage {\n /** The page's draft. */\n screen: string\n slots: AiSiteStarterSlot[]\n}\n\n/** A record of a pictured list, as its photo was written. */\nexport interface AiSiteListRecord {\n /** The record's name: its first field's value. */\n name: string\n /** What the list files it under, where it files its records (AGL-3856). */\n category?: string\n /** The value its photo field holds: a starter's path, or the library photo's CDN path. */\n value: string\n /** The library photo it shows, with its size and words; absent on a starter. */\n photo?: AiLayoutPicturePhoto\n refused?: AiStockRefusedPicks\n}\n\n/** A pictured list of the start, one of whose records holds a starter photo. */\nexport interface AiSiteStarterList {\n /** The dataset. */\n dataset: string\n /** Its photo field, by id. */\n imageField: string\n /** The field its records keep what their photo shows in, by id (AGL-3856). */\n altField?: string\n /** A list of the business's people: a record of it never takes a photo of anything but a person. */\n people?: boolean\n /** Every record, in order, so a record's neighbors are known. */\n records: AiSiteListRecord[]\n}\n\n/** What a site job holds on its pictures that took a starter, until its late photo pass. */\nexport interface AiSitePhotos {\n pages: AiSiteStarterPage[]\n lists: AiSiteStarterList[]\n /**\n * The late photo passes that ran out of time and handed this back (AGL-3988).\n * The site step publishes on the last pass its bound gives them, whatever\n * the pictures hold then, so a burst that never clears never holds a site\n * back.\n */\n latePasses?: number\n}\n\n/** The most of each a job keeps: a site's pages, each one's pictures, and its lists' records. */\nconst PAGES_MAX = 12\nconst SLOTS_MAX = 16\nconst LISTS_MAX = 8\nconst RECORDS_MAX = 24\nconst KEYS_MAX = 3\n\n/** An entry with its refused picks bounded, and none where it has none. */\nfunction withPicks<T extends { refused?: AiStockRefusedPicks }>(entry: T): T {\n const copy = { ...entry }\n delete copy.refused\n const keys = entry.refused?.keys.slice(0, KEYS_MAX) ?? []\n return entry.refused && keys.length ? { ...copy, refused: { ...entry.refused, keys } } : copy\n}\n\n/** A page's starters as a job keeps them: bounded, and none without a picture. */\nfunction pageOf(page: AiSiteStarterPage): AiSiteStarterPage | null {\n const slots = page.slots.slice(0, SLOTS_MAX).map(withPicks)\n return page.screen && slots.length ? { screen: page.screen, slots } : null\n}\n\n/** Whether a list still holds a starter on any record. */\nfunction listHoldsStarters(list: AiSiteStarterList): boolean {\n return list.records.some((record) => aiSiteIsStarterSrc(record.value))\n}\n\n/**\n * The job's photos state once a unit reported its own (AGL-3988): a page's\n * or a list's entry replaces the one it had, by its draft or its dataset;\n * every other entry stays. `null` where neither holds anything.\n */\nexport function aiSiteMergePhotos(\n previous: AiSitePhotos | null | undefined,\n next: Partial<AiSitePhotos> | null | undefined,\n): AiSitePhotos | null {\n const pages = new Map((previous?.pages ?? []).map((page) => [page.screen, page]))\n for (const page of next?.pages ?? []) {\n const kept = pageOf(page)\n if (kept) pages.set(kept.screen, kept)\n else pages.delete(page.screen)\n }\n const lists = new Map((previous?.lists ?? []).map((list) => [list.dataset, list]))\n for (const list of next?.lists ?? []) {\n if (list.dataset && list.imageField && listHoldsStarters(list)) {\n lists.set(list.dataset, { ...list, records: list.records.slice(0, RECORDS_MAX).map(withPicks) })\n } else lists.delete(list.dataset)\n }\n const merged: AiSitePhotos = { pages: [...pages.values()].slice(0, PAGES_MAX), lists: [...lists.values()].slice(0, LISTS_MAX) }\n const latePasses = next?.latePasses ?? previous?.latePasses\n if (latePasses) merged.latePasses = latePasses\n return merged.pages.length || merged.lists.length ? merged : null\n}\n\n/**\n * Whether a site job owes its late photo pass: a page picture or a list\n * record it left on a starter. With `pageIds`, only a picture of one of\n * those pages counts: a page that was not built is never published.\n */\nexport function aiSitePhotosOwed(photos: AiSitePhotos | null | undefined, pageIds?: readonly string[]): boolean {\n const built = pageIds ? new Set(pageIds) : null\n return Boolean(\n photos?.pages.some((page) => page.slots.length && (!built || built.has(page.screen))) || photos?.lists.some(listHoldsStarters),\n )\n}\n\n/** When the newest refusal the state holds was made, in ms; `null` where none was. */\nexport function aiSitePhotosLastRefusalMs(photos: AiSitePhotos | null | undefined): number | null {\n const times = [\n ...(photos?.pages ?? []).flatMap((page) => page.slots.map((slot) => slot.refused?.at)),\n ...(photos?.lists ?? []).flatMap((list) => list.records.map((record) => record.refused?.at)),\n ]\n .map((at) => (at ? Date.parse(at) : NaN))\n .filter((ms) => Number.isFinite(ms))\n return times.length ? Math.max(...times) : null\n}\n"],"names":["AI_SITE_STARTER_PREFIX","aiSiteIsStarterSrc","src","startsWith","PAGES_MAX","SLOTS_MAX","LISTS_MAX","RECORDS_MAX","KEYS_MAX","withPicks","entry","copy","refused","keys","slice","length","pageOf","page","slots","map","screen","listHoldsStarters","list","records","some","record","value","aiSiteMergePhotos","previous","next","pages","Map","kept","set","delete","lists","dataset","imageField","merged","values","latePasses","aiSitePhotosOwed","photos","pageIds","built","Set","Boolean","has","aiSitePhotosLastRefusalMs","times","flatMap","slot","at","Date","parse","NaN","filter","ms","Number","isFinite","Math","max"],"mappings":"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"}
@@ -619,7 +619,9 @@ export declare function aiDanglingWord(line: string, language?: string | null):
619
619
  * past half of `max` is cut at `max` itself, mid-word.
620
620
  *
621
621
  * The page step clips what it stores with it; `aiSettlePlanSeo` clips a plan
622
- * with it only where the cut lands on a whole word (AGL-3837).
622
+ * with it only where the cut lands on a whole word (AGL-3837); and an SEO
623
+ * listing or audit batch whose re-ask still ran past a length is fitted with
624
+ * it (`aiFitSeoText`, AGL-4016).
623
625
  */
624
626
  export declare function aiClipAtWord(value: string, max: number, language?: string | null): string;
625
627
  /**
@@ -2477,7 +2477,9 @@ function danglingTableFor(language) {
2477
2477
  * past half of `max` is cut at `max` itself, mid-word.
2478
2478
  *
2479
2479
  * The page step clips what it stores with it; `aiSettlePlanSeo` clips a plan
2480
- * with it only where the cut lands on a whole word (AGL-3837).
2480
+ * with it only where the cut lands on a whole word (AGL-3837); and an SEO
2481
+ * listing or audit batch whose re-ask still ran past a length is fitted with
2482
+ * it (`aiFitSeoText`, AGL-4016).
2481
2483
  */ export function aiClipAtWord(value, max, language) {
2482
2484
  const text = value.trim();
2483
2485
  if (text.length <= max) return text;