@aglyn/plugins-ai 1.0.0-beta.287 → 1.0.0-beta.288
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.js +5 -3
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +2 -1
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +97 -4
- package/src/lib/jobs/ai-layout-stock-photos.js +635 -154
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
- package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +9 -1
- package/src/lib/layout-language/ai-layout-pictures.js +14 -5
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
- package/src/lib/model/ai-plan-said-sections.js +22 -5
- package/src/lib/model/ai-plan-said-sections.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +6 -2
- package/src/lib/model/ai-site-context.js +8 -2
- package/src/lib/model/ai-site-context.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +26 -5
- package/src/lib/runtime/ai-dataset-generation.js +110 -9
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +2 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +5 -4
- package/src/lib/runtime/ai-doctrine.js +2 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/server/assist-build.d.ts +15 -1
- package/src/lib/server/assist-build.js +29 -3
- package/src/lib/server/assist-build.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-ai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.288",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-errors": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
41
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.288",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.288",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.288",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.288",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.288",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.288",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.288",
|
|
35
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.288",
|
|
36
|
+
"@aglyn/shared-util-email": "1.0.0-beta.288",
|
|
37
|
+
"@aglyn/shared-util-errors": "1.0.0-beta.288",
|
|
38
|
+
"@aglyn/shared-util-http": "1.0.0-beta.288",
|
|
39
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.288",
|
|
40
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.288",
|
|
41
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.288",
|
|
42
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.288",
|
|
43
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.288",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -23,8 +23,8 @@ import { prefillAiBusinessProfile } from "../runtime/site-context.js";
|
|
|
23
23
|
*
|
|
24
24
|
* Two sources, each recorded as itself:
|
|
25
25
|
* - `start`: the answers the owner gave the guided start — what the business
|
|
26
|
-
* is, who it is for, the areas it serves (else its city)
|
|
27
|
-
* given in another place.
|
|
26
|
+
* is, who it is for, the areas it serves (else its city), the year it was
|
|
27
|
+
* founded (AGL-4092). Their words, given in another place.
|
|
28
28
|
* - `ai`: what the plan wrote for the home page's search description, used
|
|
29
29
|
* only where the start said nothing about what the business does.
|
|
30
30
|
*
|
|
@@ -45,12 +45,15 @@ import { prefillAiBusinessProfile } from "../runtime/site-context.js";
|
|
|
45
45
|
// The areas "More details" names (AGL-3979), as many whole ones as the field holds; else the city.
|
|
46
46
|
const max = BUSINESS_PROFILE_MAX_CHARS.serviceArea;
|
|
47
47
|
let serviceArea = '';
|
|
48
|
-
|
|
48
|
+
const details = aiSiteDetailsOf(inputs);
|
|
49
|
+
for (const area of aiSiteServiceAreas(details.serviceArea)){
|
|
49
50
|
const next = serviceArea ? `${serviceArea}, ${area}` : area;
|
|
50
51
|
if (next.length > max) break;
|
|
51
52
|
serviceArea = next;
|
|
52
53
|
}
|
|
53
54
|
if (serviceArea || city) values.serviceArea = (serviceArea || city).slice(0, max);
|
|
55
|
+
// The year "More details" names (AGL-4092), so a later build reads it from the profile instead of marking a gap for it.
|
|
56
|
+
if (details.founded) values.founded = details.founded.slice(0, BUSINESS_PROFILE_MAX_CHARS.founded);
|
|
54
57
|
return Object.keys(values).length ? values : null;
|
|
55
58
|
}
|
|
56
59
|
/** The plan's home page description, as a line about the business; `null` when it has none. */ export function aiBusinessProfilePlanValues(job) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-business-profile-prefill.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 { BUSINESS_PROFILE_MAX_CHARS, type BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { aiSiteDetailsOf, aiSiteServiceAreas } from '../model/ai-site-details'\nimport { prefillAiBusinessProfile } from '../runtime/site-context'\nimport type { AiJobTransitionListener } from './ai-jobs'\n\n/**\n * A site job fills in the site's business profile (AGL-3661), so the next job\n * — and the owner, on Setup → Business profile — starts from what the guided\n * start was told rather than from nothing.\n *\n * Two sources, each recorded as itself:\n * - `start`: the answers the owner gave the guided start — what the business\n * is, who it is for, the areas it serves (else its city). Their words
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-business-profile-prefill.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 { BUSINESS_PROFILE_MAX_CHARS, type BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { aiSiteDetailsOf, aiSiteServiceAreas } from '../model/ai-site-details'\nimport { prefillAiBusinessProfile } from '../runtime/site-context'\nimport type { AiJobTransitionListener } from './ai-jobs'\n\n/**\n * A site job fills in the site's business profile (AGL-3661), so the next job\n * — and the owner, on Setup → Business profile — starts from what the guided\n * start was told rather than from nothing.\n *\n * Two sources, each recorded as itself:\n * - `start`: the answers the owner gave the guided start — what the business\n * is, who it is for, the areas it serves (else its city), the year it was\n * founded (AGL-4092). Their words, given in another place.\n * - `ai`: what the plan wrote for the home page's search description, used\n * only where the start said nothing about what the business does.\n *\n * Neither ever replaces what the owner typed into the profile; the merge in\n * `@aglyn/aglyn/app-utils/business-profile` decides that, not this file.\n */\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** \"a local dog groomer\" reads as a line once its first letter is a capital. */\nconst sentence = (value: string): string => (value ? value[0].toUpperCase() + value.slice(1) : value)\n\n/** The guided start's answers, as profile values; `null` when it gave none. */\nexport function aiBusinessProfileStartValues(job: Pick<AiJob, 'kind' | 'inputs'>): BusinessProfileValues | null {\n if (job.kind !== 'site') return null\n const inputs = job.inputs ?? {}\n const values: BusinessProfileValues = {}\n const businessType = text(inputs['businessType'])\n const audience = text(inputs['audience'])\n const city = text(inputs['city'])\n if (businessType) values.whatYouDo = sentence(businessType)\n if (audience) values.audience = audience\n // The areas \"More details\" names (AGL-3979), as many whole ones as the field holds; else the city.\n const max = BUSINESS_PROFILE_MAX_CHARS.serviceArea\n let serviceArea = ''\n const details = aiSiteDetailsOf(inputs)\n for (const area of aiSiteServiceAreas(details.serviceArea)) {\n const next = serviceArea ? `${serviceArea}, ${area}` : area\n if (next.length > max) break\n serviceArea = next\n }\n if (serviceArea || city) values.serviceArea = (serviceArea || city).slice(0, max)\n // The year \"More details\" names (AGL-4092), so a later build reads it from the profile instead of marking a gap for it.\n if (details.founded) values.founded = details.founded.slice(0, BUSINESS_PROFILE_MAX_CHARS.founded)\n return Object.keys(values).length ? values : null\n}\n\n/** The plan's home page description, as a line about the business; `null` when it has none. */\nexport function aiBusinessProfilePlanValues(job: Pick<AiJob, 'kind' | 'plan'>): BusinessProfileValues | null {\n if (job.kind !== 'site') return null\n const screens = job.plan?.screens ?? []\n const home = screens.find((screen) => ['', '/', 'home', 'index'].includes(text(screen.slug).replace(/^\\/+|\\/+$/g, '')))\n const description = text(home?.seoDescription)\n return description ? { whatYouDo: description } : null\n}\n\n/**\n * The transition listener's half that prefills (AGL-3661): on a site job's\n * plan and on its finish, the start's answers and then the plan's guess, each\n * a no-op once written. A failure is logged and never fails the job.\n */\nexport function aiBusinessProfilePrefiller(\n firestoreOf: () => FirebaseFirestore.Firestore,\n): AiJobTransitionListener {\n return async ({ job, to }) => {\n if (job.kind !== 'site' || !job.hostId || to === 'failed') return\n const start = aiBusinessProfileStartValues(job)\n const plan = aiBusinessProfilePlanValues(job)\n if (!start && !plan) return\n try {\n const firestore = firestoreOf()\n if (start) await prefillAiBusinessProfile(firestore, job.hostId, start, 'start')\n if (plan) await prefillAiBusinessProfile(firestore, job.hostId, plan, 'ai')\n } catch (error) {\n console.warn('ai business profile prefill failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n }\n}\n"],"names":["BUSINESS_PROFILE_MAX_CHARS","aiSiteDetailsOf","aiSiteServiceAreas","prefillAiBusinessProfile","text","value","trim","sentence","toUpperCase","slice","aiBusinessProfileStartValues","job","kind","inputs","values","businessType","audience","city","whatYouDo","max","serviceArea","details","area","next","length","founded","Object","keys","aiBusinessProfilePlanValues","screens","plan","home","find","screen","includes","slug","replace","description","seoDescription","aiBusinessProfilePrefiller","firestoreOf","to","hostId","start","firestore","error","console","warn","orgId","jobId","$id"],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ import { AI_TYPED_LIST_MIN_ITEMS } from "../runtime/ai-doctrine-validators.js";
|
|
|
23
23
|
import { AI_SITE_DATASETS_MAX } from "../model/ai-site-job.js";
|
|
24
24
|
import { AI_STEP_TIERS } from "../providers/catalog.js";
|
|
25
25
|
import { aiModelForStep } from "../providers/routing.js";
|
|
26
|
-
import { AI_DATASET_MAX_TOKENS, AI_DATASET_PLACEHOLDER_CODE, AI_DATASET_STEP, aiDatasetDraftContent, aiDatasetListedAsPast, aiDatasetRealRecords, aiDatasetSampleDates, aiDatasetWithoutPlaceholders, generateAiDataset } from "../runtime/ai-dataset-generation.js";
|
|
26
|
+
import { AI_DATASET_MAX_TOKENS, AI_DATASET_PLACEHOLDER_CODE, AI_DATASET_STEP, aiDatasetDraftContent, aiDatasetListedAsPast, aiDatasetRealRecords, aiDatasetSampleDates, aiDatasetWithoutInstructions, aiDatasetWithoutPlaceholders, generateAiDataset } from "../runtime/ai-dataset-generation.js";
|
|
27
27
|
import { aiLayoutListingId, aiLayoutRecordBaseOf, aiLayoutRecordsPlacements } from "../layout-language/ai-layout-listings.js";
|
|
28
28
|
import { aiLayoutNeutralStarterRun, aiLayoutSeedNumber, aiLayoutStarterPhotos } from "../layout-language/ai-layout-pictures.js";
|
|
29
29
|
import { aiSiteWords } from "../model/ai-site-job.js";
|
|
@@ -936,8 +936,10 @@ function proposalOf(output) {
|
|
|
936
936
|
});
|
|
937
937
|
// An answer that still numbers placeholders after its re-ask is kept with
|
|
938
938
|
// the records that name real things — none, as often as not — and written
|
|
939
|
-
// with its fields, for the owner to fill (AGL-4065).
|
|
940
|
-
|
|
939
|
+
// with its fields, for the owner to fill (AGL-4065). An instruction to the
|
|
940
|
+
// owner in it is taken out first, so a sample keeps the rest of what it
|
|
941
|
+
// says, and only a row nothing is left of goes (AGL-4092).
|
|
942
|
+
const salvaged = generation.status === 'needs_input' && generation.violations.some((violation)=>violation.code === AI_DATASET_PLACEHOLDER_CODE) ? aiDatasetWithoutPlaceholders(aiDatasetWithoutInstructions(generation.answer), {
|
|
941
943
|
planned: creation.fields,
|
|
942
944
|
merchantWords: job.brief,
|
|
943
945
|
name: creation.name
|