@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.230
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/README.md +3 -3
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +38 -0
- package/src/lib/components/ai-automation-zones.js +5 -1
- package/src/lib/components/ai-automation-zones.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
- package/src/lib/components/ai-brief-dialog.component.js +87 -8
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
- package/src/lib/components/ai-campaign-create.component.js +270 -0
- package/src/lib/components/ai-campaign-create.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
- package/src/lib/components/ai-describe-automation.component.js +15 -6
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +20 -8
- package/src/lib/components/ai-describe-button.component.js +19 -36
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-describe-email.component.d.ts +31 -0
- package/src/lib/components/ai-describe-email.component.js +22 -0
- package/src/lib/components/ai-describe-email.component.js.map +1 -0
- package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
- package/src/lib/components/ai-describe-org-automation.component.js +238 -0
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
- package/src/lib/components/ai-experiment-cards.component.js +104 -18
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -5
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
- package/src/lib/components/ai-funnel-zones.component.js +178 -0
- package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
- package/src/lib/components/ai-insight-card.component.d.ts +42 -0
- package/src/lib/components/ai-insight-card.component.js +137 -0
- package/src/lib/components/ai-insight-card.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
- package/src/lib/components/ai-insight-dialog.component.js +9 -3
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +7 -2
- package/src/lib/components/ai-job-plan.component.js +35 -5
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +17 -1
- package/src/lib/components/ai-job-requests.js +39 -3
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-logic-zones.d.ts +64 -0
- package/src/lib/components/ai-logic-zones.js +18 -0
- package/src/lib/components/ai-logic-zones.js.map +1 -0
- package/src/lib/components/ai-logic.component.d.ts +77 -0
- package/src/lib/components/ai-logic.component.js +553 -0
- package/src/lib/components/ai-logic.component.js.map +1 -0
- package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
- package/src/lib/components/ai-marketing-insight.component.js +80 -0
- package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
- package/src/lib/components/ai-media-create.component.d.ts +55 -0
- package/src/lib/components/ai-media-create.component.js +433 -0
- package/src/lib/components/ai-media-create.component.js.map +1 -0
- package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
- package/src/lib/components/ai-overlay-cards.component.js +505 -0
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
- package/src/lib/components/ai-product-zones.d.ts +5 -0
- package/src/lib/components/ai-product-zones.js +1 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
- package/src/lib/components/ai-products-create-button.component.js +67 -0
- package/src/lib/components/ai-products-create-button.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.js +100 -81
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
- package/src/lib/components/ai-revise-automation.component.js +207 -0
- package/src/lib/components/ai-revise-automation.component.js.map +1 -0
- package/src/lib/components/ai-site-build-page.component.js +140 -16
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
- package/src/lib/components/ai-upsell-dialog.component.js +177 -0
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.d.ts +42 -0
- package/src/lib/components/assist-build-card.component.js +183 -0
- package/src/lib/components/assist-build-card.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +2 -0
- package/src/lib/components/assist-edit-canvas.js +56 -1
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +48 -3
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +51 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -14
- package/src/lib/components/use-ai-job-run.js +0 -50
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
- package/src/lib/jobs/ai-build-capabilities.js +256 -0
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
- package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +657 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.js +9 -1
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
- package/src/lib/jobs/ai-job-drafts.js +25 -4
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
- package/src/lib/jobs/ai-job-logic-step.js +395 -0
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
- package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
- package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
- package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
- package/src/lib/jobs/ai-job-page-step.js +6 -6
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
- package/src/lib/jobs/ai-job-plan-step.js +79 -11
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
- package/src/lib/jobs/ai-job-site-step.js +200 -58
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
- package/src/lib/jobs/ai-job-text-step.js +12 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
- package/src/lib/jobs/ai-job-workflow-step.js +318 -13
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +80 -2
- package/src/lib/jobs/ai-jobs.js +377 -13
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-logic-records.d.ts +40 -0
- package/src/lib/jobs/ai-logic-records.js +55 -0
- package/src/lib/jobs/ai-logic-records.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +7 -0
- package/src/lib/jobs/ai-site-publish.js +9 -0
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
- package/src/lib/jobs/ai-workflow-records.js +40 -1
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-automation-draft.d.ts +18 -0
- package/src/lib/model/ai-automation-draft.js +86 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +201 -0
- package/src/lib/model/ai-build-job.js +471 -0
- package/src/lib/model/ai-build-job.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +61 -0
- package/src/lib/model/ai-build-plan.js +154 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +56 -0
- package/src/lib/model/ai-build-progress.js +128 -0
- package/src/lib/model/ai-build-progress.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +20 -10
- package/src/lib/model/ai-insight.js +72 -9
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +3 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +80 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +108 -0
- package/src/lib/model/ai-logic-job.js +327 -0
- package/src/lib/model/ai-logic-job.js.map +1 -0
- package/src/lib/model/ai-media-credits.d.ts +52 -0
- package/src/lib/model/ai-media-credits.js +61 -0
- package/src/lib/model/ai-media-credits.js.map +1 -0
- package/src/lib/model/ai-overlay-copy.d.ts +114 -0
- package/src/lib/model/ai-overlay-copy.js +175 -0
- package/src/lib/model/ai-overlay-copy.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +20 -3
- package/src/lib/model/ai-site-build-progress.js +170 -10
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-svg.d.ts +69 -0
- package/src/lib/model/ai-svg.js +265 -0
- package/src/lib/model/ai-svg.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +3 -2
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +92 -4
- package/src/lib/model/ai-workflow-job.js +124 -7
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/model/assist-build.d.ts +58 -0
- package/src/lib/model/assist-build.js +52 -0
- package/src/lib/model/assist-build.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +48 -3
- package/src/lib/model/assist-edit.js +48 -4
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +240 -11
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +81 -4
- package/src/lib/providers/catalog.js +80 -9
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +116 -0
- package/src/lib/providers/image-contract.js +74 -0
- package/src/lib/providers/image-contract.js.map +1 -0
- package/src/lib/providers/model-choice.js +11 -0
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image.d.ts +121 -0
- package/src/lib/providers/vertex-image.js +312 -0
- package/src/lib/providers/vertex-image.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
- package/src/lib/runtime/ai-doctrine-validators.js +89 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +11 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +15 -1
- package/src/lib/runtime/ai-eval.js +52 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
- package/src/lib/runtime/experiment-variant-copy.js +102 -0
- package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
- package/src/lib/server/ai-experiment-versions.d.ts +51 -0
- package/src/lib/server/ai-experiment-versions.js +270 -0
- package/src/lib/server/ai-experiment-versions.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.js +74 -4
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +66 -0
- package/src/lib/server/ai-media-image.js +521 -0
- package/src/lib/server/ai-media-image.js.map +1 -0
- package/src/lib/server/ai-media-svg.d.ts +91 -0
- package/src/lib/server/ai-media-svg.js +186 -0
- package/src/lib/server/ai-media-svg.js.map +1 -0
- package/src/lib/server/assist-build.d.ts +54 -0
- package/src/lib/server/assist-build.js +121 -0
- package/src/lib/server/assist-build.js.map +1 -0
- package/src/lib/server/assist-chat.js +108 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +213 -3
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.js +242 -4
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-logic-tool.d.ts +27 -0
- package/src/lib/tools/ai-logic-tool.js +207 -0
- package/src/lib/tools/ai-logic-tool.js.map +1 -0
- package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
- package/src/lib/tools/ai-overlay-tool.js +107 -0
- package/src/lib/tools/ai-overlay-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
- package/src/lib/tools/ai-workflow-tool.js +31 -0
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +28 -1
- package/src/lib/usage/assist-job-refund.js +44 -9
- package/src/lib/usage/assist-job-refund.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +5 -0
- package/src/lib/usage/assist-usage.js +2 -0
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -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 Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\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 and a\n * reusable component from Components.\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. 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'\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}\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}\n\nexport const AI_BRIEF_NO_CHOICE: AiBriefChoice = {\n pageType: null,\n subject: null,\n collectionId: null,\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. 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 === '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 const copy = AI_BRIEF_COPY[kind]\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\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 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, 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 === '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","Chip","Dialog","DialogActions","DialogContent","DialogTitle","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","AI_TEMPLATE_SUBJECT_LABELS","entry","product","author","AI_BRIEF_NO_CHOICE","pageType","subject","collectionId","aiBriefJobInputs","kind","choice","inputs","AiBriefDialog","open","onClose","orgId","hostId","user","orgSlug","isStaff","copy","userRef","current","brief","setBrief","setChoice","busy","setBusy","notice","setNotice","setStarted","pick","patch","ready","start","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","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 { 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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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
|
|
2
|
+
/**
|
|
3
|
+
* "Create with AI" on the Campaigns section (AGL-3603), in the zone the
|
|
4
|
+
* marketing plugin hosts beside Create campaign: a brief starts the
|
|
5
|
+
* `campaign` job, which writes an email design and the DRAFT campaign that
|
|
6
|
+
* would send it — aimed at nobody, scheduled for nothing, sent by nobody until
|
|
7
|
+
* a member does — or, where the workspace's plan sends no campaign email, the
|
|
8
|
+
* `email` job, which writes the design on its own.
|
|
9
|
+
*
|
|
10
|
+
* The choice is the runner's own: the campaign step and its admission ask
|
|
11
|
+
* `checkQuota(org, 'emailSendsPerMonth', 0)`, and this widget asks the same of
|
|
12
|
+
* the same org document before it offers either. A plan that changes between
|
|
13
|
+
* the two is the door's to refuse, and the dialog then offers the design on
|
|
14
|
+
* its own rather than a dead end.
|
|
15
|
+
*
|
|
16
|
+
* The campaign is the marketing plugin's and neither plugin imports the other:
|
|
17
|
+
* the props below restate the half of the zone's contract this widget reads.
|
|
18
|
+
*/
|
|
19
|
+
/** One site a campaign could be placed on, as the zone hands it over. */
|
|
20
|
+
export interface AiCampaignSite {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
/** What the `hostCampaigns` zone hands this widget. */
|
|
25
|
+
export interface AiCreateCampaignButtonProps extends ConsoleWidgetEntitlementProps {
|
|
26
|
+
hostId: string | null;
|
|
27
|
+
orgId: string | null;
|
|
28
|
+
sites: readonly AiCampaignSite[];
|
|
29
|
+
}
|
|
30
|
+
/** What the dialog says for each of the two jobs it can start. */
|
|
31
|
+
export declare const AI_CAMPAIGN_CREATE_COPY: {
|
|
32
|
+
readonly campaign: {
|
|
33
|
+
readonly title: "Create a campaign with AI";
|
|
34
|
+
readonly next: string;
|
|
35
|
+
readonly submit: "Write the campaign";
|
|
36
|
+
readonly started: string;
|
|
37
|
+
};
|
|
38
|
+
readonly email: {
|
|
39
|
+
readonly title: "Write a campaign email with AI";
|
|
40
|
+
readonly next: string;
|
|
41
|
+
readonly submit: "Plan the email";
|
|
42
|
+
readonly started: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type AiCampaignJobKind = keyof typeof AI_CAMPAIGN_CREATE_COPY;
|
|
46
|
+
/** Which job a workspace's plan allows: a campaign where it sends campaign email, else the design alone. */
|
|
47
|
+
export declare function aiCampaignJobKind(org: object | null | undefined): AiCampaignJobKind;
|
|
48
|
+
/** The job's inputs: the name the member gave it, when they gave one. */
|
|
49
|
+
export declare function aiCampaignJobInputs(name: string): Record<string, string>;
|
|
50
|
+
export declare function AiCreateCampaignButton(props: AiCreateCampaignButtonProps): import("react").JSX.Element;
|
|
51
|
+
export declare namespace AiCreateCampaignButton {
|
|
52
|
+
var displayName: string;
|
|
53
|
+
}
|
|
54
|
+
export default AiCreateCampaignButton;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
|
|
19
|
+
import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
|
|
20
|
+
import { checkQuota } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
21
|
+
import { mdiCreation } from "@aglyn/shared-data-mdi";
|
|
22
|
+
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
23
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
24
|
+
import { useHostOrgId, useOrgPlan, useUser } from "@aglyn/tenant-feature-instance";
|
|
25
|
+
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, Link, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
26
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
27
|
+
import { AiJobFollow } from "./ai-job-follow.component.js";
|
|
28
|
+
import { publishAiJob } from "./ai-jobs-store.js";
|
|
29
|
+
import { AiUpsellButton } from "./ai-upsell-dialog.component.js";
|
|
30
|
+
/** The longest brief a job admits. */ const BRIEF_MAX_CHARS = 4000;
|
|
31
|
+
/** The longest name a campaign job reads. */ const NAME_MAX_CHARS = 120;
|
|
32
|
+
const FAILED_COPY = 'The campaign could not be started. Try again.';
|
|
33
|
+
/**
|
|
34
|
+
* The campaign door's refusal for a plan that sends no campaign email, as the
|
|
35
|
+
* campaign step words it, so a plan that changed after the dialog opened is
|
|
36
|
+
* recognized and answered with the design-only path.
|
|
37
|
+
*/ const PLAN_REFUSAL_PREFIX = 'Campaign email is not included';
|
|
38
|
+
/** What the dialog says for each of the two jobs it can start. */ export const AI_CAMPAIGN_CREATE_COPY = {
|
|
39
|
+
campaign: {
|
|
40
|
+
title: 'Create a campaign with AI',
|
|
41
|
+
next: 'It writes an email design with three subject lines and three preheaders, and a draft ' + 'campaign that would send it. Nothing is sent or scheduled, and the campaign is aimed at ' + 'nobody until you pick its lists.',
|
|
42
|
+
submit: 'Write the campaign',
|
|
43
|
+
started: 'The campaign is being written. When it is done, open the draft campaign to pick who ' + 'receives it, check the email and schedule it.'
|
|
44
|
+
},
|
|
45
|
+
email: {
|
|
46
|
+
title: 'Write a campaign email with AI',
|
|
47
|
+
next: 'This workspace’s plan does not send campaign email, so this writes the email design on ' + 'its own, with three subject lines and three preheaders. Upgrade in Billing to draft a ' + 'campaign around it.',
|
|
48
|
+
submit: 'Plan the email',
|
|
49
|
+
started: 'The email is being planned. Review the plan here or in AI jobs, and confirm it to build. ' + 'The design is built as a draft and sent to nobody.'
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** Which job a workspace's plan allows: a campaign where it sends campaign email, else the design alone. */ export function aiCampaignJobKind(org) {
|
|
53
|
+
return checkQuota(org, 'emailSendsPerMonth', 0).allowed ? 'campaign' : 'email';
|
|
54
|
+
}
|
|
55
|
+
/** The job's inputs: the name the member gave it, when they gave one. */ export function aiCampaignJobInputs(name) {
|
|
56
|
+
const trimmed = name.replace(/\s+/g, ' ').trim().slice(0, NAME_MAX_CHARS);
|
|
57
|
+
return trimmed ? {
|
|
58
|
+
name: trimmed
|
|
59
|
+
} : {};
|
|
60
|
+
}
|
|
61
|
+
export function AiCreateCampaignButton(props) {
|
|
62
|
+
var _ref, _useHostOrgId, _props_orgId;
|
|
63
|
+
var _sites_;
|
|
64
|
+
const { hostId, sites } = props;
|
|
65
|
+
const { data: user } = useUser();
|
|
66
|
+
const userRef = useRef(user);
|
|
67
|
+
userRef.current = user;
|
|
68
|
+
const [open, setOpen] = useState(false);
|
|
69
|
+
const [siteId, setSiteId] = useState((_ref = hostId != null ? hostId : (_sites_ = sites[0]) == null ? void 0 : _sites_.id) != null ? _ref : '');
|
|
70
|
+
const site = hostId != null ? hostId : siteId;
|
|
71
|
+
const siteOrgId = (_useHostOrgId = useHostOrgId(site || undefined)) != null ? _useHostOrgId : undefined;
|
|
72
|
+
const orgId = (_props_orgId = props.orgId) != null ? _props_orgId : siteOrgId;
|
|
73
|
+
const { org, ready } = useOrgPlan(site || undefined);
|
|
74
|
+
const [refusedPlan, setRefusedPlan] = useState(false);
|
|
75
|
+
const kind = refusedPlan ? 'email' : aiCampaignJobKind(org);
|
|
76
|
+
const copy = AI_CAMPAIGN_CREATE_COPY[kind];
|
|
77
|
+
const [name, setName] = useState('');
|
|
78
|
+
const [brief, setBrief] = useState('');
|
|
79
|
+
const [busy, setBusy] = useState(false);
|
|
80
|
+
const [notice, setNotice] = useState(null);
|
|
81
|
+
const [started, setStarted] = useState(null);
|
|
82
|
+
// The org hub's sites settle after the widget mounts; take the first once they do.
|
|
83
|
+
useEffect(()=>{
|
|
84
|
+
if (!hostId && !siteId && sites[0]) setSiteId(sites[0].id);
|
|
85
|
+
}, [
|
|
86
|
+
hostId,
|
|
87
|
+
siteId,
|
|
88
|
+
sites
|
|
89
|
+
]);
|
|
90
|
+
useEffect(()=>{
|
|
91
|
+
if (!open) return;
|
|
92
|
+
setNotice(null);
|
|
93
|
+
setStarted(null);
|
|
94
|
+
setRefusedPlan(false);
|
|
95
|
+
}, [
|
|
96
|
+
open
|
|
97
|
+
]);
|
|
98
|
+
const start = useCallback(async ()=>{
|
|
99
|
+
if (!orgId || !site || !brief.trim()) return;
|
|
100
|
+
setBusy(true);
|
|
101
|
+
setNotice(null);
|
|
102
|
+
try {
|
|
103
|
+
var _ref;
|
|
104
|
+
const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json'
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
orgId,
|
|
111
|
+
hostId: site,
|
|
112
|
+
kind,
|
|
113
|
+
brief: brief.trim(),
|
|
114
|
+
inputs: aiCampaignJobInputs(name)
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
const payload = await response.json().catch(()=>null);
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
var _ref1;
|
|
120
|
+
const locked = parseLockdownRefusal(response.status, payload);
|
|
121
|
+
const error = String((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : FAILED_COPY);
|
|
122
|
+
if (kind === 'campaign' && response.status === 403 && error.startsWith(PLAN_REFUSAL_PREFIX)) {
|
|
123
|
+
setRefusedPlan(true);
|
|
124
|
+
}
|
|
125
|
+
setNotice(locked ? lockdownRefusalText(locked) : error);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const job = (_ref = payload == null ? void 0 : payload.job) != null ? _ref : null;
|
|
129
|
+
if (!job) {
|
|
130
|
+
setNotice(FAILED_COPY);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
publishAiJob(job);
|
|
134
|
+
setStarted(job);
|
|
135
|
+
setBrief('');
|
|
136
|
+
setName('');
|
|
137
|
+
} catch (unused) {
|
|
138
|
+
setNotice(FAILED_COPY);
|
|
139
|
+
} finally{
|
|
140
|
+
setBusy(false);
|
|
141
|
+
}
|
|
142
|
+
}, [
|
|
143
|
+
orgId,
|
|
144
|
+
site,
|
|
145
|
+
kind,
|
|
146
|
+
brief,
|
|
147
|
+
name
|
|
148
|
+
]);
|
|
149
|
+
if (!hostId && !sites.length) return null;
|
|
150
|
+
// Drawn from the shell's gates alone (AGL-3601): nothing asks a server until
|
|
151
|
+
// it is used, and a plan without the add-on gets the add-on's dialog.
|
|
152
|
+
if (props.entitled === false) {
|
|
153
|
+
return props.upgrade ? /*#__PURE__*/ _jsx(AiUpsellButton, {
|
|
154
|
+
kind: "campaign",
|
|
155
|
+
upgrade: props.upgrade
|
|
156
|
+
}) : null;
|
|
157
|
+
}
|
|
158
|
+
const help = pluginDocsHelp('aiMarketing', {
|
|
159
|
+
anchor: '#create-a-campaign',
|
|
160
|
+
excerpt: 'Writes an email design and a draft campaign that would send it, from a brief. Nothing is sent or scheduled, and the campaign is aimed at nobody until you pick its lists.'
|
|
161
|
+
});
|
|
162
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
163
|
+
children: [
|
|
164
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
165
|
+
size: "small",
|
|
166
|
+
variant: "outlined",
|
|
167
|
+
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
168
|
+
path: mdiCreation.path
|
|
169
|
+
}),
|
|
170
|
+
onClick: ()=>setOpen(true),
|
|
171
|
+
children: 'Create with AI'
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
174
|
+
open: open,
|
|
175
|
+
onClose: busy ? undefined : ()=>setOpen(false),
|
|
176
|
+
fullWidth: true,
|
|
177
|
+
maxWidth: "sm",
|
|
178
|
+
children: [
|
|
179
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
180
|
+
children: copy.title
|
|
181
|
+
}),
|
|
182
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
183
|
+
children: started && orgId ? /*#__PURE__*/ _jsx(AiJobFollow, {
|
|
184
|
+
job: started,
|
|
185
|
+
orgId: orgId,
|
|
186
|
+
user: user,
|
|
187
|
+
intro: AI_CAMPAIGN_CREATE_COPY[started.kind === 'email' ? 'email' : 'campaign'].started,
|
|
188
|
+
onOpenJobs: ()=>setOpen(false)
|
|
189
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
190
|
+
spacing: 2,
|
|
191
|
+
sx: {
|
|
192
|
+
pt: 1
|
|
193
|
+
},
|
|
194
|
+
children: [
|
|
195
|
+
hostId ? null : /*#__PURE__*/ _jsx(TextField, {
|
|
196
|
+
select: true,
|
|
197
|
+
size: "small",
|
|
198
|
+
label: "Site",
|
|
199
|
+
helperText: "The site the campaign is placed on and sent as.",
|
|
200
|
+
value: siteId,
|
|
201
|
+
onChange: (event)=>setSiteId(event.target.value),
|
|
202
|
+
disabled: busy,
|
|
203
|
+
children: sites.map((entry)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
204
|
+
value: entry.id,
|
|
205
|
+
children: entry.name || entry.id
|
|
206
|
+
}, entry.id))
|
|
207
|
+
}),
|
|
208
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
209
|
+
size: "small",
|
|
210
|
+
label: "Campaign name (optional)",
|
|
211
|
+
value: name,
|
|
212
|
+
onChange: (event)=>setName(event.target.value.slice(0, NAME_MAX_CHARS)),
|
|
213
|
+
disabled: busy
|
|
214
|
+
}),
|
|
215
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
216
|
+
label: "What is the campaign for?",
|
|
217
|
+
placeholder: "Announce the autumn menu to our regulars, and point them at the class page to book the Tuesday bread class",
|
|
218
|
+
multiline: true,
|
|
219
|
+
minRows: 4,
|
|
220
|
+
value: brief,
|
|
221
|
+
onChange: (event)=>setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS)),
|
|
222
|
+
disabled: busy,
|
|
223
|
+
autoFocus: true
|
|
224
|
+
}),
|
|
225
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
226
|
+
variant: "body2",
|
|
227
|
+
color: "text.secondary",
|
|
228
|
+
children: [
|
|
229
|
+
copy.next,
|
|
230
|
+
' ',
|
|
231
|
+
/*#__PURE__*/ _jsx(Link, {
|
|
232
|
+
href: help.href,
|
|
233
|
+
target: "_blank",
|
|
234
|
+
rel: "noopener",
|
|
235
|
+
title: help.excerpt,
|
|
236
|
+
children: 'How it works'
|
|
237
|
+
})
|
|
238
|
+
]
|
|
239
|
+
}),
|
|
240
|
+
notice ? /*#__PURE__*/ _jsx(Alert, {
|
|
241
|
+
severity: "warning",
|
|
242
|
+
children: notice
|
|
243
|
+
}) : null
|
|
244
|
+
]
|
|
245
|
+
})
|
|
246
|
+
}),
|
|
247
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
248
|
+
children: [
|
|
249
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
250
|
+
onClick: ()=>setOpen(false),
|
|
251
|
+
disabled: busy,
|
|
252
|
+
children: started ? 'Close' : 'Cancel'
|
|
253
|
+
}),
|
|
254
|
+
started ? null : /*#__PURE__*/ _jsx(Button, {
|
|
255
|
+
variant: "contained",
|
|
256
|
+
onClick: ()=>void start(),
|
|
257
|
+
disabled: busy || !ready || !orgId || !site || !brief.trim(),
|
|
258
|
+
children: copy.submit
|
|
259
|
+
})
|
|
260
|
+
]
|
|
261
|
+
})
|
|
262
|
+
]
|
|
263
|
+
})
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
AiCreateCampaignButton.displayName = 'AiCreateCampaignButton';
|
|
268
|
+
export default AiCreateCampaignButton;
|
|
269
|
+
|
|
270
|
+
//# sourceMappingURL=ai-campaign-create.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-campaign-create.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useOrgPlan, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\n\n/**\n * \"Create with AI\" on the Campaigns section (AGL-3603), in the zone the\n * marketing plugin hosts beside Create campaign: a brief starts the\n * `campaign` job, which writes an email design and the DRAFT campaign that\n * would send it — aimed at nobody, scheduled for nothing, sent by nobody until\n * a member does — or, where the workspace's plan sends no campaign email, the\n * `email` job, which writes the design on its own.\n *\n * The choice is the runner's own: the campaign step and its admission ask\n * `checkQuota(org, 'emailSendsPerMonth', 0)`, and this widget asks the same of\n * the same org document before it offers either. A plan that changes between\n * the two is the door's to refuse, and the dialog then offers the design on\n * its own rather than a dead end.\n *\n * The campaign is the marketing plugin's and neither plugin imports the other:\n * the props below restate the half of the zone's contract this widget reads.\n */\n\n/** One site a campaign could be placed on, as the zone hands it over. */\nexport interface AiCampaignSite {\n id: string\n name: string\n}\n\n/** What the `hostCampaigns` zone hands this widget. */\nexport interface AiCreateCampaignButtonProps extends ConsoleWidgetEntitlementProps {\n hostId: string | null\n orgId: string | null\n sites: readonly AiCampaignSite[]\n}\n\n/** The longest brief a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\n/** The longest name a campaign job reads. */\nconst NAME_MAX_CHARS = 120\n\nconst FAILED_COPY = 'The campaign could not be started. Try again.'\n\n/**\n * The campaign door's refusal for a plan that sends no campaign email, as the\n * campaign step words it, so a plan that changed after the dialog opened is\n * recognized and answered with the design-only path.\n */\nconst PLAN_REFUSAL_PREFIX = 'Campaign email is not included'\n\n/** What the dialog says for each of the two jobs it can start. */\nexport const AI_CAMPAIGN_CREATE_COPY = {\n campaign: {\n title: 'Create a campaign with AI',\n next:\n 'It writes an email design with three subject lines and three preheaders, and a draft ' +\n 'campaign that would send it. Nothing is sent or scheduled, and the campaign is aimed at ' +\n 'nobody until you pick its lists.',\n submit: 'Write the campaign',\n started:\n 'The campaign is being written. When it is done, open the draft campaign to pick who ' +\n 'receives it, check the email and schedule it.',\n },\n email: {\n title: 'Write a campaign email with AI',\n next:\n 'This workspace’s plan does not send campaign email, so this writes the email design on ' +\n 'its own, with three subject lines and three preheaders. Upgrade in Billing to draft a ' +\n 'campaign around it.',\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 build. ' +\n 'The design is built as a draft and sent to nobody.',\n },\n} as const\n\nexport type AiCampaignJobKind = keyof typeof AI_CAMPAIGN_CREATE_COPY\n\n/** Which job a workspace's plan allows: a campaign where it sends campaign email, else the design alone. */\nexport function aiCampaignJobKind(org: object | null | undefined): AiCampaignJobKind {\n return checkQuota(org as never, 'emailSendsPerMonth', 0).allowed ? 'campaign' : 'email'\n}\n\n/** The job's inputs: the name the member gave it, when they gave one. */\nexport function aiCampaignJobInputs(name: string): Record<string, string> {\n const trimmed = name.replace(/\\s+/g, ' ').trim().slice(0, NAME_MAX_CHARS)\n return trimmed ? { name: trimmed } : {}\n}\n\nexport function AiCreateCampaignButton(props: AiCreateCampaignButtonProps) {\n const { hostId, sites } = props\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const [open, setOpen] = useState(false)\n const [siteId, setSiteId] = useState<string>(hostId ?? sites[0]?.id ?? '')\n const site = hostId ?? siteId\n const siteOrgId = useHostOrgId(site || undefined) ?? undefined\n const orgId = props.orgId ?? siteOrgId\n const { org, ready } = useOrgPlan(site || undefined)\n const [refusedPlan, setRefusedPlan] = useState(false)\n const kind: AiCampaignJobKind = refusedPlan ? 'email' : aiCampaignJobKind(org)\n const copy = AI_CAMPAIGN_CREATE_COPY[kind]\n const [name, setName] = useState('')\n const [brief, setBrief] = useState('')\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n\n // The org hub's sites settle after the widget mounts; take the first once they do.\n useEffect(() => {\n if (!hostId && !siteId && sites[0]) setSiteId(sites[0].id)\n }, [hostId, siteId, sites])\n\n useEffect(() => {\n if (!open) return\n setNotice(null)\n setStarted(null)\n setRefusedPlan(false)\n }, [open])\n\n const start = useCallback(async () => {\n if (!orgId || !site || !brief.trim()) 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({\n orgId,\n hostId: site,\n kind,\n brief: brief.trim(),\n inputs: aiCampaignJobInputs(name),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n const error = String(payload?.error ?? FAILED_COPY)\n if (kind === 'campaign' && response.status === 403 && error.startsWith(PLAN_REFUSAL_PREFIX)) {\n setRefusedPlan(true)\n }\n setNotice(locked ? lockdownRefusalText(locked) : error)\n return\n }\n const job = (payload?.job as AiJobSummary | undefined) ?? null\n if (!job) {\n setNotice(FAILED_COPY)\n return\n }\n publishAiJob(job)\n setStarted(job)\n setBrief('')\n setName('')\n } catch {\n setNotice(FAILED_COPY)\n } finally {\n setBusy(false)\n }\n }, [orgId, site, kind, brief, name])\n\n if (!hostId && !sites.length) return null\n // Drawn from the shell's gates alone (AGL-3601): nothing asks a server until\n // it is used, and a plan without the add-on gets the add-on's dialog.\n if (props.entitled === false) {\n return props.upgrade ? <AiUpsellButton kind=\"campaign\" upgrade={props.upgrade} /> : null\n }\n\n const help = pluginDocsHelp('aiMarketing', {\n anchor: '#create-a-campaign',\n excerpt:\n 'Writes an email design and a draft campaign that would send it, from a brief. Nothing is sent or scheduled, and the campaign is aimed at nobody until you pick its lists.',\n })\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <Dialog open={open} onClose={busy ? undefined : () => setOpen(false)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {started && orgId ? (\n <AiJobFollow\n job={started}\n orgId={orgId}\n user={user}\n intro={AI_CAMPAIGN_CREATE_COPY[started.kind === 'email' ? 'email' : 'campaign'].started}\n onOpenJobs={() => setOpen(false)}\n />\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {hostId ? null : (\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n helperText=\"The site the campaign is placed on and sent as.\"\n value={siteId}\n onChange={(event) => setSiteId(event.target.value)}\n disabled={busy}\n >\n {sites.map((entry) => (\n <MenuItem key={entry.id} value={entry.id}>\n {entry.name || entry.id}\n </MenuItem>\n ))}\n </TextField>\n )}\n <TextField\n size=\"small\"\n label=\"Campaign name (optional)\"\n value={name}\n onChange={(event) => setName(event.target.value.slice(0, NAME_MAX_CHARS))}\n disabled={busy}\n />\n <TextField\n label=\"What is the campaign for?\"\n placeholder=\"Announce the autumn menu to our regulars, and point them at the class page to book the Tuesday bread class\"\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpen(false)} disabled={busy}>\n {started ? 'Close' : 'Cancel'}\n </Button>\n {started ? null : (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={busy || !ready || !orgId || !site || !brief.trim()}\n >\n {copy.submit}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n </>\n )\n}\nAiCreateCampaignButton.displayName = 'AiCreateCampaignButton'\n\nexport default AiCreateCampaignButton\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","checkQuota","mdiCreation","MdiIcon","authorizedFetch","useHostOrgId","useOrgPlan","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","Link","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AiJobFollow","publishAiJob","AiUpsellButton","BRIEF_MAX_CHARS","NAME_MAX_CHARS","FAILED_COPY","PLAN_REFUSAL_PREFIX","AI_CAMPAIGN_CREATE_COPY","campaign","title","next","submit","started","email","aiCampaignJobKind","org","allowed","aiCampaignJobInputs","name","trimmed","replace","trim","slice","AiCreateCampaignButton","props","hostId","sites","data","user","userRef","current","open","setOpen","siteId","setSiteId","id","site","siteOrgId","undefined","orgId","ready","refusedPlan","setRefusedPlan","kind","copy","setName","brief","setBrief","busy","setBusy","notice","setNotice","setStarted","start","response","method","headers","body","JSON","stringify","inputs","payload","json","catch","ok","locked","status","error","String","startsWith","job","length","entitled","upgrade","help","anchor","excerpt","size","variant","startIcon","path","onClick","onClose","fullWidth","maxWidth","intro","onOpenJobs","spacing","sx","pt","select","label","helperText","value","onChange","event","target","disabled","map","entry","placeholder","multiline","minRows","autoFocus","color","href","rel","severity","displayName"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
|
|
1
2
|
import type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones';
|
|
2
3
|
import { useAiJobRun } from './use-ai-job-run';
|
|
3
4
|
export declare const AI_AUTOMATION_BRIEF_COPY: {
|
|
@@ -24,8 +25,12 @@ export interface AiAutomationBriefDialogProps {
|
|
|
24
25
|
}
|
|
25
26
|
export declare function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps): import("react").JSX.Element;
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* Drawn at once, from the gates the shell resolved before mounting it (the
|
|
29
|
+
* plan, `ai.generate`, the site's AI switch and the release flag); nothing is
|
|
30
|
+
* asked of a server to draw it (AGL-3601). The start door decides when the
|
|
31
|
+
* description is sent, and says why in the dialog if it refuses. On a plan
|
|
32
|
+
* that could buy the AI add-on, the shell mounts it with `entitled={false}`
|
|
33
|
+
* and the same button opens the add-on's dialog.
|
|
29
34
|
*/
|
|
30
|
-
export declare function AiDescribeAutomationButton({ hostId, orgId, openAction }: ConsoleHostAutomationsZoneProps): import("react").JSX.Element;
|
|
35
|
+
export declare function AiDescribeAutomationButton({ hostId, orgId, openAction, entitled, upgrade, }: ConsoleHostAutomationsZoneProps & ConsoleWidgetEntitlementProps): import("react").JSX.Element;
|
|
31
36
|
export default AiDescribeAutomationButton;
|
|
@@ -22,7 +22,8 @@ import { useUser } from "@aglyn/tenant-feature-instance";
|
|
|
22
22
|
import { Alert, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, Link, Stack, TextField, Typography } from "@mui/material";
|
|
23
23
|
import { useEffect, useState } from "react";
|
|
24
24
|
import { openAiJobs } from "./ai-jobs-store.js";
|
|
25
|
-
import {
|
|
25
|
+
import { AiUpsellButton } from "./ai-upsell-dialog.component.js";
|
|
26
|
+
import { aiJobProblem, useAiJobRun } from "./use-ai-job-run.js";
|
|
26
27
|
/**
|
|
27
28
|
* "Create with AI" on the Automation page's Actions (AGL-2919), beside Add action
|
|
28
29
|
* and Recipes through the `hostAutomations` zone: a description becomes a
|
|
@@ -212,13 +213,21 @@ export function AiAutomationBriefDialog(props) {
|
|
|
212
213
|
});
|
|
213
214
|
}
|
|
214
215
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
|
|
216
|
+
* Drawn at once, from the gates the shell resolved before mounting it (the
|
|
217
|
+
* plan, `ai.generate`, the site's AI switch and the release flag); nothing is
|
|
218
|
+
* asked of a server to draw it (AGL-3601). The start door decides when the
|
|
219
|
+
* description is sent, and says why in the dialog if it refuses. On a plan
|
|
220
|
+
* that could buy the AI add-on, the shell mounts it with `entitled={false}`
|
|
221
|
+
* and the same button opens the add-on's dialog.
|
|
222
|
+
*/ export function AiDescribeAutomationButton({ hostId, orgId, openAction, entitled, upgrade }) {
|
|
218
223
|
const { data: user } = useUser();
|
|
219
|
-
const verdict = useAiJobsVerdict(user, orgId);
|
|
220
224
|
const [open, setOpen] = useState(false);
|
|
221
|
-
if (
|
|
225
|
+
if (entitled === false) {
|
|
226
|
+
return upgrade ? /*#__PURE__*/ _jsx(AiUpsellButton, {
|
|
227
|
+
kind: "workflow",
|
|
228
|
+
upgrade: upgrade
|
|
229
|
+
}) : null;
|
|
230
|
+
}
|
|
222
231
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
223
232
|
children: [
|
|
224
233
|
/*#__PURE__*/ _jsx(Button, {
|