@aglyn/plugins-ai 1.0.0-beta.261 → 1.0.0-beta.262

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.
Files changed (58) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-brief-dialog.component.d.ts +21 -58
  3. package/src/lib/components/ai-brief-dialog.component.js +8 -132
  4. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  5. package/src/lib/components/ai-job-follow.component.js +2 -35
  6. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  7. package/src/lib/components/ai-job-plan.component.d.ts +2 -8
  8. package/src/lib/components/ai-job-plan.component.js +13 -74
  9. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  10. package/src/lib/components/ai-template-collection-field.component.d.ts +2 -12
  11. package/src/lib/components/ai-template-collection-field.component.js +13 -10
  12. package/src/lib/components/ai-template-collection-field.component.js.map +1 -1
  13. package/src/lib/components/ai-upsell-dialog.component.d.ts +3 -25
  14. package/src/lib/components/ai-upsell-dialog.component.js +9 -66
  15. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  16. package/src/lib/components/use-ai-job-run.d.ts +3 -8
  17. package/src/lib/components/use-ai-job-run.js +4 -15
  18. package/src/lib/components/use-ai-job-run.js.map +1 -1
  19. package/src/lib/insights/ai-figure-readers.js +15 -5
  20. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  21. package/src/lib/insights/ai-figure-summaries.d.ts +27 -0
  22. package/src/lib/insights/ai-figure-summaries.js +79 -0
  23. package/src/lib/insights/ai-figure-summaries.js.map +1 -0
  24. package/src/lib/insights/ai-insight-readers.d.ts +2 -1
  25. package/src/lib/insights/ai-insight-readers.js +11 -26
  26. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  27. package/src/lib/jobs/ai-layout-stock-photos.js +11 -1
  28. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  29. package/src/lib/model/ai-brief-copy.d.ts +85 -0
  30. package/src/lib/model/ai-brief-copy.js +152 -0
  31. package/src/lib/model/ai-brief-copy.js.map +1 -0
  32. package/src/lib/model/ai-create-view.d.ts +200 -0
  33. package/src/lib/model/ai-create-view.js +263 -0
  34. package/src/lib/model/ai-create-view.js.map +1 -0
  35. package/src/lib/model/ai-insight.d.ts +1 -1
  36. package/src/lib/model/ai-insight.js +4 -39
  37. package/src/lib/model/ai-insight.js.map +1 -1
  38. package/src/lib/model/ai-job-activity.d.ts +24 -0
  39. package/src/lib/model/ai-job-activity.js +52 -1
  40. package/src/lib/model/ai-job-activity.js.map +1 -1
  41. package/src/lib/model/ai-job-plan-lines.d.ts +46 -0
  42. package/src/lib/model/ai-job-plan-lines.js +70 -0
  43. package/src/lib/model/ai-job-plan-lines.js.map +1 -0
  44. package/src/lib/model/ai-template-collections.d.ts +28 -0
  45. package/src/lib/model/ai-template-collections.js +28 -0
  46. package/src/lib/model/ai-template-collections.js.map +1 -0
  47. package/src/lib/model/ai-upsell-copy.d.ts +41 -0
  48. package/src/lib/model/ai-upsell-copy.js +85 -0
  49. package/src/lib/model/ai-upsell-copy.js.map +1 -0
  50. package/src/lib/runtime/ai-doctrine-validators.js +16 -2
  51. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  52. package/src/lib/runtime/ai-eval-memory-firestore.js +2 -0
  53. package/src/lib/runtime/ai-eval-memory-firestore.js.map +1 -1
  54. package/src/lib/server/ai-create-route.d.ts +32 -0
  55. package/src/lib/server/ai-create-route.js +295 -0
  56. package/src/lib/server/ai-create-route.js.map +1 -0
  57. package/src/lib/server.js +14 -0
  58. package/src/lib/server.js.map +1 -1
@@ -0,0 +1,85 @@
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 { AiEmailType } from '../jobs/ai-job-email-step';
18
+ import type { AiJobKind } from './ai-jobs.types';
19
+ import type { AiPageType } from './ai-page-job';
20
+ import { type AiTemplateSubject } from './ai-template-subjects';
21
+ /**
22
+ * WHAT EVERY "CREATE WITH AI" BRIEF SAYS AND SENDS (AGL-2907, AGL-3043,
23
+ * AGL-3051, AGL-3596): each kind's words, the choices beside the brief, and
24
+ * the job and inputs they start. Pure, so the console's brief dialog and the
25
+ * door the native apps read (AGL-3854) say the same thing from one place.
26
+ */
27
+ /** The longest brief a job admits. */
28
+ export declare const AI_BRIEF_MAX_CHARS = 4000;
29
+ /** The job kinds a member describes from a console page. */
30
+ export type AiBriefKind = Extract<AiJobKind, 'page' | 'template' | 'layout' | 'form' | 'component' | 'email' | 'campaign'>;
31
+ /** What the dialog says for one kind. */
32
+ export interface AiBriefCopy {
33
+ title: string;
34
+ /** The brief box's label. */
35
+ briefLabel: string;
36
+ placeholder: string;
37
+ /** What happens after the member starts the job, under the fields. */
38
+ next: string;
39
+ /** The action that starts the job. */
40
+ submit: string;
41
+ /** What the dialog says once the job exists, above its live status. */
42
+ started: string;
43
+ /** What it says when the door gave no reason, or could not be reached. */
44
+ failed: string;
45
+ }
46
+ export declare const AI_BRIEF_COPY: Readonly<Record<AiBriefKind, AiBriefCopy>>;
47
+ /**
48
+ * The kinds of email a member picks from, as their chips name them. The jobs'
49
+ * own catalog (`AI_EMAIL_TYPES`) also holds `reply`, which only a job the
50
+ * platform composes sets, so it is not offered here.
51
+ */
52
+ export declare const AI_EMAIL_TYPE_LABELS: Readonly<Record<Exclude<AiEmailType, 'reply'>, string>>;
53
+ /** Each template subject as its chip names it: what one page is drawn for. */
54
+ export declare const AI_TEMPLATE_SUBJECT_LABELS: Readonly<Record<AiTemplateSubject, string>>;
55
+ /** What a member picked beside the brief; each kind reads only its own. */
56
+ export interface AiBriefChoice {
57
+ /** A page's type, or `null` for none. */
58
+ pageType: AiPageType | null;
59
+ /** What a template's pages are drawn for, or `null` until one is picked. */
60
+ subject: AiTemplateSubject | null;
61
+ /** A collection entry template's collection, or `null` until one is picked. */
62
+ collectionId: string | null;
63
+ /** An email's or a campaign's kind of email; `null` or absent for none. */
64
+ emailType?: Exclude<AiEmailType, 'reply'> | null;
65
+ /** An email's door only: also draft the campaign that would send it. */
66
+ withCampaign?: boolean;
67
+ }
68
+ export declare const AI_BRIEF_NO_CHOICE: AiBriefChoice;
69
+ /**
70
+ * The job the door starts: its own kind, except an email that also asked for
71
+ * its campaign, which is a `campaign` job — the campaign step writes the same
72
+ * design and drafts the campaign around it.
73
+ */
74
+ export declare function aiBriefJobKind(kind: AiBriefKind, choice: AiBriefChoice): AiBriefKind;
75
+ /**
76
+ * The job's `inputs` for what the member picked, or `null` while the kind's
77
+ * door would refuse them.
78
+ *
79
+ * A template's are read by the parser its door reads, so the dialog cannot
80
+ * send a subject the door does not know or an entry page with no collection.
81
+ * A page's type is optional, and so is an email's or a campaign's kind of
82
+ * email (`inputs.emailType`, which both steps read). A layout, a form and a component read no inputs
83
+ * at all (AGL-2909, AGL-2913, AGL-2908), so theirs are empty.
84
+ */
85
+ export declare function aiBriefJobInputs(kind: AiBriefKind, choice: AiBriefChoice): Record<string, string> | null;
@@ -0,0 +1,152 @@
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 { parseAiTemplateJobInputs } from "./ai-template-subjects.js";
17
+ /**
18
+ * WHAT EVERY "CREATE WITH AI" BRIEF SAYS AND SENDS (AGL-2907, AGL-3043,
19
+ * AGL-3051, AGL-3596): each kind's words, the choices beside the brief, and
20
+ * the job and inputs they start. Pure, so the console's brief dialog and the
21
+ * door the native apps read (AGL-3854) say the same thing from one place.
22
+ */ /** The longest brief a job admits. */ export const AI_BRIEF_MAX_CHARS = 4000;
23
+ export const AI_BRIEF_COPY = {
24
+ page: {
25
+ title: 'Describe a page',
26
+ briefLabel: 'What is the page for?',
27
+ placeholder: 'A landing page for our spring roof inspection offer, with the quote request form',
28
+ next: 'A plan comes first, and nothing is built until you confirm it. The page uses your ' + 'theme and what the site already has, and the plan lists anything the job builds first.',
29
+ submit: 'Plan the page',
30
+ started: 'The page is being planned. Review the plan here or in AI jobs, and confirm it to ' + 'build. The page is built as an unpublished draft.',
31
+ failed: 'The page could not be started. Try again.'
32
+ },
33
+ template: {
34
+ title: 'Describe a page template',
35
+ briefLabel: 'What should each page show?',
36
+ placeholder: 'A page for each of our team members: their name and photo, their role, a short ' + 'biography and a way to get in touch',
37
+ next: 'A plan comes first, and nothing is built until you confirm it. What changes from one ' + 'page to the next, such as a name or a photo, is filled in from each record rather ' + 'than typed.',
38
+ submit: 'Plan the template',
39
+ started: 'The template is being planned. Review the plan here or in AI jobs, and confirm it ' + 'to build. The template is built as a draft in your library, and nothing on your ' + 'site uses it until you do.',
40
+ failed: 'The template could not be started. Try again.'
41
+ },
42
+ layout: {
43
+ title: 'Describe a layout',
44
+ briefLabel: 'What should the layout hold?',
45
+ placeholder: 'A header with our name and links to Services, About and Contact, and a footer with ' + 'our address and opening hours',
46
+ next: 'A plan comes first, and nothing is built until you confirm it. The layout links your ' + 'pages, and places your navigation or menu component when the site has one.',
47
+ submit: 'Plan the layout',
48
+ started: 'The layout is being planned. Review the plan here or in AI jobs, and confirm it to ' + 'build. The layout is built as a draft, and no page uses it until you assign it.',
49
+ failed: 'The layout could not be started. Try again.'
50
+ },
51
+ form: {
52
+ title: 'Describe a form',
53
+ briefLabel: 'What is the form for?',
54
+ placeholder: 'A quote request form for a roofing company, asking what needs fixing and the best ' + 'time to call',
55
+ next: 'A plan comes first, and nothing is built until you confirm it. The form’s fields, its ' + 'marketing consent and where each submission goes are planned together.',
56
+ submit: 'Plan the form',
57
+ started: 'The form is being planned. Review the plan here or in AI jobs, and confirm it to ' + 'build. The form is built as a draft, and it collects nothing until you place it on ' + 'a page.',
58
+ failed: 'The form could not be started. Try again.'
59
+ },
60
+ component: {
61
+ title: 'Describe a reusable component',
62
+ briefLabel: 'What should the component show?',
63
+ placeholder: 'A testimonial card with the customer’s name, their role, their quote and a photo',
64
+ next: 'A plan comes first, and nothing is built until you confirm it. What each page can ' + 'change, such as a name or a photo, becomes a property of the component.',
65
+ submit: 'Plan the component',
66
+ started: 'The component is being planned. Review the plan here or in AI jobs, and confirm it ' + 'to build. The component is built as a draft, and no page shows it until you insert ' + 'it.',
67
+ failed: 'The component could not be started. Try again.'
68
+ },
69
+ email: {
70
+ title: 'Describe an email',
71
+ briefLabel: 'What is the email for?',
72
+ placeholder: 'Announce the autumn menu to our regulars, and point them at the class page to book ' + 'the Tuesday bread class',
73
+ next: 'A plan comes first, and nothing is built until you confirm it. The email is built ' + 'from email blocks, with three subject lines and three preheaders to choose from. ' + 'Nothing is sent or scheduled.',
74
+ submit: 'Plan the email',
75
+ started: 'The email is being planned. Review the plan here or in AI jobs, and confirm it to ' + 'build. The email is saved as a draft template, and nothing sends it until a ' + 'campaign does.',
76
+ failed: 'The email could not be started. Try again.'
77
+ },
78
+ campaign: {
79
+ title: 'Describe a campaign',
80
+ briefLabel: 'What is the campaign’s email for?',
81
+ placeholder: 'Announce the autumn menu to our regulars, and point them at the class page to book ' + 'the Tuesday bread class',
82
+ next: 'A plan comes first, and nothing is built until you confirm it. The job writes the ' + 'email and a draft campaign that would send it. The campaign is aimed at nobody and ' + 'sends nothing until you pick who receives it and schedule it.',
83
+ submit: 'Plan the campaign',
84
+ started: 'The campaign is being planned. Review the plan here or in AI jobs, and confirm it ' + 'to build. The email and the campaign are saved as drafts, and nothing is sent.',
85
+ failed: 'The campaign could not be started. Try again.'
86
+ }
87
+ };
88
+ /**
89
+ * The kinds of email a member picks from, as their chips name them. The jobs'
90
+ * own catalog (`AI_EMAIL_TYPES`) also holds `reply`, which only a job the
91
+ * platform composes sets, so it is not offered here.
92
+ */ export const AI_EMAIL_TYPE_LABELS = {
93
+ welcome: 'Welcome',
94
+ newsletter: 'Newsletter',
95
+ launch: 'Launch',
96
+ abandonedCart: 'Cart reminder',
97
+ eventReminder: 'Event reminder'
98
+ };
99
+ /** Each template subject as its chip names it: what one page is drawn for. */ export const AI_TEMPLATE_SUBJECT_LABELS = {
100
+ entry: 'Collection entry',
101
+ product: 'Product',
102
+ author: 'Author'
103
+ };
104
+ export const AI_BRIEF_NO_CHOICE = {
105
+ pageType: null,
106
+ subject: null,
107
+ collectionId: null,
108
+ emailType: null,
109
+ withCampaign: false
110
+ };
111
+ /**
112
+ * The job the door starts: its own kind, except an email that also asked for
113
+ * its campaign, which is a `campaign` job — the campaign step writes the same
114
+ * design and drafts the campaign around it.
115
+ */ export function aiBriefJobKind(kind, choice) {
116
+ return kind === 'email' && choice.withCampaign ? 'campaign' : kind;
117
+ }
118
+ /**
119
+ * The job's `inputs` for what the member picked, or `null` while the kind's
120
+ * door would refuse them.
121
+ *
122
+ * A template's are read by the parser its door reads, so the dialog cannot
123
+ * send a subject the door does not know or an entry page with no collection.
124
+ * A page's type is optional, and so is an email's or a campaign's kind of
125
+ * email (`inputs.emailType`, which both steps read). A layout, a form and a component read no inputs
126
+ * at all (AGL-2909, AGL-2913, AGL-2908), so theirs are empty.
127
+ */ export function aiBriefJobInputs(kind, choice) {
128
+ if (kind === 'page') return choice.pageType ? {
129
+ pageType: choice.pageType
130
+ } : {};
131
+ if (kind === 'email' || kind === 'campaign') {
132
+ return choice.emailType ? {
133
+ emailType: choice.emailType
134
+ } : {};
135
+ }
136
+ if (kind === 'template') {
137
+ const inputs = parseAiTemplateJobInputs({
138
+ subject: choice.subject,
139
+ collectionId: choice.collectionId
140
+ });
141
+ if (typeof inputs === 'string') return null;
142
+ return inputs.collectionId ? {
143
+ subject: inputs.subject,
144
+ collectionId: inputs.collectionId
145
+ } : {
146
+ subject: inputs.subject
147
+ };
148
+ }
149
+ return {};
150
+ }
151
+
152
+ //# sourceMappingURL=ai-brief-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-brief-copy.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 { AiEmailType } from '../jobs/ai-job-email-step'\nimport type { AiJobKind } from './ai-jobs.types'\nimport type { AiPageType } from './ai-page-job'\nimport { parseAiTemplateJobInputs, type AiTemplateSubject } from './ai-template-subjects'\n\n/**\n * WHAT EVERY \"CREATE WITH AI\" BRIEF SAYS AND SENDS (AGL-2907, AGL-3043,\n * AGL-3051, AGL-3596): each kind's words, the choices beside the brief, and\n * the job and inputs they start. Pure, so the console's brief dialog and the\n * door the native apps read (AGL-3854) say the same thing from one place.\n */\n\n/** The longest brief a job admits. */\nexport const AI_BRIEF_MAX_CHARS = 4_000\n\n/** The job kinds a member describes from a console page. */\nexport type AiBriefKind = Extract<\n AiJobKind,\n 'page' | 'template' | 'layout' | 'form' | 'component' | 'email' | 'campaign'\n>\n\n/** What the dialog says for one kind. */\nexport interface AiBriefCopy {\n title: string\n /** The brief box's label. */\n briefLabel: string\n placeholder: string\n /** What happens after the member starts the job, under the fields. */\n next: string\n /** The action that starts the job. */\n submit: string\n /** What the dialog says once the job exists, above its live status. */\n started: string\n /** What it says when the door gave no reason, or could not be reached. */\n failed: string\n}\n\nexport const AI_BRIEF_COPY: Readonly<Record<AiBriefKind, AiBriefCopy>> = {\n page: {\n title: 'Describe a page',\n briefLabel: 'What is the page for?',\n placeholder:\n 'A landing page for our spring roof inspection offer, with the quote request form',\n next:\n 'A plan comes first, and nothing is built until you confirm it. The page uses your ' +\n 'theme and what the site already has, and the plan lists anything the job builds first.',\n submit: 'Plan the page',\n started:\n 'The page is being planned. Review the plan here or in AI jobs, and confirm it to ' +\n 'build. The page is built as an unpublished draft.',\n failed: 'The page could not be started. Try again.',\n },\n template: {\n title: 'Describe a page template',\n briefLabel: 'What should each page show?',\n placeholder:\n 'A page for each of our team members: their name and photo, their role, a short ' +\n 'biography and a way to get in touch',\n next:\n 'A plan comes first, and nothing is built until you confirm it. What changes from one ' +\n 'page to the next, such as a name or a photo, is filled in from each record rather ' +\n 'than typed.',\n submit: 'Plan the template',\n started:\n 'The template is being planned. Review the plan here or in AI jobs, and confirm it ' +\n 'to build. The template is built as a draft in your library, and nothing on your ' +\n 'site uses it until you do.',\n failed: 'The template could not be started. Try again.',\n },\n layout: {\n title: 'Describe a layout',\n briefLabel: 'What should the layout hold?',\n placeholder:\n 'A header with our name and links to Services, About and Contact, and a footer with ' +\n 'our address and opening hours',\n next:\n 'A plan comes first, and nothing is built until you confirm it. The layout links your ' +\n 'pages, and places your navigation or menu component when the site has one.',\n submit: 'Plan the layout',\n started:\n 'The layout is being planned. Review the plan here or in AI jobs, and confirm it to ' +\n 'build. The layout is built as a draft, and no page uses it until you assign it.',\n failed: 'The layout could not be started. Try again.',\n },\n form: {\n title: 'Describe a form',\n briefLabel: 'What is the form for?',\n placeholder:\n 'A quote request form for a roofing company, asking what needs fixing and the best ' +\n 'time to call',\n next:\n 'A plan comes first, and nothing is built until you confirm it. The form’s fields, its ' +\n 'marketing consent and where each submission goes are planned together.',\n submit: 'Plan the form',\n started:\n 'The form is being planned. Review the plan here or in AI jobs, and confirm it to ' +\n 'build. The form is built as a draft, and it collects nothing until you place it on ' +\n 'a page.',\n failed: 'The form could not be started. Try again.',\n },\n component: {\n title: 'Describe a reusable component',\n briefLabel: 'What should the component show?',\n placeholder:\n 'A testimonial card with the customer’s name, their role, their quote and a photo',\n next:\n 'A plan comes first, and nothing is built until you confirm it. What each page can ' +\n 'change, such as a name or a photo, becomes a property of the component.',\n submit: 'Plan the component',\n started:\n 'The component is being planned. Review the plan here or in AI jobs, and confirm it ' +\n 'to build. The component is built as a draft, and no page shows it until you insert ' +\n 'it.',\n failed: 'The component could not be started. Try again.',\n },\n email: {\n title: 'Describe an email',\n briefLabel: 'What is the email for?',\n placeholder:\n 'Announce the autumn menu to our regulars, and point them at the class page to book ' +\n 'the Tuesday bread class',\n next:\n 'A plan comes first, and nothing is built until you confirm it. The email is built ' +\n 'from email blocks, with three subject lines and three preheaders to choose from. ' +\n 'Nothing is sent or scheduled.',\n submit: 'Plan the email',\n started:\n 'The email is being planned. Review the plan here or in AI jobs, and confirm it to ' +\n 'build. The email is saved as a draft template, and nothing sends it until a ' +\n 'campaign does.',\n failed: 'The email could not be started. Try again.',\n },\n campaign: {\n title: 'Describe a campaign',\n briefLabel: 'What is the campaign’s email for?',\n placeholder:\n 'Announce the autumn menu to our regulars, and point them at the class page to book ' +\n 'the Tuesday bread class',\n next:\n 'A plan comes first, and nothing is built until you confirm it. The job writes the ' +\n 'email and a draft campaign that would send it. The campaign is aimed at nobody and ' +\n 'sends nothing until you pick who receives it and schedule it.',\n submit: 'Plan the campaign',\n started:\n 'The campaign is being planned. Review the plan here or in AI jobs, and confirm it ' +\n 'to build. The email and the campaign are saved as drafts, and nothing is sent.',\n failed: 'The campaign could not be started. Try again.',\n },\n}\n\n/**\n * The kinds of email a member picks from, as their chips name them. The jobs'\n * own catalog (`AI_EMAIL_TYPES`) also holds `reply`, which only a job the\n * platform composes sets, so it is not offered here.\n */\nexport const AI_EMAIL_TYPE_LABELS: Readonly<Record<Exclude<AiEmailType, 'reply'>, string>> = {\n welcome: 'Welcome',\n newsletter: 'Newsletter',\n launch: 'Launch',\n abandonedCart: 'Cart reminder',\n eventReminder: 'Event reminder',\n}\n\n/** Each template subject as its chip names it: what one page is drawn for. */\nexport const AI_TEMPLATE_SUBJECT_LABELS: Readonly<Record<AiTemplateSubject, string>> = {\n entry: 'Collection entry',\n product: 'Product',\n author: 'Author',\n}\n\n/** What a member picked beside the brief; each kind reads only its own. */\nexport interface AiBriefChoice {\n /** A page's type, or `null` for none. */\n pageType: AiPageType | null\n /** What a template's pages are drawn for, or `null` until one is picked. */\n subject: AiTemplateSubject | null\n /** A collection entry template's collection, or `null` until one is picked. */\n collectionId: string | null\n /** An email's or a campaign's kind of email; `null` or absent for none. */\n emailType?: Exclude<AiEmailType, 'reply'> | null\n /** An email's door only: also draft the campaign that would send it. */\n withCampaign?: boolean\n}\n\nexport const AI_BRIEF_NO_CHOICE: AiBriefChoice = {\n pageType: null,\n subject: null,\n collectionId: null,\n emailType: null,\n withCampaign: false,\n}\n\n/**\n * The job the door starts: its own kind, except an email that also asked for\n * its campaign, which is a `campaign` job — the campaign step writes the same\n * design and drafts the campaign around it.\n */\nexport function aiBriefJobKind(kind: AiBriefKind, choice: AiBriefChoice): AiBriefKind {\n return kind === 'email' && choice.withCampaign ? 'campaign' : kind\n}\n\n/**\n * The job's `inputs` for what the member picked, or `null` while the kind's\n * door would refuse them.\n *\n * A template's are read by the parser its door reads, so the dialog cannot\n * send a subject the door does not know or an entry page with no collection.\n * A page's type is optional, and so is an email's or a campaign's kind of\n * email (`inputs.emailType`, which both steps read). A layout, a form and a component read no inputs\n * at all (AGL-2909, AGL-2913, AGL-2908), so theirs are empty.\n */\nexport function aiBriefJobInputs(\n kind: AiBriefKind,\n choice: AiBriefChoice,\n): Record<string, string> | null {\n if (kind === 'page') return choice.pageType ? { pageType: choice.pageType } : {}\n if (kind === 'email' || kind === 'campaign') {\n return choice.emailType ? { emailType: choice.emailType } : {}\n }\n if (kind === 'template') {\n const inputs = parseAiTemplateJobInputs({\n subject: choice.subject,\n collectionId: choice.collectionId,\n })\n if (typeof inputs === 'string') return null\n return inputs.collectionId\n ? { subject: inputs.subject, collectionId: inputs.collectionId }\n : { subject: inputs.subject }\n }\n return {}\n}\n"],"names":["parseAiTemplateJobInputs","AI_BRIEF_MAX_CHARS","AI_BRIEF_COPY","page","title","briefLabel","placeholder","next","submit","started","failed","template","layout","form","component","email","campaign","AI_EMAIL_TYPE_LABELS","welcome","newsletter","launch","abandonedCart","eventReminder","AI_TEMPLATE_SUBJECT_LABELS","entry","product","author","AI_BRIEF_NO_CHOICE","pageType","subject","collectionId","emailType","withCampaign","aiBriefJobKind","kind","choice","aiBriefJobInputs","inputs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAKD,SAASA,wBAAwB,QAAgC,4BAAwB;AAEzF;;;;;CAKC,GAED,oCAAoC,GACpC,OAAO,MAAMC,qBAAqB,KAAK;AAwBvC,OAAO,MAAMC,gBAA4D;IACvEC,MAAM;QACJC,OAAO;QACPC,YAAY;QACZC,aACE;QACFC,MACE,uFACA;QACFC,QAAQ;QACRC,SACE,sFACA;QACFC,QAAQ;IACV;IACAC,UAAU;QACRP,OAAO;QACPC,YAAY;QACZC,aACE,oFACA;QACFC,MACE,0FACA,uFACA;QACFC,QAAQ;QACRC,SACE,uFACA,qFACA;QACFC,QAAQ;IACV;IACAE,QAAQ;QACNR,OAAO;QACPC,YAAY;QACZC,aACE,wFACA;QACFC,MACE,0FACA;QACFC,QAAQ;QACRC,SACE,wFACA;QACFC,QAAQ;IACV;IACAG,MAAM;QACJT,OAAO;QACPC,YAAY;QACZC,aACE,uFACA;QACFC,MACE,2FACA;QACFC,QAAQ;QACRC,SACE,sFACA,wFACA;QACFC,QAAQ;IACV;IACAI,WAAW;QACTV,OAAO;QACPC,YAAY;QACZC,aACE;QACFC,MACE,uFACA;QACFC,QAAQ;QACRC,SACE,wFACA,wFACA;QACFC,QAAQ;IACV;IACAK,OAAO;QACLX,OAAO;QACPC,YAAY;QACZC,aACE,wFACA;QACFC,MACE,uFACA,sFACA;QACFC,QAAQ;QACRC,SACE,uFACA,iFACA;QACFC,QAAQ;IACV;IACAM,UAAU;QACRZ,OAAO;QACPC,YAAY;QACZC,aACE,wFACA;QACFC,MACE,uFACA,wFACA;QACFC,QAAQ;QACRC,SACE,uFACA;QACFC,QAAQ;IACV;AACF,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMO,uBAAgF;IAC3FC,SAAS;IACTC,YAAY;IACZC,QAAQ;IACRC,eAAe;IACfC,eAAe;AACjB,EAAC;AAED,4EAA4E,GAC5E,OAAO,MAAMC,6BAA0E;IACrFC,OAAO;IACPC,SAAS;IACTC,QAAQ;AACV,EAAC;AAgBD,OAAO,MAAMC,qBAAoC;IAC/CC,UAAU;IACVC,SAAS;IACTC,cAAc;IACdC,WAAW;IACXC,cAAc;AAChB,EAAC;AAED;;;;CAIC,GACD,OAAO,SAASC,eAAeC,IAAiB,EAAEC,MAAqB;IACrE,OAAOD,SAAS,WAAWC,OAAOH,YAAY,GAAG,aAAaE;AAChE;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASE,iBACdF,IAAiB,EACjBC,MAAqB;IAErB,IAAID,SAAS,QAAQ,OAAOC,OAAOP,QAAQ,GAAG;QAAEA,UAAUO,OAAOP,QAAQ;IAAC,IAAI,CAAC;IAC/E,IAAIM,SAAS,WAAWA,SAAS,YAAY;QAC3C,OAAOC,OAAOJ,SAAS,GAAG;YAAEA,WAAWI,OAAOJ,SAAS;QAAC,IAAI,CAAC;IAC/D;IACA,IAAIG,SAAS,YAAY;QACvB,MAAMG,SAASrC,yBAAyB;YACtC6B,SAASM,OAAON,OAAO;YACvBC,cAAcK,OAAOL,YAAY;QACnC;QACA,IAAI,OAAOO,WAAW,UAAU,OAAO;QACvC,OAAOA,OAAOP,YAAY,GACtB;YAAED,SAASQ,OAAOR,OAAO;YAAEC,cAAcO,OAAOP,YAAY;QAAC,IAC7D;YAAED,SAASQ,OAAOR,OAAO;QAAC;IAChC;IACA,OAAO,CAAC;AACV"}
@@ -0,0 +1,200 @@
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 AiBriefCopy } from './ai-brief-copy';
18
+ import { AI_JOB_FOLLOW_PHASE_LINES, type AiFollowTone, type AiJobPhase } from './ai-job-activity';
19
+ import { type AiJobOutput, type AiJobSummary } from './ai-jobs.types';
20
+ import { type AiFreeCreditsLeft } from './ai-site-job';
21
+ import type { AiTemplateCollectionOption } from './ai-template-collections';
22
+ /**
23
+ * "CREATE WITH AI" AS THE NATIVE APPS DRAW IT (AGL-3854): the door a list's
24
+ * Create with AI button reads, and the follow view of the job it starts.
25
+ *
26
+ * The console decides each entry from gates its shell already resolved — the
27
+ * plan and the add-on it could buy, `ai.generate`, the release flag and the
28
+ * site's AI switch — and draws the brief and the job from the plugin's own
29
+ * components. An app has neither the shell nor the components, so the AI
30
+ * plugin's `/api/ai/create` door answers both as data: whether the entry is
31
+ * drawn and as what, every word the brief dialog says, and the job as the
32
+ * dialog's follow card reads it, in sentences built here from the same models
33
+ * the console's components read. Nothing here decides what a request may do:
34
+ * the jobs route's own ladder still decides each start, confirm and cancel.
35
+ */
36
+ /** The lists the apps offer Create with AI on, beside their own create action. */
37
+ export declare const AI_CREATE_KINDS: readonly ["page", "template", "layout", "form", "component"];
38
+ export type AiCreateKind = (typeof AI_CREATE_KINDS)[number];
39
+ export declare function isAiCreateKind(value: unknown): value is AiCreateKind;
40
+ /** One row of chips beside the brief, and the input the pick is sent as. */
41
+ export interface AiCreateChoiceView {
42
+ /** The `inputs` key the pick is sent under. */
43
+ key: 'pageType' | 'subject';
44
+ label: string;
45
+ /** A required pick keeps the start disabled until one is made; an optional one clears when pressed again. */
46
+ required: boolean;
47
+ options: Array<{
48
+ id: string;
49
+ label: string;
50
+ }>;
51
+ }
52
+ /** A template's collection field, shown once "Collection entry" is picked. */
53
+ export interface AiCreateCollectionsView {
54
+ /** The subject the field belongs to. */
55
+ when: 'entry';
56
+ label: string;
57
+ options: AiTemplateCollectionOption[];
58
+ helper: string;
59
+ }
60
+ /** What is left of the month's AI credits, said before a brief is sent. */
61
+ export interface AiCreateCreditsView {
62
+ text: string;
63
+ /** Nothing is left and the workspace stops at its band, so a start is refused until credits return. */
64
+ exhausted: boolean;
65
+ }
66
+ /** The add-on's offer, on a plan that could buy it and has not (AGL-3601). */
67
+ export interface AiCreateUpsellView {
68
+ title: string;
69
+ does: string;
70
+ offer: string;
71
+ /** The reader may add the add-on (`billing.manage`); the action is drawn only then. */
72
+ canManageBilling: boolean;
73
+ /** "Add Aglyn AI", or `null` for a reader who cannot. */
74
+ action: string | null;
75
+ dismiss: string;
76
+ }
77
+ export interface AiCreateDoorView {
78
+ kind: AiCreateKind;
79
+ /** `create`: the brief; `upsell`: the add-on's offer; `hidden`: the entry is not drawn. */
80
+ door: 'create' | 'upsell' | 'hidden';
81
+ /** The entry's label, "Create with AI". */
82
+ label: string;
83
+ copy: AiBriefCopy | null;
84
+ briefMaxChars: number;
85
+ choices: AiCreateChoiceView[];
86
+ collections: AiCreateCollectionsView | null;
87
+ credits: AiCreateCreditsView | null;
88
+ /** Another page already being built on this site (AGL-3765): a warning, not a stop. */
89
+ building: string | null;
90
+ upsell: AiCreateUpsellView | null;
91
+ }
92
+ /** What the door knows about the month's credits. */
93
+ export type AiCreateCreditsFacts = {
94
+ kind: 'free';
95
+ left: AiFreeCreditsLeft;
96
+ } | {
97
+ kind: 'band';
98
+ used: number;
99
+ limit: number;
100
+ remaining: number;
101
+ stopsAtBand: boolean;
102
+ } | {
103
+ kind: 'unlimited';
104
+ } | {
105
+ kind: 'none';
106
+ };
107
+ /** The credits line above the brief, or `null` where the workspace has no band to read. */
108
+ export declare function aiCreateCreditsView(facts: AiCreateCreditsFacts): AiCreateCreditsView | null;
109
+ /** The chips each kind offers beside its brief, as the console's dialog draws them. */
110
+ export declare function aiCreateChoices(kind: AiCreateKind): AiCreateChoiceView[];
111
+ export interface AiCreateDoorInput {
112
+ kind: AiCreateKind;
113
+ /** The gates that draw no entry at all: `ai.generate`, the release flag, the site's AI switch. */
114
+ shown: boolean;
115
+ /** The plan carries `aiGenerative`. */
116
+ entitled: boolean;
117
+ /** The workspace could buy the add-on that grants it, and the add-on store is open to it. */
118
+ purchasable: boolean;
119
+ canManageBilling: boolean;
120
+ credits: AiCreateCreditsFacts;
121
+ /** The site's content collections, read only for a template's door. */
122
+ collections?: {
123
+ status: 'success' | 'error';
124
+ options: AiTemplateCollectionOption[];
125
+ } | null;
126
+ /** Another page build in flight on the site, for a page's door. */
127
+ buildInFlight?: boolean;
128
+ addonName: string;
129
+ }
130
+ /** The door one list's Create with AI reads. */
131
+ export declare function aiCreateDoorView(input: AiCreateDoorInput): AiCreateDoorView;
132
+ export { AI_JOB_FOLLOW_PHASE_LINES, type AiFollowTone };
133
+ /**
134
+ * Where one output opens in an app, as the console addresses the apps' link
135
+ * resolver answers natively: a page opens its page (the console's preview
136
+ * address, which the apps' Pages claims), a component, layout or template its
137
+ * details, and anything else the page its owner publishes through the
138
+ * record-route seam (a form's own page). `besigner` is the version the job
139
+ * wrote, opened in the Besigner. `null` where the output has no page of its own,
140
+ * names no site, or its owner is not loaded.
141
+ */
142
+ export declare function aiJobOutputAppLinks(output: Pick<AiJobOutput, 'resource' | 'id' | 'versionId' | 'hostSubdomain'>, orgSlug: string): {
143
+ detail: string;
144
+ besigner: string | null;
145
+ } | null;
146
+ /** The one thing a finished job leads with, as the console's `aiJobPrimaryLink` picks it. */
147
+ export interface AiJobFollowOpen {
148
+ label: string;
149
+ detail: string;
150
+ besigner: string | null;
151
+ }
152
+ export declare function aiJobFollowOpen(job: Pick<AiJobSummary, 'status' | 'outputs'>, orgSlug: string): AiJobFollowOpen | null;
153
+ export interface AiJobFollowView {
154
+ id: string;
155
+ kind: string;
156
+ status: string;
157
+ hostId: string | null;
158
+ phase: AiJobPhase;
159
+ phaseLabel: string;
160
+ line: {
161
+ tone: AiFollowTone;
162
+ text: string;
163
+ };
164
+ /** Still moving, so the apps read it again shortly. */
165
+ moving: boolean;
166
+ /** Finished, failed or canceled: nothing more happens to it. */
167
+ settled: boolean;
168
+ /** "2 of 3 steps", for a job of more than one step. */
169
+ steps: string | null;
170
+ plan: {
171
+ heading: string;
172
+ lines: string[];
173
+ } | null;
174
+ /** The building rules a refused answer broke, as a member reads them. */
175
+ findings: string[];
176
+ /** The checks' own sentence on a refused answer, for staff. */
177
+ detail: string | null;
178
+ /** The plan's estimated cost, read before it is confirmed. */
179
+ estimate: string | null;
180
+ retryRefusal: string | null;
181
+ /** "Confirm plan" or "Try again" while the job waits for the person. */
182
+ confirm: {
183
+ label: string;
184
+ enabled: boolean;
185
+ } | null;
186
+ /** Another page build in flight on the site, said beside Confirm (AGL-3765). */
187
+ building: string | null;
188
+ /** Why a stopped job has nothing to show. */
189
+ problem: string | null;
190
+ /** What the job spent so far. */
191
+ spent: string | null;
192
+ open: AiJobFollowOpen | null;
193
+ }
194
+ /** The follow card's view of one job, for the apps (the console's `AiJobFollow` and `AiJobPlan`). */
195
+ export declare function aiJobFollowView(job: AiJobSummary, options?: {
196
+ orgSlug?: string | null;
197
+ staff?: boolean;
198
+ buildInFlight?: boolean;
199
+ now?: number;
200
+ }): AiJobFollowView;