@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +38 -0
- package/src/lib/components/ai-automation-zones.js +5 -1
- package/src/lib/components/ai-automation-zones.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
- package/src/lib/components/ai-brief-dialog.component.js +87 -8
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
- package/src/lib/components/ai-campaign-create.component.js +270 -0
- package/src/lib/components/ai-campaign-create.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
- package/src/lib/components/ai-describe-automation.component.js +15 -6
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +20 -8
- package/src/lib/components/ai-describe-button.component.js +19 -36
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-describe-email.component.d.ts +31 -0
- package/src/lib/components/ai-describe-email.component.js +22 -0
- package/src/lib/components/ai-describe-email.component.js.map +1 -0
- package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
- package/src/lib/components/ai-describe-org-automation.component.js +238 -0
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
- package/src/lib/components/ai-experiment-cards.component.js +104 -18
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -5
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
- package/src/lib/components/ai-funnel-zones.component.js +178 -0
- package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
- package/src/lib/components/ai-insight-card.component.d.ts +42 -0
- package/src/lib/components/ai-insight-card.component.js +137 -0
- package/src/lib/components/ai-insight-card.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
- package/src/lib/components/ai-insight-dialog.component.js +9 -3
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +7 -2
- package/src/lib/components/ai-job-plan.component.js +38 -6
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +17 -1
- package/src/lib/components/ai-job-requests.js +39 -3
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-logic-zones.d.ts +64 -0
- package/src/lib/components/ai-logic-zones.js +18 -0
- package/src/lib/components/ai-logic-zones.js.map +1 -0
- package/src/lib/components/ai-logic.component.d.ts +77 -0
- package/src/lib/components/ai-logic.component.js +553 -0
- package/src/lib/components/ai-logic.component.js.map +1 -0
- package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
- package/src/lib/components/ai-marketing-insight.component.js +80 -0
- package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
- package/src/lib/components/ai-media-create.component.d.ts +55 -0
- package/src/lib/components/ai-media-create.component.js +433 -0
- package/src/lib/components/ai-media-create.component.js.map +1 -0
- package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
- package/src/lib/components/ai-overlay-cards.component.js +505 -0
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +5 -0
- package/src/lib/components/ai-product-zones.js +1 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
- package/src/lib/components/ai-products-create-button.component.js +67 -0
- package/src/lib/components/ai-products-create-button.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.js +100 -81
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
- package/src/lib/components/ai-revise-automation.component.js +207 -0
- package/src/lib/components/ai-revise-automation.component.js.map +1 -0
- package/src/lib/components/ai-site-build-page.component.js +160 -24
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
- package/src/lib/components/ai-upsell-dialog.component.js +177 -0
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.d.ts +42 -0
- package/src/lib/components/assist-build-card.component.js +183 -0
- package/src/lib/components/assist-build-card.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +2 -0
- package/src/lib/components/assist-edit-canvas.js +56 -1
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +48 -3
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +51 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -14
- package/src/lib/components/use-ai-job-run.js +0 -50
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
- package/src/lib/jobs/ai-build-capabilities.js +256 -0
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
- package/src/lib/jobs/ai-build-unit-outcome.js +140 -0
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +668 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.js +9 -1
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
- package/src/lib/jobs/ai-job-drafts.js +25 -4
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +8 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
- package/src/lib/jobs/ai-job-layout-language.js +194 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +105 -5
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
- package/src/lib/jobs/ai-job-logic-step.js +395 -0
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
- package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
- package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
- package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
- package/src/lib/jobs/ai-job-page-language.js +355 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +111 -20
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +27 -2
- package/src/lib/jobs/ai-job-plan-step.js +87 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +19 -1
- package/src/lib/jobs/ai-job-site-step.js +220 -61
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
- package/src/lib/jobs/ai-job-text-step.js +12 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
- package/src/lib/jobs/ai-job-workflow-step.js +320 -15
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +80 -2
- package/src/lib/jobs/ai-jobs.js +391 -18
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/jobs/ai-logic-records.d.ts +40 -0
- package/src/lib/jobs/ai-logic-records.js +55 -0
- package/src/lib/jobs/ai-logic-records.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +7 -0
- package/src/lib/jobs/ai-site-publish.js +9 -0
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
- package/src/lib/jobs/ai-workflow-records.js +40 -1
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +457 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
- package/src/lib/layout-language/ai-layout-language.js +446 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
- package/src/lib/layout-language/ai-layout-store.js +69 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-automation-draft.d.ts +18 -0
- package/src/lib/model/ai-automation-draft.js +86 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +201 -0
- package/src/lib/model/ai-build-job.js +472 -0
- package/src/lib/model/ai-build-job.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +61 -0
- package/src/lib/model/ai-build-plan.js +154 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +73 -0
- package/src/lib/model/ai-build-progress.js +170 -0
- package/src/lib/model/ai-build-progress.js.map +1 -0
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +20 -10
- package/src/lib/model/ai-insight.js +72 -9
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +25 -2
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +99 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +108 -0
- package/src/lib/model/ai-logic-job.js +327 -0
- package/src/lib/model/ai-logic-job.js.map +1 -0
- package/src/lib/model/ai-media-credits.d.ts +52 -0
- package/src/lib/model/ai-media-credits.js +61 -0
- package/src/lib/model/ai-media-credits.js.map +1 -0
- package/src/lib/model/ai-overlay-copy.d.ts +114 -0
- package/src/lib/model/ai-overlay-copy.js +175 -0
- package/src/lib/model/ai-overlay-copy.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +25 -4
- package/src/lib/model/ai-site-build-progress.js +194 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +8 -0
- package/src/lib/model/ai-site-job.js +9 -0
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +5 -2
- package/src/lib/model/ai-site-start.js +5 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-svg.d.ts +69 -0
- package/src/lib/model/ai-svg.js +265 -0
- package/src/lib/model/ai-svg.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +3 -2
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +92 -4
- package/src/lib/model/ai-workflow-job.js +124 -7
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/model/assist-build.d.ts +58 -0
- package/src/lib/model/assist-build.js +52 -0
- package/src/lib/model/assist-build.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +48 -3
- package/src/lib/model/assist-edit.js +48 -4
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +308 -47
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +82 -5
- package/src/lib/providers/catalog.js +83 -12
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +116 -0
- package/src/lib/providers/image-contract.js +74 -0
- package/src/lib/providers/image-contract.js.map +1 -0
- package/src/lib/providers/model-choice.js +11 -0
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image.d.ts +121 -0
- package/src/lib/providers/vertex-image.js +312 -0
- package/src/lib/providers/vertex-image.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +101 -0
- package/src/lib/runtime/ai-doctrine-validators.js +525 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +21 -6
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +15 -1
- package/src/lib/runtime/ai-eval.js +52 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
- package/src/lib/runtime/experiment-variant-copy.js +102 -0
- package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
- package/src/lib/server/ai-experiment-versions.d.ts +51 -0
- package/src/lib/server/ai-experiment-versions.js +270 -0
- package/src/lib/server/ai-experiment-versions.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.js +74 -4
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +66 -0
- package/src/lib/server/ai-media-image.js +521 -0
- package/src/lib/server/ai-media-image.js.map +1 -0
- package/src/lib/server/ai-media-svg.d.ts +91 -0
- package/src/lib/server/ai-media-svg.js +186 -0
- package/src/lib/server/ai-media-svg.js.map +1 -0
- package/src/lib/server/assist-build.d.ts +54 -0
- package/src/lib/server/assist-build.js +121 -0
- package/src/lib/server/assist-build.js.map +1 -0
- package/src/lib/server/assist-chat.js +108 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +213 -3
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.js +242 -4
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-logic-tool.d.ts +27 -0
- package/src/lib/tools/ai-logic-tool.js +207 -0
- package/src/lib/tools/ai-logic-tool.js.map +1 -0
- package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
- package/src/lib/tools/ai-overlay-tool.js +107 -0
- package/src/lib/tools/ai-overlay-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +17 -2
- package/src/lib/tools/ai-workflow-tool.js +47 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +28 -1
- package/src/lib/usage/assist-job-refund.js +44 -9
- package/src/lib/usage/assist-job-refund.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +5 -0
- package/src/lib/usage/assist-usage.js +2 -0
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ // lockdown-423: via libs/plugins/ai/src/lib/runtime/ai-gate.ts
|
|
18
|
+
// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict, and
|
|
19
|
+
// every stored picture also passes the upload route's.
|
|
20
|
+
import { mediaFilterKeys } from "@aglyn/aglyn/app-utils/media-metadata";
|
|
21
|
+
import { MEDIA_ALT_MAX_LENGTH } from "@aglyn/aglyn/app-utils/media-alt";
|
|
22
|
+
import { aiMediaCreditsPerPicture } from "../model/ai-media-credits.js";
|
|
23
|
+
import { aiSvgThemePalette, isAiSvgColor } from "../model/ai-svg.js";
|
|
24
|
+
import { estimateAiBilledUsd } from "../providers/catalog.js";
|
|
25
|
+
import { AI_UPSTREAM_FAILURE_COPY, AiUpstreamError } from "../providers/contract.js";
|
|
26
|
+
import { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS, AI_SVG_MAX_COLORS, AI_SVG_STYLES, AiImageSafetyRefusal, isAiImageAspectRatio } from "../providers/image-contract.js";
|
|
27
|
+
import { vertexImageProvider } from "../providers/vertex-image.js";
|
|
28
|
+
import { aiGateLadder } from "../runtime/ai-gate.js";
|
|
29
|
+
import { aiInventoryTheme } from "../runtime/site-inventory.js";
|
|
30
|
+
import { assistBandRefuses, assistCreditsFromUsd } from "../usage/assist-credits.js";
|
|
31
|
+
import { publicAssistQuota, recordAssistCost, releaseAssistMessage } from "../usage/assist-usage.js";
|
|
32
|
+
import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
|
|
33
|
+
/**
|
|
34
|
+
* "Create with AI" in Media (AGL-3602): pictures from a description, stored
|
|
35
|
+
* in the library that asked.
|
|
36
|
+
*
|
|
37
|
+
* `POST /api/ai/media/images { orgId, library, hostId?, folderId?, mode,
|
|
38
|
+
* prompt, aspectRatio, count, style?, palette? }` answers
|
|
39
|
+
* `{ mediaIds, filtered, failed, credits, warning? }`.
|
|
40
|
+
*
|
|
41
|
+
* Two modes:
|
|
42
|
+
*
|
|
43
|
+
* - `photo` — Google's image models on Vertex AI (`vertex-image.ts`). Off
|
|
44
|
+
* unless the deployment configured them; the door answers 404 with a
|
|
45
|
+
* sentence the dialog shows.
|
|
46
|
+
* - `illustration` — an SVG drawn by the text provider every other AI door
|
|
47
|
+
* uses (`ai-media-svg.ts`): an illustration, an icon, a tileable pattern or
|
|
48
|
+
* a simple logo mark, in the site's theme colors or the person's own. No
|
|
49
|
+
* new vendor, so it runs wherever text AI does.
|
|
50
|
+
*
|
|
51
|
+
* There is no GET: the button draws from the shell's own gates, and this door
|
|
52
|
+
* decides when someone asks for a picture.
|
|
53
|
+
*
|
|
54
|
+
* ## A request, not a job
|
|
55
|
+
*
|
|
56
|
+
* Both modes answer inside the request, the way "Save as a component with
|
|
57
|
+
* AI" does, and are metered on the same meter every door uses.
|
|
58
|
+
*
|
|
59
|
+
* ## The money, in order
|
|
60
|
+
*
|
|
61
|
+
* 1. `aiGateLadder` RESERVES before anything is spent: the `aiGenerative`
|
|
62
|
+
* entitlement, `release_ai_generative`, the `ai-generate` kill switch,
|
|
63
|
+
* `ai.generate` on the site, the per-account and per-address windows, the
|
|
64
|
+
* Free taste's own rungs and the band.
|
|
65
|
+
* 2. A workspace whose band is a wall (Free, or a hard cap) is refused before
|
|
66
|
+
* the provider is called when the pictures' estimate is more than it has
|
|
67
|
+
* left.
|
|
68
|
+
* 3. The provider is called. A fault of ours or the provider's hands the
|
|
69
|
+
* reservation back and charges nothing. A declined description charges
|
|
70
|
+
* only what the decline spent, recorded as a declined request (which a
|
|
71
|
+
* Free workspace is never charged for, and which its daily refusal pause
|
|
72
|
+
* counts).
|
|
73
|
+
* 4. Each picture is stored, and only what was stored is SETTLED, at billed
|
|
74
|
+
* rates above the provider's: a photo per picture plus its prompt and
|
|
75
|
+
* thinking, an illustration as the tokens that drew it. A picture held
|
|
76
|
+
* back, one the library refused, and an illustration that failed its
|
|
77
|
+
* safety check twice are never charged — that last one is ours.
|
|
78
|
+
*
|
|
79
|
+
* ## Stored the way any upload is stored
|
|
80
|
+
*
|
|
81
|
+
* Each picture is posted to the console's own `/api/media/upload` with the
|
|
82
|
+
* caller's credential, so the scope, the uploads lockdown, the structural
|
|
83
|
+
* inspection, SVG sanitizing, the quarantine list, the storage band, the CDN
|
|
84
|
+
* variants and the storage counter all apply exactly as they do to a file
|
|
85
|
+
* the person dropped in. This door writes no media document of its own; it
|
|
86
|
+
* only adds, to the documents that route made, the alt text and the record
|
|
87
|
+
* of how the picture was made.
|
|
88
|
+
*/ /** The per-account window: a person makes a few sets a minute, not dozens. */ const RATE_LIMIT = {
|
|
89
|
+
key: 'ai-media-image',
|
|
90
|
+
limit: 6,
|
|
91
|
+
windowMs: 60000
|
|
92
|
+
};
|
|
93
|
+
/** What a person reads when a picture failed on our side. */ export const AI_MEDIA_OURS_COPY = "This one's on us — you weren't charged. The picture didn't come out right; try again, or describe it a little differently.";
|
|
94
|
+
/** What the door answers on a deployment that makes no photos. */ export const AI_MEDIA_NO_PHOTOS_COPY = "Photos aren't available here yet. Choose Illustration to draw one instead.";
|
|
95
|
+
/** The provider photos come from; a spec stands one in through jest. */ function imageProvider() {
|
|
96
|
+
return vertexImageProvider;
|
|
97
|
+
}
|
|
98
|
+
/** Every control character as a space, squeezed, trimmed and cut. */ export function cleanAiImagePrompt(value) {
|
|
99
|
+
let out = '';
|
|
100
|
+
for (const char of String(value != null ? value : '')){
|
|
101
|
+
const code = char.charCodeAt(0);
|
|
102
|
+
out += code < 32 || code === 127 || code === 0x2028 || code === 0x2029 ? ' ' : char;
|
|
103
|
+
}
|
|
104
|
+
return out.replace(/\s+/g, ' ').trim().slice(0, AI_IMAGE_PROMPT_MAX_CHARS);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The alt text a picture is stored with: the sentence given, as a sentence,
|
|
108
|
+
* cut at a word inside the library's alt limit. A person edits it like any
|
|
109
|
+
* other.
|
|
110
|
+
*/ export function aiImageAltText(text) {
|
|
111
|
+
const clean = text.replace(/\s+/g, ' ').trim();
|
|
112
|
+
if (!clean) return '';
|
|
113
|
+
const sentence = clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
114
|
+
if (sentence.length <= MEDIA_ALT_MAX_LENGTH) return sentence;
|
|
115
|
+
const cut = sentence.slice(0, MEDIA_ALT_MAX_LENGTH - 1);
|
|
116
|
+
const space = cut.lastIndexOf(' ');
|
|
117
|
+
return `${(space > 40 ? cut.slice(0, space) : cut).trim()}…`;
|
|
118
|
+
}
|
|
119
|
+
const EXTENSIONS = {
|
|
120
|
+
'image/png': 'png',
|
|
121
|
+
'image/jpeg': 'jpg',
|
|
122
|
+
'image/webp': 'webp',
|
|
123
|
+
'image/svg+xml': 'svg'
|
|
124
|
+
};
|
|
125
|
+
/** A file name from the description's first words: `ai-a-red-barn-2.png`. */ export function aiImageFileName(prompt, index, mimeType) {
|
|
126
|
+
var _EXTENSIONS_mimeType;
|
|
127
|
+
const words = prompt.toLowerCase().normalize('NFKD').replace(/[^a-z0-9\s-]/g, ' ').split(/\s+/).filter(Boolean).slice(0, 6).join('-').slice(0, 60).replace(/-+$/, '');
|
|
128
|
+
return `ai-${words || 'image'}-${index + 1}.${(_EXTENSIONS_mimeType = EXTENSIONS[mimeType]) != null ? _EXTENSIONS_mimeType : 'png'}`;
|
|
129
|
+
}
|
|
130
|
+
export function parseAiMediaImageInput(body) {
|
|
131
|
+
var _ref;
|
|
132
|
+
const text = (key, limit = 128)=>{
|
|
133
|
+
const value = body == null ? void 0 : body[key];
|
|
134
|
+
return typeof value === 'string' ? value.trim().slice(0, limit) : '';
|
|
135
|
+
};
|
|
136
|
+
const count = Math.floor(Number((_ref = body == null ? void 0 : body['count']) != null ? _ref : 1));
|
|
137
|
+
const palette = body == null ? void 0 : body['palette'];
|
|
138
|
+
const colors = Array.isArray(palette == null ? void 0 : palette['colors']) ? palette == null ? void 0 : palette['colors'] : [];
|
|
139
|
+
return {
|
|
140
|
+
orgId: text('orgId'),
|
|
141
|
+
library: (body == null ? void 0 : body['library']) === 'org' ? 'org' : 'host',
|
|
142
|
+
hostId: text('hostId') || null,
|
|
143
|
+
folderId: text('folderId', 64) || null,
|
|
144
|
+
mode: (body == null ? void 0 : body['mode']) === 'illustration' ? 'illustration' : 'photo',
|
|
145
|
+
prompt: cleanAiImagePrompt(body == null ? void 0 : body['prompt']),
|
|
146
|
+
aspectRatio: text('aspectRatio', 8),
|
|
147
|
+
count: Number.isFinite(count) ? count : 0,
|
|
148
|
+
style: text('style', 24) || 'illustration',
|
|
149
|
+
palette: (palette == null ? void 0 : palette['source']) === 'custom' ? {
|
|
150
|
+
source: 'custom',
|
|
151
|
+
colors: colors.map((color)=>String(color != null ? color : '').trim().toLowerCase()).slice(0, AI_SVG_MAX_COLORS + 1)
|
|
152
|
+
} : {
|
|
153
|
+
source: 'theme'
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Posts one picture to the console's own upload route, as the caller. The
|
|
159
|
+
* same body the library's Upload button sends, so the route cannot tell the
|
|
160
|
+
* two apart and applies every check it applies to that one.
|
|
161
|
+
*/ async function storeThroughUploadRoute(request, input, image, fileName) {
|
|
162
|
+
const scope = input.library === 'org' ? _extends({
|
|
163
|
+
orgId: input.orgId
|
|
164
|
+
}, input.hostId ? {
|
|
165
|
+
forHostId: input.hostId
|
|
166
|
+
} : {}) : {
|
|
167
|
+
hostId: input.hostId
|
|
168
|
+
};
|
|
169
|
+
let response;
|
|
170
|
+
try {
|
|
171
|
+
var _request_headers_get;
|
|
172
|
+
response = await fetch(new URL('/api/media/upload', request.url), {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: (_request_headers_get = request.headers.get('authorization')) != null ? _request_headers_get : '',
|
|
176
|
+
'Content-Type': 'application/json'
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify(_extends({}, scope, {
|
|
179
|
+
fileName,
|
|
180
|
+
contentType: image.mimeType,
|
|
181
|
+
folderId: input.folderId,
|
|
182
|
+
data: image.base64
|
|
183
|
+
}))
|
|
184
|
+
});
|
|
185
|
+
} catch (unused) {
|
|
186
|
+
return {
|
|
187
|
+
error: 'The picture could not be saved to the media library.',
|
|
188
|
+
status: 502
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const payload = await response.json().catch(()=>({}));
|
|
192
|
+
const mediaId = typeof payload['mediaId'] === 'string' ? payload['mediaId'] : '';
|
|
193
|
+
if (response.ok && mediaId) return {
|
|
194
|
+
mediaId
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
error: typeof payload['error'] === 'string' ? payload['error'] : 'The picture could not be saved to the media library.',
|
|
198
|
+
status: response.ok ? 502 : response.status
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Adds the alt text and the provenance to a document the upload route made.
|
|
203
|
+
* An `update`, which cannot mint a document: the route minted it.
|
|
204
|
+
*/ async function annotateStoredImage(gate, input, mediaId, alt, provenance) {
|
|
205
|
+
var _data;
|
|
206
|
+
const scopeRef = input.library === 'org' ? gate.firestore.collection('orgs').doc(input.orgId) : gate.firestore.collection('hosts').doc(String(input.hostId));
|
|
207
|
+
const mediaRef = scopeRef.collection('media').doc(mediaId);
|
|
208
|
+
const stored = (_data = (await mediaRef.get()).data()) != null ? _data : {};
|
|
209
|
+
await mediaRef.update(_extends({
|
|
210
|
+
alt,
|
|
211
|
+
description: 'Created with AI from a description.',
|
|
212
|
+
aiGenerated: _extends({}, provenance, {
|
|
213
|
+
createdAt: new Date()
|
|
214
|
+
})
|
|
215
|
+
}, mediaFilterKeys(_extends({}, stored, {
|
|
216
|
+
alt
|
|
217
|
+
}))));
|
|
218
|
+
}
|
|
219
|
+
/** The colors an illustration is drawn in. */ async function illustrationPalette(gate, input) {
|
|
220
|
+
var _snapshot_data;
|
|
221
|
+
var _aiInventoryTheme;
|
|
222
|
+
if (input.palette.source === 'custom') return input.palette.colors;
|
|
223
|
+
if (!input.hostId) return [];
|
|
224
|
+
const snapshot = await gate.firestore.collection('hosts').doc(input.hostId).get();
|
|
225
|
+
return aiSvgThemePalette((_aiInventoryTheme = aiInventoryTheme(snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : null : null)) == null ? void 0 : _aiInventoryTheme.colors);
|
|
226
|
+
}
|
|
227
|
+
const ZERO_USAGE = {
|
|
228
|
+
inputTokens: 0,
|
|
229
|
+
outputTokens: 0,
|
|
230
|
+
cacheReadTokens: 0,
|
|
231
|
+
cacheWriteTokens: 0
|
|
232
|
+
};
|
|
233
|
+
const sumUsage = (usages)=>usages.reduce((sum, usage)=>({
|
|
234
|
+
inputTokens: sum.inputTokens + usage.inputTokens,
|
|
235
|
+
outputTokens: sum.outputTokens + usage.outputTokens,
|
|
236
|
+
cacheReadTokens: sum.cacheReadTokens + usage.cacheReadTokens,
|
|
237
|
+
cacheWriteTokens: sum.cacheWriteTokens + usage.cacheWriteTokens
|
|
238
|
+
}), ZERO_USAGE);
|
|
239
|
+
export async function POST(request) {
|
|
240
|
+
var _ref;
|
|
241
|
+
let body;
|
|
242
|
+
try {
|
|
243
|
+
body = await request.json();
|
|
244
|
+
} catch (unused) {
|
|
245
|
+
body = null;
|
|
246
|
+
}
|
|
247
|
+
const input = parseAiMediaImageInput(body);
|
|
248
|
+
const provider = imageProvider();
|
|
249
|
+
const svgModel = input.mode === 'illustration' ? aiMediaSvgModel() : undefined;
|
|
250
|
+
// A deployment that makes no pictures of this kind has no such door, so
|
|
251
|
+
// nothing about the caller or the workspace is read first.
|
|
252
|
+
if (input.mode === 'photo' && !provider.configured()) {
|
|
253
|
+
return Response.json({
|
|
254
|
+
error: AI_MEDIA_NO_PHOTOS_COPY,
|
|
255
|
+
reason: 'unavailable'
|
|
256
|
+
}, {
|
|
257
|
+
status: 404
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (input.mode === 'illustration' && !svgModel) {
|
|
261
|
+
return Response.json({
|
|
262
|
+
error: 'Not found'
|
|
263
|
+
}, {
|
|
264
|
+
status: 404
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const gate = await aiGateLadder({
|
|
268
|
+
request,
|
|
269
|
+
orgId: input.orgId,
|
|
270
|
+
hostId: input.hostId
|
|
271
|
+
}, {
|
|
272
|
+
feature: 'aiGenerative',
|
|
273
|
+
releaseFlag: 'release_ai_generative',
|
|
274
|
+
lockdownFeature: 'ai-generate',
|
|
275
|
+
permission: 'ai.generate',
|
|
276
|
+
rateLimit: RATE_LIMIT
|
|
277
|
+
});
|
|
278
|
+
if (gate instanceof Response) return gate;
|
|
279
|
+
// Nothing is spent until a provider answers, so every exit before it
|
|
280
|
+
// hands the reservation back.
|
|
281
|
+
const release = ()=>releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(()=>undefined);
|
|
282
|
+
const refuse = async (error, status, extra = {})=>{
|
|
283
|
+
await release();
|
|
284
|
+
return Response.json(_extends({
|
|
285
|
+
error
|
|
286
|
+
}, extra), {
|
|
287
|
+
status
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
if (input.library === 'host' && !input.hostId) {
|
|
291
|
+
return refuse('Open the site whose media library the pictures go in.', 400);
|
|
292
|
+
}
|
|
293
|
+
if (!input.prompt) return refuse('Describe the picture you want first.', 400);
|
|
294
|
+
if (!isAiImageAspectRatio(input.aspectRatio)) {
|
|
295
|
+
return refuse(`Choose a shape: ${AI_IMAGE_ASPECT_RATIOS.join(', ')}.`, 400);
|
|
296
|
+
}
|
|
297
|
+
const aspectRatio = input.aspectRatio;
|
|
298
|
+
if (input.count < 1 || input.count > AI_IMAGE_MAX_COUNT) {
|
|
299
|
+
return refuse(`Make between 1 and ${AI_IMAGE_MAX_COUNT} pictures at a time.`, 400);
|
|
300
|
+
}
|
|
301
|
+
if (input.mode === 'illustration') {
|
|
302
|
+
if (!AI_SVG_STYLES.includes(input.style)) {
|
|
303
|
+
return refuse('Choose an illustration, an icon, a pattern or a logo mark.', 400);
|
|
304
|
+
}
|
|
305
|
+
if (input.palette.source === 'custom' && (!input.palette.colors.length || input.palette.colors.length > AI_SVG_MAX_COLORS || !input.palette.colors.every(isAiSvgColor))) {
|
|
306
|
+
return refuse(`Give between 1 and ${AI_SVG_MAX_COLORS} colors as hex values, such as #1a73e8.`, 400);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const model = input.mode === 'photo' ? provider.defaultModel() : svgModel;
|
|
310
|
+
const perPicture = aiMediaCreditsPerPicture(input.mode, model);
|
|
311
|
+
const estimate = perPicture * input.count;
|
|
312
|
+
const credits = publicAssistQuota(gate.reservation).credits;
|
|
313
|
+
if (credits && assistBandRefuses(gate.org) && credits.remaining < estimate) {
|
|
314
|
+
return refuse(credits.remaining < perPicture ? 'This workspace has used its AI credits for the month.' : `That needs about ${estimate} credits and this workspace has ${credits.remaining} left this month. Make fewer pictures.`, 429, {
|
|
315
|
+
reason: 'quota',
|
|
316
|
+
quota: publicAssistQuota(gate.reservation)
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/** Records what a decline spent: a declined request, never a picture. */ const recordDecline = (usage, declinedModel)=>{
|
|
320
|
+
var _gate_reservation_free;
|
|
321
|
+
return recordAssistCost(gate.firestore, gate.orgId, {
|
|
322
|
+
route: '/api/ai/media/images',
|
|
323
|
+
hostId: input.hostId,
|
|
324
|
+
model: declinedModel,
|
|
325
|
+
tier: 'entitled',
|
|
326
|
+
usage: _extends({}, usage, {
|
|
327
|
+
images: 0
|
|
328
|
+
}),
|
|
329
|
+
docsPaths: [],
|
|
330
|
+
stopReason: 'refusal',
|
|
331
|
+
free: (_gate_reservation_free = gate.reservation.free) != null ? _gate_reservation_free : null,
|
|
332
|
+
uid: gate.uid,
|
|
333
|
+
kind: 'image'
|
|
334
|
+
}).catch((meterError)=>console.error('ai media refusal not recorded', {
|
|
335
|
+
orgId: gate.orgId,
|
|
336
|
+
meterError
|
|
337
|
+
}));
|
|
338
|
+
};
|
|
339
|
+
let made;
|
|
340
|
+
/** Pictures asked for and not made: held back, declined or failed on our side. */ let unmade;
|
|
341
|
+
/** Tokens a photo request spent beside its pictures, charged once any is stored. */ let photoUsage = ZERO_USAGE;
|
|
342
|
+
let declineReason = null;
|
|
343
|
+
let style;
|
|
344
|
+
if (input.mode === 'photo') {
|
|
345
|
+
try {
|
|
346
|
+
const result = await provider.generate({
|
|
347
|
+
model,
|
|
348
|
+
prompt: input.prompt,
|
|
349
|
+
aspectRatio,
|
|
350
|
+
count: input.count
|
|
351
|
+
});
|
|
352
|
+
photoUsage = result.usage;
|
|
353
|
+
unmade = result.filtered;
|
|
354
|
+
made = result.images.map((image)=>_extends({}, image, {
|
|
355
|
+
alt: aiImageAltText(input.prompt),
|
|
356
|
+
usage: ZERO_USAGE
|
|
357
|
+
}));
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (error instanceof AiImageSafetyRefusal) {
|
|
360
|
+
// Declined, not failed: no picture is charged, and the request is
|
|
361
|
+
// recorded as a declined one so the Free taste's refusal pause
|
|
362
|
+
// counts it.
|
|
363
|
+
await recordDecline(ZERO_USAGE, model);
|
|
364
|
+
return Response.json({
|
|
365
|
+
error: error.message,
|
|
366
|
+
reason: 'safety'
|
|
367
|
+
}, {
|
|
368
|
+
status: 422
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
await release();
|
|
372
|
+
console.error('ai photo generation failed', {
|
|
373
|
+
orgId: gate.orgId,
|
|
374
|
+
error
|
|
375
|
+
});
|
|
376
|
+
const status = error instanceof AiUpstreamError && error.accountProblem ? 503 : 502;
|
|
377
|
+
return Response.json({
|
|
378
|
+
error: AI_UPSTREAM_FAILURE_COPY
|
|
379
|
+
}, {
|
|
380
|
+
status
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
var _ref1;
|
|
385
|
+
var _declined_;
|
|
386
|
+
style = input.style;
|
|
387
|
+
const palette = await illustrationPalette(gate, input);
|
|
388
|
+
const settled = await Promise.allSettled(Array.from({
|
|
389
|
+
length: input.count
|
|
390
|
+
}, (_unused, variant)=>generateAiMediaSvg({
|
|
391
|
+
model,
|
|
392
|
+
prompt: input.prompt,
|
|
393
|
+
style: style,
|
|
394
|
+
aspectRatio,
|
|
395
|
+
palette,
|
|
396
|
+
variant,
|
|
397
|
+
count: input.count
|
|
398
|
+
})));
|
|
399
|
+
const outcomes = settled.flatMap((entry)=>entry.status === 'fulfilled' ? [
|
|
400
|
+
entry.value
|
|
401
|
+
] : []);
|
|
402
|
+
if (!outcomes.length) {
|
|
403
|
+
var _settled_;
|
|
404
|
+
await release();
|
|
405
|
+
console.error('ai illustration generation failed', {
|
|
406
|
+
orgId: gate.orgId,
|
|
407
|
+
error: (_settled_ = settled[0]) == null ? void 0 : _settled_.reason
|
|
408
|
+
});
|
|
409
|
+
return Response.json({
|
|
410
|
+
error: AI_UPSTREAM_FAILURE_COPY
|
|
411
|
+
}, {
|
|
412
|
+
status: 502
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
const drawn = outcomes.filter((outcome)=>outcome.kind === 'drawn');
|
|
416
|
+
const declined = outcomes.filter((outcome)=>outcome.kind === 'declined');
|
|
417
|
+
unmade = input.count - drawn.length;
|
|
418
|
+
declineReason = (_ref1 = (_declined_ = declined[0]) == null ? void 0 : _declined_.reason) != null ? _ref1 : null;
|
|
419
|
+
made = drawn.map((outcome)=>({
|
|
420
|
+
base64: Buffer.from(outcome.svg, 'utf8').toString('base64'),
|
|
421
|
+
mimeType: 'image/svg+xml',
|
|
422
|
+
alt: aiImageAltText(outcome.alt || input.prompt),
|
|
423
|
+
usage: outcome.usage
|
|
424
|
+
}));
|
|
425
|
+
if (!made.length) {
|
|
426
|
+
if (declined.length) {
|
|
427
|
+
// The model declined: what it spent is a declined request.
|
|
428
|
+
await recordDecline(sumUsage(declined.map((outcome)=>outcome.usage)), model);
|
|
429
|
+
return Response.json({
|
|
430
|
+
error: declineReason,
|
|
431
|
+
reason: 'safety'
|
|
432
|
+
}, {
|
|
433
|
+
status: 422
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
// Every picture failed its check twice. Ours, so nothing is charged.
|
|
437
|
+
await release();
|
|
438
|
+
return Response.json({
|
|
439
|
+
error: AI_MEDIA_OURS_COPY,
|
|
440
|
+
reason: 'ours'
|
|
441
|
+
}, {
|
|
442
|
+
status: 502
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const stored = [];
|
|
447
|
+
let refusal = null;
|
|
448
|
+
for (const [index, picture] of made.entries()){
|
|
449
|
+
const outcome = await storeThroughUploadRoute(request, input, picture, aiImageFileName(input.prompt, index, picture.mimeType));
|
|
450
|
+
if ('mediaId' in outcome) stored.push({
|
|
451
|
+
mediaId: outcome.mediaId,
|
|
452
|
+
picture
|
|
453
|
+
});
|
|
454
|
+
else refusal != null ? refusal : refusal = outcome;
|
|
455
|
+
}
|
|
456
|
+
if (!stored.length) {
|
|
457
|
+
var _ref2, _ref3;
|
|
458
|
+
// Made and not kept: the library refused every picture (its storage
|
|
459
|
+
// band, a lockdown, a permission), so nothing is charged.
|
|
460
|
+
await release();
|
|
461
|
+
return Response.json({
|
|
462
|
+
error: (_ref2 = refusal == null ? void 0 : refusal.error) != null ? _ref2 : 'The pictures could not be saved.'
|
|
463
|
+
}, {
|
|
464
|
+
status: (_ref3 = refusal == null ? void 0 : refusal.status) != null ? _ref3 : 502
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
// Only what was stored is settled: a photo's pictures at the per-picture
|
|
468
|
+
// rate with the request's prompt and thinking, an illustration's tokens.
|
|
469
|
+
const usage = input.mode === 'photo' ? _extends({}, photoUsage, {
|
|
470
|
+
images: stored.length
|
|
471
|
+
}) : sumUsage(stored.map((entry)=>entry.picture.usage));
|
|
472
|
+
let signalId = null;
|
|
473
|
+
try {
|
|
474
|
+
var _gate_reservation_free;
|
|
475
|
+
signalId = await recordAssistCost(gate.firestore, gate.orgId, {
|
|
476
|
+
route: '/api/ai/media/images',
|
|
477
|
+
hostId: input.hostId,
|
|
478
|
+
model,
|
|
479
|
+
tier: 'entitled',
|
|
480
|
+
usage,
|
|
481
|
+
docsPaths: [],
|
|
482
|
+
stopReason: 'end_turn',
|
|
483
|
+
free: (_gate_reservation_free = gate.reservation.free) != null ? _gate_reservation_free : null,
|
|
484
|
+
uid: gate.uid,
|
|
485
|
+
kind: 'image'
|
|
486
|
+
});
|
|
487
|
+
} catch (error) {
|
|
488
|
+
// The pictures are in the library and were paid for by us; a meter
|
|
489
|
+
// that could not be written is not a reason to hide them.
|
|
490
|
+
console.error('ai media usage not recorded', {
|
|
491
|
+
orgId: gate.orgId,
|
|
492
|
+
error
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
await Promise.all(stored.map(({ mediaId, picture })=>annotateStoredImage(gate, input, mediaId, picture.alt, _extends({
|
|
496
|
+
model,
|
|
497
|
+
prompt: input.prompt,
|
|
498
|
+
mode: input.mode
|
|
499
|
+
}, style ? {
|
|
500
|
+
style
|
|
501
|
+
} : {}, {
|
|
502
|
+
aspectRatio,
|
|
503
|
+
signalId,
|
|
504
|
+
generatedBy: gate.uid
|
|
505
|
+
})).catch((error)=>console.error('ai media alt text not written', {
|
|
506
|
+
mediaId,
|
|
507
|
+
error
|
|
508
|
+
}))));
|
|
509
|
+
const warning = (_ref = refusal == null ? void 0 : refusal.error) != null ? _ref : declineReason && unmade > 0 ? declineReason : unmade > 0 && input.mode === 'illustration' ? AI_MEDIA_OURS_COPY : null;
|
|
510
|
+
return Response.json(_extends({
|
|
511
|
+
mediaIds: stored.map((entry)=>entry.mediaId),
|
|
512
|
+
filtered: unmade,
|
|
513
|
+
failed: made.length - stored.length,
|
|
514
|
+
// What was charged, as the meter priced it.
|
|
515
|
+
credits: assistCreditsFromUsd(estimateAiBilledUsd(usage, model))
|
|
516
|
+
}, warning ? {
|
|
517
|
+
warning
|
|
518
|
+
} : {}));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
//# sourceMappingURL=ai-media-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-media-image.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// lockdown-423: via libs/plugins/ai/src/lib/runtime/ai-gate.ts\n// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict, and\n// every stored picture also passes the upload route's.\n\nimport { mediaFilterKeys } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { MEDIA_ALT_MAX_LENGTH } from '@aglyn/aglyn/app-utils/media-alt'\nimport { aiMediaCreditsPerPicture } from '../model/ai-media-credits'\nimport { aiSvgThemePalette, isAiSvgColor } from '../model/ai-svg'\nimport { estimateAiBilledUsd } from '../providers/catalog'\nimport { AI_UPSTREAM_FAILURE_COPY, AiUpstreamError, type AiUsage } from '../providers/contract'\nimport {\n AI_IMAGE_ASPECT_RATIOS,\n AI_IMAGE_MAX_COUNT,\n AI_IMAGE_PROMPT_MAX_CHARS,\n AI_SVG_MAX_COLORS,\n AI_SVG_STYLES,\n AiImageSafetyRefusal,\n isAiImageAspectRatio,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiImageProvider,\n type AiSvgStyle,\n} from '../providers/image-contract'\nimport { vertexImageProvider } from '../providers/vertex-image'\nimport { aiGateLadder, type AiGateContext } from '../runtime/ai-gate'\nimport { aiInventoryTheme } from '../runtime/site-inventory'\nimport { assistBandRefuses, assistCreditsFromUsd } from '../usage/assist-credits'\nimport {\n publicAssistQuota,\n recordAssistCost,\n releaseAssistMessage,\n} from '../usage/assist-usage'\nimport { aiMediaSvgModel, generateAiMediaSvg, type AiMediaSvgOutcome } from './ai-media-svg'\n\n/**\n * \"Create with AI\" in Media (AGL-3602): pictures from a description, stored\n * in the library that asked.\n *\n * `POST /api/ai/media/images { orgId, library, hostId?, folderId?, mode,\n * prompt, aspectRatio, count, style?, palette? }` answers\n * `{ mediaIds, filtered, failed, credits, warning? }`.\n *\n * Two modes:\n *\n * - `photo` — Google's image models on Vertex AI (`vertex-image.ts`). Off\n * unless the deployment configured them; the door answers 404 with a\n * sentence the dialog shows.\n * - `illustration` — an SVG drawn by the text provider every other AI door\n * uses (`ai-media-svg.ts`): an illustration, an icon, a tileable pattern or\n * a simple logo mark, in the site's theme colors or the person's own. No\n * new vendor, so it runs wherever text AI does.\n *\n * There is no GET: the button draws from the shell's own gates, and this door\n * decides when someone asks for a picture.\n *\n * ## A request, not a job\n *\n * Both modes answer inside the request, the way \"Save as a component with\n * AI\" does, and are metered on the same meter every door uses.\n *\n * ## The money, in order\n *\n * 1. `aiGateLadder` RESERVES before anything is spent: the `aiGenerative`\n * entitlement, `release_ai_generative`, the `ai-generate` kill switch,\n * `ai.generate` on the site, the per-account and per-address windows, the\n * Free taste's own rungs and the band.\n * 2. A workspace whose band is a wall (Free, or a hard cap) is refused before\n * the provider is called when the pictures' estimate is more than it has\n * left.\n * 3. The provider is called. A fault of ours or the provider's hands the\n * reservation back and charges nothing. A declined description charges\n * only what the decline spent, recorded as a declined request (which a\n * Free workspace is never charged for, and which its daily refusal pause\n * counts).\n * 4. Each picture is stored, and only what was stored is SETTLED, at billed\n * rates above the provider's: a photo per picture plus its prompt and\n * thinking, an illustration as the tokens that drew it. A picture held\n * back, one the library refused, and an illustration that failed its\n * safety check twice are never charged — that last one is ours.\n *\n * ## Stored the way any upload is stored\n *\n * Each picture is posted to the console's own `/api/media/upload` with the\n * caller's credential, so the scope, the uploads lockdown, the structural\n * inspection, SVG sanitizing, the quarantine list, the storage band, the CDN\n * variants and the storage counter all apply exactly as they do to a file\n * the person dropped in. This door writes no media document of its own; it\n * only adds, to the documents that route made, the alt text and the record\n * of how the picture was made.\n */\n\n/** The per-account window: a person makes a few sets a minute, not dozens. */\nconst RATE_LIMIT = { key: 'ai-media-image', limit: 6, windowMs: 60_000 }\n\n/** What a person reads when a picture failed on our side. */\nexport const AI_MEDIA_OURS_COPY =\n \"This one's on us — you weren't charged. The picture didn't come out right; try again, or describe it a little differently.\"\n\n/** What the door answers on a deployment that makes no photos. */\nexport const AI_MEDIA_NO_PHOTOS_COPY =\n \"Photos aren't available here yet. Choose Illustration to draw one instead.\"\n\n/** The provider photos come from; a spec stands one in through jest. */\nfunction imageProvider(): AiImageProvider {\n return vertexImageProvider\n}\n\n/** Every control character as a space, squeezed, trimmed and cut. */\nexport function cleanAiImagePrompt(value: unknown): string {\n let out = ''\n for (const char of String(value ?? '')) {\n const code = char.charCodeAt(0)\n out += code < 32 || code === 127 || code === 0x2028 || code === 0x2029 ? ' ' : char\n }\n return out.replace(/\\s+/g, ' ').trim().slice(0, AI_IMAGE_PROMPT_MAX_CHARS)\n}\n\n/**\n * The alt text a picture is stored with: the sentence given, as a sentence,\n * cut at a word inside the library's alt limit. A person edits it like any\n * other.\n */\nexport function aiImageAltText(text: string): string {\n const clean = text.replace(/\\s+/g, ' ').trim()\n if (!clean) return ''\n const sentence = clean.charAt(0).toUpperCase() + clean.slice(1)\n if (sentence.length <= MEDIA_ALT_MAX_LENGTH) return sentence\n const cut = sentence.slice(0, MEDIA_ALT_MAX_LENGTH - 1)\n const space = cut.lastIndexOf(' ')\n return `${(space > 40 ? cut.slice(0, space) : cut).trim()}…`\n}\n\nconst EXTENSIONS: Readonly<Record<string, string>> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/svg+xml': 'svg',\n}\n\n/** A file name from the description's first words: `ai-a-red-barn-2.png`. */\nexport function aiImageFileName(prompt: string, index: number, mimeType: string): string {\n const words = prompt\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[^a-z0-9\\s-]/g, ' ')\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 6)\n .join('-')\n .slice(0, 60)\n .replace(/-+$/, '')\n return `ai-${words || 'image'}-${index + 1}.${EXTENSIONS[mimeType] ?? 'png'}`\n}\n\n/** What the library asked for, read and bounded. */\nexport interface AiMediaImageInput {\n orgId: string\n library: 'host' | 'org'\n /** The site whose library this is, or the site on screen for the org's. */\n hostId: string | null\n folderId: string | null\n mode: AiImageMode\n prompt: string\n aspectRatio: string\n count: number\n /** An illustration's kind; unread for a photo. */\n style: string\n /** An illustration's colors: the site theme's, or these. */\n palette: { source: 'theme' } | { source: 'custom'; colors: string[] }\n}\n\nexport function parseAiMediaImageInput(body: Record<string, unknown> | null): AiMediaImageInput {\n const text = (key: string, limit = 128) => {\n const value = body?.[key]\n return typeof value === 'string' ? value.trim().slice(0, limit) : ''\n }\n const count = Math.floor(Number(body?.['count'] ?? 1))\n const palette = body?.['palette'] as Record<string, unknown> | undefined\n const colors = Array.isArray(palette?.['colors']) ? (palette?.['colors'] as unknown[]) : []\n return {\n orgId: text('orgId'),\n library: body?.['library'] === 'org' ? 'org' : 'host',\n hostId: text('hostId') || null,\n folderId: text('folderId', 64) || null,\n mode: body?.['mode'] === 'illustration' ? 'illustration' : 'photo',\n prompt: cleanAiImagePrompt(body?.['prompt']),\n aspectRatio: text('aspectRatio', 8),\n count: Number.isFinite(count) ? count : 0,\n style: text('style', 24) || 'illustration',\n palette:\n palette?.['source'] === 'custom'\n ? {\n source: 'custom',\n colors: colors\n .map((color) => String(color ?? '').trim().toLowerCase())\n .slice(0, AI_SVG_MAX_COLORS + 1),\n }\n : { source: 'theme' },\n }\n}\n\n/** One stored picture, or the upload route's refusal. */\ntype StoreOutcome = { mediaId: string } | { error: string; status: number }\n\n/**\n * Posts one picture to the console's own upload route, as the caller. The\n * same body the library's Upload button sends, so the route cannot tell the\n * two apart and applies every check it applies to that one.\n */\nasync function storeThroughUploadRoute(\n request: Request,\n input: AiMediaImageInput,\n image: { base64: string; mimeType: string },\n fileName: string,\n): Promise<StoreOutcome> {\n const scope =\n input.library === 'org'\n ? { orgId: input.orgId, ...(input.hostId ? { forHostId: input.hostId } : {}) }\n : { hostId: input.hostId }\n let response: Response\n try {\n response = await fetch(new URL('/api/media/upload', request.url), {\n method: 'POST',\n headers: {\n Authorization: request.headers.get('authorization') ?? '',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...scope,\n fileName,\n contentType: image.mimeType,\n folderId: input.folderId,\n data: image.base64,\n }),\n })\n } catch {\n return { error: 'The picture could not be saved to the media library.', status: 502 }\n }\n const payload = (await response.json().catch(() => ({}))) as Record<string, unknown>\n const mediaId = typeof payload['mediaId'] === 'string' ? payload['mediaId'] : ''\n if (response.ok && mediaId) return { mediaId }\n return {\n error:\n typeof payload['error'] === 'string'\n ? payload['error']\n : 'The picture could not be saved to the media library.',\n status: response.ok ? 502 : response.status,\n }\n}\n\n/** The record a generated picture keeps of how it was made. */\nexport interface AiMediaProvenance {\n model: string\n prompt: string\n mode: AiImageMode\n /** An illustration's kind; absent on a photo. */\n style?: AiSvgStyle\n aspectRatio: string\n /** The meter's signal id for the request that made it. */\n signalId: string | null\n generatedBy: string\n}\n\n/**\n * Adds the alt text and the provenance to a document the upload route made.\n * An `update`, which cannot mint a document: the route minted it.\n */\nasync function annotateStoredImage(\n gate: AiGateContext,\n input: AiMediaImageInput,\n mediaId: string,\n alt: string,\n provenance: AiMediaProvenance,\n): Promise<void> {\n const scopeRef =\n input.library === 'org'\n ? gate.firestore.collection('orgs').doc(input.orgId)\n : gate.firestore.collection('hosts').doc(String(input.hostId))\n const mediaRef = scopeRef.collection('media').doc(mediaId)\n const stored = ((await mediaRef.get()).data() ?? {}) as Parameters<typeof mediaFilterKeys>[0]\n await mediaRef.update({\n alt,\n description: 'Created with AI from a description.',\n aiGenerated: { ...provenance, createdAt: new Date() },\n // What the library filters by, restamped with the alt now set: the upload\n // route wrote \"Alt text is missing\" before the alt existed.\n ...mediaFilterKeys({ ...stored, alt }),\n })\n}\n\n/** The colors an illustration is drawn in. */\nasync function illustrationPalette(\n gate: AiGateContext,\n input: AiMediaImageInput,\n): Promise<string[]> {\n if (input.palette.source === 'custom') return input.palette.colors\n if (!input.hostId) return []\n const snapshot = await gate.firestore.collection('hosts').doc(input.hostId).get()\n return aiSvgThemePalette(aiInventoryTheme(snapshot.exists ? snapshot.data() ?? null : null)?.colors)\n}\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst sumUsage = (usages: readonly AiUsage[]): AiUsage =>\n usages.reduce<AiUsage>(\n (sum, usage) => ({\n inputTokens: sum.inputTokens + usage.inputTokens,\n outputTokens: sum.outputTokens + usage.outputTokens,\n cacheReadTokens: sum.cacheReadTokens + usage.cacheReadTokens,\n cacheWriteTokens: sum.cacheWriteTokens + usage.cacheWriteTokens,\n }),\n ZERO_USAGE,\n )\n\n/** A picture made, before it is stored. */\ninterface MadePicture {\n base64: string\n mimeType: string\n alt: string\n /** What making it spent, charged only once it is stored. */\n usage: AiUsage\n}\n\nexport async function POST(request: Request): Promise<Response> {\n let body: Record<string, unknown> | null\n try {\n body = (await request.json()) as Record<string, unknown>\n } catch {\n body = null\n }\n const input = parseAiMediaImageInput(body)\n const provider = imageProvider()\n const svgModel = input.mode === 'illustration' ? aiMediaSvgModel() : undefined\n // A deployment that makes no pictures of this kind has no such door, so\n // nothing about the caller or the workspace is read first.\n if (input.mode === 'photo' && !provider.configured()) {\n return Response.json({ error: AI_MEDIA_NO_PHOTOS_COPY, reason: 'unavailable' }, { status: 404 })\n }\n if (input.mode === 'illustration' && !svgModel) {\n return Response.json({ error: 'Not found' }, { status: 404 })\n }\n\n const gate = await aiGateLadder(\n { request, orgId: input.orgId, hostId: input.hostId },\n {\n feature: 'aiGenerative',\n releaseFlag: 'release_ai_generative',\n lockdownFeature: 'ai-generate',\n permission: 'ai.generate',\n rateLimit: RATE_LIMIT,\n },\n )\n if (gate instanceof Response) return gate\n // Nothing is spent until a provider answers, so every exit before it\n // hands the reservation back.\n const release = () =>\n releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(() => undefined)\n const refuse = async (error: string, status: number, extra: Record<string, unknown> = {}) => {\n await release()\n return Response.json({ error, ...extra }, { status })\n }\n\n if (input.library === 'host' && !input.hostId) {\n return refuse('Open the site whose media library the pictures go in.', 400)\n }\n if (!input.prompt) return refuse('Describe the picture you want first.', 400)\n if (!isAiImageAspectRatio(input.aspectRatio)) {\n return refuse(`Choose a shape: ${AI_IMAGE_ASPECT_RATIOS.join(', ')}.`, 400)\n }\n const aspectRatio: AiImageAspectRatio = input.aspectRatio\n if (input.count < 1 || input.count > AI_IMAGE_MAX_COUNT) {\n return refuse(`Make between 1 and ${AI_IMAGE_MAX_COUNT} pictures at a time.`, 400)\n }\n if (input.mode === 'illustration') {\n if (!(AI_SVG_STYLES as readonly string[]).includes(input.style)) {\n return refuse('Choose an illustration, an icon, a pattern or a logo mark.', 400)\n }\n if (\n input.palette.source === 'custom' &&\n (!input.palette.colors.length ||\n input.palette.colors.length > AI_SVG_MAX_COLORS ||\n !input.palette.colors.every(isAiSvgColor))\n ) {\n return refuse(`Give between 1 and ${AI_SVG_MAX_COLORS} colors as hex values, such as #1a73e8.`, 400)\n }\n }\n\n const model = input.mode === 'photo' ? provider.defaultModel() : (svgModel as string)\n const perPicture = aiMediaCreditsPerPicture(input.mode, model)\n const estimate = perPicture * input.count\n const credits = publicAssistQuota(gate.reservation).credits\n if (credits && assistBandRefuses(gate.org) && credits.remaining < estimate) {\n return refuse(\n credits.remaining < perPicture\n ? 'This workspace has used its AI credits for the month.'\n : `That needs about ${estimate} credits and this workspace has ${credits.remaining} left this month. Make fewer pictures.`,\n 429,\n { reason: 'quota', quota: publicAssistQuota(gate.reservation) },\n )\n }\n\n /** Records what a decline spent: a declined request, never a picture. */\n const recordDecline = (usage: AiUsage, declinedModel: string) =>\n recordAssistCost(gate.firestore, gate.orgId, {\n route: '/api/ai/media/images',\n hostId: input.hostId,\n model: declinedModel,\n tier: 'entitled',\n usage: { ...usage, images: 0 },\n docsPaths: [],\n stopReason: 'refusal',\n free: gate.reservation.free ?? null,\n uid: gate.uid,\n kind: 'image',\n }).catch((meterError) =>\n console.error('ai media refusal not recorded', { orgId: gate.orgId, meterError }),\n )\n\n let made: MadePicture[]\n /** Pictures asked for and not made: held back, declined or failed on our side. */\n let unmade: number\n /** Tokens a photo request spent beside its pictures, charged once any is stored. */\n let photoUsage: AiUsage = ZERO_USAGE\n let declineReason: string | null = null\n let style: AiSvgStyle | undefined\n\n if (input.mode === 'photo') {\n try {\n const result = await provider.generate({\n model,\n prompt: input.prompt,\n aspectRatio,\n count: input.count,\n })\n photoUsage = result.usage\n unmade = result.filtered\n made = result.images.map((image) => ({\n ...image,\n alt: aiImageAltText(input.prompt),\n usage: ZERO_USAGE,\n }))\n } catch (error) {\n if (error instanceof AiImageSafetyRefusal) {\n // Declined, not failed: no picture is charged, and the request is\n // recorded as a declined one so the Free taste's refusal pause\n // counts it.\n await recordDecline(ZERO_USAGE, model)\n return Response.json({ error: error.message, reason: 'safety' }, { status: 422 })\n }\n await release()\n console.error('ai photo generation failed', { orgId: gate.orgId, error })\n const status = error instanceof AiUpstreamError && error.accountProblem ? 503 : 502\n return Response.json({ error: AI_UPSTREAM_FAILURE_COPY }, { status })\n }\n } else {\n style = input.style as AiSvgStyle\n const palette = await illustrationPalette(gate, input)\n const settled = await Promise.allSettled(\n Array.from({ length: input.count }, (_unused, variant) =>\n generateAiMediaSvg({\n model,\n prompt: input.prompt,\n style: style as AiSvgStyle,\n aspectRatio,\n palette,\n variant,\n count: input.count,\n }),\n ),\n )\n const outcomes = settled.flatMap((entry) =>\n entry.status === 'fulfilled' ? [entry.value] : [],\n )\n if (!outcomes.length) {\n await release()\n console.error('ai illustration generation failed', {\n orgId: gate.orgId,\n error: (settled[0] as PromiseRejectedResult | undefined)?.reason,\n })\n return Response.json({ error: AI_UPSTREAM_FAILURE_COPY }, { status: 502 })\n }\n const drawn = outcomes.filter(\n (outcome): outcome is Extract<AiMediaSvgOutcome, { kind: 'drawn' }> => outcome.kind === 'drawn',\n )\n const declined = outcomes.filter((outcome) => outcome.kind === 'declined')\n unmade = input.count - drawn.length\n declineReason =\n (declined[0] as Extract<AiMediaSvgOutcome, { kind: 'declined' }> | undefined)?.reason ?? null\n made = drawn.map((outcome) => ({\n base64: Buffer.from(outcome.svg, 'utf8').toString('base64'),\n mimeType: 'image/svg+xml',\n alt: aiImageAltText(outcome.alt || input.prompt),\n usage: outcome.usage,\n }))\n if (!made.length) {\n if (declined.length) {\n // The model declined: what it spent is a declined request.\n await recordDecline(sumUsage(declined.map((outcome) => outcome.usage)), model)\n return Response.json({ error: declineReason, reason: 'safety' }, { status: 422 })\n }\n // Every picture failed its check twice. Ours, so nothing is charged.\n await release()\n return Response.json({ error: AI_MEDIA_OURS_COPY, reason: 'ours' }, { status: 502 })\n }\n }\n\n const stored: Array<{ mediaId: string; picture: MadePicture }> = []\n let refusal: { error: string; status: number } | null = null\n for (const [index, picture] of made.entries()) {\n const outcome = await storeThroughUploadRoute(\n request,\n input,\n picture,\n aiImageFileName(input.prompt, index, picture.mimeType),\n )\n if ('mediaId' in outcome) stored.push({ mediaId: outcome.mediaId, picture })\n else refusal ??= outcome\n }\n\n if (!stored.length) {\n // Made and not kept: the library refused every picture (its storage\n // band, a lockdown, a permission), so nothing is charged.\n await release()\n return Response.json(\n { error: refusal?.error ?? 'The pictures could not be saved.' },\n { status: refusal?.status ?? 502 },\n )\n }\n\n // Only what was stored is settled: a photo's pictures at the per-picture\n // rate with the request's prompt and thinking, an illustration's tokens.\n const usage =\n input.mode === 'photo'\n ? { ...photoUsage, images: stored.length }\n : sumUsage(stored.map((entry) => entry.picture.usage))\n let signalId: string | null = null\n try {\n signalId = await recordAssistCost(gate.firestore, gate.orgId, {\n route: '/api/ai/media/images',\n hostId: input.hostId,\n model,\n tier: 'entitled',\n usage,\n docsPaths: [],\n stopReason: 'end_turn',\n free: gate.reservation.free ?? null,\n uid: gate.uid,\n kind: 'image',\n })\n } catch (error) {\n // The pictures are in the library and were paid for by us; a meter\n // that could not be written is not a reason to hide them.\n console.error('ai media usage not recorded', { orgId: gate.orgId, error })\n }\n\n await Promise.all(\n stored.map(({ mediaId, picture }) =>\n annotateStoredImage(gate, input, mediaId, picture.alt, {\n model,\n prompt: input.prompt,\n mode: input.mode,\n ...(style ? { style } : {}),\n aspectRatio,\n signalId,\n generatedBy: gate.uid,\n }).catch((error) => console.error('ai media alt text not written', { mediaId, error })),\n ),\n )\n\n const warning =\n refusal?.error ??\n (declineReason && unmade > 0 ? declineReason : unmade > 0 && input.mode === 'illustration'\n ? AI_MEDIA_OURS_COPY\n : null)\n return Response.json({\n mediaIds: stored.map((entry) => entry.mediaId),\n filtered: unmade,\n failed: made.length - stored.length,\n // What was charged, as the meter priced it.\n credits: assistCreditsFromUsd(estimateAiBilledUsd(usage, model)),\n ...(warning ? { warning } : {}),\n })\n}\n"],"names":["mediaFilterKeys","MEDIA_ALT_MAX_LENGTH","aiMediaCreditsPerPicture","aiSvgThemePalette","isAiSvgColor","estimateAiBilledUsd","AI_UPSTREAM_FAILURE_COPY","AiUpstreamError","AI_IMAGE_ASPECT_RATIOS","AI_IMAGE_MAX_COUNT","AI_IMAGE_PROMPT_MAX_CHARS","AI_SVG_MAX_COLORS","AI_SVG_STYLES","AiImageSafetyRefusal","isAiImageAspectRatio","vertexImageProvider","aiGateLadder","aiInventoryTheme","assistBandRefuses","assistCreditsFromUsd","publicAssistQuota","recordAssistCost","releaseAssistMessage","aiMediaSvgModel","generateAiMediaSvg","RATE_LIMIT","key","limit","windowMs","AI_MEDIA_OURS_COPY","AI_MEDIA_NO_PHOTOS_COPY","imageProvider","cleanAiImagePrompt","value","out","char","String","code","charCodeAt","replace","trim","slice","aiImageAltText","text","clean","sentence","charAt","toUpperCase","length","cut","space","lastIndexOf","EXTENSIONS","aiImageFileName","prompt","index","mimeType","words","toLowerCase","normalize","split","filter","Boolean","join","parseAiMediaImageInput","body","count","Math","floor","Number","palette","colors","Array","isArray","orgId","library","hostId","folderId","mode","aspectRatio","isFinite","style","source","map","color","storeThroughUploadRoute","request","input","image","fileName","scope","forHostId","response","fetch","URL","url","method","headers","Authorization","get","JSON","stringify","contentType","data","base64","error","status","payload","json","catch","mediaId","ok","annotateStoredImage","gate","alt","provenance","scopeRef","firestore","collection","doc","mediaRef","stored","update","description","aiGenerated","createdAt","Date","illustrationPalette","snapshot","exists","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","sumUsage","usages","reduce","sum","usage","POST","provider","svgModel","undefined","configured","Response","reason","feature","releaseFlag","lockdownFeature","permission","rateLimit","release","reservation","refuse","extra","includes","every","model","defaultModel","perPicture","estimate","credits","org","remaining","quota","recordDecline","declinedModel","route","tier","images","docsPaths","stopReason","free","uid","kind","meterError","console","made","unmade","photoUsage","declineReason","result","generate","filtered","message","accountProblem","declined","settled","Promise","allSettled","from","_unused","variant","outcomes","flatMap","entry","drawn","outcome","Buffer","svg","toString","refusal","picture","entries","push","signalId","all","generatedBy","warning","mediaIds","failed"],"mappings":"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"}
|