@aglyn/plugins-ai 1.0.0-beta.266 → 1.0.0-beta.268
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +18 -18
- package/src/lib/components/ai-job-site.d.ts +8 -1
- package/src/lib/components/ai-job-site.js +7 -0
- package/src/lib/components/ai-job-site.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +37 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +25 -6
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +8 -2
- package/src/lib/components/ai-site-start-steps.component.js +55 -26
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
- package/src/lib/components/ai-site-start-tracking.d.ts +11 -3
- package/src/lib/components/ai-site-start-tracking.js +28 -9
- package/src/lib/components/ai-site-start-tracking.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +24 -2
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
- package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
- package/src/lib/jobs/ai-jobs-beat.js +23 -6
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +46 -7
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +321 -19
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +31 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +208 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-design.js +5 -0
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +20 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
- package/src/lib/model/ai-site-start-handoff.js +88 -0
- package/src/lib/model/ai-site-start-handoff.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { StockPhoto } from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\n\n/**\n * PIXABAY'S RECORDED ANSWERS FOR THE BETA.267 BRIGHTVALE FREELANCE MARKETING\n * START (AGL-3923, job dUxJutW8Dp, host QyM-_Ii5Kh, 2026-10-18 19:41Z): the\n * searches its cover heroes and its About page's \"Our story\" picture sent,\n * read back from production's `stockPhotoSearches` by each search's key (a\n * SHA-256 of the provider and the request's canonical parameters), never\n * searched again. Ids, sizes, page slugs and tags are Pixabay's own, in its\n * order; the contributor is left out. A hit whose download address is not a\n * .jpg keeps its extension, as Pixabay's own address names it: 10157445's is a\n * .png, labeled image/png, and its bytes were not PNG.\n */\n\nconst hit = (id: number, width: number, height: number, slug: string, tags: string, extension = 'jpg'): StockPhoto =>\n ({\n provider: 'pixabay',\n id: String(id),\n width,\n height,\n pageUrl: `https://pixabay.com/photos/${slug}/`,\n photographer: 'A Pixabay contributor',\n tags: tags ? tags.split(', ') : [],\n downloadUrl: `https://pixabay.com/get/g${id}_1280.${extension}`,\n }) as StockPhoto\n\n/** `freelencing` (horizontal, min_width 1600), every page's cover hero first; key `34565a1176c6bdc7e5797e3cc714ad5b97b0d75b63fe7572d022ac6d64d2c0b9`, answered 2026-10-18T11:36:29.367Z. */\nexport const FREELENCING_HERO: StockPhoto[] = []\n\n/** `small business owner` (horizontal, min_width 1600), the business kind's hero search (#1758); key `e76f3be5ca9474724764f9587fec08e3ff5a8313f37438790f8db25917a3edf0`, answered 2026-10-18T19:44:21.162Z. */\nexport const SMALL_BUSINESS_OWNER_HERO: StockPhoto[] = [\n hit(10207329, 1280, 853, 'small-business-e-commerce-10207329', 'small business, e commerce, order packing, business owner, online store, packaging, shipping orders, home business, startup, entrepreneur, workspace, wooden desk, natural light, productivity, order fulfillment, retail, small business owner, logistics, packaging process, handmade business'),\n hit(5475187, 1280, 853, 'man-entrepreneur-business-manager-5475187', 'man, entrepreneur, business, manager, owner, restaurant, young, start, local, small business, bar, small business, small business, small business, small business, small business'),\n hit(10436141, 1280, 714, 'baker-bakery-woman-smiling-happy-10436141', 'baker, bakery, woman, smiling, happy, bread, craft, freshly baked, small business, owner, entrepreneur, authentic, commercial, food, product, organic, delicious, successful, natural ingredients, artisan product'),\n hit(8379770, 1280, 827, 'jack-russell-puppy-mammal-dog-8379770', 'jack russell, puppy, mammal, dog, nature, playing, cute, pet, playful, animal'),\n hit(5124948, 1280, 853, 'puppy-dog-mammal-cute-small-5124948', 'puppy, pet, animal, dog, mammal, cute, small, adorable, purebred, canine, sweet'),\n hit(7213011, 1280, 853, 'dog-walk-sea-beach-sand-leash-7213011', 'dog, animal, walk, sea, nature, beach, sand, pet, leash, outdoors, friend'),\n hit(3047316, 1280, 853, 'business-businessman-closing-couch-3047316', 'business, businessman, closing, couch, display, indian, male, man, market, metal, metallic, motor, motorcycle, rider, owner, person, seller, shop, shop owner, sitting'),\n hit(1838969, 1280, 660, 'standup-paddleboarding-man-dog-1838969', 'standup paddleboarding, animal, man, dog, friendship, companion, pet, owner, paddleboard, paddle, together, stand, standing, ocean, person, nature, sea, surfboard, water, leisure'),\n hit(5623231, 1280, 853, 'girl-dog-pet-friendship-companion-5623231', 'girl, dog, pet, friendship, companion, owner, caucasian, blonde, caucasian female, blonde female, canine, domestic dog, doggy, happy, animal, nature, love, woman, cute, puppy'),\n hit(1861839, 1280, 850, 'dog-labrador-pet-canine-companion-1861839', 'dog, labrador, pet, canine, companion, friendship, nature, trust, animal, snout'),\n hit(3344414, 1280, 854, 'dog-adorable-animal-cute-happiness-3344414', 'dog, nature, adorable, animal, cute, happiness, happy, man, pet, portrait, puppy, purebred, retriever'),\n hit(1850276, 1280, 853, 'puppy-sleeping-pet-owner-adorable-1850276', 'puppy, sleeping, pet, nature, owner, adorable, animal, canine, cute, dog, sleep, sleeping puppy, asleep, portrait'),\n hit(2264812, 1280, 790, 'arrangement-attractive-beautiful-2264812', 'arrangement, attractive, beautiful, bloom, blossom, bouquet, business, colorful, decoration, detail, floral, florist, flowers, natural, plants, present, shop, nature, small business, flower background'),\n hit(4778387, 1280, 854, 'cat-pet-care-pet-care-kitten-4778387', 'cat, pet, care, pet care, kitten, nature, animal, domestic cat, feline, mammal, cute, furry, playful, fur, kitty, adorable, rescue cat, animal care, closeup'),\n hit(5751151, 1280, 853, 'dog-woman-owner-pet-companion-5751151', 'dog, woman, owner, pet, animal, companion, friends, friendship, canine, fur, furry, furry dog, mammal, adorable, cuddle, cute'),\n hit(1868563, 1280, 853, 'man-kitten-pet-owner-kitty-tabby-1868563', 'man, kitten, pet, owner, kitty, tabby, cat, gray tabby, tabby cat, adorable, animal, cute, feline, nature, caring, hands, holding, portrait'),\n hit(8199216, 1280, 853, 'dog-puppy-grass-canine-mammal-8199216', 'dog, beautiful nature, cool backgrounds, puppy, wallpaper hd, grass, canine, 4k wallpaper, wallpaper 4k, 4k wallpaper 1920x1080, mammal, nature wallpaper, mac wallpaper, happy, free wallpaper, walking, windows wallpaper, free background, playing, laptop wallpaper'),\n hit(5331883, 1280, 853, 'cat-work-technology-pet-cute-pc-5331883', 'cat, work, technology, pet, cute, animal, pc, keyboard, remote work'),\n hit(237193, 1280, 853, 'dog-puppy-bath-water-person-girl-237193', 'dog, puppy, nature, bath, animal, water, person, girl, friendship, kiss, pet'),\n hit(5249583, 1280, 887, 'arabian-horse-horse-man-golden-hour-5249583', 'arabian horse, horse, man, golden hour, mane, equestrian, animal, nature, portrait, horse, horse, horse, horse, horse'),\n hit(2607704, 1280, 854, 'animals-dogs-friendship-pets-eyes-2607704', 'animals, dogs, friendship, pets, eyes, muzzle, adorable, nature, miniature, man, male, people, owner, love, love wallpaper'),\n hit(841174, 1280, 858, 'company-model-woman-message-girl-841174', 'company, model, woman, message, girl, open, model, woman, message, message, message, open, open, open, open, open'),\n hit(4411622, 1280, 720, 'chihuahua-scared-astonished-fun-4411622', 'chihuahua, scared, astonished, fun, small, cute, dog, animal, on the lap, man, hands, poor, animal portrait, love, lifestyle, loyal friend, maul, pet, nose, snout'),\n hit(4930050, 1280, 853, 'mask-walk-dog-pet-new-normal-4930050', 'mask, walk, dog, pet, new normal, animal, virus, pandemic, covid, safey'),\n hit(5467838, 1280, 854, 'woman-entrepreneur-owner-business-5467838', 'woman, entrepreneur, owner, business, shop, store, clothing, manager, saleswoman, retail, sales, small business, retail, small business, small business, small business, small business, small business'),\n hit(5753302, 1280, 853, 'dog-pet-pet-owner-animal-owner-5753302', 'dog, pet, pet owner, animal, owner, domestic dog, canine, harness, dog harness, mammal, cute, adorable, woman, female, nature, outdoors, saxony, germany, leipzig'),\n hit(841164, 1280, 853, 'woman-girl-dog-portrait-smile-841164', 'woman, girl, dog, portrait, pet, smile, animal, look, face'),\n hit(1868578, 1280, 853, 'animal-canine-chihuahua-cute-dog-1868578', 'animal, nature, canine, chihuahua, cute, dog, hand, pet, puppy'),\n hit(5020587, 1280, 853, 'dog-dog-training-training-5020587', 'dog, dog training, training, human and animal, pet, animal, nature, domestic animal, yorkie, yorkshire terrier, garden, meadow'),\n hit(698630, 1280, 853, 'yorkshire-terrier-dog-puppy-animal-698630', 'yorkshire terrier, nature, dog, puppy, animal, pet, tongue, cute, girl, legs, sandals'),\n hit(2562376, 1280, 851, 'people-girl-pet-animal-dog-2562376', 'people, girl, pet, nature, animal, dog'),\n hit(100544, 1280, 857, 'dog-small-cute-ice-cream-sun-100544', 'dog, nature, small, cute, ice cream, pet, sun, summer, sand, animal, human, person, receive'),\n hit(2808859, 1280, 853, 'dog-pet-dog-pets-puppy-walk-cute-2808859', 'dog, pet dog, pets, puppy, pet, walk, cute, nature, gladness, consensus, stare, animal'),\n hit(2592333, 1280, 853, 'dog-puppy-animal-hands-owner-2592333', 'dog, puppy, pet, animal, hands, owner, nature, people, ears'),\n hit(10157445, 1280, 1280, 'small-business-entrepreneur-10157445', 'small business, entrepreneur, ecommerce, packing orders, shipping orders, home business, workspace, wooden desk, startup, small business workflow, independent seller, office equipment, work from home, wrapping paper, labels, home office, agenda, office desk', 'png'),\n hit(1093675, 1280, 854, 'woman-dog-pet-people-female-1093675', 'woman, dog, pet, people, female, animal, lifestyle, together, friendship, togetherness, close, nature, friends'),\n hit(654511, 1280, 853, 'poodle-dog-pets-animal-654511', 'poodle, dog, pets, pet, nature, animal'),\n hit(672786, 1280, 870, 'hybrids-small-cute-small-dog-672786', 'hybrids, small, cute, small dog, domestic animal, fluffy, little hybrid, knuffig'),\n hit(5031737, 1280, 854, 'cat-playful-yellow-eyes-pedigree-5031737', 'cat, playful, yellow eyes, pedigree, striped, persian, exotic, iron, affectionate, care, love, pet, one owner, not let, fluffy, important cat, business, animal, pedigreed, evil'),\n hit(3951245, 1280, 853, 'superman-statue-success-trophy-3951245', 'superman, statue, success, trophy, small, business, design, superman, superman, superman, superman, superman'),\n]\n\n/** `freelencing story` (vertical, min_height 900, category people), the About page's \"Our story\" picture; key `ac3bd740cb1dafac2a9a53c4692c47d3dd2dd9c1f860c9d7592014ebd49816a9`, answered 2026-10-18T19:46:32.905Z. */\nexport const FREELENCING_STORY_PEOPLE: StockPhoto[] = [\n hit(6840320, 894, 1280, 'girl-magic-space-architecture-6840320', 'girl, magic, space, architecture, digital art, art, story'),\n hit(3715695, 1081, 1280, 'daughter-red-rain-girl-fashion-3715695', 'daughter, red, rain, girl, fashion, nature, story'),\n hit(4367341, 853, 1280, 'princess-historical-fairy-tale-4367341', 'princess, historical, fairy tale, fantasy, fairy, crown, magic, conjure up, female, woman, portrait, mystical, story, princess, princess, princess, princess, princess, crown, crown'),\n hit(2974363, 857, 1280, 'prince-boy-kids-magic-crown-2974363', 'prince, boy, kids, magic, crown, a cute baby, story, elf, portrait, sight, prince, prince, prince, prince, prince'),\n hit(5283552, 850, 1280, 'angel-wings-fantasy-horns-sukub-5283552', 'angel, wings, fantasy, horns, sukub, fallen angel, story, wing, young woman, mystical, mystic, magic, daemon, good'),\n hit(7755278, 837, 1280, 'madder-kokoshnik-woman-story-7755278', 'madder, kokoshnik, woman, story, mystic, fantasy, mythology, goddess, slavic, character, nature, costume, image, rite, crown, cosplay, goddess of death'),\n hit(7362364, 837, 1280, 'woman-cosplay-crown-kokoshnik-7362364', 'woman, cosplay, crown, kokoshnik, witch, svarog, magician, scull, horns, damage, shrew, sorceress, enchantress, story, magic, fantasy, cosplay, crown, crown, crown'),\n hit(4747076, 1024, 1280, 'young-woman-forest-deer-fantasy-4747076', 'young woman, forest, deer, fantasy, story, photoshop, nature, photo montage, surreal, fantastic, smoke, mystic, magic, sveta, dream'),\n hit(5344381, 855, 1280, 'young-woman-forest-story-beauty-5344381', 'young woman, forest, story, beauty, nature, mood'),\n hit(7362362, 853, 1280, 'woman-cosplay-crown-kokoshnik-7362362', 'woman, cosplay, crown, kokoshnik, witch, svarog, magician, scull, horns, damage, shrew, sorceress, enchantress, story, magic, fantasy, cosplay, crown, witch, witch'),\n hit(3172580, 1096, 1280, 'stone-door-old-wall-arch-3172580', 'stone, door, old, wall, arch, young woman, magic, story, fantasy, art, illustration, middle ages, magic, illustration, illustration, illustration, illustration, illustration'),\n hit(7359398, 876, 1280, 'woman-book-read-reading-study-7359398', 'woman, book, read, reading, study, girl, hobby, novel, bench, story, literature, reader, leisure, rocker'),\n hit(5344383, 855, 1280, 'young-woman-forest-light-story-5344383', 'young woman, forest, nature, light, story, trees, mood'),\n hit(5344382, 855, 1280, 'young-woman-forest-story-trees-5344382', 'young woman, forest, story, nature, trees, beauty, mood, landscape'),\n hit(3172588, 1141, 1280, 'people-art-adult-young-woman-magic-3172588', 'people, art, adult, young woman, magic, story, fantasy, illustration, middle ages, woman'),\n hit(6857724, 851, 1280, 'elf-cosplay-fantasy-the-enchantress-6857724', 'elf, cosplay, fantasy, the enchantress, woman, portrait, elves, image, magic, enchantress, ears, gems, sparkle, story, fairytale character, young woman, nimbus, holy, cosplay, cosplay'),\n hit(8533942, 834, 1280, 'woman-fashion-beauty-eastern-8533942', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose, dagger, weapon, blade'),\n hit(7362361, 1024, 1280, 'woman-cosplay-crown-kokoshnik-7362361', 'woman, cosplay, crown, kokoshnik, witch, svarog, magician, scull, horns, damage, shrew, sorceress, enchantress, story, magic, fantasy, woman, cosplay, cosplay, cosplay'),\n hit(4351441, 853, 1280, 'human-trees-foliage-house-4351441', 'human, trees, foliage, house, pterodactyl, light, man, tree, hike, nature, journey, story, fantasy, photoshop, amazing'),\n hit(5381815, 851, 1280, 'elf-ears-story-magic-fairy-5381815', 'elf, ears, story, magic, fairy, fantasy, elves, pipe, music, tolkien, cosplay, young woman, woman, figure, the flute, elf, fairy, elves, elves, elves'),\n hit(4439625, 855, 1280, 'lighthouse-crystal-girl-ball-moon-4439625', 'lighthouse, crystal, girl, ball, moon, light, sparks, bokeh sky, clouds, fantasy, nature, photoshop, steam, lighting, story'),\n hit(7613624, 853, 1280, 'elf-elvish-fantasy-young-woman-7613624', 'elf, elvish, fantasy, young woman, ears, story, magic, folklore, spirit of the forest, character, cosplay, image, mythology, outfit, tolkien, lord of the rings, elves, elf, magic, folklore'),\n hit(7040028, 921, 1280, 'woman-fantasy-story-fiction-girl-7040028', 'woman, fantasy, story, fiction, girl, young, beauty, white dress, lamp, nature, light, forest, stairs, mushroom, magic, fairytale, book cover'),\n hit(4440401, 853, 1280, 'young-couple-love-story-courtship-4440401', 'young couple, love story, courtship, young, hug, young woman, beauty, portrait, model, person, hair, femininity, fashion, embrace'),\n hit(5283548, 881, 1280, 'angel-wings-fantasy-horns-sukub-5283548', 'angel, wings, fantasy, horns, sukub, fallen angel, story, wing, young woman, mystical, mystic, magic, dream, mysterious, mood, fairy tales'),\n hit(7613621, 853, 1280, 'elf-elvish-fantasy-young-woman-7613621', 'elf, elvish, fantasy, young woman, ears, story, magic, folklore, spirit of the forest, character, cosplay, image, mythology, outfit, tolkien, lord of the rings, elves'),\n hit(8533939, 832, 1280, 'woman-fashion-beauty-eastern-8533939', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, exotic, decorations, long hair, dancer, hands, gracefully, portrait, model, pose'),\n hit(5532334, 853, 1280, 'mysterious-mystery-thriller-face-5532334', 'mysterious, mystery, thriller, face, dark, shadows, serious, male, dirt, brave, hero, warrior, soldier, headshot, man, black, white, eyes, story, telling'),\n hit(3877719, 850, 1280, 'autumn-girl-the-apprentice-backpack-3877719', 'autumn, girl, the apprentice, backpack, young woman, model, leaves, nature, story, the park, baby, childhood, backpack, backpack, backpack, backpack, backpack'),\n hit(5381802, 851, 1280, 'elf-ears-story-magic-fairy-5381802', 'elf, ears, story, magic, fairy, fantasy, elves, pipe, music, tolkien, cosplay, young woman, woman, figure'),\n hit(4304019, 854, 1280, 'fantasy-gothic-dark-dream-portrait-4304019', 'fantasy, gothic, dark, dream, portrait, fantasy portrait, female, girl, model, night, lamp, candle, wood, mystery, mysterious, ghost story, atmosphere, composing, book cover, photoshop'),\n hit(4273436, 848, 1280, 'horse-horses-forest-red-hood-4273436', 'horse, horses, forest, red hood, little red riding hood, fairy tale, story teller, fantasy, nature, girl, adventure, woods, enchanted, green forest, green fantasy'),\n hit(8533943, 848, 1280, 'woman-fashion-beauty-eastern-8533943', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose'),\n hit(5016422, 853, 1280, 'winter-young-woman-model-portrait-5016422', 'winter, young woman, model, nature, portrait, snow, story, smile, holiday, january, cold, woman, forest, christmas tree, cosiness'),\n hit(8533944, 796, 1280, 'woman-fashion-beauty-eastern-8533944', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose, dagger, weapon, blade'),\n hit(8533947, 834, 1280, 'woman-fashion-beauty-eastern-8533947', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose, dagger, weapons, blade'),\n hit(6858089, 851, 1280, 'queen-crown-monarch-cosplay-6858089', 'queen, crown, monarch, cosplay, fantasy, throne, power, woman, image, jewelry, decorations, story, fairytale character, young woman, portrait, drapery, kingdom'),\n hit(8533938, 802, 1280, 'woman-beauty-fashion-eastern-8533938', 'woman, beauty, fashion, eastern, gorgeous, arabic, scheherazade, story, cosplay, thousand and one nights, exotic, decorations, long hair, dancer, graceful'),\n hit(4440380, 853, 1280, 'young-couple-love-story-courtship-4440380', 'young couple, love story, courtship, young, hug, young woman, beauty, portrait, model, person, hair, femininity, fashion, dress, birch'),\n hit(5283555, 853, 1280, 'portrait-angel-wings-fantasy-horns-5283555', 'portrait, angel, wings, fantasy, horns, sukub, fallen angel, story, wing, young woman, mystical, mystic, magic'),\n]\n\n/** `freelencing` (vertical, min_height 900, category people), the same picture; key `047c7c65b06bb5d1d8172b67951b37937adc34e7f86ae2cf133f2039107067ef`, answered 2026-10-18T19:46:33.166Z. */\nexport const FREELENCING_TALL_PEOPLE: StockPhoto[] = []\n\n/** `freelencing` (vertical, min_height 900), the same picture; key `aeb056269f4c83fff44bc71068a085a67f501a4639625370b0045f8675fc5a16`, answered 2026-10-18T19:46:33.335Z. */\nexport const FREELENCING_TALL: StockPhoto[] = []\n\n/** `story` (vertical, min_height 900, category people), the same picture; key `b260f8d7f9c430236d7443b0cecdbc0a613f5b8ae7c7cfd0c6c54ab0900e5075`, answered 2026-10-18T19:46:33.653Z. */\nexport const STORY_PEOPLE: StockPhoto[] = [\n hit(6840320, 894, 1280, 'girl-magic-space-architecture-6840320', 'girl, magic, space, architecture, digital art, art, story'),\n hit(3715695, 1081, 1280, 'daughter-red-rain-girl-fashion-3715695', 'daughter, red, rain, girl, fashion, nature, story'),\n hit(2974363, 857, 1280, 'prince-boy-kids-magic-crown-2974363', 'prince, boy, kids, magic, crown, a cute baby, story, elf, portrait, sight, prince, prince, prince, prince, prince'),\n hit(4367341, 853, 1280, 'princess-historical-fairy-tale-4367341', 'princess, historical, fairy tale, fantasy, fairy, crown, magic, conjure up, female, woman, portrait, mystical, story, princess, princess, princess, princess, princess, crown, crown'),\n hit(5283552, 850, 1280, 'angel-wings-fantasy-horns-sukub-5283552', 'angel, wings, fantasy, horns, sukub, fallen angel, story, wing, young woman, mystical, mystic, magic, daemon, good'),\n hit(7362364, 837, 1280, 'woman-cosplay-crown-kokoshnik-7362364', 'woman, cosplay, crown, kokoshnik, witch, svarog, magician, scull, horns, damage, shrew, sorceress, enchantress, story, magic, fantasy, cosplay, crown, crown, crown'),\n hit(7755278, 837, 1280, 'madder-kokoshnik-woman-story-7755278', 'madder, kokoshnik, woman, story, mystic, fantasy, mythology, goddess, slavic, character, nature, costume, image, rite, crown, cosplay, goddess of death'),\n hit(5344381, 855, 1280, 'young-woman-forest-story-beauty-5344381', 'young woman, forest, story, beauty, nature, mood'),\n hit(4747076, 1024, 1280, 'young-woman-forest-deer-fantasy-4747076', 'young woman, forest, deer, fantasy, story, photoshop, nature, photo montage, surreal, fantastic, smoke, mystic, magic, sveta, dream'),\n hit(7362362, 853, 1280, 'woman-cosplay-crown-kokoshnik-7362362', 'woman, cosplay, crown, kokoshnik, witch, svarog, magician, scull, horns, damage, shrew, sorceress, enchantress, story, magic, fantasy, cosplay, crown, witch, witch'),\n hit(3172580, 1096, 1280, 'stone-door-old-wall-arch-3172580', 'stone, door, old, wall, arch, young woman, magic, story, fantasy, art, illustration, middle ages, magic, illustration, illustration, illustration, illustration, illustration'),\n hit(7359398, 876, 1280, 'woman-book-read-reading-study-7359398', 'woman, book, read, reading, study, girl, hobby, novel, bench, story, literature, reader, leisure, rocker'),\n hit(5344382, 855, 1280, 'young-woman-forest-story-trees-5344382', 'young woman, forest, story, nature, trees, beauty, mood, landscape'),\n hit(5344383, 855, 1280, 'young-woman-forest-light-story-5344383', 'young woman, forest, nature, light, story, trees, mood'),\n hit(6857724, 851, 1280, 'elf-cosplay-fantasy-the-enchantress-6857724', 'elf, cosplay, fantasy, the enchantress, woman, portrait, elves, image, magic, enchantress, ears, gems, sparkle, story, fairytale character, young woman, nimbus, holy, cosplay, cosplay'),\n hit(8533942, 834, 1280, 'woman-fashion-beauty-eastern-8533942', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose, dagger, weapon, blade'),\n hit(3172588, 1141, 1280, 'people-art-adult-young-woman-magic-3172588', 'people, art, adult, young woman, magic, story, fantasy, illustration, middle ages, woman'),\n hit(7362361, 1024, 1280, 'woman-cosplay-crown-kokoshnik-7362361', 'woman, cosplay, crown, kokoshnik, witch, svarog, magician, scull, horns, damage, shrew, sorceress, enchantress, story, magic, fantasy, woman, cosplay, cosplay, cosplay'),\n hit(4351441, 853, 1280, 'human-trees-foliage-house-4351441', 'human, trees, foliage, house, pterodactyl, light, man, tree, hike, nature, journey, story, fantasy, photoshop, amazing'),\n hit(7040028, 921, 1280, 'woman-fantasy-story-fiction-girl-7040028', 'woman, fantasy, story, fiction, girl, young, beauty, white dress, lamp, nature, light, forest, stairs, mushroom, magic, fairytale, book cover'),\n hit(7613624, 853, 1280, 'elf-elvish-fantasy-young-woman-7613624', 'elf, elvish, fantasy, young woman, ears, story, magic, folklore, spirit of the forest, character, cosplay, image, mythology, outfit, tolkien, lord of the rings, elves, elf, magic, folklore'),\n hit(5381815, 851, 1280, 'elf-ears-story-magic-fairy-5381815', 'elf, ears, story, magic, fairy, fantasy, elves, pipe, music, tolkien, cosplay, young woman, woman, figure, the flute, elf, fairy, elves, elves, elves'),\n hit(4439625, 855, 1280, 'lighthouse-crystal-girl-ball-moon-4439625', 'lighthouse, crystal, girl, ball, moon, light, sparks, bokeh sky, clouds, fantasy, nature, photoshop, steam, lighting, story'),\n hit(4440401, 853, 1280, 'young-couple-love-story-courtship-4440401', 'young couple, love story, courtship, young, hug, young woman, beauty, portrait, model, person, hair, femininity, fashion, embrace'),\n hit(7613621, 853, 1280, 'elf-elvish-fantasy-young-woman-7613621', 'elf, elvish, fantasy, young woman, ears, story, magic, folklore, spirit of the forest, character, cosplay, image, mythology, outfit, tolkien, lord of the rings, elves'),\n hit(5283548, 881, 1280, 'angel-wings-fantasy-horns-sukub-5283548', 'angel, wings, fantasy, horns, sukub, fallen angel, story, wing, young woman, mystical, mystic, magic, dream, mysterious, mood, fairy tales'),\n hit(8533939, 832, 1280, 'woman-fashion-beauty-eastern-8533939', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, exotic, decorations, long hair, dancer, hands, gracefully, portrait, model, pose'),\n hit(5532334, 853, 1280, 'mysterious-mystery-thriller-face-5532334', 'mysterious, mystery, thriller, face, dark, shadows, serious, male, dirt, brave, hero, warrior, soldier, headshot, man, black, white, eyes, story, telling'),\n hit(3877719, 850, 1280, 'autumn-girl-the-apprentice-backpack-3877719', 'autumn, girl, the apprentice, backpack, young woman, model, leaves, nature, story, the park, baby, childhood, backpack, backpack, backpack, backpack, backpack'),\n hit(5381802, 851, 1280, 'elf-ears-story-magic-fairy-5381802', 'elf, ears, story, magic, fairy, fantasy, elves, pipe, music, tolkien, cosplay, young woman, woman, figure'),\n hit(4304019, 854, 1280, 'fantasy-gothic-dark-dream-portrait-4304019', 'fantasy, gothic, dark, dream, portrait, fantasy portrait, female, girl, model, night, lamp, candle, wood, mystery, mysterious, ghost story, atmosphere, composing, book cover, photoshop'),\n hit(4273436, 848, 1280, 'horse-horses-forest-red-hood-4273436', 'horse, horses, forest, red hood, little red riding hood, fairy tale, story teller, fantasy, nature, girl, adventure, woods, enchanted, green forest, green fantasy'),\n hit(5016422, 853, 1280, 'winter-young-woman-model-portrait-5016422', 'winter, young woman, model, nature, portrait, snow, story, smile, holiday, january, cold, woman, forest, christmas tree, cosiness'),\n hit(8533943, 848, 1280, 'woman-fashion-beauty-eastern-8533943', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose'),\n hit(8533944, 796, 1280, 'woman-fashion-beauty-eastern-8533944', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose, dagger, weapon, blade'),\n hit(8533947, 834, 1280, 'woman-fashion-beauty-eastern-8533947', 'woman, fashion, beauty, eastern, gorgeous, arabic, scheherazade, story, cosplay, decorations, dancer, hands, gracefully, portrait, model, pose, dagger, weapons, blade'),\n hit(8533938, 802, 1280, 'woman-beauty-fashion-eastern-8533938', 'woman, beauty, fashion, eastern, gorgeous, arabic, scheherazade, story, cosplay, thousand and one nights, exotic, decorations, long hair, dancer, graceful'),\n hit(6858089, 851, 1280, 'queen-crown-monarch-cosplay-6858089', 'queen, crown, monarch, cosplay, fantasy, throne, power, woman, image, jewelry, decorations, story, fairytale character, young woman, portrait, drapery, kingdom'),\n hit(5283555, 853, 1280, 'portrait-angel-wings-fantasy-horns-5283555', 'portrait, angel, wings, fantasy, horns, sukub, fallen angel, story, wing, young woman, mystical, mystic, magic'),\n hit(4440380, 853, 1280, 'young-couple-love-story-courtship-4440380', 'young couple, love story, courtship, young, hug, young woman, beauty, portrait, model, person, hair, femininity, fashion, dress, birch'),\n]\n\n/** `small business owner` (vertical, min_height 900), the same picture, widened to the business kind; key `9ee171cee5bff5e5b565e69074a57764babd6283a1da648807f5085444ba8ab2`, answered 2026-10-18T19:46:34.056Z. */\nexport const SMALL_BUSINESS_OWNER_TALL: StockPhoto[] = [\n hit(992643, 850, 1280, 'working-girl-working-woman-young-992643', 'working, girl, working woman, young, people, female, woman, work, happy, person, business, adult, lifestyle, home, woman on computer, woman computer, lady, working, working, working'),\n hit(5964181, 853, 1280, 'dog-girl-love-pet-owner-companion-5964181', 'dog, girl, love, pet, owner, companion, nature, friends, friendship, happy, animal, akita, portrait, pet owner'),\n hit(7308033, 854, 1280, 'woman-dog-walk-pooch-dog-leash-7308033', 'woman, animal, dog, pet, walk, pooch, dog leash, domestic animal'),\n hit(6401957, 854, 1280, 'woman-model-portrait-pose-dog-pet-6401957', 'woman, model, portrait, pose, nature, dog, pet, animal, style, fashion, posing, young woman, girl, modeling, female, woman portrait'),\n hit(7862413, 853, 1280, 'woman-shopping-market-lifestyle-7862413', 'woman, shopping market, lifestyle, marketplace, people, people, people, people, people, people'),\n hit(7172131, 853, 1280, 'ferret-pet-animal-mustelidae-owner-7172131', 'ferret, pet, nature, animal, mustelidae, owner, hug, domestic, closeup'),\n hit(2586356, 851, 1280, 'people-girl-woman-dog-puppy-pet-2586356', 'people, girl, woman, nature, dog, puppy, pet, animal'),\n hit(6032824, 853, 1280, 'woman-dog-playing-playful-bonding-6032824', 'woman, dog, nature, playing, playful, bonding, happiness, pet, animal, girl, pet owner, leisure, fun, happy'),\n hit(2517682, 853, 1280, 'pet-dog-puppy-animal-dog-stare-2517682', 'pet dog, puppy, nature, animal, dog, pet, stare, park, walk, consensus, emotion, snow, gladness, pets, cute'),\n hit(9248858, 851, 1280, 'teen-boy-portrait-cavy-pet-9248858', 'teen, boy, portrait, cavy, pet, darling, master, owner'),\n hit(9248855, 851, 1280, 'teen-boy-portrait-cavy-pet-9248855', 'teen, boy, portrait, cavy, pet, darling, master, owner'),\n hit(8032746, 854, 1280, 'open-sign-signage-window-store-8032746', 'open, sign, signage, window, store, business, small business, green, business, small business, small business, small business, small business, small business'),\n hit(9971558, 853, 1280, 'nature-wildlife-mammal-fauna-fur-9971558', 'nature, wildlife, mammal, fauna, fur, animals, mouse, rodent, cute, mice, harvest mice, seedhead, plant, wheat'),\n hit(8702640, 853, 1280, 'man-dog-pet-animal-canine-fur-8702640', 'man, nature, dog, pet, animal, canine, fur, sitting, owner, boy, tattoo, outdoor, terrace, restaurant, lifestyle, relaxation'),\n hit(8013214, 854, 1280, 'hummingbird-bird-nature-wallpaper-8013214', 'hummingbird, samsung wallpaper, bird, iphone wallpaper, nature, lock screen wallpaper, wallpaper, wildlife, phone wallpaper, mobile wallpaper 4k, ipad wallpaper, beautiful wallpaper, mobile wallpaper, animal'),\n hit(9846388, 853, 1280, 'fennec-fox-fennec-fox-africa-9846388', 'fennec fox, fennec, fox, africa, animal, wildlife, nature, mammal'),\n hit(8781844, 853, 1280, 'dog-puppy-pooch-domestic-animal-8781844', 'dog, puppy, pooch, domestic animal, animal, pet, cute, nature'),\n hit(4051412, 853, 1280, 'bird-perched-animal-feathers-4051412', 'bird, perched, animal, feathers, plumage, beak, bill, bird watching, ornithology, animal world, nature, bird, bird, bird, bird, bird'),\n hit(8496039, 853, 1280, 'wren-bird-animal-nature-wildlife-8496039', 'wren, bird, animal, nature, wildlife, songbird, ornithology, bird watching, bird, bird, bird, bird, bird, animal, nature'),\n hit(6027672, 853, 1280, 'man-dog-drink-drinking-6027672', 'man, dog, drink, drinking, drinking water, pet, owner, canine, landscape, desert, nature, travel, traveling, sand, animal, refreshment'),\n hit(1281284, 1052, 1280, 'elder-man-dogs-puppy-man-senior-1281284', 'elder man, dogs, puppy, man, senior, nature, old, elderly, retired, people, pet, person, animal, love, aged, older, care, happy, calm, human'),\n hit(9830647, 958, 1280, 'small-copper-butterfly-insect-9830647', 'small copper, butterfly, insect, nature, pollinator, entomology, pollinate, lepidoptera'),\n hit(10021805, 939, 1280, 'brown-breasted-nun-gorgeous-finch-10021805', 'brown-breasted nun, gorgeous finch, songbird, sperling, bronze men, bird, sparrow bird, bird watching, plumage, nature, animal'),\n hit(8024809, 853, 1280, 'blue-tit-nuthatch-bird-animal-8024809', 'blue tit, nuthatch, bird, animal, wildlife, plumage, branch, perched, nature, birdwatching, wildlife photography, nature photography, bird photography, nature, nature, nature, nature, nature'),\n hit(10260310, 853, 1280, 'kitten-feline-pet-portrait-fur-10260310', 'kitten, feline, pet, portrait, fur, mackerel, tabby, charming, woman, glasses, closeness, cozy, coat, young, domestic animal, cat, cuddly, contact, animal, home'),\n hit(9617024, 854, 1280, 'sparrow-branch-springtime-nature-9617024', 'sparrow, mobile wallpaper 4k, branch, springtime, iphone wallpaper, lock screen wallpaper, nature, birdwatching, soft colors, cherry blossom, ipad wallpaper, wildlife photography, mobile wallpaper, tranquility, beautiful wallpaper, phone wallpaper, small birds, urban nature, avian, wallpaper'),\n hit(4997202, 960, 1280, 'if-you-ski-super-ski-pet-dog-puppy-4997202', 'if you, ski super ski, pet dog, puppy, pet, dog, consensus, walk, cute, animal, emotion, park, jindo dog, new man, laugh, preciousness, dang stuck, nature, happiness, pets'),\n hit(3824672, 877, 1280, 'animal-baby-bird-born-child-cute-3824672', 'animal, baby, bird, born, child, cute, duck, duckling, ducklings, ducky, farm, feathers, fluff, fluffy, fun, funny, grass, green, little, nature'),\n hit(10022157, 853, 1280, 'eurasian-wren-bird-bird-photography-10022157', 'eurasian wren, bird, bird photography, ornithology, spring, songbird, hd background, garden, bird watching, animal, wildlife, feathered animal, forest, nature, chirp, tweet, chirrup, forest-aged tree'),\n hit(9286306, 853, 1280, 'animal-shih-tzu-dog-puppy-cute-9286306', 'animal, shih tzu, dog, puppy, cute, mammal, fur, grass, snout, small, pet, home, race, portrait, nature, fluffy, companion'),\n hit(9643209, 853, 1280, 'cat-kitten-animal-feline-kitty-9643209', 'cat, kitten, animal, feline, kitty, pet, cute, small, nature, whiskers'),\n hit(1551708, 848, 1280, 'leisure-time-leisure-activities-1551708', 'leisure time, leisure activities, dog photography, dog, dog runs, animal, nature, fun, dog human, dog with human, comrade, plays, action, dog plays, dogs portrait, portrait, man dog, dog with man, forest, walk'),\n hit(8666099, 853, 1280, 'bird-winged-pen-animal-fauna-wild-8666099', 'bird, winged, pen, animal, fauna, wild, nature, little bird, small bird, sparrow-like, sparrow bird, gray, bird, bird, bird, bird, bird'),\n hit(817371, 888, 1280, 'child-running-children-girl-happy-817371', 'child, running, children, girl, happy, people, nature, female, kids, person, happiness, summer, outdoors, pet'),\n hit(10180402, 853, 1280, 'frog-tree-frog-small-frog-amphibian-10180402', 'frog, tree frog, small frog, amphibian, tropical frog, terrarium, frog on branch, wildlife, animal close-up, amphibian skin, exotic animal, zoo animal, nature detail, macro wildlife, frog portrait, rainforest animal, small animal, indoor habitat, green leaves, camouflage'),\n hit(4912213, 853, 1280, 'cat-kitten-british-pet-kitty-4912213', 'cat, kitten, british, pet, kitty, animal, feline, sweet, fur, domestic, nature, young, adorable, playful, small, funny, furry, pets, portrait, curious'),\n hit(4906211, 857, 1280, 'shaoxing-watertown-small-river-4906211', 'shaoxing, watertown, small river, scenery, small town, shaoxing, shaoxing, shaoxing, shaoxing, shaoxing'),\n hit(1851108, 904, 1280, 'terrier-dog-puppy-animal-breed-1851108', 'terrier, dog, puppy, animal, nature, breed, canine, adorable, cute, domestic, fur, grass, little, mammal, pedigree, pet, portrait, purebred, furry'),\n hit(9432600, 1097, 1280, 'bird-animal-wildlife-avian-9432600', 'bird, animal, wildlife, avian, nature, hyderabad, india, zoo'),\n hit(4906607, 853, 1280, 'veterinarian-examination-dog-4906607', 'veterinarian, examination dog, ambulance, clinic, pets, dog, health, nature, prevention, animal, diagnosis, dog diseases, vaccination, care, pet, treatment, petcare'),\n]\n"],"names":["hit","id","width","height","slug","tags","extension","provider","String","pageUrl","photographer","split","downloadUrl","FREELENCING_HERO","SMALL_BUSINESS_OWNER_HERO","FREELENCING_STORY_PEOPLE","FREELENCING_TALL_PEOPLE","FREELENCING_TALL","STORY_PEOPLE","SMALL_BUSINESS_OWNER_TALL"],"mappings":"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"}
|
|
@@ -194,6 +194,7 @@ const DISHES = /\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|p
|
|
|
194
194
|
// A store's own products are listed by its Product grid (AGL-3676), never drawn
|
|
195
195
|
// from words: what a store shows as pictures is how its range is grouped — its
|
|
196
196
|
// collections, gift sets, occasions and scents. "Candle care and burn notes" is no band of pictures.
|
|
197
|
+
/** Words a section uses for itself when it is a showing of pictures, on any kind of site (AGL-3923). */ const GALLERY = /\b(gallery|galleries|portfolio|portfolios|lookbook|lookbooks)\b/i;
|
|
197
198
|
const CATEGORIES = /\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\b/i;
|
|
198
199
|
/**
|
|
199
200
|
* How a section's group of cards is drawn, from what the section is about:
|
|
@@ -206,6 +207,10 @@ const CATEGORIES = /\b(collection|collections|range|ranges|categor(?:y|ies)|gift
|
|
|
206
207
|
if (family === 'editorial' && WRITING.test(words)) return 'articles';
|
|
207
208
|
if (family === 'hospitality' && DISHES.test(words)) return 'menu';
|
|
208
209
|
if (family === 'retail' && CATEGORIES.test(words)) return 'pictures';
|
|
210
|
+
// A section that calls itself a gallery or a portfolio shows pictures on any
|
|
211
|
+
// kind of site (AGL-3923): the beta.265 Kasvora Marketing Solutions home drew
|
|
212
|
+
// its "Selected work gallery" as four cards of icons.
|
|
213
|
+
if (GALLERY.test(words)) return 'pictures';
|
|
209
214
|
return choices.group;
|
|
210
215
|
}
|
|
211
216
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out);\n * - `icons`: open columns, each led by an icon in a soft disc over its title\n * and words — a storefront's reasons to buy (shipping, small batch,\n * returns), never a block of text (AGL-3676).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu' | 'icons'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled' | 'icons'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n /**\n * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it, each drawn at even odds\n * among the options that suit the kind (AGL-3899), so two sites of one kind\n * are drawn apart. Favoring each list's first option made the newest\n * production sites one design: the kind's first hero won 55% before the\n * pick, a business site opened split, groups were ruled two times in three\n * and every text section took a photo.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = pick(next, heroes)\n const group = pick(next, ['cards', 'ruled', 'icons'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open columns each led by an icon, the way a storefront theme draws\n // them: never a SaaS page's feature cards, and never the plain columns\n // of a title and a sentence the beta.237 Willow Wick home showed four\n // bands of (AGL-3676).\n group: family === 'retail' ? 'icons' : group,\n steps: pick(next, ['timeline', 'numbers'] as const),\n // Whether a section of words takes a photo beside it: some sites are\n // photo-led, some let their type carry the page. A storefront never\n // shows a band of text alone (AGL-3676), so its story always takes one.\n features: next() < 0.6 || family === 'retail',\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && CATEGORIES.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","writing","WORK","WRITING","DISHES","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out);\n * - `icons`: open columns, each led by an icon in a soft disc over its title\n * and words — a storefront's reasons to buy (shipping, small batch,\n * returns), never a block of text (AGL-3676).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu' | 'icons'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled' | 'icons'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n /**\n * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it, each drawn at even odds\n * among the options that suit the kind (AGL-3899), so two sites of one kind\n * are drawn apart. Favoring each list's first option made the newest\n * production sites one design: the kind's first hero won 55% before the\n * pick, a business site opened split, groups were ruled two times in three\n * and every text section took a photo.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = pick(next, heroes)\n const group = pick(next, ['cards', 'ruled', 'icons'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open columns each led by an icon, the way a storefront theme draws\n // them: never a SaaS page's feature cards, and never the plain columns\n // of a title and a sentence the beta.237 Willow Wick home showed four\n // bands of (AGL-3676).\n group: family === 'retail' ? 'icons' : group,\n steps: pick(next, ['timeline', 'numbers'] as const),\n // Whether a section of words takes a photo beside it: some sites are\n // photo-led, some let their type carry the page. A storefront never\n // shows a band of text alone (AGL-3676), so its story always takes one.\n features: next() < 0.6 || family === 'retail',\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\n/** Words a section uses for itself when it is a showing of pictures, on any kind of site (AGL-3923). */\nconst GALLERY = /\\b(gallery|galleries|portfolio|portfolios|lookbook|lookbooks)\\b/i\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && CATEGORIES.test(words)) return 'pictures'\n // A section that calls itself a gallery or a portfolio shows pictures on any\n // kind of site (AGL-3923): the beta.265 Kasvora Marketing Solutions home drew\n // its \"Selected work gallery\" as four cards of icons.\n if (GALLERY.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","writing","WORK","WRITING","DISHES","GALLERY","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
|
|
@@ -492,6 +492,20 @@ export interface AiJob {
|
|
|
492
492
|
* `canceled` at its next write. Absent on a job nobody canceled.
|
|
493
493
|
*/
|
|
494
494
|
cancelRequested?: AiJobCancelRequest | null;
|
|
495
|
+
/**
|
|
496
|
+
* The empty site this guided start was for was removed (AGL-3936): the
|
|
497
|
+
* job was cancelled or failed before it built anything, nobody chose what
|
|
498
|
+
* to do next within the grace period, and the AI jobs beat deleted the
|
|
499
|
+
* site. Written once, by the beat, after the site is gone. Absent on every
|
|
500
|
+
* other job.
|
|
501
|
+
*/
|
|
502
|
+
siteRemoved?: AiJobSiteRemoved | null;
|
|
503
|
+
}
|
|
504
|
+
/** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */
|
|
505
|
+
export interface AiJobSiteRemoved {
|
|
506
|
+
at: ITimestamp;
|
|
507
|
+
/** The site's name as it was, for a sentence that names it. */
|
|
508
|
+
name: string | null;
|
|
495
509
|
}
|
|
496
510
|
/** Who asked to cancel a job, and when (AGL-3616). */
|
|
497
511
|
export interface AiJobCancelRequest {
|
|
@@ -619,4 +633,10 @@ export interface AiJobSummary {
|
|
|
619
633
|
* the job is terminal, and on every job nobody canceled.
|
|
620
634
|
*/
|
|
621
635
|
cancelRequested?: boolean;
|
|
636
|
+
/**
|
|
637
|
+
* The empty site this guided start was for was removed (AGL-3936): its
|
|
638
|
+
* page is gone, and Start again opens Create site with the same answers.
|
|
639
|
+
* Absent on every other job.
|
|
640
|
+
*/
|
|
641
|
+
siteRemoved?: boolean;
|
|
622
642
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\nimport type { AiFreeCreditsLeft } from './ai-site-job'\nimport type { AiJobCreditsConfirmation } from './ai-credit-estimate'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n /** A change to a site's header or footer menu, made by code (AGL-3728). */\n | 'navigation'\n /** A copy of something a site already has, made by core's duplicate (AGL-3740). */\n | 'duplicate'\n /** A change to a site's settings, made by code and saved live (AGL-3748). */\n | 'settings'\n /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */\n | 'media'\n /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */\n | 'tracking'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n 'navigation',\n 'duplicate',\n 'settings',\n 'media',\n 'tracking',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n /** A site's settings a build changed (AGL-3748): saved live, on the host document. */\n | 'settings'\n /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */\n | 'media'\n /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */\n | 'tracking'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n /**\n * What a Free workspace had left when a build's plan was kept (AGL-3722),\n * so its plan card says, before Confirm, whether the build fits and offers\n * the go-ahead or a smaller first build when it does not. The confirm door\n * reads what is left again; this is what the card shows.\n */\n freeCredits?: AiFreeCreditsLeft\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n /**\n * A Free job started past what was left (AGL-3722): the person chose to\n * build what fits, knowing it pauses where the credits run out. Who, when,\n * what was left and the range they were shown; absent on every job that fit.\n */\n creditsConfirmed?: AiJobCreditsConfirmation | null\n /**\n * A person asked to stop the job (AGL-3616): written only by the cancel\n * door, in the transaction that decides it. A job no step holds ends\n * `canceled` in that same write; a job whose step holds a live lease keeps\n * running until the step finishes or aborts, and the machine ends it\n * `canceled` at its next write. Absent on a job nobody canceled.\n */\n cancelRequested?: AiJobCancelRequest | null\n}\n\n/** Who asked to cancel a job, and when (AGL-3616). */\nexport interface AiJobCancelRequest {\n at: ITimestamp\n /** The member or staff account that asked; `null` when nobody was named. */\n by: string | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n /**\n * A cancel was asked and the job has not ended yet (AGL-3616): the step in\n * flight is finishing or aborting, and nothing after it runs. Absent once\n * the job is terminal, and on every job nobody canceled.\n */\n cancelRequested?: boolean\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GA6ED,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAoBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\nimport type { AiFreeCreditsLeft } from './ai-site-job'\nimport type { AiJobCreditsConfirmation } from './ai-credit-estimate'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n /** A change to a site's header or footer menu, made by code (AGL-3728). */\n | 'navigation'\n /** A copy of something a site already has, made by core's duplicate (AGL-3740). */\n | 'duplicate'\n /** A change to a site's settings, made by code and saved live (AGL-3748). */\n | 'settings'\n /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */\n | 'media'\n /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */\n | 'tracking'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n 'navigation',\n 'duplicate',\n 'settings',\n 'media',\n 'tracking',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n /** A site's settings a build changed (AGL-3748): saved live, on the host document. */\n | 'settings'\n /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */\n | 'media'\n /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */\n | 'tracking'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n /**\n * What a Free workspace had left when a build's plan was kept (AGL-3722),\n * so its plan card says, before Confirm, whether the build fits and offers\n * the go-ahead or a smaller first build when it does not. The confirm door\n * reads what is left again; this is what the card shows.\n */\n freeCredits?: AiFreeCreditsLeft\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n /**\n * A Free job started past what was left (AGL-3722): the person chose to\n * build what fits, knowing it pauses where the credits run out. Who, when,\n * what was left and the range they were shown; absent on every job that fit.\n */\n creditsConfirmed?: AiJobCreditsConfirmation | null\n /**\n * A person asked to stop the job (AGL-3616): written only by the cancel\n * door, in the transaction that decides it. A job no step holds ends\n * `canceled` in that same write; a job whose step holds a live lease keeps\n * running until the step finishes or aborts, and the machine ends it\n * `canceled` at its next write. Absent on a job nobody canceled.\n */\n cancelRequested?: AiJobCancelRequest | null\n /**\n * The empty site this guided start was for was removed (AGL-3936): the\n * job was cancelled or failed before it built anything, nobody chose what\n * to do next within the grace period, and the AI jobs beat deleted the\n * site. Written once, by the beat, after the site is gone. Absent on every\n * other job.\n */\n siteRemoved?: AiJobSiteRemoved | null\n}\n\n/** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */\nexport interface AiJobSiteRemoved {\n at: ITimestamp\n /** The site's name as it was, for a sentence that names it. */\n name: string | null\n}\n\n/** Who asked to cancel a job, and when (AGL-3616). */\nexport interface AiJobCancelRequest {\n at: ITimestamp\n /** The member or staff account that asked; `null` when nobody was named. */\n by: string | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n /**\n * A cancel was asked and the job has not ended yet (AGL-3616): the step in\n * flight is finishing or aborting, and nothing after it runs. Absent once\n * the job is terminal, and on every job nobody canceled.\n */\n cancelRequested?: boolean\n /**\n * The empty site this guided start was for was removed (AGL-3936): its\n * page is gone, and Start again opens Create site with the same answers.\n * Absent on every other job.\n */\n siteRemoved?: boolean\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GA6ED,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAoBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type AiSiteStartAnswers } from './ai-site-start';
|
|
18
|
+
/**
|
|
19
|
+
* START AGAIN AFTER THE EMPTY SITE WAS REMOVED (AGL-3936).
|
|
20
|
+
*
|
|
21
|
+
* A guided start cancelled or failed before it built anything, and left for
|
|
22
|
+
* an hour, has its empty site removed by the AI jobs beat. Its job is kept,
|
|
23
|
+
* with its answers, and "Start again" then opens Create site — there is no
|
|
24
|
+
* site left to start on — with those answers filled in. The answers travel
|
|
25
|
+
* in this tab's session storage for the few seconds between the click and
|
|
26
|
+
* Create site's start drawing; nothing leaves the browser, and an answer
|
|
27
|
+
* older than {@link AI_SITE_START_HANDOFF_MS} is not used.
|
|
28
|
+
*/
|
|
29
|
+
/** What the job's own page and the AI jobs drawer say about a removed site. */
|
|
30
|
+
export declare const AI_SITE_REMOVED_HEADING = "This site was removed";
|
|
31
|
+
export declare const AI_SITE_REMOVED_COPY = "Nothing was built before this start stopped, so the empty site was removed. Start again to create a site with the same answers.";
|
|
32
|
+
export declare const AI_SITE_REMOVED_ROW_COPY = "Nothing was built, so the empty site was removed.";
|
|
33
|
+
export declare const AI_SITE_START_AGAIN_LABEL = "Start again";
|
|
34
|
+
/** How long the answers wait for Create site to open. */
|
|
35
|
+
export declare const AI_SITE_START_HANDOFF_MS: number;
|
|
36
|
+
/** Create site on the workspace's Sites page: `/{org}/hosts?create=site`. */
|
|
37
|
+
export declare function aiCreateSiteHref(orgSlug: string): string | null;
|
|
38
|
+
/** Keeps a start's answers (its job's `siteInputs`) for Create site to open on. */
|
|
39
|
+
export declare function stashAiSiteStartAnswers(inputs: Readonly<Record<string, unknown>> | null | undefined, now?: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* The answers a removed start left for Create site, or `null`. Read, not
|
|
42
|
+
* taken: Create site may draw its start more than once while it opens; the
|
|
43
|
+
* answers are dropped once a start is planned ({@link clearAiSiteStartAnswers}).
|
|
44
|
+
*/
|
|
45
|
+
export declare function readAiSiteStartAnswers(options?: {
|
|
46
|
+
freeTaste?: boolean;
|
|
47
|
+
}, now?: number): AiSiteStartAnswers | null;
|
|
48
|
+
/** Drops the answers once a start has been planned with them. */
|
|
49
|
+
export declare function clearAiSiteStartAnswers(): void;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
|
|
17
|
+
import { aiSiteStartAnswersFromInputs } from "./ai-site-start.js";
|
|
18
|
+
/**
|
|
19
|
+
* START AGAIN AFTER THE EMPTY SITE WAS REMOVED (AGL-3936).
|
|
20
|
+
*
|
|
21
|
+
* A guided start cancelled or failed before it built anything, and left for
|
|
22
|
+
* an hour, has its empty site removed by the AI jobs beat. Its job is kept,
|
|
23
|
+
* with its answers, and "Start again" then opens Create site — there is no
|
|
24
|
+
* site left to start on — with those answers filled in. The answers travel
|
|
25
|
+
* in this tab's session storage for the few seconds between the click and
|
|
26
|
+
* Create site's start drawing; nothing leaves the browser, and an answer
|
|
27
|
+
* older than {@link AI_SITE_START_HANDOFF_MS} is not used.
|
|
28
|
+
*/ /** What the job's own page and the AI jobs drawer say about a removed site. */ export const AI_SITE_REMOVED_HEADING = 'This site was removed';
|
|
29
|
+
export const AI_SITE_REMOVED_COPY = 'Nothing was built before this start stopped, so the empty site was removed. Start again to create a site with the same answers.';
|
|
30
|
+
export const AI_SITE_REMOVED_ROW_COPY = 'Nothing was built, so the empty site was removed.';
|
|
31
|
+
export const AI_SITE_START_AGAIN_LABEL = 'Start again';
|
|
32
|
+
/** How long the answers wait for Create site to open. */ export const AI_SITE_START_HANDOFF_MS = 10 * 60000;
|
|
33
|
+
const KEY = 'aglyn:ai-site-start:answers';
|
|
34
|
+
function storage() {
|
|
35
|
+
try {
|
|
36
|
+
return typeof window === 'undefined' ? null : window.sessionStorage;
|
|
37
|
+
} catch (unused) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Create site on the workspace's Sites page: `/{org}/hosts?create=site`. */ export function aiCreateSiteHref(orgSlug) {
|
|
42
|
+
return orgSlug ? `${buildRoute(Route.HOST_LIST, {
|
|
43
|
+
orgSlug
|
|
44
|
+
})}?create=site` : null;
|
|
45
|
+
}
|
|
46
|
+
/** Keeps a start's answers (its job's `siteInputs`) for Create site to open on. */ export function stashAiSiteStartAnswers(inputs, now = Date.now()) {
|
|
47
|
+
if (!inputs) return;
|
|
48
|
+
try {
|
|
49
|
+
var _storage;
|
|
50
|
+
(_storage = storage()) == null ? void 0 : _storage.setItem(KEY, JSON.stringify({
|
|
51
|
+
inputs,
|
|
52
|
+
at: now
|
|
53
|
+
}));
|
|
54
|
+
} catch (unused) {
|
|
55
|
+
// Create site opens on its blank questions instead.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The answers a removed start left for Create site, or `null`. Read, not
|
|
60
|
+
* taken: Create site may draw its start more than once while it opens; the
|
|
61
|
+
* answers are dropped once a start is planned ({@link clearAiSiteStartAnswers}).
|
|
62
|
+
*/ export function readAiSiteStartAnswers(options = {}, now = Date.now()) {
|
|
63
|
+
try {
|
|
64
|
+
var _parsed_inputs;
|
|
65
|
+
var _storage;
|
|
66
|
+
const raw = (_storage = storage()) == null ? void 0 : _storage.getItem(KEY);
|
|
67
|
+
if (!raw) return null;
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (typeof parsed.at !== 'number' || now - parsed.at > AI_SITE_START_HANDOFF_MS) {
|
|
70
|
+
var _storage1;
|
|
71
|
+
(_storage1 = storage()) == null ? void 0 : _storage1.removeItem(KEY);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return aiSiteStartAnswersFromInputs((_parsed_inputs = parsed.inputs) != null ? _parsed_inputs : null, options);
|
|
75
|
+
} catch (unused) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Drops the answers once a start has been planned with them. */ export function clearAiSiteStartAnswers() {
|
|
80
|
+
try {
|
|
81
|
+
var _storage;
|
|
82
|
+
(_storage = storage()) == null ? void 0 : _storage.removeItem(KEY);
|
|
83
|
+
} catch (unused) {
|
|
84
|
+
// Nothing to drop.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=ai-site-start-handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start-handoff.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { aiSiteStartAnswersFromInputs, type AiSiteStartAnswers } from './ai-site-start'\n\n/**\n * START AGAIN AFTER THE EMPTY SITE WAS REMOVED (AGL-3936).\n *\n * A guided start cancelled or failed before it built anything, and left for\n * an hour, has its empty site removed by the AI jobs beat. Its job is kept,\n * with its answers, and \"Start again\" then opens Create site — there is no\n * site left to start on — with those answers filled in. The answers travel\n * in this tab's session storage for the few seconds between the click and\n * Create site's start drawing; nothing leaves the browser, and an answer\n * older than {@link AI_SITE_START_HANDOFF_MS} is not used.\n */\n\n/** What the job's own page and the AI jobs drawer say about a removed site. */\nexport const AI_SITE_REMOVED_HEADING = 'This site was removed'\nexport const AI_SITE_REMOVED_COPY =\n 'Nothing was built before this start stopped, so the empty site was removed. Start again to create a site with the same answers.'\nexport const AI_SITE_REMOVED_ROW_COPY = 'Nothing was built, so the empty site was removed.'\nexport const AI_SITE_START_AGAIN_LABEL = 'Start again'\n\n/** How long the answers wait for Create site to open. */\nexport const AI_SITE_START_HANDOFF_MS = 10 * 60_000\n\nconst KEY = 'aglyn:ai-site-start:answers'\n\nfunction storage(): Storage | null {\n try {\n return typeof window === 'undefined' ? null : window.sessionStorage\n } catch {\n return null\n }\n}\n\n/** Create site on the workspace's Sites page: `/{org}/hosts?create=site`. */\nexport function aiCreateSiteHref(orgSlug: string): string | null {\n return orgSlug ? `${buildRoute(Route.HOST_LIST, { orgSlug })}?create=site` : null\n}\n\n/** Keeps a start's answers (its job's `siteInputs`) for Create site to open on. */\nexport function stashAiSiteStartAnswers(inputs: Readonly<Record<string, unknown>> | null | undefined, now = Date.now()): void {\n if (!inputs) return\n try {\n storage()?.setItem(KEY, JSON.stringify({ inputs, at: now }))\n } catch {\n // Create site opens on its blank questions instead.\n }\n}\n\n/**\n * The answers a removed start left for Create site, or `null`. Read, not\n * taken: Create site may draw its start more than once while it opens; the\n * answers are dropped once a start is planned ({@link clearAiSiteStartAnswers}).\n */\nexport function readAiSiteStartAnswers(options: { freeTaste?: boolean } = {}, now = Date.now()): AiSiteStartAnswers | null {\n try {\n const raw = storage()?.getItem(KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw) as { inputs?: Record<string, unknown>; at?: number }\n if (typeof parsed.at !== 'number' || now - parsed.at > AI_SITE_START_HANDOFF_MS) {\n storage()?.removeItem(KEY)\n return null\n }\n return aiSiteStartAnswersFromInputs(parsed.inputs ?? null, options)\n } catch {\n return null\n }\n}\n\n/** Drops the answers once a start has been planned with them. */\nexport function clearAiSiteStartAnswers(): void {\n try {\n storage()?.removeItem(KEY)\n } catch {\n // Nothing to drop.\n }\n}\n"],"names":["buildRoute","Route","aiSiteStartAnswersFromInputs","AI_SITE_REMOVED_HEADING","AI_SITE_REMOVED_COPY","AI_SITE_REMOVED_ROW_COPY","AI_SITE_START_AGAIN_LABEL","AI_SITE_START_HANDOFF_MS","KEY","storage","window","sessionStorage","aiCreateSiteHref","orgSlug","HOST_LIST","stashAiSiteStartAnswers","inputs","now","Date","setItem","JSON","stringify","at","readAiSiteStartAnswers","options","parsed","raw","getItem","parse","removeItem","clearAiSiteStartAnswers"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,4BAA4B,QAAiC,qBAAiB;AAEvF;;;;;;;;;;CAUC,GAED,6EAA6E,GAC7E,OAAO,MAAMC,0BAA0B,wBAAuB;AAC9D,OAAO,MAAMC,uBACX,kIAAiI;AACnI,OAAO,MAAMC,2BAA2B,oDAAmD;AAC3F,OAAO,MAAMC,4BAA4B,cAAa;AAEtD,uDAAuD,GACvD,OAAO,MAAMC,2BAA2B,KAAK,MAAM;AAEnD,MAAMC,MAAM;AAEZ,SAASC;IACP,IAAI;QACF,OAAO,OAAOC,WAAW,cAAc,OAAOA,OAAOC,cAAc;IACrE,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,2EAA2E,GAC3E,OAAO,SAASC,iBAAiBC,OAAe;IAC9C,OAAOA,UAAU,GAAGb,WAAWC,MAAMa,SAAS,EAAE;QAAED;IAAQ,GAAG,YAAY,CAAC,GAAG;AAC/E;AAEA,iFAAiF,GACjF,OAAO,SAASE,wBAAwBC,MAA4D,EAAEC,MAAMC,KAAKD,GAAG,EAAE;IACpH,IAAI,CAACD,QAAQ;IACb,IAAI;YACFP;SAAAA,WAAAA,8BAAAA,SAAWU,OAAO,CAACX,KAAKY,KAAKC,SAAS,CAAC;YAAEL;YAAQM,IAAIL;QAAI;IAC3D,EAAE,eAAM;IACN,oDAAoD;IACtD;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASM,uBAAuBC,UAAmC,CAAC,CAAC,EAAEP,MAAMC,KAAKD,GAAG,EAAE;IAC5F,IAAI;YAQkCQ;YAPxBhB;QAAZ,MAAMiB,OAAMjB,WAAAA,8BAAAA,SAAWkB,OAAO,CAACnB;QAC/B,IAAI,CAACkB,KAAK,OAAO;QACjB,MAAMD,SAASL,KAAKQ,KAAK,CAACF;QAC1B,IAAI,OAAOD,OAAOH,EAAE,KAAK,YAAYL,MAAMQ,OAAOH,EAAE,GAAGf,0BAA0B;gBAC/EE;aAAAA,YAAAA,8BAAAA,UAAWoB,UAAU,CAACrB;YACtB,OAAO;QACT;QACA,OAAON,8BAA6BuB,iBAAAA,OAAOT,MAAM,YAAbS,iBAAiB,MAAMD;IAC7D,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,+DAA+D,GAC/D,OAAO,SAASM;IACd,IAAI;YACFrB;SAAAA,WAAAA,8BAAAA,SAAWoB,UAAU,CAACrB;IACxB,EAAE,eAAM;IACN,mBAAmB;IACrB;AACF"}
|