@aglyn/plugins-ai 1.0.0-beta.264 → 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.
- package/package.json +18 -18
- package/src/lib/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-job-site.d.ts +8 -1
- package/src/lib/components/ai-job-site.js +7 -0
- package/src/lib/components/ai-job-site.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +37 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
- package/src/lib/components/ai-site-start-card.component.js +279 -240
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +114 -0
- package/src/lib/components/ai-site-start-steps.component.js +480 -0
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
- package/src/lib/components/ai-site-start-tracking.d.ts +122 -0
- package/src/lib/components/ai-site-start-tracking.js +296 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -0
- package/src/lib/components/ai-site-style-previews.d.ts +26 -0
- package/src/lib/components/ai-site-style-previews.js +115 -0
- package/src/lib/components/ai-site-style-previews.js.map +1 -0
- package/src/lib/components/assist-jobs-drawer.component.js +24 -2
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/insights/ai-insight-digest.d.ts +9 -0
- package/src/lib/insights/ai-insight-digest.js +35 -11
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
- package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
- package/src/lib/jobs/ai-job-page-language.js +20 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
- package/src/lib/jobs/ai-job-plan-step.js +99 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +82 -1
- package/src/lib/jobs/ai-job-site-datasets.js +276 -14
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
- package/src/lib/jobs/ai-jobs-beat.js +23 -6
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +46 -7
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -10
- package/src/lib/jobs/ai-layout-stock-photos.js +1046 -102
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +31 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +208 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +49 -12
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +19 -13
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +48 -0
- package/src/lib/layout-language/ai-layout-listings.js +91 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +15 -0
- package/src/lib/layout-language/ai-layout-pictures.js +6 -2
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +20 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
- package/src/lib/model/ai-site-home-arcs.js +741 -0
- package/src/lib/model/ai-site-home-arcs.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +18 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +9 -6
- package/src/lib/model/ai-site-job.js +8 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +24 -24
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
- package/src/lib/model/ai-site-start-handoff.js +88 -0
- package/src/lib/model/ai-site-start-handoff.js.map +1 -0
- package/src/lib/model/ai-site-start-steps.d.ts +67 -0
- package/src/lib/model/ai-site-start-steps.js +127 -0
- package/src/lib/model/ai-site-start-steps.js.map +1 -0
- package/src/lib/model/ai-site-start.js +4 -1
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
- package/src/lib/runtime/ai-doctrine-validators.js +119 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +9 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +5 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +6 -0
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +2 -2
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -0
- package/src/lib/usage/assist-free-taste.js.map +1 -1
|
@@ -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).
|
|
@@ -1291,6 +1303,19 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1291
1303
|
'photograph'
|
|
1292
1304
|
],
|
|
1293
1305
|
scenes: true,
|
|
1306
|
+
maker: [
|
|
1307
|
+
'photographer',
|
|
1308
|
+
'photographers',
|
|
1309
|
+
'camera',
|
|
1310
|
+
'cameras',
|
|
1311
|
+
'lens',
|
|
1312
|
+
'lenses',
|
|
1313
|
+
'tripod',
|
|
1314
|
+
'dslr',
|
|
1315
|
+
'slr',
|
|
1316
|
+
'journalist',
|
|
1317
|
+
'paparazzi'
|
|
1318
|
+
],
|
|
1294
1319
|
kin: [
|
|
1295
1320
|
'photographer',
|
|
1296
1321
|
'photography',
|
|
@@ -1490,6 +1515,16 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1490
1515
|
]
|
|
1491
1516
|
}, groups.some((group)=>group.scenes) ? {
|
|
1492
1517
|
scenes: true
|
|
1518
|
+
} : {}, groups.some((group)=>{
|
|
1519
|
+
var _group_maker;
|
|
1520
|
+
return (_group_maker = group.maker) == null ? void 0 : _group_maker.length;
|
|
1521
|
+
}) ? {
|
|
1522
|
+
maker: [
|
|
1523
|
+
...new Set(groups.flatMap((group)=>{
|
|
1524
|
+
var _group_maker;
|
|
1525
|
+
return (_group_maker = group.maker) != null ? _group_maker : [];
|
|
1526
|
+
}).map(aiStockStem))
|
|
1527
|
+
]
|
|
1493
1528
|
} : {}, {
|
|
1494
1529
|
named: [
|
|
1495
1530
|
...new Set(stemList(businessType))
|
|
@@ -1584,6 +1619,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1584
1619
|
'mother',
|
|
1585
1620
|
'father',
|
|
1586
1621
|
'parents',
|
|
1622
|
+
'mom',
|
|
1623
|
+
'dad',
|
|
1587
1624
|
'daughter',
|
|
1588
1625
|
'son',
|
|
1589
1626
|
'baby',
|
|
@@ -1625,6 +1662,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1625
1662
|
'elope',
|
|
1626
1663
|
'eloping',
|
|
1627
1664
|
'bride',
|
|
1665
|
+
'brides',
|
|
1666
|
+
'bridal',
|
|
1628
1667
|
'groom',
|
|
1629
1668
|
'engagement',
|
|
1630
1669
|
'engaged',
|
|
@@ -1632,11 +1671,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1632
1671
|
'couples',
|
|
1633
1672
|
'newlywed',
|
|
1634
1673
|
'newlyweds',
|
|
1635
|
-
'honeymoon'
|
|
1674
|
+
'honeymoon',
|
|
1675
|
+
'ceremony',
|
|
1676
|
+
'ceremonies',
|
|
1677
|
+
'reception',
|
|
1678
|
+
'receptions',
|
|
1679
|
+
'vows'
|
|
1636
1680
|
],
|
|
1637
1681
|
query: (named)=>[
|
|
1638
1682
|
'wedding',
|
|
1683
|
+
'weddings',
|
|
1639
1684
|
'bride',
|
|
1685
|
+
'bridal',
|
|
1640
1686
|
'groom'
|
|
1641
1687
|
].some((word)=>named.has(word)) ? 'wedding couple' : 'couple',
|
|
1642
1688
|
subject: 'couple',
|
|
@@ -1666,6 +1712,273 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1666
1712
|
shun: []
|
|
1667
1713
|
}
|
|
1668
1714
|
];
|
|
1715
|
+
/**
|
|
1716
|
+
* A portrait session (AGL-3856), read for a record's cover alone: "Milestone
|
|
1717
|
+
* Portraits" is a person's portrait, never a cat's. A page's "portrait of the
|
|
1718
|
+
* photographer" keeps its own words.
|
|
1719
|
+
*/ const PORTRAIT_SCENE = {
|
|
1720
|
+
words: [
|
|
1721
|
+
'portrait',
|
|
1722
|
+
'portraits',
|
|
1723
|
+
'headshot',
|
|
1724
|
+
'headshots'
|
|
1725
|
+
],
|
|
1726
|
+
query: ()=>'portrait',
|
|
1727
|
+
subject: 'portrait',
|
|
1728
|
+
lead: [
|
|
1729
|
+
'portrait',
|
|
1730
|
+
'woman',
|
|
1731
|
+
'man',
|
|
1732
|
+
'couple',
|
|
1733
|
+
'person',
|
|
1734
|
+
'people'
|
|
1735
|
+
],
|
|
1736
|
+
people: [
|
|
1737
|
+
'woman',
|
|
1738
|
+
'women',
|
|
1739
|
+
'man',
|
|
1740
|
+
'men',
|
|
1741
|
+
'couple',
|
|
1742
|
+
'person',
|
|
1743
|
+
'people',
|
|
1744
|
+
'bride',
|
|
1745
|
+
'groom',
|
|
1746
|
+
'mother',
|
|
1747
|
+
'father',
|
|
1748
|
+
'family'
|
|
1749
|
+
],
|
|
1750
|
+
shun: []
|
|
1751
|
+
};
|
|
1752
|
+
/**
|
|
1753
|
+
* Words a photo of a child is tagged with (AGL-3856), as stems. Pixabay tags
|
|
1754
|
+
* a schoolgirl "girl", "schoolgirl" or "school girl" far more often than
|
|
1755
|
+
* "child", so a site for adults that names a school let children in: the
|
|
1756
|
+
* beta.264 Brightpath Adult Learning Center's "Meet your teachers" showed a
|
|
1757
|
+
* teen graduate, a schoolgirl and kids.
|
|
1758
|
+
*/ const CHILD_STEMS = [
|
|
1759
|
+
...new Set(('child children childhood kid kids baby babies toddler toddlers infant infants newborn boy boys girl girls ' + 'schoolgirl schoolgirls schoolboy schoolboys schoolchild schoolchildren schoolkid schoolkids pupil pupils ' + 'teen teens teenager teenagers teenage preteen youth kindergarten preschool').split(' ').map(aiStockStem))
|
|
1760
|
+
];
|
|
1761
|
+
/** Words of which a photo of one of the business's people names one: an adult, at work or in their role. */ const ADULT_PEOPLE_STEMS = [
|
|
1762
|
+
...new Set(('man men woman women adult adults businessman businesswoman businessmen professional ' + 'professionals staff worker workers employee employees team colleague colleagues coworker coworkers ' + 'teacher teachers instructor instructors tutor tutors lecturer professor educator faculty coach coaches ' + 'trainer trainers chef chefs cook baker barista doctor doctors dentist nurse nurses hygienist therapist ' + 'physician stylist hairdresser barber artist musician photographer entrepreneur founder owner manager ' + 'leader speaker volunteer volunteers mechanic electrician plumber carpenter craftsman technician').split(' ').map(aiStockStem))
|
|
1763
|
+
];
|
|
1764
|
+
/**
|
|
1765
|
+
* The roles a list of the business's people names (AGL-3856): the words that
|
|
1766
|
+
* name each, the words a photo of someone in it is tagged with, and the word
|
|
1767
|
+
* a search for them leads with, where the libraries know one. A teacher is
|
|
1768
|
+
* shown teaching, a coach coaching, never a student or a whistle. A team or
|
|
1769
|
+
* a staff is anyone at work: its words are preferred, never required.
|
|
1770
|
+
*
|
|
1771
|
+
* A role whose photos are often of someone merely tagged with it carries the
|
|
1772
|
+
* words of it being done, one of which the photo's first tags must name
|
|
1773
|
+
* (`scene`), and the words of something else being done, which none of them
|
|
1774
|
+
* may (`refuse`): the beta.265 Northgate Adult Learning Center's "Meet your
|
|
1775
|
+
* teachers" placed a woman painting at an easel ("artist, teacher of
|
|
1776
|
+
* drawing"), a model in a traditional dress ("woman, model, show, teacher")
|
|
1777
|
+
* and an elderly man's portrait ("teacher, professor, man, senior").
|
|
1778
|
+
*/ const PEOPLE_ROLES = [
|
|
1779
|
+
{
|
|
1780
|
+
words: [
|
|
1781
|
+
'instructor',
|
|
1782
|
+
'teacher',
|
|
1783
|
+
'tutor',
|
|
1784
|
+
'educator',
|
|
1785
|
+
'faculty',
|
|
1786
|
+
'professor',
|
|
1787
|
+
'lecturer',
|
|
1788
|
+
'mentor'
|
|
1789
|
+
],
|
|
1790
|
+
kin: [
|
|
1791
|
+
'teacher',
|
|
1792
|
+
'instructor',
|
|
1793
|
+
'tutor',
|
|
1794
|
+
'educator',
|
|
1795
|
+
'professor',
|
|
1796
|
+
'lecturer',
|
|
1797
|
+
'teaching',
|
|
1798
|
+
'lecture',
|
|
1799
|
+
'faculty'
|
|
1800
|
+
],
|
|
1801
|
+
query: 'teacher',
|
|
1802
|
+
scene: [
|
|
1803
|
+
'teaching',
|
|
1804
|
+
'teach',
|
|
1805
|
+
'classroom',
|
|
1806
|
+
'class',
|
|
1807
|
+
'lecture',
|
|
1808
|
+
'lesson',
|
|
1809
|
+
'lessons',
|
|
1810
|
+
'tutor',
|
|
1811
|
+
'tutoring',
|
|
1812
|
+
'blackboard',
|
|
1813
|
+
'chalkboard',
|
|
1814
|
+
'whiteboard',
|
|
1815
|
+
'board',
|
|
1816
|
+
'seminar',
|
|
1817
|
+
'student',
|
|
1818
|
+
'students',
|
|
1819
|
+
'learner',
|
|
1820
|
+
'learners',
|
|
1821
|
+
'presentation'
|
|
1822
|
+
],
|
|
1823
|
+
refuse: [
|
|
1824
|
+
'model',
|
|
1825
|
+
'modeling',
|
|
1826
|
+
'fashion',
|
|
1827
|
+
'pose',
|
|
1828
|
+
'posing',
|
|
1829
|
+
'beauty',
|
|
1830
|
+
'glamour',
|
|
1831
|
+
'cosplay',
|
|
1832
|
+
'artist',
|
|
1833
|
+
'artists',
|
|
1834
|
+
'painter',
|
|
1835
|
+
'painting',
|
|
1836
|
+
'paint',
|
|
1837
|
+
'easel',
|
|
1838
|
+
'canvas',
|
|
1839
|
+
'draw',
|
|
1840
|
+
'draws',
|
|
1841
|
+
'drawing',
|
|
1842
|
+
'calligraphy'
|
|
1843
|
+
]
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
words: [
|
|
1847
|
+
'coach',
|
|
1848
|
+
'trainer'
|
|
1849
|
+
],
|
|
1850
|
+
kin: [
|
|
1851
|
+
'coach',
|
|
1852
|
+
'coaching',
|
|
1853
|
+
'trainer',
|
|
1854
|
+
'instructor'
|
|
1855
|
+
],
|
|
1856
|
+
query: 'coach'
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
words: [
|
|
1860
|
+
'chef',
|
|
1861
|
+
'cook',
|
|
1862
|
+
'baker'
|
|
1863
|
+
],
|
|
1864
|
+
kin: [
|
|
1865
|
+
'chef',
|
|
1866
|
+
'cook',
|
|
1867
|
+
'cooking',
|
|
1868
|
+
'baker',
|
|
1869
|
+
'baking'
|
|
1870
|
+
],
|
|
1871
|
+
query: 'chef'
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
words: [
|
|
1875
|
+
'stylist',
|
|
1876
|
+
'barber',
|
|
1877
|
+
'hairdresser',
|
|
1878
|
+
'colorist'
|
|
1879
|
+
],
|
|
1880
|
+
kin: [
|
|
1881
|
+
'stylist',
|
|
1882
|
+
'barber',
|
|
1883
|
+
'hairdresser',
|
|
1884
|
+
'hairstylist',
|
|
1885
|
+
'haircut'
|
|
1886
|
+
],
|
|
1887
|
+
query: 'hairdresser'
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
words: [
|
|
1891
|
+
'doctor',
|
|
1892
|
+
'dentist',
|
|
1893
|
+
'hygienist',
|
|
1894
|
+
'nurse',
|
|
1895
|
+
'therapist',
|
|
1896
|
+
'practitioner',
|
|
1897
|
+
'physician'
|
|
1898
|
+
],
|
|
1899
|
+
kin: [
|
|
1900
|
+
'doctor',
|
|
1901
|
+
'dentist',
|
|
1902
|
+
'hygienist',
|
|
1903
|
+
'nurse',
|
|
1904
|
+
'therapist',
|
|
1905
|
+
'physician',
|
|
1906
|
+
'medical'
|
|
1907
|
+
]
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
words: [
|
|
1911
|
+
'staff',
|
|
1912
|
+
'team',
|
|
1913
|
+
'crew',
|
|
1914
|
+
'member',
|
|
1915
|
+
'manager',
|
|
1916
|
+
'founder',
|
|
1917
|
+
'owner',
|
|
1918
|
+
'partner',
|
|
1919
|
+
'associate',
|
|
1920
|
+
'employee'
|
|
1921
|
+
],
|
|
1922
|
+
kin: [
|
|
1923
|
+
'staff',
|
|
1924
|
+
'team',
|
|
1925
|
+
'worker',
|
|
1926
|
+
'employee',
|
|
1927
|
+
'colleague',
|
|
1928
|
+
'coworker',
|
|
1929
|
+
'professional',
|
|
1930
|
+
'businessman',
|
|
1931
|
+
'businesswoman',
|
|
1932
|
+
'owner',
|
|
1933
|
+
'manager'
|
|
1934
|
+
],
|
|
1935
|
+
loose: true
|
|
1936
|
+
}
|
|
1937
|
+
];
|
|
1938
|
+
/** How many of a photo's first tags show a role being done: "teacher, presentation, education, man, students" (AGL-3856). */ export const AI_STOCK_ROLE_TAGS_READ = 6;
|
|
1939
|
+
/**
|
|
1940
|
+
* The role a person's words say, else the first of the other texts that names
|
|
1941
|
+
* one (their list's name): "Online learning coach" of the "Instructors" list
|
|
1942
|
+
* is a coach. `null` where none names a listed role.
|
|
1943
|
+
*/ export function aiStockPeopleRole(...texts) {
|
|
1944
|
+
for (const text of texts){
|
|
1945
|
+
var _role_query;
|
|
1946
|
+
const named = new Set(stemList(text));
|
|
1947
|
+
const role = PEOPLE_ROLES.find((entry)=>entry.words.some((word)=>named.has(aiStockStem(word))));
|
|
1948
|
+
if (role) return _extends({
|
|
1949
|
+
kin: [
|
|
1950
|
+
...new Set(role.kin.map(aiStockStem))
|
|
1951
|
+
],
|
|
1952
|
+
query: (_role_query = role.query) != null ? _role_query : '',
|
|
1953
|
+
required: !role.loose
|
|
1954
|
+
}, role.scene ? {
|
|
1955
|
+
scene: [
|
|
1956
|
+
...new Set(role.scene.map(aiStockStem))
|
|
1957
|
+
]
|
|
1958
|
+
} : {}, role.refuse ? {
|
|
1959
|
+
refuse: [
|
|
1960
|
+
...new Set(role.refuse.map(aiStockStem))
|
|
1961
|
+
]
|
|
1962
|
+
} : {});
|
|
1963
|
+
}
|
|
1964
|
+
return null;
|
|
1965
|
+
}
|
|
1966
|
+
/** A role's refusals, less what the person's own words name: a "Drawing instructor" may be drawing. */ export function aiStockRoleRefusals(role, words) {
|
|
1967
|
+
var _role_refuse;
|
|
1968
|
+
const named = new Set(stemList(words));
|
|
1969
|
+
return ((_role_refuse = role.refuse) != null ? _role_refuse : []).filter((stem)=>!named.has(stem));
|
|
1970
|
+
}
|
|
1971
|
+
/** A site's audience in its own words, when it is adults: "Adults finishing a high school diploma". */ const ADULT_AUDIENCE = /\b(?:adults?|grown[- ]?ups?|seniors?|retirees?|older (?:adults|people)|professionals|employees|workforce|job ?seekers)\b/i;
|
|
1972
|
+
/** Words that say a site is for children too, whatever else it says. */ const YOUNG_AUDIENCE = /\b(?:kids?|child(?:ren)?|teens?|teenagers?|youth|young people|famil(?:y|ies)|boys?|girls?|bab(?:y|ies)|toddlers?|k-12|elementary|middle school|preschool|kindergarten|pupils?)\b/i;
|
|
1973
|
+
/**
|
|
1974
|
+
* Whether a site is for adults alone (AGL-3856), as its audience, its name
|
|
1975
|
+
* or its business type says: "Adults finishing a high school diploma",
|
|
1976
|
+
* "Brightpath Adult Learning Center". Then no photo of a child is placed
|
|
1977
|
+
* anywhere on it, whatever else its words name ("school").
|
|
1978
|
+
*/ export function aiStockAdultsOnly(texts) {
|
|
1979
|
+
const said = texts.filter(Boolean).join(' . ');
|
|
1980
|
+
return ADULT_AUDIENCE.test(said) && !YOUNG_AUDIENCE.test(said);
|
|
1981
|
+
}
|
|
1669
1982
|
/** Where a picture of a moment is set, by the words that name it, and the word a search and a photo say it with. */ const SCENE_SETTINGS = [
|
|
1670
1983
|
{
|
|
1671
1984
|
words: [
|
|
@@ -1739,12 +2052,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1739
2052
|
* family in autumn — read from every word, never only the first clause.
|
|
1740
2053
|
* `null` where it names no people.
|
|
1741
2054
|
*/ export function aiStockScene(text) {
|
|
1742
|
-
|
|
1743
|
-
var _SCENE_SETTINGS_find;
|
|
1744
|
-
const named = new Set(text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean));
|
|
2055
|
+
const named = sceneWords(text);
|
|
1745
2056
|
const scene = PEOPLE_SCENES.find((entry)=>entry.words.some((word)=>named.has(word)));
|
|
1746
2057
|
if (!scene) return null;
|
|
1747
|
-
const setting = (
|
|
2058
|
+
const setting = aiStockSetting(text);
|
|
1748
2059
|
const query = scene.query(named);
|
|
1749
2060
|
return {
|
|
1750
2061
|
queries: [
|
|
@@ -1766,6 +2077,69 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1766
2077
|
]
|
|
1767
2078
|
};
|
|
1768
2079
|
}
|
|
2080
|
+
/** A text's lowercase words, letters only. */ const sceneWords = (text)=>new Set(text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean));
|
|
2081
|
+
/** Where a text's words set a picture ({@link SCENE_SETTINGS}): "mountains" of "Couples planning mountain weddings"; empty for none. */ export function aiStockSetting(text) {
|
|
2082
|
+
var _ref;
|
|
2083
|
+
var _SCENE_SETTINGS_find;
|
|
2084
|
+
const named = sceneWords(text);
|
|
2085
|
+
return (_ref = (_SCENE_SETTINGS_find = SCENE_SETTINGS.find((entry)=>entry.words.some((word)=>named.has(word)))) == null ? void 0 : _SCENE_SETTINGS_find.said) != null ? _ref : '';
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* The moment a portfolio's record shows (AGL-3856): read from the record's
|
|
2089
|
+
* own name first, then from what it is filed under, then as a portrait, and
|
|
2090
|
+
* set where its name, its category or else the site's own words say. A
|
|
2091
|
+
* photographer's "Getting Ready" filed under "Weddings and elopements" is a
|
|
2092
|
+
* wedding couple in the mountains; "Milestone Portraits" filed under "Family
|
|
2093
|
+
* portraits" a family. Its searches end on the people alone ("couple"), so a
|
|
2094
|
+
* moment no photo shows in its setting still shows its people. `null` where
|
|
2095
|
+
* neither names people at a moment nor a portrait.
|
|
2096
|
+
*/ export function aiStockRecordScene(name, category = '', siteSetting = '') {
|
|
2097
|
+
var _texts_find, _PEOPLE_SCENES_find, _texts_map_find;
|
|
2098
|
+
const texts = [
|
|
2099
|
+
name,
|
|
2100
|
+
category
|
|
2101
|
+
].filter((text)=>text.trim());
|
|
2102
|
+
const source = (_texts_find = texts.find((text)=>PEOPLE_SCENES.some((entry)=>entry.words.some((word)=>sceneWords(text).has(word))))) != null ? _texts_find : texts.find((text)=>PORTRAIT_SCENE.words.some((word)=>sceneWords(text).has(word)));
|
|
2103
|
+
if (!source) return null;
|
|
2104
|
+
const named = sceneWords(source);
|
|
2105
|
+
const scene = (_PEOPLE_SCENES_find = PEOPLE_SCENES.find((entry)=>entry.words.some((word)=>named.has(word)))) != null ? _PEOPLE_SCENES_find : PORTRAIT_SCENE;
|
|
2106
|
+
const setting = (_texts_map_find = texts.map(aiStockSetting).find(Boolean)) != null ? _texts_map_find : siteSetting;
|
|
2107
|
+
const query = scene.query(named);
|
|
2108
|
+
const all = sceneWords(texts.join(' '));
|
|
2109
|
+
return {
|
|
2110
|
+
queries: [
|
|
2111
|
+
...new Set([
|
|
2112
|
+
setting ? `${query} ${setting}` : '',
|
|
2113
|
+
query,
|
|
2114
|
+
scene.subject
|
|
2115
|
+
].filter(Boolean))
|
|
2116
|
+
],
|
|
2117
|
+
subject: [
|
|
2118
|
+
scene.subject,
|
|
2119
|
+
setting
|
|
2120
|
+
].filter(Boolean).join(' '),
|
|
2121
|
+
object: scene.subject,
|
|
2122
|
+
// A record's cover leads with its people: a wedding's table decor is not "Overlook Ceremonies".
|
|
2123
|
+
lead: scene.lead.filter((phrase)=>!RECORD_SCENE_UNPEOPLED.has(phrase)).map((phrase)=>stemList(phrase).join(' ')),
|
|
2124
|
+
people: scene.people.map(aiStockStem),
|
|
2125
|
+
shun: scene.shun.filter((word)=>!all.has(word)).map(aiStockStem),
|
|
2126
|
+
named: [
|
|
2127
|
+
...new Set(stemList(texts.join(' ')))
|
|
2128
|
+
]
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* A scene's lead words a record's cover never leads with alone
|
|
2133
|
+
* ({@link aiStockRecordScene}): the moment with no one in it (a wedding's table
|
|
2134
|
+
* decor for "Overlook Ceremonies"), or children with no family (a ski class
|
|
2135
|
+
* for "Milestone Portraits").
|
|
2136
|
+
*/ const RECORD_SCENE_UNPEOPLED = words('wedding elopement child children kid kids baby toddler');
|
|
2137
|
+
/**
|
|
2138
|
+
* What a portfolio's record is never OF, among its first tags, unless its own
|
|
2139
|
+
* words name it (AGL-3856): an animal, a toy or a figurine. The beta.265
|
|
2140
|
+
* Laurel Ridge "Gallery collections" covers were a toy frog with a camera, a
|
|
2141
|
+
* toy duck and a cat's portrait.
|
|
2142
|
+
*/ const RECORD_ODD_LEADS = words('animal animals cat cats kitten dog dogs puppy pet pets bird birds frog frogs duck ducks horse horses cow rabbit bunny ' + 'toy toys figurine figurines lego doll dolls teddy puppet miniature');
|
|
1769
2143
|
/** An object's kin phrases as stems, or none. */ const kinStems = (entry)=>entry ? [
|
|
1770
2144
|
...new Set(entry.kin.map((phrase)=>stemList(phrase).join(' ')))
|
|
1771
2145
|
] : [];
|
|
@@ -1987,8 +2361,14 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1987
2361
|
}
|
|
1988
2362
|
return searches;
|
|
1989
2363
|
}
|
|
1990
|
-
/**
|
|
1991
|
-
|
|
2364
|
+
/**
|
|
2365
|
+
* The searches a slot tries, in order, most specific first, each distinct.
|
|
2366
|
+
* A portrait of one of the business's people ({@link AiLayoutPictureSlot.people})
|
|
2367
|
+
* asks first for the role its words or its list's name (`sectionName`) say,
|
|
2368
|
+
* in the site's world: "teacher classroom" for a "Math instructor" (AGL-3856).
|
|
2369
|
+
*/ export function aiStockSearchesFor(slot, terms, subject, part, sectionName = '') {
|
|
2370
|
+
var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _ref2, _terms_maker, _ref3;
|
|
2371
|
+
var _aiStockPeopleRole, _slot_record, _slot_record1;
|
|
1992
2372
|
const orientation = aiStockOrientation(slot.aspect);
|
|
1993
2373
|
const size = slot.role === 'hero' ? {
|
|
1994
2374
|
minWidth: 1600
|
|
@@ -1998,6 +2378,11 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
1998
2378
|
minWidth: 900
|
|
1999
2379
|
};
|
|
2000
2380
|
const domain = new Set(terms.domain);
|
|
2381
|
+
// A portfolio's record of a craft whose work is people at a moment shows the
|
|
2382
|
+
// moment, never the maker: the beta.265 Laurel Ridge "Gallery collections"
|
|
2383
|
+
// searched "photographer mountain elopements" and placed a press
|
|
2384
|
+
// photographer, a toy frog with a camera and a cat (AGL-3856).
|
|
2385
|
+
const work = aiStockShowsTheWork(slot, terms);
|
|
2001
2386
|
const object = aiStockObjectWord(subject);
|
|
2002
2387
|
const subjectStems = stemList(subject);
|
|
2003
2388
|
const kin = object ? objectKin(object, [
|
|
@@ -2017,23 +2402,28 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2017
2402
|
// A collection tile is product-like: held to the shop's category and its
|
|
2018
2403
|
// own object among the first tags, and searched by its object's kin, never
|
|
2019
2404
|
// the category's world alone (AGL-3660).
|
|
2020
|
-
const collection = part === 'collection' && Boolean(terms.craft);
|
|
2021
|
-
const broad = kin && collection ? [] : terms.broad.map((query)=>({
|
|
2405
|
+
const collection = part === 'collection' && Boolean(terms.craft) && !work;
|
|
2406
|
+
const broad = kin && collection || work ? [] : terms.broad.map((query)=>({
|
|
2022
2407
|
query,
|
|
2023
2408
|
broad: true,
|
|
2024
2409
|
category: collection
|
|
2025
2410
|
}));
|
|
2026
2411
|
// The maker at work, held to the craft: never "hands" (AGL-3660).
|
|
2027
|
-
const making = ((_terms_making = terms.making) != null ? _terms_making : []).map((query)=>({
|
|
2412
|
+
const making = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).map((query)=>({
|
|
2028
2413
|
query,
|
|
2029
2414
|
broad: true,
|
|
2030
2415
|
category: true
|
|
2031
2416
|
}));
|
|
2417
|
+
// A portrait in its role, in the site's world, where the libraries know a word for it.
|
|
2418
|
+
const roleWord = slot.people ? (_ref = (_aiStockPeopleRole = aiStockPeopleRole(slot.alt, sectionName)) == null ? void 0 : _aiStockPeopleRole.query) != null ? _ref : '' : '';
|
|
2419
|
+
const role = roleWord ? stemList(terms.business).includes(roleWord) ? terms.business : `${roleWord} ${terms.business}`.trim() : '';
|
|
2032
2420
|
// A scene's people are read from every word of the picture's, never its first clause alone.
|
|
2033
|
-
const scene = terms.scenes && slot.role !== 'hero' && part !== 'collection' ? aiStockScene(slot.alt) : null;
|
|
2421
|
+
const scene = work ? aiStockRecordScene(slot.alt, (_ref1 = (_slot_record = slot.record) == null ? void 0 : _slot_record.category) != null ? _ref1 : '', (_terms_setting = terms.setting) != null ? _terms_setting : '') : terms.scenes && slot.role !== 'hero' && part !== 'collection' ? aiStockScene(slot.alt) : null;
|
|
2422
|
+
// The work's own subject is never qualified by the craft: "getting ready", never "photographer getting ready".
|
|
2423
|
+
const own = work ? (phrase)=>phrase : qualify;
|
|
2034
2424
|
const thing = subject ? [
|
|
2035
2425
|
{
|
|
2036
|
-
query:
|
|
2426
|
+
query: own(subject),
|
|
2037
2427
|
held: true
|
|
2038
2428
|
},
|
|
2039
2429
|
...kin ? kin.queries.map((query)=>({
|
|
@@ -2041,11 +2431,21 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2041
2431
|
held: true
|
|
2042
2432
|
})) : [
|
|
2043
2433
|
{
|
|
2044
|
-
query:
|
|
2434
|
+
query: own(object),
|
|
2045
2435
|
held: true
|
|
2046
2436
|
}
|
|
2047
2437
|
]
|
|
2048
2438
|
] : [];
|
|
2439
|
+
// Never the maker, the camera, an animal or a toy, unless the record names it.
|
|
2440
|
+
const recordNamed = work ? new Set(stemList(`${slot.alt} ${(_ref2 = (_slot_record1 = slot.record) == null ? void 0 : _slot_record1.category) != null ? _ref2 : ''}`)) : null;
|
|
2441
|
+
const refuseLead = recordNamed ? [
|
|
2442
|
+
...new Set([
|
|
2443
|
+
...(_terms_maker = terms.maker) != null ? _terms_maker : [],
|
|
2444
|
+
...[
|
|
2445
|
+
...RECORD_ODD_LEADS
|
|
2446
|
+
].map(aiStockStem)
|
|
2447
|
+
])
|
|
2448
|
+
].filter((stem)=>!recordNamed.has(stem)) : [];
|
|
2049
2449
|
const queries = slot.role === 'hero' ? [
|
|
2050
2450
|
{
|
|
2051
2451
|
query: terms.business,
|
|
@@ -2053,6 +2453,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2053
2453
|
},
|
|
2054
2454
|
...broad
|
|
2055
2455
|
] : slot.role === 'about' ? [
|
|
2456
|
+
...role ? [
|
|
2457
|
+
{
|
|
2458
|
+
query: role,
|
|
2459
|
+
broad: true,
|
|
2460
|
+
people: true
|
|
2461
|
+
}
|
|
2462
|
+
] : [],
|
|
2056
2463
|
...part === 'story' ? making : [],
|
|
2057
2464
|
{
|
|
2058
2465
|
query: `${terms.business} ${subject}`,
|
|
@@ -2076,7 +2483,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2076
2483
|
// A picture of people at a moment asks for them, and where, before anything
|
|
2077
2484
|
// else, and takes only a photo leading with them (AGL-3856): never the
|
|
2078
2485
|
// roses of a "Mountain Wedding Day", nor a bride for a family session.
|
|
2079
|
-
for (const query of (
|
|
2486
|
+
for (const query of (_ref3 = scene == null ? void 0 : scene.queries) != null ? _ref3 : []){
|
|
2080
2487
|
seen.add(`${query}|false`);
|
|
2081
2488
|
searches.push(_extends({
|
|
2082
2489
|
query,
|
|
@@ -2091,6 +2498,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2091
2498
|
allow: scene.named
|
|
2092
2499
|
}, scene.shun.length ? {
|
|
2093
2500
|
refuse: scene.shun
|
|
2501
|
+
} : {}, refuseLead.length ? {
|
|
2502
|
+
refuseLead
|
|
2503
|
+
} : {}, work ? {
|
|
2504
|
+
worldless: true
|
|
2094
2505
|
} : {}));
|
|
2095
2506
|
}
|
|
2096
2507
|
for (const raw of queries){
|
|
@@ -2121,10 +2532,23 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2121
2532
|
category: core
|
|
2122
2533
|
} : {}, slot.role === 'hero' ? {
|
|
2123
2534
|
first: true
|
|
2535
|
+
} : {}, refuseLead.length ? {
|
|
2536
|
+
refuseLead
|
|
2537
|
+
} : {}, work ? {
|
|
2538
|
+
worldless: true
|
|
2124
2539
|
} : {}));
|
|
2125
2540
|
}
|
|
2126
2541
|
return searches;
|
|
2127
2542
|
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Whether a slot is a portfolio's record of a craft whose work is people at a
|
|
2545
|
+
* moment (AGL-3856): a photographer's "Mountain Elopements" shows an
|
|
2546
|
+
* elopement, never the photographer. Searched by the moment its words and its
|
|
2547
|
+
* category name, held to no world of the craft, and never led by the craft's
|
|
2548
|
+
* own words, an animal or a toy unless the record names them.
|
|
2549
|
+
*/ export function aiStockShowsTheWork(slot, terms) {
|
|
2550
|
+
return Boolean(slot.record) && Boolean(terms.scenes) && slot.role !== 'hero' && !slot.people && !slot.product;
|
|
2551
|
+
}
|
|
2128
2552
|
/** The words of a photo's page address at its library, its id dropped: "robin-bird-songbird-garden-winter". */ function pageSlug(pageUrl) {
|
|
2129
2553
|
try {
|
|
2130
2554
|
var _ref;
|
|
@@ -2197,7 +2621,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2197
2621
|
* the subject's two-word phrase 3, each of its other words 1, and the
|
|
2198
2622
|
* site's world 1, plus a quarter when it is among the first tags.
|
|
2199
2623
|
*/ export function aiStockRelevance(photo, judgement) {
|
|
2200
|
-
var _judgement_rivals, _judgement_subject, _judgement_named, _judgement_shun, _judgement_refuse, _judgement_needs, _judgement_shunLead, _judgement_leadAny, _judgement_kin, _judgement_requires, _judgement_category, _judgement_object, _judgement_domain;
|
|
2624
|
+
var _judgement_rivals, _judgement_subject, _judgement_named, _judgement_shun, _judgement_refuse, _judgement_refuseLead, _judgement_needs, _judgement_shunLead, _judgement_leadAny, _judgement_kin, _judgement_requires, _judgement_category, _judgement_object, _judgement_domain, _judgement_roles;
|
|
2625
|
+
var _judgement_roleScene, _judgement_roleRefuse;
|
|
2201
2626
|
const hit = hitWords(photo);
|
|
2202
2627
|
if (hit.rejected) return 0;
|
|
2203
2628
|
if (aiStockSensitive(photo, judgement.named)) return 0;
|
|
@@ -2213,6 +2638,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2213
2638
|
if (((_judgement_shun = judgement.shun) != null ? _judgement_shun : []).some((word)=>hit.all.has(word) && !owned.has(word))) return 0;
|
|
2214
2639
|
// A family session is never a bride, however the photographer's brief names weddings.
|
|
2215
2640
|
if (((_judgement_refuse = judgement.refuse) != null ? _judgement_refuse : []).some((word)=>hit.all.has(word))) return 0;
|
|
2641
|
+
// A teacher's portrait is never OF a child, whatever else it names.
|
|
2642
|
+
if (((_judgement_refuseLead = judgement.refuseLead) != null ? _judgement_refuseLead : []).some((word)=>hit.lead.has(word))) return 0;
|
|
2643
|
+
// A teacher is shown teaching: a classroom, a lecture or students among the
|
|
2644
|
+
// first tags, and never a model, a pose or a painting (AGL-3856).
|
|
2645
|
+
if (((_judgement_roleScene = judgement.roleScene) == null ? void 0 : _judgement_roleScene.length) || ((_judgement_roleRefuse = judgement.roleRefuse) == null ? void 0 : _judgement_roleRefuse.length)) {
|
|
2646
|
+
var _photo_tags, _photo_tags1, _judgement_roleRefuse1;
|
|
2647
|
+
var _judgement_roleScene1;
|
|
2648
|
+
const first = new Set(stemList(((_photo_tags = photo.tags) != null ? _photo_tags : []).slice(0, AI_STOCK_ROLE_TAGS_READ).join(' ')));
|
|
2649
|
+
if (((_judgement_roleScene1 = judgement.roleScene) == null ? void 0 : _judgement_roleScene1.length) && !judgement.roleScene.some((stem)=>first.has(stem))) return 0;
|
|
2650
|
+
const read = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, ALT_TAGS_READ).join(' ')));
|
|
2651
|
+
if (((_judgement_roleRefuse1 = judgement.roleRefuse) != null ? _judgement_roleRefuse1 : []).some((stem)=>read.has(stem))) return 0;
|
|
2652
|
+
}
|
|
2216
2653
|
// A wedding day shows its people: a close-up of roses tagged "wedding" never fills it.
|
|
2217
2654
|
const needs = (_judgement_needs = judgement.needs) != null ? _judgement_needs : [];
|
|
2218
2655
|
if (needs.length && !needs.some((word)=>hit.all.has(word))) return 0;
|
|
@@ -2294,6 +2731,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2294
2731
|
if (phrased) score += 3;
|
|
2295
2732
|
for (const word of new Set(subject))if (word !== object && hit.all.has(word)) score += 1;
|
|
2296
2733
|
if (world) score += worldLead ? 1.25 : 1;
|
|
2734
|
+
// A portrait in its role: a teacher at the board over a woman in a classroom.
|
|
2735
|
+
const roles = (_judgement_roles = judgement.roles) != null ? _judgement_roles : [];
|
|
2736
|
+
if (roles.some((word)=>hit.lead.has(word))) score += 4;
|
|
2737
|
+
else if (roles.some((word)=>hit.all.has(word))) score += 2;
|
|
2297
2738
|
// A hero is most of all what its first tags say: "law office, lawyer,
|
|
2298
2739
|
// attorney" over "lawyers, personal injury, accident" (AGL-3856): a second
|
|
2299
2740
|
// of them naming its world says it is of that world, not of something else.
|
|
@@ -2328,6 +2769,36 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2328
2769
|
}
|
|
2329
2770
|
return ranked;
|
|
2330
2771
|
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Whether two hits are of one shoot (AGL-3856): one contributor's photos a few
|
|
2774
|
+
* uploads apart, as Pixabay numbers them — the beta.265 Granite Peak Barbell
|
|
2775
|
+
* coaches were four of one model, 728223 to 728229 — else one page address.
|
|
2776
|
+
*/ export function aiStockSameShoot(a, b) {
|
|
2777
|
+
const near = Number(a.id) && Number(b.id) ? Math.abs(Number(a.id) - Number(b.id)) < 100 : false;
|
|
2778
|
+
if (a.photographer && a.photographer === b.photographer && near) return true;
|
|
2779
|
+
const slug = pageSlug(a.pageUrl);
|
|
2780
|
+
return Boolean(slug) && slug === pageSlug(b.pageUrl);
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* A search's ranked hits with those of a shoot no slot shows yet first among
|
|
2784
|
+
* the best (AGL-3856): a team's three cards are three people where the
|
|
2785
|
+
* library has them, never one model three times while another fits as well.
|
|
2786
|
+
* Every other order is kept.
|
|
2787
|
+
*/ export function aiStockFreshShootsFirst(ranked, shown, judgement) {
|
|
2788
|
+
if (!shown.length || ranked.length < 2) return [
|
|
2789
|
+
...ranked
|
|
2790
|
+
];
|
|
2791
|
+
const best = aiStockRelevance(ranked[0], judgement);
|
|
2792
|
+
const top = ranked.filter((photo)=>aiStockRelevance(photo, judgement) === best);
|
|
2793
|
+
const fresh = top.filter((photo)=>!shown.some((other)=>aiStockSameShoot(photo, other)));
|
|
2794
|
+
if (!fresh.length || fresh[0] === ranked[0]) return [
|
|
2795
|
+
...ranked
|
|
2796
|
+
];
|
|
2797
|
+
return [
|
|
2798
|
+
...fresh,
|
|
2799
|
+
...ranked.filter((photo)=>!fresh.includes(photo))
|
|
2800
|
+
];
|
|
2801
|
+
}
|
|
2331
2802
|
/** The pick among a search's hits: the first {@link aiStockRank} would try, or `null`. */ export function aiStockPick(photos, used, seed, judgement = {}) {
|
|
2332
2803
|
var _aiStockRank_;
|
|
2333
2804
|
return (_aiStockRank_ = aiStockRank(photos, used, seed, judgement)[0]) != null ? _aiStockRank_ : null;
|
|
@@ -2353,6 +2824,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2353
2824
|
* later does not place what an earlier one did, while a pass repeated over
|
|
2354
2825
|
* its own page keeps its own photos. Bounded; the oldest job goes first.
|
|
2355
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';
|
|
2356
2828
|
const JOB_PHOTOS_MAX = 200;
|
|
2357
2829
|
function jobPhotos(key) {
|
|
2358
2830
|
if (!key) return null;
|
|
@@ -2374,33 +2846,74 @@ function jobPhotos(key) {
|
|
|
2374
2846
|
/** The longest description of a photo written from the library's own words. */ const AI_STOCK_ALT_MAX = 160;
|
|
2375
2847
|
/**
|
|
2376
2848
|
* Words a tag uses for how a photo looks or feels, never a thing it shows
|
|
2377
|
-
* (AGL-3856): "extravagant" of "wedding dress, bride, extravagant"
|
|
2378
|
-
|
|
2849
|
+
* (AGL-3856): "extravagant" of "wedding dress, bride, extravagant", "moody"
|
|
2850
|
+
* of "forest, human, photographer", a season or a people's name.
|
|
2851
|
+
*/ const DESCRIBING = words('love romance romantic luxury luxurious fancy extravagant happiness happy beauty emotion emotions mood lifestyle ' + 'concept aesthetic wallpaper background retro vintage antique old young cute sexy fun funny sad positive negative healthy fit ' + 'strong powerful homemade tasty yummy sweet sweetness nice dark bw monochrome macro sporty active married bridal ' + 'female male adult good bad poc bipoc moody sunny rainy foggy misty snowy cloudy stormy dreamy shiny trendy messy ' + 'casual blonde blond brunette muscular athletic clever intelligent concentrated curious lonely sleepy heavy ' + 'nature outdoors outdoor indoors indoor scenery scenic autumn fall winter spring summer ' + 'physical teenage traveling ' + // Adjectives a tag gives alone, never a thing: "students, multicultural" (AGL-3856).
|
|
2852
|
+
'multicultural multiethnic multiracial diverse international cultural traditional modern classic classical ' + 'domestic wild tropical rural urban ' + // How the photo was made, never what it shows: "cutout, perspective", "men, silhouettes".
|
|
2853
|
+
'silhouette silhouettes cutout perspective bokeh closeup ' + // A holiday names when, never a thing: "tet market" is a market.
|
|
2854
|
+
'tet christmas easter halloween diwali ramadan hanukkah thanksgiving ' + 'indian asian african american european caucasian vietnamese japanese chinese korean thai latino latina hispanic ' + 'japan vietnam viet nam thailand india china korea germany france italy spain england britain uk usa america ' + 'canada mexico brazil australia africa europe asia');
|
|
2379
2855
|
/**
|
|
2380
2856
|
* Words a tag uses for what a person or a thing is like, said before it
|
|
2381
|
-
* rather than as a thing of its own (AGL-3856): "a confident lawyer",
|
|
2382
|
-
* "a lawyer, law and a confident".
|
|
2383
|
-
*/ const QUALITIES = words('confident professional successful serious smart busy creative proud skilled experienced determined focused ' + 'senior junior expert friendly cheerful smiling');
|
|
2384
|
-
/** What a tag names that takes no article: "chalk" of "board, chalk". */ const MASS_NOUNS = words('food coffee tea bread wood water
|
|
2857
|
+
* rather than as a thing of its own (AGL-3856): "a confident lawyer", "an
|
|
2858
|
+
* elderly couple", "an analog camera", never "a lawyer, law and a confident".
|
|
2859
|
+
*/ const QUALITIES = words('confident professional successful serious smart busy creative proud skilled experienced determined focused ' + 'senior junior expert friendly cheerful smiling elderly analog analogue digital mirrorless');
|
|
2860
|
+
/** What a tag names that takes no article: "chalk" of "board, chalk". */ const MASS_NOUNS = words('food coffee tea bread wood water wax soap clay money fabric yarn wool furniture jewelry jewellery ' + 'pottery glass paper music art hair makeup chalk paint ink dust stone marble concrete steel metal ' + 'skincare equipment decor fashion wildlife weather traffic wine beer chocolate rice sugar flour milk ' + 'cheese honey oil ice fire light sunlight dough sourdough pastry dessert people foliage greenery faculty staff ' + 'clothing bedding frosting icing flooring');
|
|
2861
|
+
/** Nouns plural without an "-s": said without an article, "women in a classroom". */ const PLURAL_NOUNS = words('men women people children feet teeth mice geese sheep fish');
|
|
2385
2862
|
/**
|
|
2386
|
-
* What a tag names that no photo shows as a thing of its own (AGL-3856):
|
|
2387
|
-
* of "lawyer, law", "feedback" of "board, chalk, feedback"
|
|
2388
|
-
*
|
|
2389
|
-
|
|
2863
|
+
* What a tag names that no photo shows as a thing of its own (AGL-3856): an
|
|
2864
|
+
* idea ("law" of "lawyer, law", "feedback" of "board, chalk, feedback"), a
|
|
2865
|
+
* doing ("read", "lift", "fitness", "a workout"), a word for the photo itself
|
|
2866
|
+
* ("photo", "spot"). Left out, never said with an article: "A bookwork and a
|
|
2867
|
+
* read", "A kettlebell and fitness", "A board and training" said nothing.
|
|
2868
|
+
*/ const ABSTRACT_NOUNS = words('law justice advocacy business feedback review news return communication communicate discussion dialog conversation ' + 'success career work job satisfaction quality concept idea strategy marketing education technology health support ' + 'confirmation opinion rating evaluation planning project presentation optimization information internet finance ' + 'management leadership teamwork motivation growth strength service services help advice consulting consultation ' + 'marriage photography architecture travel transport transportation industry company corporate social e-business ' + 'fitness workout workouts exercise exercises crossfit sport sports skill skills knowledge exam exams lecture ' + 'session lesson course hobby leisure power energy wellness talent formation participation ' + 'press show day days night year years time holiday month months week weeks morning evening afternoon ' + 'kiss hug embrace ' + 'photo photos picture pictures image images shot shots shoot shoots spot spots ' + 'read write learn study lift move remove focus relax think sit smile laugh eat swim teach enjoy celebrate explore ' + 'hike can');
|
|
2869
|
+
/** Words ending as an idea does that are things: "a station", "a city", "a ship". */ const CONCRETE_ABSTRACT_ENDINGS = words('station mansion lotion potion ornament instrument apartment monument pavement basement garment compartment ' + 'equipment cement city university facility ship warship spaceship airship');
|
|
2870
|
+
/** Whether a word is an idea or a doing by how it ends: "participation", "unity", "empowerment". */ const saysAnIdea = (word)=>/(?:tion|sion|ment|ness|ity|ship|ism)s?$/.test(word) && !CONCRETE_ABSTRACT_ENDINGS.has(aiStockStem(word));
|
|
2871
|
+
/**
|
|
2872
|
+
* Makers' names a tag gives, never what a photo shows to someone who cannot
|
|
2873
|
+
* see it (AGL-3856): "A photo-camera and a sony".
|
|
2874
|
+
*/ const BRANDS = words('sony canon cannon nikon fujifilm fuji leica olympus panasonic kodak polaroid hasselblad pentax gopro dji ' + 'apple iphone ipad macbook imac samsung huawei android google microsoft windows dell lenovo nintendo playstation ' + 'xbox nike adidas puma reebok peloton bmw mercedes audi porsche ferrari lamborghini tesla ford toyota honda ' + 'volkswagen vw jeep chevrolet coca-cola pepsi starbucks mcdonalds ikea lego barbie disney altman ' + 'petri zenit praktica yashica minolta mamiya rolleiflex voigtlander contax ricoh konica lomo lomography holga zeiss');
|
|
2390
2875
|
/** Stopwords a description still says, as the people a photo shows: "a family", "a person". */ const SAID_PEOPLE = words('people person family');
|
|
2391
|
-
/** Words ending in "-ing" that are things, and take an article: "a wedding", "a ring". */ const COUNTED_ING = words('wedding ring earring building painting drawing ceiling string swing king sibling wing');
|
|
2392
|
-
/**
|
|
2393
|
-
const
|
|
2876
|
+
/** Words ending in "-ing" that are things, and take an article: "a wedding", "a ring". */ const COUNTED_ING = words('wedding ring earring building painting drawing ceiling string swing king sibling wing dumpling pudding sapling');
|
|
2877
|
+
/** "-ing" words that are things, and take no article: "clothing". */ const UNCOUNTED_ING = words('clothing bedding frosting icing flooring stuffing seasoning filling');
|
|
2878
|
+
/** People a photo shows; the generic ones give way to a role ("man" to "businessman"). */ const PEOPLE_NOUNS = words('man men woman women person people human humans boy boys girl girls guy guys lady ladies child children kid kids ' + 'baby family couple bride groom schoolgirl schoolboy teen teenager student students graduate athlete athletes ' + 'businessman businesswoman businessmen lawyer attorney doctor dentist nurse chef cook baker barista ' + 'trainer coach teacher teachers instructor lecturer professor tutor worker workers entrepreneur model ' + 'photographer potter artist florist mother father parent parents grandmother grandfather grandparents ' + 'faculty staff team participants volunteer volunteers musician dancer leader manager owner founder ' + 'journalist journalists reporter reporters master masters lovers newlywed newlyweds husband wife fiance fiancee spouse');
|
|
2879
|
+
/** People said by no role: a model is whoever posed (AGL-3856), "a woman" of "woman, model, show, teacher". */ const GENERIC_PEOPLE = words('man men woman women person people human humans guy guys lady ladies adult boy boys girl girls model models');
|
|
2880
|
+
/**
|
|
2881
|
+
* A body's parts, never a thing a photo is described by (AGL-3856): "A girl
|
|
2882
|
+
* with a hand" said nothing, and "A body in a gym" is a gym.
|
|
2883
|
+
*/ const BODY_PARTS = words('hand hands finger fingers arm arms leg legs foot feet body bodies face faces eye eyes skin head heads lip lips mouth nose ' + 'ear ears neck shoulder shoulders knee knees');
|
|
2884
|
+
/**
|
|
2885
|
+
* Words for a kind of thing, said only where nothing names the thing itself
|
|
2886
|
+
* (AGL-3856): "A domestic cat and an animal" is a cat.
|
|
2887
|
+
*/ const KINDS_OF_THING = words('animal animals pet pets creature creatures mammal mammals object objects thing things item items');
|
|
2888
|
+
/** How many of a photo's first tags a person leads its description from, where it names a thing too: "A camera" of "petri, camera, film, photography, photographer" (AGL-3856). */ const ALT_PERSON_TAGS_READ = 4;
|
|
2889
|
+
/** How many of a photo's first tags a role is said from, over a man or a woman before it: "A man" of "bed, book, man, …, student" (AGL-3856). */ const ALT_ROLE_TAGS_READ = 6;
|
|
2890
|
+
/** What a person in a photo wears, said "in": "a bride in a wedding dress". */ const CLOTHING = words('dress gown suit uniform costume coat jacket robe apron');
|
|
2394
2891
|
/**
|
|
2395
2892
|
* Places a photo is set in, each with how a phrase says it is there: "a
|
|
2396
|
-
* train at a station", "a lawyer in court".
|
|
2893
|
+
* train at a station", "a lawyer in court", "a couple at a wedding".
|
|
2397
2894
|
*/ const PLACES = {
|
|
2398
2895
|
station: 'at',
|
|
2399
2896
|
market: 'at',
|
|
2400
2897
|
desk: 'at',
|
|
2401
2898
|
table: 'at',
|
|
2402
2899
|
counter: 'at',
|
|
2900
|
+
school: 'at',
|
|
2901
|
+
college: 'at',
|
|
2902
|
+
university: 'at',
|
|
2903
|
+
wedding: 'at',
|
|
2904
|
+
sunset: 'at',
|
|
2905
|
+
sunrise: 'at',
|
|
2906
|
+
dusk: 'at',
|
|
2907
|
+
dawn: 'at',
|
|
2403
2908
|
beach: 'on',
|
|
2909
|
+
bed: 'on',
|
|
2910
|
+
sofa: 'on',
|
|
2911
|
+
couch: 'on',
|
|
2912
|
+
bench: 'on',
|
|
2913
|
+
trail: 'on',
|
|
2914
|
+
path: 'on',
|
|
2915
|
+
bridge: 'on',
|
|
2916
|
+
street: 'on',
|
|
2404
2917
|
court: 'in',
|
|
2405
2918
|
courtroom: 'in',
|
|
2406
2919
|
office: 'in',
|
|
@@ -2408,6 +2921,8 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
|
|
|
2408
2921
|
gym: 'in',
|
|
2409
2922
|
studio: 'in',
|
|
2410
2923
|
classroom: 'in',
|
|
2924
|
+
class: 'in',
|
|
2925
|
+
hall: 'in',
|
|
2411
2926
|
room: 'in',
|
|
2412
2927
|
garden: 'in',
|
|
2413
2928
|
park: 'in',
|
|
@@ -2423,35 +2938,57 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
|
|
|
2423
2938
|
forest: 'in',
|
|
2424
2939
|
city: 'in',
|
|
2425
2940
|
church: 'in',
|
|
2426
|
-
field: 'in'
|
|
2941
|
+
field: 'in',
|
|
2942
|
+
meadow: 'in',
|
|
2943
|
+
valley: 'in',
|
|
2944
|
+
mountain: 'in',
|
|
2945
|
+
hill: 'on',
|
|
2946
|
+
cliff: 'on',
|
|
2947
|
+
river: 'by',
|
|
2948
|
+
lake: 'by',
|
|
2949
|
+
waterfall: 'at'
|
|
2950
|
+
};
|
|
2951
|
+
/** Places that are things too, a photo's subject where nothing else is: "a desk in a classroom". */ const OBJECT_PLACES = words('desk table counter bridge bed sofa couch bench');
|
|
2952
|
+
/** Places said without an article: "in court", "at sunset". */ const BARE_PLACES = words('court sunset sunrise dusk dawn');
|
|
2953
|
+
/** Places said in words of their own: a mountain tag is "in the mountains". */ const PLACES_SAID = {
|
|
2954
|
+
mountain: 'the mountains'
|
|
2427
2955
|
};
|
|
2428
|
-
/**
|
|
2956
|
+
/** How many of a photo's first tags its description reads: the things and people it is OF. */ const ALT_TAGS_READ = 8;
|
|
2957
|
+
/** How far a photo's tags are read for where it is set, said last: "in a gym" of a kettlebell's seventh tag. */ const ALT_PLACE_TAGS_READ = 10;
|
|
2958
|
+
/** A tag's words, a word for the photo itself taken off a compound: "photo-camera" is a camera. */ function tagWords(tag) {
|
|
2959
|
+
return tag.toLowerCase().replace(/[^a-zÀ-ɏ\s'-]/g, ' ').replace(/\b(?:photo|picture|image)-(?=[a-z])/g, '').split(/\s+/).filter(Boolean);
|
|
2960
|
+
}
|
|
2429
2961
|
/**
|
|
2430
2962
|
* A tag as a phrase of an English description (AGL-3856): "a wedding dress",
|
|
2431
2963
|
* "an attorney", "books", "chalk", "Columbus"; `null` for a tag that names no
|
|
2432
|
-
* thing ("extravagant", "
|
|
2964
|
+
* thing ("extravagant", "feedback", "training", "sony") or is too long to be
|
|
2965
|
+
* one.
|
|
2433
2966
|
*/ function describedTag(tag, proper) {
|
|
2434
2967
|
const describing = (word)=>FILLER.has(word) || DESCRIBING.has(word) || QUALITIES.has(word) || /(?:ous|ful)$/.test(word) || word.length > 4 && /[^e]ed$/.test(word);
|
|
2435
|
-
const all = tag
|
|
2436
|
-
// "Beautiful flowers" shows flowers; "roasted" shows nothing
|
|
2437
|
-
|
|
2968
|
+
const all = tagWords(tag);
|
|
2969
|
+
// "Beautiful flowers" shows flowers; "roasted" shows nothing; "photo model"
|
|
2970
|
+
// is a model and "traveling couples" are couples: a word before the last
|
|
2971
|
+
// that is a doing says what they do, never what they are.
|
|
2972
|
+
const list = all.filter((word, index)=>!describing(word) && !(all.length > 1 && /^(?:photo|picture|image)$/.test(word)) && !(index < all.length - 1 && index > 0 && word.endsWith('ing') && !COUNTED_ING.has(word)));
|
|
2438
2973
|
if (!list.length || all.length > 3) return null;
|
|
2439
2974
|
// A phrase of no thing ("up close") or a doing ("take pictures", "to lift", "work out") is not what it shows.
|
|
2440
2975
|
if (list.every((word)=>STOPWORDS.has(word) && !SAID_PEOPLE.has(word))) return null;
|
|
2441
|
-
if (/^(?:to|take|taking|make|making|get|do|go|lie)$/.test(list[0])) return null;
|
|
2976
|
+
if (/^(?:to|take|taking|make|making|get|do|go|lie|in|on|at|for|with|by|of)$/.test(list[0])) return null;
|
|
2442
2977
|
if (/^(?:out|up|off|down|away|on|in)$/.test(list[list.length - 1])) return null;
|
|
2978
|
+
if (list.some((word)=>BRANDS.has(word))) return null;
|
|
2443
2979
|
if (list.every((word)=>proper.has(word))) return list.map((word)=>`${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
|
|
2444
2980
|
const last = list[list.length - 1];
|
|
2445
|
-
// An idea, never a thing: "law", "feedback", "customer satisfaction".
|
|
2446
|
-
if (ABSTRACT_NOUNS.has(last)) return null;
|
|
2981
|
+
// An idea or a doing, never a thing: "law", "feedback", "customer satisfaction", "participation", "weightlifting".
|
|
2982
|
+
if (ABSTRACT_NOUNS.has(last) || ABSTRACT_NOUNS.has(aiStockStem(last)) || saysAnIdea(last)) return null;
|
|
2983
|
+
const doing = last.endsWith('ing') && !COUNTED_ING.has(aiStockStem(last)) && !UNCOUNTED_ING.has(last);
|
|
2984
|
+
if (doing) return null;
|
|
2447
2985
|
const phrase = list.join(' ');
|
|
2448
|
-
const plural = aiStockStem(last) !== last && !/^(?:lens|canvas|atlas|gas|bus)$/.test(last);
|
|
2449
|
-
|
|
2450
|
-
if (plural || uncounted || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return phrase;
|
|
2986
|
+
const plural = PLURAL_NOUNS.has(last) || aiStockStem(last) !== last && !/^(?:lens|canvas|atlas|gas|bus)$/.test(last);
|
|
2987
|
+
if (plural || MASS_NOUNS.has(last) || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return phrase;
|
|
2451
2988
|
return `${articleFor(phrase)} ${phrase}`;
|
|
2452
2989
|
}
|
|
2453
2990
|
/** "a" or "an" before a phrase. */ const articleFor = (phrase)=>/^(?:[aeio]|u(?!ni|s[eu]))/.test(phrase) ? 'an' : 'a';
|
|
2454
|
-
/** Whether two words name one thing, one inside the other: "law" and "lawyer", "
|
|
2991
|
+
/** Whether two words name one thing, one inside the other: "law" and "lawyer", "cam" and "camera". */ function nearSynonyms(a, b) {
|
|
2455
2992
|
const [short, long] = a.length <= b.length ? [
|
|
2456
2993
|
a,
|
|
2457
2994
|
b
|
|
@@ -2463,28 +3000,41 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
|
|
|
2463
3000
|
}
|
|
2464
3001
|
/**
|
|
2465
3002
|
* A photo described from its own tags (AGL-3856): a short English phrase of
|
|
2466
|
-
*
|
|
2467
|
-
*
|
|
2468
|
-
*
|
|
2469
|
-
* "
|
|
2470
|
-
*
|
|
2471
|
-
*
|
|
2472
|
-
*
|
|
3003
|
+
* concrete things, never the library's comma-separated list: "A train at a
|
|
3004
|
+
* station", "A confident lawyer in court", "A portrait of a businessman", "A
|
|
3005
|
+
* photographer with a camera on a beach", "A kettlebell in a gym", "An analog
|
|
3006
|
+
* camera". It says one person at most (a role over a man or a woman, an adult
|
|
3007
|
+
* over a girl), what they hold or wear, or one or two things, and where:
|
|
3008
|
+
*
|
|
3009
|
+
* - an idea or a doing ("law", "feedback", "training", "a read",
|
|
3010
|
+
* "participation"), a maker's name ("sony"), a word for the photo itself
|
|
3011
|
+
* ("photo", "spot") and a look ("moody", "analog" alone, a season) are left
|
|
3012
|
+
* out; a quality ("elderly", "analog") is said before the one thing it
|
|
3013
|
+
* qualifies;
|
|
3014
|
+
* - of two words for one thing the first tag's stays ("book" over
|
|
3015
|
+
* "bookwork", "camera" over "photo-camera"), unless the later phrase holds
|
|
3016
|
+
* it whole ("wedding dress" over "wedding");
|
|
3017
|
+
* - a second thing comes only from the first two tags and where no place is
|
|
3018
|
+
* said, what a person holds only from the first three, where it is set
|
|
3019
|
+
* from the first ten;
|
|
3020
|
+
* - no word is said twice: never "an elderly couple and an elderly".
|
|
3021
|
+
*
|
|
3022
|
+
* `null` where no tag names a thing.
|
|
2473
3023
|
*/ export function aiStockTagsAlt(tags, proper = new Set()) {
|
|
2474
|
-
var _people_find;
|
|
2475
3024
|
const parts = [];
|
|
2476
3025
|
let quality = '';
|
|
2477
3026
|
let portrait = false;
|
|
2478
|
-
for (const tag of tags.slice(0,
|
|
3027
|
+
for (const [index, tag] of tags.slice(0, ALT_PLACE_TAGS_READ).entries()){
|
|
2479
3028
|
var _ref;
|
|
2480
3029
|
var _bare_split_pop;
|
|
2481
3030
|
const lower = tag.toLowerCase().trim();
|
|
2482
|
-
|
|
3031
|
+
const reading = index < ALT_TAGS_READ;
|
|
3032
|
+
if (reading && (lower === 'portrait' || lower === 'portraits')) {
|
|
2483
3033
|
portrait = true;
|
|
2484
3034
|
continue;
|
|
2485
3035
|
}
|
|
2486
3036
|
if (QUALITIES.has(lower)) {
|
|
2487
|
-
quality || (quality = lower);
|
|
3037
|
+
if (reading) quality || (quality = lower);
|
|
2488
3038
|
continue;
|
|
2489
3039
|
}
|
|
2490
3040
|
const said = describedTag(tag, proper);
|
|
@@ -2493,47 +3043,86 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
|
|
|
2493
3043
|
const stems = stemList(bare);
|
|
2494
3044
|
const head = (_ref = (_bare_split_pop = bare.split(/\s+/).pop()) == null ? void 0 : _bare_split_pop.toLowerCase()) != null ? _ref : '';
|
|
2495
3045
|
if (!head) continue;
|
|
2496
|
-
const
|
|
3046
|
+
const headStem = aiStockStem(head);
|
|
3047
|
+
const kind = PEOPLE_NOUNS.has(head) || PEOPLE_NOUNS.has(headStem) ? 'person' : PLACES[headStem] ? 'place' : 'thing';
|
|
3048
|
+
// Past the first tags only where it is set is read.
|
|
3049
|
+
if (!reading && kind !== 'place') continue;
|
|
3050
|
+
// A hand or a body is never what a photo is described by.
|
|
3051
|
+
if (kind === 'thing' && (BODY_PARTS.has(head) || BODY_PARTS.has(headStem))) continue;
|
|
2497
3052
|
if (parts.some((part)=>stems.every((stem)=>part.stems.includes(stem)))) continue;
|
|
2498
|
-
// A longer phrase, or a word holding another, takes its place: "wedding dress" for "wedding", "lawyer" for "law".
|
|
2499
|
-
const within = parts.findIndex((part)=>part.stems.every((stem)=>stems.includes(stem)) || nearSynonyms(part.head, head));
|
|
2500
3053
|
const part = {
|
|
2501
3054
|
said,
|
|
2502
3055
|
stems,
|
|
2503
|
-
head,
|
|
2504
|
-
kind
|
|
3056
|
+
head: headStem,
|
|
3057
|
+
kind,
|
|
3058
|
+
index
|
|
2505
3059
|
};
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
3060
|
+
// A longer phrase of the same thing takes its place: "elderly couple" for "couple".
|
|
3061
|
+
const holds = parts.findIndex((held)=>held.head === headStem && held.stems.every((stem)=>stems.includes(stem)));
|
|
3062
|
+
if (holds >= 0) {
|
|
3063
|
+
parts.splice(holds, 1, _extends({}, part, {
|
|
3064
|
+
index: parts[holds].index
|
|
3065
|
+
}));
|
|
2510
3066
|
continue;
|
|
2511
3067
|
}
|
|
3068
|
+
// Two words for one thing ("book" and "bookwork", "cam" and "camera"): the
|
|
3069
|
+
// first tag's stays; a person's are told apart by their role below.
|
|
3070
|
+
if (kind !== 'person' && parts.some((held)=>held.kind !== 'person' && nearSynonyms(held.head, headStem))) continue;
|
|
2512
3071
|
parts.push(part);
|
|
2513
3072
|
}
|
|
2514
|
-
// One person: a role over a man
|
|
3073
|
+
// One person: a role among the first tags over a man, a woman, a girl
|
|
3074
|
+
// ("businessman" over "man"), else the first; a role tagged later is not
|
|
3075
|
+
// what the photo shows ("bed, book, man, …, student" is a man), nor is one
|
|
3076
|
+
// a model posed as ("woman, model, show, teacher" is a woman).
|
|
2515
3077
|
const people = parts.filter((part)=>part.kind === 'person');
|
|
2516
|
-
const
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
3078
|
+
const posed = people.some((part)=>part.head === 'model');
|
|
3079
|
+
let places = parts.filter((part)=>part.kind === 'place');
|
|
3080
|
+
let things = parts.filter((part)=>part.kind === 'thing');
|
|
3081
|
+
// A kind of thing only where nothing names the thing: "a cat", never "a cat and an animal".
|
|
3082
|
+
if (things.some((part)=>!KINDS_OF_THING.has(part.head))) things = things.filter((part)=>!KINDS_OF_THING.has(part.head));
|
|
3083
|
+
// A person tagged after the things a photo is of is not who it shows:
|
|
3084
|
+
// "petri, camera, film, photography, photographer" is a camera, and "men,
|
|
3085
|
+
// silhouettes, camera, nature, photographer" men with a camera.
|
|
3086
|
+
const leads = (part)=>portrait || part.index < ALT_PERSON_TAGS_READ || !things.length;
|
|
3087
|
+
const role = posed ? undefined : people.find((part)=>!GENERIC_PEOPLE.has(part.head) && part.index < ALT_ROLE_TAGS_READ && leads(part));
|
|
3088
|
+
const person = role != null ? role : people.find(leads);
|
|
3089
|
+
// Nothing but places: a desk is the thing in a classroom; "a school in a classroom" says one place.
|
|
3090
|
+
if (!person && !things.length && places.length > 1) {
|
|
3091
|
+
const object = places.find((part)=>OBJECT_PLACES.has(part.head));
|
|
3092
|
+
if (object) {
|
|
3093
|
+
things = [
|
|
3094
|
+
object
|
|
3095
|
+
];
|
|
3096
|
+
places = places.filter((part)=>part !== object);
|
|
3097
|
+
} else places = places.slice(0, 1);
|
|
3098
|
+
}
|
|
3099
|
+
const place = places[0];
|
|
3100
|
+
if (!person && !things.length && !place) return null;
|
|
3101
|
+
/** Every word said so far, so none is said twice. */ const said = new Set();
|
|
3102
|
+
const fresh = (part)=>{
|
|
3103
|
+
const list = stemList(part.said.replace(/^an?\s+/, ''));
|
|
3104
|
+
if (list.some((stem)=>said.has(stem))) return false;
|
|
3105
|
+
for (const stem of list)said.add(stem);
|
|
3106
|
+
return true;
|
|
3107
|
+
};
|
|
3108
|
+
/** A quality before a single counted word: "a confident lawyer", "an analog camera". */ const qualified = (part)=>quality && /^an? \S+$/.test(part.said) && !said.has(aiStockStem(quality)) ? `${articleFor(quality)} ${quality} ${part.said.replace(/^an?\s+/, '')}` : part.said;
|
|
3109
|
+
let phrase = '';
|
|
3110
|
+
if (person && fresh(person)) {
|
|
3111
|
+
phrase = portrait ? `a portrait of ${person.said}` : qualified(person);
|
|
3112
|
+
const held = things.find((part)=>part.index <= 2);
|
|
3113
|
+
if (!portrait && held && fresh(held)) phrase += ` ${CLOTHING.has(held.head) ? 'in' : 'with'} ${held.said}`;
|
|
3114
|
+
} else if (things[0] && fresh(things[0])) {
|
|
3115
|
+
phrase = qualified(things[0]);
|
|
3116
|
+
const second = things[1];
|
|
3117
|
+
if (second && second.index <= 1 && !place && fresh(second)) phrase += ` and ${second.said}`;
|
|
2530
3118
|
}
|
|
2531
|
-
if (place) {
|
|
2532
|
-
var _PLACES_place_head;
|
|
3119
|
+
if (place && fresh(place)) {
|
|
3120
|
+
var _PLACES_place_head, _PLACES_SAID_place_head;
|
|
2533
3121
|
const preposition = (_PLACES_place_head = PLACES[place.head]) != null ? _PLACES_place_head : 'in';
|
|
2534
|
-
const where = BARE_PLACES.has(place.head) && /^an? \S+$/.test(place.said) ? place.head : place.said;
|
|
3122
|
+
const where = (_PLACES_SAID_place_head = PLACES_SAID[place.head]) != null ? _PLACES_SAID_place_head : BARE_PLACES.has(place.head) && /^an? \S+$/.test(place.said) ? place.head : place.said;
|
|
2535
3123
|
phrase = phrase ? `${phrase} ${preposition} ${where}` : where;
|
|
2536
3124
|
}
|
|
3125
|
+
if (!phrase) return null;
|
|
2537
3126
|
const out = `${phrase.charAt(0).toUpperCase()}${phrase.slice(1)}`;
|
|
2538
3127
|
return out.length > AI_STOCK_ALT_MAX ? out.slice(0, AI_STOCK_ALT_MAX).replace(/[\s,]+\S*$/, '') : out;
|
|
2539
3128
|
}
|
|
@@ -2662,8 +3251,27 @@ const EXTENSIONS = {
|
|
|
2662
3251
|
* its business named in English, else its plan's photo words' subject, else
|
|
2663
3252
|
* its kind of site), every other site by its business type. One reading for
|
|
2664
3253
|
* every slot of every source — hero, background, gallery, card, product,
|
|
2665
|
-
* post — so no slot searches by words another slot is held against.
|
|
3254
|
+
* post — so no slot searches by words another slot is held against. A site for
|
|
3255
|
+
* adults alone ({@link aiStockAdultsOnly}) is told so, whatever its world.
|
|
2666
3256
|
*/ export function aiStockSourceTerms(input) {
|
|
3257
|
+
var _input_audience;
|
|
3258
|
+
const terms = sourceWorldTerms(input);
|
|
3259
|
+
// Where the site's own words set it: a photographer "in the Blue Ridge" for "mountain weddings" (AGL-3856).
|
|
3260
|
+
const setting = aiStockSetting([
|
|
3261
|
+
input.business,
|
|
3262
|
+
(_input_audience = input.audience) != null ? _input_audience : ''
|
|
3263
|
+
].join(' . '));
|
|
3264
|
+
return _extends({}, terms, aiStockAdultsOnly([
|
|
3265
|
+
input.audience,
|
|
3266
|
+
input.name,
|
|
3267
|
+
input.business
|
|
3268
|
+
]) ? {
|
|
3269
|
+
adults: true
|
|
3270
|
+
} : {}, setting ? {
|
|
3271
|
+
setting
|
|
3272
|
+
} : {});
|
|
3273
|
+
}
|
|
3274
|
+
/** What a source's photos are searched by: its world, as {@link aiStockSourceTerms} reads it. */ function sourceWorldTerms(input) {
|
|
2667
3275
|
var _input_name, _input_audience, _input_name1, _input_audience1, _input_kind;
|
|
2668
3276
|
const english = aiStockSourceSubject(input);
|
|
2669
3277
|
const terms = aiStockSiteTerms(english || input.business);
|
|
@@ -3206,6 +3814,188 @@ const EXTENSIONS = {
|
|
|
3206
3814
|
]
|
|
3207
3815
|
});
|
|
3208
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
|
+
}
|
|
3209
3999
|
/**
|
|
3210
4000
|
* The first word of a text that names a craft {@link CRAFT_KIN} lists, as
|
|
3211
4001
|
* its table writes it: "gym" of "Synchronicity Gym", "law" of "Hartwell
|
|
@@ -3246,7 +4036,7 @@ const EXTENSIONS = {
|
|
|
3246
4036
|
* The source for one page, or `null` when this deployment has no stock
|
|
3247
4037
|
* photo library or no media door, which leaves every slot to the starters.
|
|
3248
4038
|
*/ export function aiLayoutStockPhotoSource(input, deps = {}) {
|
|
3249
|
-
var _deps_provider, _deps_ingest;
|
|
4039
|
+
var _deps_provider, _deps_ingest, _input_kind;
|
|
3250
4040
|
const provider = ((_deps_provider = deps.provider) != null ? _deps_provider : stockPhotoProvider)();
|
|
3251
4041
|
const ingest = ((_deps_ingest = deps.ingest) != null ? _deps_ingest : pluginMediaIngest)();
|
|
3252
4042
|
if (!provider || !ingest || !input.hostId || !input.uid) return null;
|
|
@@ -3258,6 +4048,8 @@ const EXTENSIONS = {
|
|
|
3258
4048
|
// alt texts, in its own language, name no subject a search could use.
|
|
3259
4049
|
const english = aiStockSourceSubject(input);
|
|
3260
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 : '');
|
|
3261
4053
|
const proper = aiStockProperWords(input.business);
|
|
3262
4054
|
return async (slots)=>{
|
|
3263
4055
|
var _deps_budgetMs, _input_avoid;
|
|
@@ -3271,10 +4063,13 @@ const EXTENSIONS = {
|
|
|
3271
4063
|
const avoid = new Set((_input_avoid = input.avoid) != null ? _input_avoid : []);
|
|
3272
4064
|
/** Placed elsewhere in the job: by another page in this process, or one the caller read. */ const elsewhere = (key)=>avoid.has(key) || (job == null ? void 0 : job.has(key)) === true && job.get(key) !== scope;
|
|
3273
4065
|
/** Source keys this page has claimed for a slot, or turned down. */ const claimed = new Set();
|
|
4066
|
+
/** The photos this page's slots have claimed, first choices, by slot order. */ const shown = [];
|
|
3274
4067
|
/** Asset srcs this page has placed. */ const placedSrcs = new Set();
|
|
3275
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();
|
|
3276
4070
|
let searches = 0;
|
|
3277
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();
|
|
3278
4073
|
/** Whether the library answered any search of this page: a slot left empty then found nothing fitting. */ let answered = false;
|
|
3279
4074
|
const remember = (sourceKey, src)=>{
|
|
3280
4075
|
placedSrcs.add(src);
|
|
@@ -3297,6 +4092,8 @@ const EXTENSIONS = {
|
|
|
3297
4092
|
delete request.refuse;
|
|
3298
4093
|
delete request.needs;
|
|
3299
4094
|
delete request.allow;
|
|
4095
|
+
delete request.refuseLead;
|
|
4096
|
+
delete request.worldless;
|
|
3300
4097
|
const key = JSON.stringify([
|
|
3301
4098
|
request.query,
|
|
3302
4099
|
request.orientation,
|
|
@@ -3355,7 +4152,17 @@ const EXTENSIONS = {
|
|
|
3355
4152
|
maxBytes: AI_LAYOUT_STOCK_MAX_BYTES,
|
|
3356
4153
|
signal
|
|
3357
4154
|
});
|
|
3358
|
-
if (!bytes || signal.aborted || !storing)
|
|
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
|
+
}
|
|
3359
4166
|
const credit = provider.credit(photo);
|
|
3360
4167
|
const stored = await ingest.ingest({
|
|
3361
4168
|
hostId: input.hostId,
|
|
@@ -3390,25 +4197,76 @@ const EXTENSIONS = {
|
|
|
3390
4197
|
height: (_stored_height = stored.height) != null ? _stored_height : photo.height
|
|
3391
4198
|
}, described);
|
|
3392
4199
|
}
|
|
3393
|
-
storing = false;
|
|
3394
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;
|
|
3395
4213
|
console.warn('ai layout pictures: the media library refused a stock photo', {
|
|
3396
4214
|
status: refusal.status,
|
|
3397
|
-
reason: refusal.reason
|
|
4215
|
+
reason: refusal.reason,
|
|
4216
|
+
provider: photo.provider,
|
|
4217
|
+
id: photo.id,
|
|
4218
|
+
holds: ofThisPhoto ? 'this photo' : 'the page'
|
|
3398
4219
|
});
|
|
3399
4220
|
} catch (error) {
|
|
4221
|
+
uncopied.add(slot);
|
|
3400
4222
|
console.warn('ai layout pictures: a stock photo could not be kept', {
|
|
3401
4223
|
error: String(error)
|
|
3402
4224
|
});
|
|
3403
4225
|
}
|
|
3404
4226
|
return null;
|
|
3405
4227
|
};
|
|
3406
|
-
/**
|
|
4228
|
+
/**
|
|
4229
|
+
* How a search's hits are judged for a slot. On a site for adults alone no
|
|
4230
|
+
* hit names a child; a portrait of one of the business's people names an
|
|
4231
|
+
* adult, never leads with a child, and is preferred in its role (AGL-3856).
|
|
4232
|
+
*/ const judgementFor = (slot, request, subject)=>{
|
|
4233
|
+
var _request_refuse, _input_sectionNames_slot_sectionIndex, _worldOf_get, _request_refuseLead;
|
|
4234
|
+
var _request_needs, _role_scene, _role_refuse;
|
|
4235
|
+
const refuse = [
|
|
4236
|
+
...(_request_refuse = request.refuse) != null ? _request_refuse : [],
|
|
4237
|
+
...terms.adults ? CHILD_STEMS : []
|
|
4238
|
+
];
|
|
4239
|
+
const role = slot.people ? aiStockPeopleRole(slot.alt, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '') : null;
|
|
4240
|
+
return _extends({}, judgementOf(slot, request, subject, (_worldOf_get = worldOf.get(request)) != null ? _worldOf_get : terms), refuse.length ? {
|
|
4241
|
+
refuse: [
|
|
4242
|
+
...new Set(refuse)
|
|
4243
|
+
]
|
|
4244
|
+
} : {}, slot.people ? _extends({
|
|
4245
|
+
needs: ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? request.needs : (role == null ? void 0 : role.required) ? role.kin : ADULT_PEOPLE_STEMS,
|
|
4246
|
+
refuseLead: [
|
|
4247
|
+
...new Set([
|
|
4248
|
+
...CHILD_STEMS,
|
|
4249
|
+
...(_request_refuseLead = request.refuseLead) != null ? _request_refuseLead : []
|
|
4250
|
+
])
|
|
4251
|
+
]
|
|
4252
|
+
}, role ? {
|
|
4253
|
+
roles: role.kin
|
|
4254
|
+
} : {}, (role == null ? void 0 : (_role_scene = role.scene) == null ? void 0 : _role_scene.length) ? {
|
|
4255
|
+
roleScene: role.scene
|
|
4256
|
+
} : {}, (role == null ? void 0 : (_role_refuse = role.refuse) == null ? void 0 : _role_refuse.length) ? {
|
|
4257
|
+
roleRefuse: aiStockRoleRefusals(role, slot.alt)
|
|
4258
|
+
} : {}) : {});
|
|
4259
|
+
};
|
|
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)=>{
|
|
3407
4265
|
var _request_subject, _request_shun;
|
|
3408
|
-
var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs;
|
|
4266
|
+
var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead;
|
|
3409
4267
|
return _extends({
|
|
3410
4268
|
subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
|
|
3411
|
-
domain:
|
|
4269
|
+
domain: world.domain,
|
|
3412
4270
|
strict: slot.role === 'gallery' && !request.broad || !!request.requires || !!request.strict,
|
|
3413
4271
|
named: ((_request_allow = request.allow) == null ? void 0 : _request_allow.length) ? [
|
|
3414
4272
|
...new Set([
|
|
@@ -3416,16 +4274,16 @@ const EXTENSIONS = {
|
|
|
3416
4274
|
...request.allow
|
|
3417
4275
|
])
|
|
3418
4276
|
] : terms.named,
|
|
3419
|
-
rivals:
|
|
3420
|
-
},
|
|
4277
|
+
rivals: world.rivals
|
|
4278
|
+
}, world.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
|
|
3421
4279
|
shun: [
|
|
3422
4280
|
...new Set([
|
|
3423
|
-
...
|
|
4281
|
+
...world.shun,
|
|
3424
4282
|
...(_request_shun = request.shun) != null ? _request_shun : []
|
|
3425
4283
|
])
|
|
3426
4284
|
]
|
|
3427
|
-
} : {},
|
|
3428
|
-
shunLead:
|
|
4285
|
+
} : {}, world.shunLead.length ? {
|
|
4286
|
+
shunLead: world.shunLead
|
|
3429
4287
|
} : {}, request.product ? {
|
|
3430
4288
|
product: true
|
|
3431
4289
|
} : {}, ((_request_leadAny = request.leadAny) == null ? void 0 : _request_leadAny.length) ? {
|
|
@@ -3448,14 +4306,20 @@ const EXTENSIONS = {
|
|
|
3448
4306
|
refuse: request.refuse
|
|
3449
4307
|
} : {}, ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? {
|
|
3450
4308
|
needs: request.needs
|
|
4309
|
+
} : {}, ((_request_refuseLead = request.refuseLead) == null ? void 0 : _request_refuseLead.length) ? {
|
|
4310
|
+
refuseLead: request.refuseLead
|
|
4311
|
+
} : {}, request.worldless ? {
|
|
4312
|
+
domain: [],
|
|
4313
|
+
rivals: []
|
|
3451
4314
|
} : {});
|
|
3452
4315
|
};
|
|
3453
4316
|
/**
|
|
3454
4317
|
* The craft's own photos, the last thing a slot of a craft's site is
|
|
3455
4318
|
* filled from (AGL-3660): its broad searches, any hit naming the craft
|
|
3456
4319
|
* itself. Never a desk, a laptop or a living room for a soap shop.
|
|
3457
|
-
*/ const craftSearches = (slot)=>{
|
|
3458
|
-
|
|
4320
|
+
*/ const craftSearches = (slot, world = terms)=>{
|
|
4321
|
+
// A portfolio's record of the work is never filled by the craft's own photos (AGL-3856).
|
|
4322
|
+
if (!world.core.length || aiStockShowsTheWork(slot, world)) return [];
|
|
3459
4323
|
const orientation = aiStockOrientation(slot.aspect);
|
|
3460
4324
|
const size = slot.role === 'hero' ? {
|
|
3461
4325
|
minWidth: 1600
|
|
@@ -3464,12 +4328,12 @@ const EXTENSIONS = {
|
|
|
3464
4328
|
} : {
|
|
3465
4329
|
minWidth: 900
|
|
3466
4330
|
};
|
|
3467
|
-
return
|
|
4331
|
+
return world.broad.map((query)=>_extends({
|
|
3468
4332
|
query: clip(query),
|
|
3469
4333
|
orientation
|
|
3470
4334
|
}, size, {
|
|
3471
4335
|
broad: true,
|
|
3472
|
-
category:
|
|
4336
|
+
category: world.core
|
|
3473
4337
|
}, slot.role === 'hero' ? {
|
|
3474
4338
|
first: true
|
|
3475
4339
|
} : {}, slot.product ? {
|
|
@@ -3477,6 +4341,57 @@ const EXTENSIONS = {
|
|
|
3477
4341
|
tier: 'weak'
|
|
3478
4342
|
} : {}));
|
|
3479
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
|
+
};
|
|
3480
4395
|
// 1. Choose, slot by slot, so the page's choices never collide: each
|
|
3481
4396
|
// slot claims its best accepted hit before the next slot looks.
|
|
3482
4397
|
const choices = [];
|
|
@@ -3493,7 +4408,7 @@ const EXTENSIONS = {
|
|
|
3493
4408
|
...slot.product ? aiStockProductSearches(slot, terms.craft, terms.domain, {
|
|
3494
4409
|
broad: terms.broad,
|
|
3495
4410
|
core: terms.core
|
|
3496
|
-
}) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName)),
|
|
4411
|
+
}) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName), sectionName),
|
|
3497
4412
|
...craftSearches(slot)
|
|
3498
4413
|
];
|
|
3499
4414
|
/** The slot's accepted hits for a search; `null` once the page's searches are spent. */ const accepted = async (request, repeat)=>{
|
|
@@ -3507,7 +4422,15 @@ const EXTENSIONS = {
|
|
|
3507
4422
|
if (elsewhere(key)) blocked.add(key);
|
|
3508
4423
|
}
|
|
3509
4424
|
}
|
|
3510
|
-
|
|
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
|
+
}
|
|
4430
|
+
const judgement = judgementFor(slot, request, subject);
|
|
4431
|
+
const ranked = aiStockRank(found, blocked, `${input.seed}:${index}:${request.query}`, judgement);
|
|
4432
|
+
// A team's portraits are of different people where the hits allow (AGL-3856).
|
|
4433
|
+
return slot.people && !repeat ? aiStockFreshShootsFirst(ranked, shown, judgement) : ranked;
|
|
3511
4434
|
};
|
|
3512
4435
|
/** The first of these searches with a hit to take: a new one, or one the job already shows. */ const choose = async (tried, repeat)=>{
|
|
3513
4436
|
for (const request of tried){
|
|
@@ -3515,7 +4438,10 @@ const EXTENSIONS = {
|
|
|
3515
4438
|
// Spent searches answer nothing; those already asked still answer from memory.
|
|
3516
4439
|
const ranked = await accepted(request, repeat);
|
|
3517
4440
|
if (!(ranked == null ? void 0 : ranked.length)) continue;
|
|
3518
|
-
if (!repeat)
|
|
4441
|
+
if (!repeat) {
|
|
4442
|
+
claimed.add(stockPhotoSourceKey(ranked[0]));
|
|
4443
|
+
shown.push(ranked[0]);
|
|
4444
|
+
}
|
|
3519
4445
|
return _extends({
|
|
3520
4446
|
candidates: ranked.slice(0, AI_LAYOUT_STOCK_TRIES_PER_SLOT),
|
|
3521
4447
|
query: request.query
|
|
@@ -3535,6 +4461,9 @@ const EXTENSIONS = {
|
|
|
3535
4461
|
const repeats = Boolean(slot.product) || slot.role === 'hero';
|
|
3536
4462
|
const sure = slot.product ? requests.filter((request)=>request.tier !== 'weak') : requests;
|
|
3537
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);
|
|
3538
4467
|
const phases = [
|
|
3539
4468
|
[
|
|
3540
4469
|
sure,
|
|
@@ -3555,6 +4484,18 @@ const EXTENSIONS = {
|
|
|
3555
4484
|
unsure,
|
|
3556
4485
|
true
|
|
3557
4486
|
]
|
|
4487
|
+
] : [],
|
|
4488
|
+
...widened.length ? [
|
|
4489
|
+
[
|
|
4490
|
+
widened,
|
|
4491
|
+
false
|
|
4492
|
+
]
|
|
4493
|
+
] : [],
|
|
4494
|
+
...widened.length && repeats ? [
|
|
4495
|
+
[
|
|
4496
|
+
widened,
|
|
4497
|
+
true
|
|
4498
|
+
]
|
|
3558
4499
|
] : []
|
|
3559
4500
|
];
|
|
3560
4501
|
for (const [tried, repeat] of phases){
|
|
@@ -3623,7 +4564,10 @@ const EXTENSIONS = {
|
|
|
3623
4564
|
// The library answered and nothing in it fits: no photo rather than a
|
|
3624
4565
|
// starter that shows another business (AGL-3831). A library that never
|
|
3625
4566
|
// answered, or a site that refused to store, leaves the starters.
|
|
3626
|
-
|
|
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;
|
|
3627
4571
|
};
|
|
3628
4572
|
}
|
|
3629
4573
|
|