@aglyn/plugins-ai 1.0.0-beta.285 → 1.0.0-beta.288
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 +17 -17
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/declarations.js +5 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
- package/src/lib/jobs/ai-build-capabilities.js +27 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +59 -0
- package/src/lib/jobs/ai-image-reads.js +138 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
- package/src/lib/jobs/ai-job-build-step.js +83 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-import-step.js +15 -11
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
- package/src/lib/jobs/ai-job-insight-step.js +23 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +2 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-media-step.js +6 -8
- package/src/lib/jobs/ai-job-media-step.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-navigation-step.js +7 -9
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +7 -1
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +16 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +9 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
- package/src/lib/jobs/ai-job-plan-step.js +78 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-privacy-step.js +14 -15
- package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.js +5 -3
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-settings-step.js +7 -8
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
- package/src/lib/jobs/ai-job-site-content.js +67 -8
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
- package/src/lib/jobs/ai-job-site-datasets.js +300 -30
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +2 -1
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-tracking-step.js +7 -8
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
- package/src/lib/jobs/ai-jobs-beat.js +18 -8
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
- package/src/lib/jobs/ai-jobs-notify.js +136 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +155 -7
- package/src/lib/jobs/ai-layout-stock-photos.js +1517 -220
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-product-image.d.ts +14 -0
- package/src/lib/jobs/ai-product-image.js +73 -21
- package/src/lib/jobs/ai-product-image.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
- package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
- package/src/lib/layout-language/ai-layout-compiler.js +194 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-copy.js +383 -3
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
- package/src/lib/layout-language/ai-layout-design.js +31 -11
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
- package/src/lib/layout-language/ai-layout-listings.js +9 -1
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +39 -1
- package/src/lib/layout-language/ai-layout-pictures.js +141 -4
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
- package/src/lib/layout-language/ai-layout-storefront.js +100 -20
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +7 -0
- package/src/lib/model/ai-build-job.js +36 -7
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +28 -4
- package/src/lib/model/ai-build-plan.js +49 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +3 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +8 -0
- package/src/lib/model/ai-job-notice.js +33 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
- package/src/lib/model/ai-job-plan-lines.js +6 -1
- package/src/lib/model/ai-job-plan-lines.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +8 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-permissions.d.ts +13 -0
- package/src/lib/model/ai-permissions.js +29 -1
- package/src/lib/model/ai-permissions.js.map +1 -1
- package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
- package/src/lib/model/ai-plan-said-sections.js +22 -5
- package/src/lib/model/ai-plan-said-sections.js.map +1 -1
- package/src/lib/model/ai-products.d.ts +18 -0
- package/src/lib/model/ai-products.js +32 -0
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +6 -2
- package/src/lib/model/ai-site-context.js +8 -2
- package/src/lib/model/ai-site-context.js.map +1 -1
- package/src/lib/model/ai-site-inspiration.d.ts +3 -2
- package/src/lib/model/ai-site-inspiration.js +3 -2
- package/src/lib/model/ai-site-inspiration.js.map +1 -1
- package/src/lib/plugin.js +4 -13
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.js +9 -2
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.js +14 -5
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +24 -1
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.js +5 -2
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +120 -1
- package/src/lib/runtime/ai-dataset-generation.js +352 -3
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
- package/src/lib/runtime/ai-doctrine-validators.js +345 -28
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +5 -4
- package/src/lib/runtime/ai-doctrine.js +10 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +27 -1
- package/src/lib/runtime/ai-icon-library.js +30 -3
- package/src/lib/runtime/ai-icon-library.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +11 -1
- package/src/lib/runtime/ai-products-generation.js +51 -4
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
- package/src/lib/runtime/assist-answer-cache.js +5 -2
- package/src/lib/runtime/assist-answer-cache.js.map +1 -1
- package/src/lib/server/ai-allotments.js +7 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-experiment-versions.js +5 -5
- package/src/lib/server/ai-experiment-versions.js.map +1 -1
- package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
- package/src/lib/server/ai-host-permissions-roster.js +33 -0
- package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
- package/src/lib/server/ai-host-permissions.js +8 -6
- package/src/lib/server/ai-host-permissions.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.d.ts +17 -0
- package/src/lib/server/ai-image-reads-route.js +106 -0
- package/src/lib/server/ai-image-reads-route.js.map +1 -0
- package/src/lib/server/ai-seo-apply.js +5 -5
- package/src/lib/server/ai-seo-apply.js.map +1 -1
- package/src/lib/server/ai-site-import-route.js +7 -4
- package/src/lib/server/ai-site-import-route.js.map +1 -1
- package/src/lib/server/assist-build.d.ts +15 -1
- package/src/lib/server/assist-build.js +30 -3
- package/src/lib/server/assist-build.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +11 -7
- package/src/lib/server/assist-chat.js +185 -48
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-docs-query.d.ts +51 -0
- package/src/lib/server/assist-docs-query.js +57 -0
- package/src/lib/server/assist-docs-query.js.map +1 -0
- package/src/lib/server/assist-jobs-context.d.ts +102 -0
- package/src/lib/server/assist-jobs-context.js +227 -0
- package/src/lib/server/assist-jobs-context.js.map +1 -0
- package/src/lib/server/assist-view-context.d.ts +8 -4
- package/src/lib/server/assist-view-context.js +12 -7
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
- package/src/lib/tools/ai-dataset-tool.js +24 -7
- package/src/lib/tools/ai-dataset-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
- package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
- package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
2
3
|
/**
|
|
3
4
|
* @license
|
|
4
5
|
* Copyright 2026 Aglyn LLC
|
|
@@ -140,6 +141,36 @@ import { aiStockBusinessNoun } from "./ai-stock-business-nouns.js";
|
|
|
140
141
|
* where this module lists it ("gym" of "Synchronicity Gym"); and a
|
|
141
142
|
* training word's other senses (a railway's train, an elevator's lift, a
|
|
142
143
|
* newspaper's press, a boat's row) never fill a gym's picture.
|
|
144
|
+
* 10. **The trade, never a word of it** (the 2026-10-25 booth kit, AGL-4065:
|
|
145
|
+
* six trades whose photos were race cars, cyclists, horses, a Honda
|
|
146
|
+
* scooter, a canoe for "Parts for the ranch", hotel buffets for a taco
|
|
147
|
+
* trailer and an office for a landscaper). A business named by two
|
|
148
|
+
* things ("Motorcycle and dirt bike repair shop") is both; the craft
|
|
149
|
+
* named first leads, never one named only in what a customer may ask
|
|
150
|
+
* ("takes catering requests"); a hero's first tag and a broad search's
|
|
151
|
+
* hit name the business's own world ({@link AiStockSiteTerms.lead}),
|
|
152
|
+
* never any word of its type ("race" of "race car"); a two-word world
|
|
153
|
+
* ("dirt bike") is named whole; a maker's name never shows; a sentence to
|
|
154
|
+
* the reader, a job or an order is never a subject, and a part or gear is
|
|
155
|
+
* searched only with its world; a record's cover is searched in its world
|
|
156
|
+
* first; and a listed world widens only where the library knew nothing of
|
|
157
|
+
* it, never an outdoor or a trade world to an office.
|
|
158
|
+
* 11. **The thing, the place and the trade** (the 2026-10-28 live check,
|
|
159
|
+
* AGL-4092: a bare foot tagged "body part" for a parts shop's "Engine and
|
|
160
|
+
* air", a transmission chain for its "Brake Pad Set", a cat tagged "hungry"
|
|
161
|
+
* for a taco trailer's "Come hungry", a European alley for "Around town", a
|
|
162
|
+
* taco bowl salad for "Race day", and a man posing with a street bike tagged
|
|
163
|
+
* "susuki" in a motocross team's band of pictures). A picture of a thing is
|
|
164
|
+
* held to the business's own world, never any word of its type; a feeling,
|
|
165
|
+
* a time or a way of being somewhere is never a thing, and "around town" is
|
|
166
|
+
* the business's customers out there; a broad search's photo is OF the
|
|
167
|
+
* world among its first tags, never a "taco bowl"; an animal, a toy or a
|
|
168
|
+
* bare body part never leads a photo naming nothing of the world; a trade's
|
|
169
|
+
* things are held to the world it serves, never to "part"; a product takes
|
|
170
|
+
* a shoot no other product shows; a motocross business's world is
|
|
171
|
+
* off-road; and a maker is named one letter off ("susuki") and by its
|
|
172
|
+
* gear's and tires' names. Still by the tags: a mark only the pixels show
|
|
173
|
+
* is placed.
|
|
143
174
|
*
|
|
144
175
|
* A slot the library could not fill because it never answered — down, or
|
|
145
176
|
* out of time before its first answer — or because the site's library
|
|
@@ -211,7 +242,80 @@ import { aiStockBusinessNoun } from "./ai-stock-business-nouns.js";
|
|
|
211
242
|
});
|
|
212
243
|
}
|
|
213
244
|
const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
214
|
-
/** Words that carry no subject. */ const STOPWORDS = words('a an the and or of in on at to for with from by into onto over under near our your their my his her its ' + 'this that these those is are be being was were who whom which where while as about than then very just ' + 'small local best new top quality premium family-owned family owned independent professional ' + 'business company services service page photo picture image showing shows show ' + 'someone people person one two three some many several')
|
|
245
|
+
/** Words that carry no subject. */ const STOPWORDS = words('a an the and or of in on at to for with from by into onto over under near our your their my his her its ' + 'this that these those is are be being was were who whom which where while as about than then very just ' + 'small local best new top quality premium family-owned family owned independent professional ' + 'business company services service page photo picture image showing shows show ' + 'someone people person one two three some many several ' + // A sentence to the reader, never what a picture shows (AGL-4065): "Tell us
|
|
246
|
+
// the bike and we will say if it will fit" searched "tell us bike".
|
|
247
|
+
'we us you they them i me if tell say describe send ask need want get let know see find will would could');
|
|
248
|
+
/**
|
|
249
|
+
* Words of a sentence to the reader that a business type may use as a name
|
|
250
|
+
* ("IT support", "Fit club", "call center"): never a picture's subject
|
|
251
|
+
* (AGL-4065), always a business's own word.
|
|
252
|
+
*/ const SENTENCE_WORDS = words('it fit can call');
|
|
253
|
+
/**
|
|
254
|
+
* Nouns that never stand alone as what a picture shows (AGL-4065): a job, a
|
|
255
|
+
* request, an order. "Describe your job" searched "handyman describe job" and
|
|
256
|
+
* placed a job application form; "OEM ATV Parts Order" searched "atv parts
|
|
257
|
+
* order" and placed trees tagged "order".
|
|
258
|
+
*/ const GENERIC_OBJECTS = words('job jobs request requests order orders service services detail details thing things stuff action cta');
|
|
259
|
+
/**
|
|
260
|
+
* Things a world has many of, which a photo shows only in that world
|
|
261
|
+
* (AGL-4065): a part, gear, a machine. Searched only with the site's own
|
|
262
|
+
* world ("dirt bike parts"), never alone: "parts" answered a canoe on a
|
|
263
|
+
* river, tagged "part", for a dirt bike shop's "Parts for the ranch".
|
|
264
|
+
*/ const WORLD_OBJECTS = words('part parts gear gears machine machines equipment spare spares accessory accessories');
|
|
265
|
+
/**
|
|
266
|
+
* Words of a picture's caption that are never the thing it shows (AGL-4092):
|
|
267
|
+
* a feeling, a time, a way of being somewhere. "Come hungry" read its object
|
|
268
|
+
* as "hungry" and took a cat tagged "hungry"; "Around town" read "town" and
|
|
269
|
+
* took a European alley. A caption left with no thing is a picture of the
|
|
270
|
+
* site's own world. A personal record is a feat, never a thing: "Deadlift PR
|
|
271
|
+
* night" is of the deadlift.
|
|
272
|
+
*/ const NON_THINGS = words('come hungry happy ready welcome day days night nights time times today tonight weekend weekends season seasons ' + 'around town hours hour way moment moments memories pr pb');
|
|
273
|
+
/**
|
|
274
|
+
* Where a business is, said as a place to go (AGL-4092): "Around town", "At
|
|
275
|
+
* events". Never a street of houses: a picture of it shows the business's
|
|
276
|
+
* customers out where it is, or its stand.
|
|
277
|
+
*/ const PLACE_PHRASES = /\b(?:around town|in town|downtown|on the road|out and about|at (?:the )?events|neighbou?rhoods?)\b/i;
|
|
278
|
+
/** What a picture of a business out where it is shows, as stems: its people, a line, a street stand (AGL-4092). */ const PLACE_NEEDS = [
|
|
279
|
+
'people',
|
|
280
|
+
'crowd',
|
|
281
|
+
'customer',
|
|
282
|
+
'customers',
|
|
283
|
+
'queue',
|
|
284
|
+
'line',
|
|
285
|
+
'street food',
|
|
286
|
+
'food truck',
|
|
287
|
+
'market',
|
|
288
|
+
'festival'
|
|
289
|
+
];
|
|
290
|
+
/** A street that is someone else's, never where a business is found (AGL-4092): a European alley for "Around town". */ const FOREIGN_STREET = [
|
|
291
|
+
'alley',
|
|
292
|
+
'cobblestone',
|
|
293
|
+
'village',
|
|
294
|
+
'old town',
|
|
295
|
+
'historic',
|
|
296
|
+
'castle',
|
|
297
|
+
'cathedral',
|
|
298
|
+
'europe',
|
|
299
|
+
'landscape',
|
|
300
|
+
'architecture'
|
|
301
|
+
];
|
|
302
|
+
/**
|
|
303
|
+
* The verbs a business type's clause opens with when it says what a customer
|
|
304
|
+
* may ask of it (AGL-4065): "takes quote requests", "books repair
|
|
305
|
+
* appointments". The verb is never the business's world.
|
|
306
|
+
*/ const TRANSACTION_VERBS = words('take takes taking book books booking accept accepts accepting welcome welcomes');
|
|
307
|
+
/**
|
|
308
|
+
* What a business does with a customer, never what its photos show
|
|
309
|
+
* (AGL-4065), as stems: dropped from its world. "Special orders" named
|
|
310
|
+
* Berm & Bolt's world, so a delivery man's "order" and trees tagged "order"
|
|
311
|
+
* filled its products.
|
|
312
|
+
*/ const TRANSACTION_STEMS = new Set(// "Takes" and "books" are verbs only where they open a clause (TRANSACTION_VERBS): a bookshop's books are its world.
|
|
313
|
+
'booking appointment order request quote estimate free inquiry pickup special around town'.split(' '));
|
|
314
|
+
/**
|
|
315
|
+
* Words of a business's own clause that name its world only beside another
|
|
316
|
+
* (AGL-4065): "race" and "team" of "amateur motocross race team" are a race
|
|
317
|
+
* car's and an office's as often. Never its own world alone.
|
|
318
|
+
*/ const LEAD_GENERIC = words('amateur pro race racing team club crew group repair repairs service home house food truck trailer mobile ' + 'street event events custom family families');
|
|
215
319
|
/**
|
|
216
320
|
* Words that describe a picture's look, not what it shows, dropped from a
|
|
217
321
|
* subject. A word that is as often a noun ("light", "set") is not one.
|
|
@@ -547,6 +651,165 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
547
651
|
].map(aiStockStem))
|
|
548
652
|
};
|
|
549
653
|
});
|
|
654
|
+
/**
|
|
655
|
+
* Makers whose names a photo's tags or its page address give, which no
|
|
656
|
+
* site's pictures carry unless its own words name the maker (AGL-4065): the
|
|
657
|
+
* Rut Runners Racing heroes were race cars in a pit lane under their makers'
|
|
658
|
+
* logos, and Holeshot Moto Repair's a Honda scooter and an Indian cruiser. A
|
|
659
|
+
* name that is also a common word ("apple", "puma", "triumph", "indian") is
|
|
660
|
+
* named only in the phrase that makes it the maker's. Read from the tags and
|
|
661
|
+
* the address, so a logo no tag names still shows: the live check is where
|
|
662
|
+
* that is seen.
|
|
663
|
+
*/ /** The one-word makers of riding gear and tires (AGL-4092), also never said in a photo's description ({@link BRANDS}). */ const GEAR_MAKER_WORDS = [
|
|
664
|
+
'alpinestars',
|
|
665
|
+
'oneal',
|
|
666
|
+
'leatt',
|
|
667
|
+
'arai',
|
|
668
|
+
'shoei',
|
|
669
|
+
'agv',
|
|
670
|
+
'oakley',
|
|
671
|
+
'renthal',
|
|
672
|
+
'fmf',
|
|
673
|
+
'acerbis',
|
|
674
|
+
'dunlop',
|
|
675
|
+
'michelin',
|
|
676
|
+
'pirelli',
|
|
677
|
+
'bridgestone',
|
|
678
|
+
'metzeler',
|
|
679
|
+
'maxxis',
|
|
680
|
+
'kenda'
|
|
681
|
+
];
|
|
682
|
+
const MAKERS = [
|
|
683
|
+
'porsche',
|
|
684
|
+
'ferrari',
|
|
685
|
+
'lamborghini',
|
|
686
|
+
'bmw',
|
|
687
|
+
'mercedes',
|
|
688
|
+
'audi',
|
|
689
|
+
'volkswagen',
|
|
690
|
+
'vw',
|
|
691
|
+
'toyota',
|
|
692
|
+
'honda',
|
|
693
|
+
'yamaha',
|
|
694
|
+
'kawasaki',
|
|
695
|
+
'suzuki',
|
|
696
|
+
'ktm',
|
|
697
|
+
'ducati',
|
|
698
|
+
'harley',
|
|
699
|
+
'harley-davidson',
|
|
700
|
+
'husqvarna',
|
|
701
|
+
'gasgas',
|
|
702
|
+
'tesla',
|
|
703
|
+
'ford',
|
|
704
|
+
'chevrolet',
|
|
705
|
+
'chevy',
|
|
706
|
+
'dodge',
|
|
707
|
+
'jeep',
|
|
708
|
+
'nissan',
|
|
709
|
+
'subaru',
|
|
710
|
+
'mazda',
|
|
711
|
+
'indian motorcycle',
|
|
712
|
+
'triumph motorcycle',
|
|
713
|
+
'gran turismo',
|
|
714
|
+
'playstation',
|
|
715
|
+
'xbox',
|
|
716
|
+
'nintendo',
|
|
717
|
+
'nike',
|
|
718
|
+
'adidas',
|
|
719
|
+
'reebok',
|
|
720
|
+
'red bull',
|
|
721
|
+
'monster energy',
|
|
722
|
+
'coca cola',
|
|
723
|
+
'cocacola',
|
|
724
|
+
'pepsi',
|
|
725
|
+
'iphone',
|
|
726
|
+
'ipad',
|
|
727
|
+
'macbook',
|
|
728
|
+
'imac',
|
|
729
|
+
'samsung',
|
|
730
|
+
'starbucks',
|
|
731
|
+
'mcdonalds',
|
|
732
|
+
'mcdonald',
|
|
733
|
+
// A rider's gear and a bike's tires carry their makers' marks as often as
|
|
734
|
+
// the bike does (AGL-4092). A maker whose name is a common word ("fox",
|
|
735
|
+
// "shift", "thor", "fly", "bell") is named only in its phrase.
|
|
736
|
+
...GEAR_MAKER_WORDS,
|
|
737
|
+
'fox racing',
|
|
738
|
+
'shift mx',
|
|
739
|
+
'shift racing',
|
|
740
|
+
'thor mx',
|
|
741
|
+
'fly racing',
|
|
742
|
+
'troy lee',
|
|
743
|
+
'bell helmets',
|
|
744
|
+
'pro taper'
|
|
745
|
+
];
|
|
746
|
+
/** The makers as stems and stem phrases, as a hit's words are read. */ const MAKER_STEMS = new Set(MAKERS.map((maker)=>stemList(maker).join(' ')));
|
|
747
|
+
/**
|
|
748
|
+
* The one-word makers long enough that a tag one letter off still names them
|
|
749
|
+
* (AGL-4092): Pixabay's "susuki" is a Suzuki. Never a common word one letter
|
|
750
|
+
* off a maker: "barley" is not a Harley, nor "rental" a Renthal.
|
|
751
|
+
*/ const FUZZY_MAKERS = [
|
|
752
|
+
...MAKER_STEMS
|
|
753
|
+
].filter((maker)=>!maker.includes(' ') && maker.length >= 6);
|
|
754
|
+
/** Words one letter off a maker that are words of their own (AGL-4092). */ const MAKER_LOOKALIKES = words('barley parley harlem rental samsun ducat phone suzuka');
|
|
755
|
+
/** Whether two words are one edit apart: a letter changed, added, dropped, or two side by side swapped. */ function oneEditApart(a, b) {
|
|
756
|
+
if (a === b) return false;
|
|
757
|
+
if (Math.abs(a.length - b.length) > 1) return false;
|
|
758
|
+
if (a.length === b.length) {
|
|
759
|
+
const differ = [
|
|
760
|
+
...a
|
|
761
|
+
].flatMap((letter, index)=>letter === b[index] ? [] : [
|
|
762
|
+
index
|
|
763
|
+
]);
|
|
764
|
+
if (differ.length === 1) return true;
|
|
765
|
+
const [first, second] = differ;
|
|
766
|
+
return differ.length === 2 && second === first + 1 && a[first] === b[second] && a[second] === b[first];
|
|
767
|
+
}
|
|
768
|
+
const [short, long] = a.length < b.length ? [
|
|
769
|
+
a,
|
|
770
|
+
b
|
|
771
|
+
] : [
|
|
772
|
+
b,
|
|
773
|
+
a
|
|
774
|
+
];
|
|
775
|
+
let index = 0;
|
|
776
|
+
while(index < short.length && short[index] === long[index])index += 1;
|
|
777
|
+
return short.slice(index) === long.slice(index + 1);
|
|
778
|
+
}
|
|
779
|
+
/** The maker a word names one letter off, else `null` ({@link FUZZY_MAKERS}). */ function fuzzyMaker(word) {
|
|
780
|
+
var _FUZZY_MAKERS_find;
|
|
781
|
+
if (word.length < 5 || MAKER_LOOKALIKES.has(word) || MAKER_STEMS.has(word)) return null;
|
|
782
|
+
return (_FUZZY_MAKERS_find = FUZZY_MAKERS.find((maker)=>oneEditApart(word, maker))) != null ? _FUZZY_MAKERS_find : null;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* A phrase's words and each pair of them in turn, as stems (AGL-4065): "dirt
|
|
786
|
+
* bike" names the motocross world where "bike" alone names a bicycle's too.
|
|
787
|
+
*/ function withPairs(list) {
|
|
788
|
+
const out = [
|
|
789
|
+
...list
|
|
790
|
+
];
|
|
791
|
+
for(let index = 0; index + 1 < list.length; index += 1)out.push(`${list[index]} ${list[index + 1]}`);
|
|
792
|
+
return out;
|
|
793
|
+
}
|
|
794
|
+
/** A text's stems and stem pairs, every tag or phrase read on its own. */ function termsOf(texts) {
|
|
795
|
+
return new Set(texts.flatMap((text)=>withPairs(stemList(text))));
|
|
796
|
+
}
|
|
797
|
+
/** A word or phrase as stems: "dirt bikes" is "dirt bike". */ const stemPhrase = (phrase)=>stemList(phrase).join(' ');
|
|
798
|
+
/**
|
|
799
|
+
* A tag that says what a photo suits, never what it shows (AGL-4065):
|
|
800
|
+
* "samsung wallpaper", "iphone wallpaper", "lock screen". A latte tagged so
|
|
801
|
+
* shows no phone.
|
|
802
|
+
*/ const SUITS = /\b(?:wallpapers?|backgrounds?|screens?|screensavers?|lockscreen|4k|hd)\b/i;
|
|
803
|
+
/** The words a photo names a maker by: its tags, those of what it suits left out, its description and its page address. */ function aiStockMakerTerms(photo) {
|
|
804
|
+
var _photo_tags, _photo_alt;
|
|
805
|
+
const tags = ((_photo_tags = photo.tags) != null ? _photo_tags : []).filter((tag)=>!SUITS.test(tag));
|
|
806
|
+
// "O'Neal" is one name, never "neal" (AGL-4092).
|
|
807
|
+
return termsOf([
|
|
808
|
+
...tags,
|
|
809
|
+
(_photo_alt = photo.alt) != null ? _photo_alt : '',
|
|
810
|
+
pageSlug(photo.pageUrl)
|
|
811
|
+
].map((text)=>text.replace(/['’]/g, '')));
|
|
812
|
+
}
|
|
550
813
|
/**
|
|
551
814
|
* Hands are never a picture's subject (AGL-3660): "hand-poured", "handmade"
|
|
552
815
|
* and "by hand" say how a thing was made, and a search for "hands" found
|
|
@@ -735,11 +998,216 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
735
998
|
'cream jar',
|
|
736
999
|
'moisturizer'
|
|
737
1000
|
]
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
// A rider's gear is what they wear (AGL-4065): "Riding Gear Item" took a
|
|
1004
|
+
// gear wheel of a transmission, tagged "gear, mechanics, bikes".
|
|
1005
|
+
objects: [
|
|
1006
|
+
'gear'
|
|
1007
|
+
],
|
|
1008
|
+
within: [
|
|
1009
|
+
'motocross',
|
|
1010
|
+
'motorcycle',
|
|
1011
|
+
'motorbike',
|
|
1012
|
+
'dirtbike',
|
|
1013
|
+
'dirt bike',
|
|
1014
|
+
'enduro',
|
|
1015
|
+
'atv'
|
|
1016
|
+
],
|
|
1017
|
+
kin: [
|
|
1018
|
+
'riding gear',
|
|
1019
|
+
'motocross gear',
|
|
1020
|
+
'protective gear',
|
|
1021
|
+
'helmet',
|
|
1022
|
+
'helmets',
|
|
1023
|
+
'motocross helmet',
|
|
1024
|
+
'motorcycle helmet',
|
|
1025
|
+
'jersey',
|
|
1026
|
+
'boots',
|
|
1027
|
+
'goggles',
|
|
1028
|
+
'gloves'
|
|
1029
|
+
],
|
|
1030
|
+
queries: [
|
|
1031
|
+
'motocross gear',
|
|
1032
|
+
'motocross helmet'
|
|
1033
|
+
]
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
// A dirt bike's tire is knobby; a car's on a highway is not one (AGL-4065).
|
|
1037
|
+
objects: [
|
|
1038
|
+
'tire'
|
|
1039
|
+
],
|
|
1040
|
+
within: [
|
|
1041
|
+
'motocross',
|
|
1042
|
+
'motorcycle',
|
|
1043
|
+
'motorbike',
|
|
1044
|
+
'dirtbike',
|
|
1045
|
+
'dirt bike',
|
|
1046
|
+
'enduro',
|
|
1047
|
+
'atv'
|
|
1048
|
+
],
|
|
1049
|
+
kin: [
|
|
1050
|
+
'tire',
|
|
1051
|
+
'tires',
|
|
1052
|
+
'knobby tire',
|
|
1053
|
+
'motocross tire',
|
|
1054
|
+
'dirt bike tire',
|
|
1055
|
+
'motorcycle tire',
|
|
1056
|
+
'bike tire',
|
|
1057
|
+
'wheel'
|
|
1058
|
+
],
|
|
1059
|
+
queries: [
|
|
1060
|
+
'dirt bike tire',
|
|
1061
|
+
'motocross tire'
|
|
1062
|
+
]
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
// A brake pad is a brake's (AGL-4092): "Brake Pad Set" found nothing by its
|
|
1066
|
+
// pad, then searched "part" alone and took a transmission chain.
|
|
1067
|
+
objects: [
|
|
1068
|
+
'brake',
|
|
1069
|
+
'pad',
|
|
1070
|
+
'rotor',
|
|
1071
|
+
'caliper'
|
|
1072
|
+
],
|
|
1073
|
+
within: [
|
|
1074
|
+
'motocross',
|
|
1075
|
+
'motorcycle',
|
|
1076
|
+
'motorbike',
|
|
1077
|
+
'dirtbike',
|
|
1078
|
+
'dirt bike',
|
|
1079
|
+
'enduro',
|
|
1080
|
+
'atv'
|
|
1081
|
+
],
|
|
1082
|
+
kin: [
|
|
1083
|
+
'brake',
|
|
1084
|
+
'brakes',
|
|
1085
|
+
'brake pad',
|
|
1086
|
+
'brake pads',
|
|
1087
|
+
'brake disc',
|
|
1088
|
+
'brake caliper',
|
|
1089
|
+
'disc brake',
|
|
1090
|
+
'brake rotor'
|
|
1091
|
+
],
|
|
1092
|
+
queries: [
|
|
1093
|
+
'motorcycle brake disc',
|
|
1094
|
+
'dirt bike brake'
|
|
1095
|
+
]
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
// An engine's air is its filter's (AGL-4092): "Engine and air" tiled a
|
|
1099
|
+
// collection with a bare foot tagged "body part".
|
|
1100
|
+
objects: [
|
|
1101
|
+
'engine',
|
|
1102
|
+
'filter',
|
|
1103
|
+
'air',
|
|
1104
|
+
'carburetor'
|
|
1105
|
+
],
|
|
1106
|
+
within: [
|
|
1107
|
+
'motocross',
|
|
1108
|
+
'motorcycle',
|
|
1109
|
+
'motorbike',
|
|
1110
|
+
'dirtbike',
|
|
1111
|
+
'dirt bike',
|
|
1112
|
+
'enduro',
|
|
1113
|
+
'atv'
|
|
1114
|
+
],
|
|
1115
|
+
kin: [
|
|
1116
|
+
'engine',
|
|
1117
|
+
'air filter',
|
|
1118
|
+
'motorcycle engine',
|
|
1119
|
+
'dirt bike engine',
|
|
1120
|
+
'carburetor'
|
|
1121
|
+
],
|
|
1122
|
+
queries: [
|
|
1123
|
+
'dirt bike engine',
|
|
1124
|
+
'motorcycle engine'
|
|
1125
|
+
]
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
// A taco trailer is a food truck, never a tank truck at a rest stop (AGL-4092).
|
|
1129
|
+
objects: [
|
|
1130
|
+
'trailer',
|
|
1131
|
+
'truck'
|
|
1132
|
+
],
|
|
1133
|
+
within: [
|
|
1134
|
+
'taco',
|
|
1135
|
+
'taqueria',
|
|
1136
|
+
'burrito',
|
|
1137
|
+
'mexican food',
|
|
1138
|
+
'street food',
|
|
1139
|
+
'food truck'
|
|
1140
|
+
],
|
|
1141
|
+
kin: [
|
|
1142
|
+
'food truck',
|
|
1143
|
+
'food trailer',
|
|
1144
|
+
'taco truck',
|
|
1145
|
+
'taco trailer',
|
|
1146
|
+
'street food truck'
|
|
1147
|
+
],
|
|
1148
|
+
queries: [
|
|
1149
|
+
'taco truck',
|
|
1150
|
+
'food truck'
|
|
1151
|
+
]
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
// A taco trailer's chips are tortilla chips, never crisps or a cookie's (AGL-4092).
|
|
1155
|
+
objects: [
|
|
1156
|
+
'chip'
|
|
1157
|
+
],
|
|
1158
|
+
within: [
|
|
1159
|
+
'taco',
|
|
1160
|
+
'taqueria',
|
|
1161
|
+
'burrito',
|
|
1162
|
+
'mexican food',
|
|
1163
|
+
'street food',
|
|
1164
|
+
'food truck'
|
|
1165
|
+
],
|
|
1166
|
+
kin: [
|
|
1167
|
+
'tortilla chip',
|
|
1168
|
+
'tortilla chips',
|
|
1169
|
+
'chips and salsa',
|
|
1170
|
+
'chips salsa',
|
|
1171
|
+
'nachos',
|
|
1172
|
+
'nacho'
|
|
1173
|
+
],
|
|
1174
|
+
queries: [
|
|
1175
|
+
'tortilla chips salsa',
|
|
1176
|
+
'nachos'
|
|
1177
|
+
]
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
// A fountain drink is a soda, never a well's fountain (AGL-4092).
|
|
1181
|
+
objects: [
|
|
1182
|
+
'drink',
|
|
1183
|
+
'soda'
|
|
1184
|
+
],
|
|
1185
|
+
within: [
|
|
1186
|
+
'taco',
|
|
1187
|
+
'taqueria',
|
|
1188
|
+
'burrito',
|
|
1189
|
+
'mexican food',
|
|
1190
|
+
'street food',
|
|
1191
|
+
'food truck'
|
|
1192
|
+
],
|
|
1193
|
+
// Never "fountain drink" itself: a well's page address says it word for word.
|
|
1194
|
+
kin: [
|
|
1195
|
+
'soda',
|
|
1196
|
+
'soft drink',
|
|
1197
|
+
'iced tea',
|
|
1198
|
+
'agua fresca',
|
|
1199
|
+
'lemonade',
|
|
1200
|
+
'soda cup'
|
|
1201
|
+
],
|
|
1202
|
+
queries: [
|
|
1203
|
+
'soda cup',
|
|
1204
|
+
'agua fresca'
|
|
1205
|
+
]
|
|
738
1206
|
}
|
|
739
1207
|
];
|
|
740
1208
|
/** An object's kin where the site or the subject is of its world, else `null`. */ function objectKin(object, world) {
|
|
741
1209
|
var _OBJECT_KIN_find;
|
|
742
|
-
return (_OBJECT_KIN_find = OBJECT_KIN.find((entry)=>entry.objects.some((word)=>aiStockStem(word) === object) && entry.within.some((word)=>world.includes(
|
|
1210
|
+
return (_OBJECT_KIN_find = OBJECT_KIN.find((entry)=>entry.objects.some((word)=>aiStockStem(word) === object) && entry.within.some((word)=>world.includes(stemPhrase(word))))) != null ? _OBJECT_KIN_find : null;
|
|
743
1211
|
}
|
|
744
1212
|
/**
|
|
745
1213
|
* The kin of a few common crafts: the words a photo of that world is
|
|
@@ -1389,6 +1857,388 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1389
1857
|
'analog'
|
|
1390
1858
|
]
|
|
1391
1859
|
}
|
|
1860
|
+
},
|
|
1861
|
+
// The six trades of the 2026-10-25 booth kit (AGL-4065), none of which the
|
|
1862
|
+
// table listed: a landscaper and a handyman widened to an office, three
|
|
1863
|
+
// motocross businesses took race cars, cyclists, horses and scooters, and a
|
|
1864
|
+
// taco trailer that also takes catering requests was read as a caterer.
|
|
1865
|
+
{
|
|
1866
|
+
// A landscaper's photos are lawns, beds and gardens at work; a zen garden
|
|
1867
|
+
// of raked sand is not one (Bluebonnet's irrigation section).
|
|
1868
|
+
match: [
|
|
1869
|
+
'landscaping',
|
|
1870
|
+
'landscaper',
|
|
1871
|
+
'landscapers',
|
|
1872
|
+
'lawn',
|
|
1873
|
+
'lawns',
|
|
1874
|
+
'xeriscape',
|
|
1875
|
+
'xeriscaping',
|
|
1876
|
+
'sod',
|
|
1877
|
+
'irrigation',
|
|
1878
|
+
'hardscape',
|
|
1879
|
+
'hardscaping',
|
|
1880
|
+
'gardening',
|
|
1881
|
+
'gardener',
|
|
1882
|
+
'gardeners'
|
|
1883
|
+
],
|
|
1884
|
+
kin: [
|
|
1885
|
+
'landscaping',
|
|
1886
|
+
'landscape',
|
|
1887
|
+
'landscaper',
|
|
1888
|
+
'lawn',
|
|
1889
|
+
'grass',
|
|
1890
|
+
'garden',
|
|
1891
|
+
'yard',
|
|
1892
|
+
'mower',
|
|
1893
|
+
'mowing',
|
|
1894
|
+
'lawn mower',
|
|
1895
|
+
'turf',
|
|
1896
|
+
'hedge',
|
|
1897
|
+
'shrub',
|
|
1898
|
+
'mulch',
|
|
1899
|
+
'sprinkler',
|
|
1900
|
+
'planting',
|
|
1901
|
+
'flowerbed',
|
|
1902
|
+
'flower bed',
|
|
1903
|
+
'backyard',
|
|
1904
|
+
'gardening',
|
|
1905
|
+
'gardener',
|
|
1906
|
+
'xeriscape',
|
|
1907
|
+
'sod',
|
|
1908
|
+
'irrigation'
|
|
1909
|
+
],
|
|
1910
|
+
broad: [
|
|
1911
|
+
'lawn care',
|
|
1912
|
+
'landscaping garden',
|
|
1913
|
+
'garden landscaping'
|
|
1914
|
+
],
|
|
1915
|
+
making: [
|
|
1916
|
+
'landscaper mowing lawn',
|
|
1917
|
+
'gardener planting'
|
|
1918
|
+
],
|
|
1919
|
+
shunLead: [
|
|
1920
|
+
'zen',
|
|
1921
|
+
'sand',
|
|
1922
|
+
'office',
|
|
1923
|
+
'business',
|
|
1924
|
+
'desk',
|
|
1925
|
+
'laptop',
|
|
1926
|
+
'meeting',
|
|
1927
|
+
'golf',
|
|
1928
|
+
'stadium'
|
|
1929
|
+
]
|
|
1930
|
+
},
|
|
1931
|
+
{
|
|
1932
|
+
// A handyman's photos are tools and repairs; never an architect's house
|
|
1933
|
+
// with a family before it, nor a job application (Two Oaks).
|
|
1934
|
+
match: [
|
|
1935
|
+
'handyman',
|
|
1936
|
+
'handymen',
|
|
1937
|
+
'repairman',
|
|
1938
|
+
'drywall',
|
|
1939
|
+
'remodel',
|
|
1940
|
+
'remodeling',
|
|
1941
|
+
'remodeler',
|
|
1942
|
+
'renovation',
|
|
1943
|
+
'renovations',
|
|
1944
|
+
'home repair'
|
|
1945
|
+
],
|
|
1946
|
+
kin: [
|
|
1947
|
+
'handyman',
|
|
1948
|
+
'tools',
|
|
1949
|
+
'tool',
|
|
1950
|
+
'tool belt',
|
|
1951
|
+
'toolbox',
|
|
1952
|
+
'drill',
|
|
1953
|
+
'hammer',
|
|
1954
|
+
'screwdriver',
|
|
1955
|
+
'wrench',
|
|
1956
|
+
'ladder',
|
|
1957
|
+
'workshop',
|
|
1958
|
+
'repair',
|
|
1959
|
+
'home repair',
|
|
1960
|
+
'carpenter',
|
|
1961
|
+
'carpentry',
|
|
1962
|
+
'renovation',
|
|
1963
|
+
'remodel',
|
|
1964
|
+
'deck',
|
|
1965
|
+
'fence',
|
|
1966
|
+
'door',
|
|
1967
|
+
'wall',
|
|
1968
|
+
'plaster',
|
|
1969
|
+
'drywall',
|
|
1970
|
+
'construction'
|
|
1971
|
+
],
|
|
1972
|
+
broad: [
|
|
1973
|
+
'handyman tools',
|
|
1974
|
+
'home repair',
|
|
1975
|
+
'carpenter workshop'
|
|
1976
|
+
],
|
|
1977
|
+
making: [
|
|
1978
|
+
'handyman at work',
|
|
1979
|
+
'handyman drilling'
|
|
1980
|
+
],
|
|
1981
|
+
shunLead: [
|
|
1982
|
+
'architecture',
|
|
1983
|
+
'family',
|
|
1984
|
+
'office',
|
|
1985
|
+
'application',
|
|
1986
|
+
'job',
|
|
1987
|
+
'business',
|
|
1988
|
+
'meeting',
|
|
1989
|
+
'desk',
|
|
1990
|
+
'laptop'
|
|
1991
|
+
]
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
// Motocross and the motorcycle world: a dirt bike, never a bicycle, a
|
|
1995
|
+
// race car, a horse or a scooter (Rut Runners, Holeshot, Berm & Bolt).
|
|
1996
|
+
match: [
|
|
1997
|
+
'motocross',
|
|
1998
|
+
'motorcycle',
|
|
1999
|
+
'motorcycles',
|
|
2000
|
+
'motorbike',
|
|
2001
|
+
'motorbikes',
|
|
2002
|
+
'dirtbike',
|
|
2003
|
+
'dirtbikes',
|
|
2004
|
+
'dirt bike',
|
|
2005
|
+
'enduro',
|
|
2006
|
+
'supercross',
|
|
2007
|
+
'moto',
|
|
2008
|
+
'mx',
|
|
2009
|
+
'atv',
|
|
2010
|
+
'quad bike',
|
|
2011
|
+
'powersports'
|
|
2012
|
+
],
|
|
2013
|
+
kin: [
|
|
2014
|
+
'motocross',
|
|
2015
|
+
'dirt bike',
|
|
2016
|
+
'dirtbike',
|
|
2017
|
+
'enduro',
|
|
2018
|
+
'supercross',
|
|
2019
|
+
'motorbike',
|
|
2020
|
+
'motorcycle',
|
|
2021
|
+
'mx',
|
|
2022
|
+
'atv',
|
|
2023
|
+
'quad bike',
|
|
2024
|
+
'motocross track',
|
|
2025
|
+
'motocross rider',
|
|
2026
|
+
'dirt track'
|
|
2027
|
+
],
|
|
2028
|
+
near: [
|
|
2029
|
+
'rider',
|
|
2030
|
+
'riders',
|
|
2031
|
+
'helmet',
|
|
2032
|
+
'jump',
|
|
2033
|
+
'track',
|
|
2034
|
+
'mud',
|
|
2035
|
+
'dust',
|
|
2036
|
+
'berm',
|
|
2037
|
+
'racing',
|
|
2038
|
+
'race',
|
|
2039
|
+
'racer'
|
|
2040
|
+
],
|
|
2041
|
+
broad: [
|
|
2042
|
+
'motocross',
|
|
2043
|
+
'dirt bike',
|
|
2044
|
+
'motocross race',
|
|
2045
|
+
'motocross rider'
|
|
2046
|
+
],
|
|
2047
|
+
making: [
|
|
2048
|
+
'motorcycle mechanic',
|
|
2049
|
+
'motorcycle workshop'
|
|
2050
|
+
],
|
|
2051
|
+
rivals: {
|
|
2052
|
+
words: [
|
|
2053
|
+
'bicycle',
|
|
2054
|
+
'bicycles',
|
|
2055
|
+
'cycling',
|
|
2056
|
+
'cyclist',
|
|
2057
|
+
'cyclists',
|
|
2058
|
+
'cycle racing',
|
|
2059
|
+
'bike race',
|
|
2060
|
+
'road bike',
|
|
2061
|
+
'mountain bike',
|
|
2062
|
+
'mtb',
|
|
2063
|
+
'downhill',
|
|
2064
|
+
'car',
|
|
2065
|
+
'cars',
|
|
2066
|
+
'race car',
|
|
2067
|
+
'formula',
|
|
2068
|
+
'kart',
|
|
2069
|
+
'karting',
|
|
2070
|
+
'horse',
|
|
2071
|
+
'horses',
|
|
2072
|
+
'equestrian',
|
|
2073
|
+
'scooter',
|
|
2074
|
+
'moped',
|
|
2075
|
+
'vespa',
|
|
2076
|
+
'cruiser',
|
|
2077
|
+
'chopper'
|
|
2078
|
+
],
|
|
2079
|
+
core: [
|
|
2080
|
+
'motocross',
|
|
2081
|
+
'motorcycle',
|
|
2082
|
+
'motorbike',
|
|
2083
|
+
'dirt bike',
|
|
2084
|
+
'dirtbike',
|
|
2085
|
+
'supercross',
|
|
2086
|
+
'atv',
|
|
2087
|
+
'quad bike'
|
|
2088
|
+
]
|
|
2089
|
+
},
|
|
2090
|
+
shunLead: [
|
|
2091
|
+
'car',
|
|
2092
|
+
'scooter',
|
|
2093
|
+
'moped',
|
|
2094
|
+
'cruiser',
|
|
2095
|
+
'chopper',
|
|
2096
|
+
'vespa',
|
|
2097
|
+
'bicycle',
|
|
2098
|
+
'cyclist',
|
|
2099
|
+
'horse',
|
|
2100
|
+
'highway',
|
|
2101
|
+
'asphalt',
|
|
2102
|
+
'traffic'
|
|
2103
|
+
],
|
|
2104
|
+
// A motocross team's "Gate drop" took a man posing with a street bike, tagged "man, model, motorcycle" (AGL-4092).
|
|
2105
|
+
wider: {
|
|
2106
|
+
words: [
|
|
2107
|
+
'motorcycle',
|
|
2108
|
+
'motorcycles',
|
|
2109
|
+
'motorbike',
|
|
2110
|
+
'motorbikes',
|
|
2111
|
+
'moto'
|
|
2112
|
+
],
|
|
2113
|
+
shunLead: [
|
|
2114
|
+
'model',
|
|
2115
|
+
'urban',
|
|
2116
|
+
'street',
|
|
2117
|
+
'road bike',
|
|
2118
|
+
'road racing',
|
|
2119
|
+
'sportbike',
|
|
2120
|
+
'superbike',
|
|
2121
|
+
'studio',
|
|
2122
|
+
'posing'
|
|
2123
|
+
]
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
{
|
|
2127
|
+
// A parts shop of the motocross world: its parts, tires and riding gear,
|
|
2128
|
+
// never a canoe, a delivery or an industrial lathe (Berm & Bolt).
|
|
2129
|
+
match: [
|
|
2130
|
+
'parts',
|
|
2131
|
+
'part',
|
|
2132
|
+
'aftermarket',
|
|
2133
|
+
'oem',
|
|
2134
|
+
'spare parts'
|
|
2135
|
+
],
|
|
2136
|
+
of: [
|
|
2137
|
+
'motocross',
|
|
2138
|
+
'motorcycle',
|
|
2139
|
+
'motorbike',
|
|
2140
|
+
'dirtbike',
|
|
2141
|
+
'dirt bike',
|
|
2142
|
+
'enduro',
|
|
2143
|
+
'supercross',
|
|
2144
|
+
'moto',
|
|
2145
|
+
'atv',
|
|
2146
|
+
'quad bike',
|
|
2147
|
+
'powersports'
|
|
2148
|
+
],
|
|
2149
|
+
kin: [
|
|
2150
|
+
'parts',
|
|
2151
|
+
'part',
|
|
2152
|
+
'spare part',
|
|
2153
|
+
'motorcycle parts',
|
|
2154
|
+
'tire',
|
|
2155
|
+
'tires',
|
|
2156
|
+
'wheel',
|
|
2157
|
+
'chain',
|
|
2158
|
+
'sprocket',
|
|
2159
|
+
'brake',
|
|
2160
|
+
'exhaust',
|
|
2161
|
+
'riding gear',
|
|
2162
|
+
'helmet'
|
|
2163
|
+
],
|
|
2164
|
+
broad: [
|
|
2165
|
+
'dirt bike parts',
|
|
2166
|
+
'motorcycle parts',
|
|
2167
|
+
'motocross gear'
|
|
2168
|
+
],
|
|
2169
|
+
making: [
|
|
2170
|
+
'motorcycle mechanic',
|
|
2171
|
+
'motorcycle workshop'
|
|
2172
|
+
],
|
|
2173
|
+
shunLead: [
|
|
2174
|
+
'canoe',
|
|
2175
|
+
'river',
|
|
2176
|
+
'delivery',
|
|
2177
|
+
'food',
|
|
2178
|
+
'trees',
|
|
2179
|
+
'office',
|
|
2180
|
+
'industry',
|
|
2181
|
+
'industrial',
|
|
2182
|
+
'factory'
|
|
2183
|
+
]
|
|
2184
|
+
},
|
|
2185
|
+
{
|
|
2186
|
+
// A taco trailer's photos are tacos and the truck, never a hotel's buffet,
|
|
2187
|
+
// a chef in whites or raw potatoes (Pit Row Tacos).
|
|
2188
|
+
match: [
|
|
2189
|
+
'taco',
|
|
2190
|
+
'tacos',
|
|
2191
|
+
'taqueria',
|
|
2192
|
+
'taco truck',
|
|
2193
|
+
'taco trailer',
|
|
2194
|
+
'burrito',
|
|
2195
|
+
'burritos'
|
|
2196
|
+
],
|
|
2197
|
+
kin: [
|
|
2198
|
+
'taco',
|
|
2199
|
+
'tacos',
|
|
2200
|
+
'tortilla',
|
|
2201
|
+
'tortillas',
|
|
2202
|
+
'salsa',
|
|
2203
|
+
'mexican',
|
|
2204
|
+
'mexican food',
|
|
2205
|
+
'street food',
|
|
2206
|
+
'food truck',
|
|
2207
|
+
'taco truck',
|
|
2208
|
+
'griddle',
|
|
2209
|
+
'burrito',
|
|
2210
|
+
'quesadilla',
|
|
2211
|
+
'nachos',
|
|
2212
|
+
'guacamole',
|
|
2213
|
+
'carnitas',
|
|
2214
|
+
'pastor',
|
|
2215
|
+
'asada'
|
|
2216
|
+
],
|
|
2217
|
+
broad: [
|
|
2218
|
+
'tacos',
|
|
2219
|
+
'street tacos',
|
|
2220
|
+
'taco truck'
|
|
2221
|
+
],
|
|
2222
|
+
making: [
|
|
2223
|
+
'taco truck cooking',
|
|
2224
|
+
'tacos on the griddle'
|
|
2225
|
+
],
|
|
2226
|
+
out: [
|
|
2227
|
+
'food truck crowd',
|
|
2228
|
+
'street food customers',
|
|
2229
|
+
'taco stand customers'
|
|
2230
|
+
],
|
|
2231
|
+
shunLead: [
|
|
2232
|
+
'buffet',
|
|
2233
|
+
'hotel',
|
|
2234
|
+
'fine dining',
|
|
2235
|
+
'chef',
|
|
2236
|
+
'indian',
|
|
2237
|
+
'potato',
|
|
2238
|
+
'banquet',
|
|
2239
|
+
'cafeteria',
|
|
2240
|
+
'wedding'
|
|
2241
|
+
]
|
|
1392
2242
|
}
|
|
1393
2243
|
];
|
|
1394
2244
|
/** Lowercase words, letters only, without stopwords. */ function contentWords(text) {
|
|
@@ -1409,28 +2259,60 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1409
2259
|
return text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter((word)=>word.length > 1).map(aiStockStem);
|
|
1410
2260
|
}
|
|
1411
2261
|
/** Where a noun phrase ends: punctuation, a preposition, a conjunction. */ const CLAUSE_BREAK = /[,;:.()]|\s[-–—]\s|\b(?:with|on|in|at|against|beside|next|near|under|over|by|from|for|during|while|atop|inside|across|beneath|through|into|onto|amid|among|and|of|that|which|who)\b|&/;
|
|
1412
|
-
/** A
|
|
2262
|
+
/** A clause's words that name the business: no site kind, no filler. */ const businessOf = (clause)=>clause.filter((word)=>!SITE_KINDS.has(word) && !FILLER.has(word));
|
|
2263
|
+
/**
|
|
2264
|
+
* A business type's clauses, each with the word that opened it. Where the
|
|
2265
|
+
* business's own clause is a word or two joined by "and" to a noun phrase
|
|
2266
|
+
* that ends in a place of business ("Motorcycle and dirt bike repair shop",
|
|
2267
|
+
* "Dirt bike and ATV parts shop"), the two are one clause (AGL-4065): the
|
|
2268
|
+
* Holeshot start read its business as "motorcycle" and lost the dirt bikes,
|
|
2269
|
+
* Berm & Bolt's as "dirt bike" and its craft as "bike". "And" before a verb
|
|
2270
|
+
* or a place still ends the clause. A clause that opens with what a customer
|
|
2271
|
+
* may ask ("takes", "books") loses that verb.
|
|
2272
|
+
*/ function businessClauses(businessType) {
|
|
1413
2273
|
const text = businessType.toLowerCase().replace(DOING, ' | ');
|
|
1414
2274
|
const clauses = [];
|
|
1415
2275
|
const breaker = new RegExp(`${BUSINESS_BREAK.source}|\\|`, 'g');
|
|
1416
2276
|
let at = 0;
|
|
1417
2277
|
let opener = '';
|
|
2278
|
+
const push = (raw)=>{
|
|
2279
|
+
var _found_;
|
|
2280
|
+
const found = contentWords(raw);
|
|
2281
|
+
// Never the type's first words: "Books and coffee" sells books.
|
|
2282
|
+
const asked = opener !== '' && found.length > 1 && TRANSACTION_VERBS.has((_found_ = found[0]) != null ? _found_ : '');
|
|
2283
|
+
clauses.push(_extends({
|
|
2284
|
+
opener,
|
|
2285
|
+
words: asked ? found.slice(1) : found
|
|
2286
|
+
}, asked ? {
|
|
2287
|
+
asked: true
|
|
2288
|
+
} : {}));
|
|
2289
|
+
};
|
|
1418
2290
|
for (const match of text.matchAll(breaker)){
|
|
1419
2291
|
var _match_index;
|
|
1420
|
-
|
|
1421
|
-
opener,
|
|
1422
|
-
words: contentWords(text.slice(at, match.index))
|
|
1423
|
-
});
|
|
2292
|
+
push(text.slice(at, match.index));
|
|
1424
2293
|
opener = match[0].trim();
|
|
1425
2294
|
at = ((_match_index = match.index) != null ? _match_index : 0) + match[0].length;
|
|
1426
2295
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
2296
|
+
push(text.slice(at));
|
|
2297
|
+
const own = clauses.findIndex((clause)=>businessOf(clause.words).length > 0);
|
|
2298
|
+
const first = clauses[own];
|
|
2299
|
+
const next = clauses[own + 1];
|
|
2300
|
+
if (first && next && (next.opener === 'and' || next.opener === '&')) {
|
|
2301
|
+
var _right_;
|
|
2302
|
+
const left = businessOf(first.words);
|
|
2303
|
+
const right = businessOf(next.words);
|
|
2304
|
+
const head = (_right_ = right[right.length - 1]) != null ? _right_ : '';
|
|
2305
|
+
if (left.length <= 2 && left.every((word)=>!VENUES.has(word) && !ROLES.has(word)) && right.length >= 2 && VENUES.has(head)) clauses.splice(own, 2, {
|
|
2306
|
+
opener: first.opener,
|
|
2307
|
+
words: [
|
|
2308
|
+
...first.words,
|
|
2309
|
+
...next.words
|
|
2310
|
+
],
|
|
2311
|
+
joined: next.words
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
1431
2314
|
return clauses;
|
|
1432
2315
|
}
|
|
1433
|
-
/** A clause's words that name the business: no site kind, no filler. */ const businessOf = (clause)=>clause.filter((word)=>!SITE_KINDS.has(word) && !FILLER.has(word));
|
|
1434
2316
|
/**
|
|
1435
2317
|
* The kind of business in a few words: the first clause of the business
|
|
1436
2318
|
* type that says more than what kind of site it is, before a place, an
|
|
@@ -1438,9 +2320,13 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1438
2320
|
* of an English noun phrase is at its end). "ceramic artist" from "a
|
|
1439
2321
|
* portfolio for a ceramic artist who makes stoneware bowls"; "candle shop"
|
|
1440
2322
|
* from "a small-batch candle shop selling hand-poured soy candles online".
|
|
2323
|
+
* A business named by two things joined by "and" is its second noun phrase
|
|
2324
|
+
* whole: "dirt bike repair shop" of "Motorcycle and dirt bike repair shop"
|
|
2325
|
+
* (AGL-4065).
|
|
1441
2326
|
*/ export function aiStockBusinessWords(businessType) {
|
|
1442
|
-
for (const { words: clause } of businessClauses(businessType)){
|
|
2327
|
+
for (const { words: clause, joined } of businessClauses(businessType)){
|
|
1443
2328
|
const named = businessOf(clause);
|
|
2329
|
+
if (joined) return businessOf(joined).slice(-4).join(' ');
|
|
1444
2330
|
if (named.length) return named.slice(-3).join(' ');
|
|
1445
2331
|
}
|
|
1446
2332
|
return '';
|
|
@@ -1455,16 +2341,36 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1455
2341
|
const craft = named[named.length - 1];
|
|
1456
2342
|
return craft ? aiStockStem(craft) : '';
|
|
1457
2343
|
}
|
|
1458
|
-
/**
|
|
2344
|
+
/** A word or phrase of a group's list, as stems: what a business type and a hit are read for. */ const groupTerm = (word)=>stemPhrase(word);
|
|
2345
|
+
/**
|
|
2346
|
+
* The site's terms, read from its business type.
|
|
2347
|
+
*
|
|
2348
|
+
* Two worlds are read (AGL-4065). Its DOMAIN is everything its words name
|
|
2349
|
+
* of its world, what a picture of a thing is held to. Its LEAD is the
|
|
2350
|
+
* business's own world: the listed crafts its own clause names (and the
|
|
2351
|
+
* craft's), their names and kin, and the clause's own words where no craft
|
|
2352
|
+
* covers them, never a word that names a world only beside another ("race",
|
|
2353
|
+
* "team") nor what a customer is asked to do. A hero's first tag and a broad
|
|
2354
|
+
* search's hit name the lead: the Rut Runners heroes, held to every word of
|
|
2355
|
+
* "Amateur motocross race team: rider profiles, race schedule…", took race
|
|
2356
|
+
* cars because "race car" named "race". Of the crafts its words list, the one
|
|
2357
|
+
* named first leads (a taco trailer that "takes catering requests" is a taco
|
|
2358
|
+
* trailer), and a craft named only in what a customer may ask for gives the
|
|
2359
|
+
* site no searches of its own.
|
|
2360
|
+
*/ export function aiStockSiteTerms(businessType) {
|
|
1459
2361
|
let business = aiStockBusinessWords(businessType);
|
|
1460
2362
|
let craft = aiStockCraftWords(business);
|
|
1461
2363
|
const domain = new Set();
|
|
1462
2364
|
/** The things it makes, by their head nouns. */ const made = new Set();
|
|
2365
|
+
/** Stems and stem pairs of every clause that names its world, and of those that only say what it is asked for. */ const said = new Set();
|
|
2366
|
+
const asked = new Set();
|
|
2367
|
+
/** The business's own clause, as stems and stem pairs: what its lead is read from. */ let own = [];
|
|
2368
|
+
let ownSeen = false;
|
|
1463
2369
|
/** The last word of the first clause that names the world: "soap" of "for handmade cold-process soap". */ let firstWorld = '';
|
|
1464
2370
|
let named = false;
|
|
1465
2371
|
let making = false;
|
|
1466
2372
|
let place = false;
|
|
1467
|
-
for (const { opener, words: clause } of businessClauses(businessType)){
|
|
2373
|
+
for (const { opener, words: clause, asked: isAsked } of businessClauses(businessType)){
|
|
1468
2374
|
if (opener === '|') making = true;
|
|
1469
2375
|
// Where it is, and who it is for, are not its world — once something has named it.
|
|
1470
2376
|
if (named && PLACE_OR_AUDIENCE.test(opener)) {
|
|
@@ -1478,6 +2384,10 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1478
2384
|
if (place && opener === ',') continue;
|
|
1479
2385
|
place = false;
|
|
1480
2386
|
const kept = businessOf(clause).filter((word)=>!VENUES.has(word) && !ROLES.has(word));
|
|
2387
|
+
if (!ownSeen && businessOf(clause).length) {
|
|
2388
|
+
ownSeen = true;
|
|
2389
|
+
own = kept.filter((word)=>!LEAD_GENERIC.has(word));
|
|
2390
|
+
}
|
|
1481
2391
|
if (kept.length) named = true;
|
|
1482
2392
|
// What it makes names its world by what the things are made of, never
|
|
1483
2393
|
// the things: "stoneware" of "stoneware bowls", "soy" of "soy candles".
|
|
@@ -1485,32 +2395,55 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1485
2395
|
// A set or a gift is never a world: "gift" of "gift sets" is not one.
|
|
1486
2396
|
const world = (making ? kept.slice(0, -1) : kept).filter((word)=>!WRAPPERS.has(word));
|
|
1487
2397
|
for (const word of world)for (const stem of stemList(word))domain.add(stem);
|
|
2398
|
+
for (const term of termsOf([
|
|
2399
|
+
kept.join(' ')
|
|
2400
|
+
]))(isAsked ? asked : said).add(term);
|
|
1488
2401
|
const last = world[world.length - 1];
|
|
1489
2402
|
if (!firstWorld && last) firstWorld = aiStockStem(last);
|
|
1490
2403
|
if (making) for (const word of kept.slice(-1))for (const stem of stemList(word))made.add(stem);
|
|
1491
2404
|
}
|
|
1492
2405
|
// A business named only by what it sells ("a shop selling pottery") is of that world.
|
|
1493
2406
|
if (!domain.size) for (const stem of made)domain.add(stem);
|
|
1494
|
-
const
|
|
2407
|
+
const typeStems = stemList(businessType);
|
|
2408
|
+
const typeTerms = withPairs(typeStems);
|
|
2409
|
+
/** Where a word or a two-word name first stands in the business type; the end where it stands nowhere. */ const positionOf = (term)=>{
|
|
2410
|
+
const [head, tail] = term.split(' ');
|
|
2411
|
+
const index = typeStems.findIndex((stem, at)=>stem === head && (tail === undefined || typeStems[at + 1] === tail));
|
|
2412
|
+
return index >= 0 ? index : Number.MAX_SAFE_INTEGER;
|
|
2413
|
+
};
|
|
2414
|
+
/** Where a group's list first meets the business type, its words in order. */ const at = (list)=>Math.min(...list.map((word)=>positionOf(groupTerm(word))));
|
|
2415
|
+
const names = (word)=>{
|
|
2416
|
+
const term = groupTerm(word);
|
|
2417
|
+
return term.includes(' ') ? said.has(term) || asked.has(term) : domain.has(term) || made.has(term) || term === craft;
|
|
2418
|
+
};
|
|
2419
|
+
const found = CRAFT_KIN.map((group, index)=>({
|
|
2420
|
+
group,
|
|
2421
|
+
index
|
|
2422
|
+
})).filter(({ group })=>group.match.some(names) && (!group.of || group.of.some(names))).sort((a, b)=>Number(Boolean(b.group.of)) - Number(Boolean(a.group.of)) || at(a.group.match) - at(b.group.match) || a.index - b.index);
|
|
2423
|
+
const groups = found.map(({ group })=>group);
|
|
2424
|
+
// A craft named only in what the business is asked for ("takes catering
|
|
2425
|
+
// requests") lends its kin and its refusals, never its searches (AGL-4065).
|
|
2426
|
+
const major = groups.filter((group)=>group.match.some((word)=>said.has(groupTerm(word)) || made.has(groupTerm(word))));
|
|
1495
2427
|
// A business that is only a place ("an online shop for handmade
|
|
1496
2428
|
// cold-process soap") takes its craft from what it is for: the Saltmarsh
|
|
1497
2429
|
// Soap Co start (2026-10-10) read its craft as nothing, so its hero searched
|
|
1498
2430
|
// "shop" (a florist's bouquet) and no product was held to soap.
|
|
1499
2431
|
if (!craft) {
|
|
1500
|
-
var
|
|
1501
|
-
const listed = (
|
|
2432
|
+
var _major_;
|
|
2433
|
+
const listed = (_major_ = major[0]) == null ? void 0 : _major_.match.map(groupTerm).find((stem)=>!stem.includes(' ') && (domain.has(stem) || made.has(stem)));
|
|
1502
2434
|
craft = listed || firstWorld || [
|
|
1503
2435
|
...made
|
|
1504
2436
|
][0] || '';
|
|
1505
2437
|
if (craft && !stemList(business).includes(craft)) business = `${craft} ${business}`.trim();
|
|
1506
|
-
} else if (
|
|
1507
|
-
var
|
|
2438
|
+
} else if (major[0] && !major.some((group)=>group.match.some((word)=>groupTerm(word) === craft))) {
|
|
2439
|
+
var _major__match_map_find;
|
|
1508
2440
|
// A word the table does not list, beside a craft it does, says what kind
|
|
1509
2441
|
// of that craft it is (AGL-3856): "wedding" of "a wedding and portrait
|
|
1510
2442
|
// photographer" is a photographer's subject, never a craft to search by.
|
|
1511
|
-
craft = (
|
|
2443
|
+
craft = (_major__match_map_find = major[0].match.map(groupTerm).find((stem)=>!stem.includes(' ') && (domain.has(stem) || made.has(stem)))) != null ? _major__match_map_find : craft;
|
|
1512
2444
|
}
|
|
1513
2445
|
if (craft) domain.add(craft);
|
|
2446
|
+
const craftGroup = major.find((group)=>group.match.some((word)=>groupTerm(word) === craft));
|
|
1514
2447
|
const broad = [];
|
|
1515
2448
|
const makingSearches = [];
|
|
1516
2449
|
const rivals = [];
|
|
@@ -1520,25 +2453,96 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1520
2453
|
const shun = new Set();
|
|
1521
2454
|
const shunLead = new Set();
|
|
1522
2455
|
for (const group of groups){
|
|
1523
|
-
var _group_shun, _group_shunLead;
|
|
1524
|
-
for (const word of (_group_shun = group.shun) != null ? _group_shun : [])shun.add(
|
|
1525
|
-
for (const word of (_group_shunLead = group.shunLead) != null ? _group_shunLead : [])shunLead.add(
|
|
1526
|
-
for (const word of
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
2456
|
+
var _group_shun, _group_shunLead, _group_near;
|
|
2457
|
+
for (const word of (_group_shun = group.shun) != null ? _group_shun : [])shun.add(groupTerm(word));
|
|
2458
|
+
for (const word of (_group_shunLead = group.shunLead) != null ? _group_shunLead : [])shunLead.add(groupTerm(word));
|
|
2459
|
+
for (const word of [
|
|
2460
|
+
...group.kin,
|
|
2461
|
+
...(_group_near = group.near) != null ? _group_near : []
|
|
2462
|
+
])domain.add(groupTerm(word));
|
|
1530
2463
|
if (group.rivals) rivals.push({
|
|
1531
|
-
words: group.rivals.words.map(
|
|
1532
|
-
core: group.rivals.core.map(
|
|
2464
|
+
words: group.rivals.words.map(groupTerm),
|
|
2465
|
+
core: group.rivals.core.map(groupTerm)
|
|
1533
2466
|
});
|
|
1534
2467
|
}
|
|
2468
|
+
for (const group of major){
|
|
2469
|
+
for (const word of group.match)core.add(groupTerm(word));
|
|
2470
|
+
broad.push(...group.broad);
|
|
2471
|
+
makingSearches.push(...group.making);
|
|
2472
|
+
}
|
|
2473
|
+
// A two-word name of a listed world names it whole: "dirt bike", never
|
|
2474
|
+
// "bike" (a bicycle's) where the type says it nowhere else (AGL-4065).
|
|
2475
|
+
for (const group of groups)for (const word of group.match){
|
|
2476
|
+
const term = groupTerm(word);
|
|
2477
|
+
if (!term.includes(' ') || !typeTerms.includes(term)) continue;
|
|
2478
|
+
domain.add(term);
|
|
2479
|
+
const [head, tail] = term.split(' ');
|
|
2480
|
+
const alone = (stem)=>typeStems.some((found, index)=>found === stem && !(found === head ? typeStems[index + 1] === tail : typeStems[index - 1] === head));
|
|
2481
|
+
for (const stem of [
|
|
2482
|
+
head,
|
|
2483
|
+
tail
|
|
2484
|
+
])if (!alone(stem)) domain.delete(stem);
|
|
2485
|
+
}
|
|
2486
|
+
// What a customer is asked to do is never a world (AGL-4065).
|
|
2487
|
+
for (const stem of TRANSACTION_STEMS)domain.delete(stem);
|
|
2488
|
+
// The business's own world: the crafts its own clause names, and the craft's.
|
|
2489
|
+
const ownTerms = new Set(termsOf([
|
|
2490
|
+
own.join(' ')
|
|
2491
|
+
]));
|
|
2492
|
+
const leading = major.filter((group)=>group === craftGroup || group.match.some((word)=>ownTerms.has(groupTerm(word))));
|
|
2493
|
+
const lead = new Set();
|
|
2494
|
+
for (const group of leading)for (const word of [
|
|
2495
|
+
...group.match,
|
|
2496
|
+
...group.kin
|
|
2497
|
+
])lead.add(groupTerm(word));
|
|
2498
|
+
if (leading.length) {
|
|
2499
|
+
for (const word of own)for (const stem of stemList(word))if (![
|
|
2500
|
+
...lead
|
|
2501
|
+
].some((term)=>term === stem || term.includes(' ') && term.split(' ').includes(stem) && ownTerms.has(term))) lead.add(stem);
|
|
2502
|
+
}
|
|
2503
|
+
// A wider world's names lead only where the business's own words say them
|
|
2504
|
+
// (AGL-4092): a motocross team's world is off-road, never "motorcycle" alone.
|
|
2505
|
+
for (const group of leading){
|
|
2506
|
+
if (!group.wider) continue;
|
|
2507
|
+
const wider = group.wider.words.map(groupTerm);
|
|
2508
|
+
if (wider.some((term)=>ownTerms.has(term))) continue;
|
|
2509
|
+
for (const term of wider)lead.delete(term);
|
|
2510
|
+
for (const word of group.wider.shunLead)shunLead.add(groupTerm(word));
|
|
2511
|
+
}
|
|
2512
|
+
// A trade's world, named by the world it serves: "dirt bike" for a dirt bike parts shop.
|
|
2513
|
+
const served = (craftGroup == null ? void 0 : craftGroup.of) ? groups.filter((group)=>group !== craftGroup).flatMap((group)=>group.match).filter((word)=>typeTerms.includes(groupTerm(word))).sort((a, b)=>positionOf(groupTerm(a)) - positionOf(groupTerm(b)))[0] : undefined;
|
|
1535
2514
|
if (!broad.length) broad.push(business, craft);
|
|
2515
|
+
// A part or gear names no world on its own: a dirt bike shop's part, never a canoe's (AGL-4065).
|
|
2516
|
+
const ofAWorld = (term)=>term.length > 1 && !term.split(' ').every((word)=>WORLD_OBJECTS.has(word));
|
|
2517
|
+
// A trade's things are held to the world it serves, never to its own word (AGL-4092).
|
|
2518
|
+
const tradeNames = new Set(((craftGroup == null ? void 0 : craftGroup.of) ? craftGroup.match : []).map(groupTerm));
|
|
2519
|
+
const held = (craftGroup == null ? void 0 : craftGroup.of) ? [
|
|
2520
|
+
...core
|
|
2521
|
+
].filter((term)=>ofAWorld(term) && !tradeNames.has(term)) : [];
|
|
2522
|
+
const out = [
|
|
2523
|
+
...new Set(leading.flatMap((group)=>{
|
|
2524
|
+
var _group_out;
|
|
2525
|
+
return (_group_out = group.out) != null ? _group_out : [];
|
|
2526
|
+
}))
|
|
2527
|
+
];
|
|
1536
2528
|
return _extends({
|
|
1537
2529
|
business,
|
|
1538
2530
|
craft,
|
|
1539
2531
|
domain: [
|
|
1540
2532
|
...domain
|
|
1541
2533
|
].filter((stem)=>stem.length > 1),
|
|
2534
|
+
lead: leading.length ? [
|
|
2535
|
+
...lead
|
|
2536
|
+
].filter(ofAWorld) : [
|
|
2537
|
+
...domain
|
|
2538
|
+
].filter((stem)=>stem.length > 1)
|
|
2539
|
+
}, served ? {
|
|
2540
|
+
leadWord: served
|
|
2541
|
+
} : {}, held.length ? {
|
|
2542
|
+
held
|
|
2543
|
+
} : {}, out.length ? {
|
|
2544
|
+
out
|
|
2545
|
+
} : {}, {
|
|
1542
2546
|
broad: [
|
|
1543
2547
|
...new Set(broad.map((query)=>query.trim()).filter(Boolean))
|
|
1544
2548
|
],
|
|
@@ -1559,12 +2563,12 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1559
2563
|
]
|
|
1560
2564
|
} : {}, {
|
|
1561
2565
|
named: [
|
|
1562
|
-
...new Set(
|
|
2566
|
+
...new Set(typeTerms)
|
|
1563
2567
|
],
|
|
1564
2568
|
rivals,
|
|
1565
2569
|
core: [
|
|
1566
2570
|
...core
|
|
1567
|
-
],
|
|
2571
|
+
].filter((term)=>!(craftGroup == null ? void 0 : craftGroup.of) || ofAWorld(term)),
|
|
1568
2572
|
shun: [
|
|
1569
2573
|
...shun
|
|
1570
2574
|
],
|
|
@@ -1578,20 +2582,45 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1578
2582
|
* the beta.266 Mexicano portfolio's gallery was "Project one" to "Project
|
|
1579
2583
|
* four", and searched "project" (students at a whiteboard), "project four"
|
|
1580
2584
|
* (lavender) and "four" (a running track's lane numbers).
|
|
1581
|
-
*/ const PLACEHOLDER_SUBJECTS = words('project projects piece pieces item items example examples sample samples entry entries untitled work works ' + 'selected recent latest ' + 'four five six seven eight nine ten first second third fourth fifth sixth seventh eighth ninth tenth')
|
|
2585
|
+
*/ const PLACEHOLDER_SUBJECTS = words('project projects piece pieces item items example examples sample samples entry entries untitled work works ' + 'selected recent latest ' + 'four five six seven eight nine ten first second third fourth fifth sixth seventh eighth ninth tenth ' + // When a thing is, never the thing (AGL-4065): "Upcoming race 1 (details to be added)" searched "upcoming race".
|
|
2586
|
+
'upcoming past next previous future');
|
|
1582
2587
|
/** A phrase's content words without its filler; all filler, and nothing is left. */ function subjectOf(list) {
|
|
1583
|
-
return list.filter((word)=>!FILLER.has(word) && !PLACEHOLDER_SUBJECTS.has(word));
|
|
2588
|
+
return list.filter((word)=>!FILLER.has(word) && !PLACEHOLDER_SUBJECTS.has(word) && !GENERIC_OBJECTS.has(word) && !SENTENCE_WORDS.has(word));
|
|
1584
2589
|
}
|
|
1585
2590
|
/**
|
|
1586
2591
|
* A picture's subject in a few words (AGL-3660): the noun phrase its alt
|
|
1587
2592
|
* text opens with — its first clause holding more than filler, cut where a
|
|
1588
2593
|
* preposition starts the setting, filler dropped, its last three words (the
|
|
1589
|
-
* head noun is the last) — else its section's name.
|
|
2594
|
+
* head noun is the last) — else its section's name. A word or two joined by
|
|
2595
|
+
* "and" to a longer noun phrase only modify its head: the subject of "Potato
|
|
2596
|
+
* and egg breakfast taco" is "egg breakfast taco", never "potato" (AGL-4065).
|
|
1590
2597
|
*/ export function aiStockSubjectWords(alt, sectionName) {
|
|
1591
2598
|
// "Hand-poured", "by hand" and "hands" say how it was made, never what it shows.
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
2599
|
+
const text = withoutHands(alt);
|
|
2600
|
+
const breaker = new RegExp(CLAUSE_BREAK.source, 'g');
|
|
2601
|
+
const clauses = [];
|
|
2602
|
+
let at = 0;
|
|
2603
|
+
for (const match of text.matchAll(breaker)){
|
|
2604
|
+
var _match_index;
|
|
2605
|
+
clauses.push({
|
|
2606
|
+
words: subjectOf(contentWords(text.slice(at, match.index))),
|
|
2607
|
+
then: match[0].trim()
|
|
2608
|
+
});
|
|
2609
|
+
at = ((_match_index = match.index) != null ? _match_index : 0) + match[0].length;
|
|
2610
|
+
}
|
|
2611
|
+
clauses.push({
|
|
2612
|
+
words: subjectOf(contentWords(text.slice(at))),
|
|
2613
|
+
then: ''
|
|
2614
|
+
});
|
|
2615
|
+
for (const [index, { words: found, then }] of clauses.entries()){
|
|
2616
|
+
var _ref;
|
|
2617
|
+
var _clauses_;
|
|
2618
|
+
if (!found.length) continue;
|
|
2619
|
+
// Never where the second is one thing in a wrapper: "Soap and Lotion Gift Set" is a gift of both.
|
|
2620
|
+
const next = (_ref = (_clauses_ = clauses[index + 1]) == null ? void 0 : _clauses_.words) != null ? _ref : [];
|
|
2621
|
+
const things = next.filter((word)=>!WRAPPERS.has(word));
|
|
2622
|
+
if ((then === 'and' || then === '&') && found.length <= 2 && things.length >= 2) return next.slice(-3).join(' ');
|
|
2623
|
+
return found.slice(-3).join(' ');
|
|
1595
2624
|
}
|
|
1596
2625
|
return subjectOf(contentWords(withoutHands(sectionName))).slice(0, 2).join(' ');
|
|
1597
2626
|
}
|
|
@@ -1599,10 +2628,12 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
|
|
|
1599
2628
|
* The thing a subject shows, one singular word: its last word that is not
|
|
1600
2629
|
* a set or a wrapper of it — "bowl" from "nesting bowl set", "candle" from
|
|
1601
2630
|
* "candle gift set", "vase" from "bud vase". Its last word when every word
|
|
1602
|
-
* is one ("gift set").
|
|
2631
|
+
* is one ("gift set"). A feeling, a time or a way of being somewhere is
|
|
2632
|
+
* never one ({@link NON_THINGS}, AGL-4092): "Come hungry" and "Around town"
|
|
2633
|
+
* show no thing, and "Race day" is of the race.
|
|
1603
2634
|
*/ export function aiStockObjectWord(subject) {
|
|
1604
2635
|
var _ref, _reverse_find;
|
|
1605
|
-
const list = subject.split(/\s+/).filter(
|
|
2636
|
+
const list = subject.split(/\s+/).filter((word)=>word && !NON_THINGS.has(word));
|
|
1606
2637
|
const object = (_ref = (_reverse_find = [
|
|
1607
2638
|
...list
|
|
1608
2639
|
].reverse().find((word)=>!WRAPPERS.has(word))) != null ? _reverse_find : list[list.length - 1]) != null ? _ref : '';
|
|
@@ -2378,7 +3409,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2378
3409
|
* product names it. A size never names the product: "Bath Gift Set Starter
|
|
2379
3410
|
* Size" is a bath gift set.
|
|
2380
3411
|
*/ export function aiStockProductSearches(slot, craft, domain = [], fallback = {}) {
|
|
2381
|
-
var _ref, _subjects_, _fallback_core, _ref1, _fallback_broad, _fallback_broad1;
|
|
3412
|
+
var _ref, _subjects_, _fallback_held, _fallback_core, _fallback_leadWord, _ref1, _fallback_broad, _fallback_broad1;
|
|
2382
3413
|
var _slot_product;
|
|
2383
3414
|
const orientation = aiStockOrientation(slot.aspect);
|
|
2384
3415
|
const size = orientation === 'vertical' ? {
|
|
@@ -2404,23 +3435,31 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2404
3435
|
// A product of the shop's world beside its category is held to either:
|
|
2405
3436
|
// a soap shop's body lotion names "lotion", never "soap".
|
|
2406
3437
|
const ownWorld = stemList(own).filter((stem)=>world.has(stem) && !WRAPPERS.has(stem) && !craftStems.includes(stem));
|
|
2407
|
-
|
|
3438
|
+
// A trade's product is held to the world it serves, never to its craft
|
|
3439
|
+
// (AGL-4092): a parts shop's "part" named a transmission's "mechanical part".
|
|
3440
|
+
const served = [
|
|
3441
|
+
...new Set(((_fallback_held = fallback.held) != null ? _fallback_held : []).map(stemPhrase).filter(Boolean))
|
|
3442
|
+
];
|
|
3443
|
+
const trade = served.length > 0;
|
|
3444
|
+
const category = trade ? served : craft && ownWorld.length ? [
|
|
2408
3445
|
...craftStems,
|
|
2409
3446
|
...ownWorld
|
|
2410
3447
|
] : [];
|
|
2411
|
-
const core = [
|
|
3448
|
+
const core = trade ? served : [
|
|
2412
3449
|
...new Set([
|
|
2413
3450
|
...craftStems,
|
|
2414
3451
|
...(_fallback_core = fallback.core) != null ? _fallback_core : []
|
|
2415
3452
|
])
|
|
2416
3453
|
];
|
|
3454
|
+
// A trade's product is named by the world it serves: "dirt bike skid plate", never "part skid plate".
|
|
3455
|
+
const prefix = trade ? (_fallback_leadWord = fallback.leadWord) != null ? _fallback_leadWord : '' : craft;
|
|
2417
3456
|
const shun = ((_ref1 = kin == null ? void 0 : kin.shun) != null ? _ref1 : []).map(aiStockStem);
|
|
2418
3457
|
const queries = [];
|
|
2419
3458
|
for (const subject of subjects){
|
|
2420
3459
|
var _ref2;
|
|
2421
3460
|
const stems = stemList(subject);
|
|
2422
3461
|
const qualifier = (_ref2 = kin == null ? void 0 : kin.qualifier) != null ? _ref2 : '';
|
|
2423
|
-
const query = kin ? !qualifier || stems.includes(aiStockStem(qualifier)) ? subject : kin.after ? `${subject} ${qualifier}` : `${qualifier} ${subject}` :
|
|
3462
|
+
const query = kin ? !qualifier || stems.includes(aiStockStem(qualifier)) ? subject : kin.after ? `${subject} ${qualifier}` : `${qualifier} ${subject}` : prefix && !stems.some((stem)=>world.has(stem)) ? `${prefix} ${subject}` : subject;
|
|
2424
3463
|
queries.push({
|
|
2425
3464
|
query,
|
|
2426
3465
|
subject,
|
|
@@ -2444,7 +3483,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2444
3483
|
object,
|
|
2445
3484
|
lead: true
|
|
2446
3485
|
});
|
|
2447
|
-
else if (craft) queries.push({
|
|
3486
|
+
else if (craft && !trade) queries.push({
|
|
2448
3487
|
query: craft,
|
|
2449
3488
|
subject: own || craft,
|
|
2450
3489
|
object: craft,
|
|
@@ -2507,6 +3546,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2507
3546
|
tier: entry.tier
|
|
2508
3547
|
} : {}, shun.length ? {
|
|
2509
3548
|
shun
|
|
3549
|
+
} : {}, trade ? {
|
|
3550
|
+
served
|
|
2510
3551
|
} : {});
|
|
2511
3552
|
if (entry.broad) {
|
|
2512
3553
|
const heldTo = [
|
|
@@ -2523,9 +3564,11 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2523
3564
|
kin: kinPhrases
|
|
2524
3565
|
} : {}, heldTo.length ? {
|
|
2525
3566
|
category: heldTo
|
|
2526
|
-
} : {}) : core.length ? {
|
|
3567
|
+
} : {}) : core.length ? _extends({
|
|
2527
3568
|
category: core
|
|
2528
|
-
}
|
|
3569
|
+
}, trade ? {
|
|
3570
|
+
leadOwn: true
|
|
3571
|
+
} : {}) : {}));
|
|
2529
3572
|
continue;
|
|
2530
3573
|
}
|
|
2531
3574
|
searches.push(_extends({}, common, entry.lead ? {
|
|
@@ -2536,7 +3579,9 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2536
3579
|
object: entry.object
|
|
2537
3580
|
} : {}, kinPhrases.length && entry.object === object ? {
|
|
2538
3581
|
kin: kinPhrases
|
|
2539
|
-
} : {},
|
|
3582
|
+
} : {}, trade ? {
|
|
3583
|
+
strict: true
|
|
3584
|
+
} : craft ? {
|
|
2540
3585
|
requires: craft
|
|
2541
3586
|
} : {}, category.length ? {
|
|
2542
3587
|
category
|
|
@@ -2550,8 +3595,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2550
3595
|
* asks first for the role its words or its list's name (`sectionName`) say,
|
|
2551
3596
|
* in the site's world: "teacher classroom" for a "Math instructor" (AGL-3856).
|
|
2552
3597
|
*/ export function aiStockSearchesFor(slot, terms, subject, part, sectionName = '') {
|
|
2553
|
-
var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _terms_setting1, _ref2, _terms_maker, _terms_maker1, _ref3;
|
|
2554
|
-
var _aiStockPeopleRole, _slot_record, _slot_record1;
|
|
3598
|
+
var _terms_core, _terms_leadWord, _terms_making, _ref, _ref1, _terms_setting, _terms_setting1, _ref2, _terms_maker, _terms_maker1, _ref3;
|
|
3599
|
+
var _terms_held, _aiStockPeopleRole, _slot_record, _terms_out, _slot_record1;
|
|
2555
3600
|
const orientation = aiStockOrientation(slot.aspect);
|
|
2556
3601
|
const size = slot.role === 'hero' ? {
|
|
2557
3602
|
minWidth: 1600
|
|
@@ -2596,16 +3641,36 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2596
3641
|
// A hero of a craft names the craft itself: the Saltmarsh Soap Co hero
|
|
2597
3642
|
// (2026-10-10) was a florist's bouquet tagged "natural" (AGL-3660).
|
|
2598
3643
|
const core = (_terms_core = terms.core) != null ? _terms_core : [];
|
|
2599
|
-
|
|
3644
|
+
// The world a trade serves qualifies its pictures where the craft is the trade: "dirt bike", never "part" (AGL-4065).
|
|
3645
|
+
const leadWord = (_terms_leadWord = terms.leadWord) != null ? _terms_leadWord : terms.craft;
|
|
3646
|
+
const leadStems = stemList(leadWord);
|
|
3647
|
+
const qualify = (phrase)=>leadWord && !namesWorld && !leadStems.every((stem)=>stemList(phrase).includes(stem)) ? `${leadWord} ${phrase}` : phrase;
|
|
3648
|
+
// A part, gear, a machine, or one word of a two-word world ("bike" of "dirt
|
|
3649
|
+
// bike"), is never searched alone (AGL-4065): searched with the world, and
|
|
3650
|
+
// its photo is OF the thing and names the world. "Parts for the ranch"
|
|
3651
|
+
// searched "parts" and took a canoe tagged "part".
|
|
3652
|
+
const ofTheWorld = Boolean(leadWord) && subjectStems.length > 0 && subjectStems.every((stem)=>WORLD_OBJECTS.has(stem) || leadStems.includes(stem)) && (subjectStems.some((stem)=>WORLD_OBJECTS.has(stem)) || leadStems.length > 1 && subjectStems.length < leadStems.length);
|
|
3653
|
+
/** The words a photo of the world names, the world's things left out: a dirt bike's, never "part". */ const worldNames = core.filter((stem)=>!stem.split(' ').some((word)=>WORLD_OBJECTS.has(word)));
|
|
2600
3654
|
// A collection tile is product-like: held to the shop's category and its
|
|
2601
3655
|
// own object among the first tags, and searched by its object's kin, never
|
|
2602
3656
|
// the category's world alone (AGL-3660).
|
|
2603
3657
|
const collection = part === 'collection' && Boolean(terms.craft) && !work;
|
|
2604
|
-
|
|
3658
|
+
// A picture's broad searches find a photo OF the business's own world,
|
|
3659
|
+
// never one naming it in a word of a tag (AGL-4092); a hero's first tag
|
|
3660
|
+
// already names it.
|
|
3661
|
+
const broad = kin && collection || work ? [] : terms.broad.filter(shown).map((query)=>_extends({
|
|
2605
3662
|
query,
|
|
2606
3663
|
broad: true,
|
|
2607
3664
|
category: collection
|
|
3665
|
+
}, slot.role === 'hero' ? {} : {
|
|
3666
|
+
own: true
|
|
2608
3667
|
}));
|
|
3668
|
+
// A trade's things are held to the world it serves, never to its craft (AGL-4092).
|
|
3669
|
+
const heldTo = ((_terms_held = terms.held) == null ? void 0 : _terms_held.length) ? terms.held : null;
|
|
3670
|
+
// A caption that names no thing (AGL-4092): "Come hungry" is a picture of
|
|
3671
|
+
// the site's own world, and "Around town" of its customers out where it is.
|
|
3672
|
+
const thingless = Boolean(subject) && !object && !work;
|
|
3673
|
+
const out = thingless && slot.role !== 'hero' && PLACE_PHRASES.test(slot.alt);
|
|
2609
3674
|
// The maker at work, held to the craft: never "hands" (AGL-3660).
|
|
2610
3675
|
const makers = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).filter(shown).map((query)=>({
|
|
2611
3676
|
query,
|
|
@@ -2627,21 +3692,61 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2627
3692
|
}
|
|
2628
3693
|
// The work's own subject is never qualified by the craft: "getting ready", never "photographer getting ready".
|
|
2629
3694
|
const own = work ? (phrase)=>phrase : qualify;
|
|
2630
|
-
|
|
3695
|
+
// A record's cover is OF what it names, searched in the site's world first
|
|
3696
|
+
// (AGL-4065): "handyman drywall" before "drywall", which took an architect's
|
|
3697
|
+
// house with a family before its studs.
|
|
3698
|
+
const record = Boolean(slot.record) && !work && !ofTheWorld && Boolean(leadWord);
|
|
3699
|
+
const thing = (!subject ? [] : thingless ? out ? (((_terms_out = terms.out) == null ? void 0 : _terms_out.length) ? terms.out : [
|
|
3700
|
+
`${leadWord} customers`.trim()
|
|
3701
|
+
]).map((query)=>({
|
|
3702
|
+
query,
|
|
3703
|
+
out: true
|
|
3704
|
+
})) : [
|
|
2631
3705
|
{
|
|
2632
|
-
query:
|
|
2633
|
-
|
|
3706
|
+
query: `${leadWord} ${subject}`.trim(),
|
|
3707
|
+
own: true
|
|
3708
|
+
}
|
|
3709
|
+
] : ofTheWorld ? [
|
|
3710
|
+
{
|
|
3711
|
+
query: `${leadWord} ${subjectStems.some((stem)=>!leadStems.includes(stem)) ? subject.split(/\s+/).filter((word)=>!leadStems.includes(aiStockStem(word))).join(' ') : ''}`.trim(),
|
|
3712
|
+
held: true,
|
|
3713
|
+
leads: true,
|
|
3714
|
+
ofWorld: true
|
|
2634
3715
|
},
|
|
2635
3716
|
...kin ? kin.queries.map((query)=>({
|
|
2636
3717
|
query,
|
|
2637
|
-
held: true
|
|
2638
|
-
|
|
3718
|
+
held: true,
|
|
3719
|
+
leads: true,
|
|
3720
|
+
ofWorld: true
|
|
3721
|
+
})) : []
|
|
3722
|
+
] : [
|
|
3723
|
+
...record && !leadStems.every((stem)=>subjectStems.includes(stem)) ? [
|
|
2639
3724
|
{
|
|
3725
|
+
query: `${leadWord} ${subject}`,
|
|
3726
|
+
held: true,
|
|
3727
|
+
leads: true
|
|
3728
|
+
}
|
|
3729
|
+
] : [],
|
|
3730
|
+
_extends({
|
|
3731
|
+
query: own(subject),
|
|
3732
|
+
held: true
|
|
3733
|
+
}, record ? {
|
|
3734
|
+
leads: true
|
|
3735
|
+
} : {}),
|
|
3736
|
+
...kin ? kin.queries.map((query)=>_extends({
|
|
3737
|
+
query,
|
|
3738
|
+
held: true
|
|
3739
|
+
}, record ? {
|
|
3740
|
+
leads: true
|
|
3741
|
+
} : {})) : [
|
|
3742
|
+
_extends({
|
|
2640
3743
|
query: own(object),
|
|
2641
3744
|
held: true
|
|
2642
|
-
}
|
|
3745
|
+
}, record ? {
|
|
3746
|
+
leads: true
|
|
3747
|
+
} : {})
|
|
2643
3748
|
]
|
|
2644
|
-
]
|
|
3749
|
+
]).filter((query)=>said(query.query));
|
|
2645
3750
|
// Never the maker, the camera, an animal or a toy, unless the record names it.
|
|
2646
3751
|
const recordNamed = work ? new Set(stemList(`${slot.alt} ${(_ref2 = (_slot_record1 = slot.record) == null ? void 0 : _slot_record1.category) != null ? _ref2 : ''}`)) : null;
|
|
2647
3752
|
const refuseLead = recordNamed ? [
|
|
@@ -2795,20 +3900,36 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2795
3900
|
broad: true
|
|
2796
3901
|
} : {}, held && kinPhrases.length ? {
|
|
2797
3902
|
kin: kinPhrases
|
|
2798
|
-
} : {}, held && collection ? {
|
|
3903
|
+
} : {}, held && collection || raw.leads ? {
|
|
2799
3904
|
lead: true
|
|
2800
|
-
} : {}, terms.craft && (raw.category || raw.held && collection) ?
|
|
3905
|
+
} : {}, terms.craft && !raw.ofWorld && (raw.category || raw.held && collection) ? heldTo ? {
|
|
3906
|
+
category: heldTo,
|
|
3907
|
+
served: heldTo
|
|
3908
|
+
} : _extends({
|
|
2801
3909
|
requires: terms.craft
|
|
2802
3910
|
}, ownWorld.length ? {
|
|
2803
3911
|
category: [
|
|
2804
3912
|
terms.craft,
|
|
2805
3913
|
...ownWorld
|
|
2806
3914
|
]
|
|
2807
|
-
} : {}) : {},
|
|
3915
|
+
} : {}) : {}, raw.ofWorld && worldNames.length ? {
|
|
3916
|
+
category: worldNames
|
|
3917
|
+
} : {}, slot.role === 'hero' && core.length ? {
|
|
2808
3918
|
category: core
|
|
2809
3919
|
} : {}, slot.role === 'hero' ? {
|
|
2810
3920
|
first: true
|
|
2811
|
-
} : {},
|
|
3921
|
+
} : {}, raw.own ? {
|
|
3922
|
+
leadOwn: true
|
|
3923
|
+
} : {}, raw.out ? {
|
|
3924
|
+
broad: true,
|
|
3925
|
+
needs: PLACE_NEEDS.map(stemPhrase),
|
|
3926
|
+
refuseLead: [
|
|
3927
|
+
...new Set([
|
|
3928
|
+
...refuseLead,
|
|
3929
|
+
...FOREIGN_STREET.map(stemPhrase)
|
|
3930
|
+
])
|
|
3931
|
+
]
|
|
3932
|
+
} : refuseLead.length ? {
|
|
2812
3933
|
refuseLead
|
|
2813
3934
|
} : {}, work ? {
|
|
2814
3935
|
worldless: true
|
|
@@ -2849,15 +3970,17 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2849
3970
|
var _photo_tags, _photo_alt, _photo_alt1, _tags_, _photo_alt2;
|
|
2850
3971
|
const slug = pageSlug(photo.pageUrl);
|
|
2851
3972
|
const tags = ((_photo_tags = photo.tags) != null ? _photo_tags : []).map((tag)=>tag.toLowerCase().trim()).filter(Boolean);
|
|
2852
|
-
|
|
3973
|
+
// Each tag's words and pairs of them, read tag by tag (AGL-4065): "race car"
|
|
3974
|
+
// and "dirt bike" are phrases a world names; the page address is read whole.
|
|
3975
|
+
const lead = termsOf([
|
|
2853
3976
|
...tags.slice(0, 3),
|
|
2854
3977
|
slug
|
|
2855
|
-
]
|
|
2856
|
-
const all =
|
|
3978
|
+
]);
|
|
3979
|
+
const all = termsOf([
|
|
2857
3980
|
...tags,
|
|
2858
3981
|
(_photo_alt = photo.alt) != null ? _photo_alt : '',
|
|
2859
3982
|
slug
|
|
2860
|
-
]
|
|
3983
|
+
]);
|
|
2861
3984
|
const phrases = [
|
|
2862
3985
|
...tags.map((tag)=>stemList(tag).join(' ')),
|
|
2863
3986
|
stemList((_photo_alt1 = photo.alt) != null ? _photo_alt1 : '').join(' '),
|
|
@@ -2885,12 +4008,61 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2885
4008
|
};
|
|
2886
4009
|
}
|
|
2887
4010
|
/** Whether a phrase is among a hit's phrases, word for word. */ const among = (phrases, phrase)=>phrases.some((found)=>` ${found} `.includes(` ${phrase} `));
|
|
4011
|
+
/**
|
|
4012
|
+
* Whether one of a hit's first three tags is OF a world (AGL-4092): it names
|
|
4013
|
+
* the world, and its last word, or its last two, is a word of the world or of
|
|
4014
|
+
* its domain. "Street tacos" and "dirt bike tire" are; "taco bowl" is a bowl,
|
|
4015
|
+
* "body part" a part. Read from the tags alone: the page address repeats them
|
|
4016
|
+
* run together, and a run of words has no head.
|
|
4017
|
+
*/ function leadsWithWorld(tags, world, domain) {
|
|
4018
|
+
return tags.slice(0, 3).some((tag)=>{
|
|
4019
|
+
var _stems_;
|
|
4020
|
+
const stems = stemList(tag);
|
|
4021
|
+
if (!withPairs(stems).some((term)=>world.has(term))) return false;
|
|
4022
|
+
return [
|
|
4023
|
+
(_stems_ = stems[stems.length - 1]) != null ? _stems_ : '',
|
|
4024
|
+
stems.slice(-2).join(' ')
|
|
4025
|
+
].some((term)=>Boolean(term) && (world.has(term) || domain.has(term)));
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
/**
|
|
4029
|
+
* What a picture of a business's world is never OF unless its words name it
|
|
4030
|
+
* (AGL-4092): an animal or a toy (as a portfolio's record never is,
|
|
4031
|
+
* {@link RECORD_ODD_LEADS}), or a bare body part. Each group is let in whole
|
|
4032
|
+
* by any of its words in `said` — the slot's words, its section's, the
|
|
4033
|
+
* brief's — so a dog groomer's pictures show dogs and cats alike. As stems;
|
|
4034
|
+
* the body's left out where `body` is false (a portrait's face).
|
|
4035
|
+
*/ export function aiStockOddLeads(said, options = {}) {
|
|
4036
|
+
const named = new Set(said.flatMap((text)=>stemList(text)));
|
|
4037
|
+
const groups = [
|
|
4038
|
+
'animal animals cat cats kitten kittens dog dogs puppy puppies pet pets bird birds frog frogs duck ducks horse horses ' + 'cow cows rabbit rabbits bunny',
|
|
4039
|
+
'toy toys figurine figurines lego doll dolls teddy puppet miniature',
|
|
4040
|
+
...options.body === false ? [] : [
|
|
4041
|
+
`${[
|
|
4042
|
+
...BODY_PARTS
|
|
4043
|
+
].join(' ')} toe toes barefoot`
|
|
4044
|
+
]
|
|
4045
|
+
].map((group)=>[
|
|
4046
|
+
...new Set(stemList(group))
|
|
4047
|
+
]);
|
|
4048
|
+
return groups.filter((group)=>!group.some((stem)=>named.has(stem))).flat();
|
|
4049
|
+
}
|
|
2888
4050
|
/**
|
|
2889
4051
|
* Whether a hit carries a topic its site never named ({@link SENSITIVE_TOPICS}):
|
|
2890
4052
|
* a cross, a grave, a flag, a gun, a glass of wine, a child, a mannequin.
|
|
2891
4053
|
*/ export function aiStockSensitive(photo, named = []) {
|
|
2892
4054
|
const { words: found } = hitWords(photo);
|
|
2893
4055
|
const site = new Set(named);
|
|
4056
|
+
// A maker's name, in its tags or its page address, unless the site names the maker (AGL-4065),
|
|
4057
|
+
// or a word one letter off it: "susuki" is a Suzuki (AGL-4092).
|
|
4058
|
+
const makers = aiStockMakerTerms(photo);
|
|
4059
|
+
if ([
|
|
4060
|
+
...MAKER_STEMS
|
|
4061
|
+
].some((maker)=>makers.has(maker) && !site.has(maker))) return true;
|
|
4062
|
+
for (const term of makers){
|
|
4063
|
+
const maker = term.includes(' ') ? null : fuzzyMaker(term);
|
|
4064
|
+
if (maker && !site.has(maker) && !site.has(term)) return true;
|
|
4065
|
+
}
|
|
2894
4066
|
return SENSITIVE_STEMS.some((topic)=>[
|
|
2895
4067
|
...topic.words
|
|
2896
4068
|
].some((word)=>found.has(word)) && ![
|
|
@@ -2908,13 +4080,15 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2908
4080
|
* the subject's two-word phrase 3, each of its other words 1, and the
|
|
2909
4081
|
* site's world 1, plus a quarter when it is among the first tags.
|
|
2910
4082
|
*/ export function aiStockRelevance(photo, judgement) {
|
|
2911
|
-
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;
|
|
2912
|
-
var _judgement_roleScene, _judgement_roleRefuse, _judgement_away;
|
|
4083
|
+
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, _photo_tags, _judgement_roles;
|
|
4084
|
+
var _judgement_served, _judgement_roleScene, _judgement_roleRefuse, _judgement_away, _judgement_world, _judgement_oddLeads;
|
|
2913
4085
|
const hit = hitWords(photo);
|
|
2914
4086
|
if (hit.rejected) return 0;
|
|
2915
4087
|
if (aiStockSensitive(photo, judgement.named)) return 0;
|
|
2916
4088
|
// Glass art is not pottery: a rival craft's photo names this craft's own words.
|
|
2917
4089
|
for (const rival of (_judgement_rivals = judgement.rivals) != null ? _judgement_rivals : [])if (rival.words.some((word)=>hit.all.has(word)) && !rival.core.some((word)=>hit.all.has(word))) return 0;
|
|
4090
|
+
// A trade's thing is of the world it serves, whatever else names it (AGL-4092).
|
|
4091
|
+
if (((_judgement_served = judgement.served) == null ? void 0 : _judgement_served.length) && !judgement.served.some((term)=>hit.all.has(stemPhrase(term)))) return 0;
|
|
2918
4092
|
const subject = stemList((_judgement_subject = judgement.subject) != null ? _judgement_subject : '');
|
|
2919
4093
|
// What the slot or the brief names lets a shunned word, a room or a fragrance in.
|
|
2920
4094
|
const owned = new Set([
|
|
@@ -2930,17 +4104,17 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2930
4104
|
// A teacher is shown teaching: a classroom, a lecture or students among the
|
|
2931
4105
|
// first tags, and never a model, a pose or a painting (AGL-3856).
|
|
2932
4106
|
if (((_judgement_roleScene = judgement.roleScene) == null ? void 0 : _judgement_roleScene.length) || ((_judgement_roleRefuse = judgement.roleRefuse) == null ? void 0 : _judgement_roleRefuse.length)) {
|
|
2933
|
-
var
|
|
4107
|
+
var _photo_tags1, _photo_tags2, _judgement_roleRefuse1;
|
|
2934
4108
|
var _judgement_roleScene1;
|
|
2935
|
-
const first = new Set(stemList(((
|
|
4109
|
+
const first = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, AI_STOCK_ROLE_TAGS_READ).join(' ')));
|
|
2936
4110
|
if (((_judgement_roleScene1 = judgement.roleScene) == null ? void 0 : _judgement_roleScene1.length) && !judgement.roleScene.some((stem)=>first.has(stem))) return 0;
|
|
2937
|
-
const read = new Set(stemList(((
|
|
4111
|
+
const read = new Set(stemList(((_photo_tags2 = photo.tags) != null ? _photo_tags2 : []).slice(0, ALT_TAGS_READ).join(' ')));
|
|
2938
4112
|
if (((_judgement_roleRefuse1 = judgement.roleRefuse) != null ? _judgement_roleRefuse1 : []).some((stem)=>read.has(stem))) return 0;
|
|
2939
4113
|
}
|
|
2940
4114
|
// A Blue Ridge couple is never on a beach, however its later tags name a mountain (AGL-3942).
|
|
2941
4115
|
if ((_judgement_away = judgement.away) == null ? void 0 : _judgement_away.length) {
|
|
2942
|
-
var
|
|
2943
|
-
const read = new Set(stemList(((
|
|
4116
|
+
var _photo_tags3;
|
|
4117
|
+
const read = new Set(stemList(((_photo_tags3 = photo.tags) != null ? _photo_tags3 : []).slice(0, ALT_TAGS_READ).join(' ')));
|
|
2944
4118
|
if (judgement.away.some((stem)=>read.has(stem))) return 0;
|
|
2945
4119
|
}
|
|
2946
4120
|
// A wedding day shows its people: a close-up of roses tagged "wedding" never fills it.
|
|
@@ -2965,7 +4139,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2965
4139
|
const kinNamed = kin.some((phrase)=>among(hit.phrases, phrase));
|
|
2966
4140
|
const kinLead = kin.some((phrase)=>among(hit.leadPhrases, phrase));
|
|
2967
4141
|
const required = stemList((_judgement_requires = judgement.requires) != null ? _judgement_requires : '');
|
|
2968
|
-
|
|
4142
|
+
// A two-word name stays whole: "dirt bike", never "bike" (AGL-4065).
|
|
4143
|
+
const category = ((_judgement_category = judgement.category) != null ? _judgement_category : []).map(stemPhrase).filter(Boolean);
|
|
2969
4144
|
const anyOf = category.length ? [
|
|
2970
4145
|
...new Set([
|
|
2971
4146
|
...required,
|
|
@@ -2978,21 +4153,36 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
2978
4153
|
if (demanded && !kinNamed && !meets) return 0;
|
|
2979
4154
|
const object = (_judgement_object = judgement.object) != null ? _judgement_object : judgement.subject ? aiStockObjectWord(judgement.subject) : '';
|
|
2980
4155
|
const domain = ((_judgement_domain = judgement.domain) != null ? _judgement_domain : []).filter((stem)=>stem !== object);
|
|
4156
|
+
// The business's own world, which a picture that is not of a thing names (AGL-4065).
|
|
4157
|
+
const own = ((_judgement_world = judgement.world) == null ? void 0 : _judgement_world.length) ? judgement.world.filter((stem)=>stem !== object) : domain;
|
|
2981
4158
|
// A hit naming the word it must name is of the site's world: a product's
|
|
2982
4159
|
// category is that world even where it is the product's own object.
|
|
2983
4160
|
const world = demanded || kinNamed || domain.some((stem)=>hit.all.has(stem));
|
|
4161
|
+
const ownWorld = demanded || kinNamed || own.some((stem)=>hit.all.has(stem));
|
|
2984
4162
|
const worldLead = (anyOf.length ? anyOf.some((stem)=>hit.lead.has(stem)) : required.length > 0 && required.every((stem)=>hit.lead.has(stem))) || kinLead || domain.some((stem)=>hit.lead.has(stem));
|
|
2985
4163
|
if (judgement.leadWorld && !worldLead) return 0;
|
|
4164
|
+
// One of its first tags is OF the business's own world (AGL-4092): "tacos",
|
|
4165
|
+
// "street tacos", "dirt bike" lead with it; "taco bowl", "body part" and a
|
|
4166
|
+
// man posing with a "motorcycle" for a motocross team do not.
|
|
4167
|
+
if (judgement.leadOwn && own.length && !kinLead && !leadsWithWorld((_photo_tags = photo.tags) != null ? _photo_tags : [], new Set(own), new Set(domain))) return 0;
|
|
4168
|
+
// A cat, a toy or a bare foot leads it, and nothing of the business's own world does (AGL-4092).
|
|
4169
|
+
if (((_judgement_oddLeads = judgement.oddLeads) == null ? void 0 : _judgement_oddLeads.length) && own.length) {
|
|
4170
|
+
var _photo_tags4;
|
|
4171
|
+
const first = termsOf(((_photo_tags4 = photo.tags) != null ? _photo_tags4 : []).slice(0, 3));
|
|
4172
|
+
if (judgement.oddLeads.some((stem)=>first.has(stem)) && !own.some((stem)=>first.has(stem))) return 0;
|
|
4173
|
+
}
|
|
2986
4174
|
// The first tag alone, never the page's address: Pixabay's slug repeats a
|
|
2987
4175
|
// late tag ("coffee-latte-art-candle-…"). A site whose words name no world
|
|
2988
4176
|
// holds its hero to none.
|
|
4177
|
+
// The business's own world, never every word of its type (AGL-4065): "race
|
|
4178
|
+
// car" named "race" of "Amateur motocross race team: … race schedule".
|
|
2989
4179
|
const worldStems = new Set([
|
|
2990
4180
|
...anyOf,
|
|
2991
4181
|
...required,
|
|
2992
|
-
...
|
|
4182
|
+
...own
|
|
2993
4183
|
]);
|
|
2994
4184
|
if (judgement.first && (worldStems.size || kin.length)) {
|
|
2995
|
-
const firstWorld = hit.firstPhrase.split(' ').some((stem)=>worldStems.has(stem)) || kin.some((phrase)=>among([
|
|
4185
|
+
const firstWorld = withPairs(hit.firstPhrase.split(' ').filter(Boolean)).some((stem)=>worldStems.has(stem)) || kin.some((phrase)=>among([
|
|
2996
4186
|
hit.firstPhrase
|
|
2997
4187
|
], phrase));
|
|
2998
4188
|
if (!firstWorld) return 0;
|
|
@@ -3011,14 +4201,25 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
3011
4201
|
const leading = kin.length ? kinLead : hit.lead.has(object);
|
|
3012
4202
|
// A lifestyle scene whose object is a last tag is not a photo OF it.
|
|
3013
4203
|
if (judgement.lead && !leading) return 0;
|
|
3014
|
-
|
|
4204
|
+
// The business's own world, never any word of its type (AGL-4092): a cat
|
|
4205
|
+
// tagged "hungry" named "breakfast", and a European alley "street", of
|
|
4206
|
+
// "Taco food trailer: street and breakfast tacos".
|
|
4207
|
+
if (own.length && !ownWorld && !phrased) return 0;
|
|
3015
4208
|
score += leading ? 4 : 2;
|
|
3016
4209
|
if (kinNamed) score += 3;
|
|
3017
4210
|
if (firstNamed(kin.length ? kin : [
|
|
3018
4211
|
object
|
|
3019
4212
|
])) score += 1;
|
|
3020
|
-
|
|
3021
|
-
if (
|
|
4213
|
+
// A product's photo is OF it: named by its first or second tag, over one naming it fourth (AGL-4092).
|
|
4214
|
+
if (judgement.product) {
|
|
4215
|
+
var _photo_tags5;
|
|
4216
|
+
const firstTwo = ((_photo_tags5 = photo.tags) != null ? _photo_tags5 : []).slice(0, 2).map((tag)=>stemList(tag).join(' '));
|
|
4217
|
+
if ((kin.length ? kin : [
|
|
4218
|
+
object
|
|
4219
|
+
]).some((phrase)=>among(firstTwo, phrase))) score += 2;
|
|
4220
|
+
}
|
|
4221
|
+
} else if (own.length) {
|
|
4222
|
+
if (!ownWorld) return 0;
|
|
3022
4223
|
}
|
|
3023
4224
|
if (leadAny.length && firstNamed(leadAny)) score += 1;
|
|
3024
4225
|
if (phrased) score += 3;
|
|
@@ -3032,7 +4233,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
3032
4233
|
// attorney" over "lawyers, personal injury, accident" (AGL-3856): a second
|
|
3033
4234
|
// of them naming its world says it is of that world, not of something else.
|
|
3034
4235
|
if (judgement.first && worldStems.size) {
|
|
3035
|
-
const said = hit.leadPhrases.slice(0, -1).filter((phrase)=>phrase.split(' ').some((stem)=>worldStems.has(stem)));
|
|
4236
|
+
const said = hit.leadPhrases.slice(0, -1).filter((phrase)=>withPairs(phrase.split(' ')).some((stem)=>worldStems.has(stem)));
|
|
3036
4237
|
if (said.length >= 2) score += 0.25;
|
|
3037
4238
|
}
|
|
3038
4239
|
return Math.max(score, 0.5);
|
|
@@ -3076,13 +4277,15 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
|
|
|
3076
4277
|
* A search's ranked hits with those of a shoot no slot shows yet first among
|
|
3077
4278
|
* the best (AGL-3856): a team's three cards are three people where the
|
|
3078
4279
|
* library has them, never one model three times while another fits as well.
|
|
3079
|
-
* Every other order is kept.
|
|
3080
|
-
|
|
4280
|
+
* Every other order is kept. A product's are of a shoot no slot shows yet
|
|
4281
|
+
* first among ALL its accepted hits (`whole`, AGL-4092): Berm & Bolt's two
|
|
4282
|
+
* tires were one contributor's shoot, 32 uploads apart.
|
|
4283
|
+
*/ export function aiStockFreshShootsFirst(ranked, shown, judgement, options = {}) {
|
|
3081
4284
|
if (!shown.length || ranked.length < 2) return [
|
|
3082
4285
|
...ranked
|
|
3083
4286
|
];
|
|
3084
4287
|
const best = aiStockRelevance(ranked[0], judgement);
|
|
3085
|
-
const top = ranked.filter((photo)=>aiStockRelevance(photo, judgement) === best);
|
|
4288
|
+
const top = options.whole ? ranked : ranked.filter((photo)=>aiStockRelevance(photo, judgement) === best);
|
|
3086
4289
|
const fresh = top.filter((photo)=>!shown.some((other)=>aiStockSameShoot(photo, other)));
|
|
3087
4290
|
if (!fresh.length || fresh[0] === ranked[0]) return [
|
|
3088
4291
|
...ranked
|
|
@@ -3217,7 +4420,8 @@ function jobPhotos(key) {
|
|
|
3217
4420
|
/**
|
|
3218
4421
|
* Makers' names a tag gives, never what a photo shows to someone who cannot
|
|
3219
4422
|
* see it (AGL-3856): "A photo-camera and a sony".
|
|
3220
|
-
*/ 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 ' +
|
|
4423
|
+
*/ 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 ' + // The vehicle makers a moto site's photos are tagged with (AGL-4065), and its gear's and tires' (AGL-4092).
|
|
4424
|
+
'yamaha kawasaki suzuki ktm ducati harley harley-davidson husqvarna gasgas nissan subaru mazda chevy dodge reebok ' + `${GEAR_MAKER_WORDS.join(' ')} ` + 'petri zenit praktica yashica minolta mamiya rolleiflex voigtlander contax ricoh konica lomo lomography holga zeiss');
|
|
3221
4425
|
/** Stopwords a description still says, as the people a photo shows: "a family", "a person". */ const SAID_PEOPLE = words('people person family');
|
|
3222
4426
|
/** 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');
|
|
3223
4427
|
/** "-ing" words that are things, and take no article: "clothing". */ const UNCOUNTED_ING = words('clothing bedding frosting icing flooring stuffing seasoning filling');
|
|
@@ -3658,6 +4862,9 @@ const EXTENSIONS = {
|
|
|
3658
4862
|
'kin',
|
|
3659
4863
|
'lead',
|
|
3660
4864
|
'leadWorld',
|
|
4865
|
+
'leadOwn',
|
|
4866
|
+
'oddLeads',
|
|
4867
|
+
'served',
|
|
3661
4868
|
'first',
|
|
3662
4869
|
'category',
|
|
3663
4870
|
'strict',
|
|
@@ -4339,13 +5546,16 @@ const EXTENSIONS = {
|
|
|
4339
5546
|
const base = aiStockSiteTerms(entry.subject);
|
|
4340
5547
|
if (!((_entry_kin = entry.kin) == null ? void 0 : _entry_kin.length)) return base;
|
|
4341
5548
|
const kin = entry.kin.map(aiStockStem);
|
|
5549
|
+
const domain = [
|
|
5550
|
+
...new Set([
|
|
5551
|
+
...base.domain,
|
|
5552
|
+
...kin
|
|
5553
|
+
])
|
|
5554
|
+
];
|
|
4342
5555
|
return _extends({}, base, {
|
|
4343
|
-
domain
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
...kin
|
|
4347
|
-
])
|
|
4348
|
-
],
|
|
5556
|
+
domain,
|
|
5557
|
+
// The kind's own world is all of it: its subject and its kin (AGL-4065).
|
|
5558
|
+
lead: domain,
|
|
4349
5559
|
broad: [
|
|
4350
5560
|
...(_entry_broad = entry.broad) != null ? _entry_broad : base.broad
|
|
4351
5561
|
],
|
|
@@ -4880,7 +6090,7 @@ const WORKSPACE = {
|
|
|
4880
6090
|
* "freelencing", which no library tags, so its cover hero searched it, found
|
|
4881
6091
|
* nothing, and shipped a flat dark band with no photo.
|
|
4882
6092
|
*/ export function aiStockWiderTerms(terms, kind) {
|
|
4883
|
-
var _STARTER_KINDS_starter,
|
|
6093
|
+
var _STARTER_KINDS_starter, _STARTER_GENERIC_KIND_starter;
|
|
4884
6094
|
const rungs = [];
|
|
4885
6095
|
const seen = new Set([
|
|
4886
6096
|
JSON.stringify([
|
|
@@ -4890,8 +6100,11 @@ const WORKSPACE = {
|
|
|
4890
6100
|
]
|
|
4891
6101
|
])
|
|
4892
6102
|
]);
|
|
4893
|
-
|
|
6103
|
+
// An outdoor or a trade world never widens to an office's photos (AGL-4065).
|
|
6104
|
+
const outdoor = aiStockWorldIsOutdoorOrTrade(terms);
|
|
6105
|
+
const add = (rung, of, found)=>{
|
|
4894
6106
|
if (!found || !found.broad.length) return;
|
|
6107
|
+
if (outdoor && OFFICE_KINDS.has(of)) return;
|
|
4895
6108
|
const key = JSON.stringify([
|
|
4896
6109
|
found.craft,
|
|
4897
6110
|
found.broad
|
|
@@ -4904,15 +6117,42 @@ const WORKSPACE = {
|
|
|
4904
6117
|
});
|
|
4905
6118
|
};
|
|
4906
6119
|
if (!aiStockWorldIsUnlisted(terms)) {
|
|
4907
|
-
if (kind && worldOfKind(terms, kind)) add('generic', aiStockKindGenericTerms(kind));
|
|
6120
|
+
if (kind && worldOfKind(terms, kind)) add('generic', kind, aiStockKindGenericTerms(kind));
|
|
4908
6121
|
return rungs;
|
|
4909
6122
|
}
|
|
4910
6123
|
const starter = starterOfKind(kind);
|
|
4911
|
-
|
|
4912
|
-
add('
|
|
4913
|
-
add('
|
|
6124
|
+
const starterKind = (_STARTER_KINDS_starter = STARTER_KINDS[starter]) != null ? _STARTER_KINDS_starter : 'business';
|
|
6125
|
+
if (kind) add('kind', kind, aiStockKindTerms(kind));
|
|
6126
|
+
add('starter', starterKind, aiStockKindTerms(starterKind));
|
|
6127
|
+
const generic = KIND_GENERIC[kind] ? kind : (_STARTER_GENERIC_KIND_starter = STARTER_GENERIC_KIND[starter]) != null ? _STARTER_GENERIC_KIND_starter : 'business';
|
|
6128
|
+
add('generic', generic, aiStockKindGenericTerms(generic));
|
|
4914
6129
|
return rungs;
|
|
4915
6130
|
}
|
|
6131
|
+
/**
|
|
6132
|
+
* The kinds of site whose photos are an office, a desk or a workspace
|
|
6133
|
+
* (AGL-4065): what a landscaper, a roofer or a taco trailer never widens to.
|
|
6134
|
+
*/ const OFFICE_KINDS = new Set([
|
|
6135
|
+
'business',
|
|
6136
|
+
'landing',
|
|
6137
|
+
'professional',
|
|
6138
|
+
'personal',
|
|
6139
|
+
'coming-soon',
|
|
6140
|
+
'studio'
|
|
6141
|
+
]);
|
|
6142
|
+
/**
|
|
6143
|
+
* Words of a world whose work is done outdoors, on a vehicle, with tools or
|
|
6144
|
+
* over food (AGL-4065), as stems: such a world's pictures are never an
|
|
6145
|
+
* office's. The Bluebonnet Yard & Landscape start, a world the module did not
|
|
6146
|
+
* list, picked as Business & services, opened every page on an office.
|
|
6147
|
+
*/ const OUTDOOR_AND_TRADE_STEMS = new Set(('landscaping landscaper landscape lawn garden gardening yard roof roofing roofer fence fencing deck patio construction ' + 'contractor farm ranch moto motorcycle motocross bike dirt atv truck trailer tow towing auto car mechanic food ' + 'restaurant kitchen taco plumbing plumber electrician electrical hvac painting painter handyman carpentry carpenter ' + 'tree pool pest cleaning junk excavation concrete masonry welding').split(' ').map(aiStockStem));
|
|
6148
|
+
/** Whether a site's own world is outdoors, on a vehicle, with tools or over food ({@link OUTDOOR_AND_TRADE_STEMS}). */ export function aiStockWorldIsOutdoorOrTrade(terms) {
|
|
6149
|
+
var _terms_lead;
|
|
6150
|
+
const own = [
|
|
6151
|
+
...(_terms_lead = terms.lead) != null ? _terms_lead : terms.domain,
|
|
6152
|
+
...stemList(`${terms.business} ${terms.craft}`)
|
|
6153
|
+
];
|
|
6154
|
+
return own.some((term)=>term.split(' ').some((stem)=>OUTDOOR_AND_TRADE_STEMS.has(stem)));
|
|
6155
|
+
}
|
|
4916
6156
|
/**
|
|
4917
6157
|
* The first word of a text that names a craft {@link CRAFT_KIN} lists, as
|
|
4918
6158
|
* its table writes it: "gym" of "Synchronicity Gym", "law" of "Hartwell
|
|
@@ -4949,6 +6189,147 @@ const WORKSPACE = {
|
|
|
4949
6189
|
if (!aiLayoutSpeaksOtherThanEnglish(input.language)) return '';
|
|
4950
6190
|
return aiStockBusinessNoun(input.business) || aiStockPhotoWordsSubject((_input_photoWords = input.photoWords) != null ? _input_photoWords : '') || ((_input_kindWords = input.kindWords) != null ? _input_kindWords : '').trim();
|
|
4951
6191
|
}
|
|
6192
|
+
/**
|
|
6193
|
+
* How a search's hits are judged for a slot (AGL-3660), as the source judges
|
|
6194
|
+
* them: by the search's own words, held to the world it was asked for — the
|
|
6195
|
+
* site's own (`site`), or a wider one where the site's found nothing
|
|
6196
|
+
* (`world`, AGL-3923). What lets a sensitive topic, an animal or a body part
|
|
6197
|
+
* in is always the site's own words. On a site for adults alone no hit names
|
|
6198
|
+
* a child; a portrait of one of the business's people names an adult, never
|
|
6199
|
+
* leads with a child, and is preferred in its role (AGL-3856). Any other
|
|
6200
|
+
* picture never leads with an animal, a toy or a bare body part its words
|
|
6201
|
+
* and the brief do not name, unless its first tags name the business's own
|
|
6202
|
+
* world too (AGL-4092): Pit Row Tacos' "Come hungry" took a cat. Pure.
|
|
6203
|
+
*/ export function aiStockJudgementFor(slot, request, subject, site, sectionName = '', world = site) {
|
|
6204
|
+
var _request_refuse, _ref, _request_subject, _request_shun, _request_refuseLead;
|
|
6205
|
+
var _slot_product, _world_lead, _request_allow, _request_shun1, _request_leadAny, _request_served, _request_needs, _request_refuseLead1, _request_away, _request_needs1, _role_scene, _role_refuse;
|
|
6206
|
+
const refuse = [
|
|
6207
|
+
...(_request_refuse = request.refuse) != null ? _request_refuse : [],
|
|
6208
|
+
...site.adults ? CHILD_STEMS : []
|
|
6209
|
+
];
|
|
6210
|
+
const role = slot.people ? aiStockPeopleRole(slot.alt, sectionName) : null;
|
|
6211
|
+
// A portrait's face is its own, and a craft whose pictures are people at a moment shows their hands and rings (AGL-4092).
|
|
6212
|
+
const oddLeads = slot.people ? [] : aiStockOddLeads([
|
|
6213
|
+
slot.alt,
|
|
6214
|
+
...(_ref = (_slot_product = slot.product) == null ? void 0 : _slot_product.subjects) != null ? _ref : [],
|
|
6215
|
+
sectionName,
|
|
6216
|
+
...site.named
|
|
6217
|
+
], {
|
|
6218
|
+
body: slot.role !== 'about' && !site.scenes
|
|
6219
|
+
});
|
|
6220
|
+
return _extends({
|
|
6221
|
+
subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
|
|
6222
|
+
domain: world.domain
|
|
6223
|
+
}, ((_world_lead = world.lead) == null ? void 0 : _world_lead.length) ? {
|
|
6224
|
+
world: world.lead
|
|
6225
|
+
} : {}, {
|
|
6226
|
+
strict: slot.role === 'gallery' && !request.broad || !!request.requires || !!request.strict,
|
|
6227
|
+
named: ((_request_allow = request.allow) == null ? void 0 : _request_allow.length) ? [
|
|
6228
|
+
...new Set([
|
|
6229
|
+
...site.named,
|
|
6230
|
+
...request.allow
|
|
6231
|
+
])
|
|
6232
|
+
] : site.named,
|
|
6233
|
+
rivals: world.rivals
|
|
6234
|
+
}, world.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
|
|
6235
|
+
shun: [
|
|
6236
|
+
...new Set([
|
|
6237
|
+
...world.shun,
|
|
6238
|
+
...(_request_shun = request.shun) != null ? _request_shun : []
|
|
6239
|
+
])
|
|
6240
|
+
]
|
|
6241
|
+
} : {}, world.shunLead.length ? {
|
|
6242
|
+
shunLead: world.shunLead
|
|
6243
|
+
} : {}, request.product ? {
|
|
6244
|
+
product: true
|
|
6245
|
+
} : {}, ((_request_leadAny = request.leadAny) == null ? void 0 : _request_leadAny.length) ? {
|
|
6246
|
+
leadAny: request.leadAny
|
|
6247
|
+
} : {}, request.requires ? {
|
|
6248
|
+
requires: request.requires
|
|
6249
|
+
} : {}, request.category ? {
|
|
6250
|
+
category: request.category
|
|
6251
|
+
} : {}, request.object ? {
|
|
6252
|
+
object: request.object
|
|
6253
|
+
} : {}, request.kin ? {
|
|
6254
|
+
kin: request.kin
|
|
6255
|
+
} : {}, request.lead ? {
|
|
6256
|
+
lead: true
|
|
6257
|
+
} : {}, request.leadWorld ? {
|
|
6258
|
+
leadWorld: true
|
|
6259
|
+
} : {}, request.leadOwn ? {
|
|
6260
|
+
leadOwn: true
|
|
6261
|
+
} : {}, ((_request_served = request.served) == null ? void 0 : _request_served.length) ? {
|
|
6262
|
+
served: request.served
|
|
6263
|
+
} : {}, request.first ? {
|
|
6264
|
+
first: true
|
|
6265
|
+
} : {}, refuse.length ? {
|
|
6266
|
+
refuse: [
|
|
6267
|
+
...new Set(refuse)
|
|
6268
|
+
]
|
|
6269
|
+
} : {}, ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? {
|
|
6270
|
+
needs: request.needs
|
|
6271
|
+
} : {}, ((_request_refuseLead1 = request.refuseLead) == null ? void 0 : _request_refuseLead1.length) ? {
|
|
6272
|
+
refuseLead: request.refuseLead
|
|
6273
|
+
} : {}, ((_request_away = request.away) == null ? void 0 : _request_away.length) ? {
|
|
6274
|
+
away: request.away
|
|
6275
|
+
} : {}, oddLeads.length ? {
|
|
6276
|
+
oddLeads
|
|
6277
|
+
} : {}, request.worldless ? {
|
|
6278
|
+
domain: [],
|
|
6279
|
+
rivals: []
|
|
6280
|
+
} : {}, slot.people ? _extends({
|
|
6281
|
+
needs: ((_request_needs1 = request.needs) == null ? void 0 : _request_needs1.length) ? request.needs : (role == null ? void 0 : role.required) ? role.kin : ADULT_PEOPLE_STEMS,
|
|
6282
|
+
refuseLead: [
|
|
6283
|
+
...new Set([
|
|
6284
|
+
...CHILD_STEMS,
|
|
6285
|
+
...(_request_refuseLead = request.refuseLead) != null ? _request_refuseLead : []
|
|
6286
|
+
])
|
|
6287
|
+
]
|
|
6288
|
+
}, role ? {
|
|
6289
|
+
roles: role.kin
|
|
6290
|
+
} : {}, (role == null ? void 0 : (_role_scene = role.scene) == null ? void 0 : _role_scene.length) ? {
|
|
6291
|
+
roleScene: role.scene
|
|
6292
|
+
} : {}, (role == null ? void 0 : (_role_refuse = role.refuse) == null ? void 0 : _role_refuse.length) ? {
|
|
6293
|
+
roleRefuse: aiStockRoleRefusals(role, slot.alt)
|
|
6294
|
+
} : {}) : {});
|
|
6295
|
+
}
|
|
6296
|
+
/**
|
|
6297
|
+
* The craft's own photos, the last thing a slot of a craft's site is filled
|
|
6298
|
+
* from (AGL-3660): its broad searches, any hit naming the craft itself. Never
|
|
6299
|
+
* a desk, a laptop or a living room for a soap shop. A picture that is not a
|
|
6300
|
+
* hero nor a product takes only a photo OF the business's own world among its
|
|
6301
|
+
* first tags (AGL-4092): a taco trailer's "Race day" took a chicken salad
|
|
6302
|
+
* tagged "taco bowl". Pure.
|
|
6303
|
+
*/ export function aiStockCraftSearches(slot, world) {
|
|
6304
|
+
// A portfolio's record of the work is never filled by the craft's own
|
|
6305
|
+
// photos (AGL-3856), nor is any picture of a site that shows its work (AGL-3942).
|
|
6306
|
+
if (!world.core.length || aiStockShowsTheWork(slot, world) || aiStockShowsItsWork(slot, world)) return [];
|
|
6307
|
+
const orientation = aiStockOrientation(slot.aspect);
|
|
6308
|
+
const size = slot.role === 'hero' ? {
|
|
6309
|
+
minWidth: 1600
|
|
6310
|
+
} : orientation === 'vertical' ? {
|
|
6311
|
+
minHeight: 900
|
|
6312
|
+
} : {
|
|
6313
|
+
minWidth: 900
|
|
6314
|
+
};
|
|
6315
|
+
return world.broad.map((query)=>{
|
|
6316
|
+
var _world_held;
|
|
6317
|
+
return _extends({
|
|
6318
|
+
query: clip(query),
|
|
6319
|
+
orientation
|
|
6320
|
+
}, size, {
|
|
6321
|
+
broad: true,
|
|
6322
|
+
category: world.core
|
|
6323
|
+
}, slot.role === 'hero' ? {
|
|
6324
|
+
first: true
|
|
6325
|
+
} : slot.product && !((_world_held = world.held) == null ? void 0 : _world_held.length) ? {} : {
|
|
6326
|
+
leadOwn: true
|
|
6327
|
+
}, slot.product ? {
|
|
6328
|
+
product: true,
|
|
6329
|
+
tier: 'weak'
|
|
6330
|
+
} : {});
|
|
6331
|
+
});
|
|
6332
|
+
}
|
|
4952
6333
|
/**
|
|
4953
6334
|
* The source for one page, or `null` when this deployment has no stock
|
|
4954
6335
|
* photo library or no media door, which leaves every slot to the starters.
|
|
@@ -5126,120 +6507,10 @@ const WORKSPACE = {
|
|
|
5126
6507
|
* hit names a child; a portrait of one of the business's people names an
|
|
5127
6508
|
* adult, never leads with a child, and is preferred in its role (AGL-3856).
|
|
5128
6509
|
*/ const judgementFor = (slot, request, subject)=>{
|
|
5129
|
-
var
|
|
5130
|
-
|
|
5131
|
-
const refuse = [
|
|
5132
|
-
...(_request_refuse = request.refuse) != null ? _request_refuse : [],
|
|
5133
|
-
...terms.adults ? CHILD_STEMS : []
|
|
5134
|
-
];
|
|
5135
|
-
const role = slot.people ? aiStockPeopleRole(slot.alt, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '') : null;
|
|
5136
|
-
return _extends({}, judgementOf(slot, request, subject, (_worldOf_get = worldOf.get(request)) != null ? _worldOf_get : terms), refuse.length ? {
|
|
5137
|
-
refuse: [
|
|
5138
|
-
...new Set(refuse)
|
|
5139
|
-
]
|
|
5140
|
-
} : {}, slot.people ? _extends({
|
|
5141
|
-
needs: ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? request.needs : (role == null ? void 0 : role.required) ? role.kin : ADULT_PEOPLE_STEMS,
|
|
5142
|
-
refuseLead: [
|
|
5143
|
-
...new Set([
|
|
5144
|
-
...CHILD_STEMS,
|
|
5145
|
-
...(_request_refuseLead = request.refuseLead) != null ? _request_refuseLead : []
|
|
5146
|
-
])
|
|
5147
|
-
]
|
|
5148
|
-
}, role ? {
|
|
5149
|
-
roles: role.kin
|
|
5150
|
-
} : {}, (role == null ? void 0 : (_role_scene = role.scene) == null ? void 0 : _role_scene.length) ? {
|
|
5151
|
-
roleScene: role.scene
|
|
5152
|
-
} : {}, (role == null ? void 0 : (_role_refuse = role.refuse) == null ? void 0 : _role_refuse.length) ? {
|
|
5153
|
-
roleRefuse: aiStockRoleRefusals(role, slot.alt)
|
|
5154
|
-
} : {}) : {});
|
|
5155
|
-
};
|
|
5156
|
-
/**
|
|
5157
|
-
* A search's own judgement, as its words set it, held to the world it was
|
|
5158
|
-
* asked for: the site's own, or a wider one where the site's found nothing
|
|
5159
|
-
* (AGL-3923). What lets a sensitive topic in is always the site's own words.
|
|
5160
|
-
*/ const judgementOf = (slot, request, subject, world)=>{
|
|
5161
|
-
var _request_subject, _request_shun;
|
|
5162
|
-
var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead, _request_away;
|
|
5163
|
-
return _extends({
|
|
5164
|
-
subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
|
|
5165
|
-
domain: world.domain,
|
|
5166
|
-
strict: slot.role === 'gallery' && !request.broad || !!request.requires || !!request.strict,
|
|
5167
|
-
named: ((_request_allow = request.allow) == null ? void 0 : _request_allow.length) ? [
|
|
5168
|
-
...new Set([
|
|
5169
|
-
...terms.named,
|
|
5170
|
-
...request.allow
|
|
5171
|
-
])
|
|
5172
|
-
] : terms.named,
|
|
5173
|
-
rivals: world.rivals
|
|
5174
|
-
}, world.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
|
|
5175
|
-
shun: [
|
|
5176
|
-
...new Set([
|
|
5177
|
-
...world.shun,
|
|
5178
|
-
...(_request_shun = request.shun) != null ? _request_shun : []
|
|
5179
|
-
])
|
|
5180
|
-
]
|
|
5181
|
-
} : {}, world.shunLead.length ? {
|
|
5182
|
-
shunLead: world.shunLead
|
|
5183
|
-
} : {}, request.product ? {
|
|
5184
|
-
product: true
|
|
5185
|
-
} : {}, ((_request_leadAny = request.leadAny) == null ? void 0 : _request_leadAny.length) ? {
|
|
5186
|
-
leadAny: request.leadAny
|
|
5187
|
-
} : {}, request.requires ? {
|
|
5188
|
-
requires: request.requires
|
|
5189
|
-
} : {}, request.category ? {
|
|
5190
|
-
category: request.category
|
|
5191
|
-
} : {}, request.object ? {
|
|
5192
|
-
object: request.object
|
|
5193
|
-
} : {}, request.kin ? {
|
|
5194
|
-
kin: request.kin
|
|
5195
|
-
} : {}, request.lead ? {
|
|
5196
|
-
lead: true
|
|
5197
|
-
} : {}, request.leadWorld ? {
|
|
5198
|
-
leadWorld: true
|
|
5199
|
-
} : {}, request.first ? {
|
|
5200
|
-
first: true
|
|
5201
|
-
} : {}, ((_request_refuse = request.refuse) == null ? void 0 : _request_refuse.length) ? {
|
|
5202
|
-
refuse: request.refuse
|
|
5203
|
-
} : {}, ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? {
|
|
5204
|
-
needs: request.needs
|
|
5205
|
-
} : {}, ((_request_refuseLead = request.refuseLead) == null ? void 0 : _request_refuseLead.length) ? {
|
|
5206
|
-
refuseLead: request.refuseLead
|
|
5207
|
-
} : {}, ((_request_away = request.away) == null ? void 0 : _request_away.length) ? {
|
|
5208
|
-
away: request.away
|
|
5209
|
-
} : {}, request.worldless ? {
|
|
5210
|
-
domain: [],
|
|
5211
|
-
rivals: []
|
|
5212
|
-
} : {});
|
|
5213
|
-
};
|
|
5214
|
-
/**
|
|
5215
|
-
* The craft's own photos, the last thing a slot of a craft's site is
|
|
5216
|
-
* filled from (AGL-3660): its broad searches, any hit naming the craft
|
|
5217
|
-
* itself. Never a desk, a laptop or a living room for a soap shop.
|
|
5218
|
-
*/ const craftSearches = (slot, world = terms)=>{
|
|
5219
|
-
// A portfolio's record of the work is never filled by the craft's own
|
|
5220
|
-
// photos (AGL-3856), nor is any picture of a site that shows its work (AGL-3942).
|
|
5221
|
-
if (!world.core.length || aiStockShowsTheWork(slot, world) || aiStockShowsItsWork(slot, world)) return [];
|
|
5222
|
-
const orientation = aiStockOrientation(slot.aspect);
|
|
5223
|
-
const size = slot.role === 'hero' ? {
|
|
5224
|
-
minWidth: 1600
|
|
5225
|
-
} : orientation === 'vertical' ? {
|
|
5226
|
-
minHeight: 900
|
|
5227
|
-
} : {
|
|
5228
|
-
minWidth: 900
|
|
5229
|
-
};
|
|
5230
|
-
return world.broad.map((query)=>_extends({
|
|
5231
|
-
query: clip(query),
|
|
5232
|
-
orientation
|
|
5233
|
-
}, size, {
|
|
5234
|
-
broad: true,
|
|
5235
|
-
category: world.core
|
|
5236
|
-
}, slot.role === 'hero' ? {
|
|
5237
|
-
first: true
|
|
5238
|
-
} : {}, slot.product ? {
|
|
5239
|
-
product: true,
|
|
5240
|
-
tier: 'weak'
|
|
5241
|
-
} : {}));
|
|
6510
|
+
var _input_sectionNames_slot_sectionIndex, _worldOf_get;
|
|
6511
|
+
return aiStockJudgementFor(slot, request, subject, terms, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '', (_worldOf_get = worldOf.get(request)) != null ? _worldOf_get : terms);
|
|
5242
6512
|
};
|
|
6513
|
+
const craftSearches = (slot, world = terms)=>aiStockCraftSearches(slot, world);
|
|
5243
6514
|
/**
|
|
5244
6515
|
* A slot's searches once its own world found it nothing (AGL-3923), in
|
|
5245
6516
|
* order: the kind of site's world, the starter's, the slot's own words
|
|
@@ -5259,10 +6530,16 @@ const WORKSPACE = {
|
|
|
5259
6530
|
};
|
|
5260
6531
|
for (const { rung, terms: world } of wider){
|
|
5261
6532
|
if (rung === 'generic') continue;
|
|
6533
|
+
// A wider world is the site's only as a kind of site: its photos are held to it as before (AGL-4092).
|
|
5262
6534
|
ask(world, [
|
|
5263
6535
|
...aiStockSearchesFor(slot, world, subject, part, sectionName),
|
|
5264
6536
|
...craftSearches(slot, world)
|
|
5265
|
-
])
|
|
6537
|
+
].map((_0)=>{
|
|
6538
|
+
let { leadOwn: _own } = _0, request = _object_without_properties_loose(_0, [
|
|
6539
|
+
"leadOwn"
|
|
6540
|
+
]);
|
|
6541
|
+
return request;
|
|
6542
|
+
}));
|
|
5266
6543
|
}
|
|
5267
6544
|
const orientation = aiStockOrientation(slot.aspect);
|
|
5268
6545
|
const size = slot.role === 'hero' ? {
|
|
@@ -5325,15 +6602,21 @@ const WORKSPACE = {
|
|
|
5325
6602
|
const subject = english ? '' : aiStockSubjectWords(slot.alt, sectionName);
|
|
5326
6603
|
let choice = null;
|
|
5327
6604
|
const requests = [
|
|
5328
|
-
...slot.product ? aiStockProductSearches(slot, terms.craft, terms.domain, {
|
|
6605
|
+
...slot.product ? aiStockProductSearches(slot, terms.craft, terms.domain, _extends({
|
|
5329
6606
|
broad: terms.broad,
|
|
5330
6607
|
core: terms.core
|
|
5331
|
-
}
|
|
6608
|
+
}, terms.held ? {
|
|
6609
|
+
held: terms.held
|
|
6610
|
+
} : {}, terms.leadWord ? {
|
|
6611
|
+
leadWord: terms.leadWord
|
|
6612
|
+
} : {})) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName), sectionName),
|
|
5332
6613
|
...craftSearches(slot)
|
|
5333
6614
|
];
|
|
6615
|
+
/** Whether the library answered any of the slot's own searches with a photo at all (AGL-4065). */ let knew = false;
|
|
5334
6616
|
/** The slot's accepted hits for a search; `null` once the page's searches are spent. */ const accepted = async (request, repeat)=>{
|
|
5335
6617
|
const found = await search(request);
|
|
5336
6618
|
if (found === null) return null;
|
|
6619
|
+
if (found.length && !worldOf.has(request)) knew = true;
|
|
5337
6620
|
const blocked = new Set();
|
|
5338
6621
|
if (!repeat) {
|
|
5339
6622
|
for (const key of claimed)blocked.add(key);
|
|
@@ -5349,8 +6632,12 @@ const WORKSPACE = {
|
|
|
5349
6632
|
}
|
|
5350
6633
|
const judgement = judgementFor(slot, request, subject);
|
|
5351
6634
|
const ranked = aiStockRank(found, blocked, `${input.seed}:${index}:${request.query}`, judgement);
|
|
5352
|
-
// A team's portraits are of different people where the hits allow (AGL-3856)
|
|
5353
|
-
|
|
6635
|
+
// A team's portraits are of different people where the hits allow (AGL-3856),
|
|
6636
|
+
// and a shop's products of different shoots (AGL-4092).
|
|
6637
|
+
if (repeat || !(slot.people || slot.product)) return ranked;
|
|
6638
|
+
return aiStockFreshShootsFirst(ranked, shown, judgement, slot.product ? {
|
|
6639
|
+
whole: true
|
|
6640
|
+
} : {});
|
|
5354
6641
|
};
|
|
5355
6642
|
/** The first of these searches with a hit to take: a new one, or one the job already shows. */ const choose = async (tried, repeat)=>{
|
|
5356
6643
|
for (const request of tried){
|
|
@@ -5383,44 +6670,54 @@ const WORKSPACE = {
|
|
|
5383
6670
|
const sure = slot.product ? requests.filter((request)=>request.tier !== 'weak') : requests;
|
|
5384
6671
|
const unsure = slot.product ? requests.filter((request)=>request.tier === 'weak') : [];
|
|
5385
6672
|
// 3. Nothing of its own world at all: the wider worlds (AGL-3923), new
|
|
5386
|
-
// and then, for a hero, one the job already shows.
|
|
6673
|
+
// and then, for a hero, one the job already shows. A listed world
|
|
6674
|
+
// widens only where the library knew nothing of it (AGL-4065): one
|
|
6675
|
+
// it answered with photos it turned down keeps its slot without a
|
|
6676
|
+
// photo, never an office. A world of its own words alone still widens.
|
|
5387
6677
|
const widened = widerSearches(slot, subject, sectionName);
|
|
5388
6678
|
const phases = [
|
|
5389
6679
|
[
|
|
5390
6680
|
sure,
|
|
6681
|
+
false,
|
|
5391
6682
|
false
|
|
5392
6683
|
],
|
|
5393
6684
|
...repeats ? [
|
|
5394
6685
|
[
|
|
5395
6686
|
sure,
|
|
5396
|
-
true
|
|
6687
|
+
true,
|
|
6688
|
+
false
|
|
5397
6689
|
]
|
|
5398
6690
|
] : [],
|
|
5399
6691
|
...unsure.length ? [
|
|
5400
6692
|
[
|
|
5401
6693
|
unsure,
|
|
6694
|
+
false,
|
|
5402
6695
|
false
|
|
5403
6696
|
],
|
|
5404
6697
|
[
|
|
5405
6698
|
unsure,
|
|
5406
|
-
true
|
|
6699
|
+
true,
|
|
6700
|
+
false
|
|
5407
6701
|
]
|
|
5408
6702
|
] : [],
|
|
5409
6703
|
...widened.length ? [
|
|
5410
6704
|
[
|
|
5411
6705
|
widened,
|
|
5412
|
-
false
|
|
6706
|
+
false,
|
|
6707
|
+
true
|
|
5413
6708
|
]
|
|
5414
6709
|
] : [],
|
|
5415
6710
|
...widened.length && repeats ? [
|
|
5416
6711
|
[
|
|
5417
6712
|
widened,
|
|
6713
|
+
true,
|
|
5418
6714
|
true
|
|
5419
6715
|
]
|
|
5420
6716
|
] : []
|
|
5421
6717
|
];
|
|
5422
|
-
for (const [tried, repeat] of phases){
|
|
6718
|
+
for (const [tried, repeat, wide] of phases){
|
|
5423
6719
|
if (choice || signal.aborted) break;
|
|
6720
|
+
if (wide && knew && !unlisted) break;
|
|
5424
6721
|
choice = await choose(tried, repeat);
|
|
5425
6722
|
}
|
|
5426
6723
|
choices.push(choice);
|