@aglyn/plugins-ai 1.0.0-beta.244 → 1.0.0-beta.247
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-links.js +21 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +3 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.js +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.js +17 -9
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +98 -23
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +216 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +3 -1
- package/src/lib/jobs/ai-job-build-step.js +124 -18
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
- package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
- package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
- package/src/lib/jobs/ai-job-form-dataset.js +28 -10
- package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
- package/src/lib/jobs/ai-job-form-step.js +98 -28
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
- package/src/lib/jobs/ai-job-layout-language.js +29 -5
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +4 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
- package/src/lib/jobs/ai-job-media-step.js +610 -0
- package/src/lib/jobs/ai-job-media-step.js.map +1 -0
- package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
- package/src/lib/jobs/ai-job-navigation-step.js +439 -0
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +4 -1
- package/src/lib/jobs/ai-job-page-language.js +30 -5
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +5 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +14 -3
- package/src/lib/jobs/ai-job-plan-step.js +87 -20
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-products-step.js +2 -2
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +268 -0
- package/src/lib/jobs/ai-job-settings-step.js +690 -0
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
- package/src/lib/jobs/ai-job-site-content.d.ts +22 -8
- package/src/lib/jobs/ai-job-site-content.js +33 -15
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +3 -1
- package/src/lib/jobs/ai-job-site-datasets.js +13 -5
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
- package/src/lib/jobs/ai-job-site-events.js +313 -0
- package/src/lib/jobs/ai-job-site-events.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +12 -4
- package/src/lib/jobs/ai-job-site-step.js +132 -26
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
- package/src/lib/jobs/ai-job-tracking-step.js +501 -0
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +21 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +41 -4
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.d.ts +62 -4
- package/src/lib/jobs/ai-site-publish.js +202 -56
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
- package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
- package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -7
- package/src/lib/layout-language/ai-layout-compiler.js +62 -42
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
- package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
- package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +24 -5
- package/src/lib/layout-language/ai-layout-frame.js +93 -20
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +3 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
- package/src/lib/layout-language/ai-layout-labels.js +162 -0
- package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.js +5 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
- package/src/lib/layout-language/ai-layout-listings.js +66 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-tree.js +17 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
- package/src/lib/model/ai-build-event-page.d.ts +70 -0
- package/src/lib/model/ai-build-event-page.js +145 -0
- package/src/lib/model/ai-build-event-page.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +44 -0
- package/src/lib/model/ai-build-job.js +141 -13
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +39 -3
- package/src/lib/model/ai-build-plan.js +59 -5
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +52 -0
- package/src/lib/model/ai-credit-estimate.js +51 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +6 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +18 -2
- package/src/lib/model/ai-jobs.types.js +6 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-layout-menus.d.ts +104 -0
- package/src/lib/model/ai-layout-menus.js +518 -0
- package/src/lib/model/ai-layout-menus.js.map +1 -0
- package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-products.d.ts +31 -1
- package/src/lib/model/ai-products.js +34 -1
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +17 -0
- package/src/lib/model/ai-site-inventory.js +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +12 -2
- package/src/lib/model/ai-site-job.js +39 -12
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.js +1 -1
- package/src/lib/model/ai-site-store-pages.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +78 -3
- package/src/lib/runtime/ai-doctrine-validators.js +219 -31
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +16 -7
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.d.ts +1 -1
- package/src/lib/runtime/ai-eval-live.js +1 -1
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +3 -1
- package/src/lib/runtime/ai-palette.generated.js +499 -11
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +5 -3
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +4 -2
- package/src/lib/runtime/ai-products-generation.js +12 -6
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/ai-template-examples.js +8 -2
- package/src/lib/runtime/ai-template-examples.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +12 -0
- package/src/lib/runtime/site-inventory.js +29 -1
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server.js +20 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +37 -3
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
- package/src/lib/tools/ai-products-tool.d.ts +3 -1
- package/src/lib/tools/ai-products-tool.js +15 -5
- package/src/lib/tools/ai-products-tool.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-products-proposals.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 { ConsoleProductsHubZoneProps } from './ai-product-zones'\nimport { ListTable, type ListTableProps } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { Alert, Button, Chip, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useMemo, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport {\n aiProductCopyProposals,\n aiProductsBulkCount,\n aiProductsProposalOfKind,\n type AiProductCopyProposal,\n type AiProposedDiscount,\n} from '../model/ai-products'\nimport { aiProductCopyValues } from './ai-product-copy-card.component'\nimport { isAiJobMoving } from './use-ai-products-jobs'\n\n/**\n * The review tables of the products hub's AI card (AGL-2916): what a\n * `products` job proposed, row by row, before anything is written. Every\n * write is one the hub makes when a person asks for it — Apply, Apply to all,\n * Create drafts, Create selected — and the hub passes over what the site\n * already has, so a proposal applied twice creates nothing twice.\n *\n * Each table is one job's whole answer, held in hand and capped by the job\n * (`AI_PRODUCTS_BULK_MAX`, `AI_CATEGORIES_MAX`, `AI_DISCOUNTS_MAX`, a dozen\n * proposed products), so none offers a Filters panel or a search box: a\n * bounded readout is read whole, and a grid that filtered the rows it holds\n * is the pattern every list is leaving (AGL-3321).\n */\n\n/** A column of a review table, in the shared list table's own terms. */\ntype GridColDef = NonNullable<ListTableProps['columns']>[number]\n\nconst clip = (text: string, max: number) => (text.length > max ? `${text.slice(0, max - 1)}…` : text)\n\nconst messageOf = (error: unknown, fallback: string) =>\n error instanceof Error && error.message ? error.message : fallback\n\n/** A discount's offer, in the words the discounts card uses. */\nexport function aiDiscountOffer(discount: AiProposedDiscount): string {\n if (discount.kind === 'percent') return `${discount.valuePct ?? 0}% off`\n if (discount.kind === 'fixed') return `$${((discount.valueCents ?? 0) / 100).toFixed(2)} off`\n return 'Free shipping'\n}\n\nfunction JobState(props: { job: AiJobSummary; doing: string }) {\n const { job } = props\n if (isAiJobMoving(job)) {\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label={props.doing} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.doing}\n </Typography>\n </Stack>\n )\n }\n return job.error ? <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert> : null\n}\n\n/* ------------------------------------------------------------------------ *\n * Copy for many products\n * ------------------------------------------------------------------------ */\n\ntype RowState = 'applying' | 'applied' | { error: string }\n\nexport function AiBulkCopyReview(props: {\n job: AiJobSummary\n applyProductCopy: ConsoleProductsHubZoneProps['applyProductCopy']\n}) {\n const { job, applyProductCopy } = props\n const proposals = aiProductCopyProposals(job)\n const asked = aiProductsBulkCount(job) ?? proposals.length\n const [rows, setRows] = useState<Record<string, RowState>>({})\n const [applyingAll, setApplyingAll] = useState(false)\n\n const ready = (proposal: AiProductCopyProposal) =>\n Boolean(proposal.values && proposal.product.id) && rows[proposal.product.id as string] !== 'applied'\n\n const apply = useCallback(\n async (proposal: AiProductCopyProposal) => {\n const id = proposal.product.id\n if (!id || !proposal.values) return\n setRows((current) => ({ ...current, [id]: 'applying' }))\n try {\n await applyProductCopy(id, aiProductCopyValues(proposal))\n setRows((current) => ({ ...current, [id]: 'applied' }))\n } catch (error) {\n setRows((current) => ({ ...current, [id]: { error: messageOf(error, 'The copy could not be saved.') } }))\n }\n },\n [applyProductCopy],\n )\n\n /** Applies each proposal in turn, so one refusal names its product and the rest still land. */\n const applyAll = useCallback(\n async (targets: readonly AiProductCopyProposal[]) => {\n setApplyingAll(true)\n try {\n for (const proposal of targets) await apply(proposal)\n } finally {\n setApplyingAll(false)\n }\n },\n [apply],\n )\n\n const columns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Product', flex: 1, minWidth: 160 },\n { field: 'description', headerName: 'Proposed description', flex: 2, minWidth: 220 },\n { field: 'seoTitle', headerName: 'Search title', flex: 1, minWidth: 160 },\n {\n field: 'actions',\n headerName: '',\n width: 170,\n sortable: false,\n renderCell: ({ row }: { row: { proposal: AiProductCopyProposal } }) => {\n const { proposal } = row\n if (proposal.skipped) return <Chip size=\"small\" variant=\"outlined\" label=\"Skipped\" title={proposal.skipped} />\n const state = proposal.product.id ? rows[proposal.product.id] : undefined\n if (state === 'applied') return <Chip size=\"small\" color=\"success\" variant=\"outlined\" label=\"Applied\" />\n if (typeof state === 'object') return <Chip size=\"small\" color=\"error\" variant=\"outlined\" label=\"Not saved\" title={state.error} />\n return (\n <Button size=\"small\" disabled={state === 'applying' || applyingAll} onClick={() => void apply(proposal)}>\n {'Apply'}\n </Button>\n )\n },\n },\n ],\n [rows, applyingAll, apply],\n )\n\n const tableRows = proposals.map((proposal) => ({\n $id: proposal.product.id ?? proposal.product.name,\n name: proposal.product.name,\n description: proposal.values ? clip(proposal.values.description, 140) : (proposal.skipped ?? ''),\n seoTitle: proposal.values?.seoTitle ?? '',\n proposal,\n }))\n const readyCount = proposals.filter(ready).length\n const errors = Object.values(rows).filter((state): state is { error: string } => typeof state === 'object')\n const gaps = proposals.filter((proposal) => proposal.gaps.length).length\n\n return (\n <Stack spacing={1} aria-label=\"Proposed product copy\">\n <Typography variant=\"subtitle2\">{`Product copy · ${proposals.length} of ${asked}`}</Typography>\n <JobState job={job} doing={`Writing copy: ${proposals.length} of ${asked}`} />\n {tableRows.length ? <ListTable rows={tableRows} columns={columns} hideFooter disableColumnFilter quickFilter={false} /> : null}\n {gaps ? (\n <Alert severity=\"warning\">\n {`${gaps} ${gaps === 1 ? 'description marks' : 'descriptions mark'} facts in [brackets] to fill in before you publish.`}\n </Alert>\n ) : null}\n {errors.length ? <Alert severity=\"error\">{errors[0].error}</Alert> : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={!readyCount || applyingAll}\n onClick={() => void applyAll(proposals.filter(ready))}\n >\n {readyCount ? `Apply to all ${readyCount}` : 'Apply to all'}\n </Button>\n </Stack>\n )\n}\n\n/* ------------------------------------------------------------------------ *\n * A store's first products\n * ------------------------------------------------------------------------ */\n\nexport function AiCatalogReview(props: {\n job: AiJobSummary\n heldNames: ReadonlySet<string>\n createProductDrafts: ConsoleProductsHubZoneProps['createProductDrafts']\n}) {\n const { job, heldNames, createProductDrafts } = props\n const proposal = aiProductsProposalOfKind(job, 'catalog')\n const products = useMemo(() => proposal?.products ?? [], [proposal])\n const held = (name: string) => heldNames.has(name.replace(/\\s+/g, ' ').trim().toLowerCase())\n const [selected, setSelected] = useState<string[] | null>(null)\n const [busy, setBusy] = useState(false)\n const [result, setResult] = useState<{ severity: 'success' | 'error'; text: string } | null>(null)\n\n const rows = products.map((product, index) => ({\n $id: `p${index}`,\n name: product.name,\n type: product.type,\n options: product.options.map((option) => `${option.name}: ${option.values.join(', ')}`).join(' · ') || '—',\n photo: product.photo,\n inCatalog: held(product.name),\n product,\n }))\n const chosen = selected ?? rows.filter((row) => !row.inCatalog).map((row) => row.$id)\n\n const columns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Product', flex: 1, minWidth: 180 },\n { field: 'options', headerName: 'Options', flex: 1, minWidth: 160 },\n {\n field: 'price',\n headerName: 'Price',\n width: 130,\n sortable: false,\n // Never proposed: prices are the merchant's to set.\n renderCell: () => <Chip size=\"small\" color=\"warning\" variant=\"outlined\" label=\"Set a price\" />,\n },\n {\n field: 'photo',\n headerName: 'Photo',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }: { row: { photo: string; inCatalog: boolean } }) => (\n <Stack spacing={0.25} sx={{ py: 0.5 }}>\n <Chip size=\"small\" color=\"warning\" variant=\"outlined\" label=\"Needs a photo\" sx={{ alignSelf: 'flex-start' }} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ whiteSpace: 'normal' }}>\n {row.photo}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'inCatalog',\n headerName: '',\n width: 150,\n sortable: false,\n renderCell: ({ row }: { row: { inCatalog: boolean } }) =>\n row.inCatalog ? <Chip size=\"small\" variant=\"outlined\" label=\"In your catalog\" /> : null,\n },\n ],\n [],\n )\n\n const create = useCallback(async () => {\n const picks = rows.filter((row) => chosen.includes(row.$id)).map((row) => row.product)\n if (!picks.length) return\n setBusy(true)\n setResult(null)\n try {\n const created = await createProductDrafts(\n picks.map((product) => ({\n name: product.name,\n type: product.type,\n description: product.description,\n tags: product.tags,\n options: product.options,\n seoTitle: product.seoTitle,\n seoDescription: product.seoDescription,\n })),\n )\n const passed = picks.length - created.length\n setResult({\n severity: 'success',\n text:\n `Created ${created.length} draft ${created.length === 1 ? 'product' : 'products'}. ` +\n 'Each needs a price and a photo before you activate it.' +\n (passed ? ` ${passed} already in your catalog ${passed === 1 ? 'was' : 'were'} left as they are.` : ''),\n })\n setSelected([])\n } catch (error) {\n setResult({ severity: 'error', text: messageOf(error, 'The draft products could not be created.') })\n } finally {\n setBusy(false)\n }\n }, [rows, chosen, createProductDrafts])\n\n return (\n <Stack spacing={1} aria-label=\"Proposed products\">\n <Typography variant=\"subtitle2\">{'Proposed products'}</Typography>\n <JobState job={job} doing=\"Proposing products from your brief\" />\n {rows.length ? (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Each is created as a draft with its price left empty and no photo. Set both in the product editor, ' +\n 'then activate it.'}\n </Typography>\n <ListTable\n rows={rows}\n columns={columns}\n hideFooter\n disableColumnFilter\n quickFilter={false}\n getRowHeight={() => 'auto'}\n selectable={{ selected: chosen, onChange: setSelected }}\n />\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={busy || !chosen.length}\n onClick={() => void create()}\n >\n {`Create ${chosen.length} ${chosen.length === 1 ? 'draft' : 'drafts'}`}\n </Button>\n </>\n ) : null}\n {result ? <Alert severity={result.severity}>{result.text}</Alert> : null}\n </Stack>\n )\n}\n\n/* ------------------------------------------------------------------------ *\n * Categories and discounts\n * ------------------------------------------------------------------------ */\n\nexport function AiCategoriesReview(props: {\n job: AiJobSummary\n createCategories: ConsoleProductsHubZoneProps['createCategories']\n createDiscountDrafts: ConsoleProductsHubZoneProps['createDiscountDrafts']\n}) {\n const { job, createCategories, createDiscountDrafts } = props\n const proposal = aiProductsProposalOfKind(job, 'categories')\n const categoryRows = (proposal?.categories ?? []).map((category, index) => ({\n $id: `c${index}`,\n name: category.name,\n why: category.why,\n }))\n const discountRows = (proposal?.discounts ?? []).map((discount, index) => ({\n $id: `d${index}`,\n name: discount.name,\n code: discount.code ?? 'Applies on its own',\n offer: aiDiscountOffer(discount),\n minimum: discount.minSubtotalCents ? `$${(discount.minSubtotalCents / 100).toFixed(2)}` : 'Any order',\n why: discount.why,\n discount,\n }))\n const [categories, setCategories] = useState<string[] | null>(null)\n const [discounts, setDiscounts] = useState<string[] | null>(null)\n const [busy, setBusy] = useState(false)\n const [result, setResult] = useState<{ severity: 'success' | 'error'; text: string } | null>(null)\n const chosenCategories = categories ?? categoryRows.map((row) => row.$id)\n const chosenDiscounts = discounts ?? discountRows.map((row) => row.$id)\n\n const categoryColumns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Category', flex: 1, minWidth: 160 },\n { field: 'why', headerName: 'Why', flex: 2, minWidth: 220 },\n ],\n [],\n )\n const discountColumns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Discount', flex: 1, minWidth: 150 },\n { field: 'code', headerName: 'Code', width: 150 },\n { field: 'offer', headerName: 'Offer', width: 120 },\n { field: 'minimum', headerName: 'Minimum order', width: 130 },\n { field: 'why', headerName: 'Why', flex: 2, minWidth: 200 },\n ],\n [],\n )\n\n const create = useCallback(async () => {\n setBusy(true)\n setResult(null)\n try {\n const madeCategories = await createCategories(\n categoryRows.filter((row) => chosenCategories.includes(row.$id)).map((row) => row.name),\n )\n const madeDiscounts = await createDiscountDrafts(\n discountRows\n .filter((row) => chosenDiscounts.includes(row.$id))\n .map(({ discount }) => ({\n name: discount.name,\n code: discount.code,\n kind: discount.kind,\n valuePct: discount.valuePct,\n valueCents: discount.valueCents,\n minSubtotalCents: discount.minSubtotalCents,\n })),\n )\n setResult({\n severity: 'success',\n text:\n `Created ${madeCategories} ${madeCategories === 1 ? 'category' : 'categories'} and ` +\n `${madeDiscounts} ${madeDiscounts === 1 ? 'discount' : 'discounts'}. ` +\n 'Discounts start switched off: switch each on under Promotions when you are ready. ' +\n 'Anything your store already had was left as it is.',\n })\n setCategories([])\n setDiscounts([])\n } catch (error) {\n setResult({ severity: 'error', text: messageOf(error, 'The categories and discounts could not be created.') })\n } finally {\n setBusy(false)\n }\n }, [categoryRows, discountRows, chosenCategories, chosenDiscounts, createCategories, createDiscountDrafts])\n\n const any = chosenCategories.length + chosenDiscounts.length\n return (\n <Stack spacing={1} aria-label=\"Proposed categories and discounts\">\n <Typography variant=\"subtitle2\">{'Proposed categories and discounts'}</Typography>\n <JobState job={job} doing=\"Proposing categories and discounts from your brief\" />\n {categoryRows.length ? (\n <ListTable\n rows={categoryRows}\n columns={categoryColumns}\n hideFooter\n disableColumnFilter\n quickFilter={false}\n selectable={{ selected: chosenCategories, onChange: setCategories }}\n />\n ) : null}\n {discountRows.length ? (\n <ListTable\n rows={discountRows}\n columns={discountColumns}\n hideFooter\n disableColumnFilter\n quickFilter={false}\n selectable={{ selected: chosenDiscounts, onChange: setDiscounts }}\n />\n ) : null}\n {categoryRows.length || discountRows.length ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={busy || !any}\n onClick={() => void create()}\n >\n {'Create selected'}\n </Button>\n ) : null}\n {result ? <Alert severity={result.severity}>{result.text}</Alert> : null}\n </Stack>\n )\n}\n"],"names":["ListTable","Alert","Button","Chip","CircularProgress","Stack","Typography","useCallback","useMemo","useState","aiProductCopyProposals","aiProductsBulkCount","aiProductsProposalOfKind","aiProductCopyValues","isAiJobMoving","clip","text","max","length","slice","messageOf","error","fallback","Error","message","aiDiscountOffer","discount","kind","valuePct","valueCents","toFixed","JobState","props","job","direction","spacing","sx","alignItems","size","aria-label","doing","variant","color","severity","status","AiBulkCopyReview","applyProductCopy","proposals","asked","rows","setRows","applyingAll","setApplyingAll","ready","proposal","Boolean","values","product","id","apply","current","applyAll","targets","columns","field","headerName","flex","minWidth","width","sortable","renderCell","row","skipped","label","title","state","undefined","disabled","onClick","tableRows","map","$id","name","description","seoTitle","readyCount","filter","errors","Object","gaps","hideFooter","disableColumnFilter","quickFilter","alignSelf","AiCatalogReview","heldNames","createProductDrafts","products","held","has","replace","trim","toLowerCase","selected","setSelected","busy","setBusy","result","setResult","index","type","options","option","join","photo","inCatalog","chosen","py","whiteSpace","create","picks","includes","created","tags","seoDescription","passed","getRowHeight","selectable","onChange","AiCategoriesReview","createCategories","createDiscountDrafts","categoryRows","categories","category","why","discountRows","discounts","code","offer","minimum","minSubtotalCents","setCategories","setDiscounts","chosenCategories","chosenDiscounts","categoryColumns","discountColumns","madeCategories","madeDiscounts","any"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-products-proposals.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 { ConsoleProductsHubZoneProps } from './ai-product-zones'\nimport { ListTable, type ListTableProps } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { Alert, Button, Chip, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useMemo, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport {\n aiCatalogPrice,\n aiProductCopyProposals,\n aiProductsBulkCount,\n aiProductsProposalOfKind,\n type AiProductCopyProposal,\n type AiProposedDiscount,\n} from '../model/ai-products'\nimport { aiProductCopyValues } from './ai-product-copy-card.component'\nimport { isAiJobMoving } from './use-ai-products-jobs'\n\n/**\n * The review tables of the products hub's AI card (AGL-2916): what a\n * `products` job proposed, row by row, before anything is written. Every\n * write is one the hub makes when a person asks for it — Apply, Apply to all,\n * Create drafts, Create selected — and the hub passes over what the site\n * already has, so a proposal applied twice creates nothing twice.\n *\n * Each table is one job's whole answer, held in hand and capped by the job\n * (`AI_PRODUCTS_BULK_MAX`, `AI_CATEGORIES_MAX`, `AI_DISCOUNTS_MAX`, a dozen\n * proposed products), so none offers a Filters panel or a search box: a\n * bounded readout is read whole, and a grid that filtered the rows it holds\n * is the pattern every list is leaving (AGL-3321).\n */\n\n/** A column of a review table, in the shared list table's own terms. */\ntype GridColDef = NonNullable<ListTableProps['columns']>[number]\n\nconst clip = (text: string, max: number) => (text.length > max ? `${text.slice(0, max - 1)}…` : text)\n\nconst messageOf = (error: unknown, fallback: string) =>\n error instanceof Error && error.message ? error.message : fallback\n\n/** A discount's offer, in the words the discounts card uses. */\nexport function aiDiscountOffer(discount: AiProposedDiscount): string {\n if (discount.kind === 'percent') return `${discount.valuePct ?? 0}% off`\n if (discount.kind === 'fixed') return `$${((discount.valueCents ?? 0) / 100).toFixed(2)} off`\n return 'Free shipping'\n}\n\nfunction JobState(props: { job: AiJobSummary; doing: string }) {\n const { job } = props\n if (isAiJobMoving(job)) {\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label={props.doing} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.doing}\n </Typography>\n </Stack>\n )\n }\n return job.error ? <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert> : null\n}\n\n/* ------------------------------------------------------------------------ *\n * Copy for many products\n * ------------------------------------------------------------------------ */\n\ntype RowState = 'applying' | 'applied' | { error: string }\n\nexport function AiBulkCopyReview(props: {\n job: AiJobSummary\n applyProductCopy: ConsoleProductsHubZoneProps['applyProductCopy']\n}) {\n const { job, applyProductCopy } = props\n const proposals = aiProductCopyProposals(job)\n const asked = aiProductsBulkCount(job) ?? proposals.length\n const [rows, setRows] = useState<Record<string, RowState>>({})\n const [applyingAll, setApplyingAll] = useState(false)\n\n const ready = (proposal: AiProductCopyProposal) =>\n Boolean(proposal.values && proposal.product.id) && rows[proposal.product.id as string] !== 'applied'\n\n const apply = useCallback(\n async (proposal: AiProductCopyProposal) => {\n const id = proposal.product.id\n if (!id || !proposal.values) return\n setRows((current) => ({ ...current, [id]: 'applying' }))\n try {\n await applyProductCopy(id, aiProductCopyValues(proposal))\n setRows((current) => ({ ...current, [id]: 'applied' }))\n } catch (error) {\n setRows((current) => ({ ...current, [id]: { error: messageOf(error, 'The copy could not be saved.') } }))\n }\n },\n [applyProductCopy],\n )\n\n /** Applies each proposal in turn, so one refusal names its product and the rest still land. */\n const applyAll = useCallback(\n async (targets: readonly AiProductCopyProposal[]) => {\n setApplyingAll(true)\n try {\n for (const proposal of targets) await apply(proposal)\n } finally {\n setApplyingAll(false)\n }\n },\n [apply],\n )\n\n const columns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Product', flex: 1, minWidth: 160 },\n { field: 'description', headerName: 'Proposed description', flex: 2, minWidth: 220 },\n { field: 'seoTitle', headerName: 'Search title', flex: 1, minWidth: 160 },\n {\n field: 'actions',\n headerName: '',\n width: 170,\n sortable: false,\n renderCell: ({ row }: { row: { proposal: AiProductCopyProposal } }) => {\n const { proposal } = row\n if (proposal.skipped) return <Chip size=\"small\" variant=\"outlined\" label=\"Skipped\" title={proposal.skipped} />\n const state = proposal.product.id ? rows[proposal.product.id] : undefined\n if (state === 'applied') return <Chip size=\"small\" color=\"success\" variant=\"outlined\" label=\"Applied\" />\n if (typeof state === 'object') return <Chip size=\"small\" color=\"error\" variant=\"outlined\" label=\"Not saved\" title={state.error} />\n return (\n <Button size=\"small\" disabled={state === 'applying' || applyingAll} onClick={() => void apply(proposal)}>\n {'Apply'}\n </Button>\n )\n },\n },\n ],\n [rows, applyingAll, apply],\n )\n\n const tableRows = proposals.map((proposal) => ({\n $id: proposal.product.id ?? proposal.product.name,\n name: proposal.product.name,\n description: proposal.values ? clip(proposal.values.description, 140) : (proposal.skipped ?? ''),\n seoTitle: proposal.values?.seoTitle ?? '',\n proposal,\n }))\n const readyCount = proposals.filter(ready).length\n const errors = Object.values(rows).filter((state): state is { error: string } => typeof state === 'object')\n const gaps = proposals.filter((proposal) => proposal.gaps.length).length\n\n return (\n <Stack spacing={1} aria-label=\"Proposed product copy\">\n <Typography variant=\"subtitle2\">{`Product copy · ${proposals.length} of ${asked}`}</Typography>\n <JobState job={job} doing={`Writing copy: ${proposals.length} of ${asked}`} />\n {tableRows.length ? <ListTable rows={tableRows} columns={columns} hideFooter disableColumnFilter quickFilter={false} /> : null}\n {gaps ? (\n <Alert severity=\"warning\">\n {`${gaps} ${gaps === 1 ? 'description marks' : 'descriptions mark'} facts in [brackets] to fill in before you publish.`}\n </Alert>\n ) : null}\n {errors.length ? <Alert severity=\"error\">{errors[0].error}</Alert> : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={!readyCount || applyingAll}\n onClick={() => void applyAll(proposals.filter(ready))}\n >\n {readyCount ? `Apply to all ${readyCount}` : 'Apply to all'}\n </Button>\n </Stack>\n )\n}\n\n/* ------------------------------------------------------------------------ *\n * A store's first products\n * ------------------------------------------------------------------------ */\n\nexport function AiCatalogReview(props: {\n job: AiJobSummary\n heldNames: ReadonlySet<string>\n createProductDrafts: ConsoleProductsHubZoneProps['createProductDrafts']\n}) {\n const { job, heldNames, createProductDrafts } = props\n const proposal = aiProductsProposalOfKind(job, 'catalog')\n const products = useMemo(() => proposal?.products ?? [], [proposal])\n const held = (name: string) => heldNames.has(name.replace(/\\s+/g, ' ').trim().toLowerCase())\n const [selected, setSelected] = useState<string[] | null>(null)\n const [busy, setBusy] = useState(false)\n const [result, setResult] = useState<{ severity: 'success' | 'error'; text: string } | null>(null)\n\n const rows = products.map((product, index) => ({\n $id: `p${index}`,\n name: product.name,\n type: product.type,\n options: product.options.map((option) => `${option.name}: ${option.values.join(', ')}`).join(' · ') || '—',\n photo: product.photo,\n inCatalog: held(product.name),\n product,\n }))\n const chosen = selected ?? rows.filter((row) => !row.inCatalog).map((row) => row.$id)\n\n const columns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Product', flex: 1, minWidth: 180 },\n { field: 'options', headerName: 'Options', flex: 1, minWidth: 160 },\n {\n field: 'price',\n headerName: 'Price',\n width: 130,\n sortable: false,\n // A suggested price by what the product is (Zach, 2026-10-12), the\n // merchant's to change; a proposal without one takes the default.\n renderCell: ({ row }: { row: { product: { price?: number | null } } }) => {\n const price = aiCatalogPrice(row.product.price)\n return price === null ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Default price\" />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label={`$${price.toFixed(2)} suggested`} />\n )\n },\n },\n {\n field: 'photo',\n headerName: 'Photo',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }: { row: { photo: string; inCatalog: boolean } }) => (\n <Stack spacing={0.25} sx={{ py: 0.5 }}>\n <Chip size=\"small\" color=\"warning\" variant=\"outlined\" label=\"Needs a photo\" sx={{ alignSelf: 'flex-start' }} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ whiteSpace: 'normal' }}>\n {row.photo}\n </Typography>\n </Stack>\n ),\n },\n {\n field: 'inCatalog',\n headerName: '',\n width: 150,\n sortable: false,\n renderCell: ({ row }: { row: { inCatalog: boolean } }) =>\n row.inCatalog ? <Chip size=\"small\" variant=\"outlined\" label=\"In your catalog\" /> : null,\n },\n ],\n [],\n )\n\n const create = useCallback(async () => {\n const picks = rows.filter((row) => chosen.includes(row.$id)).map((row) => row.product)\n if (!picks.length) return\n setBusy(true)\n setResult(null)\n try {\n const created = await createProductDrafts(\n picks.map((product) => ({\n name: product.name,\n type: product.type,\n description: product.description,\n tags: product.tags,\n options: product.options,\n seoTitle: product.seoTitle,\n seoDescription: product.seoDescription,\n priceUsd: aiCatalogPrice(product.price),\n })),\n )\n const passed = picks.length - created.length\n setResult({\n severity: 'success',\n text:\n `Created ${created.length} draft ${created.length === 1 ? 'product' : 'products'}. ` +\n 'Review each price and add a photo before you activate it.' +\n (passed ? ` ${passed} already in your catalog ${passed === 1 ? 'was' : 'were'} left as they are.` : ''),\n })\n setSelected([])\n } catch (error) {\n setResult({ severity: 'error', text: messageOf(error, 'The draft products could not be created.') })\n } finally {\n setBusy(false)\n }\n }, [rows, chosen, createProductDrafts])\n\n return (\n <Stack spacing={1} aria-label=\"Proposed products\">\n <Typography variant=\"subtitle2\">{'Proposed products'}</Typography>\n <JobState job={job} doing=\"Proposing products from your brief\" />\n {rows.length ? (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Each is created as a draft at its suggested price, with no photo. Review the price and add a photo ' +\n 'in the product editor, then activate it.'}\n </Typography>\n <ListTable\n rows={rows}\n columns={columns}\n hideFooter\n disableColumnFilter\n quickFilter={false}\n getRowHeight={() => 'auto'}\n selectable={{ selected: chosen, onChange: setSelected }}\n />\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={busy || !chosen.length}\n onClick={() => void create()}\n >\n {`Create ${chosen.length} ${chosen.length === 1 ? 'draft' : 'drafts'}`}\n </Button>\n </>\n ) : null}\n {result ? <Alert severity={result.severity}>{result.text}</Alert> : null}\n </Stack>\n )\n}\n\n/* ------------------------------------------------------------------------ *\n * Categories and discounts\n * ------------------------------------------------------------------------ */\n\nexport function AiCategoriesReview(props: {\n job: AiJobSummary\n createCategories: ConsoleProductsHubZoneProps['createCategories']\n createDiscountDrafts: ConsoleProductsHubZoneProps['createDiscountDrafts']\n}) {\n const { job, createCategories, createDiscountDrafts } = props\n const proposal = aiProductsProposalOfKind(job, 'categories')\n const categoryRows = (proposal?.categories ?? []).map((category, index) => ({\n $id: `c${index}`,\n name: category.name,\n why: category.why,\n }))\n const discountRows = (proposal?.discounts ?? []).map((discount, index) => ({\n $id: `d${index}`,\n name: discount.name,\n code: discount.code ?? 'Applies on its own',\n offer: aiDiscountOffer(discount),\n minimum: discount.minSubtotalCents ? `$${(discount.minSubtotalCents / 100).toFixed(2)}` : 'Any order',\n why: discount.why,\n discount,\n }))\n const [categories, setCategories] = useState<string[] | null>(null)\n const [discounts, setDiscounts] = useState<string[] | null>(null)\n const [busy, setBusy] = useState(false)\n const [result, setResult] = useState<{ severity: 'success' | 'error'; text: string } | null>(null)\n const chosenCategories = categories ?? categoryRows.map((row) => row.$id)\n const chosenDiscounts = discounts ?? discountRows.map((row) => row.$id)\n\n const categoryColumns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Category', flex: 1, minWidth: 160 },\n { field: 'why', headerName: 'Why', flex: 2, minWidth: 220 },\n ],\n [],\n )\n const discountColumns = useMemo<GridColDef[]>(\n () => [\n { field: 'name', headerName: 'Discount', flex: 1, minWidth: 150 },\n { field: 'code', headerName: 'Code', width: 150 },\n { field: 'offer', headerName: 'Offer', width: 120 },\n { field: 'minimum', headerName: 'Minimum order', width: 130 },\n { field: 'why', headerName: 'Why', flex: 2, minWidth: 200 },\n ],\n [],\n )\n\n const create = useCallback(async () => {\n setBusy(true)\n setResult(null)\n try {\n const madeCategories = await createCategories(\n categoryRows.filter((row) => chosenCategories.includes(row.$id)).map((row) => row.name),\n )\n const madeDiscounts = await createDiscountDrafts(\n discountRows\n .filter((row) => chosenDiscounts.includes(row.$id))\n .map(({ discount }) => ({\n name: discount.name,\n code: discount.code,\n kind: discount.kind,\n valuePct: discount.valuePct,\n valueCents: discount.valueCents,\n minSubtotalCents: discount.minSubtotalCents,\n })),\n )\n setResult({\n severity: 'success',\n text:\n `Created ${madeCategories} ${madeCategories === 1 ? 'category' : 'categories'} and ` +\n `${madeDiscounts} ${madeDiscounts === 1 ? 'discount' : 'discounts'}. ` +\n 'Discounts start switched off: switch each on under Promotions when you are ready. ' +\n 'Anything your store already had was left as it is.',\n })\n setCategories([])\n setDiscounts([])\n } catch (error) {\n setResult({ severity: 'error', text: messageOf(error, 'The categories and discounts could not be created.') })\n } finally {\n setBusy(false)\n }\n }, [categoryRows, discountRows, chosenCategories, chosenDiscounts, createCategories, createDiscountDrafts])\n\n const any = chosenCategories.length + chosenDiscounts.length\n return (\n <Stack spacing={1} aria-label=\"Proposed categories and discounts\">\n <Typography variant=\"subtitle2\">{'Proposed categories and discounts'}</Typography>\n <JobState job={job} doing=\"Proposing categories and discounts from your brief\" />\n {categoryRows.length ? (\n <ListTable\n rows={categoryRows}\n columns={categoryColumns}\n hideFooter\n disableColumnFilter\n quickFilter={false}\n selectable={{ selected: chosenCategories, onChange: setCategories }}\n />\n ) : null}\n {discountRows.length ? (\n <ListTable\n rows={discountRows}\n columns={discountColumns}\n hideFooter\n disableColumnFilter\n quickFilter={false}\n selectable={{ selected: chosenDiscounts, onChange: setDiscounts }}\n />\n ) : null}\n {categoryRows.length || discountRows.length ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={busy || !any}\n onClick={() => void create()}\n >\n {'Create selected'}\n </Button>\n ) : null}\n {result ? <Alert severity={result.severity}>{result.text}</Alert> : null}\n </Stack>\n )\n}\n"],"names":["ListTable","Alert","Button","Chip","CircularProgress","Stack","Typography","useCallback","useMemo","useState","aiCatalogPrice","aiProductCopyProposals","aiProductsBulkCount","aiProductsProposalOfKind","aiProductCopyValues","isAiJobMoving","clip","text","max","length","slice","messageOf","error","fallback","Error","message","aiDiscountOffer","discount","kind","valuePct","valueCents","toFixed","JobState","props","job","direction","spacing","sx","alignItems","size","aria-label","doing","variant","color","severity","status","AiBulkCopyReview","applyProductCopy","proposals","asked","rows","setRows","applyingAll","setApplyingAll","ready","proposal","Boolean","values","product","id","apply","current","applyAll","targets","columns","field","headerName","flex","minWidth","width","sortable","renderCell","row","skipped","label","title","state","undefined","disabled","onClick","tableRows","map","$id","name","description","seoTitle","readyCount","filter","errors","Object","gaps","hideFooter","disableColumnFilter","quickFilter","alignSelf","AiCatalogReview","heldNames","createProductDrafts","products","held","has","replace","trim","toLowerCase","selected","setSelected","busy","setBusy","result","setResult","index","type","options","option","join","photo","inCatalog","chosen","price","py","whiteSpace","create","picks","includes","created","tags","seoDescription","priceUsd","passed","getRowHeight","selectable","onChange","AiCategoriesReview","createCategories","createDiscountDrafts","categoryRows","categories","category","why","discountRows","discounts","code","offer","minimum","minSubtotalCents","setCategories","setDiscounts","chosenCategories","chosenDiscounts","categoryColumns","discountColumns","madeCategories","madeDiscounts","any"],"mappings":"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"}
|
|
@@ -32,5 +32,5 @@ export declare const AI_SITE_START_SURFACE_SX: {
|
|
|
32
32
|
export type AiSiteStartCardProps = ConsoleHostFirstRunZoneProps & {
|
|
33
33
|
initialAnswers?: AiSiteStartAnswers | null;
|
|
34
34
|
};
|
|
35
|
-
export declare function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leave, initialAnswers, }: AiSiteStartCardProps): import("react").JSX.Element;
|
|
35
|
+
export declare function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leave, pendingSite, initialAnswers, }: AiSiteStartCardProps): import("react").JSX.Element;
|
|
36
36
|
export default AiSiteStartCard;
|
|
@@ -34,7 +34,7 @@ import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
|
|
|
34
34
|
import { AI_CREDITS_CONFIRM_CODE, aiCreditRangeText } from "../model/ai-credit-estimate.js";
|
|
35
35
|
import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
|
|
36
36
|
import { AI_SITE_START_ANSWERS, AI_SITE_START_TYPES, aiSiteStartBrief, aiSiteStartKind, aiSiteStartInputs, aiSiteStartRefusal } from "../model/ai-site-start.js";
|
|
37
|
-
import { AI_SITE_KINDS } from "../model/ai-site-kinds.js";
|
|
37
|
+
import { AI_SITE_KINDS, aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
|
|
38
38
|
/** The icon each kind of site's card shows. */ const AI_SITE_KIND_ICONS = {
|
|
39
39
|
business: mdiBriefcaseOutline.path,
|
|
40
40
|
trades: mdiWrench.path,
|
|
@@ -217,9 +217,9 @@ function SectionLabel({ children, help }) {
|
|
|
217
217
|
]
|
|
218
218
|
});
|
|
219
219
|
}
|
|
220
|
-
export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leave, initialAnswers = null }) {
|
|
221
|
-
var _ref, _aiFreeSitePrompt;
|
|
222
|
-
var _modelChoice_options_options_find, _modelChoice_options;
|
|
220
|
+
export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leave, pendingSite, initialAnswers = null }) {
|
|
221
|
+
var _ref, _aiFreeSitePrompt, _ref1;
|
|
222
|
+
var _modelChoice_options_options_find, _modelChoice_options, _aiSiteKindOfInputs;
|
|
223
223
|
const { data: user } = useUser();
|
|
224
224
|
const router = useRouter();
|
|
225
225
|
// Held in a ref so a request reads WHO is signed in, never the identity of
|
|
@@ -247,6 +247,13 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
247
247
|
const [confirmingLeave, setConfirmingLeave] = useState(false);
|
|
248
248
|
// The dialog's own root, to tell its Escape from a nested popup's.
|
|
249
249
|
const dialogRef = useRef(null);
|
|
250
|
+
// Drawn by "Create site" before the site exists (AGL-3761): the shell's
|
|
251
|
+
// name and address are asked on step 1, and the site is created only by
|
|
252
|
+
// the starter, Skip, or Plan my site. Read through a ref by the verdict
|
|
253
|
+
// below, which must not re-run when the shell re-renders it.
|
|
254
|
+
const pendingRef = useRef(pendingSite);
|
|
255
|
+
pendingRef.current = pendingSite;
|
|
256
|
+
const creating = Boolean(pendingSite) && !(pendingSite == null ? void 0 : pendingSite.created);
|
|
250
257
|
useEffect(()=>{
|
|
251
258
|
if (!orgId || !uid) return;
|
|
252
259
|
let active = true;
|
|
@@ -255,6 +262,8 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
255
262
|
const response = await authorizedFetch(userRef.current, `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`);
|
|
256
263
|
const payload = response.ok ? await response.json().catch(()=>null) : null;
|
|
257
264
|
if (active) {
|
|
265
|
+
var // "Create site" shows its own dialog when this one will not draw.
|
|
266
|
+
_pendingRef_current;
|
|
258
267
|
if ((payload == null ? void 0 : payload.freeTaste) === true) {
|
|
259
268
|
setFreeTaste(true);
|
|
260
269
|
const credits = payload == null ? void 0 : payload.freeCredits;
|
|
@@ -265,10 +274,16 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
265
274
|
welcomeEmail: false
|
|
266
275
|
}));
|
|
267
276
|
}
|
|
268
|
-
|
|
277
|
+
const shown = response.status !== 404 && response.status !== 403;
|
|
278
|
+
setVerdict(shown ? 'ready' : 'hidden');
|
|
279
|
+
(_pendingRef_current = pendingRef.current) == null ? void 0 : _pendingRef_current.ready(shown);
|
|
269
280
|
}
|
|
270
281
|
} catch (unused) {
|
|
271
|
-
if (active)
|
|
282
|
+
if (active) {
|
|
283
|
+
var _pendingRef_current1;
|
|
284
|
+
setVerdict('hidden');
|
|
285
|
+
(_pendingRef_current1 = pendingRef.current) == null ? void 0 : _pendingRef_current1.ready(false);
|
|
286
|
+
}
|
|
272
287
|
}
|
|
273
288
|
})();
|
|
274
289
|
return ()=>{
|
|
@@ -334,6 +349,28 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
334
349
|
setServerPrompt(null);
|
|
335
350
|
try {
|
|
336
351
|
var _ref;
|
|
352
|
+
// The site, created now when it does not exist yet (AGL-3761): this is
|
|
353
|
+
// the moment the person asked for it. A refusal is said here, and one
|
|
354
|
+
// about the name or address goes back to step 1, where they are typed.
|
|
355
|
+
let site = {
|
|
356
|
+
hostId,
|
|
357
|
+
host,
|
|
358
|
+
orgSlug
|
|
359
|
+
};
|
|
360
|
+
if (!hostId && pendingSite) {
|
|
361
|
+
const made = await pendingSite.create();
|
|
362
|
+
if ('error' in made) {
|
|
363
|
+
setNotice(made.error);
|
|
364
|
+
if (made.address) setStep('choose');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
site = {
|
|
368
|
+
hostId: made.hostId,
|
|
369
|
+
host: made.host,
|
|
370
|
+
orgSlug: made.orgSlug || orgSlug
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (!site.hostId) return;
|
|
337
374
|
const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {
|
|
338
375
|
method: 'POST',
|
|
339
376
|
headers: {
|
|
@@ -341,7 +378,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
341
378
|
},
|
|
342
379
|
body: JSON.stringify(_extends({
|
|
343
380
|
orgId,
|
|
344
|
-
hostId,
|
|
381
|
+
hostId: site.hostId,
|
|
345
382
|
kind: 'site',
|
|
346
383
|
brief: aiSiteStartBrief(asked),
|
|
347
384
|
// The guided start confirms its own plan (AGL-3594): the build
|
|
@@ -376,7 +413,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
376
413
|
// Where the next step is (AGL-3594): the site's "Building your site"
|
|
377
414
|
// page for this job. The zone closes without the starter first, so the
|
|
378
415
|
// setup page does not ask again behind it.
|
|
379
|
-
const href = aiSiteBuildHref(orgSlug, host, created.id);
|
|
416
|
+
const href = aiSiteBuildHref(site.orgSlug, site.host, created.id);
|
|
380
417
|
if (href) {
|
|
381
418
|
;
|
|
382
419
|
(leave != null ? leave : ()=>undefined)();
|
|
@@ -401,30 +438,55 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
401
438
|
host,
|
|
402
439
|
leave,
|
|
403
440
|
router,
|
|
404
|
-
pickedModel
|
|
441
|
+
pickedModel,
|
|
442
|
+
pendingSite
|
|
443
|
+
]);
|
|
444
|
+
// The zone's `startBlank`. Before the site exists it creates it with the
|
|
445
|
+
// starter, and answers `false` when it could not — most often a name or
|
|
446
|
+
// address to fix, which step 1 shows.
|
|
447
|
+
const runStartBlank = useCallback(()=>{
|
|
448
|
+
const result = startBlank();
|
|
449
|
+
if (result && typeof result.then === 'function') {
|
|
450
|
+
void result.then((done)=>{
|
|
451
|
+
if (done) return;
|
|
452
|
+
setStartingStarter(false);
|
|
453
|
+
setStep('choose');
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}, [
|
|
457
|
+
startBlank
|
|
405
458
|
]);
|
|
406
459
|
const chooseStarter = useCallback(()=>{
|
|
460
|
+
if (pendingSite && !pendingSite.created && !pendingSite.check()) return;
|
|
407
461
|
setStartingStarter(true);
|
|
408
462
|
trackEvent('site_start_choice', {
|
|
409
463
|
choice: 'starter'
|
|
410
464
|
});
|
|
411
|
-
|
|
465
|
+
runStartBlank();
|
|
412
466
|
}, [
|
|
413
|
-
|
|
467
|
+
pendingSite,
|
|
468
|
+
runStartBlank
|
|
414
469
|
]);
|
|
415
470
|
const chooseAi = useCallback(()=>{
|
|
471
|
+
if (pendingSite && !pendingSite.created && !pendingSite.check()) return;
|
|
416
472
|
trackEvent('site_start_choice', {
|
|
417
473
|
choice: 'ai'
|
|
418
474
|
});
|
|
419
475
|
setStep('describe');
|
|
420
|
-
}, [
|
|
476
|
+
}, [
|
|
477
|
+
pendingSite
|
|
478
|
+
]);
|
|
421
479
|
if (verdict !== 'ready') return null;
|
|
422
|
-
// Before a job is started
|
|
423
|
-
//
|
|
424
|
-
const exit = started ? leave != null ? leave :
|
|
480
|
+
// Before a job is started Skip is the blank site, which writes the starter
|
|
481
|
+
// (AGL-3594); after, it only closes — the job builds the site.
|
|
482
|
+
const exit = started ? leave != null ? leave : runStartBlank : runStartBlank;
|
|
483
|
+
// Escape and the close control. Before "Create site" has made the site
|
|
484
|
+
// (AGL-3761) there is nothing to give a starter to, so they only close,
|
|
485
|
+
// and nothing is created; otherwise they are the same way out as Skip.
|
|
486
|
+
const dismissTo = creating && !started ? leave != null ? leave : runStartBlank : exit;
|
|
425
487
|
// Anything the person typed or picked, which a dismissal must not discard
|
|
426
488
|
// silently (AGL-3660). After a job starts there is nothing left to lose.
|
|
427
|
-
const drafted = !started && Boolean(answers.siteType.trim() || answers.audience.trim() || answers.kind !== null);
|
|
489
|
+
const drafted = !started && Boolean(answers.siteType.trim() || answers.audience.trim() || answers.kind !== null || creating && (pendingSite == null ? void 0 : pendingSite.dirty));
|
|
428
490
|
// Escape and the close control: a dismissal, never a choice. Escape that
|
|
429
491
|
// bubbled here through React from a portal outside this dialog's own DOM is
|
|
430
492
|
// a nested menu's, already handled there, and is not this dialog's to act on.
|
|
@@ -435,23 +497,25 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
435
497
|
if (root && target instanceof Node && !root.contains(target)) return;
|
|
436
498
|
}
|
|
437
499
|
if (drafted) setConfirmingLeave(true);
|
|
438
|
-
else
|
|
500
|
+
else dismissTo();
|
|
439
501
|
};
|
|
440
502
|
const leaveDraft = ()=>{
|
|
441
503
|
setConfirmingLeave(false);
|
|
442
|
-
|
|
504
|
+
dismissTo();
|
|
443
505
|
};
|
|
444
506
|
// The chosen model's cost against Auto's, as the model list states it.
|
|
445
507
|
const modelMultiplier = pickedModel && ((_modelChoice_options = modelChoice.options) == null ? void 0 : (_modelChoice_options_options_find = _modelChoice_options.options.find((option)=>option.id === pickedModel)) == null ? void 0 : _modelChoice_options_options_find.multiplier) || 1;
|
|
446
508
|
// What the start is likely to cost and at most (AGL-3722): "About 137
|
|
447
|
-
// credits (up to
|
|
509
|
+
// credits (up to 283)", the measured figures scaled by the chosen model.
|
|
448
510
|
const scaled = (range)=>({
|
|
449
511
|
likely: Math.round(range.likely * modelMultiplier),
|
|
450
512
|
p90: Math.round(range.p90 * modelMultiplier),
|
|
451
513
|
ceiling: Math.round(range.ceiling * modelMultiplier)
|
|
452
514
|
});
|
|
453
515
|
const estimate = freeTaste ? aiFreeSiteCreditRange(answers.pages) : scaled(aiSiteCreditRange(answers.pages, {
|
|
454
|
-
welcomeEmail: answers.welcomeEmail
|
|
516
|
+
welcomeEmail: answers.welcomeEmail,
|
|
517
|
+
// A store's first products and a blog's first posts are part of the start (AGL-3676).
|
|
518
|
+
siteKind: (_ref1 = (_aiSiteKindOfInputs = aiSiteKindOfInputs(aiSiteStartInputs(answers))) == null ? void 0 : _aiSiteKindOfInputs.id) != null ? _ref1 : null
|
|
455
519
|
}));
|
|
456
520
|
// A Free start quotes what is LEFT (AGL-3660), shared across the owner's
|
|
457
521
|
// Free workspaces — never the month's whole allowance as if none were spent.
|
|
@@ -572,7 +636,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
572
636
|
float: 'right'
|
|
573
637
|
}
|
|
574
638
|
}, START_HELP.choose)) : null,
|
|
575
|
-
choosing ? 'You can change everything later.' : `A few answers and ${PLATFORM_BRAND_NAME} AI plans and writes your pages, then publishes your site.`
|
|
639
|
+
choosing ? creating ? 'Name your site, then choose how it starts. You can change everything later.' : 'You can change everything later.' : `A few answers and ${PLATFORM_BRAND_NAME} AI plans and writes your pages, then publishes your site.`
|
|
576
640
|
]
|
|
577
641
|
})
|
|
578
642
|
]
|
|
@@ -604,6 +668,17 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
604
668
|
pages: 1
|
|
605
669
|
})
|
|
606
670
|
}),
|
|
671
|
+
choosing && creating && pendingSite ? /*#__PURE__*/ _jsxs(Stack, {
|
|
672
|
+
spacing: 2,
|
|
673
|
+
component: "section",
|
|
674
|
+
"aria-label": "Your site",
|
|
675
|
+
children: [
|
|
676
|
+
/*#__PURE__*/ _jsx(SectionLabel, {
|
|
677
|
+
children: 'Your site'
|
|
678
|
+
}),
|
|
679
|
+
pendingSite.fields
|
|
680
|
+
]
|
|
681
|
+
}) : null,
|
|
607
682
|
choosing ? /*#__PURE__*/ _jsxs(Box, {
|
|
608
683
|
sx: {
|
|
609
684
|
display: 'grid',
|
|
@@ -889,7 +964,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
889
964
|
/*#__PURE__*/ _jsx(DialogContent, {
|
|
890
965
|
children: /*#__PURE__*/ _jsx(DialogContentText, {
|
|
891
966
|
id: `${TITLE_ID}-leave-text`,
|
|
892
|
-
children: 'What you typed is not saved. Leaving starts your site from the starter site instead.'
|
|
967
|
+
children: creating ? 'What you typed is not saved, and no site is created.' : 'What you typed is not saved. Leaving starts your site from the starter site instead.'
|
|
893
968
|
})
|
|
894
969
|
}),
|
|
895
970
|
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
@@ -897,7 +972,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
|
|
|
897
972
|
/*#__PURE__*/ _jsx(Button, {
|
|
898
973
|
color: "inherit",
|
|
899
974
|
onClick: leaveDraft,
|
|
900
|
-
children: 'Leave and start blank'
|
|
975
|
+
children: creating ? 'Leave' : 'Leave and start blank'
|
|
901
976
|
}),
|
|
902
977
|
/*#__PURE__*/ _jsx(Button, {
|
|
903
978
|
variant: "contained",
|