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

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 (90) 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 +96 -0
  23. package/src/lib/insights/ai-figure-summaries.js.map +1 -0
  24. package/src/lib/insights/ai-insight-digest.d.ts +2 -0
  25. package/src/lib/insights/ai-insight-digest.js +7 -4
  26. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  27. package/src/lib/insights/ai-insight-readers.d.ts +2 -1
  28. package/src/lib/insights/ai-insight-readers.js +11 -26
  29. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  30. package/src/lib/jobs/ai-job-insight-step.d.ts +1 -1
  31. package/src/lib/jobs/ai-job-insight-step.js +5 -3
  32. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  33. package/src/lib/jobs/ai-job-page-language.d.ts +2 -0
  34. package/src/lib/jobs/ai-job-page-language.js +23 -2
  35. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  36. package/src/lib/jobs/ai-job-plan-step.d.ts +14 -1
  37. package/src/lib/jobs/ai-job-plan-step.js +44 -9
  38. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  39. package/src/lib/jobs/ai-jobs-notify.d.ts +8 -1
  40. package/src/lib/jobs/ai-jobs-notify.js +44 -2
  41. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  42. package/src/lib/jobs/ai-layout-stock-photos.js +11 -1
  43. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  44. package/src/lib/layout-language/ai-layout-compiler.js +18 -7
  45. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  46. package/src/lib/layout-language/ai-layout-promised-copy.d.ts +54 -0
  47. package/src/lib/layout-language/ai-layout-promised-copy.js +104 -0
  48. package/src/lib/layout-language/ai-layout-promised-copy.js.map +1 -0
  49. package/src/lib/model/ai-brief-copy.d.ts +85 -0
  50. package/src/lib/model/ai-brief-copy.js +152 -0
  51. package/src/lib/model/ai-brief-copy.js.map +1 -0
  52. package/src/lib/model/ai-create-view.d.ts +200 -0
  53. package/src/lib/model/ai-create-view.js +263 -0
  54. package/src/lib/model/ai-create-view.js.map +1 -0
  55. package/src/lib/model/ai-insight.d.ts +10 -1
  56. package/src/lib/model/ai-insight.js +16 -39
  57. package/src/lib/model/ai-insight.js.map +1 -1
  58. package/src/lib/model/ai-job-activity.d.ts +24 -0
  59. package/src/lib/model/ai-job-activity.js +52 -1
  60. package/src/lib/model/ai-job-activity.js.map +1 -1
  61. package/src/lib/model/ai-job-notice.d.ts +7 -1
  62. package/src/lib/model/ai-job-notice.js +10 -9
  63. package/src/lib/model/ai-job-notice.js.map +1 -1
  64. package/src/lib/model/ai-job-plan-lines.d.ts +46 -0
  65. package/src/lib/model/ai-job-plan-lines.js +70 -0
  66. package/src/lib/model/ai-job-plan-lines.js.map +1 -0
  67. package/src/lib/model/ai-plan-said-sections.d.ts +81 -0
  68. package/src/lib/model/ai-plan-said-sections.js +168 -0
  69. package/src/lib/model/ai-plan-said-sections.js.map +1 -0
  70. package/src/lib/model/ai-site-job.d.ts +8 -2
  71. package/src/lib/model/ai-site-job.js +7 -1
  72. package/src/lib/model/ai-site-job.js.map +1 -1
  73. package/src/lib/model/ai-template-collections.d.ts +28 -0
  74. package/src/lib/model/ai-template-collections.js +28 -0
  75. package/src/lib/model/ai-template-collections.js.map +1 -0
  76. package/src/lib/model/ai-upsell-copy.d.ts +41 -0
  77. package/src/lib/model/ai-upsell-copy.js +85 -0
  78. package/src/lib/model/ai-upsell-copy.js.map +1 -0
  79. package/src/lib/runtime/ai-doctrine-validators.d.ts +15 -1
  80. package/src/lib/runtime/ai-doctrine-validators.js +96 -3
  81. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  82. package/src/lib/runtime/ai-eval-memory-firestore.js +2 -0
  83. package/src/lib/runtime/ai-eval-memory-firestore.js.map +1 -1
  84. package/src/lib/runtime/ai-palette.generated.js +11 -3
  85. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  86. package/src/lib/server/ai-create-route.d.ts +32 -0
  87. package/src/lib/server/ai-create-route.js +295 -0
  88. package/src/lib/server/ai-create-route.js.map +1 -0
  89. package/src/lib/server.js +14 -0
  90. package/src/lib/server.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.261",
3
+ "version": "1.0.0-beta.263",
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.261",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.261",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.261",
31
- "@aglyn/shared-data-types": "1.0.0-beta.261",
32
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.261",
33
- "@aglyn/shared-ui-jsx": "1.0.0-beta.261",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.261",
35
- "@aglyn/shared-ui-theme": "1.0.0-beta.261",
36
- "@aglyn/shared-util-email": "1.0.0-beta.261",
37
- "@aglyn/shared-util-errors": "1.0.0-beta.261",
38
- "@aglyn/shared-util-http": "1.0.0-beta.261",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.261",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.261",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.261",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.261",
43
- "@aglyn/tenant-runtime": "1.0.0-beta.261",
28
+ "@aglyn/aglyn": "1.0.0-beta.263",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.263",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.263",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.263",
32
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.263",
33
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.263",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.263",
35
+ "@aglyn/shared-ui-theme": "1.0.0-beta.263",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.263",
37
+ "@aglyn/shared-util-errors": "1.0.0-beta.263",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.263",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.263",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.263",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.263",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.263",
43
+ "@aglyn/tenant-runtime": "1.0.0-beta.263",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -1,65 +1,28 @@
1
1
  import { authorizedFetch } from '@aglyn/shared-util-http/authorized-token';
2
- import type { AiEmailType } from '../jobs/ai-job-email-step';
3
- import type { AiJobKind } from '../model/ai-jobs.types';
4
- import { type AiPageType } from '../model/ai-page-job';
5
- import { type AiTemplateSubject } from '../model/ai-template-subjects';
6
- /** The job kinds a member describes from a console page. */
7
- export type AiBriefKind = Extract<AiJobKind, 'page' | 'template' | 'layout' | 'form' | 'component' | 'email' | 'campaign'>;
8
- /** What the dialog says for one kind. */
9
- export interface AiBriefCopy {
10
- title: string;
11
- /** The brief box's label. */
12
- briefLabel: string;
13
- placeholder: string;
14
- /** What happens after the member starts the job, under the fields. */
15
- next: string;
16
- /** The action that starts the job. */
17
- submit: string;
18
- /** What the dialog says once the job exists, above its live status. */
19
- started: string;
20
- /** What it says when the door gave no reason, or could not be reached. */
21
- failed: string;
22
- }
23
- export declare const AI_BRIEF_COPY: Readonly<Record<AiBriefKind, AiBriefCopy>>;
24
- /**
25
- * The kinds of email a member picks from, as their chips name them. The jobs'
26
- * own catalog (`AI_EMAIL_TYPES`) also holds `reply`, which only a job the
27
- * platform composes sets, so it is not offered here.
28
- */
29
- export declare const AI_EMAIL_TYPE_LABELS: Readonly<Record<Exclude<AiEmailType, 'reply'>, string>>;
30
- /** Each template subject as its chip names it: what one page is drawn for. */
31
- export declare const AI_TEMPLATE_SUBJECT_LABELS: Readonly<Record<AiTemplateSubject, string>>;
32
- /** What a member picked beside the brief; each kind reads only its own. */
33
- export interface AiBriefChoice {
34
- /** A page's type, or `null` for none. */
35
- pageType: AiPageType | null;
36
- /** What a template's pages are drawn for, or `null` until one is picked. */
37
- subject: AiTemplateSubject | null;
38
- /** A collection entry template's collection, or `null` until one is picked. */
39
- collectionId: string | null;
40
- /** An email's or a campaign's kind of email; `null` or absent for none. */
41
- emailType?: Exclude<AiEmailType, 'reply'> | null;
42
- /** An email's door only: also draft the campaign that would send it. */
43
- withCampaign?: boolean;
44
- }
45
- export declare const AI_BRIEF_NO_CHOICE: AiBriefChoice;
46
- /**
47
- * The job the door starts: its own kind, except an email that also asked for
48
- * its campaign, which is a `campaign` job — the campaign step writes the same
49
- * design and drafts the campaign around it.
50
- */
51
- export declare function aiBriefJobKind(kind: AiBriefKind, choice: AiBriefChoice): AiBriefKind;
2
+ import { AI_BRIEF_COPY, AI_BRIEF_NO_CHOICE, AI_EMAIL_TYPE_LABELS, AI_TEMPLATE_SUBJECT_LABELS, aiBriefJobInputs, aiBriefJobKind, type AiBriefKind } from '../model/ai-brief-copy';
52
3
  /**
53
- * The job's `inputs` for what the member picked, or `null` while the kind's
54
- * door would refuse them.
4
+ * The brief dialog every "Create with AI" opens (AGL-2907, AGL-3043, AGL-3051):
5
+ * a page from the Screens page and from the Assist panel's AI jobs, a page
6
+ * template from Templates, a layout from Layouts, a form from Forms, a
7
+ * reusable component from Components, and an email design from the Emails
8
+ * page's templates (AGL-3596). The `campaign` kind is the job an email's door
9
+ * starts when the campaign is asked for too; the Campaigns section's own door
10
+ * is a dialog of its own (`ai-campaign-create.component`, AGL-3603).
55
11
  *
56
- * A template's are read by the parser its door reads, so the dialog cannot
57
- * send a subject the door does not know or an entry page with no collection.
58
- * A page's type is optional, and so is an email's or a campaign's kind of
59
- * email (`inputs.emailType`, which both steps read). A layout, a form and a component read no inputs
60
- * at all (AGL-2909, AGL-2913, AGL-2908), so theirs are empty.
12
+ * One text box, and whatever the kind's door reads beside it, start one job of
13
+ * that kind: a page's optional page type; a template's subject and, for a
14
+ * collection entry's page, its collection. A layout, a form and a component
15
+ * read nothing but the brief and the site. An email's reads the optional
16
+ * kind of email, and its door also offers to draft the
17
+ * campaign that would send it, which starts a `campaign` job instead — the
18
+ * same design, plus a draft campaign aimed at nobody. The job plans first; once it
19
+ * exists the dialog follows it live (AGL-3593) — planning, the plan with its
20
+ * Confirm, building, then the draft to open — and "Open AI jobs" follows it
21
+ * anywhere else in the console. Nothing here builds or writes anything until
22
+ * the member confirms the plan.
61
23
  */
62
- export declare function aiBriefJobInputs(kind: AiBriefKind, choice: AiBriefChoice): Record<string, string> | null;
24
+ export { AI_BRIEF_COPY, AI_BRIEF_NO_CHOICE, AI_EMAIL_TYPE_LABELS, AI_TEMPLATE_SUBJECT_LABELS, aiBriefJobInputs, aiBriefJobKind, };
25
+ export type { AiBriefChoice, AiBriefCopy, AiBriefKind } from '../model/ai-brief-copy';
63
26
  export interface AiBriefDialogProps {
64
27
  /** The job the brief starts. */
65
28
  kind: AiBriefKind;
@@ -23,7 +23,8 @@ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
23
23
  import { Alert, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, Stack, TextField, Typography } from "@mui/material";
24
24
  import { useCallback, useEffect, useRef, useState } from "react";
25
25
  import { AI_PAGE_TYPES } from "../model/ai-page-job.js";
26
- import { AI_TEMPLATE_SUBJECTS, parseAiTemplateJobInputs } from "../model/ai-template-subjects.js";
26
+ import { AI_TEMPLATE_SUBJECTS } from "../model/ai-template-subjects.js";
27
+ import { AI_BRIEF_COPY, AI_BRIEF_MAX_CHARS, AI_BRIEF_NO_CHOICE, AI_EMAIL_TYPE_LABELS, AI_TEMPLATE_SUBJECT_LABELS, aiBriefJobInputs, aiBriefJobKind } from "../model/ai-brief-copy.js";
27
28
  import { AI_SITE_BUILD_IN_FLIGHT_COPY, aiSiteBuildInFlight } from "../model/ai-job-activity.js";
28
29
  import { AiJobFollow } from "./ai-job-follow.component.js";
29
30
  import { publishAiJob, useAiJobsInFlightKnown } from "./ai-jobs-store.js";
@@ -48,72 +49,10 @@ import { AiTemplateCollectionField } from "./ai-template-collection-field.compon
48
49
  * Confirm, building, then the draft to open — and "Open AI jobs" follows it
49
50
  * anywhere else in the console. Nothing here builds or writes anything until
50
51
  * the member confirms the plan.
51
- */ /** The longest brief a job admits. */ const BRIEF_MAX_CHARS = 4000;
52
- export const AI_BRIEF_COPY = {
53
- page: {
54
- title: 'Describe a page',
55
- briefLabel: 'What is the page for?',
56
- placeholder: 'A landing page for our spring roof inspection offer, with the quote request form',
57
- 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.',
58
- submit: 'Plan the page',
59
- 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.',
60
- failed: 'The page could not be started. Try again.'
61
- },
62
- template: {
63
- title: 'Describe a page template',
64
- briefLabel: 'What should each page show?',
65
- 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',
66
- 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.',
67
- submit: 'Plan the template',
68
- 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.',
69
- failed: 'The template could not be started. Try again.'
70
- },
71
- layout: {
72
- title: 'Describe a layout',
73
- briefLabel: 'What should the layout hold?',
74
- placeholder: 'A header with our name and links to Services, About and Contact, and a footer with ' + 'our address and opening hours',
75
- 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.',
76
- submit: 'Plan the layout',
77
- 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.',
78
- failed: 'The layout could not be started. Try again.'
79
- },
80
- form: {
81
- title: 'Describe a form',
82
- briefLabel: 'What is the form for?',
83
- placeholder: 'A quote request form for a roofing company, asking what needs fixing and the best ' + 'time to call',
84
- 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.',
85
- submit: 'Plan the form',
86
- 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.',
87
- failed: 'The form could not be started. Try again.'
88
- },
89
- component: {
90
- title: 'Describe a reusable component',
91
- briefLabel: 'What should the component show?',
92
- placeholder: 'A testimonial card with the customer’s name, their role, their quote and a photo',
93
- 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.',
94
- submit: 'Plan the component',
95
- 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.',
96
- failed: 'The component could not be started. Try again.'
97
- },
98
- email: {
99
- title: 'Describe an email',
100
- briefLabel: 'What is the email for?',
101
- placeholder: 'Announce the autumn menu to our regulars, and point them at the class page to book ' + 'the Tuesday bread class',
102
- 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.',
103
- submit: 'Plan the email',
104
- 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.',
105
- failed: 'The email could not be started. Try again.'
106
- },
107
- campaign: {
108
- title: 'Describe a campaign',
109
- briefLabel: 'What is the campaign’s email for?',
110
- placeholder: 'Announce the autumn menu to our regulars, and point them at the class page to book ' + 'the Tuesday bread class',
111
- 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.',
112
- submit: 'Plan the campaign',
113
- 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.',
114
- failed: 'The campaign could not be started. Try again.'
115
- }
116
- };
52
+ */ // The words and the request each kind starts are the model's (AGL-3854), so
53
+ // the native apps' door reads the same ones; re-exported for this dialog's
54
+ // existing readers.
55
+ export { AI_BRIEF_COPY, AI_BRIEF_NO_CHOICE, AI_EMAIL_TYPE_LABELS, AI_TEMPLATE_SUBJECT_LABELS, aiBriefJobInputs, aiBriefJobKind, };
117
56
  /**
118
57
  * Each kind's own help beside the dialog's title (AGL-3660): the guide for
119
58
  * the thing being described, opened at the section about writing its brief,
@@ -148,69 +87,6 @@ export const AI_BRIEF_COPY = {
148
87
  excerpt: 'Writes the email and a draft campaign that would send it. The campaign is aimed at nobody until you pick its lists and schedule it.'
149
88
  })
150
89
  };
151
- /**
152
- * The kinds of email a member picks from, as their chips name them. The jobs'
153
- * own catalog (`AI_EMAIL_TYPES`) also holds `reply`, which only a job the
154
- * platform composes sets, so it is not offered here.
155
- */ export const AI_EMAIL_TYPE_LABELS = {
156
- welcome: 'Welcome',
157
- newsletter: 'Newsletter',
158
- launch: 'Launch',
159
- abandonedCart: 'Cart reminder',
160
- eventReminder: 'Event reminder'
161
- };
162
- /** Each template subject as its chip names it: what one page is drawn for. */ export const AI_TEMPLATE_SUBJECT_LABELS = {
163
- entry: 'Collection entry',
164
- product: 'Product',
165
- author: 'Author'
166
- };
167
- export const AI_BRIEF_NO_CHOICE = {
168
- pageType: null,
169
- subject: null,
170
- collectionId: null,
171
- emailType: null,
172
- withCampaign: false
173
- };
174
- /**
175
- * The job the door starts: its own kind, except an email that also asked for
176
- * its campaign, which is a `campaign` job — the campaign step writes the same
177
- * design and drafts the campaign around it.
178
- */ export function aiBriefJobKind(kind, choice) {
179
- return kind === 'email' && choice.withCampaign ? 'campaign' : kind;
180
- }
181
- /**
182
- * The job's `inputs` for what the member picked, or `null` while the kind's
183
- * door would refuse them.
184
- *
185
- * A template's are read by the parser its door reads, so the dialog cannot
186
- * send a subject the door does not know or an entry page with no collection.
187
- * A page's type is optional, and so is an email's or a campaign's kind of
188
- * email (`inputs.emailType`, which both steps read). A layout, a form and a component read no inputs
189
- * at all (AGL-2909, AGL-2913, AGL-2908), so theirs are empty.
190
- */ export function aiBriefJobInputs(kind, choice) {
191
- if (kind === 'page') return choice.pageType ? {
192
- pageType: choice.pageType
193
- } : {};
194
- if (kind === 'email' || kind === 'campaign') {
195
- return choice.emailType ? {
196
- emailType: choice.emailType
197
- } : {};
198
- }
199
- if (kind === 'template') {
200
- const inputs = parseAiTemplateJobInputs({
201
- subject: choice.subject,
202
- collectionId: choice.collectionId
203
- });
204
- if (typeof inputs === 'string') return null;
205
- return inputs.collectionId ? {
206
- subject: inputs.subject,
207
- collectionId: inputs.collectionId
208
- } : {
209
- subject: inputs.subject
210
- };
211
- }
212
- return {};
213
- }
214
90
  export function AiBriefDialog({ kind, open, onClose, orgId, hostId, user, orgSlug, isStaff }) {
215
91
  var _choice_emailType;
216
92
  // Held in a ref so the request reads WHO is signed in, and nothing keys on
@@ -334,8 +210,8 @@ export function AiBriefDialog({ kind, open, onClose, orgId, hostId, user, orgSlu
334
210
  multiline: true,
335
211
  minRows: 4,
336
212
  value: brief,
337
- onChange: (event)=>setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS)),
338
- helperText: `${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`,
213
+ onChange: (event)=>setBrief(event.target.value.slice(0, AI_BRIEF_MAX_CHARS)),
214
+ helperText: `${brief.length.toLocaleString('en-US')} / ${AI_BRIEF_MAX_CHARS.toLocaleString('en-US')}`,
339
215
  disabled: busy,
340
216
  autoFocus: true
341
217
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-brief-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\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 { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AiEmailType } from '../jobs/ai-job-email-step'\nimport type { AiJobKind, AiJobSummary } from '../model/ai-jobs.types'\nimport { AI_PAGE_TYPES, type AiPageType } from '../model/ai-page-job'\nimport {\n AI_TEMPLATE_SUBJECTS,\n parseAiTemplateJobInputs,\n type AiTemplateSubject,\n} from '../model/ai-template-subjects'\nimport { AI_SITE_BUILD_IN_FLIGHT_COPY, aiSiteBuildInFlight } from '../model/ai-job-activity'\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob, useAiJobsInFlightKnown } from './ai-jobs-store'\nimport { AiTemplateCollectionField } from './ai-template-collection-field.component'\n\n/**\n * The brief dialog every \"Create with AI\" opens (AGL-2907, AGL-3043, AGL-3051):\n * a page from the Screens page and from the Assist panel's AI jobs, a page\n * template from Templates, a layout from Layouts, a form from Forms, a\n * reusable component from Components, and an email design from the Emails\n * page's templates (AGL-3596). The `campaign` kind is the job an email's door\n * starts when the campaign is asked for too; the Campaigns section's own door\n * is a dialog of its own (`ai-campaign-create.component`, AGL-3603).\n *\n * One text box, and whatever the kind's door reads beside it, start one job of\n * that kind: a page's optional page type; a template's subject and, for a\n * collection entry's page, its collection. A layout, a form and a component\n * read nothing but the brief and the site. An email's reads the optional\n * kind of email, and its door also offers to draft the\n * campaign that would send it, which starts a `campaign` job instead — the\n * same design, plus a draft campaign aimed at nobody. The job plans first; once it\n * exists the dialog follows it live (AGL-3593) — planning, the plan with its\n * Confirm, building, then the draft to open — and \"Open AI jobs\" follows it\n * anywhere else in the console. Nothing here builds or writes anything until\n * the member confirms the plan.\n */\n\n/** The longest brief a job admits. */\nconst 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 * Each kind's own help beside the dialog's title (AGL-3660): the guide for\n * the thing being described, opened at the section about writing its brief,\n * never one page shared by all seven.\n */\nconst AI_BRIEF_HELP: Readonly<Record<AiBriefKind, ReturnType<typeof pluginDocsHelp>>> = {\n page: pluginDocsHelp('aiPage', {\n anchor: '#describe-the-page',\n excerpt:\n 'Say what the page is for and who it is for. A plan comes first, and the page is built as an unpublished draft once you confirm it.',\n }),\n template: pluginDocsHelp('aiTemplate', {\n anchor: '#generate-a-page-template-with-aglyn-ai',\n excerpt:\n 'Describe what each page made from the template should show. What changes per page is filled in from each record.',\n }),\n layout: pluginDocsHelp('aiLayout', {\n anchor: '#generate-a-layout-with-aglyn-ai',\n excerpt:\n 'Describe the header, navigation and footer. The layout is built as a draft, and no page uses it until you assign it.',\n }),\n form: pluginDocsHelp('aiForm', {\n anchor: '#describe-the-form',\n excerpt:\n 'Say what the form is for. Its fields, marketing consent and where submissions go are planned together, and it collects nothing until you place it.',\n }),\n component: pluginDocsHelp('aiComponent', {\n anchor: '#from-a-brief',\n excerpt:\n 'Describe one reusable block. What each page can change, such as a name or a photo, becomes a property of the component.',\n }),\n email: pluginDocsHelp('aiEmail', {\n anchor: '#write-the-brief',\n excerpt:\n 'Say what the email is for. It is built from email blocks with three subject lines to choose from, and nothing is sent.',\n }),\n campaign: pluginDocsHelp('aiEmail', {\n anchor: '#where-to-start-it',\n excerpt:\n 'Writes the email and a draft campaign that would send it. The campaign is aimed at nobody until you pick its lists and schedule it.',\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\nexport interface AiBriefDialogProps {\n /** The job the brief starts. */\n kind: AiBriefKind\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the request carries. */\n user: Parameters<typeof authorizedFetch>[0]\n /** Path slug for the link to the draft; read from the address when omitted. */\n orgSlug?: string | null\n /** The viewer is staff (AGL-3078), for a refused plan's details. */\n isStaff?: boolean\n}\n\nexport function AiBriefDialog({\n kind,\n open,\n onClose,\n orgId,\n hostId,\n user,\n orgSlug,\n isStaff,\n}: AiBriefDialogProps) {\n // Held in a ref so the request reads WHO is signed in, and nothing keys on\n // the identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const [brief, setBrief] = useState('')\n const [choice, setChoice] = useState<AiBriefChoice>(AI_BRIEF_NO_CHOICE)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** The job the brief started, as the create door answered with it. */\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n // An email that asked for its campaign is said as the campaign it starts,\n // and a started job as the kind it is (the choice is cleared once it starts).\n const shownKind =\n started && (started.kind === 'email' || started.kind === 'campaign')\n ? started.kind\n : aiBriefJobKind(kind, choice)\n const copy = AI_BRIEF_COPY[shownKind]\n // Another page already being built on this site (AGL-3765), from the list\n // the top-bar indicator already holds, read again by nothing here: two at\n // once each plan against the site as it was, and race for what its plan\n // holds one of. A warning, not a stop.\n const asking = open && kind === 'page' && !started\n const inFlight = useAiJobsInFlightKnown(user, orgId, asking)\n const building = asking ? aiSiteBuildInFlight(inFlight, { hostId }) : null\n\n useEffect(() => {\n if (!open) return\n setNotice(null)\n setStarted(null)\n }, [open])\n\n const pick = useCallback(\n (patch: Partial<AiBriefChoice>) => setChoice((current) => ({ ...current, ...patch })),\n [],\n )\n const ready = aiBriefJobInputs(kind, choice) !== null\n\n const start = useCallback(async () => {\n const inputs = aiBriefJobInputs(kind, choice)\n const jobKind = aiBriefJobKind(kind, choice)\n if (!orgId || !brief.trim() || !inputs) return\n setBusy(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, hostId, kind: jobKind, brief: brief.trim(), inputs }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? copy.failed))\n return\n }\n const created = (payload?.job as AiJobSummary | undefined) ?? null\n if (!created) {\n setNotice(copy.failed)\n return\n }\n // The indicator and the launcher count it from the moment it exists.\n publishAiJob(created)\n setStarted(created)\n setBrief('')\n setChoice(AI_BRIEF_NO_CHOICE)\n } catch {\n setNotice(copy.failed)\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, kind, brief, choice, copy])\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...AI_BRIEF_HELP[shownKind]} />\n </DialogTitle>\n <DialogContent>\n {started && orgId ? (\n <AiJobFollow\n job={started}\n orgId={orgId}\n orgSlug={orgSlug}\n user={user}\n intro={copy.started}\n staff={isStaff}\n // The dialog covers the panel AI jobs opens in.\n onOpenJobs={onClose}\n />\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={busy}\n autoFocus\n />\n {kind === 'page' ? (\n <ChipGroup\n id=\"ai-page-type-label\"\n label=\"Page type (optional)\"\n options={AI_PAGE_TYPES.map((type) => ({ id: type.id, label: type.label }))}\n value={choice.pageType}\n // Optional: pressing the pressed type again sends none.\n onPress={(id) =>\n pick({ pageType: choice.pageType === id ? null : (id as AiPageType) })\n }\n disabled={busy}\n />\n ) : null}\n {kind === 'email' || kind === 'campaign' ? (\n <ChipGroup\n id=\"ai-email-type-label\"\n label=\"Kind of email (optional)\"\n options={Object.entries(AI_EMAIL_TYPE_LABELS).map(([id, label]) => ({\n id,\n label,\n }))}\n value={choice.emailType ?? null}\n // Optional: pressing the pressed kind again sends none.\n onPress={(id) =>\n pick({\n emailType:\n choice.emailType === id ? null : (id as AiBriefChoice['emailType']),\n })\n }\n disabled={busy}\n />\n ) : null}\n {kind === 'email' ? (\n <FormControlLabel\n control={\n <Checkbox\n checked={Boolean(choice.withCampaign)}\n onChange={(event) => pick({ withCampaign: event.target.checked })}\n disabled={busy}\n />\n }\n label=\"Also draft a campaign that sends it\"\n />\n ) : null}\n {kind === 'template' ? (\n <>\n <ChipGroup\n id=\"ai-template-subject-label\"\n // Marked required because it IS (AGL-3143 §12): \"Plan the\n // template\" stays disabled until one is picked, and the page\n // dialog's chip group beside it reads \"(optional)\" — so an\n // unmarked label here read as optional too, and a member who\n // wrote a brief and stopped had nothing telling them why the\n // button would not light.\n label=\"One page for each (required)\"\n options={AI_TEMPLATE_SUBJECTS.map((subject) => ({\n id: subject,\n label: AI_TEMPLATE_SUBJECT_LABELS[subject],\n }))}\n value={choice.subject}\n // Required, so a press picks and never clears.\n onPress={(id) => pick({ subject: id as AiTemplateSubject })}\n disabled={busy}\n />\n {choice.subject === 'entry' ? (\n <AiTemplateCollectionField\n hostId={hostId}\n value={choice.collectionId}\n onChange={(collectionId) => pick({ collectionId })}\n disabled={busy}\n />\n ) : null}\n </>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}\n </Typography>\n {building ? <Alert severity=\"info\">{AI_SITE_BUILD_IN_FLIGHT_COPY}</Alert> : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {started ? 'Close' : 'Cancel'}\n </Button>\n {started ? null : (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={busy || !orgId || !brief.trim() || !ready}\n >\n {copy.submit}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n )\n}\n\n/** A labeled row of chips, each a toggle button that says whether it is pressed. */\nfunction ChipGroup(props: {\n id: string\n label: string\n options: ReadonlyArray<{ id: string; label: string }>\n value: string | null\n onPress: (id: string) => void\n disabled: boolean\n}) {\n const { id, label, options, value, onPress, disabled } = props\n return (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\" id={id}>\n {label}\n </Typography>\n <Stack\n direction=\"row\"\n useFlexGap\n role=\"group\"\n aria-labelledby={id}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {options.map((option) => (\n <Chip\n key={option.id}\n label={option.label}\n color={value === option.id ? 'primary' : 'default'}\n variant={value === option.id ? 'filled' : 'outlined'}\n onClick={() => onPress(option.id)}\n aria-pressed={value === option.id}\n disabled={disabled}\n />\n ))}\n </Stack>\n </Stack>\n )\n}\n\nexport default AiBriefDialog\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","HelpTip","authorizedFetch","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_PAGE_TYPES","AI_TEMPLATE_SUBJECTS","parseAiTemplateJobInputs","AI_SITE_BUILD_IN_FLIGHT_COPY","aiSiteBuildInFlight","AiJobFollow","publishAiJob","useAiJobsInFlightKnown","AiTemplateCollectionField","BRIEF_MAX_CHARS","AI_BRIEF_COPY","page","title","briefLabel","placeholder","next","submit","started","failed","template","layout","form","component","email","campaign","AI_BRIEF_HELP","anchor","excerpt","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","AiBriefDialog","open","onClose","orgId","hostId","user","orgSlug","isStaff","userRef","current","brief","setBrief","setChoice","busy","setBusy","notice","setNotice","setStarted","shownKind","copy","asking","inFlight","building","pick","patch","ready","start","jobKind","trim","response","method","headers","body","JSON","stringify","payload","json","catch","ok","locked","status","String","error","created","job","undefined","fullWidth","maxWidth","sx","display","alignItems","gap","intro","staff","onOpenJobs","spacing","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","ChipGroup","id","options","map","type","onPress","Object","entries","control","checked","Boolean","variant","color","severity","onClick","props","direction","useFlexGap","role","aria-labelledby","flexWrap","option","aria-pressed"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-brief-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\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 { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { AI_PAGE_TYPES, type AiPageType } from '../model/ai-page-job'\nimport { AI_TEMPLATE_SUBJECTS, type AiTemplateSubject } from '../model/ai-template-subjects'\nimport {\n AI_BRIEF_COPY,\n AI_BRIEF_MAX_CHARS,\n AI_BRIEF_NO_CHOICE,\n AI_EMAIL_TYPE_LABELS,\n AI_TEMPLATE_SUBJECT_LABELS,\n aiBriefJobInputs,\n aiBriefJobKind,\n type AiBriefChoice,\n type AiBriefKind,\n} from '../model/ai-brief-copy'\nimport { AI_SITE_BUILD_IN_FLIGHT_COPY, aiSiteBuildInFlight } from '../model/ai-job-activity'\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob, useAiJobsInFlightKnown } from './ai-jobs-store'\nimport { AiTemplateCollectionField } from './ai-template-collection-field.component'\n\n/**\n * The brief dialog every \"Create with AI\" opens (AGL-2907, AGL-3043, AGL-3051):\n * a page from the Screens page and from the Assist panel's AI jobs, a page\n * template from Templates, a layout from Layouts, a form from Forms, a\n * reusable component from Components, and an email design from the Emails\n * page's templates (AGL-3596). The `campaign` kind is the job an email's door\n * starts when the campaign is asked for too; the Campaigns section's own door\n * is a dialog of its own (`ai-campaign-create.component`, AGL-3603).\n *\n * One text box, and whatever the kind's door reads beside it, start one job of\n * that kind: a page's optional page type; a template's subject and, for a\n * collection entry's page, its collection. A layout, a form and a component\n * read nothing but the brief and the site. An email's reads the optional\n * kind of email, and its door also offers to draft the\n * campaign that would send it, which starts a `campaign` job instead — the\n * same design, plus a draft campaign aimed at nobody. The job plans first; once it\n * exists the dialog follows it live (AGL-3593) — planning, the plan with its\n * Confirm, building, then the draft to open — and \"Open AI jobs\" follows it\n * anywhere else in the console. Nothing here builds or writes anything until\n * the member confirms the plan.\n */\n\n// The words and the request each kind starts are the model's (AGL-3854), so\n// the native apps' door reads the same ones; re-exported for this dialog's\n// existing readers.\nexport {\n AI_BRIEF_COPY,\n AI_BRIEF_NO_CHOICE,\n AI_EMAIL_TYPE_LABELS,\n AI_TEMPLATE_SUBJECT_LABELS,\n aiBriefJobInputs,\n aiBriefJobKind,\n}\nexport type { AiBriefChoice, AiBriefCopy, AiBriefKind } from '../model/ai-brief-copy'\n\n/**\n * Each kind's own help beside the dialog's title (AGL-3660): the guide for\n * the thing being described, opened at the section about writing its brief,\n * never one page shared by all seven.\n */\nconst AI_BRIEF_HELP: Readonly<Record<AiBriefKind, ReturnType<typeof pluginDocsHelp>>> = {\n page: pluginDocsHelp('aiPage', {\n anchor: '#describe-the-page',\n excerpt:\n 'Say what the page is for and who it is for. A plan comes first, and the page is built as an unpublished draft once you confirm it.',\n }),\n template: pluginDocsHelp('aiTemplate', {\n anchor: '#generate-a-page-template-with-aglyn-ai',\n excerpt:\n 'Describe what each page made from the template should show. What changes per page is filled in from each record.',\n }),\n layout: pluginDocsHelp('aiLayout', {\n anchor: '#generate-a-layout-with-aglyn-ai',\n excerpt:\n 'Describe the header, navigation and footer. The layout is built as a draft, and no page uses it until you assign it.',\n }),\n form: pluginDocsHelp('aiForm', {\n anchor: '#describe-the-form',\n excerpt:\n 'Say what the form is for. Its fields, marketing consent and where submissions go are planned together, and it collects nothing until you place it.',\n }),\n component: pluginDocsHelp('aiComponent', {\n anchor: '#from-a-brief',\n excerpt:\n 'Describe one reusable block. What each page can change, such as a name or a photo, becomes a property of the component.',\n }),\n email: pluginDocsHelp('aiEmail', {\n anchor: '#write-the-brief',\n excerpt:\n 'Say what the email is for. It is built from email blocks with three subject lines to choose from, and nothing is sent.',\n }),\n campaign: pluginDocsHelp('aiEmail', {\n anchor: '#where-to-start-it',\n excerpt:\n 'Writes the email and a draft campaign that would send it. The campaign is aimed at nobody until you pick its lists and schedule it.',\n }),\n}\n\n\nexport interface AiBriefDialogProps {\n /** The job the brief starts. */\n kind: AiBriefKind\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the request carries. */\n user: Parameters<typeof authorizedFetch>[0]\n /** Path slug for the link to the draft; read from the address when omitted. */\n orgSlug?: string | null\n /** The viewer is staff (AGL-3078), for a refused plan's details. */\n isStaff?: boolean\n}\n\nexport function AiBriefDialog({\n kind,\n open,\n onClose,\n orgId,\n hostId,\n user,\n orgSlug,\n isStaff,\n}: AiBriefDialogProps) {\n // Held in a ref so the request reads WHO is signed in, and nothing keys on\n // the identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const [brief, setBrief] = useState('')\n const [choice, setChoice] = useState<AiBriefChoice>(AI_BRIEF_NO_CHOICE)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** The job the brief started, as the create door answered with it. */\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n // An email that asked for its campaign is said as the campaign it starts,\n // and a started job as the kind it is (the choice is cleared once it starts).\n const shownKind =\n started && (started.kind === 'email' || started.kind === 'campaign')\n ? started.kind\n : aiBriefJobKind(kind, choice)\n const copy = AI_BRIEF_COPY[shownKind]\n // Another page already being built on this site (AGL-3765), from the list\n // the top-bar indicator already holds, read again by nothing here: two at\n // once each plan against the site as it was, and race for what its plan\n // holds one of. A warning, not a stop.\n const asking = open && kind === 'page' && !started\n const inFlight = useAiJobsInFlightKnown(user, orgId, asking)\n const building = asking ? aiSiteBuildInFlight(inFlight, { hostId }) : null\n\n useEffect(() => {\n if (!open) return\n setNotice(null)\n setStarted(null)\n }, [open])\n\n const pick = useCallback(\n (patch: Partial<AiBriefChoice>) => setChoice((current) => ({ ...current, ...patch })),\n [],\n )\n const ready = aiBriefJobInputs(kind, choice) !== null\n\n const start = useCallback(async () => {\n const inputs = aiBriefJobInputs(kind, choice)\n const jobKind = aiBriefJobKind(kind, choice)\n if (!orgId || !brief.trim() || !inputs) return\n setBusy(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, hostId, kind: jobKind, brief: brief.trim(), inputs }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? copy.failed))\n return\n }\n const created = (payload?.job as AiJobSummary | undefined) ?? null\n if (!created) {\n setNotice(copy.failed)\n return\n }\n // The indicator and the launcher count it from the moment it exists.\n publishAiJob(created)\n setStarted(created)\n setBrief('')\n setChoice(AI_BRIEF_NO_CHOICE)\n } catch {\n setNotice(copy.failed)\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, kind, brief, choice, copy])\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...AI_BRIEF_HELP[shownKind]} />\n </DialogTitle>\n <DialogContent>\n {started && orgId ? (\n <AiJobFollow\n job={started}\n orgId={orgId}\n orgSlug={orgSlug}\n user={user}\n intro={copy.started}\n staff={isStaff}\n // The dialog covers the panel AI jobs opens in.\n onOpenJobs={onClose}\n />\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, AI_BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${AI_BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={busy}\n autoFocus\n />\n {kind === 'page' ? (\n <ChipGroup\n id=\"ai-page-type-label\"\n label=\"Page type (optional)\"\n options={AI_PAGE_TYPES.map((type) => ({ id: type.id, label: type.label }))}\n value={choice.pageType}\n // Optional: pressing the pressed type again sends none.\n onPress={(id) =>\n pick({ pageType: choice.pageType === id ? null : (id as AiPageType) })\n }\n disabled={busy}\n />\n ) : null}\n {kind === 'email' || kind === 'campaign' ? (\n <ChipGroup\n id=\"ai-email-type-label\"\n label=\"Kind of email (optional)\"\n options={Object.entries(AI_EMAIL_TYPE_LABELS).map(([id, label]) => ({\n id,\n label,\n }))}\n value={choice.emailType ?? null}\n // Optional: pressing the pressed kind again sends none.\n onPress={(id) =>\n pick({\n emailType:\n choice.emailType === id ? null : (id as AiBriefChoice['emailType']),\n })\n }\n disabled={busy}\n />\n ) : null}\n {kind === 'email' ? (\n <FormControlLabel\n control={\n <Checkbox\n checked={Boolean(choice.withCampaign)}\n onChange={(event) => pick({ withCampaign: event.target.checked })}\n disabled={busy}\n />\n }\n label=\"Also draft a campaign that sends it\"\n />\n ) : null}\n {kind === 'template' ? (\n <>\n <ChipGroup\n id=\"ai-template-subject-label\"\n // Marked required because it IS (AGL-3143 §12): \"Plan the\n // template\" stays disabled until one is picked, and the page\n // dialog's chip group beside it reads \"(optional)\" — so an\n // unmarked label here read as optional too, and a member who\n // wrote a brief and stopped had nothing telling them why the\n // button would not light.\n label=\"One page for each (required)\"\n options={AI_TEMPLATE_SUBJECTS.map((subject) => ({\n id: subject,\n label: AI_TEMPLATE_SUBJECT_LABELS[subject],\n }))}\n value={choice.subject}\n // Required, so a press picks and never clears.\n onPress={(id) => pick({ subject: id as AiTemplateSubject })}\n disabled={busy}\n />\n {choice.subject === 'entry' ? (\n <AiTemplateCollectionField\n hostId={hostId}\n value={choice.collectionId}\n onChange={(collectionId) => pick({ collectionId })}\n disabled={busy}\n />\n ) : null}\n </>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}\n </Typography>\n {building ? <Alert severity=\"info\">{AI_SITE_BUILD_IN_FLIGHT_COPY}</Alert> : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {started ? 'Close' : 'Cancel'}\n </Button>\n {started ? null : (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={busy || !orgId || !brief.trim() || !ready}\n >\n {copy.submit}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n )\n}\n\n/** A labeled row of chips, each a toggle button that says whether it is pressed. */\nfunction ChipGroup(props: {\n id: string\n label: string\n options: ReadonlyArray<{ id: string; label: string }>\n value: string | null\n onPress: (id: string) => void\n disabled: boolean\n}) {\n const { id, label, options, value, onPress, disabled } = props\n return (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\" id={id}>\n {label}\n </Typography>\n <Stack\n direction=\"row\"\n useFlexGap\n role=\"group\"\n aria-labelledby={id}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {options.map((option) => (\n <Chip\n key={option.id}\n label={option.label}\n color={value === option.id ? 'primary' : 'default'}\n variant={value === option.id ? 'filled' : 'outlined'}\n onClick={() => onPress(option.id)}\n aria-pressed={value === option.id}\n disabled={disabled}\n />\n ))}\n </Stack>\n </Stack>\n )\n}\n\nexport default AiBriefDialog\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","HelpTip","authorizedFetch","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_PAGE_TYPES","AI_TEMPLATE_SUBJECTS","AI_BRIEF_COPY","AI_BRIEF_MAX_CHARS","AI_BRIEF_NO_CHOICE","AI_EMAIL_TYPE_LABELS","AI_TEMPLATE_SUBJECT_LABELS","aiBriefJobInputs","aiBriefJobKind","AI_SITE_BUILD_IN_FLIGHT_COPY","aiSiteBuildInFlight","AiJobFollow","publishAiJob","useAiJobsInFlightKnown","AiTemplateCollectionField","AI_BRIEF_HELP","page","anchor","excerpt","template","layout","form","component","email","campaign","AiBriefDialog","kind","open","onClose","orgId","hostId","user","orgSlug","isStaff","choice","userRef","current","brief","setBrief","setChoice","busy","setBusy","notice","setNotice","started","setStarted","shownKind","copy","asking","inFlight","building","pick","patch","ready","start","inputs","jobKind","trim","response","method","headers","body","JSON","stringify","payload","json","catch","ok","locked","status","String","error","failed","created","job","undefined","fullWidth","maxWidth","sx","display","alignItems","gap","title","intro","staff","onOpenJobs","spacing","pt","label","briefLabel","placeholder","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","ChipGroup","id","options","map","type","pageType","onPress","Object","entries","emailType","control","checked","Boolean","withCampaign","subject","collectionId","variant","color","next","severity","onClick","submit","props","direction","useFlexGap","role","aria-labelledby","flexWrap","option","aria-pressed"],"mappings":"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"}
@@ -18,7 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
18
  import { AppLink } from "@aglyn/shared-ui-jsx";
19
19
  import { Alert, Box, Button, Chip, CircularProgress, Stack, Typography } from "@mui/material";
20
20
  import { useCallback, useEffect, useRef, useState } from "react";
21
- import { AI_JOB_PHASE_LABELS, aiJobPhase } from "../model/ai-job-activity.js";
21
+ import { AI_JOB_FOLLOW_PHASE_LINES, AI_JOB_PHASE_LABELS, aiJobPhase } from "../model/ai-job-activity.js";
22
22
  import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
23
23
  import { AiJobPlan } from "./ai-job-plan.component.js";
24
24
  import { cancelAiJobRequest, resumeAiJobRequest } from "./ai-job-requests.js";
@@ -26,40 +26,7 @@ import { openAiJobs, publishAiJob } from "./ai-jobs-store.js";
26
26
  import { followAiJobEvents } from "./ai-job-events.js";
27
27
  import { aiJobPrimaryLink } from "./ai-job-links.js";
28
28
  import { aiJobProblem } from "./use-ai-job-run.js";
29
- /** What the status line says in each phase, and how loudly. */ const PHASE_LINE = {
30
- queued: {
31
- severity: 'info',
32
- text: 'Queued. It starts in a moment.'
33
- },
34
- planning: {
35
- severity: 'info',
36
- text: 'Planning. The plan appears here when it is ready.'
37
- },
38
- 'plan-ready': {
39
- severity: 'warning',
40
- text: 'Your plan is ready. Read it below and confirm it to build — nothing is built until you do.'
41
- },
42
- building: {
43
- severity: 'info',
44
- text: 'Building. Everything it builds is an unpublished draft.'
45
- },
46
- attention: {
47
- severity: 'warning',
48
- text: 'The job stopped and needs you.'
49
- },
50
- done: {
51
- severity: 'success',
52
- text: 'Done. Your drafts are ready to open.'
53
- },
54
- failed: {
55
- severity: 'error',
56
- text: 'The job stopped before it finished.'
57
- },
58
- canceled: {
59
- severity: 'info',
60
- text: 'The job was canceled.'
61
- }
62
- };
29
+ /** What the status line says in each phase, and how loudly: the model's, which the apps' follow view sends too. */ const PHASE_LINE = AI_JOB_FOLLOW_PHASE_LINES;
63
30
  /** The org slug a console address names: its first segment. */ function orgSlugFromLocation() {
64
31
  var _path_split_;
65
32
  var _globalThis_location;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-follow.component.tsx"],"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'use client'\n\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { Alert, Box, Button, Chip, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobPhase,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { cancelAiJobRequest, resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\nimport { followAiJobEvents } from './ai-job-events'\nimport { aiJobPrimaryLink } from './ai-job-links'\nimport { aiJobProblem } from './use-ai-job-run'\n\n/**\n * A JOB, WATCHED AND DECIDED WHERE IT WAS STARTED (AGL-3593).\n *\n * The dialog that starts a planned job — \"Plan my site\", \"Plan the page\" —\n * used to end on a sentence pointing at a drawer a person had to go and find,\n * and a person who could not find it never got their site built. This is\n * what such a dialog shows instead, once its job exists:\n *\n * - where the job stands, live — queued, planning, plan ready, building,\n * then done or failed — read from the same events route the AI jobs\n * drawer follows;\n * - the plan, once it is ready, with the drawer's own plan component and its\n * Confirm, through the same request the drawer sends\n * ({@link resumeAiJobRequest}), and Cancel beside it;\n * - once it is done, the one action that opens what it built;\n * - and always \"Open AI jobs\", which follows the job anywhere else in the\n * console.\n *\n * Closing the dialog stops following; the job carries on, and the top-bar\n * indicator, the Assist launcher and the notification the machine raises say\n * when it needs the person again.\n */\n\nexport interface AiJobFollowProps {\n /** The job as the create door answered with it. */\n job: AiJobSummary\n orgId: string\n /**\n * Path slug for the link to what the job built. A dialog mounted on a\n * console page that names its org can leave it out: the slug is the first\n * segment of every org-scoped console address.\n */\n orgSlug?: string | null\n user: MaybeTokenSource\n /** What the dialog says about the job it started, above its live status. */\n intro: string\n /**\n * Closes the dialog. Called beside opening AI jobs, because a dialog\n * covers the panel AI jobs opens in.\n */\n onOpenJobs?: () => void\n /** The viewer is staff (AGL-3078): a refused answer's details are theirs to open. */\n staff?: boolean\n}\n\n/** What the status line says in each phase, and how loudly. */\nconst PHASE_LINE: Readonly<\n Record<AiJobPhase, { severity: 'info' | 'warning' | 'success' | 'error'; text: string }>\n> = {\n queued: { severity: 'info', text: 'Queued. It starts in a moment.' },\n planning: { severity: 'info', text: 'Planning. The plan appears here when it is ready.' },\n 'plan-ready': {\n severity: 'warning',\n text: 'Your plan is ready. Read it below and confirm it to build — nothing is built until you do.',\n },\n building: { severity: 'info', text: 'Building. Everything it builds is an unpublished draft.' },\n attention: { severity: 'warning', text: 'The job stopped and needs you.' },\n done: { severity: 'success', text: 'Done. Your drafts are ready to open.' },\n failed: { severity: 'error', text: 'The job stopped before it finished.' },\n canceled: { severity: 'info', text: 'The job was canceled.' },\n}\n\n/** The org slug a console address names: its first segment. */\nfunction orgSlugFromLocation(): string {\n const path = typeof globalThis.location?.pathname === 'string' ? globalThis.location.pathname : ''\n return path.split('/')[1] ?? ''\n}\n\n/** Follows `initial` through the events route while mounted, and lets a decision replace it. */\nexport function useFollowedAiJob(\n user: MaybeTokenSource,\n orgId: string,\n initial: AiJobSummary,\n): [AiJobSummary, (job: AiJobSummary) => void] {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState(initial)\n useEffect(() => setJob(initial), [initial])\n\n // One stream while the job is moving. A job parked for the person settles\n // the stream; confirming moves it again, and the status change re-opens it.\n const moving = job.status === 'queued' || job.status === 'running'\n useEffect(() => {\n if (!moving) return undefined\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, job.id, controller.signal, setJob)\n return () => controller.abort()\n }, [moving, orgId, job.id])\n\n const replace = useCallback((next: AiJobSummary) => {\n publishAiJob(next)\n setJob(next)\n }, [])\n return [job, replace]\n}\n\nexport function AiJobFollow({\n job: initial,\n orgId,\n orgSlug,\n user,\n intro,\n onOpenJobs,\n staff = false,\n}: AiJobFollowProps) {\n const [job, replace] = useFollowedAiJob(user, orgId, initial)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const phase = aiJobPhase(job)\n const line = PHASE_LINE[phase]\n const moving = phase === 'queued' || phase === 'planning' || phase === 'building'\n const settled = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const primary = aiJobPrimaryLink(job, orgSlug || orgSlugFromLocation())\n const problem = phase === 'attention' || phase === 'failed' ? aiJobProblem(job, line.text) : null\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n\n const decide = useCallback(\n async (request: () => ReturnType<typeof resumeAiJobRequest>) => {\n setBusy(true)\n setNotice(null)\n const { job: next, error } = await request()\n if (next) replace(next)\n if (error) setNotice(error)\n setBusy(false)\n },\n [replace],\n )\n\n const openJobs = () => {\n openAiJobs({ jobId: job.id })\n onOpenJobs?.()\n }\n\n return (\n <Stack spacing={2} aria-live=\"polite\">\n <Typography variant=\"body2\" color=\"text.secondary\">\n {intro}\n </Typography>\n <Alert\n severity={line.severity}\n icon={moving ? <CircularProgress size={18} aria-label={AI_JOB_PHASE_LABELS[phase]} /> : undefined}\n action={\n <Button color=\"inherit\" size=\"small\" variant=\"outlined\" onClick={openJobs}>\n {'Open AI jobs'}\n </Button>\n }\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" label={AI_JOB_PHASE_LABELS[phase]} />\n {job.steps.length > 1 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${stepsDone} of ${job.steps.length} steps`}\n </Typography>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {problem ?? line.text}\n </Typography>\n </Alert>\n {primary ? (\n <Box>\n <Button variant=\"contained\" component={AppLink} href={primary.href}>\n {primary.label}\n </Button>\n </Box>\n ) : null}\n {/* The drawer's own plan and its Confirm, on the drawer's request. */}\n <AiJobPlan\n job={job}\n onResume={(target) => void decide(() => resumeAiJobRequest(user, orgId, target))}\n busy={busy}\n staff={staff}\n // \"Use the starter site instead\" on a guided start that did not work out (AGL-3594).\n user={user ?? undefined}\n />\n {!settled ? (\n <Box>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => void decide(() => cancelAiJobRequest(user, orgId, job.id))}\n >\n {'Cancel job'}\n </Button>\n </Box>\n ) : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )\n}\n\nexport default AiJobFollow\n"],"names":["AppLink","Alert","Box","Button","Chip","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobPhase","AI_JOB_TERMINAL_STATUSES","AiJobPlan","cancelAiJobRequest","resumeAiJobRequest","openAiJobs","publishAiJob","followAiJobEvents","aiJobPrimaryLink","aiJobProblem","PHASE_LINE","queued","severity","text","planning","building","attention","done","failed","canceled","orgSlugFromLocation","path","globalThis","location","pathname","split","useFollowedAiJob","user","orgId","initial","userRef","current","job","setJob","moving","status","undefined","controller","AbortController","id","signal","abort","replace","next","AiJobFollow","orgSlug","intro","onOpenJobs","staff","busy","setBusy","notice","setNotice","phase","line","settled","includes","primary","problem","stepsDone","steps","filter","step","length","decide","request","error","openJobs","jobId","spacing","aria-live","variant","color","icon","size","aria-label","action","onClick","useFlexGap","direction","sx","alignItems","flexWrap","rowGap","label","mt","component","href","onResume","target","disabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-follow.component.tsx"],"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'use client'\n\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { Alert, Box, Button, Chip, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_FOLLOW_PHASE_LINES, AI_JOB_PHASE_LABELS, aiJobPhase } from '../model/ai-job-activity'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { cancelAiJobRequest, resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\nimport { followAiJobEvents } from './ai-job-events'\nimport { aiJobPrimaryLink } from './ai-job-links'\nimport { aiJobProblem } from './use-ai-job-run'\n\n/**\n * A JOB, WATCHED AND DECIDED WHERE IT WAS STARTED (AGL-3593).\n *\n * The dialog that starts a planned job — \"Plan my site\", \"Plan the page\" —\n * used to end on a sentence pointing at a drawer a person had to go and find,\n * and a person who could not find it never got their site built. This is\n * what such a dialog shows instead, once its job exists:\n *\n * - where the job stands, live — queued, planning, plan ready, building,\n * then done or failed — read from the same events route the AI jobs\n * drawer follows;\n * - the plan, once it is ready, with the drawer's own plan component and its\n * Confirm, through the same request the drawer sends\n * ({@link resumeAiJobRequest}), and Cancel beside it;\n * - once it is done, the one action that opens what it built;\n * - and always \"Open AI jobs\", which follows the job anywhere else in the\n * console.\n *\n * Closing the dialog stops following; the job carries on, and the top-bar\n * indicator, the Assist launcher and the notification the machine raises say\n * when it needs the person again.\n */\n\nexport interface AiJobFollowProps {\n /** The job as the create door answered with it. */\n job: AiJobSummary\n orgId: string\n /**\n * Path slug for the link to what the job built. A dialog mounted on a\n * console page that names its org can leave it out: the slug is the first\n * segment of every org-scoped console address.\n */\n orgSlug?: string | null\n user: MaybeTokenSource\n /** What the dialog says about the job it started, above its live status. */\n intro: string\n /**\n * Closes the dialog. Called beside opening AI jobs, because a dialog\n * covers the panel AI jobs opens in.\n */\n onOpenJobs?: () => void\n /** The viewer is staff (AGL-3078): a refused answer's details are theirs to open. */\n staff?: boolean\n}\n\n/** What the status line says in each phase, and how loudly: the model's, which the apps' follow view sends too. */\nconst PHASE_LINE = AI_JOB_FOLLOW_PHASE_LINES\n\n/** The org slug a console address names: its first segment. */\nfunction orgSlugFromLocation(): string {\n const path = typeof globalThis.location?.pathname === 'string' ? globalThis.location.pathname : ''\n return path.split('/')[1] ?? ''\n}\n\n/** Follows `initial` through the events route while mounted, and lets a decision replace it. */\nexport function useFollowedAiJob(\n user: MaybeTokenSource,\n orgId: string,\n initial: AiJobSummary,\n): [AiJobSummary, (job: AiJobSummary) => void] {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState(initial)\n useEffect(() => setJob(initial), [initial])\n\n // One stream while the job is moving. A job parked for the person settles\n // the stream; confirming moves it again, and the status change re-opens it.\n const moving = job.status === 'queued' || job.status === 'running'\n useEffect(() => {\n if (!moving) return undefined\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, job.id, controller.signal, setJob)\n return () => controller.abort()\n }, [moving, orgId, job.id])\n\n const replace = useCallback((next: AiJobSummary) => {\n publishAiJob(next)\n setJob(next)\n }, [])\n return [job, replace]\n}\n\nexport function AiJobFollow({\n job: initial,\n orgId,\n orgSlug,\n user,\n intro,\n onOpenJobs,\n staff = false,\n}: AiJobFollowProps) {\n const [job, replace] = useFollowedAiJob(user, orgId, initial)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const phase = aiJobPhase(job)\n const line = PHASE_LINE[phase]\n const moving = phase === 'queued' || phase === 'planning' || phase === 'building'\n const settled = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const primary = aiJobPrimaryLink(job, orgSlug || orgSlugFromLocation())\n const problem = phase === 'attention' || phase === 'failed' ? aiJobProblem(job, line.text) : null\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n\n const decide = useCallback(\n async (request: () => ReturnType<typeof resumeAiJobRequest>) => {\n setBusy(true)\n setNotice(null)\n const { job: next, error } = await request()\n if (next) replace(next)\n if (error) setNotice(error)\n setBusy(false)\n },\n [replace],\n )\n\n const openJobs = () => {\n openAiJobs({ jobId: job.id })\n onOpenJobs?.()\n }\n\n return (\n <Stack spacing={2} aria-live=\"polite\">\n <Typography variant=\"body2\" color=\"text.secondary\">\n {intro}\n </Typography>\n <Alert\n severity={line.severity}\n icon={moving ? <CircularProgress size={18} aria-label={AI_JOB_PHASE_LABELS[phase]} /> : undefined}\n action={\n <Button color=\"inherit\" size=\"small\" variant=\"outlined\" onClick={openJobs}>\n {'Open AI jobs'}\n </Button>\n }\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" label={AI_JOB_PHASE_LABELS[phase]} />\n {job.steps.length > 1 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${stepsDone} of ${job.steps.length} steps`}\n </Typography>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {problem ?? line.text}\n </Typography>\n </Alert>\n {primary ? (\n <Box>\n <Button variant=\"contained\" component={AppLink} href={primary.href}>\n {primary.label}\n </Button>\n </Box>\n ) : null}\n {/* The drawer's own plan and its Confirm, on the drawer's request. */}\n <AiJobPlan\n job={job}\n onResume={(target) => void decide(() => resumeAiJobRequest(user, orgId, target))}\n busy={busy}\n staff={staff}\n // \"Use the starter site instead\" on a guided start that did not work out (AGL-3594).\n user={user ?? undefined}\n />\n {!settled ? (\n <Box>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => void decide(() => cancelAiJobRequest(user, orgId, job.id))}\n >\n {'Cancel job'}\n </Button>\n </Box>\n ) : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )\n}\n\nexport default AiJobFollow\n"],"names":["AppLink","Alert","Box","Button","Chip","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_FOLLOW_PHASE_LINES","AI_JOB_PHASE_LABELS","aiJobPhase","AI_JOB_TERMINAL_STATUSES","AiJobPlan","cancelAiJobRequest","resumeAiJobRequest","openAiJobs","publishAiJob","followAiJobEvents","aiJobPrimaryLink","aiJobProblem","PHASE_LINE","orgSlugFromLocation","path","globalThis","location","pathname","split","useFollowedAiJob","user","orgId","initial","userRef","current","job","setJob","moving","status","undefined","controller","AbortController","id","signal","abort","replace","next","AiJobFollow","orgSlug","intro","onOpenJobs","staff","busy","setBusy","notice","setNotice","phase","line","settled","includes","primary","problem","text","stepsDone","steps","filter","step","length","decide","request","error","openJobs","jobId","spacing","aria-live","variant","color","severity","icon","size","aria-label","action","onClick","useFlexGap","direction","sx","alignItems","flexWrap","rowGap","label","mt","component","href","onResume","target","disabled"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type AiBuildPlanEmbed } from '../model/ai-build-plan';
17
+ import { aiPlanEmbedLine } from '../model/ai-job-plan-lines';
18
18
  import type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types';
19
19
  import type { AiJobResumeOptions } from './ai-job-requests';
20
20
  import { AiSiteStarterFallback } from './ai-site-starter-fallback.component';
@@ -56,12 +56,6 @@ export interface AiJobPlanProps {
56
56
  * Grid's layout as written, and the names of what it sets and holds.
57
57
  */
58
58
  export declare function aiJobReviewDetails(review: AiJobReview | null): string[];
59
- /**
60
- * A third-party player the plan lists, with what it costs the page, read
61
- * before the member confirms it (rule 16, AGL-3433). The cost is the
62
- * platform's sentence, never the model's: the player is framed lazily, so its
63
- * host's code loads when a visitor reaches it, whether or not they press play.
64
- */
65
- export declare function aiPlanEmbedLine(embed: AiBuildPlanEmbed, named: (ref: string) => string): string;
59
+ export { aiPlanEmbedLine };
66
60
  export declare function AiJobPlan({ job, onResume, busy, staff, user, orgSlug, }: AiJobPlanProps): JSX.Element | null;
67
61
  export default AiJobPlan;