@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +25 -0
- package/src/lib/activity/ai-activity-actions.js +85 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.js +46 -2
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.js +1 -1
- package/src/lib/components/ai-campaign-create.component.js.map +1 -1
- package/src/lib/components/ai-credits-card.component.js +3 -3
- package/src/lib/components/ai-credits-card.component.js.map +1 -1
- package/src/lib/components/ai-crm-email-draft.component.js +1 -1
- package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
- package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
- package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
- package/src/lib/components/ai-crm-record-card.component.js +1 -1
- package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.js +4 -2
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-org-automation.component.js +1 -1
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +2 -2
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -1
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-insight-card.component.js +3 -1
- package/src/lib/components/ai-insight-card.component.js.map +1 -1
- package/src/lib/components/ai-job-links.d.ts +6 -0
- package/src/lib/components/ai-job-links.js +9 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
- package/src/lib/components/ai-jobs-list-page.component.js +15 -2
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-logic.component.js +5 -3
- package/src/lib/components/ai-logic.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.js +88 -16
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-model-selector.component.js +8 -0
- package/src/lib/components/ai-model-selector.component.js.map +1 -1
- package/src/lib/components/ai-overlay-cards.component.js +2 -2
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.js +2 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.js +2 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.js +1 -1
- package/src/lib/components/ai-revise-automation.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +7 -3
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-batch-card.component.js +7 -4
- package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +48 -6
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
- package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +152 -36
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js +17 -3
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +13 -2
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.js +20 -3
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +7 -1
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +2 -2
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -2
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/host-ai-allotments.component.js +2 -2
- package/src/lib/components/host-ai-allotments.component.js.map +1 -1
- package/src/lib/components/member-ai-allotment-card.component.js +2 -2
- package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
- package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +2 -1
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
- package/src/lib/jobs/ai-job-email-step.js +116 -5
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
- package/src/lib/jobs/ai-job-layout-language.js +6 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +3 -2
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
- package/src/lib/jobs/ai-job-page-language.js +75 -13
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-page-step.js +32 -7
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
- package/src/lib/jobs/ai-job-plan-step.js +106 -14
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
- package/src/lib/jobs/ai-job-site-content.js +210 -43
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
- package/src/lib/jobs/ai-job-site-step.js +152 -51
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +8 -4
- package/src/lib/jobs/ai-jobs.js +19 -2
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +11 -0
- package/src/lib/jobs/ai-site-publish.js +31 -1
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
- package/src/lib/layout-language/ai-layout-design.js +209 -0
- package/src/lib/layout-language/ai-layout-design.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-frame.js +33 -8
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +18 -2
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-language.js +15 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
- package/src/lib/layout-language/ai-layout-links.js +26 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
- package/src/lib/layout-language/ai-layout-listings.js +187 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
- package/src/lib/layout-language/ai-layout-pictures.js +80 -17
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +9 -1
- package/src/lib/model/ai-job-notice.js +23 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +12 -2
- package/src/lib/model/ai-site-build-progress.js +104 -15
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +236 -24
- package/src/lib/model/ai-site-job.js +269 -22
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +30 -8
- package/src/lib/model/ai-site-kinds.js +735 -116
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-look.d.ts +21 -0
- package/src/lib/model/ai-site-look.js +63 -14
- package/src/lib/model/ai-site-look.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +30 -0
- package/src/lib/model/ai-site-start-seo.js +132 -11
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +2 -2
- package/src/lib/model/ai-site-start.js +3 -3
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/plugin.js +15 -3
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
- package/src/lib/runtime/ai-doctrine-validators.js +227 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
- package/src/lib/runtime/ai-palette.generated.js +31 -5
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-admin-conversations.d.ts +9 -0
- package/src/lib/server/ai-admin-conversations.js +151 -9
- package/src/lib/server/ai-admin-conversations.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +15 -3
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-edit.js +5 -2
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
- package/src/lib/usage/free-ai-credits-left.js +62 -0
- package/src/lib/usage/free-ai-credits-left.js.map +1 -0
- package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
- package/src/lib/usage/staff-org-ai-conversations.js +45 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-ai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.235",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-errors": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
41
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.235",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.235",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.235",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.235",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.235",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.235",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.235",
|
|
35
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.235",
|
|
36
|
+
"@aglyn/shared-util-email": "1.0.0-beta.235",
|
|
37
|
+
"@aglyn/shared-util-errors": "1.0.0-beta.235",
|
|
38
|
+
"@aglyn/shared-util-http": "1.0.0-beta.235",
|
|
39
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.235",
|
|
40
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.235",
|
|
41
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.235",
|
|
42
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.235",
|
|
43
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.235",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -48,6 +48,31 @@ export declare const AI_ACTIVITY_ACTIONS: {
|
|
|
48
48
|
export type AiActivityAction = (typeof AI_ACTIVITY_ACTIONS)[keyof typeof AI_ACTIVITY_ACTIONS];
|
|
49
49
|
/** What a person reads for each code, in the feed and the actor table. */
|
|
50
50
|
export declare const AI_ACTIVITY_ACTION_LABELS: Record<AiActivityAction, string>;
|
|
51
|
+
/**
|
|
52
|
+
* The codes whose row reads as a sentence about its target (AGL-3660):
|
|
53
|
+
* `{target}` is the target's noun and name, so an output row reads
|
|
54
|
+
* `Created page Home with Aglyn AI` rather than `AI generated` beside a
|
|
55
|
+
* Target cell that had to finish the thought.
|
|
56
|
+
*/
|
|
57
|
+
export declare const AI_ACTIVITY_ACTION_SENTENCES: Partial<Record<AiActivityAction, string>>;
|
|
58
|
+
/**
|
|
59
|
+
* What the staff audit trail reads for each code the AI doors write to
|
|
60
|
+
* `adminAudit` (AGL-3660). `ai.job.output` is spelled out per row by the
|
|
61
|
+
* shared describer, which names the thing made.
|
|
62
|
+
*/
|
|
63
|
+
export declare const AI_STAFF_AUDIT_LABELS: Readonly<Record<string, string>>;
|
|
64
|
+
/**
|
|
65
|
+
* An `ai.job.output` audit row in words (AGL-3660), from the `after` the
|
|
66
|
+
* job writer stores — `{ resource, label }`: `Created page Home with Aglyn
|
|
67
|
+
* AI`, and its outcome, a draft.
|
|
68
|
+
*/
|
|
69
|
+
export declare function aiStaffAuditDescribe(entry: {
|
|
70
|
+
action: string;
|
|
71
|
+
after: Readonly<Record<string, unknown>>;
|
|
72
|
+
}): {
|
|
73
|
+
action?: string;
|
|
74
|
+
result?: string;
|
|
75
|
+
} | undefined;
|
|
51
76
|
/** Every code, in catalog order — what a filter sends as `isAnyOf`. */
|
|
52
77
|
export declare const AI_ACTIVITY_ACTION_LIST: readonly AiActivityAction[];
|
|
53
78
|
/** The feed's filter chip and the staff facet's group, by one name. */
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
* One list, so the writers in `ai-activity.ts`, the feed presenter and the
|
|
27
27
|
* staff facet cannot disagree about what counts as AI. A code missing from
|
|
28
28
|
* `AI_ACTIVITY_ACTION_LABELS` fails the type, not the reader.
|
|
29
|
-
*/ import {
|
|
29
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
30
|
+
import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
31
|
+
import { pluginStaffAuditActionGroup, pluginStaffAuditActionGroupLabel, registerPluginActivityActions } from "@aglyn/aglyn/plugin-manager/plugin-activity-actions";
|
|
30
32
|
/** The action code each AI writer stores. */ export const AI_ACTIVITY_ACTIONS = {
|
|
31
33
|
jobCreated: 'ai.job.created',
|
|
32
34
|
jobOutput: 'ai.job.output',
|
|
@@ -57,6 +59,67 @@
|
|
|
57
59
|
'ai.addon.purchased': 'Added the AI add-on',
|
|
58
60
|
'ai.addon.removed': 'Removed the AI add-on'
|
|
59
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* The codes whose row reads as a sentence about its target (AGL-3660):
|
|
64
|
+
* `{target}` is the target's noun and name, so an output row reads
|
|
65
|
+
* `Created page Home with Aglyn AI` rather than `AI generated` beside a
|
|
66
|
+
* Target cell that had to finish the thought.
|
|
67
|
+
*/ export const AI_ACTIVITY_ACTION_SENTENCES = {
|
|
68
|
+
'ai.job.output': `Created {target} with ${PLATFORM_BRAND_NAME} AI`,
|
|
69
|
+
'ai.edit.applied': `Applied ${PLATFORM_BRAND_NAME} AI edits to {target}`,
|
|
70
|
+
'ai.seo.applied': `Applied ${PLATFORM_BRAND_NAME} AI search fixes to {target}`,
|
|
71
|
+
'ai.assist.section': `Added a section to {target} with ${PLATFORM_BRAND_NAME} AI`
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* What the staff audit trail reads for each code the AI doors write to
|
|
75
|
+
* `adminAudit` (AGL-3660). `ai.job.output` is spelled out per row by the
|
|
76
|
+
* shared describer, which names the thing made.
|
|
77
|
+
*/ export const AI_STAFF_AUDIT_LABELS = _extends({
|
|
78
|
+
'ai.job.output': `Created a draft with ${PLATFORM_BRAND_NAME} AI`,
|
|
79
|
+
'ai.job.cancel': `Canceled an ${PLATFORM_BRAND_NAME} AI job`,
|
|
80
|
+
'ai.job.resume': `Confirmed an ${PLATFORM_BRAND_NAME} AI plan`,
|
|
81
|
+
'ai.job.apply': `Applied an ${PLATFORM_BRAND_NAME} AI result`,
|
|
82
|
+
'ai.generate': `Generated with ${PLATFORM_BRAND_NAME} AI`,
|
|
83
|
+
'ai.allotments.set': 'Set AI allotments',
|
|
84
|
+
'ai.credits.giveBack': 'Gave AI credits back',
|
|
85
|
+
'ai.credits.reset': 'Reset AI credits',
|
|
86
|
+
'ai.overage.clearCeiling': 'Cleared the AI overage ceiling',
|
|
87
|
+
'ai.overage.liftPause': 'Lifted the AI pause',
|
|
88
|
+
'ai.overage.resetStep': 'Reset the AI overage step',
|
|
89
|
+
'ai.overage.setCeiling': 'Set the AI overage ceiling',
|
|
90
|
+
'platform.aiFreeSpend.paused': 'Paused free AI for the day',
|
|
91
|
+
'org.ai-viewed': 'Viewed the organization’s AI usage',
|
|
92
|
+
'user.ai-usage-viewed': 'Viewed the account’s AI usage',
|
|
93
|
+
'org.ai-conversations-viewed': `Viewed the organization’s ${PLATFORM_BRAND_NAME} AI requests`,
|
|
94
|
+
'user.ai-requests-viewed': `Viewed the account’s ${PLATFORM_BRAND_NAME} AI requests`
|
|
95
|
+
}, Object.fromEntries(Object.entries(AI_ACTIVITY_ACTION_LABELS).filter(([key])=>key !== 'ai.job.output')));
|
|
96
|
+
/** A job output's `resource`, as the noun the audit sentence uses. */ const AI_OUTPUT_NOUNS = {
|
|
97
|
+
screen: 'page',
|
|
98
|
+
reusableComponent: 'component',
|
|
99
|
+
emailScreen: 'email',
|
|
100
|
+
orgAutomation: 'automation',
|
|
101
|
+
workflow: 'automation',
|
|
102
|
+
entry: 'post',
|
|
103
|
+
text: 'copy',
|
|
104
|
+
seo: 'search fixes',
|
|
105
|
+
crm: 'CRM answer',
|
|
106
|
+
insight: 'insight'
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* An `ai.job.output` audit row in words (AGL-3660), from the `after` the
|
|
110
|
+
* job writer stores — `{ resource, label }`: `Created page Home with Aglyn
|
|
111
|
+
* AI`, and its outcome, a draft.
|
|
112
|
+
*/ export function aiStaffAuditDescribe(entry) {
|
|
113
|
+
var _AI_OUTPUT_NOUNS_resource;
|
|
114
|
+
if (entry.action !== AI_ACTIVITY_ACTIONS.jobOutput) return undefined;
|
|
115
|
+
const resource = typeof entry.after['resource'] === 'string' ? entry.after['resource'] : '';
|
|
116
|
+
const label = typeof entry.after['label'] === 'string' ? entry.after['label'].trim() : '';
|
|
117
|
+
const noun = (_AI_OUTPUT_NOUNS_resource = AI_OUTPUT_NOUNS[resource]) != null ? _AI_OUTPUT_NOUNS_resource : resource ? resource.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase() : 'draft';
|
|
118
|
+
return {
|
|
119
|
+
action: `Created ${noun}${label ? ` ${label}` : ''} with ${PLATFORM_BRAND_NAME} AI`,
|
|
120
|
+
result: 'Draft created'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
60
123
|
/** Every code, in catalog order — what a filter sends as `isAnyOf`. */ export const AI_ACTIVITY_ACTION_LIST = Object.values(AI_ACTIVITY_ACTIONS);
|
|
61
124
|
/** The feed's filter chip and the staff facet's group, by one name. */ export const AI_ACTIVITY_FILTER_LABEL = 'AI';
|
|
62
125
|
export const AI_JOB_NEEDS_INPUT_REASON_LABELS = {
|
|
@@ -191,15 +254,33 @@ export function aiOutputTargetType(resource) {
|
|
|
191
254
|
// (AGL-2928), which no longer write these, so the rows already in the
|
|
192
255
|
// log still classify as reads; and a staff member reading what an org
|
|
193
256
|
// asked Aglyn AI (AGL-3675), which writes one per page.
|
|
257
|
+
// An account's AI requests on the staff user page (AGL-3660) likewise.
|
|
194
258
|
staffAuditAccessActions: [
|
|
195
259
|
'org.ai-viewed',
|
|
196
260
|
'user.ai-usage-viewed',
|
|
197
|
-
'org.ai-conversations-viewed'
|
|
261
|
+
'org.ai-conversations-viewed',
|
|
262
|
+
'user.ai-requests-viewed'
|
|
263
|
+
],
|
|
264
|
+
staffAuditLabels: AI_STAFF_AUDIT_LABELS,
|
|
265
|
+
// The audit trail's words for a job's output, its job collection and
|
|
266
|
+
// the feed's job rows, read by core's shared describer (AGL-3660).
|
|
267
|
+
staffAuditDescribe: aiStaffAuditDescribe,
|
|
268
|
+
staffAuditCollections: {
|
|
269
|
+
aiJobs: {
|
|
270
|
+
noun: `${PLATFORM_BRAND_NAME} AI job`,
|
|
271
|
+
job: true
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
jobTargetTypes: [
|
|
275
|
+
'aiJob'
|
|
198
276
|
]
|
|
199
277
|
},
|
|
200
|
-
actions: AI_ACTIVITY_ACTION_LIST.map((key)=>({
|
|
278
|
+
actions: AI_ACTIVITY_ACTION_LIST.map((key)=>_extends({
|
|
201
279
|
key,
|
|
202
|
-
label: AI_ACTIVITY_ACTION_LABELS[key]
|
|
280
|
+
label: AI_ACTIVITY_ACTION_LABELS[key]
|
|
281
|
+
}, AI_ACTIVITY_ACTION_SENTENCES[key] ? {
|
|
282
|
+
sentence: AI_ACTIVITY_ACTION_SENTENCES[key]
|
|
283
|
+
} : {}, {
|
|
203
284
|
scope: AI_ACTIVITY_ACTION_SCOPES[key]
|
|
204
285
|
})),
|
|
205
286
|
// A generation job (AGL-2904): the org feed names the job, and each of
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/activity/ai-activity-actions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The AI actions an activity log can carry (AGL-2929).\n *\n * Every other action in `orgs/{orgId}/activity` and `hosts/{hostId}/activity`\n * is a prose sentence written at the call site — \"Saved the screen\". The AI\n * rows are CODES instead, because they are read by more than a person: the\n * org feed's \"AI\" chip, the staff audit facet and the actor table all need\n * to recognize an AI row without parsing a sentence, and a sentence that\n * three writers phrase three ways is three rows that look unrelated.\n *\n * One list, so the writers in `ai-activity.ts`, the feed presenter and the\n * staff facet cannot disagree about what counts as AI. A code missing from\n * `AI_ACTIVITY_ACTION_LABELS` fails the type, not the reader.\n */\n\nimport type { AiJobOutputResource } from '../model/ai-jobs.types'\nimport {\n pluginStaffAuditActionGroup,\n pluginStaffAuditActionGroupLabel,\n registerPluginActivityActions,\n type PluginActivityScope,\n} from '@aglyn/aglyn/plugin-manager/plugin-activity-actions'\n\n/** The action code each AI writer stores. */\nexport const AI_ACTIVITY_ACTIONS = {\n jobCreated: 'ai.job.created',\n jobOutput: 'ai.job.output',\n jobCanceled: 'ai.job.canceled',\n jobNeedsInput: 'ai.job.needs_input',\n editApplied: 'ai.edit.applied',\n seoApplied: 'ai.seo.applied',\n assistSection: 'ai.assist.section',\n overageHardCap: 'ai.overage.hardCap',\n overageCap: 'ai.overage.cap',\n allotmentChanged: 'ai.allotment.changed',\n permissionChanged: 'ai.permission.changed',\n addonPurchased: 'ai.addon.purchased',\n addonRemoved: 'ai.addon.removed',\n} as const\n\nexport type AiActivityAction =\n (typeof AI_ACTIVITY_ACTIONS)[keyof typeof AI_ACTIVITY_ACTIONS]\n\n/** What a person reads for each code, in the feed and the actor table. */\nexport const AI_ACTIVITY_ACTION_LABELS: Record<AiActivityAction, string> = {\n 'ai.job.created': 'Started an AI generation',\n 'ai.job.output': 'AI generated',\n 'ai.job.canceled': 'Canceled an AI generation',\n 'ai.job.needs_input': 'AI generation paused for input',\n 'ai.edit.applied': 'Applied AI edits',\n 'ai.seo.applied': 'Applied AI SEO fixes as drafts',\n 'ai.assist.section': 'AI generated a section',\n 'ai.overage.hardCap': 'AI stop-at-band switch',\n 'ai.overage.cap': 'AI overage ceiling',\n 'ai.allotment.changed': 'AI allotment changed',\n 'ai.permission.changed': 'AI permission changed',\n 'ai.addon.purchased': 'Added the AI add-on',\n 'ai.addon.removed': 'Removed the AI add-on',\n}\n\n/** Every code, in catalog order — what a filter sends as `isAnyOf`. */\nexport const AI_ACTIVITY_ACTION_LIST: readonly AiActivityAction[] =\n Object.values(AI_ACTIVITY_ACTIONS)\n\n/** The feed's filter chip and the staff facet's group, by one name. */\nexport const AI_ACTIVITY_FILTER_LABEL = 'AI'\n\n/**\n * Why a generation stopped and asked for a person (AGL-2904): the plan's\n * band ran out, the org's own ceiling refused, the monthly message cap or\n * the job's own token budget did — or, on the Free taste (AGL-2925), one of\n * its own precautions: the account's credits, its daily request cap, a\n * pause after declined briefs, or the platform's day of free spend. Those\n * are the meter's `AssistRefusedBy` less its `null`, so a ceiling the meter\n * can name is one the feed can label. The last three are the job's own: its\n * plan is ready for review, or an answer broke a building rule on its re-ask\n * (AGL-2935), or the site is at an allowance its plan sets for what the job\n * writes (AGL-2909).\n */\nexport type AiJobNeedsInputReason =\n | 'band'\n | 'cap'\n | 'messages'\n | 'budget'\n | 'allotment'\n | 'account'\n | 'requests'\n | 'refusals'\n | 'platform'\n | 'plan'\n | 'doctrine'\n | 'limit'\n\nexport const AI_JOB_NEEDS_INPUT_REASON_LABELS: Record<\n AiJobNeedsInputReason,\n string\n> = {\n plan: 'its plan is ready for review',\n doctrine: 'its answer broke a building rule twice',\n limit: 'the site reached an allowance of its plan',\n band: 'the included AI band is used up',\n cap: 'the overage ceiling was reached',\n messages: 'the monthly message cap was reached',\n budget: 'the job budget was reached',\n allotment: 'the creator’s or the site’s AI allotment is used up',\n account: 'the account’s free AI credits are used up',\n requests: 'the daily free request cap was reached',\n refusals: 'free generation is paused for the day after declined briefs',\n platform: 'free AI generation is paused for the day',\n}\n\n/**\n * The feed target types a generation job's output can be filed under: the\n * kinds both activity logs know, so the org row and its host copy share a\n * shape.\n */\nexport type AiOutputTargetType =\n | 'screen'\n | 'layout'\n | 'component'\n | 'template'\n | 'workflow'\n | 'content'\n | 'theme'\n\n/**\n * Where the feed files each resource kind a job can write (AGL-2904).\n *\n * A job names its output by the resource it wrote, and the activity logs\n * know a narrower set of targets. The map is total, so a resource kind\n * added to the job document must say where the feed shows it before the\n * machine can log it. `reusableComponent` is the feed's `component`; a\n * `text` output is copy, which the feed files as `content`; a `theme`\n * proposal is filed under the site's theme, which the host feed links to the\n * Theme section (AGL-2938); the kinds whose runners have not shipped are\n * content of the site too, and are filed there until a target of their own\n * exists.\n */\nconst AI_OUTPUT_TARGET_TYPES: Record<AiJobOutputResource, AiOutputTargetType> = {\n screen: 'screen',\n reusableComponent: 'component',\n layout: 'layout',\n template: 'template',\n form: 'content',\n emailScreen: 'content',\n campaign: 'content',\n product: 'content',\n experiment: 'content',\n workflow: 'workflow',\n // An SEO proposal (AGL-2910) is about the site's content, and is filed there.\n seo: 'content',\n text: 'content',\n theme: 'theme',\n // An insight answer (AGL-2915) is about the site's figures; the feed row\n // names it and never what it says.\n insight: 'content',\n // A CRM proposal (AGL-2917) writes nothing; the feed files it with content.\n crm: 'content',\n // Another plugin's draft a build asked its writer for (AGL-3616).\n draft: 'content',\n // A logic proposal (AGL-3603) writes nothing either.\n logic: 'content',\n // So does an org automation's (AGL-3603): the editor's Save is the write.\n orgAutomation: 'content',\n // A blog post a guided start wrote (AGL-3676) is the site's content.\n entry: 'content',\n}\n\nexport function aiOutputTargetType(resource: AiJobOutputResource): AiOutputTargetType {\n return AI_OUTPUT_TARGET_TYPES[resource]\n}\n\n/** Whether a stored action is one of the AI codes above. */\nexport function isAiActivityAction(action: unknown): action is AiActivityAction {\n return (\n typeof action === 'string' &&\n (AI_ACTIVITY_ACTION_LIST as readonly string[]).includes(action)\n )\n}\n\n/** The readable label for an AI code; `undefined` for any other action. */\nexport function aiActivityActionLabel(action: unknown): string | undefined {\n return isAiActivityAction(action)\n ? AI_ACTIVITY_ACTION_LABELS[action]\n : undefined\n}\n\n/**\n * Staff audit rows that are ABOUT AI without carrying an `ai.` code: the\n * customer's overage controls already write `billing.assistOverage.*`, and\n * the platform's free-spend auto-pause writes `platform.aiFreeSpend.*`\n * (AGL-2925). The audit page's facet folds them under the same group as the\n * codes, because \"what did we do about AI this week\" is one question.\n */\nexport const AI_STAFF_AUDIT_ACTION_PREFIXES: readonly string[] = [\n 'ai.',\n 'billing.assistOverage.',\n 'platform.aiFreeSpend.',\n]\n\n/** The facet group an `adminAudit` action belongs to. */\nexport const STAFF_AUDIT_AI_GROUP = 'ai'\n\n/**\n * Which log each code is written to. The job and edit codes land in both\n * the org feed and the host's copy; the billing and permission codes are\n * org-level facts and land in the org feed alone.\n */\nconst AI_ACTIVITY_ACTION_SCOPES: Record<\n AiActivityAction,\n PluginActivityScope | readonly PluginActivityScope[]\n> = {\n 'ai.job.created': ['org', 'host'],\n 'ai.job.output': ['org', 'host'],\n 'ai.job.canceled': ['org', 'host'],\n 'ai.job.needs_input': ['org', 'host'],\n 'ai.edit.applied': ['org', 'host'],\n 'ai.seo.applied': ['org', 'host'],\n 'ai.assist.section': ['org', 'host'],\n 'ai.overage.hardCap': 'org',\n 'ai.overage.cap': 'org',\n 'ai.allotment.changed': 'org',\n 'ai.permission.changed': 'org',\n 'ai.addon.purchased': 'org',\n 'ai.addon.removed': 'org',\n}\n\n/**\n * The catalog, declared through the generic activity-action seam\n * (AGL-2940) so the feed's chip, the actor table's filter and the staff\n * facet read it from the registry beside every other plugin's codes.\n * `registerAiDeclarations` calls it, in both apps and in the browser.\n */\nexport function registerAiActivityActions(): void {\n registerPluginActivityActions({\n pluginId: 'ai',\n group: {\n id: STAFF_AUDIT_AI_GROUP,\n label: AI_ACTIVITY_FILTER_LABEL,\n staffAuditPrefixes: AI_STAFF_AUDIT_ACTION_PREFIXES.filter(\n (prefix) => prefix !== 'ai.',\n ),\n // The staff cards opening on an org (AGL-2930) and on one account\n // (AGL-2928), which no longer write these, so the rows already in the\n // log still classify as reads; and a staff member reading what an org\n // asked Aglyn AI (AGL-3675), which writes one per page.\n staffAuditAccessActions: ['org.ai-viewed', 'user.ai-usage-viewed', 'org.ai-conversations-viewed'],\n },\n actions: AI_ACTIVITY_ACTION_LIST.map((key) => ({\n key,\n label: AI_ACTIVITY_ACTION_LABELS[key],\n scope: AI_ACTIVITY_ACTION_SCOPES[key],\n })),\n // A generation job (AGL-2904): the org feed names the job, and each of\n // its outputs names the resource it produced. Filed as `aiJob` since\n // before plugin target types were namespaced, so the noun is declared\n // here rather than read off the type (AGL-3080).\n targetTypes: { aiJob: 'AI generation' },\n })\n}\n\n/**\n * The group the staff audit facet files an action under: `ai` for every\n * AI row, otherwise the action's leading namespace (`billing`, `org`,\n * `plugins`). Roughly seventy distinct actions write to `adminAudit`, so\n * the facet offers namespaces rather than a seventy-entry menu. Answered\n * by the registry, so every plugin's group is filed the same way.\n */\nexport function staffAuditActionGroup(action: unknown): string {\n return pluginStaffAuditActionGroup(action)\n}\n\n/** How a facet group reads in the menu: `ai` → `AI`, `billing` → `billing`. */\nexport function staffAuditActionGroupLabel(group: string): string {\n return pluginStaffAuditActionGroupLabel(group)\n}\n"],"names":["pluginStaffAuditActionGroup","pluginStaffAuditActionGroupLabel","registerPluginActivityActions","AI_ACTIVITY_ACTIONS","jobCreated","jobOutput","jobCanceled","jobNeedsInput","editApplied","seoApplied","assistSection","overageHardCap","overageCap","allotmentChanged","permissionChanged","addonPurchased","addonRemoved","AI_ACTIVITY_ACTION_LABELS","AI_ACTIVITY_ACTION_LIST","Object","values","AI_ACTIVITY_FILTER_LABEL","AI_JOB_NEEDS_INPUT_REASON_LABELS","plan","doctrine","limit","band","cap","messages","budget","allotment","account","requests","refusals","platform","AI_OUTPUT_TARGET_TYPES","screen","reusableComponent","layout","template","form","emailScreen","campaign","product","experiment","workflow","seo","text","theme","insight","crm","draft","logic","orgAutomation","entry","aiOutputTargetType","resource","isAiActivityAction","action","includes","aiActivityActionLabel","undefined","AI_STAFF_AUDIT_ACTION_PREFIXES","STAFF_AUDIT_AI_GROUP","AI_ACTIVITY_ACTION_SCOPES","registerAiActivityActions","pluginId","group","id","label","staffAuditPrefixes","filter","prefix","staffAuditAccessActions","actions","map","key","scope","targetTypes","aiJob","staffAuditActionGroup","staffAuditActionGroupLabel"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/activity/ai-activity-actions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The AI actions an activity log can carry (AGL-2929).\n *\n * Every other action in `orgs/{orgId}/activity` and `hosts/{hostId}/activity`\n * is a prose sentence written at the call site — \"Saved the screen\". The AI\n * rows are CODES instead, because they are read by more than a person: the\n * org feed's \"AI\" chip, the staff audit facet and the actor table all need\n * to recognize an AI row without parsing a sentence, and a sentence that\n * three writers phrase three ways is three rows that look unrelated.\n *\n * One list, so the writers in `ai-activity.ts`, the feed presenter and the\n * staff facet cannot disagree about what counts as AI. A code missing from\n * `AI_ACTIVITY_ACTION_LABELS` fails the type, not the reader.\n */\n\nimport type { AiJobOutputResource } from '../model/ai-jobs.types'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport {\n pluginStaffAuditActionGroup,\n pluginStaffAuditActionGroupLabel,\n registerPluginActivityActions,\n type PluginActivityScope,\n} from '@aglyn/aglyn/plugin-manager/plugin-activity-actions'\n\n/** The action code each AI writer stores. */\nexport const AI_ACTIVITY_ACTIONS = {\n jobCreated: 'ai.job.created',\n jobOutput: 'ai.job.output',\n jobCanceled: 'ai.job.canceled',\n jobNeedsInput: 'ai.job.needs_input',\n editApplied: 'ai.edit.applied',\n seoApplied: 'ai.seo.applied',\n assistSection: 'ai.assist.section',\n overageHardCap: 'ai.overage.hardCap',\n overageCap: 'ai.overage.cap',\n allotmentChanged: 'ai.allotment.changed',\n permissionChanged: 'ai.permission.changed',\n addonPurchased: 'ai.addon.purchased',\n addonRemoved: 'ai.addon.removed',\n} as const\n\nexport type AiActivityAction =\n (typeof AI_ACTIVITY_ACTIONS)[keyof typeof AI_ACTIVITY_ACTIONS]\n\n/** What a person reads for each code, in the feed and the actor table. */\nexport const AI_ACTIVITY_ACTION_LABELS: Record<AiActivityAction, string> = {\n 'ai.job.created': 'Started an AI generation',\n 'ai.job.output': 'AI generated',\n 'ai.job.canceled': 'Canceled an AI generation',\n 'ai.job.needs_input': 'AI generation paused for input',\n 'ai.edit.applied': 'Applied AI edits',\n 'ai.seo.applied': 'Applied AI SEO fixes as drafts',\n 'ai.assist.section': 'AI generated a section',\n 'ai.overage.hardCap': 'AI stop-at-band switch',\n 'ai.overage.cap': 'AI overage ceiling',\n 'ai.allotment.changed': 'AI allotment changed',\n 'ai.permission.changed': 'AI permission changed',\n 'ai.addon.purchased': 'Added the AI add-on',\n 'ai.addon.removed': 'Removed the AI add-on',\n}\n\n/**\n * The codes whose row reads as a sentence about its target (AGL-3660):\n * `{target}` is the target's noun and name, so an output row reads\n * `Created page Home with Aglyn AI` rather than `AI generated` beside a\n * Target cell that had to finish the thought.\n */\nexport const AI_ACTIVITY_ACTION_SENTENCES: Partial<Record<AiActivityAction, string>> = {\n 'ai.job.output': `Created {target} with ${PLATFORM_BRAND_NAME} AI`,\n 'ai.edit.applied': `Applied ${PLATFORM_BRAND_NAME} AI edits to {target}`,\n 'ai.seo.applied': `Applied ${PLATFORM_BRAND_NAME} AI search fixes to {target}`,\n 'ai.assist.section': `Added a section to {target} with ${PLATFORM_BRAND_NAME} AI`,\n}\n\n/**\n * What the staff audit trail reads for each code the AI doors write to\n * `adminAudit` (AGL-3660). `ai.job.output` is spelled out per row by the\n * shared describer, which names the thing made.\n */\nexport const AI_STAFF_AUDIT_LABELS: Readonly<Record<string, string>> = {\n 'ai.job.output': `Created a draft with ${PLATFORM_BRAND_NAME} AI`,\n 'ai.job.cancel': `Canceled an ${PLATFORM_BRAND_NAME} AI job`,\n 'ai.job.resume': `Confirmed an ${PLATFORM_BRAND_NAME} AI plan`,\n 'ai.job.apply': `Applied an ${PLATFORM_BRAND_NAME} AI result`,\n 'ai.generate': `Generated with ${PLATFORM_BRAND_NAME} AI`,\n 'ai.allotments.set': 'Set AI allotments',\n 'ai.credits.giveBack': 'Gave AI credits back',\n 'ai.credits.reset': 'Reset AI credits',\n 'ai.overage.clearCeiling': 'Cleared the AI overage ceiling',\n 'ai.overage.liftPause': 'Lifted the AI pause',\n 'ai.overage.resetStep': 'Reset the AI overage step',\n 'ai.overage.setCeiling': 'Set the AI overage ceiling',\n 'platform.aiFreeSpend.paused': 'Paused free AI for the day',\n 'org.ai-viewed': 'Viewed the organization’s AI usage',\n 'user.ai-usage-viewed': 'Viewed the account’s AI usage',\n 'org.ai-conversations-viewed': `Viewed the organization’s ${PLATFORM_BRAND_NAME} AI requests`,\n 'user.ai-requests-viewed': `Viewed the account’s ${PLATFORM_BRAND_NAME} AI requests`,\n ...Object.fromEntries(\n Object.entries(AI_ACTIVITY_ACTION_LABELS).filter(([key]) => key !== 'ai.job.output'),\n ),\n}\n\n/** A job output's `resource`, as the noun the audit sentence uses. */\nconst AI_OUTPUT_NOUNS: Readonly<Record<string, string>> = {\n screen: 'page',\n reusableComponent: 'component',\n emailScreen: 'email',\n orgAutomation: 'automation',\n workflow: 'automation',\n entry: 'post',\n text: 'copy',\n seo: 'search fixes',\n crm: 'CRM answer',\n insight: 'insight',\n}\n\n/**\n * An `ai.job.output` audit row in words (AGL-3660), from the `after` the\n * job writer stores — `{ resource, label }`: `Created page Home with Aglyn\n * AI`, and its outcome, a draft.\n */\nexport function aiStaffAuditDescribe(entry: {\n action: string\n after: Readonly<Record<string, unknown>>\n}): { action?: string; result?: string } | undefined {\n if (entry.action !== AI_ACTIVITY_ACTIONS.jobOutput) return undefined\n const resource = typeof entry.after['resource'] === 'string' ? entry.after['resource'] : ''\n const label = typeof entry.after['label'] === 'string' ? entry.after['label'].trim() : ''\n const noun =\n AI_OUTPUT_NOUNS[resource] ??\n (resource ? resource.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase() : 'draft')\n return {\n action: `Created ${noun}${label ? ` ${label}` : ''} with ${PLATFORM_BRAND_NAME} AI`,\n result: 'Draft created',\n }\n}\n\n/** Every code, in catalog order — what a filter sends as `isAnyOf`. */\nexport const AI_ACTIVITY_ACTION_LIST: readonly AiActivityAction[] =\n Object.values(AI_ACTIVITY_ACTIONS)\n\n/** The feed's filter chip and the staff facet's group, by one name. */\nexport const AI_ACTIVITY_FILTER_LABEL = 'AI'\n\n/**\n * Why a generation stopped and asked for a person (AGL-2904): the plan's\n * band ran out, the org's own ceiling refused, the monthly message cap or\n * the job's own token budget did — or, on the Free taste (AGL-2925), one of\n * its own precautions: the account's credits, its daily request cap, a\n * pause after declined briefs, or the platform's day of free spend. Those\n * are the meter's `AssistRefusedBy` less its `null`, so a ceiling the meter\n * can name is one the feed can label. The last three are the job's own: its\n * plan is ready for review, or an answer broke a building rule on its re-ask\n * (AGL-2935), or the site is at an allowance its plan sets for what the job\n * writes (AGL-2909).\n */\nexport type AiJobNeedsInputReason =\n | 'band'\n | 'cap'\n | 'messages'\n | 'budget'\n | 'allotment'\n | 'account'\n | 'requests'\n | 'refusals'\n | 'platform'\n | 'plan'\n | 'doctrine'\n | 'limit'\n\nexport const AI_JOB_NEEDS_INPUT_REASON_LABELS: Record<\n AiJobNeedsInputReason,\n string\n> = {\n plan: 'its plan is ready for review',\n doctrine: 'its answer broke a building rule twice',\n limit: 'the site reached an allowance of its plan',\n band: 'the included AI band is used up',\n cap: 'the overage ceiling was reached',\n messages: 'the monthly message cap was reached',\n budget: 'the job budget was reached',\n allotment: 'the creator’s or the site’s AI allotment is used up',\n account: 'the account’s free AI credits are used up',\n requests: 'the daily free request cap was reached',\n refusals: 'free generation is paused for the day after declined briefs',\n platform: 'free AI generation is paused for the day',\n}\n\n/**\n * The feed target types a generation job's output can be filed under: the\n * kinds both activity logs know, so the org row and its host copy share a\n * shape.\n */\nexport type AiOutputTargetType =\n | 'screen'\n | 'layout'\n | 'component'\n | 'template'\n | 'workflow'\n | 'content'\n | 'theme'\n\n/**\n * Where the feed files each resource kind a job can write (AGL-2904).\n *\n * A job names its output by the resource it wrote, and the activity logs\n * know a narrower set of targets. The map is total, so a resource kind\n * added to the job document must say where the feed shows it before the\n * machine can log it. `reusableComponent` is the feed's `component`; a\n * `text` output is copy, which the feed files as `content`; a `theme`\n * proposal is filed under the site's theme, which the host feed links to the\n * Theme section (AGL-2938); the kinds whose runners have not shipped are\n * content of the site too, and are filed there until a target of their own\n * exists.\n */\nconst AI_OUTPUT_TARGET_TYPES: Record<AiJobOutputResource, AiOutputTargetType> = {\n screen: 'screen',\n reusableComponent: 'component',\n layout: 'layout',\n template: 'template',\n form: 'content',\n emailScreen: 'content',\n campaign: 'content',\n product: 'content',\n experiment: 'content',\n workflow: 'workflow',\n // An SEO proposal (AGL-2910) is about the site's content, and is filed there.\n seo: 'content',\n text: 'content',\n theme: 'theme',\n // An insight answer (AGL-2915) is about the site's figures; the feed row\n // names it and never what it says.\n insight: 'content',\n // A CRM proposal (AGL-2917) writes nothing; the feed files it with content.\n crm: 'content',\n // Another plugin's draft a build asked its writer for (AGL-3616).\n draft: 'content',\n // A logic proposal (AGL-3603) writes nothing either.\n logic: 'content',\n // So does an org automation's (AGL-3603): the editor's Save is the write.\n orgAutomation: 'content',\n // A blog post a guided start wrote (AGL-3676) is the site's content.\n entry: 'content',\n}\n\nexport function aiOutputTargetType(resource: AiJobOutputResource): AiOutputTargetType {\n return AI_OUTPUT_TARGET_TYPES[resource]\n}\n\n/** Whether a stored action is one of the AI codes above. */\nexport function isAiActivityAction(action: unknown): action is AiActivityAction {\n return (\n typeof action === 'string' &&\n (AI_ACTIVITY_ACTION_LIST as readonly string[]).includes(action)\n )\n}\n\n/** The readable label for an AI code; `undefined` for any other action. */\nexport function aiActivityActionLabel(action: unknown): string | undefined {\n return isAiActivityAction(action)\n ? AI_ACTIVITY_ACTION_LABELS[action]\n : undefined\n}\n\n/**\n * Staff audit rows that are ABOUT AI without carrying an `ai.` code: the\n * customer's overage controls already write `billing.assistOverage.*`, and\n * the platform's free-spend auto-pause writes `platform.aiFreeSpend.*`\n * (AGL-2925). The audit page's facet folds them under the same group as the\n * codes, because \"what did we do about AI this week\" is one question.\n */\nexport const AI_STAFF_AUDIT_ACTION_PREFIXES: readonly string[] = [\n 'ai.',\n 'billing.assistOverage.',\n 'platform.aiFreeSpend.',\n]\n\n/** The facet group an `adminAudit` action belongs to. */\nexport const STAFF_AUDIT_AI_GROUP = 'ai'\n\n/**\n * Which log each code is written to. The job and edit codes land in both\n * the org feed and the host's copy; the billing and permission codes are\n * org-level facts and land in the org feed alone.\n */\nconst AI_ACTIVITY_ACTION_SCOPES: Record<\n AiActivityAction,\n PluginActivityScope | readonly PluginActivityScope[]\n> = {\n 'ai.job.created': ['org', 'host'],\n 'ai.job.output': ['org', 'host'],\n 'ai.job.canceled': ['org', 'host'],\n 'ai.job.needs_input': ['org', 'host'],\n 'ai.edit.applied': ['org', 'host'],\n 'ai.seo.applied': ['org', 'host'],\n 'ai.assist.section': ['org', 'host'],\n 'ai.overage.hardCap': 'org',\n 'ai.overage.cap': 'org',\n 'ai.allotment.changed': 'org',\n 'ai.permission.changed': 'org',\n 'ai.addon.purchased': 'org',\n 'ai.addon.removed': 'org',\n}\n\n/**\n * The catalog, declared through the generic activity-action seam\n * (AGL-2940) so the feed's chip, the actor table's filter and the staff\n * facet read it from the registry beside every other plugin's codes.\n * `registerAiDeclarations` calls it, in both apps and in the browser.\n */\nexport function registerAiActivityActions(): void {\n registerPluginActivityActions({\n pluginId: 'ai',\n group: {\n id: STAFF_AUDIT_AI_GROUP,\n label: AI_ACTIVITY_FILTER_LABEL,\n staffAuditPrefixes: AI_STAFF_AUDIT_ACTION_PREFIXES.filter(\n (prefix) => prefix !== 'ai.',\n ),\n // The staff cards opening on an org (AGL-2930) and on one account\n // (AGL-2928), which no longer write these, so the rows already in the\n // log still classify as reads; and a staff member reading what an org\n // asked Aglyn AI (AGL-3675), which writes one per page.\n // An account's AI requests on the staff user page (AGL-3660) likewise.\n staffAuditAccessActions: [\n 'org.ai-viewed',\n 'user.ai-usage-viewed',\n 'org.ai-conversations-viewed',\n 'user.ai-requests-viewed',\n ],\n staffAuditLabels: AI_STAFF_AUDIT_LABELS,\n // The audit trail's words for a job's output, its job collection and\n // the feed's job rows, read by core's shared describer (AGL-3660).\n staffAuditDescribe: aiStaffAuditDescribe,\n staffAuditCollections: { aiJobs: { noun: `${PLATFORM_BRAND_NAME} AI job`, job: true } },\n jobTargetTypes: ['aiJob'],\n },\n actions: AI_ACTIVITY_ACTION_LIST.map((key) => ({\n key,\n label: AI_ACTIVITY_ACTION_LABELS[key],\n ...(AI_ACTIVITY_ACTION_SENTENCES[key] ? { sentence: AI_ACTIVITY_ACTION_SENTENCES[key] } : {}),\n scope: AI_ACTIVITY_ACTION_SCOPES[key],\n })),\n // A generation job (AGL-2904): the org feed names the job, and each of\n // its outputs names the resource it produced. Filed as `aiJob` since\n // before plugin target types were namespaced, so the noun is declared\n // here rather than read off the type (AGL-3080).\n targetTypes: { aiJob: 'AI generation' },\n })\n}\n\n/**\n * The group the staff audit facet files an action under: `ai` for every\n * AI row, otherwise the action's leading namespace (`billing`, `org`,\n * `plugins`). Roughly seventy distinct actions write to `adminAudit`, so\n * the facet offers namespaces rather than a seventy-entry menu. Answered\n * by the registry, so every plugin's group is filed the same way.\n */\nexport function staffAuditActionGroup(action: unknown): string {\n return pluginStaffAuditActionGroup(action)\n}\n\n/** How a facet group reads in the menu: `ai` → `AI`, `billing` → `billing`. */\nexport function staffAuditActionGroupLabel(group: string): string {\n return pluginStaffAuditActionGroupLabel(group)\n}\n"],"names":["PLATFORM_BRAND_NAME","pluginStaffAuditActionGroup","pluginStaffAuditActionGroupLabel","registerPluginActivityActions","AI_ACTIVITY_ACTIONS","jobCreated","jobOutput","jobCanceled","jobNeedsInput","editApplied","seoApplied","assistSection","overageHardCap","overageCap","allotmentChanged","permissionChanged","addonPurchased","addonRemoved","AI_ACTIVITY_ACTION_LABELS","AI_ACTIVITY_ACTION_SENTENCES","AI_STAFF_AUDIT_LABELS","Object","fromEntries","entries","filter","key","AI_OUTPUT_NOUNS","screen","reusableComponent","emailScreen","orgAutomation","workflow","entry","text","seo","crm","insight","aiStaffAuditDescribe","action","undefined","resource","after","label","trim","noun","replace","toLowerCase","result","AI_ACTIVITY_ACTION_LIST","values","AI_ACTIVITY_FILTER_LABEL","AI_JOB_NEEDS_INPUT_REASON_LABELS","plan","doctrine","limit","band","cap","messages","budget","allotment","account","requests","refusals","platform","AI_OUTPUT_TARGET_TYPES","layout","template","form","campaign","product","experiment","theme","draft","logic","aiOutputTargetType","isAiActivityAction","includes","aiActivityActionLabel","AI_STAFF_AUDIT_ACTION_PREFIXES","STAFF_AUDIT_AI_GROUP","AI_ACTIVITY_ACTION_SCOPES","registerAiActivityActions","pluginId","group","id","staffAuditPrefixes","prefix","staffAuditAccessActions","staffAuditLabels","staffAuditDescribe","staffAuditCollections","aiJobs","job","jobTargetTypes","actions","map","sentence","scope","targetTypes","aiJob","staffAuditActionGroup","staffAuditActionGroupLabel"],"mappings":"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"}
|
|
@@ -17,6 +17,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
17
17
|
* See the License for the specific language governing permissions and
|
|
18
18
|
* limitations under the License.
|
|
19
19
|
*/ import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
|
|
20
|
+
import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
|
|
21
|
+
import { HelpTip } from "@aglyn/shared-ui-jsx";
|
|
20
22
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
21
23
|
import { Alert, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, Stack, TextField, Typography } from "@mui/material";
|
|
22
24
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -111,6 +113,40 @@ export const AI_BRIEF_COPY = {
|
|
|
111
113
|
failed: 'The campaign could not be started. Try again.'
|
|
112
114
|
}
|
|
113
115
|
};
|
|
116
|
+
/**
|
|
117
|
+
* Each kind's own help beside the dialog's title (AGL-3660): the guide for
|
|
118
|
+
* the thing being described, opened at the section about writing its brief,
|
|
119
|
+
* never one page shared by all seven.
|
|
120
|
+
*/ const AI_BRIEF_HELP = {
|
|
121
|
+
page: pluginDocsHelp('aiPage', {
|
|
122
|
+
anchor: '#describe-the-page',
|
|
123
|
+
excerpt: '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.'
|
|
124
|
+
}),
|
|
125
|
+
template: pluginDocsHelp('aiTemplate', {
|
|
126
|
+
anchor: '#generate-a-page-template-with-aglyn-ai',
|
|
127
|
+
excerpt: 'Describe what each page made from the template should show. What changes per page is filled in from each record.'
|
|
128
|
+
}),
|
|
129
|
+
layout: pluginDocsHelp('aiLayout', {
|
|
130
|
+
anchor: '#generate-a-layout-with-aglyn-ai',
|
|
131
|
+
excerpt: 'Describe the header, navigation and footer. The layout is built as a draft, and no page uses it until you assign it.'
|
|
132
|
+
}),
|
|
133
|
+
form: pluginDocsHelp('aiForm', {
|
|
134
|
+
anchor: '#describe-the-form',
|
|
135
|
+
excerpt: '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.'
|
|
136
|
+
}),
|
|
137
|
+
component: pluginDocsHelp('aiComponent', {
|
|
138
|
+
anchor: '#from-a-brief',
|
|
139
|
+
excerpt: 'Describe one reusable block. What each page can change, such as a name or a photo, becomes a property of the component.'
|
|
140
|
+
}),
|
|
141
|
+
email: pluginDocsHelp('aiEmail', {
|
|
142
|
+
anchor: '#write-the-brief',
|
|
143
|
+
excerpt: 'Say what the email is for. It is built from email blocks with three subject lines to choose from, and nothing is sent.'
|
|
144
|
+
}),
|
|
145
|
+
campaign: pluginDocsHelp('aiEmail', {
|
|
146
|
+
anchor: '#where-to-start-it',
|
|
147
|
+
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.'
|
|
148
|
+
})
|
|
149
|
+
};
|
|
114
150
|
/**
|
|
115
151
|
* The kinds of email a member picks from, as their chips name them. The jobs'
|
|
116
152
|
* own catalog (`AI_EMAIL_TYPES`) also holds `reply`, which only a job the
|
|
@@ -255,8 +291,16 @@ export function AiBriefDialog({ kind, open, onClose, orgId, hostId, user, orgSlu
|
|
|
255
291
|
fullWidth: true,
|
|
256
292
|
maxWidth: "sm",
|
|
257
293
|
children: [
|
|
258
|
-
/*#__PURE__*/
|
|
259
|
-
|
|
294
|
+
/*#__PURE__*/ _jsxs(DialogTitle, {
|
|
295
|
+
sx: {
|
|
296
|
+
display: 'flex',
|
|
297
|
+
alignItems: 'center',
|
|
298
|
+
gap: 0.5
|
|
299
|
+
},
|
|
300
|
+
children: [
|
|
301
|
+
copy.title,
|
|
302
|
+
/*#__PURE__*/ _jsx(HelpTip, _extends({}, AI_BRIEF_HELP[shownKind]))
|
|
303
|
+
]
|
|
260
304
|
}),
|
|
261
305
|
/*#__PURE__*/ _jsx(DialogContent, {
|
|
262
306
|
children: started && orgId ? /*#__PURE__*/ _jsx(AiJobFollow, {
|
|
@@ -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 { 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 { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob } 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 * 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\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>{copy.title}</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 {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","authorizedFetch","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_PAGE_TYPES","AI_TEMPLATE_SUBJECTS","parseAiTemplateJobInputs","AiJobFollow","publishAiJob","AiTemplateCollectionField","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","AiBriefDialog","open","onClose","orgId","hostId","user","orgSlug","isStaff","userRef","current","brief","setBrief","setChoice","busy","setBusy","notice","setNotice","setStarted","shownKind","copy","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","intro","staff","onOpenJobs","spacing","sx","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","gap","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 { 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 { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob } 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\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 {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","AiJobFollow","publishAiJob","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","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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"}
|