@aglyn/plugins-ai 1.0.0-beta.261 → 1.0.0-beta.262
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-brief-dialog.component.d.ts +21 -58
- package/src/lib/components/ai-brief-dialog.component.js +8 -132
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +2 -35
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +2 -8
- package/src/lib/components/ai-job-plan.component.js +13 -74
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-template-collection-field.component.d.ts +2 -12
- package/src/lib/components/ai-template-collection-field.component.js +13 -10
- package/src/lib/components/ai-template-collection-field.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +3 -25
- package/src/lib/components/ai-upsell-dialog.component.js +9 -66
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -8
- package/src/lib/components/use-ai-job-run.js +4 -15
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/insights/ai-figure-readers.js +15 -5
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/insights/ai-figure-summaries.d.ts +27 -0
- package/src/lib/insights/ai-figure-summaries.js +79 -0
- package/src/lib/insights/ai-figure-summaries.js.map +1 -0
- package/src/lib/insights/ai-insight-readers.d.ts +2 -1
- package/src/lib/insights/ai-insight-readers.js +11 -26
- package/src/lib/insights/ai-insight-readers.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.js +11 -1
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/model/ai-brief-copy.d.ts +85 -0
- package/src/lib/model/ai-brief-copy.js +152 -0
- package/src/lib/model/ai-brief-copy.js.map +1 -0
- package/src/lib/model/ai-create-view.d.ts +200 -0
- package/src/lib/model/ai-create-view.js +263 -0
- package/src/lib/model/ai-create-view.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +1 -1
- package/src/lib/model/ai-insight.js +4 -39
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +24 -0
- package/src/lib/model/ai-job-activity.js +52 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +46 -0
- package/src/lib/model/ai-job-plan-lines.js +70 -0
- package/src/lib/model/ai-job-plan-lines.js.map +1 -0
- package/src/lib/model/ai-template-collections.d.ts +28 -0
- package/src/lib/model/ai-template-collections.js +28 -0
- package/src/lib/model/ai-template-collections.js.map +1 -0
- package/src/lib/model/ai-upsell-copy.d.ts +41 -0
- package/src/lib/model/ai-upsell-copy.js +85 -0
- package/src/lib/model/ai-upsell-copy.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.js +16 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-eval-memory-firestore.js +2 -0
- package/src/lib/runtime/ai-eval-memory-firestore.js.map +1 -1
- package/src/lib/server/ai-create-route.d.ts +32 -0
- package/src/lib/server/ai-create-route.js +295 -0
- package/src/lib/server/ai-create-route.js.map +1 -0
- package/src/lib/server.js +14 -0
- package/src/lib/server.js.map +1 -1
|
@@ -15,13 +15,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
|
-
*/ import {
|
|
18
|
+
*/ import { aiJobPlanHeading, aiJobPlanLines, aiPlanEmbedLine } from "../model/ai-job-plan-lines.js";
|
|
19
19
|
import { aiBuildFreePageCapNote, aiBuildSmaller, aiJobCreditRange } from "../model/ai-build-job.js";
|
|
20
20
|
import { aiCreditRangeText, aiCreditsPromptFor } from "../model/ai-credit-estimate.js";
|
|
21
21
|
import { aiFreeCreditsResetLabel } from "../model/ai-site-job.js";
|
|
22
22
|
import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
|
|
23
|
-
import { aiBuildOpNoun } from "../model/ai-build-progress.js";
|
|
24
|
-
import { aiBuildStorePagesAdded } from "../model/ai-build-store-pages.js";
|
|
25
23
|
import { AI_PAGE_BUILDING_JOB_KINDS, AI_SITE_BUILD_IN_FLIGHT_COPY, aiJobConfirmingOwnPlan, aiSiteBuildInFlight } from "../model/ai-job-activity.js";
|
|
26
24
|
import { aiSiteStarterFallbackOffered } from "../model/ai-job-failure-copy.js";
|
|
27
25
|
import { useAiJobsInFlightKnown } from "./ai-jobs-store.js";
|
|
@@ -62,19 +60,11 @@ import { useState } from "react";
|
|
|
62
60
|
}
|
|
63
61
|
return lines;
|
|
64
62
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* platform's sentence, never the model's: the player is framed lazily, so its
|
|
69
|
-
* host's code loads when a visitor reaches it, whether or not they press play.
|
|
70
|
-
*/ export function aiPlanEmbedLine(embed, named) {
|
|
71
|
-
const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host];
|
|
72
|
-
const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where;
|
|
73
|
-
const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste';
|
|
74
|
-
return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`;
|
|
75
|
-
}
|
|
63
|
+
// A plan's lines are the model's, which the native apps' follow view sends
|
|
64
|
+
// too (AGL-3854); re-exported for this card's existing readers.
|
|
65
|
+
export { aiPlanEmbedLine };
|
|
76
66
|
export function AiJobPlan({ job, onResume, busy = false, staff = false, user, orgSlug }) {
|
|
77
|
-
var _ref
|
|
67
|
+
var _ref;
|
|
78
68
|
var _job_review;
|
|
79
69
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
80
70
|
// Unticked until the person ticks it: a build publishes only when its
|
|
@@ -92,10 +82,6 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user, or
|
|
|
92
82
|
const { plan, review } = job;
|
|
93
83
|
if (!plan && !review) return null;
|
|
94
84
|
const details = staff ? aiJobReviewDetails(review) : [];
|
|
95
|
-
/** A reference as a person reads it: the record's name, or what the plan creates. */ const named = (ref)=>{
|
|
96
|
-
var _ref;
|
|
97
|
-
return !ref ? '' : isAiPlanNewRef(ref) ? ref.slice('new:'.length) : (_ref = plan == null ? void 0 : plan.labels[ref]) != null ? _ref : ref;
|
|
98
|
-
};
|
|
99
85
|
// A plan the machine is confirming itself is not one to confirm (AGL-3596).
|
|
100
86
|
const waiting = job.status === 'needs_review' && review !== null && !aiJobConfirmingOwnPlan(job);
|
|
101
87
|
// The guard rail (AGL-2911): what the plan is estimated to cost is read
|
|
@@ -127,63 +113,16 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user, or
|
|
|
127
113
|
/*#__PURE__*/ _jsx(Typography, {
|
|
128
114
|
variant: "caption",
|
|
129
115
|
color: "text.secondary",
|
|
130
|
-
children: plan
|
|
116
|
+
children: aiJobPlanHeading(plan)
|
|
131
117
|
}),
|
|
132
|
-
plan.
|
|
133
|
-
variant: "body2",
|
|
134
|
-
role: "listitem",
|
|
135
|
-
children: [
|
|
136
|
-
"Reuses the ",
|
|
137
|
-
entry.kind === 'screen' ? 'page' : entry.kind,
|
|
138
|
-
" ",
|
|
139
|
-
named(entry.id),
|
|
140
|
-
" — ",
|
|
141
|
-
entry.purpose
|
|
142
|
-
]
|
|
143
|
-
}, `reuse-${index}`)),
|
|
144
|
-
plan.create.map((entry, index)=>/*#__PURE__*/ _jsxs(Typography, {
|
|
145
|
-
variant: "body2",
|
|
146
|
-
role: "listitem",
|
|
147
|
-
children: [
|
|
148
|
-
"Creates the ",
|
|
149
|
-
AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun,
|
|
150
|
-
" ",
|
|
151
|
-
entry.name,
|
|
152
|
-
entry.duplicateOf ? `, from a copy of ${named(entry.duplicateOf)}` : '',
|
|
153
|
-
' ',
|
|
154
|
-
"— ",
|
|
155
|
-
entry.why
|
|
156
|
-
]
|
|
157
|
-
}, `create-${index}`)),
|
|
158
|
-
plan.screens.map((screen, index)=>/*#__PURE__*/ _jsxs(Typography, {
|
|
159
|
-
variant: "body2",
|
|
160
|
-
role: "listitem",
|
|
161
|
-
children: [
|
|
162
|
-
screen.record ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…` : `Builds the page ${screen.title} at ${screen.slug}`,
|
|
163
|
-
screen.duplicateOf ? `, from a copy of ${named(screen.duplicateOf)}` : '',
|
|
164
|
-
screen.layout ? ` in ${named(screen.layout)}` : '',
|
|
165
|
-
screen.sections.length ? `: ${screen.sections.map((section)=>section.name).join(', ')}` : ''
|
|
166
|
-
]
|
|
167
|
-
}, `screen-${index}`)),
|
|
168
|
-
((_plan_items = plan.items) != null ? _plan_items : []).map((item)=>/*#__PURE__*/ _jsxs(Typography, {
|
|
169
|
-
variant: "body2",
|
|
170
|
-
role: "listitem",
|
|
171
|
-
children: [
|
|
172
|
-
`Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}`,
|
|
173
|
-
item.why ? ` — ${item.why}` : ''
|
|
174
|
-
]
|
|
175
|
-
}, `item-${item.slot}`)),
|
|
176
|
-
((_plan_embeds = plan.embeds) != null ? _plan_embeds : []).map((embed, index)=>/*#__PURE__*/ _jsx(Typography, {
|
|
177
|
-
variant: "body2",
|
|
178
|
-
role: "listitem",
|
|
179
|
-
children: aiPlanEmbedLine(embed, named)
|
|
180
|
-
}, `embed-${index}`)),
|
|
181
|
-
aiBuildStorePagesAdded(plan).map((page)=>/*#__PURE__*/ _jsx(Typography, {
|
|
118
|
+
aiJobPlanLines(plan).map((line)=>/*#__PURE__*/ _jsx(Typography, _extends({
|
|
182
119
|
variant: "body2",
|
|
183
|
-
role: "listitem"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
120
|
+
role: "listitem"
|
|
121
|
+
}, line.platformAdded ? {
|
|
122
|
+
'data-platform-added': 'true'
|
|
123
|
+
} : {}, {
|
|
124
|
+
children: line.text
|
|
125
|
+
}), line.key))
|
|
187
126
|
]
|
|
188
127
|
}),
|
|
189
128
|
staff && (review == null ? void 0 : review.detail) && /*#__PURE__*/ _jsx(Typography, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.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\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiBuildFreePageCapNote, aiBuildSmaller, aiJobCreditRange } from '../model/ai-build-job'\nimport { aiCreditRangeText, aiCreditsPromptFor } from '../model/ai-credit-estimate'\nimport { aiFreeCreditsResetLabel } from '../model/ai-site-job'\nimport { AiCreditsPromptNotice } from './ai-credits-prompt.component'\nimport type { AiJobResumeOptions } from './ai-job-requests'\nimport { aiBuildOpNoun } from '../model/ai-build-progress'\nimport { aiBuildStorePagesAdded } from '../model/ai-build-store-pages'\nimport {\n AI_PAGE_BUILDING_JOB_KINDS,\n AI_SITE_BUILD_IN_FLIGHT_COPY,\n aiJobConfirmingOwnPlan,\n aiSiteBuildInFlight,\n} from '../model/ai-job-activity'\nimport { aiSiteStarterFallbackOffered } from '../model/ai-job-failure-copy'\nimport { useAiJobsInFlightKnown } from './ai-jobs-store'\nimport { AiSiteStarterFallback } from './ai-site-starter-fallback.component'\nimport { Alert, Box, Button, Checkbox, Collapse, FormControlLabel, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer\n * and the dialog that started the job (AGL-3593): what the job reuses from\n * the site, what it creates and why, the screens it builds — and, while the\n * job waits for a person, the one button that confirms the plan or tries a\n * refused step again. Both send the same request (`resumeAiJobRequest`);\n * this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /**\n * Confirms the plan, or tries the refused step again. A build whose request\n * asked to publish confirms with `publish` when its box is ticked (AGL-3616).\n */\n onResume: (job: AiJobSummary, options?: AiJobResumeOptions) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n /**\n * The signed-in user, for \"Use the starter site instead\" on a guided start\n * that did not work out (AGL-3594); the action is drawn only when given.\n */\n user?: Parameters<typeof AiSiteStarterFallback>[0]['user']\n /** The workspace's path slug, for the Upgrade a Free build past what is left offers (AGL-3722). */\n orgSlug?: string | null\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n user,\n orgSlug,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n // Unticked until the person ticks it: a build publishes only when its\n // request asked AND they confirm it here (AGL-3616).\n const [publish, setPublish] = useState(false)\n // Confirming builds pages while another job is building on this site\n // (AGL-3765), from the list the indicator and the panel already hold: said\n // beside Confirm, which still confirms.\n const confirmsPages =\n job.status === 'needs_review' &&\n job.review?.reason === 'plan' &&\n !aiJobConfirmingOwnPlan(job) &&\n AI_PAGE_BUILDING_JOB_KINDS.includes(job.kind)\n const inFlight = useAiJobsInFlightKnown(user, job.orgId, confirmsPages)\n const building = confirmsPages ? aiSiteBuildInFlight(inFlight, { hostId: job.hostId, exceptJobId: job.id }) : null\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n // A plan the machine is confirming itself is not one to confirm (AGL-3596).\n const waiting = job.status === 'needs_review' && review !== null && !aiJobConfirmingOwnPlan(job)\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one: what it is likely to cost — what builds like it measured\n // — and its ceiling, every pass at its reserve (AGL-3722). A page job\n // counts the creations it builds before its page (AGL-3031).\n const range = plan && waiting && review.reason === 'plan' ? aiJobCreditRange(job.kind, plan) : null\n // A Free build past what is left asks first (AGL-3722): build what fits,\n // the home page first, or upgrade — never a refusal with no way on.\n const freeCredits = review?.freeCredits ?? null\n const prompt =\n plan && range && job.kind === 'build' ? aiCreditsPromptFor(range, freeCredits, aiBuildSmaller(plan)) : null\n // A Free build at its page cap says so (AGL-3722): pages past it were left for a later request.\n const pageCapNote =\n plan && waiting && review.reason === 'plan' && job.kind === 'build'\n ? aiBuildFreePageCapNote(plan, Boolean(freeCredits))\n : null\n const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan'\n const publishing = offersPublish && publish ? { publish: true } : {}\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind === 'screen' ? 'page' : entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n {screen.record\n ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…`\n : `Builds the page ${screen.title} at ${screen.slug}`}\n {screen.duplicateOf\n ? `, from a copy of ${named(screen.duplicateOf)}`\n : ''}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n {(plan.items ?? []).map((item) => (\n <Typography key={`item-${item.slot}`} variant=\"body2\" role=\"listitem\">\n {`Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}`}\n {item.why ? ` — ${item.why}` : ''}\n </Typography>\n ))}\n {(plan.embeds ?? []).map((embed, index) => (\n <Typography key={`embed-${index}`} variant=\"body2\" role=\"listitem\">\n {aiPlanEmbedLine(embed, named)}\n </Typography>\n ))}\n {/* A store's own pages (AGL-3676): added by the platform, written by code, no credits. */}\n {aiBuildStorePagesAdded(plan).map((page) => (\n <Typography key={`store-${page.key}`} variant=\"body2\" role=\"listitem\" data-platform-added=\"true\">\n {`Adds the page ${page.title} at ${page.href} — added for your store automatically, uses no AI credits`}\n </Typography>\n ))}\n </Stack>\n )}\n {/*\n A review written for the member (AGL-3594) carries the checks' own\n sentence as `detail`: its findings are then staff reading too, and the\n member reads the job's one sentence alone.\n */}\n {staff && review?.detail && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {review.detail}\n </Typography>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (staff || !review.detail) && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {range && range.ceiling > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: ${aiCreditRangeText(range)}. What it costs is what its steps spend.`}\n {freeCredits && !prompt\n ? ` You have ${freeCredits.left.toLocaleString('en-US')} of your free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}.`\n : ''}\n </Typography>\n )}\n {pageCapNote && (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', mt: 0.5 }}>\n {pageCapNote}\n </Typography>\n )}\n {waiting && review.retryRefusal && review.reason !== 'plan' && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 1 }}>\n {review.retryRefusal}\n </Typography>\n )}\n {offersPublish && (\n <FormControlLabel\n sx={{ display: 'flex', mt: 1 }}\n control={<Checkbox size=\"small\" checked={publish} onChange={(event) => setPublish(event.target.checked)} />}\n label={<Typography variant=\"body2\">{'Publish the new pages when they are built'}</Typography>}\n />\n )}\n {waiting && prompt && (\n <AiCreditsPromptNotice\n prompt={prompt}\n noun=\"build\"\n orgSlug={orgSlug}\n busy={busy}\n onBuildWhatFits={() => onResume(job, { ...publishing, creditsConfirmed: true })}\n onSmaller={() => onResume(job, { ...publishing, creditsConfirmed: true, reduce: 'first-page' })}\n />\n )}\n {building && (\n <Alert severity=\"info\" sx={{ mt: 1 }}>\n {AI_SITE_BUILD_IN_FLIGHT_COPY}\n </Alert>\n )}\n {waiting && !prompt && (\n <Button\n size=\"small\"\n variant=\"contained\"\n // Disabled with its reason above when the Free allowance left\n // cannot pay for another try (AGL-3594).\n disabled={busy || (review.reason !== 'plan' && Boolean(review.retryRefusal))}\n onClick={() => onResume(job, offersPublish && publish ? publishing : undefined)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n {/* A guided start that did not work out can take the starter instead (AGL-3594). */}\n {waiting && user && aiSiteStarterFallbackOffered(job) && <AiSiteStarterFallback job={job} user={user} />}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiBuildFreePageCapNote","aiBuildSmaller","aiJobCreditRange","aiCreditRangeText","aiCreditsPromptFor","aiFreeCreditsResetLabel","AiCreditsPromptNotice","aiBuildOpNoun","aiBuildStorePagesAdded","AI_PAGE_BUILDING_JOB_KINDS","AI_SITE_BUILD_IN_FLIGHT_COPY","aiJobConfirmingOwnPlan","aiSiteBuildInFlight","aiSiteStarterFallbackOffered","useAiJobsInFlightKnown","AiSiteStarterFallback","Alert","Box","Button","Checkbox","Collapse","FormControlLabel","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","aiPlanEmbedLine","embed","named","host","link","url","asked","AiJobPlan","job","onResume","busy","staff","user","orgSlug","plan","detailsOpen","setDetailsOpen","publish","setPublish","confirmsPages","status","includes","kind","inFlight","orgId","building","hostId","exceptJobId","details","ref","slice","labels","waiting","range","freeCredits","prompt","pageCapNote","Boolean","offersPublish","publishAsked","publishing","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","record","title","dataset","base","slug","layout","sections","section","items","item","op","toLowerCase","slot","embeds","page","data-platform-added","href","key","detail","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","ceiling","display","left","toLocaleString","resetsOn","retryRefusal","control","checked","onChange","event","target","label","onBuildWhatFits","creditsConfirmed","onSmaller","reduce","severity","disabled","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.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\nimport { aiJobPlanHeading, aiJobPlanLines, aiPlanEmbedLine } from '../model/ai-job-plan-lines'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiBuildFreePageCapNote, aiBuildSmaller, aiJobCreditRange } from '../model/ai-build-job'\nimport { aiCreditRangeText, aiCreditsPromptFor } from '../model/ai-credit-estimate'\nimport { aiFreeCreditsResetLabel } from '../model/ai-site-job'\nimport { AiCreditsPromptNotice } from './ai-credits-prompt.component'\nimport type { AiJobResumeOptions } from './ai-job-requests'\nimport {\n AI_PAGE_BUILDING_JOB_KINDS,\n AI_SITE_BUILD_IN_FLIGHT_COPY,\n aiJobConfirmingOwnPlan,\n aiSiteBuildInFlight,\n} from '../model/ai-job-activity'\nimport { aiSiteStarterFallbackOffered } from '../model/ai-job-failure-copy'\nimport { useAiJobsInFlightKnown } from './ai-jobs-store'\nimport { AiSiteStarterFallback } from './ai-site-starter-fallback.component'\nimport { Alert, Box, Button, Checkbox, Collapse, FormControlLabel, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer\n * and the dialog that started the job (AGL-3593): what the job reuses from\n * the site, what it creates and why, the screens it builds — and, while the\n * job waits for a person, the one button that confirms the plan or tries a\n * refused step again. Both send the same request (`resumeAiJobRequest`);\n * this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /**\n * Confirms the plan, or tries the refused step again. A build whose request\n * asked to publish confirms with `publish` when its box is ticked (AGL-3616).\n */\n onResume: (job: AiJobSummary, options?: AiJobResumeOptions) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n /**\n * The signed-in user, for \"Use the starter site instead\" on a guided start\n * that did not work out (AGL-3594); the action is drawn only when given.\n */\n user?: Parameters<typeof AiSiteStarterFallback>[0]['user']\n /** The workspace's path slug, for the Upgrade a Free build past what is left offers (AGL-3722). */\n orgSlug?: string | null\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\n// A plan's lines are the model's, which the native apps' follow view sends\n// too (AGL-3854); re-exported for this card's existing readers.\nexport { aiPlanEmbedLine }\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n user,\n orgSlug,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n // Unticked until the person ticks it: a build publishes only when its\n // request asked AND they confirm it here (AGL-3616).\n const [publish, setPublish] = useState(false)\n // Confirming builds pages while another job is building on this site\n // (AGL-3765), from the list the indicator and the panel already hold: said\n // beside Confirm, which still confirms.\n const confirmsPages =\n job.status === 'needs_review' &&\n job.review?.reason === 'plan' &&\n !aiJobConfirmingOwnPlan(job) &&\n AI_PAGE_BUILDING_JOB_KINDS.includes(job.kind)\n const inFlight = useAiJobsInFlightKnown(user, job.orgId, confirmsPages)\n const building = confirmsPages ? aiSiteBuildInFlight(inFlight, { hostId: job.hostId, exceptJobId: job.id }) : null\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n // A plan the machine is confirming itself is not one to confirm (AGL-3596).\n const waiting = job.status === 'needs_review' && review !== null && !aiJobConfirmingOwnPlan(job)\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one: what it is likely to cost — what builds like it measured\n // — and its ceiling, every pass at its reserve (AGL-3722). A page job\n // counts the creations it builds before its page (AGL-3031).\n const range = plan && waiting && review.reason === 'plan' ? aiJobCreditRange(job.kind, plan) : null\n // A Free build past what is left asks first (AGL-3722): build what fits,\n // the home page first, or upgrade — never a refusal with no way on.\n const freeCredits = review?.freeCredits ?? null\n const prompt =\n plan && range && job.kind === 'build' ? aiCreditsPromptFor(range, freeCredits, aiBuildSmaller(plan)) : null\n // A Free build at its page cap says so (AGL-3722): pages past it were left for a later request.\n const pageCapNote =\n plan && waiting && review.reason === 'plan' && job.kind === 'build'\n ? aiBuildFreePageCapNote(plan, Boolean(freeCredits))\n : null\n const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan'\n const publishing = offersPublish && publish ? { publish: true } : {}\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {aiJobPlanHeading(plan)}\n </Typography>\n {aiJobPlanLines(plan).map((line) => (\n <Typography\n key={line.key}\n variant=\"body2\"\n role=\"listitem\"\n {...(line.platformAdded ? { 'data-platform-added': 'true' } : {})}\n >\n {line.text}\n </Typography>\n ))}\n </Stack>\n )}\n {/*\n A review written for the member (AGL-3594) carries the checks' own\n sentence as `detail`: its findings are then staff reading too, and the\n member reads the job's one sentence alone.\n */}\n {staff && review?.detail && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {review.detail}\n </Typography>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (staff || !review.detail) && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {range && range.ceiling > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: ${aiCreditRangeText(range)}. What it costs is what its steps spend.`}\n {freeCredits && !prompt\n ? ` You have ${freeCredits.left.toLocaleString('en-US')} of your free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}.`\n : ''}\n </Typography>\n )}\n {pageCapNote && (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', mt: 0.5 }}>\n {pageCapNote}\n </Typography>\n )}\n {waiting && review.retryRefusal && review.reason !== 'plan' && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 1 }}>\n {review.retryRefusal}\n </Typography>\n )}\n {offersPublish && (\n <FormControlLabel\n sx={{ display: 'flex', mt: 1 }}\n control={<Checkbox size=\"small\" checked={publish} onChange={(event) => setPublish(event.target.checked)} />}\n label={<Typography variant=\"body2\">{'Publish the new pages when they are built'}</Typography>}\n />\n )}\n {waiting && prompt && (\n <AiCreditsPromptNotice\n prompt={prompt}\n noun=\"build\"\n orgSlug={orgSlug}\n busy={busy}\n onBuildWhatFits={() => onResume(job, { ...publishing, creditsConfirmed: true })}\n onSmaller={() => onResume(job, { ...publishing, creditsConfirmed: true, reduce: 'first-page' })}\n />\n )}\n {building && (\n <Alert severity=\"info\" sx={{ mt: 1 }}>\n {AI_SITE_BUILD_IN_FLIGHT_COPY}\n </Alert>\n )}\n {waiting && !prompt && (\n <Button\n size=\"small\"\n variant=\"contained\"\n // Disabled with its reason above when the Free allowance left\n // cannot pay for another try (AGL-3594).\n disabled={busy || (review.reason !== 'plan' && Boolean(review.retryRefusal))}\n onClick={() => onResume(job, offersPublish && publish ? publishing : undefined)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n {/* A guided start that did not work out can take the starter instead (AGL-3594). */}\n {waiting && user && aiSiteStarterFallbackOffered(job) && <AiSiteStarterFallback job={job} user={user} />}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["aiJobPlanHeading","aiJobPlanLines","aiPlanEmbedLine","aiBuildFreePageCapNote","aiBuildSmaller","aiJobCreditRange","aiCreditRangeText","aiCreditsPromptFor","aiFreeCreditsResetLabel","AiCreditsPromptNotice","AI_PAGE_BUILDING_JOB_KINDS","AI_SITE_BUILD_IN_FLIGHT_COPY","aiJobConfirmingOwnPlan","aiSiteBuildInFlight","aiSiteStarterFallbackOffered","useAiJobsInFlightKnown","AiSiteStarterFallback","Alert","Box","Button","Checkbox","Collapse","FormControlLabel","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","AiJobPlan","job","onResume","busy","staff","user","orgSlug","detailsOpen","setDetailsOpen","publish","setPublish","confirmsPages","status","includes","kind","inFlight","orgId","building","hostId","exceptJobId","plan","details","waiting","range","freeCredits","prompt","pageCapNote","Boolean","offersPublish","publishAsked","publishing","mt","spacing","role","aria-label","variant","color","line","platformAdded","text","key","detail","component","my","pl","index","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","ceiling","display","left","toLocaleString","resetsOn","retryRefusal","control","checked","onChange","event","target","label","noun","onBuildWhatFits","creditsConfirmed","onSmaller","reduce","severity","disabled","undefined"],"mappings":"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"}
|
|
@@ -9,18 +9,8 @@
|
|
|
9
9
|
* the platform's per-site cap, which the create route enforces, so it is the
|
|
10
10
|
* whole list rather than a window of it.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* The content collections among a site's collection rows, by name, each
|
|
19
|
-
* named as the template step names it: display name, name, slug, then id.
|
|
20
|
-
*/
|
|
21
|
-
export declare function aiTemplateCollectionOptions(rows: ReadonlyArray<Record<string, unknown> & {
|
|
22
|
-
$id: string;
|
|
23
|
-
}>): AiTemplateCollectionOption[];
|
|
12
|
+
export { aiTemplateCollectionOptions } from '../model/ai-template-collections';
|
|
13
|
+
export type { AiTemplateCollectionOption } from '../model/ai-template-collections';
|
|
24
14
|
export interface AiTemplateCollectionFieldProps {
|
|
25
15
|
hostId: string;
|
|
26
16
|
/** The picked collection's id, or `null` for none yet. */
|
|
@@ -16,21 +16,24 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/ import { COLLECTIONS_MAX_PER_HOST } from "@aglyn/aglyn/app-utils/collection-entries";
|
|
19
|
-
import { hostCollectionKind } from "@aglyn/aglyn/app-utils/collection-kind";
|
|
20
19
|
import { ceilingedWindow, collectionCeiling, useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
21
20
|
import { MenuItem, TextField } from "@mui/material";
|
|
22
21
|
import { collection } from "firebase/firestore";
|
|
23
22
|
import { useMemo } from "react";
|
|
23
|
+
import { aiTemplateCollectionOptions } from "../model/ai-template-collections.js";
|
|
24
24
|
/**
|
|
25
|
-
* The
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
* The collection an entry page template is for (AGL-3043), in the brief
|
|
26
|
+
* dialog once the member picks "Collection entry".
|
|
27
|
+
*
|
|
28
|
+
* It lists the site's CONTENT collections: the template door refuses any
|
|
29
|
+
* other kind, and commerce's catalog collections share the same
|
|
30
|
+
* subcollection. Mounted only while an entry template is being described, so
|
|
31
|
+
* no other brief reads the site's collections at all. The read is bounded by
|
|
32
|
+
* the platform's per-site cap, which the create route enforces, so it is the
|
|
33
|
+
* whole list rather than a window of it.
|
|
34
|
+
*/ // The options are the model's, which the native apps' door reads too
|
|
35
|
+
// (AGL-3854); re-exported for this field's existing readers.
|
|
36
|
+
export { aiTemplateCollectionOptions } from "../model/ai-template-collections.js";
|
|
34
37
|
export function AiTemplateCollectionField({ hostId, value, onChange, disabled }) {
|
|
35
38
|
const firestore = useFirestore();
|
|
36
39
|
const { data, status } = useFirestoreCollection(()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'collections'), COLLECTIONS_MAX_PER_HOST), [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-template-collection-field.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 { COLLECTIONS_MAX_PER_HOST } from '@aglyn/aglyn/app-utils/collection-entries'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-template-collection-field.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 { COLLECTIONS_MAX_PER_HOST } from '@aglyn/aglyn/app-utils/collection-entries'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { MenuItem, TextField } from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { aiTemplateCollectionOptions } from '../model/ai-template-collections'\n\n/**\n * The collection an entry page template is for (AGL-3043), in the brief\n * dialog once the member picks \"Collection entry\".\n *\n * It lists the site's CONTENT collections: the template door refuses any\n * other kind, and commerce's catalog collections share the same\n * subcollection. Mounted only while an entry template is being described, so\n * no other brief reads the site's collections at all. The read is bounded by\n * the platform's per-site cap, which the create route enforces, so it is the\n * whole list rather than a window of it.\n */\n\n// The options are the model's, which the native apps' door reads too\n// (AGL-3854); re-exported for this field's existing readers.\nexport { aiTemplateCollectionOptions } from '../model/ai-template-collections'\nexport type { AiTemplateCollectionOption } from '../model/ai-template-collections'\n\nexport interface AiTemplateCollectionFieldProps {\n hostId: string\n /** The picked collection's id, or `null` for none yet. */\n value: string | null\n onChange: (collectionId: string | null) => void\n disabled?: boolean\n}\n\nexport function AiTemplateCollectionField({\n hostId,\n value,\n onChange,\n disabled,\n}: AiTemplateCollectionFieldProps) {\n const firestore = useFirestore()\n const { data, status } = useFirestoreCollection<Record<string, unknown> & { $id: string }>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'collections'),\n COLLECTIONS_MAX_PER_HOST,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const options = useMemo(\n () => aiTemplateCollectionOptions(ceilingedWindow(data, COLLECTIONS_MAX_PER_HOST).rows),\n [data],\n )\n const listed = status === 'success' && options.length > 0\n const helperText =\n status === 'loading'\n ? 'Loading this site’s collections…'\n : status === 'error'\n ? 'This site’s collections could not be loaded. Close this and try again.'\n : options.length\n ? 'Each page shows one entry of this collection.'\n : 'This site has no content collections yet. Add one in Content first.'\n\n return (\n <TextField\n select\n label=\"Collection\"\n value={listed && options.some((option) => option.id === value) ? value : ''}\n onChange={(event) => onChange(event.target.value || null)}\n helperText={helperText}\n error={status === 'error'}\n disabled={disabled || !listed}\n >\n {options.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n )\n}\n\nexport default AiTemplateCollectionField\n"],"names":["COLLECTIONS_MAX_PER_HOST","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","MenuItem","TextField","collection","useMemo","aiTemplateCollectionOptions","AiTemplateCollectionField","hostId","value","onChange","disabled","firestore","data","status","idField","options","rows","listed","length","helperText","select","label","some","option","id","event","target","error","map","name"],"mappings":"AAAA;;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,wBAAwB,QAAQ,4CAA2C;AACpF,SACEC,eAAe,EACfC,iBAAiB,EACjBC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SAASC,QAAQ,EAAEC,SAAS,QAAQ,gBAAe;AACnD,SAASC,UAAU,QAAQ,qBAAoB;AAC/C,SAASC,OAAO,QAAQ,QAAO;AAC/B,SAASC,2BAA2B,QAAQ,sCAAkC;AAE9E;;;;;;;;;;CAUC,GAED,qEAAqE;AACrE,6DAA6D;AAC7D,SAASA,2BAA2B,QAAQ,sCAAkC;AAW9E,OAAO,SAASC,0BAA0B,EACxCC,MAAM,EACNC,KAAK,EACLC,QAAQ,EACRC,QAAQ,EACuB;IAC/B,MAAMC,YAAYZ;IAClB,MAAM,EAAEa,IAAI,EAAEC,MAAM,EAAE,GAAGb,uBACvB,IACEF,kBACEK,WAAWQ,WAAW,SAASJ,QAAQ,gBACvCX,2BAEJ;QAACe;QAAWJ;KAAO,EACnB;QAAEO,SAAS;IAAM;IAEnB,MAAMC,UAAUX,QACd,IAAMC,4BAA4BR,gBAAgBe,MAAMhB,0BAA0BoB,IAAI,GACtF;QAACJ;KAAK;IAER,MAAMK,SAASJ,WAAW,aAAaE,QAAQG,MAAM,GAAG;IACxD,MAAMC,aACJN,WAAW,YACP,qCACAA,WAAW,UACT,2EACAE,QAAQG,MAAM,GACZ,kDACA;IAEV,qBACE,KAAChB;QACCkB,MAAM;QACNC,OAAM;QACNb,OAAOS,UAAUF,QAAQO,IAAI,CAAC,CAACC,SAAWA,OAAOC,EAAE,KAAKhB,SAASA,QAAQ;QACzEC,UAAU,CAACgB,QAAUhB,SAASgB,MAAMC,MAAM,CAAClB,KAAK,IAAI;QACpDW,YAAYA;QACZQ,OAAOd,WAAW;QAClBH,UAAUA,YAAY,CAACO;kBAEtBF,QAAQa,GAAG,CAAC,CAACL,uBACZ,KAACtB;gBAAyBO,OAAOe,OAAOC,EAAE;0BACvCD,OAAOM,IAAI;eADCN,OAAOC,EAAE;;AAMhC;AAEA,eAAelB,0BAAyB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins';
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
|
-
import type
|
|
3
|
+
import { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer, type AiUpsellKind } from '../model/ai-upsell-copy';
|
|
4
4
|
/**
|
|
5
5
|
* "Create with AI" on a plan that could buy the AI add-on and has not
|
|
6
6
|
* (AGL-3601). The shell mounts the entry with `entitled={false}` only where
|
|
@@ -8,30 +8,8 @@ import type { AiBriefKind } from './ai-brief-dialog.component';
|
|
|
8
8
|
* is the one thing missing; the entry then keeps its place on the page and
|
|
9
9
|
* opens this instead of the brief, so the reader sees where the add-on is used.
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* automation, products, a function or variable, an overlay, and an answer
|
|
14
|
-
* about the figures on a page.
|
|
15
|
-
*/
|
|
16
|
-
export type AiUpsellKind = AiBriefKind | 'workflow' | 'product' | 'logic' | 'overlay' | 'funnel' | 'insight';
|
|
17
|
-
/** What one entry's dialog says. */
|
|
18
|
-
export interface AiUpsellCopy {
|
|
19
|
-
title: string;
|
|
20
|
-
does: string;
|
|
21
|
-
/** The entry's own label, which the button reads and the offer names; "Create with AI" when absent. */
|
|
22
|
-
label?: string;
|
|
23
|
-
}
|
|
24
|
-
/** The label every "Create with AI" entry reads. */
|
|
25
|
-
export declare const AI_UPSELL_CREATE_LABEL = "Create with AI";
|
|
26
|
-
/** What the dialog says one entry would do here. */
|
|
27
|
-
export declare const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>>;
|
|
28
|
-
/** The label an entry's button reads. */
|
|
29
|
-
export declare function aiUpsellLabel(kind: AiUpsellKind): string;
|
|
30
|
-
/**
|
|
31
|
-
* The sentence that says where the feature comes from, and who can add it.
|
|
32
|
-
* `feature` is what the entry is called: "Create with AI" unless it is an ask.
|
|
33
|
-
*/
|
|
34
|
-
export declare function aiUpsellOffer(canManageBilling: boolean, feature?: string): string;
|
|
11
|
+
export { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer };
|
|
12
|
+
export type { AiUpsellCopy, AiUpsellKind } from '../model/ai-upsell-copy';
|
|
35
13
|
export interface AiUpsellDialogProps {
|
|
36
14
|
kind: AiUpsellKind;
|
|
37
15
|
open: boolean;
|
|
@@ -23,73 +23,16 @@ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
|
|
|
23
23
|
import { HelpTip, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
24
24
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material";
|
|
25
25
|
import { useEffect, useState } from "react";
|
|
26
|
-
|
|
27
|
-
/** What the dialog says one entry would do here. */ export const AI_UPSELL_COPY = {
|
|
28
|
-
page: {
|
|
29
|
-
title: 'Create a page with AI',
|
|
30
|
-
does: 'Describe the page you need and AI plans it from your brief, using your theme and ' + 'what the site already has. You confirm the plan, and the page is built as an ' + 'unpublished draft.'
|
|
31
|
-
},
|
|
32
|
-
template: {
|
|
33
|
-
title: 'Create a page template with AI',
|
|
34
|
-
does: 'Describe what each page should show and AI plans the template, filling what ' + 'changes from page to page from each record. You confirm the plan, and the template ' + 'is built as a draft in your library.'
|
|
35
|
-
},
|
|
36
|
-
layout: {
|
|
37
|
-
title: 'Create a layout with AI',
|
|
38
|
-
does: 'Describe what the layout should hold and AI plans it, linking your pages and placing ' + 'your navigation or menu component when the site has one. You confirm the plan, and ' + 'the layout is built as a draft no page uses until you assign it.'
|
|
39
|
-
},
|
|
40
|
-
form: {
|
|
41
|
-
title: 'Create a form with AI',
|
|
42
|
-
does: 'Describe what the form is for and AI plans its fields, its marketing consent and ' + 'where each submission goes. You confirm the plan, and the form is built as a draft.'
|
|
43
|
-
},
|
|
44
|
-
component: {
|
|
45
|
-
title: 'Create a reusable component with AI',
|
|
46
|
-
does: 'Describe what the component should show and AI plans it, making what each page can ' + 'change into its properties. You confirm the plan, and the component is built as a ' + 'draft.'
|
|
47
|
-
},
|
|
48
|
-
workflow: {
|
|
49
|
-
title: 'Create an automation with AI',
|
|
50
|
-
does: 'Describe what should happen, and when, and AI drafts the automation from the ' + 'triggers and steps your plan includes. It is saved switched off for you to review.'
|
|
51
|
-
},
|
|
52
|
-
email: {
|
|
53
|
-
title: 'Create an email with AI',
|
|
54
|
-
does: 'Describe the email you need and AI plans a design from the email blocks, with three ' + 'subject lines and three preheaders to choose between. You confirm the plan, and the ' + 'design is built as a draft that is sent to nobody.'
|
|
55
|
-
},
|
|
56
|
-
campaign: {
|
|
57
|
-
title: 'Create a campaign with AI',
|
|
58
|
-
does: 'Describe what the campaign is for and AI writes its email design 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.'
|
|
59
|
-
},
|
|
60
|
-
product: {
|
|
61
|
-
title: 'Create products with AI',
|
|
62
|
-
does: 'Describe what the store sells and AI proposes up to twelve products. You review them ' + 'before any is created, and each is created as a draft at a suggested price that is not on ' + 'your storefront until you review it and activate it.'
|
|
63
|
-
},
|
|
64
|
-
logic: {
|
|
65
|
-
title: 'Create a function or variable with AI',
|
|
66
|
-
does: 'Describe what it should work out or hold and AI drafts the function or variable. It ' + 'opens unsaved in the editor for you to check and save.'
|
|
67
|
-
},
|
|
68
|
-
overlay: {
|
|
69
|
-
title: 'Create an overlay with AI',
|
|
70
|
-
does: 'Describe the announcement bar or popup you want and AI writes its copy within the ' + 'overlay’s limits. It is saved switched off, for you to check and turn on.'
|
|
71
|
-
},
|
|
72
|
-
funnel: {
|
|
73
|
-
title: 'Create a funnel with AI',
|
|
74
|
-
does: 'Describe the journey you want to measure and AI proposes its steps from the site’s ' + 'real pages, forms and products. It opens in the funnel editor for you to check and save.'
|
|
75
|
-
},
|
|
76
|
-
insight: {
|
|
77
|
-
title: 'Ask AI about these numbers',
|
|
78
|
-
label: 'Ask a question',
|
|
79
|
-
does: 'Ask a question about the figures on this page and AI answers from them, every answer ' + 'traced to the figures it is built from. It reads; it changes nothing.'
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
/** The label an entry's button reads. */ export function aiUpsellLabel(kind) {
|
|
83
|
-
var _AI_UPSELL_COPY_kind_label;
|
|
84
|
-
return (_AI_UPSELL_COPY_kind_label = AI_UPSELL_COPY[kind].label) != null ? _AI_UPSELL_COPY_kind_label : AI_UPSELL_CREATE_LABEL;
|
|
85
|
-
}
|
|
26
|
+
import { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer } from "../model/ai-upsell-copy.js";
|
|
86
27
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
28
|
+
* "Create with AI" on a plan that could buy the AI add-on and has not
|
|
29
|
+
* (AGL-3601). The shell mounts the entry with `entitled={false}` only where
|
|
30
|
+
* the add-on is sold to this workspace, and the jobs route has said the plan
|
|
31
|
+
* is the one thing missing; the entry then keeps its place on the page and
|
|
32
|
+
* opens this instead of the brief, so the reader sees where the add-on is used.
|
|
33
|
+
*/ // The entries' words are the model's, which the native apps' door reads too
|
|
34
|
+
// (AGL-3854); re-exported for this dialog's existing readers.
|
|
35
|
+
export { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer };
|
|
93
36
|
/** The add-on's own section of the Create with AI guide (AGL-3660). */ const aiUpsellHelp = ()=>pluginDocsHelp('aiCreate', {
|
|
94
37
|
anchor: '#without-the-add-on',
|
|
95
38
|
excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-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 { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft at a suggested price that is not on ' +\n 'your storefront until you review it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-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 { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport {\n AI_UPSELL_COPY,\n AI_UPSELL_CREATE_LABEL,\n aiUpsellLabel,\n aiUpsellOffer,\n type AiUpsellKind,\n} from '../model/ai-upsell-copy'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n// The entries' words are the model's, which the native apps' door reads too\n// (AGL-3854); re-exported for this dialog's existing readers.\nexport { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer }\nexport type { AiUpsellCopy, AiUpsellKind } from '../model/ai-upsell-copy'\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_COPY","AI_UPSELL_CREATE_LABEL","aiUpsellLabel","aiUpsellOffer","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","kind","open","onClose","upgrade","copy","canManageBilling","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","title","spacing","variant","does","color","onClick","href","AiUpsellButton","label","setOpen","size","startIcon","path"],"mappings":"AAAA;;;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,UAAU,QAAQ,0CAAyC;AAEpE,SAASC,WAAW,QAAQ,yBAAwB;AACpD,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AACvD,SACEC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,QAAQ,QAAwB,QAAO;AAC3D,SACEC,cAAc,EACdC,sBAAsB,EACtBC,aAAa,EACbC,aAAa,QAER,6BAAyB;AAEhC;;;;;;CAMC,GAED,4EAA4E;AAC5E,8DAA8D;AAC9D,SAASH,cAAc,EAAEC,sBAAsB,EAAEC,aAAa,EAAEC,aAAa,GAAE;AAU/E,qEAAqE,GACrE,MAAMC,eAAe,IACnBhB,eAAe,YAAY;QACzBiB,QAAQ;QACRC,SAAS,CAAC,yBAAyB,EAAErB,cAAc,yGAAyG,CAAC;IAC/J;AAEF,OAAO,SAASsB,eAAe,EAAEC,IAAI,EAAEC,IAAI,EAAEC,OAAO,EAAEC,OAAO,EAAuB;IAClF,MAAMC,OAAOZ,cAAc,CAACQ,KAAK;IACjC,MAAM,EAAEK,gBAAgB,EAAEC,WAAW,EAAE,GAAGH;IAE1C,yEAAyE;IACzEb,UAAU;QACR,IAAIW,MAAMvB,WAAW,mBAAmB;YAAEsB;YAAMO,YAAYF;QAAiB;IAC/E,GAAG;QAACJ;QAAMD;QAAMK;KAAiB;IAEjC,qBACE,MAACrB;QAAOiB,MAAMA;QAAMC,SAASA;QAASM,SAAS;QAACC,UAAS;;0BACvD,MAACtB;gBAAYuB,IAAI;oBAAEC,SAAS;oBAAQC,YAAY;oBAAUC,KAAK;gBAAI;;oBAChET,KAAKU,KAAK;kCACX,KAACjC,sBAAYe;;;0BAEf,KAACV;0BACC,cAAA,MAACE;oBAAM2B,SAAS;;sCACd,KAAC1B;4BAAW2B,SAAQ;sCAASZ,KAAKa,IAAI;;sCACtC,KAAC5B;4BAAW2B,SAAQ;4BAAQE,OAAM;sCAC/BvB,cAAcU,kBAAkBL,SAAS,YAAYI,KAAKU,KAAK,GAAGrB;;;;;0BAIzE,MAACR;;kCACC,KAACF;wBAAOoC,SAASjB;kCAAUG,mBAAmB,YAAY;;oBACzDA,iCACC,KAACtB;wBACCiC,SAAQ;wBACRI,MAAMd;wBACNa,SAAS,IAAMzC,WAAW,qBAAqB;gCAAEsB;4BAAK;kCAErD,CAAC,IAAI,EAAEvB,eAAe;yBAEvB;;;;;AAIZ;AAEA;;;;;CAKC,GACD,OAAO,SAAS4C,eAAe,EAC7BrB,IAAI,EACJG,OAAO,EACPmB,KAAK,EAMN;IACC,MAAM,CAACrB,MAAMsB,QAAQ,GAAGhC,SAAS;IACjC,qBACE;;0BACE,KAACR;gBACCyC,MAAK;gBACLR,SAAQ;gBACRS,yBAAW,KAAC3C;oBAAQ4C,MAAM/C,YAAY+C,IAAI;;gBAC1CP,SAAS,IAAMI,QAAQ;0BAEtBD,gBAAAA,QAAS5B,cAAcM;;0BAE1B,KAACD;gBAAeC,MAAMA;gBAAMC,MAAMA;gBAAMC,SAAS,IAAMqB,QAAQ;gBAAQpB,SAASA;;;;AAGtF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
|
|
2
|
-
import {
|
|
2
|
+
import type { AiJobKind, AiJobSummary } from '../model/ai-jobs.types';
|
|
3
|
+
import { aiJobProblem, aiJobSettled } from '../model/ai-job-activity';
|
|
3
4
|
/**
|
|
4
5
|
* Starting one AI job from a console control, and following it until it
|
|
5
6
|
* settles (AGL-2919): the jobs route's create door, then its events door, the
|
|
@@ -16,13 +17,7 @@ export interface AiJobRequest {
|
|
|
16
17
|
brief: string;
|
|
17
18
|
inputs: Record<string, string>;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
export declare function aiJobSettled(job: AiJobSummary | null): boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Why a settled job has no result to show, in words a person can act on, or
|
|
23
|
-
* `null` for a job that finished.
|
|
24
|
-
*/
|
|
25
|
-
export declare function aiJobProblem(job: AiJobSummary | null, failed: string): string | null;
|
|
20
|
+
export { aiJobProblem, aiJobSettled };
|
|
26
21
|
export interface AiJobRun {
|
|
27
22
|
/** The job as it last arrived; `null` before one is started. */
|
|
28
23
|
job: AiJobSummary | null;
|
|
@@ -17,23 +17,12 @@
|
|
|
17
17
|
*/ import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
|
|
18
18
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
19
19
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
20
|
-
import {
|
|
20
|
+
import { aiJobProblem, aiJobSettled } from "../model/ai-job-activity.js";
|
|
21
21
|
import { followAiJobEvents } from "./ai-job-events.js";
|
|
22
22
|
import { publishAiJob } from "./ai-jobs-store.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Why a settled job has no result to show, in words a person can act on, or
|
|
29
|
-
* `null` for a job that finished.
|
|
30
|
-
*/ export function aiJobProblem(job, failed) {
|
|
31
|
-
var _job_review;
|
|
32
|
-
if (!job || !aiJobSettled(job) || job.status === 'done') return null;
|
|
33
|
-
if (job.status === 'canceled') return 'The job was canceled.';
|
|
34
|
-
if (job.status === 'needs_review') return ((_job_review = job.review) == null ? void 0 : _job_review.message) || job.error || failed;
|
|
35
|
-
return job.error || failed;
|
|
36
|
-
}
|
|
23
|
+
// Whether a job settled, and why it has nothing to show, are the model's
|
|
24
|
+
// (AGL-3854), which the native apps' follow view reads too.
|
|
25
|
+
export { aiJobProblem, aiJobSettled };
|
|
37
26
|
export function useAiJobRun(user, failed) {
|
|
38
27
|
const userRef = useRef(user);
|
|
39
28
|
userRef.current = user;
|