@vectoriox/iox-ui 5.66.1 → 5.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +333 -17
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/generation-core/index.d.ts +173 -16
- package/package.json +1 -1
- package/src/generation-core/public-api.ts +1 -0
- package/src/generation-core/site-build-run.ts +3 -0
- package/src/generation-core/stock-match.ts +238 -0
- package/src/generation-core/stock-photos.ts +153 -22
|
@@ -5617,7 +5617,10 @@ declare enum BuildNoticeCode {
|
|
|
5617
5617
|
/** A content type the build reuses is not public in this studio: the site will not show its items. */
|
|
5618
5618
|
ContentTypeNotPublic = "content-type-not-public",
|
|
5619
5619
|
/** The stock library could not be read: the picture slots keep the empty box, and the run says so. */
|
|
5620
|
-
StockUnavailable = "stock-unavailable"
|
|
5620
|
+
StockUnavailable = "stock-unavailable",
|
|
5621
|
+
/** The stock library was read but holds no photo for this kind of business (never built, or reviewed down to
|
|
5622
|
+
* none): the picture slots keep the empty box, and the run says so (I2.1 — never silently). */
|
|
5623
|
+
StockMissing = "stock-missing"
|
|
5621
5624
|
}
|
|
5622
5625
|
interface BuildNotice {
|
|
5623
5626
|
code: string;
|
|
@@ -6482,10 +6485,114 @@ declare function publishVerdict(audit: SiteAudit, acknowledged?: ReadonlyArray<s
|
|
|
6482
6485
|
};
|
|
6483
6486
|
|
|
6484
6487
|
/**
|
|
6485
|
-
*
|
|
6488
|
+
* How a stock photo is judged against a picture slot (AI plan §3g, I2.1) — pure and deterministic, so the same plan and
|
|
6489
|
+
* the same library always place the same photos. A photo is described by what the library's manifest carries (the
|
|
6490
|
+
* provider's tags, its orientation, the reviewer's subject and notes — each optional, derived when absent); a slot by
|
|
6491
|
+
* its INTENT (what kind of picture its section wants), its aspect ratio and the plan's words around it.
|
|
6492
|
+
*/
|
|
6493
|
+
declare enum StockOrientation {
|
|
6494
|
+
Horizontal = "horizontal",
|
|
6495
|
+
Vertical = "vertical",
|
|
6496
|
+
Square = "square"
|
|
6497
|
+
}
|
|
6498
|
+
/** What a photo mainly shows — the reviewer may set it; otherwise it is read from the provider's tags. */
|
|
6499
|
+
declare enum StockSubject {
|
|
6500
|
+
/** A room, a shop front, a garden — somewhere. */
|
|
6501
|
+
Place = "place",
|
|
6502
|
+
/** A close-up: a texture, a tool, an ingredient. */
|
|
6503
|
+
Detail = "detail",
|
|
6504
|
+
/** A thing a business sells or serves: a cake, a bottle, a bouquet. */
|
|
6505
|
+
Product = "product"
|
|
6506
|
+
}
|
|
6507
|
+
/** What a slot wants to show. */
|
|
6508
|
+
declare enum StockIntent {
|
|
6509
|
+
/** The hero / a page header / a call to action: wide and atmospheric. */
|
|
6510
|
+
Atmosphere = "atmosphere",
|
|
6511
|
+
/** A story or a visit section: the place. */
|
|
6512
|
+
Place = "place",
|
|
6513
|
+
/** A feature row: a detail of the work or the service. */
|
|
6514
|
+
Detail = "detail",
|
|
6515
|
+
/** A list item's picture (a product, a service, a dish). */
|
|
6516
|
+
Product = "product"
|
|
6517
|
+
}
|
|
6518
|
+
declare enum StockReviewVerdict {
|
|
6519
|
+
Approved = "approved",
|
|
6520
|
+
Rejected = "rejected"
|
|
6521
|
+
}
|
|
6522
|
+
/** What the review found in a photo that keeps it out of every site (the licence: no person, no logo or brand). */
|
|
6523
|
+
declare enum StockReviewFlag {
|
|
6524
|
+
Person = "person",
|
|
6525
|
+
Logo = "logo"
|
|
6526
|
+
}
|
|
6527
|
+
/** The operator's one review of a photo, as the manifest records it. */
|
|
6528
|
+
interface StockReview {
|
|
6529
|
+
verdict: `${StockReviewVerdict}`;
|
|
6530
|
+
flags?: Array<`${StockReviewFlag}`>;
|
|
6531
|
+
/** The reviewer's own words for it — they count as tags when placing it. */
|
|
6532
|
+
notes?: string;
|
|
6533
|
+
}
|
|
6534
|
+
/** The section families a stock photo may stand in for a picture in, and what each wants. */
|
|
6535
|
+
declare const STOCK_FAMILY_INTENTS: Readonly<Record<string, StockIntent>>;
|
|
6536
|
+
/** Per intent, the subjects it takes, best first. */
|
|
6537
|
+
declare const STOCK_INTENT_SUBJECTS: Readonly<Record<StockIntent, ReadonlyArray<StockSubject>>>;
|
|
6538
|
+
/** The order slots choose in: the hero picks first, a list item last. */
|
|
6539
|
+
declare const STOCK_INTENT_ORDER: ReadonlyArray<StockIntent>;
|
|
6540
|
+
/** The words that tell a photo's subject when the reviewer did not name it (singular, lower case). */
|
|
6541
|
+
declare const STOCK_SUBJECT_WORDS: Readonly<Record<StockSubject, ReadonlyArray<string>>>;
|
|
6542
|
+
/** The share of a photo a slot may crop away and still be a fit: a portrait never fills a 21 / 9 band. */
|
|
6543
|
+
declare const STOCK_MIN_KEPT = 0.5;
|
|
6544
|
+
/**
|
|
6545
|
+
* A word as compared — a canonical form, never shown: lower case, a plain plural made singular, and `-ie` / `-y` one
|
|
6546
|
+
* ending (English cannot tell `cookies` from `pastries` by rule, so `cookie`, `cookies`, `pastry`, `pastries` all
|
|
6547
|
+
* fold to the same shape as their singular).
|
|
6548
|
+
*/
|
|
6549
|
+
declare function stockWord(raw: string): string;
|
|
6550
|
+
/** The meaningful words of a text, in order, each once. */
|
|
6551
|
+
declare function stockWords(...texts: ReadonlyArray<unknown>): string[];
|
|
6552
|
+
/** A CSS aspect ratio (`4 / 3`, `1.5`) as width / height, or undefined. */
|
|
6553
|
+
declare function aspectRatioOf(aspect: unknown): number | undefined;
|
|
6554
|
+
/** What the planner reads of a library photo. */
|
|
6555
|
+
interface StockPhotoTraits {
|
|
6556
|
+
alt: string;
|
|
6557
|
+
width?: number;
|
|
6558
|
+
height?: number;
|
|
6559
|
+
tags?: ReadonlyArray<string>;
|
|
6560
|
+
orientation?: `${StockOrientation}`;
|
|
6561
|
+
subject?: `${StockSubject}`;
|
|
6562
|
+
review?: StockReview;
|
|
6563
|
+
}
|
|
6564
|
+
/** A photo may be placed: not rejected and nothing flagged by the review. */
|
|
6565
|
+
declare function stockPhotoUsable(photo: Pick<StockPhotoTraits, 'review'>): boolean;
|
|
6566
|
+
/** The words a photo is placed by: its tags (else its alt — the provider's own words) and the reviewer's notes. */
|
|
6567
|
+
declare function stockPhotoWords(photo: Pick<StockPhotoTraits, 'alt' | 'tags' | 'review'>): string[];
|
|
6568
|
+
/** Its width / height: the size when known, else its orientation's nominal shape, else undefined. */
|
|
6569
|
+
declare function stockPhotoRatio(photo: Pick<StockPhotoTraits, 'width' | 'height' | 'orientation'>): number | undefined;
|
|
6570
|
+
/** The orientation of a width / height ratio. */
|
|
6571
|
+
declare function orientationOfRatio(ratio: number): StockOrientation;
|
|
6572
|
+
/** The reviewer's subject, else the one its words name most (ties: the enum's order), else undefined. */
|
|
6573
|
+
declare function stockPhotoSubject(photo: Pick<StockPhotoTraits, 'alt' | 'tags' | 'review' | 'subject'>): StockSubject | undefined;
|
|
6574
|
+
/** What a slot asks of a photo. */
|
|
6575
|
+
interface StockNeed {
|
|
6576
|
+
intent: `${StockIntent}`;
|
|
6577
|
+
/** The slot's width / height (undefined: any shape). */
|
|
6578
|
+
aspect?: number;
|
|
6579
|
+
/** The plan's words around the slot. */
|
|
6580
|
+
words: ReadonlyArray<string>;
|
|
6581
|
+
}
|
|
6582
|
+
/** The share of the photo a slot shows (1 = the same shape), or undefined when either shape is unknown. */
|
|
6583
|
+
declare function stockKept(photo: Pick<StockPhotoTraits, 'width' | 'height' | 'orientation'>, aspect: number | undefined): number | undefined;
|
|
6584
|
+
/** A photo fits a slot when the slot keeps at least half of it (an unknown shape fits). */
|
|
6585
|
+
declare function stockFits(photo: Pick<StockPhotoTraits, 'width' | 'height' | 'orientation'>, need: Pick<StockNeed, 'aspect'>): boolean;
|
|
6586
|
+
/** How well a photo answers a slot: its subject for the intent, its shape, the plan's words and the type's imagery. */
|
|
6587
|
+
declare function scoreStockPhoto(photo: StockPhotoTraits, need: StockNeed, imagery?: ReadonlyArray<string>): number;
|
|
6588
|
+
|
|
6589
|
+
/**
|
|
6590
|
+
* Stock photos in a built site (AI plan §3g / §3m, I2 + I2.1): a FREE picture from the platform's curated library, copied
|
|
6486
6591
|
* into the studio's own storage (never hot-linked), placed only in an EMPTY picture slot of a section that may show
|
|
6487
6592
|
* one — never the owner's work (a collection, a detail page, a gallery: what the nine portfolio types sell is theirs),
|
|
6488
|
-
* never a person (testimonials, the team), never anywhere when the business type says `stock: false
|
|
6593
|
+
* never a person (testimonials, the team), never anywhere when the business type says `stock: false` — and, since I2.1
|
|
6594
|
+
* (Doron, 2026-10-28), in the picture of a MARKED sample item (a product, a service, a dish) of a type that may show
|
|
6595
|
+
* stock. Which photo goes where is chosen by what the slot is about (`stock-match.ts`). Its alt text is
|
|
6489
6596
|
* the provider's description of what it shows (never a claim), and the credit line its licence requires — when it
|
|
6490
6597
|
* requires one — is placed in the footer. Pure: the build step does the copying.
|
|
6491
6598
|
*/
|
|
@@ -6528,34 +6635,77 @@ interface StockCredit {
|
|
|
6528
6635
|
licence: string;
|
|
6529
6636
|
licenceUrl: string;
|
|
6530
6637
|
}
|
|
6531
|
-
/**
|
|
6532
|
-
|
|
6533
|
-
|
|
6638
|
+
/**
|
|
6639
|
+
* One picture of the platform library (`iox-stock/<type>/`), as its manifest lists it. Everything past `credit` is
|
|
6640
|
+
* optional (I2.1) — a manifest written before it is read as it was, and the planner derives what is missing.
|
|
6641
|
+
*/
|
|
6642
|
+
interface StockPhoto extends StockPhotoTraits {
|
|
6643
|
+
/** The library key (`spa/12345.jpg`) — the copy's file name is derived from it, the build reads it from the library's address. */
|
|
6534
6644
|
key: string;
|
|
6535
|
-
/** Where the
|
|
6645
|
+
/** Where the operator's run wrote it (kept for older readers; a build resolves `key` against the library's address). */
|
|
6536
6646
|
url: string;
|
|
6537
6647
|
/** What it shows, in the provider's words — the alt text. Never a claim. */
|
|
6538
6648
|
alt: string;
|
|
6539
|
-
width?: number;
|
|
6540
|
-
height?: number;
|
|
6541
6649
|
credit: StockCredit;
|
|
6542
6650
|
}
|
|
6543
6651
|
/** The section families a stock photo may stand in for a picture in. */
|
|
6544
6652
|
declare const STOCK_FAMILIES: ReadonlySet<string>;
|
|
6545
|
-
|
|
6653
|
+
/** The aspect a list item's picture is shown at (Card v2's Image top, `composite-core` card layouts). */
|
|
6654
|
+
declare const STOCK_ITEM_ASPECT: number;
|
|
6655
|
+
declare enum StockSlotKind {
|
|
6656
|
+
/** An empty Image of a composed section. */
|
|
6657
|
+
Section = "section",
|
|
6658
|
+
/** The image field of a marked sample item. */
|
|
6659
|
+
Item = "item"
|
|
6660
|
+
}
|
|
6661
|
+
/** An empty picture of a composed section a stock photo may fill. */
|
|
6662
|
+
interface StockSlot extends StockNeed {
|
|
6663
|
+
kind: `${StockSlotKind.Section}`;
|
|
6546
6664
|
/** The Image node the photo goes into. */
|
|
6547
6665
|
nodeId: string;
|
|
6548
6666
|
/** The page key it lives on. */
|
|
6549
6667
|
owner: string;
|
|
6550
6668
|
pattern: string;
|
|
6551
6669
|
}
|
|
6552
|
-
/** The
|
|
6670
|
+
/** The image field of a sample item (a product, a service, a dish) a stock photo may fill. */
|
|
6671
|
+
interface StockItemSlot extends StockNeed {
|
|
6672
|
+
kind: `${StockSlotKind.Item}`;
|
|
6673
|
+
contentType: string;
|
|
6674
|
+
/** The item's 0-based position in the content type's samples. */
|
|
6675
|
+
index: number;
|
|
6676
|
+
/** The field playing the type's `image` role. */
|
|
6677
|
+
field: string;
|
|
6678
|
+
}
|
|
6679
|
+
type StockTarget = StockSlot | StockItemSlot;
|
|
6680
|
+
/** Where a photo may not appear twice: one page; one content type's samples (a list shows them together). */
|
|
6681
|
+
declare function stockGroupOf(target: StockTarget): string;
|
|
6682
|
+
/**
|
|
6683
|
+
* The empty picture slots of a composed site a stock photo may fill, in page and section order — each with its intent
|
|
6684
|
+
* (the section's family), its aspect, and the words around it: the text beside it (its row), the section's slots and
|
|
6685
|
+
* the page's name and search words.
|
|
6686
|
+
*/
|
|
6553
6687
|
declare function stockSlots(plan: SitePlan, composed: Pick<ComposedSite, 'pages' | 'placeholders'>): StockSlot[];
|
|
6554
|
-
|
|
6688
|
+
/**
|
|
6689
|
+
* The sample items' pictures a stock photo may fill: per content type with an `image` role, each MARKED sample whose
|
|
6690
|
+
* image is still a bundled sample (`{sampleImage:k}`), with the type's and the item's own words.
|
|
6691
|
+
*/
|
|
6692
|
+
declare function stockItemSlots(plan: Pick<SitePlan, 'contentTypes'>, composed: Pick<ComposedSite, 'items'>): StockItemSlot[];
|
|
6693
|
+
type StockPlacement<T extends StockTarget = StockTarget> = T & {
|
|
6555
6694
|
photo: StockPhoto;
|
|
6695
|
+
};
|
|
6696
|
+
/** What a placement run is told besides the slots and the library. */
|
|
6697
|
+
interface StockPlanOptions {
|
|
6698
|
+
/** The business type's imagery line (harness-core `BusinessType.imagery`) — its words count for every slot. */
|
|
6699
|
+
imagery?: string;
|
|
6556
6700
|
}
|
|
6557
|
-
/**
|
|
6558
|
-
|
|
6701
|
+
/**
|
|
6702
|
+
* Which photo goes where (I2.1). Slots choose in intent order (the hero first, list items last; page order within),
|
|
6703
|
+
* each taking the best-scoring photo that FITS its shape (`stockFits`) and is not already on its page (or among its
|
|
6704
|
+
* type's samples) — a photo no slot has taken yet always before one another page shows. Ties go to the library's
|
|
6705
|
+
* order. A slot nothing fits keeps its empty box (a sample its abstract picture). None at all when the type says no
|
|
6706
|
+
* stock, and never a photo the review rejected or flagged. Returned in the slots' order.
|
|
6707
|
+
*/
|
|
6708
|
+
declare function planStockPhotos<T extends StockTarget>(slots: ReadonlyArray<T>, photos: ReadonlyArray<StockPhoto>, stockAllowed: boolean, opts?: StockPlanOptions): Array<StockPlacement<T>>;
|
|
6559
6709
|
/** A placed photo's copy in the studio's storage: the key the build writes it under. */
|
|
6560
6710
|
declare function stockCopyKey(photo: Pick<StockPhoto, 'key'>): string;
|
|
6561
6711
|
/** Put the studio's copy of each photo into its slot (src + the provider's alt). Returns the nodes changed. */
|
|
@@ -6564,6 +6714,13 @@ declare function applyStockPhotos(trees: ReadonlyArray<SerializedNode>, placed:
|
|
|
6564
6714
|
url: string;
|
|
6565
6715
|
alt: string;
|
|
6566
6716
|
}>): number;
|
|
6717
|
+
/** Put the studio's copy of each photo into its sample item's image field. Returns the items changed. */
|
|
6718
|
+
declare function applyStockItemPhotos(items: Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>, placed: ReadonlyArray<{
|
|
6719
|
+
contentType: string;
|
|
6720
|
+
index: number;
|
|
6721
|
+
field: string;
|
|
6722
|
+
url: string;
|
|
6723
|
+
}>): number;
|
|
6567
6724
|
/** The line a picture's licence requires on the site, or null when it requires none (`STOCK_PROVIDER_TERMS`). */
|
|
6568
6725
|
declare function stockCreditLine(credit: Pick<StockCredit, 'provider'>): StockLink | null;
|
|
6569
6726
|
/** The credit lines a set of photos needs — one per distinct line, none for a provider whose licence asks none. */
|
|
@@ -6578,5 +6735,5 @@ declare function stockCreditNode(index: number, credit: StockLink): SerializedNo
|
|
|
6578
6735
|
/** Append the credit lines to the footer root (once each). Returns how many it added. */
|
|
6579
6736
|
declare function addStockCredits(footer: SerializedNode, credits: ReadonlyArray<StockLink>): number;
|
|
6580
6737
|
|
|
6581
|
-
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_PART_TOKENS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GRAMMAR_TOO_LARGE_MESSAGE, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPES, PLAN_EDITS_PART_SCHEMA_NAMES, PLAN_EDIT_OP_PART, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_PARTS, PLAN_PART_KEYS, PLAN_WIRE_DERIVED, PLAN_WIRE_PART_SCHEMA_NAMES, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanPart, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, aiStepParts, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, derivedDataSources, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternListGap, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditOps, planEditToWire, planEditWireGaps, planEditWireKeys, planEditWireOps, planEditWireShape, planEditsFromWire, planEditsJsonSchema, planEditsToWire, planEntrances, planFromTemplate, planNavShape, planPartOfPath, planPartToWire, planStockPhotos, planToWire, planWireGaps, planWirePartJsonSchema, planWirePartShape, planWordIssues, publishRefusal, publishVerdict, recipeListBinds, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, strictSchemaComplexity, strictSchemaRefusal, typicalStepTokens, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, withDerivedSources, wordPattern, writtenTexts };
|
|
6582
|
-
export type { AddBarItemResult, AiEntitlementsView, AiMeter, AiModelEntry, AiRefusal, AiTokenCounts, AiTokenPrice, AuditPage, AuditPlaceholder, BarItemsEditResult, BuildNotice, BuildStepError, BuildStepFacts, BuildStepRecord, CmsDataSourcePlan, ComposeContext, ComposeIssue, ComposedDataSource, ComposedItem, ComposedNavHeader, ComposedPage, ComposedPlaceholder, ComposedRoute, ComposedSection, ComposedSite, ContentFieldPlan, ContentRoles, ContentSeedPlan, ContentTypePlan, CreateNodeOptions, CreatedRecord, DataSourcePlan, DynamicKeysAsList, EntranceAction, EntranceActionOptions, EntranceInteraction, EntranceMark, EntranceSpec, EntranceStamp, FactKey, JsonSchema, NavHeaderInput, NavHeaderOptions, NavPage, NavShapeOptions, NavShapeResult, NodeBaseline, PageArchetype, PageArchetypeId, PageArchetypeSlot, PageData, PageDataLine, PagePlan, PageSectionPlan, PageSeoPlan, PathIssue, PatternManifestEntry, PatternNode, PatternPath, PatternRoleNeed, PlainObject, PlanChange, PlanEdit, PlanEditIssue, PlanEditResult, PlanEditsAnswer, PlanEntrancesInput, PlanWireContext, PlanWireParts, PlanWordContext, PlanWordIssue, PreflightRefusal, PublishRefusal, RecipeListBinds, RelativeDataSourcePlan, SectionEntrances, SectionInput, SectionOptions, SerializedBinding, SerializedNode, SerializedResponsive, ShapeEntry, ShapeIssueCodes, ShowcaseInput, SiteAudit, SiteAuditOptions, SiteAuditSnapshot, SiteBuildRun, SiteBusiness, SiteCheck, SiteFinding, SiteNeedId, SitePageRef, SitePattern, SitePlan, SitePlanIssue, SitePlanSource, SitePlanValidation, SiteTemplate, StockCredit, StockLink, StockPhoto, StockPlacement, StockProviderTerms, StockSlot, StrictSchemaComplexity, StrictSchemaRefusal, TemplatePlanInput, UndoKept, UndoPreview, UndoRecord, WireOptions, WrittenText };
|
|
6738
|
+
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_PART_TOKENS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GRAMMAR_TOO_LARGE_MESSAGE, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPES, PLAN_EDITS_PART_SCHEMA_NAMES, PLAN_EDIT_OP_PART, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_PARTS, PLAN_PART_KEYS, PLAN_WIRE_DERIVED, PLAN_WIRE_PART_SCHEMA_NAMES, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanPart, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_FAMILY_INTENTS, STOCK_INTENT_ORDER, STOCK_INTENT_SUBJECTS, STOCK_ITEM_ASPECT, STOCK_MIN_KEPT, STOCK_PROVIDER_TERMS, STOCK_SUBJECT_WORDS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockIntent, StockOrientation, StockProviderId, StockReviewFlag, StockReviewVerdict, StockSlotKind, StockSubject, addBarItem, addStockCredits, aiStepParts, applyPlanEdits, applyStockItemPhotos, applyStockPhotos, archetypeIssues, aspectRatioOf, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, derivedDataSources, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, orientationOfRatio, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternListGap, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditOps, planEditToWire, planEditWireGaps, planEditWireKeys, planEditWireOps, planEditWireShape, planEditsFromWire, planEditsJsonSchema, planEditsToWire, planEntrances, planFromTemplate, planNavShape, planPartOfPath, planPartToWire, planStockPhotos, planToWire, planWireGaps, planWirePartJsonSchema, planWirePartShape, planWordIssues, publishRefusal, publishVerdict, recipeListBinds, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, scoreStockPhoto, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockFits, stockGroupOf, stockItemSlots, stockKept, stockPhotoRatio, stockPhotoSubject, stockPhotoUsable, stockPhotoWords, stockSlots, stockWord, stockWords, strictSchemaComplexity, strictSchemaRefusal, typicalStepTokens, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, withDerivedSources, wordPattern, writtenTexts };
|
|
6739
|
+
export type { AddBarItemResult, AiEntitlementsView, AiMeter, AiModelEntry, AiRefusal, AiTokenCounts, AiTokenPrice, AuditPage, AuditPlaceholder, BarItemsEditResult, BuildNotice, BuildStepError, BuildStepFacts, BuildStepRecord, CmsDataSourcePlan, ComposeContext, ComposeIssue, ComposedDataSource, ComposedItem, ComposedNavHeader, ComposedPage, ComposedPlaceholder, ComposedRoute, ComposedSection, ComposedSite, ContentFieldPlan, ContentRoles, ContentSeedPlan, ContentTypePlan, CreateNodeOptions, CreatedRecord, DataSourcePlan, DynamicKeysAsList, EntranceAction, EntranceActionOptions, EntranceInteraction, EntranceMark, EntranceSpec, EntranceStamp, FactKey, JsonSchema, NavHeaderInput, NavHeaderOptions, NavPage, NavShapeOptions, NavShapeResult, NodeBaseline, PageArchetype, PageArchetypeId, PageArchetypeSlot, PageData, PageDataLine, PagePlan, PageSectionPlan, PageSeoPlan, PathIssue, PatternManifestEntry, PatternNode, PatternPath, PatternRoleNeed, PlainObject, PlanChange, PlanEdit, PlanEditIssue, PlanEditResult, PlanEditsAnswer, PlanEntrancesInput, PlanWireContext, PlanWireParts, PlanWordContext, PlanWordIssue, PreflightRefusal, PublishRefusal, RecipeListBinds, RelativeDataSourcePlan, SectionEntrances, SectionInput, SectionOptions, SerializedBinding, SerializedNode, SerializedResponsive, ShapeEntry, ShapeIssueCodes, ShowcaseInput, SiteAudit, SiteAuditOptions, SiteAuditSnapshot, SiteBuildRun, SiteBusiness, SiteCheck, SiteFinding, SiteNeedId, SitePageRef, SitePattern, SitePlan, SitePlanIssue, SitePlanSource, SitePlanValidation, SiteTemplate, StockCredit, StockItemSlot, StockLink, StockNeed, StockPhoto, StockPhotoTraits, StockPlacement, StockPlanOptions, StockProviderTerms, StockReview, StockSlot, StockTarget, StrictSchemaComplexity, StrictSchemaRefusal, TemplatePlanInput, UndoKept, UndoPreview, UndoRecord, WireOptions, WrittenText };
|
package/package.json
CHANGED
|
@@ -126,6 +126,9 @@ export enum BuildNoticeCode {
|
|
|
126
126
|
ContentTypeNotPublic = 'content-type-not-public',
|
|
127
127
|
/** The stock library could not be read: the picture slots keep the empty box, and the run says so. */
|
|
128
128
|
StockUnavailable = 'stock-unavailable',
|
|
129
|
+
/** The stock library was read but holds no photo for this kind of business (never built, or reviewed down to
|
|
130
|
+
* none): the picture slots keep the empty box, and the run says so (I2.1 — never silently). */
|
|
131
|
+
StockMissing = 'stock-missing',
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
export interface BuildNotice {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a stock photo is judged against a picture slot (AI plan §3g, I2.1) — pure and deterministic, so the same plan and
|
|
3
|
+
* the same library always place the same photos. A photo is described by what the library's manifest carries (the
|
|
4
|
+
* provider's tags, its orientation, the reviewer's subject and notes — each optional, derived when absent); a slot by
|
|
5
|
+
* its INTENT (what kind of picture its section wants), its aspect ratio and the plan's words around it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export enum StockOrientation {
|
|
9
|
+
Horizontal = 'horizontal',
|
|
10
|
+
Vertical = 'vertical',
|
|
11
|
+
Square = 'square',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** What a photo mainly shows — the reviewer may set it; otherwise it is read from the provider's tags. */
|
|
15
|
+
export enum StockSubject {
|
|
16
|
+
/** A room, a shop front, a garden — somewhere. */
|
|
17
|
+
Place = 'place',
|
|
18
|
+
/** A close-up: a texture, a tool, an ingredient. */
|
|
19
|
+
Detail = 'detail',
|
|
20
|
+
/** A thing a business sells or serves: a cake, a bottle, a bouquet. */
|
|
21
|
+
Product = 'product',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** What a slot wants to show. */
|
|
25
|
+
export enum StockIntent {
|
|
26
|
+
/** The hero / a page header / a call to action: wide and atmospheric. */
|
|
27
|
+
Atmosphere = 'atmosphere',
|
|
28
|
+
/** A story or a visit section: the place. */
|
|
29
|
+
Place = 'place',
|
|
30
|
+
/** A feature row: a detail of the work or the service. */
|
|
31
|
+
Detail = 'detail',
|
|
32
|
+
/** A list item's picture (a product, a service, a dish). */
|
|
33
|
+
Product = 'product',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export enum StockReviewVerdict {
|
|
37
|
+
Approved = 'approved',
|
|
38
|
+
Rejected = 'rejected',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** What the review found in a photo that keeps it out of every site (the licence: no person, no logo or brand). */
|
|
42
|
+
export enum StockReviewFlag {
|
|
43
|
+
Person = 'person',
|
|
44
|
+
Logo = 'logo',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The operator's one review of a photo, as the manifest records it. */
|
|
48
|
+
export interface StockReview {
|
|
49
|
+
verdict: `${StockReviewVerdict}`;
|
|
50
|
+
flags?: Array<`${StockReviewFlag}`>;
|
|
51
|
+
/** The reviewer's own words for it — they count as tags when placing it. */
|
|
52
|
+
notes?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The section families a stock photo may stand in for a picture in, and what each wants. */
|
|
56
|
+
export const STOCK_FAMILY_INTENTS: Readonly<Record<string, StockIntent>> = {
|
|
57
|
+
hero: StockIntent.Atmosphere,
|
|
58
|
+
header: StockIntent.Atmosphere,
|
|
59
|
+
cta: StockIntent.Atmosphere,
|
|
60
|
+
story: StockIntent.Place,
|
|
61
|
+
contact: StockIntent.Place,
|
|
62
|
+
feature: StockIntent.Detail,
|
|
63
|
+
content: StockIntent.Detail,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Per intent, the subjects it takes, best first. */
|
|
67
|
+
export const STOCK_INTENT_SUBJECTS: Readonly<Record<StockIntent, ReadonlyArray<StockSubject>>> = {
|
|
68
|
+
[StockIntent.Atmosphere]: [StockSubject.Place, StockSubject.Detail, StockSubject.Product],
|
|
69
|
+
[StockIntent.Place]: [StockSubject.Place, StockSubject.Detail, StockSubject.Product],
|
|
70
|
+
[StockIntent.Detail]: [StockSubject.Detail, StockSubject.Product, StockSubject.Place],
|
|
71
|
+
[StockIntent.Product]: [StockSubject.Product, StockSubject.Detail, StockSubject.Place],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** The order slots choose in: the hero picks first, a list item last. */
|
|
75
|
+
export const STOCK_INTENT_ORDER: ReadonlyArray<StockIntent> = [StockIntent.Atmosphere, StockIntent.Place, StockIntent.Detail, StockIntent.Product];
|
|
76
|
+
|
|
77
|
+
/** The words that tell a photo's subject when the reviewer did not name it (singular, lower case). */
|
|
78
|
+
export const STOCK_SUBJECT_WORDS: Readonly<Record<StockSubject, ReadonlyArray<string>>> = {
|
|
79
|
+
[StockSubject.Place]: [
|
|
80
|
+
'room', 'interior', 'studio', 'salon', 'shop', 'store', 'boutique', 'restaurant', 'cafe', 'bar', 'bakery', 'cellar', 'kitchen',
|
|
81
|
+
'gym', 'clinic', 'office', 'lobby', 'hall', 'building', 'architecture', 'facade', 'exterior', 'street', 'garden', 'terrace',
|
|
82
|
+
'patio', 'pool', 'sauna', 'window', 'landscape', 'view', 'beach', 'forest', 'mountain', 'city', 'house', 'workshop', 'counter',
|
|
83
|
+
'shelf', 'reception', 'spa', 'hotel', 'bedroom', 'bathroom', 'living', 'courtyard', 'vineyard', 'market',
|
|
84
|
+
],
|
|
85
|
+
[StockSubject.Detail]: [
|
|
86
|
+
'texture', 'detail', 'closeup', 'macro', 'pattern', 'fabric', 'wood', 'wooden', 'stone', 'marble', 'leaf', 'towel', 'tool',
|
|
87
|
+
'equipment', 'ingredient', 'spice', 'grain', 'bean', 'oil', 'water', 'steam', 'shadow', 'paper', 'thread', 'brush', 'ceramic',
|
|
88
|
+
],
|
|
89
|
+
[StockSubject.Product]: [
|
|
90
|
+
'product', 'bottle', 'wine', 'beer', 'coffee', 'cup', 'mug', 'tea', 'cake', 'bread', 'pastry', 'croissant', 'cookie', 'dessert',
|
|
91
|
+
'chocolate', 'food', 'dish', 'plate', 'meal', 'pizza', 'salad', 'soup', 'burger', 'fruit', 'flower', 'bouquet', 'jewelry',
|
|
92
|
+
'jewellery', 'ring', 'necklace', 'cosmetic', 'cream', 'soap', 'perfume', 'candle', 'shoe', 'bag', 'dress', 'clothing', 'book', 'toy',
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Words that say nothing about what a picture shows (the plan's sample and placeholder words among them). */
|
|
97
|
+
const STOP_WORDS: ReadonlySet<string> = new Set([
|
|
98
|
+
'the', 'and', 'with', 'for', 'your', 'our', 'you', 'from', 'into', 'this', 'that', 'here', 'there', 'about', 'page', 'section',
|
|
99
|
+
'sample', 'replace', 'image', 'photo', 'picture', 'sentence', 'words', 'own', 'how', 'what', 'who', 'are', 'all', 'one', 'two',
|
|
100
|
+
'three', 'more', 'less', 'very', 'tell', 'story', 'item', 'service', 'project',
|
|
101
|
+
]);
|
|
102
|
+
const MIN_WORD = 3;
|
|
103
|
+
const HTML_TAG = /<[^>]*>/g;
|
|
104
|
+
const WORD_SPLIT = /[^\p{L}\p{N}]+/u;
|
|
105
|
+
|
|
106
|
+
/** Nominal ratios (width / height) of an orientation when the manifest gives no size. */
|
|
107
|
+
const ORIENTATION_RATIO: Readonly<Record<StockOrientation, number>> = {
|
|
108
|
+
[StockOrientation.Horizontal]: 3 / 2,
|
|
109
|
+
[StockOrientation.Vertical]: 2 / 3,
|
|
110
|
+
[StockOrientation.Square]: 1,
|
|
111
|
+
};
|
|
112
|
+
/** The share of a photo a slot may crop away and still be a fit: a portrait never fills a 21 / 9 band. */
|
|
113
|
+
export const STOCK_MIN_KEPT = 0.5;
|
|
114
|
+
/** What an unknown shape keeps, for scoring only (neither rewarded nor refused). */
|
|
115
|
+
const UNKNOWN_KEPT = 0.75;
|
|
116
|
+
|
|
117
|
+
/** Score weights — subject, then shape, then the plan's own words, then the business type's imagery words. */
|
|
118
|
+
const SUBJECT_POINTS: ReadonlyArray<number> = [4, 2, 1];
|
|
119
|
+
const UNKNOWN_SUBJECT_POINTS = 1.5;
|
|
120
|
+
const SHAPE_POINTS = 4;
|
|
121
|
+
const WORD_POINTS = 2;
|
|
122
|
+
const MAX_WORD_MATCHES = 3;
|
|
123
|
+
const IMAGERY_POINTS = 1;
|
|
124
|
+
const MAX_IMAGERY_MATCHES = 2;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A word as compared — a canonical form, never shown: lower case, a plain plural made singular, and `-ie` / `-y` one
|
|
128
|
+
* ending (English cannot tell `cookies` from `pastries` by rule, so `cookie`, `cookies`, `pastry`, `pastries` all
|
|
129
|
+
* fold to the same shape as their singular).
|
|
130
|
+
*/
|
|
131
|
+
export function stockWord(raw: string): string {
|
|
132
|
+
let w = raw.toLowerCase();
|
|
133
|
+
if (w.length > 3 && w.endsWith('s') && !w.endsWith('ss') && !STOP_WORDS.has(w)) w = w.slice(0, -1);
|
|
134
|
+
return w.length > 3 && w.endsWith('ie') ? `${w.slice(0, -2)}y` : w;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The meaningful words of a text, in order, each once. */
|
|
138
|
+
export function stockWords(...texts: ReadonlyArray<unknown>): string[] {
|
|
139
|
+
const out = new Set<string>();
|
|
140
|
+
for (const text of texts) {
|
|
141
|
+
if (typeof text !== 'string') continue;
|
|
142
|
+
for (const raw of text.replace(HTML_TAG, ' ').split(WORD_SPLIT)) {
|
|
143
|
+
const w = stockWord(raw);
|
|
144
|
+
if (w.length >= MIN_WORD && !STOP_WORDS.has(w) && !/^\d+$/.test(w)) out.add(w);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return [...out];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A CSS aspect ratio (`4 / 3`, `1.5`) as width / height, or undefined. */
|
|
151
|
+
export function aspectRatioOf(aspect: unknown): number | undefined {
|
|
152
|
+
if (typeof aspect === 'number') return aspect > 0 ? aspect : undefined;
|
|
153
|
+
if (typeof aspect !== 'string') return undefined;
|
|
154
|
+
const [w, h] = aspect.split('/').map(s => Number(s.trim()));
|
|
155
|
+
const ratio = h === undefined ? w : w / h;
|
|
156
|
+
return Number.isFinite(ratio) && ratio > 0 ? ratio : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** What the planner reads of a library photo. */
|
|
160
|
+
export interface StockPhotoTraits {
|
|
161
|
+
alt: string;
|
|
162
|
+
width?: number;
|
|
163
|
+
height?: number;
|
|
164
|
+
tags?: ReadonlyArray<string>;
|
|
165
|
+
orientation?: `${StockOrientation}`;
|
|
166
|
+
subject?: `${StockSubject}`;
|
|
167
|
+
review?: StockReview;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A photo may be placed: not rejected and nothing flagged by the review. */
|
|
171
|
+
export function stockPhotoUsable(photo: Pick<StockPhotoTraits, 'review'>): boolean {
|
|
172
|
+
return photo.review?.verdict !== StockReviewVerdict.Rejected && !(photo.review?.flags?.length);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The words a photo is placed by: its tags (else its alt — the provider's own words) and the reviewer's notes. */
|
|
176
|
+
export function stockPhotoWords(photo: Pick<StockPhotoTraits, 'alt' | 'tags' | 'review'>): string[] {
|
|
177
|
+
const tags = photo.tags?.length ? photo.tags.join(', ') : photo.alt;
|
|
178
|
+
return stockWords(tags, photo.review?.notes);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Its width / height: the size when known, else its orientation's nominal shape, else undefined. */
|
|
182
|
+
export function stockPhotoRatio(photo: Pick<StockPhotoTraits, 'width' | 'height' | 'orientation'>): number | undefined {
|
|
183
|
+
if (photo.width && photo.height && photo.width > 0 && photo.height > 0) return photo.width / photo.height;
|
|
184
|
+
return photo.orientation ? ORIENTATION_RATIO[photo.orientation as StockOrientation] : undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The orientation of a width / height ratio. */
|
|
188
|
+
export function orientationOfRatio(ratio: number): StockOrientation {
|
|
189
|
+
if (ratio > 1.05) return StockOrientation.Horizontal;
|
|
190
|
+
return ratio < 0.95 ? StockOrientation.Vertical : StockOrientation.Square;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The reviewer's subject, else the one its words name most (ties: the enum's order), else undefined. */
|
|
194
|
+
export function stockPhotoSubject(photo: Pick<StockPhotoTraits, 'alt' | 'tags' | 'review' | 'subject'>): StockSubject | undefined {
|
|
195
|
+
if (photo.subject && (Object.values(StockSubject) as string[]).includes(photo.subject)) return photo.subject as StockSubject;
|
|
196
|
+
const words = new Set(stockPhotoWords(photo));
|
|
197
|
+
let best: StockSubject | undefined;
|
|
198
|
+
let most = 0;
|
|
199
|
+
for (const subject of Object.values(StockSubject)) {
|
|
200
|
+
const n = STOCK_SUBJECT_WORDS[subject].filter(w => words.has(stockWord(w))).length;
|
|
201
|
+
if (n > most) { most = n; best = subject; }
|
|
202
|
+
}
|
|
203
|
+
return best;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** What a slot asks of a photo. */
|
|
207
|
+
export interface StockNeed {
|
|
208
|
+
intent: `${StockIntent}`;
|
|
209
|
+
/** The slot's width / height (undefined: any shape). */
|
|
210
|
+
aspect?: number;
|
|
211
|
+
/** The plan's words around the slot. */
|
|
212
|
+
words: ReadonlyArray<string>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The share of the photo a slot shows (1 = the same shape), or undefined when either shape is unknown. */
|
|
216
|
+
export function stockKept(photo: Pick<StockPhotoTraits, 'width' | 'height' | 'orientation'>, aspect: number | undefined): number | undefined {
|
|
217
|
+
const ratio = stockPhotoRatio(photo);
|
|
218
|
+
if (!ratio || !aspect) return undefined;
|
|
219
|
+
return Math.min(ratio, aspect) / Math.max(ratio, aspect);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** A photo fits a slot when the slot keeps at least half of it (an unknown shape fits). */
|
|
223
|
+
export function stockFits(photo: Pick<StockPhotoTraits, 'width' | 'height' | 'orientation'>, need: Pick<StockNeed, 'aspect'>): boolean {
|
|
224
|
+
const kept = stockKept(photo, need.aspect);
|
|
225
|
+
return kept === undefined || kept >= STOCK_MIN_KEPT;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** How well a photo answers a slot: its subject for the intent, its shape, the plan's words and the type's imagery. */
|
|
229
|
+
export function scoreStockPhoto(photo: StockPhotoTraits, need: StockNeed, imagery: ReadonlyArray<string> = []): number {
|
|
230
|
+
const subject = stockPhotoSubject(photo);
|
|
231
|
+
const rank = subject ? STOCK_INTENT_SUBJECTS[need.intent as StockIntent].indexOf(subject) : -1;
|
|
232
|
+
const subjectPoints = subject ? SUBJECT_POINTS[rank] ?? 0 : UNKNOWN_SUBJECT_POINTS;
|
|
233
|
+
const shapePoints = SHAPE_POINTS * (stockKept(photo, need.aspect) ?? UNKNOWN_KEPT);
|
|
234
|
+
const words = new Set(stockPhotoWords(photo));
|
|
235
|
+
const matched = need.words.filter(w => words.has(w)).length;
|
|
236
|
+
const imageryMatched = imagery.filter(w => words.has(w)).length;
|
|
237
|
+
return subjectPoints + shapePoints + WORD_POINTS * Math.min(MAX_WORD_MATCHES, matched) + IMAGERY_POINTS * Math.min(MAX_IMAGERY_MATCHES, imageryMatched);
|
|
238
|
+
}
|