@aglyn/plugins-ai 1.0.0-beta.271 → 1.0.0-beta.273
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +4 -3
- package/src/lib/activity/ai-activity-actions.js +3 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +18 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
- package/src/lib/components/ai-site-import-job.component.js +707 -0
- package/src/lib/components/ai-site-import-job.component.js.map +1 -0
- package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
- package/src/lib/components/ai-site-import-start.component.js +335 -0
- package/src/lib/components/ai-site-import-start.component.js.map +1 -0
- package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
- package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
- package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +329 -252
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +12 -1
- package/src/lib/jobs/ai-empty-site-sweep.js +15 -3
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
- package/src/lib/jobs/ai-job-drafts.js +24 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
- package/src/lib/jobs/ai-job-import-step.js +809 -0
- package/src/lib/jobs/ai-job-import-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +39 -1
- package/src/lib/jobs/ai-job-page-language.js +62 -4
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-page-step.js +26 -4
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +8 -4
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +39 -1
- package/src/lib/jobs/ai-job-site-datasets.js +111 -15
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +12 -3
- package/src/lib/jobs/ai-job-site-step.js +85 -11
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +15 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +8 -0
- package/src/lib/jobs/ai-jobs.js +19 -3
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
- package/src/lib/jobs/ai-layout-owner-photos.js +49 -2
- package/src/lib/jobs/ai-layout-owner-photos.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 +114 -43
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-late-photos.d.ts +143 -0
- package/src/lib/jobs/ai-site-late-photos.js +486 -0
- package/src/lib/jobs/ai-site-late-photos.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +18 -0
- package/src/lib/jobs/ai-site-publish.js +123 -5
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +61 -3
- package/src/lib/jobs/ai-theme-brand-inputs.js +117 -27
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.d.ts +83 -0
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js +1553 -0
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +50 -13
- package/src/lib/layout-language/ai-layout-pictures.js +81 -19
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +20 -0
- package/src/lib/model/ai-credit-estimate.js +13 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +2 -2
- package/src/lib/model/ai-job-activity.js +6 -4
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-notice.js +18 -4
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +39 -2
- package/src/lib/model/ai-jobs.types.js +2 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-import-view.d.ts +130 -0
- package/src/lib/model/ai-site-import-view.js +182 -0
- package/src/lib/model/ai-site-import-view.js.map +1 -0
- package/src/lib/model/ai-site-import.d.ts +302 -0
- package/src/lib/model/ai-site-import.js +526 -0
- package/src/lib/model/ai-site-import.js.map +1 -0
- package/src/lib/model/ai-site-photos.d.ts +119 -0
- package/src/lib/model/ai-site-photos.js +128 -0
- package/src/lib/model/ai-site-photos.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +22 -9
- package/src/lib/model/ai-site-start.js +64 -23
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/seo-fields.d.ts +29 -4
- package/src/lib/runtime/seo-fields.js +58 -11
- package/src/lib/runtime/seo-fields.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +11 -0
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/ai-site-import-route.d.ts +89 -0
- package/src/lib/server/ai-site-import-route.js +356 -0
- package/src/lib/server/ai-site-import-route.js.map +1 -0
- package/src/lib/server.js +14 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +163 -0
- package/src/lib/site-import/site-import-crawl.js +467 -0
- package/src/lib/site-import/site-import-crawl.js.map +1 -0
- package/src/lib/site-import/site-import-flag.d.ts +23 -0
- package/src/lib/site-import/site-import-flag.js +23 -0
- package/src/lib/site-import/site-import-flag.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +94 -0
- package/src/lib/site-import/site-import-html.js +462 -0
- package/src/lib/site-import/site-import-html.js.map +1 -0
- package/src/lib/site-import/site-import-images.d.ts +54 -0
- package/src/lib/site-import/site-import-images.js +204 -0
- package/src/lib/site-import/site-import-images.js.map +1 -0
- package/src/lib/site-import/site-import-page.d.ts +48 -0
- package/src/lib/site-import/site-import-page.js +150 -0
- package/src/lib/site-import/site-import-page.js.map +1 -0
- package/src/lib/site-import/site-import-review.d.ts +40 -0
- package/src/lib/site-import/site-import-review.js +149 -0
- package/src/lib/site-import/site-import-review.js.map +1 -0
- package/src/lib/site-import/site-import-robots.d.ts +52 -0
- package/src/lib/site-import/site-import-robots.js +126 -0
- package/src/lib/site-import/site-import-robots.js.map +1 -0
- package/src/lib/tools/ai-seo-tool.d.ts +25 -1
- package/src/lib/tools/ai-seo-tool.js +61 -0
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /*==========================================
|
|
18
|
+
* BRING MY SITE: THE WHOLE SITE, IN REVIEWED WAVES (AGL-3976, parent AGL-3973).
|
|
19
|
+
*
|
|
20
|
+
* The `import` job reads a website the workspace has PROVED it owns
|
|
21
|
+
* (AGL-3974's claims) and brings it in as draft pages of a new site. This
|
|
22
|
+
* module is the job's plan of record, pure, so the step, the doors, the
|
|
23
|
+
* console and the specs read one set of rules:
|
|
24
|
+
*
|
|
25
|
+
* - **The inventory.** The crawl's pages, grouped: the MAIN pages (home, the
|
|
26
|
+
* pages its menu links, every top-level page, and any page that belongs to
|
|
27
|
+
* no collection), then each COLLECTION (an address prefix holding at least
|
|
28
|
+
* `AI_SITE_IMPORT_COLLECTION_MIN` pages: `/blog/…`, `/news/…`, a dated
|
|
29
|
+
* `/2024/05/…` archive).
|
|
30
|
+
* - **Never past the plan.** Pages are kept in that order until the site's
|
|
31
|
+
* room under its plan's page count is used; the rest are LEFT OUT, said so
|
|
32
|
+
* with the reason, never dropped silently. So a plan's limit always leaves
|
|
33
|
+
* out a collection's tail before it leaves out a main page.
|
|
34
|
+
* - **Waves.** The kept pages are cut into waves of at most
|
|
35
|
+
* `AI_SITE_IMPORT_WAVE_PAGES`. The first wave is the main pages, with the
|
|
36
|
+
* site's header and footer built first; each later wave is one
|
|
37
|
+
* collection's next slice, so a review is about one kind of page at a
|
|
38
|
+
* time ("Blog, 1 of 3"). A wave runs, then waits for the person: nothing
|
|
39
|
+
* starts the next one but them.
|
|
40
|
+
* - **The price.** An imported page is an AI page (`AI_MEASURED_IMPORT_CREDITS`,
|
|
41
|
+
* about 27 credits); a wave is quoted before it starts as its pages and,
|
|
42
|
+
* for the first, its header and footer.
|
|
43
|
+
*=========================================*/ import { aiCreditRangeOrdered, AI_MEASURED_IMPORT_CREDITS } from "./ai-credit-estimate.js";
|
|
44
|
+
/** The job kind. */ export const AI_SITE_IMPORT_KIND = 'import';
|
|
45
|
+
/** The input naming the proved domain the job brings in. */ export const AI_SITE_IMPORT_DOMAIN_INPUT = 'domain';
|
|
46
|
+
/** The most pages one wave brings in. */ export const AI_SITE_IMPORT_WAVE_PAGES = 10;
|
|
47
|
+
/**
|
|
48
|
+
* The most pages one import reads, whatever the plan allows: an unlimited
|
|
49
|
+
* plan is still one crawl's worth, and the crawl's own time budget may stop
|
|
50
|
+
* sooner (the inventory then says so).
|
|
51
|
+
*/ export const AI_SITE_IMPORT_MAX_PAGES = 200;
|
|
52
|
+
/** The fewest pages under one address prefix that make it a collection. */ export const AI_SITE_IMPORT_COLLECTION_MIN = 3;
|
|
53
|
+
/** The most photos brought in from one page. */ export const AI_SITE_IMPORT_IMAGES_PER_PAGE = 8;
|
|
54
|
+
/** The most characters of one page's text a wave keeps for its model answer. */ export const AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS = 6000;
|
|
55
|
+
/** The most sections one imported page is planned with. */ export const AI_SITE_IMPORT_MAX_SECTIONS = 6;
|
|
56
|
+
/** The most characters a section's name, a page's title or a label keeps. */ export const AI_SITE_IMPORT_LABEL_MAX_CHARS = 60;
|
|
57
|
+
/** A page with fewer words than this and no photo has nothing to bring in. */ export const AI_SITE_IMPORT_MIN_WORDS = 5;
|
|
58
|
+
/** The item ledger slot of the site's header and footer. */ export const AI_SITE_IMPORT_FRAME_SLOT = 'frame';
|
|
59
|
+
/** The ceiling one imported page reserves: its answer pass and its listing pass, at 50 each. */ export const AI_SITE_IMPORT_PAGE_CEILING_CREDITS = 100;
|
|
60
|
+
/** The ceiling the header and footer reserve: one answer at 50. */ export const AI_SITE_IMPORT_FRAME_CEILING_CREDITS = 50;
|
|
61
|
+
/** The most left-out pages listed by address; the rest are counted. */ export const AI_SITE_IMPORT_LEFT_OUT_LISTED = 60;
|
|
62
|
+
export const AI_SITE_IMPORT_LEFT_OUT_REASONS = [
|
|
63
|
+
'plan-limit',
|
|
64
|
+
'robots',
|
|
65
|
+
'login-form',
|
|
66
|
+
'empty',
|
|
67
|
+
'unreadable',
|
|
68
|
+
'too-large',
|
|
69
|
+
'off-site'
|
|
70
|
+
];
|
|
71
|
+
/*------------------------------------------
|
|
72
|
+
* Grouping and waves
|
|
73
|
+
*-----------------------------------------*/ const clipLabel = (text)=>{
|
|
74
|
+
const line = String(text != null ? text : '').replace(/\s+/g, ' ').trim();
|
|
75
|
+
return line.length > AI_SITE_IMPORT_LABEL_MAX_CHARS ? `${line.slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS - 1).trimEnd()}…` : line;
|
|
76
|
+
};
|
|
77
|
+
const segmentsOf = (path)=>String(path != null ? path : '').split('/').filter(Boolean);
|
|
78
|
+
/** A collection's prefix for a page address: its first segment, a dated archive's `posts`; `null` for a top-level page. */ export function aiSiteImportPrefix(path) {
|
|
79
|
+
const segments = segmentsOf(path);
|
|
80
|
+
if (segments.length < 2) return null;
|
|
81
|
+
const first = segments[0].toLowerCase();
|
|
82
|
+
return /^\d{4}$/.test(first) ? 'posts' : first;
|
|
83
|
+
}
|
|
84
|
+
/** "news-and-events" → "News and events". */ export function aiSiteImportPrefixLabel(prefix) {
|
|
85
|
+
const words = decodeURIComponent(prefix).replace(/[-_+]+/g, ' ').trim();
|
|
86
|
+
return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : 'Pages';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The inventory's groups, in the order they are brought in: the main pages,
|
|
90
|
+
* then each collection — one the home page's menu links first, in menu order,
|
|
91
|
+
* then the larger before the smaller.
|
|
92
|
+
*/ export function aiSiteImportGroups(pages, menu = []) {
|
|
93
|
+
var _sized_get, _sized_get1, _sized_get2;
|
|
94
|
+
const counts = new Map();
|
|
95
|
+
for (const page of pages){
|
|
96
|
+
var _counts_get;
|
|
97
|
+
const prefix = aiSiteImportPrefix(page.path);
|
|
98
|
+
if (prefix) counts.set(prefix, ((_counts_get = counts.get(prefix)) != null ? _counts_get : 0) + 1);
|
|
99
|
+
}
|
|
100
|
+
const menuPaths = new Set(menu.map((path)=>path.replace(/\/+$/, '') || '/'));
|
|
101
|
+
const collections = new Set([
|
|
102
|
+
...counts
|
|
103
|
+
].filter(([, count])=>count >= AI_SITE_IMPORT_COLLECTION_MIN).map(([prefix])=>prefix));
|
|
104
|
+
const groupOf = (page)=>{
|
|
105
|
+
const path = page.path.replace(/\/+$/, '') || '/';
|
|
106
|
+
if (page.foundBy === 'home' || menuPaths.has(path)) return 'main';
|
|
107
|
+
const prefix = aiSiteImportPrefix(page.path);
|
|
108
|
+
return prefix && collections.has(prefix) ? `c:${prefix}` : 'main';
|
|
109
|
+
};
|
|
110
|
+
const menuRank = (prefix)=>{
|
|
111
|
+
const index = menu.findIndex((path)=>{
|
|
112
|
+
var _segmentsOf_;
|
|
113
|
+
const first = (_segmentsOf_ = segmentsOf(path)[0]) == null ? void 0 : _segmentsOf_.toLowerCase();
|
|
114
|
+
return first === prefix;
|
|
115
|
+
});
|
|
116
|
+
return index === -1 ? Number.POSITIVE_INFINITY : index;
|
|
117
|
+
};
|
|
118
|
+
const sized = new Map();
|
|
119
|
+
for (const page of pages)sized.set(groupOf(page), ((_sized_get = sized.get(groupOf(page))) != null ? _sized_get : 0) + 1);
|
|
120
|
+
const ordered = [
|
|
121
|
+
...collections
|
|
122
|
+
].filter((prefix)=>{
|
|
123
|
+
var _sized_get;
|
|
124
|
+
return ((_sized_get = sized.get(`c:${prefix}`)) != null ? _sized_get : 0) > 0;
|
|
125
|
+
}).sort((a, b)=>{
|
|
126
|
+
var _sized_get, _sized_get1;
|
|
127
|
+
return menuRank(a) - menuRank(b) || ((_sized_get = sized.get(`c:${b}`)) != null ? _sized_get : 0) - ((_sized_get1 = sized.get(`c:${a}`)) != null ? _sized_get1 : 0) || a.localeCompare(b);
|
|
128
|
+
});
|
|
129
|
+
const groups = [];
|
|
130
|
+
if (((_sized_get1 = sized.get('main')) != null ? _sized_get1 : 0) > 0) groups.push({
|
|
131
|
+
id: 'main',
|
|
132
|
+
label: 'Main pages',
|
|
133
|
+
kind: 'main',
|
|
134
|
+
pages: (_sized_get2 = sized.get('main')) != null ? _sized_get2 : 0
|
|
135
|
+
});
|
|
136
|
+
for (const prefix of ordered){
|
|
137
|
+
var _sized_get3;
|
|
138
|
+
groups.push({
|
|
139
|
+
id: `c:${prefix}`,
|
|
140
|
+
label: aiSiteImportPrefixLabel(prefix),
|
|
141
|
+
kind: 'collection',
|
|
142
|
+
pages: (_sized_get3 = sized.get(`c:${prefix}`)) != null ? _sized_get3 : 0
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
groups,
|
|
147
|
+
groupOf
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The main pages in the order a visitor meets them: home, then the menu's
|
|
152
|
+
* order, then the rest as the crawl read them. A collection keeps the crawl's
|
|
153
|
+
* order, which is its sitemap's.
|
|
154
|
+
*/ function orderedWithin(group, pages, menu) {
|
|
155
|
+
if (group !== 'main') return [
|
|
156
|
+
...pages
|
|
157
|
+
];
|
|
158
|
+
const rank = (page)=>{
|
|
159
|
+
if (page.foundBy === 'home' || segmentsOf(page.path).length === 0) return -1;
|
|
160
|
+
const path = page.path.replace(/\/+$/, '') || '/';
|
|
161
|
+
const index = menu.findIndex((entry)=>(entry.replace(/\/+$/, '') || '/') === path);
|
|
162
|
+
return index === -1 ? Number.POSITIVE_INFINITY : index;
|
|
163
|
+
};
|
|
164
|
+
return pages.map((page, order)=>({
|
|
165
|
+
page,
|
|
166
|
+
order
|
|
167
|
+
})).sort((a, b)=>rank(a.page) - rank(b.page) || a.order - b.order).map(({ page })=>page);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The address a page takes on the new site: one segment, as every Aglyn page
|
|
171
|
+
* has, from the last segment of its old address; home is the root. Two pages
|
|
172
|
+
* that would take the same address are told apart by their collection, then
|
|
173
|
+
* by a number. The draft writer still makes it unique on the site itself.
|
|
174
|
+
*/ export function aiSiteImportSlug(path, taken) {
|
|
175
|
+
var _segments_;
|
|
176
|
+
const segments = segmentsOf(path);
|
|
177
|
+
if (!segments.length) {
|
|
178
|
+
if (!taken.has('/')) {
|
|
179
|
+
taken.add('/');
|
|
180
|
+
return '/';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const clean = (value)=>decodeURIComponent(value).toLowerCase().replace(/\.(?:html?|php|aspx?)$/, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
184
|
+
const last = clean((_segments_ = segments[segments.length - 1]) != null ? _segments_ : '') || 'page';
|
|
185
|
+
const prefixed = segments.length > 1 ? clean(`${segments[0]}-${segments[segments.length - 1]}`) : last;
|
|
186
|
+
for (const candidate of [
|
|
187
|
+
last,
|
|
188
|
+
prefixed
|
|
189
|
+
]){
|
|
190
|
+
if (candidate && !taken.has(candidate)) {
|
|
191
|
+
taken.add(candidate);
|
|
192
|
+
return candidate;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let attempt = 2;
|
|
196
|
+
while(taken.has(`${last}-${attempt}`))attempt += 1;
|
|
197
|
+
const slug = `${last}-${attempt}`;
|
|
198
|
+
taken.add(slug);
|
|
199
|
+
return slug;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The site's inventory cut into waves, every page left out said with its
|
|
203
|
+
* reason. Pure: the step reads the site, this decides what comes in.
|
|
204
|
+
*/ export function aiSiteImportInventory(input) {
|
|
205
|
+
var _input_wavePages, _leftOutCount_unreadable, _input_reviewId;
|
|
206
|
+
var _waves_;
|
|
207
|
+
const wavePages = Math.max(1, Math.floor((_input_wavePages = input.wavePages) != null ? _input_wavePages : AI_SITE_IMPORT_WAVE_PAGES));
|
|
208
|
+
const leftOut = [];
|
|
209
|
+
const leftOutCount = {};
|
|
210
|
+
const leave = (entry)=>{
|
|
211
|
+
var _leftOutCount_entry_reason;
|
|
212
|
+
leftOutCount[entry.reason] = ((_leftOutCount_entry_reason = leftOutCount[entry.reason]) != null ? _leftOutCount_entry_reason : 0) + 1;
|
|
213
|
+
if (leftOut.length < AI_SITE_IMPORT_LEFT_OUT_LISTED) leftOut.push(entry);
|
|
214
|
+
};
|
|
215
|
+
for (const skipped of input.skipped){
|
|
216
|
+
let path = '/';
|
|
217
|
+
try {
|
|
218
|
+
path = new URL(skipped.url).pathname;
|
|
219
|
+
} catch (unused) {
|
|
220
|
+
// A malformed address keeps the root as its path.
|
|
221
|
+
}
|
|
222
|
+
leave({
|
|
223
|
+
url: skipped.url,
|
|
224
|
+
path,
|
|
225
|
+
title: '',
|
|
226
|
+
reason: skipped.reason
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (input.skippedOverflow) leftOutCount['unreadable'] = ((_leftOutCount_unreadable = leftOutCount['unreadable']) != null ? _leftOutCount_unreadable : 0) + input.skippedOverflow;
|
|
230
|
+
const readable = input.pages.filter((page)=>{
|
|
231
|
+
if (page.words >= AI_SITE_IMPORT_MIN_WORDS || page.images > 0) return true;
|
|
232
|
+
leave({
|
|
233
|
+
url: page.url,
|
|
234
|
+
path: page.path,
|
|
235
|
+
title: clipLabel(page.title),
|
|
236
|
+
reason: 'empty'
|
|
237
|
+
});
|
|
238
|
+
return false;
|
|
239
|
+
});
|
|
240
|
+
const { groups, groupOf } = aiSiteImportGroups(readable, input.menu);
|
|
241
|
+
const limit = input.pageLimit === null || !Number.isFinite(input.pageLimit) ? null : Math.max(0, Math.floor(input.pageLimit));
|
|
242
|
+
let room = limit === null ? Number.POSITIVE_INFINITY : Math.max(0, limit - Math.max(0, input.existingPages));
|
|
243
|
+
const pages = [];
|
|
244
|
+
const waves = [];
|
|
245
|
+
const taken = new Set();
|
|
246
|
+
for (const group of groups){
|
|
247
|
+
const members = orderedWithin(group.id, readable.filter((page)=>groupOf(page) === group.id), input.menu);
|
|
248
|
+
const kept = [];
|
|
249
|
+
for (const page of members){
|
|
250
|
+
if (room > 0) {
|
|
251
|
+
kept.push(page);
|
|
252
|
+
room -= 1;
|
|
253
|
+
} else {
|
|
254
|
+
leave({
|
|
255
|
+
url: page.url,
|
|
256
|
+
path: page.path,
|
|
257
|
+
title: clipLabel(page.title),
|
|
258
|
+
reason: 'plan-limit'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const parts = Math.ceil(kept.length / wavePages);
|
|
263
|
+
for(let part = 0; part < parts; part += 1){
|
|
264
|
+
const index = waves.length;
|
|
265
|
+
const slice = kept.slice(part * wavePages, (part + 1) * wavePages);
|
|
266
|
+
const slots = [];
|
|
267
|
+
for (const page of slice){
|
|
268
|
+
var _segmentsOf_pop;
|
|
269
|
+
const slot = `p${pages.length}`;
|
|
270
|
+
slots.push(slot);
|
|
271
|
+
pages.push({
|
|
272
|
+
slot,
|
|
273
|
+
url: page.url,
|
|
274
|
+
path: page.path,
|
|
275
|
+
title: clipLabel(page.title) || aiSiteImportPrefixLabel((_segmentsOf_pop = segmentsOf(page.path).pop()) != null ? _segmentsOf_pop : 'Home'),
|
|
276
|
+
group: group.id,
|
|
277
|
+
wave: index,
|
|
278
|
+
draftId: input.mintId(),
|
|
279
|
+
slug: aiSiteImportSlug(page.path, taken),
|
|
280
|
+
words: page.words,
|
|
281
|
+
images: page.images
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
waves.push({
|
|
285
|
+
index,
|
|
286
|
+
label: parts > 1 ? `${group.label}, ${part + 1} of ${parts}` : group.label,
|
|
287
|
+
group: group.id,
|
|
288
|
+
slots,
|
|
289
|
+
frame: index === 0 && group.kind === 'main',
|
|
290
|
+
status: 'waiting',
|
|
291
|
+
startedAt: null,
|
|
292
|
+
finishedAt: null
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
domain: input.domain,
|
|
298
|
+
siteName: clipLabel(input.siteName) || input.domain,
|
|
299
|
+
ownership: input.ownership,
|
|
300
|
+
readAt: input.readAt,
|
|
301
|
+
pageLimit: limit,
|
|
302
|
+
existingPages: Math.max(0, input.existingPages),
|
|
303
|
+
found: input.pages.length,
|
|
304
|
+
stoppedBy: input.stoppedBy,
|
|
305
|
+
groups,
|
|
306
|
+
pages,
|
|
307
|
+
waves,
|
|
308
|
+
leftOut,
|
|
309
|
+
leftOutCount,
|
|
310
|
+
frame: ((_waves_ = waves[0]) == null ? void 0 : _waves_.frame) ? {
|
|
311
|
+
draftId: input.mintId()
|
|
312
|
+
} : null,
|
|
313
|
+
reviewId: (_input_reviewId = input.reviewId) != null ? _input_reviewId : null
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/** The item ledger an inventory starts: the header and footer, then every page, all pending. */ export function aiSiteImportLedger(state) {
|
|
317
|
+
const row = (slot, op, label)=>({
|
|
318
|
+
slot,
|
|
319
|
+
op,
|
|
320
|
+
label,
|
|
321
|
+
status: 'pending',
|
|
322
|
+
attempt: 1,
|
|
323
|
+
creditsSpent: 0,
|
|
324
|
+
creditsRefunded: 0,
|
|
325
|
+
outputs: []
|
|
326
|
+
});
|
|
327
|
+
return [
|
|
328
|
+
...state.frame ? [
|
|
329
|
+
row(AI_SITE_IMPORT_FRAME_SLOT, 'layout', 'Header and footer')
|
|
330
|
+
] : [],
|
|
331
|
+
...state.pages.map((page)=>row(page.slot, 'page', page.title))
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
const OPEN = new Set([
|
|
335
|
+
'pending',
|
|
336
|
+
'running'
|
|
337
|
+
]);
|
|
338
|
+
/** Whether an item is still to be built. */ export function aiSiteImportItemOpen(items, slot) {
|
|
339
|
+
const row = (items != null ? items : []).find((entry)=>entry.slot === slot);
|
|
340
|
+
return Boolean(row && OPEN.has(row.status));
|
|
341
|
+
}
|
|
342
|
+
/** The wave running now, else `null`. */ export function aiSiteImportRunningWave(state) {
|
|
343
|
+
var _ref;
|
|
344
|
+
return (_ref = state == null ? void 0 : state.waves.find((wave)=>wave.status === 'running')) != null ? _ref : null;
|
|
345
|
+
}
|
|
346
|
+
/** The next wave a person may start, else `null`. */ export function aiSiteImportNextWave(state) {
|
|
347
|
+
var _state_waves_find;
|
|
348
|
+
if (!state || aiSiteImportRunningWave(state)) return null;
|
|
349
|
+
return (_state_waves_find = state.waves.find((wave)=>wave.status === 'waiting')) != null ? _state_waves_find : null;
|
|
350
|
+
}
|
|
351
|
+
/** The wave a person is reviewing, else `null`. */ export function aiSiteImportReviewWave(state) {
|
|
352
|
+
var _ref;
|
|
353
|
+
return (_ref = state == null ? void 0 : state.waves.find((wave)=>wave.status === 'review')) != null ? _ref : null;
|
|
354
|
+
}
|
|
355
|
+
/** What the next pass of the step does. */ export function aiSiteImportNextWork(state, items) {
|
|
356
|
+
if (!state) return {
|
|
357
|
+
kind: 'inventory'
|
|
358
|
+
};
|
|
359
|
+
const running = aiSiteImportRunningWave(state);
|
|
360
|
+
if (!running) {
|
|
361
|
+
const next = aiSiteImportNextWave(state);
|
|
362
|
+
return next ? {
|
|
363
|
+
kind: 'start-wave',
|
|
364
|
+
wave: next
|
|
365
|
+
} : {
|
|
366
|
+
kind: 'done'
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// A page the wave's read has not reached yet is read before anything is built.
|
|
370
|
+
const unread = running.slots.map((slot)=>state.pages.find((page)=>page.slot === slot)).filter((page)=>!!page && !page.read && !page.refused && aiSiteImportItemOpen(items, page.slot));
|
|
371
|
+
if (unread.length) return {
|
|
372
|
+
kind: 'read',
|
|
373
|
+
wave: running,
|
|
374
|
+
pages: unread
|
|
375
|
+
};
|
|
376
|
+
if (running.frame && state.frame && aiSiteImportItemOpen(items, AI_SITE_IMPORT_FRAME_SLOT)) {
|
|
377
|
+
return {
|
|
378
|
+
kind: 'frame',
|
|
379
|
+
wave: running
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
for (const slot of running.slots){
|
|
383
|
+
if (!aiSiteImportItemOpen(items, slot)) continue;
|
|
384
|
+
const page = state.pages.find((entry)=>entry.slot === slot);
|
|
385
|
+
if (!page) continue;
|
|
386
|
+
return page.photos ? {
|
|
387
|
+
kind: 'page',
|
|
388
|
+
wave: running,
|
|
389
|
+
page
|
|
390
|
+
} : {
|
|
391
|
+
kind: 'images',
|
|
392
|
+
wave: running,
|
|
393
|
+
page
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
kind: 'close-wave',
|
|
398
|
+
wave: running
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/*------------------------------------------
|
|
402
|
+
* The state, one pass at a time
|
|
403
|
+
*-----------------------------------------*/ const withPage = (state, slot, patch)=>_extends({}, state, {
|
|
404
|
+
pages: state.pages.map((page)=>page.slot === slot ? _extends({}, page, patch) : page)
|
|
405
|
+
});
|
|
406
|
+
const withWave = (state, index, patch)=>_extends({}, state, {
|
|
407
|
+
waves: state.waves.map((wave)=>wave.index === index ? _extends({}, wave, patch) : wave)
|
|
408
|
+
});
|
|
409
|
+
/** A wave started: running, with what its read found on each page. */ export function aiSiteImportWaveStarted(state, index, at, reads) {
|
|
410
|
+
// The wave reviewed before this one is done: starting the next is the person's say-so.
|
|
411
|
+
const next = withWave(_extends({}, state, {
|
|
412
|
+
waves: state.waves.map((wave)=>wave.status === 'review' ? _extends({}, wave, {
|
|
413
|
+
status: 'done'
|
|
414
|
+
}) : wave)
|
|
415
|
+
}), index, {
|
|
416
|
+
status: 'running',
|
|
417
|
+
startedAt: at,
|
|
418
|
+
finishedAt: null
|
|
419
|
+
});
|
|
420
|
+
return aiSiteImportPagesRead(next, reads);
|
|
421
|
+
}
|
|
422
|
+
/** More of a running wave's pages, read now. */ export function aiSiteImportPagesRead(state, reads) {
|
|
423
|
+
let next = state;
|
|
424
|
+
for (const [slot, read] of reads){
|
|
425
|
+
var _next_pages_find;
|
|
426
|
+
next = typeof read === 'string' ? withPage(next, slot, {
|
|
427
|
+
refused: read,
|
|
428
|
+
read: null
|
|
429
|
+
}) : withPage(next, slot, {
|
|
430
|
+
read,
|
|
431
|
+
refused: null,
|
|
432
|
+
title: clipLabel(read.title) || ((_next_pages_find = next.pages.find((page)=>page.slot === slot)) == null ? void 0 : _next_pages_find.title) || ''
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return next;
|
|
436
|
+
}
|
|
437
|
+
/** A page's photos are in the library. */ export function aiSiteImportPhotosIn(state, slot, photos) {
|
|
438
|
+
return withPage(state, slot, {
|
|
439
|
+
photos
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
/** A page is in: its text is no longer kept. */ export function aiSiteImportPageSettled(state, slot) {
|
|
443
|
+
const page = state.pages.find((entry)=>entry.slot === slot);
|
|
444
|
+
if (!(page == null ? void 0 : page.read)) return state;
|
|
445
|
+
return withPage(state, slot, {
|
|
446
|
+
read: _extends({}, page.read, {
|
|
447
|
+
text: ''
|
|
448
|
+
})
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
/** A wave is in: waiting for its review, or done when no wave follows it. */ export function aiSiteImportWaveClosed(state, index, at) {
|
|
452
|
+
var _ref;
|
|
453
|
+
var _state_waves_find;
|
|
454
|
+
const later = state.waves.some((wave)=>wave.index > index && wave.status === 'waiting');
|
|
455
|
+
let next = withWave(state, index, {
|
|
456
|
+
status: later ? 'review' : 'done',
|
|
457
|
+
finishedAt: at
|
|
458
|
+
});
|
|
459
|
+
// What the wave read is no longer needed by any model answer.
|
|
460
|
+
for (const slot of (_ref = (_state_waves_find = state.waves.find((wave)=>wave.index === index)) == null ? void 0 : _state_waves_find.slots) != null ? _ref : [])next = aiSiteImportPageSettled(next, slot);
|
|
461
|
+
return next;
|
|
462
|
+
}
|
|
463
|
+
/** The state as the console reads it: every page's read text left off. */ export function aiSiteImportWire(state) {
|
|
464
|
+
if (!state) return null;
|
|
465
|
+
return _extends({}, state, {
|
|
466
|
+
pages: state.pages.map((page)=>page.read ? _extends({}, page, {
|
|
467
|
+
read: _extends({}, page.read, {
|
|
468
|
+
text: '',
|
|
469
|
+
images: []
|
|
470
|
+
})
|
|
471
|
+
}) : page)
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
/*------------------------------------------
|
|
475
|
+
* Credits
|
|
476
|
+
*-----------------------------------------*/ /**
|
|
477
|
+
* What bringing in this many pages costs, quoted before it starts (AGL-3976):
|
|
478
|
+
* each page an AI page as measured, its ceiling its two passes, and the
|
|
479
|
+
* site's header and footer once when the wave builds them.
|
|
480
|
+
*/ export function aiSiteImportCreditRange(pages, options = {}) {
|
|
481
|
+
const count = Math.max(0, Math.floor(Number.isFinite(pages) ? pages : 0));
|
|
482
|
+
const { page, frame } = AI_MEASURED_IMPORT_CREDITS;
|
|
483
|
+
return aiCreditRangeOrdered({
|
|
484
|
+
likely: count * page.median + (options.frame ? frame.median : 0),
|
|
485
|
+
p90: count * page.p90 + (options.frame ? frame.p90 : 0),
|
|
486
|
+
ceiling: count * AI_SITE_IMPORT_PAGE_CEILING_CREDITS + (options.frame ? AI_SITE_IMPORT_FRAME_CEILING_CREDITS : 0)
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/** A wave's quote: the pages it has still to bring in, and its header and footer while they are owed. */ export function aiSiteImportWaveCreditRange(state, wave, items) {
|
|
490
|
+
const owed = (slot)=>items ? aiSiteImportItemOpen(items, slot) : true;
|
|
491
|
+
const pages = wave.slots.filter((slot)=>{
|
|
492
|
+
var _state_pages_find;
|
|
493
|
+
return owed(slot) && !((_state_pages_find = state.pages.find((page)=>page.slot === slot)) == null ? void 0 : _state_pages_find.refused);
|
|
494
|
+
}).length;
|
|
495
|
+
return aiSiteImportCreditRange(pages, {
|
|
496
|
+
frame: wave.frame && !!state.frame && owed(AI_SITE_IMPORT_FRAME_SLOT)
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/*------------------------------------------
|
|
500
|
+
* Words
|
|
501
|
+
*-----------------------------------------*/ /** Why a page is not brought in, as a person reads it. */ export const AI_SITE_IMPORT_LEFT_OUT_COPY = {
|
|
502
|
+
'plan-limit': 'Past your plan’s page count. Upgrade for more pages, then bring the rest in.',
|
|
503
|
+
robots: 'The site’s robots.txt asks AglynBot not to read it.',
|
|
504
|
+
'login-form': 'A sign-in page. Pages that ask for a password are never brought in.',
|
|
505
|
+
empty: 'Nothing to bring in: the page has no text or photos.',
|
|
506
|
+
unreadable: 'It could not be read just now.',
|
|
507
|
+
'too-large': 'Larger than a page can be to bring in.',
|
|
508
|
+
'off-site': 'It sends visitors to another website.'
|
|
509
|
+
};
|
|
510
|
+
/** "3 pages" / "1 page". */ export function aiSiteImportPagesText(count) {
|
|
511
|
+
return `${count.toLocaleString('en-US')} ${count === 1 ? 'page' : 'pages'}`;
|
|
512
|
+
}
|
|
513
|
+
/** The review a finished wave parks on, or the first wave's go-ahead once the site is read. */ export function aiSiteImportWaveMessage(state, items) {
|
|
514
|
+
const next = aiSiteImportNextWave(state);
|
|
515
|
+
const reviewing = aiSiteImportReviewWave(state);
|
|
516
|
+
if (!reviewing) {
|
|
517
|
+
const kept = state.pages.length;
|
|
518
|
+
return next ? `Your site is read: ${aiSiteImportPagesText(kept)} to bring in, in ${state.waves.length === 1 ? 'one wave' : `${state.waves.length} waves`}. Start the first wave when you are ready.` : 'Your site is read, and nothing on it can be brought in.';
|
|
519
|
+
}
|
|
520
|
+
const rows = (items != null ? items : []).filter((row)=>reviewing.slots.includes(row.slot));
|
|
521
|
+
const done = rows.filter((row)=>row.status === 'succeeded' || row.status === 'degraded').length;
|
|
522
|
+
const lead = `${reviewing.label} is in: ${aiSiteImportPagesText(done)} ready to review as drafts.`;
|
|
523
|
+
return next ? `${lead} Start ${next.label} when you are ready.` : lead;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
//# sourceMappingURL=ai-site-import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-import.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 * BRING MY SITE: THE WHOLE SITE, IN REVIEWED WAVES (AGL-3976, parent AGL-3973).\n *\n * The `import` job reads a website the workspace has PROVED it owns\n * (AGL-3974's claims) and brings it in as draft pages of a new site. This\n * module is the job's plan of record, pure, so the step, the doors, the\n * console and the specs read one set of rules:\n *\n * - **The inventory.** The crawl's pages, grouped: the MAIN pages (home, the\n * pages its menu links, every top-level page, and any page that belongs to\n * no collection), then each COLLECTION (an address prefix holding at least\n * `AI_SITE_IMPORT_COLLECTION_MIN` pages: `/blog/…`, `/news/…`, a dated\n * `/2024/05/…` archive).\n * - **Never past the plan.** Pages are kept in that order until the site's\n * room under its plan's page count is used; the rest are LEFT OUT, said so\n * with the reason, never dropped silently. So a plan's limit always leaves\n * out a collection's tail before it leaves out a main page.\n * - **Waves.** The kept pages are cut into waves of at most\n * `AI_SITE_IMPORT_WAVE_PAGES`. The first wave is the main pages, with the\n * site's header and footer built first; each later wave is one\n * collection's next slice, so a review is about one kind of page at a\n * time (\"Blog, 1 of 3\"). A wave runs, then waits for the person: nothing\n * starts the next one but them.\n * - **The price.** An imported page is an AI page (`AI_MEASURED_IMPORT_CREDITS`,\n * about 27 credits); a wave is quoted before it starts as its pages and,\n * for the first, its header and footer.\n *=========================================*/\n\nimport { aiCreditRangeOrdered, AI_MEASURED_IMPORT_CREDITS, type AiCreditRange } from './ai-credit-estimate'\nimport type { AiJobItemLedger } from './ai-jobs.types'\n\n/** The job kind. */\nexport const AI_SITE_IMPORT_KIND = 'import' as const\n\n/** The input naming the proved domain the job brings in. */\nexport const AI_SITE_IMPORT_DOMAIN_INPUT = 'domain'\n\n/** The most pages one wave brings in. */\nexport const AI_SITE_IMPORT_WAVE_PAGES = 10\n\n/**\n * The most pages one import reads, whatever the plan allows: an unlimited\n * plan is still one crawl's worth, and the crawl's own time budget may stop\n * sooner (the inventory then says so).\n */\nexport const AI_SITE_IMPORT_MAX_PAGES = 200\n\n/** The fewest pages under one address prefix that make it a collection. */\nexport const AI_SITE_IMPORT_COLLECTION_MIN = 3\n\n/** The most photos brought in from one page. */\nexport const AI_SITE_IMPORT_IMAGES_PER_PAGE = 8\n\n/** The most characters of one page's text a wave keeps for its model answer. */\nexport const AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS = 6_000\n\n/** The most sections one imported page is planned with. */\nexport const AI_SITE_IMPORT_MAX_SECTIONS = 6\n\n/** The most characters a section's name, a page's title or a label keeps. */\nexport const AI_SITE_IMPORT_LABEL_MAX_CHARS = 60\n\n/** A page with fewer words than this and no photo has nothing to bring in. */\nexport const AI_SITE_IMPORT_MIN_WORDS = 5\n\n/** The item ledger slot of the site's header and footer. */\nexport const AI_SITE_IMPORT_FRAME_SLOT = 'frame'\n\n/** The ceiling one imported page reserves: its answer pass and its listing pass, at 50 each. */\nexport const AI_SITE_IMPORT_PAGE_CEILING_CREDITS = 100\n\n/** The ceiling the header and footer reserve: one answer at 50. */\nexport const AI_SITE_IMPORT_FRAME_CEILING_CREDITS = 50\n\n/** The most left-out pages listed by address; the rest are counted. */\nexport const AI_SITE_IMPORT_LEFT_OUT_LISTED = 60\n\n/** A page the crawl read, as the inventory keeps it: no text, only what plans it. */\nexport interface AiSiteImportListedPage {\n url: string\n path: string\n title: string\n foundBy: 'home' | 'sitemap' | 'link'\n words: number\n images: number\n}\n\nexport type AiSiteImportGroupKind = 'main' | 'collection'\n\nexport interface AiSiteImportGroup {\n /** `main`, or `c:<prefix>` for a collection. */\n id: string\n label: string\n kind: AiSiteImportGroupKind\n /** Pages of the group the crawl read, kept or not. */\n pages: number\n}\n\n/** Why a page the site has is not brought in. */\nexport type AiSiteImportLeftOutReason =\n /** Past the site's room under its plan's page count. */\n | 'plan-limit'\n /** robots.txt keeps AglynBot off it. */\n | 'robots'\n /** A sign-in page: refused outright. */\n | 'login-form'\n /** No text and no photo: nothing to bring in. */\n | 'empty'\n /** It could not be read: an error, no answer, or not a web page. */\n | 'unreadable'\n /** Over the size one page may be. */\n | 'too-large'\n /** It sends visitors to another website. */\n | 'off-site'\n\nexport const AI_SITE_IMPORT_LEFT_OUT_REASONS: readonly AiSiteImportLeftOutReason[] = [\n 'plan-limit',\n 'robots',\n 'login-form',\n 'empty',\n 'unreadable',\n 'too-large',\n 'off-site',\n]\n\nexport interface AiSiteImportLeftOut {\n url: string\n path: string\n title: string\n reason: AiSiteImportLeftOutReason\n}\n\n/** A photo brought into the site's media library, as a page places it. */\nexport interface AiSiteImportPhoto {\n mediaId: string\n src: string\n width?: number\n height?: number\n alt?: string\n}\n\n/** What a wave's read found on one page, for its model answer. Never on the wire. */\nexport interface AiSiteImportPageRead {\n at: string\n title: string\n /** The page's text in order, cut to `AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS`; fenced only when a prompt reads it. */\n text: string\n /** The page's sections, from its headings. */\n sections: string[]\n /** Its photos' addresses, at most `AI_SITE_IMPORT_IMAGES_PER_PAGE`. */\n images: Array<{ url: string; alt: string }>\n}\n\nexport interface AiSiteImportPage {\n /** The item ledger slot: `p0`, `p1`, … */\n slot: string\n url: string\n path: string\n title: string\n group: string\n wave: number\n /** The id the page's draft is written under, minted with the inventory. */\n draftId: string\n /** The page's address on the new site. */\n slug: string\n words: number\n images: number\n /** Filled by the wave's read; the text is cleared once the page is in. */\n read?: AiSiteImportPageRead | null\n /** Set when the wave's read found the page cannot be brought in after all. */\n refused?: AiSiteImportLeftOutReason | null\n /** Filled once the page's photos are in the library. */\n photos?: {\n brought: number\n /** Already in the library from an earlier page or import: the same file, never a second copy. */\n reused: number\n skipped: number\n assets: AiSiteImportPhoto[]\n } | null\n}\n\nexport type AiSiteImportWaveStatus = 'waiting' | 'running' | 'review' | 'done'\n\nexport interface AiSiteImportWave {\n index: number\n label: string\n /** The group the wave brings in; the first wave's is `main`. */\n group: string\n slots: string[]\n /** Whether the wave builds the site's header and footer first. */\n frame: boolean\n status: AiSiteImportWaveStatus\n startedAt: string | null\n finishedAt: string | null\n}\n\nexport interface AiSiteImportState {\n domain: string\n /** The site's name, as Create site was given it: the header's name. */\n siteName: string\n ownership: { method: string | null; provenAt: number | null; detail: string | null }\n readAt: string\n /** The plan's page count; `null` when it has none. */\n pageLimit: number | null\n /** Pages the site already had when its import read the old site. */\n existingPages: number\n /** Pages the crawl read. */\n found: number\n /** Why the crawl stopped short of the whole site, or `null` when it did not. */\n stoppedBy: string | null\n groups: AiSiteImportGroup[]\n pages: AiSiteImportPage[]\n waves: AiSiteImportWave[]\n /** At most `AI_SITE_IMPORT_LEFT_OUT_LISTED`, by address. */\n leftOut: AiSiteImportLeftOut[]\n /** Every page left out, by reason, listed or not. */\n leftOutCount: Partial<Record<AiSiteImportLeftOutReason, number>>\n /** The header and footer's draft id, built in the first wave; `null` when there are no main pages. */\n frame: { draftId: string } | null\n /** The Trust & Safety row this import is logged under. */\n reviewId: string | null\n}\n\n/*------------------------------------------\n * Grouping and waves\n *-----------------------------------------*/\n\nconst clipLabel = (text: string) => {\n const line = String(text ?? '').replace(/\\s+/g, ' ').trim()\n return line.length > AI_SITE_IMPORT_LABEL_MAX_CHARS\n ? `${line.slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS - 1).trimEnd()}…`\n : line\n}\n\nconst segmentsOf = (path: string) => String(path ?? '').split('/').filter(Boolean)\n\n/** A collection's prefix for a page address: its first segment, a dated archive's `posts`; `null` for a top-level page. */\nexport function aiSiteImportPrefix(path: string): string | null {\n const segments = segmentsOf(path)\n if (segments.length < 2) return null\n const first = segments[0].toLowerCase()\n return /^\\d{4}$/.test(first) ? 'posts' : first\n}\n\n/** \"news-and-events\" → \"News and events\". */\nexport function aiSiteImportPrefixLabel(prefix: string): string {\n const words = decodeURIComponent(prefix).replace(/[-_+]+/g, ' ').trim()\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : 'Pages'\n}\n\n/**\n * The inventory's groups, in the order they are brought in: the main pages,\n * then each collection — one the home page's menu links first, in menu order,\n * then the larger before the smaller.\n */\nexport function aiSiteImportGroups(\n pages: readonly AiSiteImportListedPage[],\n menu: readonly string[] = [],\n): { groups: AiSiteImportGroup[]; groupOf: (page: AiSiteImportListedPage) => string } {\n const counts = new Map<string, number>()\n for (const page of pages) {\n const prefix = aiSiteImportPrefix(page.path)\n if (prefix) counts.set(prefix, (counts.get(prefix) ?? 0) + 1)\n }\n const menuPaths = new Set(menu.map((path) => path.replace(/\\/+$/, '') || '/'))\n const collections = new Set([...counts].filter(([, count]) => count >= AI_SITE_IMPORT_COLLECTION_MIN).map(([prefix]) => prefix))\n const groupOf = (page: AiSiteImportListedPage) => {\n const path = page.path.replace(/\\/+$/, '') || '/'\n if (page.foundBy === 'home' || menuPaths.has(path)) return 'main'\n const prefix = aiSiteImportPrefix(page.path)\n return prefix && collections.has(prefix) ? `c:${prefix}` : 'main'\n }\n const menuRank = (prefix: string) => {\n const index = menu.findIndex((path) => {\n const first = segmentsOf(path)[0]?.toLowerCase()\n return first === prefix\n })\n return index === -1 ? Number.POSITIVE_INFINITY : index\n }\n const sized = new Map<string, number>()\n for (const page of pages) sized.set(groupOf(page), (sized.get(groupOf(page)) ?? 0) + 1)\n const ordered = [...collections]\n .filter((prefix) => (sized.get(`c:${prefix}`) ?? 0) > 0)\n .sort((a, b) => menuRank(a) - menuRank(b) || (sized.get(`c:${b}`) ?? 0) - (sized.get(`c:${a}`) ?? 0) || a.localeCompare(b))\n const groups: AiSiteImportGroup[] = []\n if ((sized.get('main') ?? 0) > 0) groups.push({ id: 'main', label: 'Main pages', kind: 'main', pages: sized.get('main') ?? 0 })\n for (const prefix of ordered) {\n groups.push({ id: `c:${prefix}`, label: aiSiteImportPrefixLabel(prefix), kind: 'collection', pages: sized.get(`c:${prefix}`) ?? 0 })\n }\n return { groups, groupOf }\n}\n\n/**\n * The main pages in the order a visitor meets them: home, then the menu's\n * order, then the rest as the crawl read them. A collection keeps the crawl's\n * order, which is its sitemap's.\n */\nfunction orderedWithin(\n group: string,\n pages: readonly AiSiteImportListedPage[],\n menu: readonly string[],\n): AiSiteImportListedPage[] {\n if (group !== 'main') return [...pages]\n const rank = (page: AiSiteImportListedPage) => {\n if (page.foundBy === 'home' || segmentsOf(page.path).length === 0) return -1\n const path = page.path.replace(/\\/+$/, '') || '/'\n const index = menu.findIndex((entry) => (entry.replace(/\\/+$/, '') || '/') === path)\n return index === -1 ? Number.POSITIVE_INFINITY : index\n }\n return pages\n .map((page, order) => ({ page, order }))\n .sort((a, b) => rank(a.page) - rank(b.page) || a.order - b.order)\n .map(({ page }) => page)\n}\n\n/**\n * The address a page takes on the new site: one segment, as every Aglyn page\n * has, from the last segment of its old address; home is the root. Two pages\n * that would take the same address are told apart by their collection, then\n * by a number. The draft writer still makes it unique on the site itself.\n */\nexport function aiSiteImportSlug(path: string, taken: Set<string>): string {\n const segments = segmentsOf(path)\n if (!segments.length) {\n if (!taken.has('/')) {\n taken.add('/')\n return '/'\n }\n }\n const clean = (value: string) =>\n decodeURIComponent(value)\n .toLowerCase()\n .replace(/\\.(?:html?|php|aspx?)$/, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n const last = clean(segments[segments.length - 1] ?? '') || 'page'\n const prefixed = segments.length > 1 ? clean(`${segments[0]}-${segments[segments.length - 1]}`) : last\n for (const candidate of [last, prefixed]) {\n if (candidate && !taken.has(candidate)) {\n taken.add(candidate)\n return candidate\n }\n }\n let attempt = 2\n while (taken.has(`${last}-${attempt}`)) attempt += 1\n const slug = `${last}-${attempt}`\n taken.add(slug)\n return slug\n}\n\nexport interface AiSiteImportInventoryInput {\n domain: string\n siteName: string\n ownership: AiSiteImportState['ownership']\n readAt: string\n pages: readonly AiSiteImportListedPage[]\n /** Addresses the home page's menu links, in its order. */\n menu: readonly string[]\n /** What the crawl could not read, by reason. */\n skipped: ReadonlyArray<{ url: string; reason: AiSiteImportLeftOutReason }>\n /** Skips the crawl only counted. */\n skippedOverflow?: number\n stoppedBy: string | null\n /** The plan's page count; `null` for none. */\n pageLimit: number | null\n existingPages: number\n /** Mints one draft id; the inventory names every page's draft before any is written. */\n mintId: () => string\n reviewId?: string | null\n wavePages?: number\n}\n\n/**\n * The site's inventory cut into waves, every page left out said with its\n * reason. Pure: the step reads the site, this decides what comes in.\n */\nexport function aiSiteImportInventory(input: AiSiteImportInventoryInput): AiSiteImportState {\n const wavePages = Math.max(1, Math.floor(input.wavePages ?? AI_SITE_IMPORT_WAVE_PAGES))\n const leftOut: AiSiteImportLeftOut[] = []\n const leftOutCount: Partial<Record<AiSiteImportLeftOutReason, number>> = {}\n const leave = (entry: AiSiteImportLeftOut) => {\n leftOutCount[entry.reason] = (leftOutCount[entry.reason] ?? 0) + 1\n if (leftOut.length < AI_SITE_IMPORT_LEFT_OUT_LISTED) leftOut.push(entry)\n }\n for (const skipped of input.skipped) {\n let path = '/'\n try {\n path = new URL(skipped.url).pathname\n } catch {\n // A malformed address keeps the root as its path.\n }\n leave({ url: skipped.url, path, title: '', reason: skipped.reason })\n }\n if (input.skippedOverflow) leftOutCount['unreadable'] = (leftOutCount['unreadable'] ?? 0) + input.skippedOverflow\n\n const readable = input.pages.filter((page) => {\n if (page.words >= AI_SITE_IMPORT_MIN_WORDS || page.images > 0) return true\n leave({ url: page.url, path: page.path, title: clipLabel(page.title), reason: 'empty' })\n return false\n })\n const { groups, groupOf } = aiSiteImportGroups(readable, input.menu)\n const limit = input.pageLimit === null || !Number.isFinite(input.pageLimit) ? null : Math.max(0, Math.floor(input.pageLimit))\n let room = limit === null ? Number.POSITIVE_INFINITY : Math.max(0, limit - Math.max(0, input.existingPages))\n const pages: AiSiteImportPage[] = []\n const waves: AiSiteImportWave[] = []\n const taken = new Set<string>()\n for (const group of groups) {\n const members = orderedWithin(group.id, readable.filter((page) => groupOf(page) === group.id), input.menu)\n const kept: AiSiteImportListedPage[] = []\n for (const page of members) {\n if (room > 0) {\n kept.push(page)\n room -= 1\n } else {\n leave({ url: page.url, path: page.path, title: clipLabel(page.title), reason: 'plan-limit' })\n }\n }\n const parts = Math.ceil(kept.length / wavePages)\n for (let part = 0; part < parts; part += 1) {\n const index = waves.length\n const slice = kept.slice(part * wavePages, (part + 1) * wavePages)\n const slots: string[] = []\n for (const page of slice) {\n const slot = `p${pages.length}`\n slots.push(slot)\n pages.push({\n slot,\n url: page.url,\n path: page.path,\n title: clipLabel(page.title) || aiSiteImportPrefixLabel(segmentsOf(page.path).pop() ?? 'Home'),\n group: group.id,\n wave: index,\n draftId: input.mintId(),\n slug: aiSiteImportSlug(page.path, taken),\n words: page.words,\n images: page.images,\n })\n }\n waves.push({\n index,\n label: parts > 1 ? `${group.label}, ${part + 1} of ${parts}` : group.label,\n group: group.id,\n slots,\n frame: index === 0 && group.kind === 'main',\n status: 'waiting',\n startedAt: null,\n finishedAt: null,\n })\n }\n }\n return {\n domain: input.domain,\n siteName: clipLabel(input.siteName) || input.domain,\n ownership: input.ownership,\n readAt: input.readAt,\n pageLimit: limit,\n existingPages: Math.max(0, input.existingPages),\n found: input.pages.length,\n stoppedBy: input.stoppedBy,\n groups,\n pages,\n waves,\n leftOut,\n leftOutCount,\n frame: waves[0]?.frame ? { draftId: input.mintId() } : null,\n reviewId: input.reviewId ?? null,\n }\n}\n\n/** The item ledger an inventory starts: the header and footer, then every page, all pending. */\nexport function aiSiteImportLedger(state: AiSiteImportState): AiJobItemLedger[] {\n const row = (slot: string, op: string, label: string): AiJobItemLedger => ({\n slot,\n op,\n label,\n status: 'pending',\n attempt: 1,\n creditsSpent: 0,\n creditsRefunded: 0,\n outputs: [],\n })\n return [\n ...(state.frame ? [row(AI_SITE_IMPORT_FRAME_SLOT, 'layout', 'Header and footer')] : []),\n ...state.pages.map((page) => row(page.slot, 'page', page.title)),\n ]\n}\n\n/*------------------------------------------\n * What runs next\n *-----------------------------------------*/\n\nexport type AiSiteImportWork =\n | { kind: 'inventory' }\n | { kind: 'start-wave'; wave: AiSiteImportWave }\n /** Pages of the running wave its read did not reach before its clock ran out. */\n | { kind: 'read'; wave: AiSiteImportWave; pages: AiSiteImportPage[] }\n | { kind: 'frame'; wave: AiSiteImportWave }\n | { kind: 'images'; wave: AiSiteImportWave; page: AiSiteImportPage }\n | { kind: 'page'; wave: AiSiteImportWave; page: AiSiteImportPage }\n | { kind: 'close-wave'; wave: AiSiteImportWave }\n | { kind: 'done' }\n\nconst OPEN = new Set(['pending', 'running'])\n\n/** Whether an item is still to be built. */\nexport function aiSiteImportItemOpen(items: readonly AiJobItemLedger[] | null | undefined, slot: string): boolean {\n const row = (items ?? []).find((entry) => entry.slot === slot)\n return Boolean(row && OPEN.has(row.status))\n}\n\n/** The wave running now, else `null`. */\nexport function aiSiteImportRunningWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n return state?.waves.find((wave) => wave.status === 'running') ?? null\n}\n\n/** The next wave a person may start, else `null`. */\nexport function aiSiteImportNextWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n if (!state || aiSiteImportRunningWave(state)) return null\n return state.waves.find((wave) => wave.status === 'waiting') ?? null\n}\n\n/** The wave a person is reviewing, else `null`. */\nexport function aiSiteImportReviewWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n return state?.waves.find((wave) => wave.status === 'review') ?? null\n}\n\n/** What the next pass of the step does. */\nexport function aiSiteImportNextWork(\n state: AiSiteImportState | null | undefined,\n items: readonly AiJobItemLedger[] | null | undefined,\n): AiSiteImportWork {\n if (!state) return { kind: 'inventory' }\n const running = aiSiteImportRunningWave(state)\n if (!running) {\n const next = aiSiteImportNextWave(state)\n return next ? { kind: 'start-wave', wave: next } : { kind: 'done' }\n }\n // A page the wave's read has not reached yet is read before anything is built.\n const unread = running.slots\n .map((slot) => state.pages.find((page) => page.slot === slot))\n .filter((page): page is AiSiteImportPage => !!page && !page.read && !page.refused && aiSiteImportItemOpen(items, page.slot))\n if (unread.length) return { kind: 'read', wave: running, pages: unread }\n if (running.frame && state.frame && aiSiteImportItemOpen(items, AI_SITE_IMPORT_FRAME_SLOT)) {\n return { kind: 'frame', wave: running }\n }\n for (const slot of running.slots) {\n if (!aiSiteImportItemOpen(items, slot)) continue\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page) continue\n return page.photos ? { kind: 'page', wave: running, page } : { kind: 'images', wave: running, page }\n }\n return { kind: 'close-wave', wave: running }\n}\n\n/*------------------------------------------\n * The state, one pass at a time\n *-----------------------------------------*/\n\nconst withPage = (state: AiSiteImportState, slot: string, patch: Partial<AiSiteImportPage>): AiSiteImportState => ({\n ...state,\n pages: state.pages.map((page) => (page.slot === slot ? { ...page, ...patch } : page)),\n})\n\nconst withWave = (state: AiSiteImportState, index: number, patch: Partial<AiSiteImportWave>): AiSiteImportState => ({\n ...state,\n waves: state.waves.map((wave) => (wave.index === index ? { ...wave, ...patch } : wave)),\n})\n\n/** A wave started: running, with what its read found on each page. */\nexport function aiSiteImportWaveStarted(\n state: AiSiteImportState,\n index: number,\n at: string,\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiSiteImportState {\n // The wave reviewed before this one is done: starting the next is the person's say-so.\n const next = withWave(\n { ...state, waves: state.waves.map((wave) => (wave.status === 'review' ? { ...wave, status: 'done' as const } : wave)) },\n index,\n { status: 'running', startedAt: at, finishedAt: null },\n )\n return aiSiteImportPagesRead(next, reads)\n}\n\n/** More of a running wave's pages, read now. */\nexport function aiSiteImportPagesRead(\n state: AiSiteImportState,\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiSiteImportState {\n let next = state\n for (const [slot, read] of reads) {\n next =\n typeof read === 'string'\n ? withPage(next, slot, { refused: read, read: null })\n : withPage(next, slot, { read, refused: null, title: clipLabel(read.title) || next.pages.find((page) => page.slot === slot)?.title || '' })\n }\n return next\n}\n\n/** A page's photos are in the library. */\nexport function aiSiteImportPhotosIn(\n state: AiSiteImportState,\n slot: string,\n photos: NonNullable<AiSiteImportPage['photos']>,\n): AiSiteImportState {\n return withPage(state, slot, { photos })\n}\n\n/** A page is in: its text is no longer kept. */\nexport function aiSiteImportPageSettled(state: AiSiteImportState, slot: string): AiSiteImportState {\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page?.read) return state\n return withPage(state, slot, { read: { ...page.read, text: '' } })\n}\n\n/** A wave is in: waiting for its review, or done when no wave follows it. */\nexport function aiSiteImportWaveClosed(state: AiSiteImportState, index: number, at: string): AiSiteImportState {\n const later = state.waves.some((wave) => wave.index > index && wave.status === 'waiting')\n let next = withWave(state, index, { status: later ? 'review' : 'done', finishedAt: at })\n // What the wave read is no longer needed by any model answer.\n for (const slot of state.waves.find((wave) => wave.index === index)?.slots ?? []) next = aiSiteImportPageSettled(next, slot)\n return next\n}\n\n/** The state as the console reads it: every page's read text left off. */\nexport function aiSiteImportWire(state: AiSiteImportState | null | undefined): AiSiteImportState | null {\n if (!state) return null\n return {\n ...state,\n pages: state.pages.map((page) =>\n page.read ? { ...page, read: { ...page.read, text: '', images: [] } } : page,\n ),\n }\n}\n\n/*------------------------------------------\n * Credits\n *-----------------------------------------*/\n\n/**\n * What bringing in this many pages costs, quoted before it starts (AGL-3976):\n * each page an AI page as measured, its ceiling its two passes, and the\n * site's header and footer once when the wave builds them.\n */\nexport function aiSiteImportCreditRange(pages: number, options: { frame?: boolean } = {}): AiCreditRange {\n const count = Math.max(0, Math.floor(Number.isFinite(pages) ? pages : 0))\n const { page, frame } = AI_MEASURED_IMPORT_CREDITS\n return aiCreditRangeOrdered({\n likely: count * page.median + (options.frame ? frame.median : 0),\n p90: count * page.p90 + (options.frame ? frame.p90 : 0),\n ceiling: count * AI_SITE_IMPORT_PAGE_CEILING_CREDITS + (options.frame ? AI_SITE_IMPORT_FRAME_CEILING_CREDITS : 0),\n })\n}\n\n/** A wave's quote: the pages it has still to bring in, and its header and footer while they are owed. */\nexport function aiSiteImportWaveCreditRange(\n state: AiSiteImportState,\n wave: AiSiteImportWave,\n items?: readonly AiJobItemLedger[] | null,\n): AiCreditRange {\n const owed = (slot: string) => (items ? aiSiteImportItemOpen(items, slot) : true)\n const pages = wave.slots.filter((slot) => owed(slot) && !state.pages.find((page) => page.slot === slot)?.refused).length\n return aiSiteImportCreditRange(pages, { frame: wave.frame && !!state.frame && owed(AI_SITE_IMPORT_FRAME_SLOT) })\n}\n\n/*------------------------------------------\n * Words\n *-----------------------------------------*/\n\n/** Why a page is not brought in, as a person reads it. */\nexport const AI_SITE_IMPORT_LEFT_OUT_COPY: Readonly<Record<AiSiteImportLeftOutReason, string>> = {\n 'plan-limit': 'Past your plan’s page count. Upgrade for more pages, then bring the rest in.',\n robots: 'The site’s robots.txt asks AglynBot not to read it.',\n 'login-form': 'A sign-in page. Pages that ask for a password are never brought in.',\n empty: 'Nothing to bring in: the page has no text or photos.',\n unreadable: 'It could not be read just now.',\n 'too-large': 'Larger than a page can be to bring in.',\n 'off-site': 'It sends visitors to another website.',\n}\n\n/** \"3 pages\" / \"1 page\". */\nexport function aiSiteImportPagesText(count: number): string {\n return `${count.toLocaleString('en-US')} ${count === 1 ? 'page' : 'pages'}`\n}\n\n/** The review a finished wave parks on, or the first wave's go-ahead once the site is read. */\nexport function aiSiteImportWaveMessage(\n state: AiSiteImportState,\n items: readonly AiJobItemLedger[] | null | undefined,\n): string {\n const next = aiSiteImportNextWave(state)\n const reviewing = aiSiteImportReviewWave(state)\n if (!reviewing) {\n const kept = state.pages.length\n return next\n ? `Your site is read: ${aiSiteImportPagesText(kept)} to bring in, in ${state.waves.length === 1 ? 'one wave' : `${state.waves.length} waves`}. Start the first wave when you are ready.`\n : 'Your site is read, and nothing on it can be brought in.'\n }\n const rows = (items ?? []).filter((row) => reviewing.slots.includes(row.slot))\n const done = rows.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const lead = `${reviewing.label} is in: ${aiSiteImportPagesText(done)} ready to review as drafts.`\n return next ? `${lead} Start ${next.label} when you are ready.` : lead\n}\n"],"names":["aiCreditRangeOrdered","AI_MEASURED_IMPORT_CREDITS","AI_SITE_IMPORT_KIND","AI_SITE_IMPORT_DOMAIN_INPUT","AI_SITE_IMPORT_WAVE_PAGES","AI_SITE_IMPORT_MAX_PAGES","AI_SITE_IMPORT_COLLECTION_MIN","AI_SITE_IMPORT_IMAGES_PER_PAGE","AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS","AI_SITE_IMPORT_MAX_SECTIONS","AI_SITE_IMPORT_LABEL_MAX_CHARS","AI_SITE_IMPORT_MIN_WORDS","AI_SITE_IMPORT_FRAME_SLOT","AI_SITE_IMPORT_PAGE_CEILING_CREDITS","AI_SITE_IMPORT_FRAME_CEILING_CREDITS","AI_SITE_IMPORT_LEFT_OUT_LISTED","AI_SITE_IMPORT_LEFT_OUT_REASONS","clipLabel","text","line","String","replace","trim","length","slice","trimEnd","segmentsOf","path","split","filter","Boolean","aiSiteImportPrefix","segments","first","toLowerCase","test","aiSiteImportPrefixLabel","prefix","words","decodeURIComponent","charAt","toUpperCase","aiSiteImportGroups","pages","menu","sized","counts","Map","page","set","get","menuPaths","Set","map","collections","count","groupOf","foundBy","has","menuRank","index","findIndex","Number","POSITIVE_INFINITY","ordered","sort","a","b","localeCompare","groups","push","id","label","kind","orderedWithin","group","rank","entry","order","aiSiteImportSlug","taken","add","clean","value","last","prefixed","candidate","attempt","slug","aiSiteImportInventory","input","leftOutCount","waves","wavePages","Math","max","floor","leftOut","leave","reason","skipped","URL","url","pathname","title","skippedOverflow","readable","images","limit","pageLimit","isFinite","room","existingPages","members","kept","parts","ceil","part","slots","slot","pop","wave","draftId","mintId","frame","status","startedAt","finishedAt","domain","siteName","ownership","readAt","found","stoppedBy","reviewId","aiSiteImportLedger","state","row","op","creditsSpent","creditsRefunded","outputs","OPEN","aiSiteImportItemOpen","items","find","aiSiteImportRunningWave","aiSiteImportNextWave","aiSiteImportReviewWave","aiSiteImportNextWork","running","next","unread","read","refused","photos","withPage","patch","withWave","aiSiteImportWaveStarted","at","reads","aiSiteImportPagesRead","aiSiteImportPhotosIn","aiSiteImportPageSettled","aiSiteImportWaveClosed","later","some","aiSiteImportWire","aiSiteImportCreditRange","options","likely","median","p90","ceiling","aiSiteImportWaveCreditRange","owed","AI_SITE_IMPORT_LEFT_OUT_COPY","robots","empty","unreadable","aiSiteImportPagesText","toLocaleString","aiSiteImportWaveMessage","reviewing","rows","includes","done","lead"],"mappings":"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"}
|