@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.232
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 +8 -4
- 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-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- 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-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +101 -7
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.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.js +14 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- 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 +29 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
- package/src/lib/jobs/ai-job-layout-language.js +200 -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 +107 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
- package/src/lib/jobs/ai-job-page-language.js +418 -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 +115 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
- package/src/lib/jobs/ai-job-plan-step.js +62 -22
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
- package/src/lib/jobs/ai-job-site-step.js +183 -27
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
- package/src/lib/jobs/ai-jobs-notify.js +15 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- 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/layout-language/ai-layout-compiler.d.ts +154 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1235 -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 +458 -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 +168 -0
- package/src/lib/layout-language/ai-layout-language.js +529 -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-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
- package/src/lib/layout-language/ai-layout-store.js +75 -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-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +52 -7
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.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-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +23 -1
- 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 +22 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +9 -1
- package/src/lib/model/ai-site-build-progress.js +54 -14
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +18 -2
- package/src/lib/model/ai-site-job.js +21 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +15 -3
- package/src/lib/model/ai-site-start.js +21 -4
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +99 -48
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +38 -8
- package/src/lib/providers/catalog.js +106 -14
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- 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-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +16 -3
- 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 +13 -1
- package/src/lib/runtime/ai-eval.js +84 -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-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.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/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { pluginRequestFromWeb } from "@aglyn/aglyn/server";
|
|
17
|
+
import { emailUnverifiedResponse, firebaseAdmin, isImpersonationSession } from "@aglyn/tenant-data-admin";
|
|
18
|
+
import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
|
|
19
|
+
import { composeStaffAiAssistRow, composeStaffAiJobRow, isStaffAiConversationKind, STAFF_AI_CONVERSATIONS_PAGE, staffAiCrmResult, staffAiInsightResult, staffAiJobResultCollection } from "../usage/staff-org-ai-conversations.js";
|
|
20
|
+
/**
|
|
21
|
+
* `/api/ai/admin/conversations` (AGL-3675): one page of what an org asked
|
|
22
|
+
* Aglyn AI and what it answered, for the staff org page.
|
|
23
|
+
*
|
|
24
|
+
* `?orgId=` and `?kind=assist|jobs`, newest first, and `?after=<id>` for the
|
|
25
|
+
* next page. Staff-gated exactly as `/api/ai/admin/org` is. The collections
|
|
26
|
+
* it reads have no client rules at all — default-deny for every browser —
|
|
27
|
+
* which is why the read is here, on the Admin SDK.
|
|
28
|
+
*
|
|
29
|
+
* ## Every page read is audited, and the audit holds no text
|
|
30
|
+
*
|
|
31
|
+
* Unlike the usage card, which loads with the page and so writes no audit
|
|
32
|
+
* row, this route returns what a person typed, and staff read it only when
|
|
33
|
+
* they ask to. Each page writes an `org.ai-conversations-viewed` access row
|
|
34
|
+
* naming who read which list of which org and how many rows came back. The
|
|
35
|
+
* Privacy policy says our audit log records an AI job "without the text of
|
|
36
|
+
* its brief or of what it wrote", so the row carries none of it.
|
|
37
|
+
*/ /** Members read to name who asked; past it a row shows the uid alone. */ const MEMBERS_SCAN = 500;
|
|
38
|
+
const ACTION = 'org.ai-conversations-viewed';
|
|
39
|
+
/** One page of a collection under the org, newest first, after the document `after` names. */ async function readPage(orgRef, collection, after) {
|
|
40
|
+
var _ref;
|
|
41
|
+
var _docs_;
|
|
42
|
+
let query = orgRef.collection(collection).orderBy('createdAt', 'desc').limit(STAFF_AI_CONVERSATIONS_PAGE + 1);
|
|
43
|
+
if (after) {
|
|
44
|
+
const cursor = await orgRef.collection(collection).doc(after).get();
|
|
45
|
+
if (cursor.exists) query = query.startAfter(cursor);
|
|
46
|
+
}
|
|
47
|
+
const snapshot = await query.get();
|
|
48
|
+
const docs = snapshot.docs.slice(0, STAFF_AI_CONVERSATIONS_PAGE);
|
|
49
|
+
const next = snapshot.docs.length > STAFF_AI_CONVERSATIONS_PAGE ? (_ref = (_docs_ = docs[docs.length - 1]) == null ? void 0 : _docs_.id) != null ? _ref : null : null;
|
|
50
|
+
return {
|
|
51
|
+
docs,
|
|
52
|
+
next
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function readMembers(orgRef) {
|
|
56
|
+
const snapshot = await orgRef.collection('members').limit(MEMBERS_SCAN).get();
|
|
57
|
+
return new Map(snapshot.docs.map((doc)=>{
|
|
58
|
+
var _doc_data;
|
|
59
|
+
return [
|
|
60
|
+
doc.id,
|
|
61
|
+
(_doc_data = doc.data()) != null ? _doc_data : {}
|
|
62
|
+
];
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
/** The documents `refs` name, by id, in one round trip; a missing one is absent. */ async function readAll(db, refs) {
|
|
66
|
+
if (!refs.length) return new Map();
|
|
67
|
+
const snapshots = await db.getAll(...refs);
|
|
68
|
+
return new Map(snapshots.filter((snapshot)=>snapshot.exists).map((snapshot)=>{
|
|
69
|
+
var _snapshot_data;
|
|
70
|
+
return [
|
|
71
|
+
snapshot.id,
|
|
72
|
+
(_snapshot_data = snapshot.data()) != null ? _snapshot_data : {}
|
|
73
|
+
];
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
/** One page of the org's conversations of `kind`. Exported for the spec. */ export async function readStaffAiConversations(db, orgId, kind, after) {
|
|
77
|
+
const orgRef = db.collection('orgs').doc(orgId);
|
|
78
|
+
if (kind === 'assist') {
|
|
79
|
+
const [{ docs, next }, members] = await Promise.all([
|
|
80
|
+
readPage(orgRef, 'assistExchanges', after),
|
|
81
|
+
readMembers(orgRef)
|
|
82
|
+
]);
|
|
83
|
+
// The signal shares the exchange's id (AGL-1972): the route, the model, the cost and the thumbs.
|
|
84
|
+
const signals = await readAll(db, docs.map((doc)=>orgRef.collection('assistSignals').doc(doc.id)));
|
|
85
|
+
return {
|
|
86
|
+
kind,
|
|
87
|
+
rows: docs.map((doc)=>{
|
|
88
|
+
var _doc_data, _signals_get;
|
|
89
|
+
return composeStaffAiAssistRow(doc.id, (_doc_data = doc.data()) != null ? _doc_data : {}, (_signals_get = signals.get(doc.id)) != null ? _signals_get : null, members);
|
|
90
|
+
}),
|
|
91
|
+
next
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const [{ docs, next }, members] = await Promise.all([
|
|
95
|
+
readPage(orgRef, 'aiJobs', after),
|
|
96
|
+
readMembers(orgRef)
|
|
97
|
+
]);
|
|
98
|
+
const insightIds = docs.filter((doc)=>staffAiJobResultCollection(doc.get('kind')) === 'aiInsights').map((doc)=>doc.id);
|
|
99
|
+
const crmIds = docs.filter((doc)=>staffAiJobResultCollection(doc.get('kind')) === 'aiCrmAnswers').map((doc)=>doc.id);
|
|
100
|
+
const [insights, crm] = await Promise.all([
|
|
101
|
+
readAll(db, insightIds.map((id)=>orgRef.collection('aiInsights').doc(id))),
|
|
102
|
+
readAll(db, crmIds.map((id)=>orgRef.collection('aiCrmAnswers').doc(id)))
|
|
103
|
+
]);
|
|
104
|
+
return {
|
|
105
|
+
kind,
|
|
106
|
+
rows: docs.map((doc)=>{
|
|
107
|
+
var _insights_get, _crm_get, _doc_data;
|
|
108
|
+
const collection = staffAiJobResultCollection(doc.get('kind'));
|
|
109
|
+
const result = collection === 'aiInsights' ? staffAiInsightResult((_insights_get = insights.get(doc.id)) != null ? _insights_get : null) : collection === 'aiCrmAnswers' ? staffAiCrmResult((_crm_get = crm.get(doc.id)) != null ? _crm_get : null) : null;
|
|
110
|
+
return composeStaffAiJobRow(doc.id, (_doc_data = doc.data()) != null ? _doc_data : {}, result, members);
|
|
111
|
+
}),
|
|
112
|
+
next
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function handler(request) {
|
|
116
|
+
var _headers_authorization, _query_orgId;
|
|
117
|
+
const { method, query, headers: rawHeaders } = await pluginRequestFromWeb(request);
|
|
118
|
+
const headers = rawHeaders;
|
|
119
|
+
if (method !== 'GET') {
|
|
120
|
+
return Response.json({
|
|
121
|
+
error: 'Method not allowed'
|
|
122
|
+
}, {
|
|
123
|
+
status: 405
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const authorization = (_headers_authorization = headers.authorization) != null ? _headers_authorization : '';
|
|
127
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
128
|
+
if (!idToken) return Response.json({
|
|
129
|
+
error: 'Unauthenticated'
|
|
130
|
+
}, {
|
|
131
|
+
status: 401
|
|
132
|
+
});
|
|
133
|
+
const orgId = String((_query_orgId = query.orgId) != null ? _query_orgId : '');
|
|
134
|
+
if (!orgId) return Response.json({
|
|
135
|
+
error: 'Missing orgId'
|
|
136
|
+
}, {
|
|
137
|
+
status: 400
|
|
138
|
+
});
|
|
139
|
+
const kind = query.kind;
|
|
140
|
+
if (!isStaffAiConversationKind(kind)) {
|
|
141
|
+
return Response.json({
|
|
142
|
+
error: 'kind must be assist or jobs'
|
|
143
|
+
}, {
|
|
144
|
+
status: 400
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const after = typeof query.after === 'string' && query.after ? query.after : null;
|
|
148
|
+
try {
|
|
149
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
150
|
+
if (!decoded.email_verified && !isImpersonationSession(decoded)) {
|
|
151
|
+
return emailUnverifiedResponse();
|
|
152
|
+
}
|
|
153
|
+
if (!decoded['staff']) {
|
|
154
|
+
return Response.json({
|
|
155
|
+
error: 'Staff only'
|
|
156
|
+
}, {
|
|
157
|
+
status: 403
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const db = firebaseAdmin.app().firestore();
|
|
161
|
+
if (!(await db.collection('orgs').doc(orgId).get()).exists) {
|
|
162
|
+
return Response.json({
|
|
163
|
+
error: 'No such organization'
|
|
164
|
+
}, {
|
|
165
|
+
status: 404
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const page = await readStaffAiConversations(db, orgId, kind, after);
|
|
169
|
+
// Written before the words leave: a read the log cannot record is not served.
|
|
170
|
+
await addAdminAudit(db, {
|
|
171
|
+
actorUid: decoded.uid,
|
|
172
|
+
action: ACTION,
|
|
173
|
+
target: `orgs/${orgId}`,
|
|
174
|
+
before: null,
|
|
175
|
+
after: {
|
|
176
|
+
kind,
|
|
177
|
+
rows: page.rows.length,
|
|
178
|
+
after
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return Response.json(page, {
|
|
182
|
+
headers: {
|
|
183
|
+
'Cache-Control': 'no-store'
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error('[ai/admin/conversations]', error);
|
|
188
|
+
return Response.json({
|
|
189
|
+
error: 'The conversations could not be read.'
|
|
190
|
+
}, {
|
|
191
|
+
status: 500
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export { handler as GET };
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=ai-admin-conversations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-admin-conversations.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\nimport { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n} from '@aglyn/tenant-data-admin'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\nimport {\n composeStaffAiAssistRow,\n composeStaffAiJobRow,\n isStaffAiConversationKind,\n STAFF_AI_CONVERSATIONS_PAGE,\n staffAiCrmResult,\n staffAiInsightResult,\n staffAiJobResultCollection,\n type StaffAiConversationKind,\n type StaffAiConversationsResponse,\n} from '../usage/staff-org-ai-conversations'\n\n/**\n * `/api/ai/admin/conversations` (AGL-3675): one page of what an org asked\n * Aglyn AI and what it answered, for the staff org page.\n *\n * `?orgId=` and `?kind=assist|jobs`, newest first, and `?after=<id>` for the\n * next page. Staff-gated exactly as `/api/ai/admin/org` is. The collections\n * it reads have no client rules at all — default-deny for every browser —\n * which is why the read is here, on the Admin SDK.\n *\n * ## Every page read is audited, and the audit holds no text\n *\n * Unlike the usage card, which loads with the page and so writes no audit\n * row, this route returns what a person typed, and staff read it only when\n * they ask to. Each page writes an `org.ai-conversations-viewed` access row\n * naming who read which list of which org and how many rows came back. The\n * Privacy policy says our audit log records an AI job \"without the text of\n * its brief or of what it wrote\", so the row carries none of it.\n */\n\n/** Members read to name who asked; past it a row shows the uid alone. */\nconst MEMBERS_SCAN = 500\n\nconst ACTION = 'org.ai-conversations-viewed'\n\ntype Firestore = FirebaseFirestore.Firestore\ntype OrgRef = FirebaseFirestore.DocumentReference\n\n/** One page of a collection under the org, newest first, after the document `after` names. */\nasync function readPage(\n orgRef: OrgRef,\n collection: 'assistExchanges' | 'aiJobs',\n after: string | null,\n): Promise<{ docs: FirebaseFirestore.QueryDocumentSnapshot[]; next: string | null }> {\n let query = orgRef.collection(collection).orderBy('createdAt', 'desc').limit(STAFF_AI_CONVERSATIONS_PAGE + 1)\n if (after) {\n const cursor = await orgRef.collection(collection).doc(after).get()\n if (cursor.exists) query = query.startAfter(cursor)\n }\n const snapshot = await query.get()\n const docs = snapshot.docs.slice(0, STAFF_AI_CONVERSATIONS_PAGE)\n const next = snapshot.docs.length > STAFF_AI_CONVERSATIONS_PAGE ? docs[docs.length - 1]?.id ?? null : null\n return { docs, next }\n}\n\nasync function readMembers(orgRef: OrgRef): Promise<Map<string, Record<string, unknown>>> {\n const snapshot = await orgRef.collection('members').limit(MEMBERS_SCAN).get()\n return new Map(snapshot.docs.map((doc) => [doc.id, doc.data() ?? {}]))\n}\n\n/** The documents `refs` name, by id, in one round trip; a missing one is absent. */\nasync function readAll(\n db: Firestore,\n refs: FirebaseFirestore.DocumentReference[],\n): Promise<Map<string, Record<string, unknown>>> {\n if (!refs.length) return new Map()\n const snapshots = await db.getAll(...refs)\n return new Map(\n snapshots.filter((snapshot) => snapshot.exists).map((snapshot) => [snapshot.id, snapshot.data() ?? {}]),\n )\n}\n\n/** One page of the org's conversations of `kind`. Exported for the spec. */\nexport async function readStaffAiConversations(\n db: Firestore,\n orgId: string,\n kind: StaffAiConversationKind,\n after: string | null,\n): Promise<StaffAiConversationsResponse> {\n const orgRef = db.collection('orgs').doc(orgId)\n if (kind === 'assist') {\n const [{ docs, next }, members] = await Promise.all([readPage(orgRef, 'assistExchanges', after), readMembers(orgRef)])\n // The signal shares the exchange's id (AGL-1972): the route, the model, the cost and the thumbs.\n const signals = await readAll(db, docs.map((doc) => orgRef.collection('assistSignals').doc(doc.id)))\n return {\n kind,\n rows: docs.map((doc) => composeStaffAiAssistRow(doc.id, doc.data() ?? {}, signals.get(doc.id) ?? null, members)),\n next,\n }\n }\n const [{ docs, next }, members] = await Promise.all([readPage(orgRef, 'aiJobs', after), readMembers(orgRef)])\n const insightIds = docs.filter((doc) => staffAiJobResultCollection(doc.get('kind')) === 'aiInsights').map((doc) => doc.id)\n const crmIds = docs.filter((doc) => staffAiJobResultCollection(doc.get('kind')) === 'aiCrmAnswers').map((doc) => doc.id)\n const [insights, crm] = await Promise.all([\n readAll(db, insightIds.map((id) => orgRef.collection('aiInsights').doc(id))),\n readAll(db, crmIds.map((id) => orgRef.collection('aiCrmAnswers').doc(id))),\n ])\n return {\n kind,\n rows: docs.map((doc) => {\n const collection = staffAiJobResultCollection(doc.get('kind'))\n const result =\n collection === 'aiInsights'\n ? staffAiInsightResult(insights.get(doc.id) ?? null)\n : collection === 'aiCrmAnswers'\n ? staffAiCrmResult(crm.get(doc.id) ?? null)\n : null\n return composeStaffAiJobRow(doc.id, doc.data() ?? {}, result, members)\n }),\n next,\n }\n}\n\nasync function handler(request: Request): Promise<Response> {\n const { method, query, headers: rawHeaders } = await pluginRequestFromWeb(request)\n const headers = rawHeaders as Partial<Record<string, string>>\n if (method !== 'GET') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return Response.json({ error: 'Unauthenticated' }, { status: 401 })\n const orgId = String(query.orgId ?? '')\n if (!orgId) return Response.json({ error: 'Missing orgId' }, { status: 400 })\n const kind = query.kind\n if (!isStaffAiConversationKind(kind)) {\n return Response.json({ error: 'kind must be assist or jobs' }, { status: 400 })\n }\n const after = typeof query.after === 'string' && query.after ? query.after : null\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n if (!decoded['staff']) {\n return Response.json({ error: 'Staff only' }, { status: 403 })\n }\n const db = firebaseAdmin.app().firestore()\n if (!(await db.collection('orgs').doc(orgId).get()).exists) {\n return Response.json({ error: 'No such organization' }, { status: 404 })\n }\n const page = await readStaffAiConversations(db, orgId, kind, after)\n // Written before the words leave: a read the log cannot record is not served.\n await addAdminAudit(db, {\n actorUid: decoded.uid,\n action: ACTION,\n target: `orgs/${orgId}`,\n before: null,\n after: { kind, rows: page.rows.length, after },\n })\n return Response.json(page, { headers: { 'Cache-Control': 'no-store' } })\n } catch (error) {\n console.error('[ai/admin/conversations]', error)\n return Response.json({ error: 'The conversations could not be read.' }, { status: 500 })\n }\n}\n\nexport { handler as GET }\n"],"names":["pluginRequestFromWeb","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","addAdminAudit","composeStaffAiAssistRow","composeStaffAiJobRow","isStaffAiConversationKind","STAFF_AI_CONVERSATIONS_PAGE","staffAiCrmResult","staffAiInsightResult","staffAiJobResultCollection","MEMBERS_SCAN","ACTION","readPage","orgRef","collection","after","docs","query","orderBy","limit","cursor","doc","get","exists","startAfter","snapshot","slice","next","length","id","readMembers","Map","map","data","readAll","db","refs","snapshots","getAll","filter","readStaffAiConversations","orgId","kind","members","Promise","all","signals","rows","insightIds","crmIds","insights","crm","result","handler","request","headers","method","rawHeaders","Response","json","error","status","authorization","idToken","startsWith","undefined","String","decoded","app","auth","verifyIdToken","email_verified","firestore","page","actorUid","uid","action","target","before","console","GET"],"mappings":"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"}
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type AiImageMode, type AiSvgStyle } from '../providers/image-contract';
|
|
17
|
+
import { type AiImageMode, type AiRasterStyle, type AiSvgStyle } from '../providers/image-contract';
|
|
18
18
|
/** What a person reads when a picture failed on our side. */
|
|
19
19
|
export declare const AI_MEDIA_OURS_COPY = "This one's on us \u2014 you weren't charged. The picture didn't come out right; try again, or describe it a little differently.";
|
|
20
20
|
/** What the door answers on a deployment that makes no photos. */
|
|
21
|
-
export declare const AI_MEDIA_NO_PHOTOS_COPY = "Photos aren't available here yet. Choose Illustration to draw one instead.";
|
|
21
|
+
export declare const AI_MEDIA_NO_PHOTOS_COPY = "Photos and painted styles aren't available here yet. Choose a vector kind, such as Illustration, to draw one instead.";
|
|
22
22
|
/** Every control character as a space, squeezed, trimmed and cut. */
|
|
23
23
|
export declare function cleanAiImagePrompt(value: unknown): string;
|
|
24
24
|
/**
|
|
@@ -40,7 +40,7 @@ export interface AiMediaImageInput {
|
|
|
40
40
|
prompt: string;
|
|
41
41
|
aspectRatio: string;
|
|
42
42
|
count: number;
|
|
43
|
-
/**
|
|
43
|
+
/** The kind: an SVG style for an illustration, an image-provider style for a photo. */
|
|
44
44
|
style: string;
|
|
45
45
|
/** An illustration's colors: the site theme's, or these. */
|
|
46
46
|
palette: {
|
|
@@ -56,8 +56,8 @@ export interface AiMediaProvenance {
|
|
|
56
56
|
model: string;
|
|
57
57
|
prompt: string;
|
|
58
58
|
mode: AiImageMode;
|
|
59
|
-
/**
|
|
60
|
-
style?: AiSvgStyle;
|
|
59
|
+
/** The kind the picture was made as. */
|
|
60
|
+
style?: AiSvgStyle | AiRasterStyle;
|
|
61
61
|
aspectRatio: string;
|
|
62
62
|
/** The meter's signal id for the request that made it. */
|
|
63
63
|
signalId: string | null;
|
|
@@ -18,17 +18,19 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict, and
|
|
19
19
|
// every stored picture also passes the upload route's.
|
|
20
20
|
import { mediaFilterKeys } from "@aglyn/aglyn/app-utils/media-metadata";
|
|
21
|
+
import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
21
22
|
import { MEDIA_ALT_MAX_LENGTH } from "@aglyn/aglyn/app-utils/media-alt";
|
|
22
23
|
import { aiMediaCreditsPerPicture } from "../model/ai-media-credits.js";
|
|
23
24
|
import { aiSvgThemePalette, isAiSvgColor } from "../model/ai-svg.js";
|
|
24
25
|
import { estimateAiBilledUsd } from "../providers/catalog.js";
|
|
25
26
|
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 { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS, AI_SVG_MAX_COLORS, AI_SVG_STYLES, AiImageSafetyRefusal, aiImageSizeForPlan, isAiImageAspectRatio, isAiRasterStyle } from "../providers/image-contract.js";
|
|
27
28
|
import { vertexImageProvider } from "../providers/vertex-image.js";
|
|
28
29
|
import { aiGateLadder } from "../runtime/ai-gate.js";
|
|
29
30
|
import { aiInventoryTheme } from "../runtime/site-inventory.js";
|
|
30
31
|
import { assistBandRefuses, assistCreditsFromUsd } from "../usage/assist-credits.js";
|
|
31
32
|
import { publicAssistQuota, recordAssistCost, releaseAssistMessage } from "../usage/assist-usage.js";
|
|
33
|
+
import { aiMediaRasterPrompt } from "./ai-media-raster-prompt.js";
|
|
32
34
|
import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
|
|
33
35
|
/**
|
|
34
36
|
* "Create with AI" in Media (AGL-3602): pictures from a description, stored
|
|
@@ -40,9 +42,11 @@ import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
|
|
|
40
42
|
*
|
|
41
43
|
* Two modes:
|
|
42
44
|
*
|
|
43
|
-
* - `photo` — Google's image models on Vertex AI (`vertex-image.ts`)
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
* - `photo` — Google's image models on Vertex AI (`vertex-image.ts`), for
|
|
46
|
+
* every kind that is not SVG: a photo, a photographic genre, an art style
|
|
47
|
+
* or a design asset, each the description followed by its kind's fixed
|
|
48
|
+
* style wording (`ai-media-raster-prompt.ts`). Off unless the deployment
|
|
49
|
+
* configured them; the door answers 404 with a sentence the dialog shows.
|
|
46
50
|
* - `illustration` — an SVG drawn by the text provider every other AI door
|
|
47
51
|
* uses (`ai-media-svg.ts`): an illustration, an icon, a tileable pattern or
|
|
48
52
|
* a simple logo mark, in the site's theme colors or the person's own. No
|
|
@@ -91,7 +95,7 @@ import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
|
|
|
91
95
|
windowMs: 60000
|
|
92
96
|
};
|
|
93
97
|
/** 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.";
|
|
98
|
+
/** What the door answers on a deployment that makes no photos. */ export const AI_MEDIA_NO_PHOTOS_COPY = "Photos and painted styles aren't available here yet. Choose a vector kind, such as Illustration, to draw one instead.";
|
|
95
99
|
/** The provider photos come from; a spec stands one in through jest. */ function imageProvider() {
|
|
96
100
|
return vertexImageProvider;
|
|
97
101
|
}
|
|
@@ -145,7 +149,7 @@ export function parseAiMediaImageInput(body) {
|
|
|
145
149
|
prompt: cleanAiImagePrompt(body == null ? void 0 : body['prompt']),
|
|
146
150
|
aspectRatio: text('aspectRatio', 8),
|
|
147
151
|
count: Number.isFinite(count) ? count : 0,
|
|
148
|
-
style: text('style', 24) || 'illustration',
|
|
152
|
+
style: text('style', 24) || ((body == null ? void 0 : body['mode']) === 'illustration' ? 'illustration' : 'photo'),
|
|
149
153
|
palette: (palette == null ? void 0 : palette['source']) === 'custom' ? {
|
|
150
154
|
source: 'custom',
|
|
151
155
|
colors: colors.map((color)=>String(color != null ? color : '').trim().toLowerCase()).slice(0, AI_SVG_MAX_COLORS + 1)
|
|
@@ -298,6 +302,9 @@ export async function POST(request) {
|
|
|
298
302
|
if (input.count < 1 || input.count > AI_IMAGE_MAX_COUNT) {
|
|
299
303
|
return refuse(`Make between 1 and ${AI_IMAGE_MAX_COUNT} pictures at a time.`, 400);
|
|
300
304
|
}
|
|
305
|
+
if (input.mode === 'photo' && !isAiRasterStyle(input.style)) {
|
|
306
|
+
return refuse('Choose a kind of picture from the list.', 400);
|
|
307
|
+
}
|
|
301
308
|
if (input.mode === 'illustration') {
|
|
302
309
|
if (!AI_SVG_STYLES.includes(input.style)) {
|
|
303
310
|
return refuse('Choose an illustration, an icon, a pattern or a logo mark.', 400);
|
|
@@ -307,7 +314,10 @@ export async function POST(request) {
|
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
const model = input.mode === 'photo' ? provider.defaultModel() : svgModel;
|
|
310
|
-
|
|
317
|
+
// The size is the plan's, never the request's: 512 px on Free, 1K on a
|
|
318
|
+
// paid plan, each estimated and charged at its own per-picture rate.
|
|
319
|
+
const imageSize = aiImageSizeForPlan(resolveEffectivePlan(gate.org) === 'free');
|
|
320
|
+
const perPicture = aiMediaCreditsPerPicture(input.mode, model, imageSize);
|
|
311
321
|
const estimate = perPicture * input.count;
|
|
312
322
|
const credits = publicAssistQuota(gate.reservation).credits;
|
|
313
323
|
if (credits && assistBandRefuses(gate.org) && credits.remaining < estimate) {
|
|
@@ -342,11 +352,13 @@ export async function POST(request) {
|
|
|
342
352
|
let declineReason = null;
|
|
343
353
|
let style;
|
|
344
354
|
if (input.mode === 'photo') {
|
|
355
|
+
style = input.style;
|
|
345
356
|
try {
|
|
346
357
|
const result = await provider.generate({
|
|
347
358
|
model,
|
|
348
|
-
prompt: input.prompt,
|
|
359
|
+
prompt: aiMediaRasterPrompt(input.prompt, style),
|
|
349
360
|
aspectRatio,
|
|
361
|
+
size: imageSize,
|
|
350
362
|
count: input.count
|
|
351
363
|
});
|
|
352
364
|
photoUsage = result.usage;
|
|
@@ -383,14 +395,15 @@ export async function POST(request) {
|
|
|
383
395
|
} else {
|
|
384
396
|
var _ref1;
|
|
385
397
|
var _declined_;
|
|
386
|
-
|
|
398
|
+
const svgStyle = input.style;
|
|
399
|
+
style = svgStyle;
|
|
387
400
|
const palette = await illustrationPalette(gate, input);
|
|
388
401
|
const settled = await Promise.allSettled(Array.from({
|
|
389
402
|
length: input.count
|
|
390
403
|
}, (_unused, variant)=>generateAiMediaSvg({
|
|
391
404
|
model,
|
|
392
405
|
prompt: input.prompt,
|
|
393
|
-
style:
|
|
406
|
+
style: svgStyle,
|
|
394
407
|
aspectRatio,
|
|
395
408
|
palette,
|
|
396
409
|
variant,
|
|
@@ -467,7 +480,8 @@ export async function POST(request) {
|
|
|
467
480
|
// Only what was stored is settled: a photo's pictures at the per-picture
|
|
468
481
|
// rate with the request's prompt and thinking, an illustration's tokens.
|
|
469
482
|
const usage = input.mode === 'photo' ? _extends({}, photoUsage, {
|
|
470
|
-
images: stored.length
|
|
483
|
+
images: stored.length,
|
|
484
|
+
imageSize
|
|
471
485
|
}) : sumUsage(stored.map((entry)=>entry.picture.usage));
|
|
472
486
|
let signalId = null;
|
|
473
487
|
try {
|
|
@@ -1 +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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"}
|
|
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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\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 aiImageSizeForPlan,\n isAiImageAspectRatio,\n isAiRasterStyle,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiImageProvider,\n type AiRasterStyle,\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 { aiMediaRasterPrompt } from './ai-media-raster-prompt'\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`), for\n * every kind that is not SVG: a photo, a photographic genre, an art style\n * or a design asset, each the description followed by its kind's fixed\n * style wording (`ai-media-raster-prompt.ts`). Off unless the deployment\n * configured them; the door answers 404 with a 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 and painted styles aren't available here yet. Choose a vector kind, such as 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 /** The kind: an SVG style for an illustration, an image-provider style 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) || (body?.['mode'] === 'illustration' ? 'illustration' : 'photo'),\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 /** The kind the picture was made as. */\n style?: AiSvgStyle | AiRasterStyle\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 === 'photo' && !isAiRasterStyle(input.style)) {\n return refuse('Choose a kind of picture from the list.', 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 // The size is the plan's, never the request's: 512 px on Free, 1K on a\n // paid plan, each estimated and charged at its own per-picture rate.\n const imageSize = aiImageSizeForPlan(resolveEffectivePlan(gate.org) === 'free')\n const perPicture = aiMediaCreditsPerPicture(input.mode, model, imageSize)\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 | AiRasterStyle | undefined\n\n if (input.mode === 'photo') {\n style = input.style as AiRasterStyle\n try {\n const result = await provider.generate({\n model,\n prompt: aiMediaRasterPrompt(input.prompt, style),\n aspectRatio,\n size: imageSize,\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 const svgStyle = input.style as AiSvgStyle\n style = svgStyle\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: svgStyle,\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, imageSize }\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","resolveEffectivePlan","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","aiImageSizeForPlan","isAiImageAspectRatio","isAiRasterStyle","vertexImageProvider","aiGateLadder","aiInventoryTheme","assistBandRefuses","assistCreditsFromUsd","publicAssistQuota","recordAssistCost","releaseAssistMessage","aiMediaRasterPrompt","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","imageSize","org","perPicture","estimate","credits","remaining","quota","recordDecline","declinedModel","route","tier","images","docsPaths","stopReason","free","uid","kind","meterError","console","made","unmade","photoUsage","declineReason","result","generate","size","filtered","message","accountProblem","declined","svgStyle","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":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,+DAA+D;AAC/D,0EAA0E;AAC1E,uDAAuD;AAEvD,SAASA,eAAe,QAAQ,wCAAuC;AACvE,SAASC,oBAAoB,QAAQ,2CAA0C;AAC/E,SAASC,oBAAoB,QAAQ,mCAAkC;AACvE,SAASC,wBAAwB,QAAQ,+BAA2B;AACpE,SAASC,iBAAiB,EAAEC,YAAY,QAAQ,qBAAiB;AACjE,SAASC,mBAAmB,QAAQ,0BAAsB;AAC1D,SAASC,wBAAwB,EAAEC,eAAe,QAAsB,2BAAuB;AAC/F,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,yBAAyB,EACzBC,iBAAiB,EACjBC,aAAa,EACbC,oBAAoB,EACpBC,kBAAkB,EAClBC,oBAAoB,EACpBC,eAAe,QAMV,iCAA6B;AACpC,SAASC,mBAAmB,QAAQ,+BAA2B;AAC/D,SAASC,YAAY,QAA4B,wBAAoB;AACrE,SAASC,gBAAgB,QAAQ,+BAA2B;AAC5D,SAASC,iBAAiB,EAAEC,oBAAoB,QAAQ,6BAAyB;AACjF,SACEC,iBAAiB,EACjBC,gBAAgB,EAChBC,oBAAoB,QACf,2BAAuB;AAC9B,SAASC,mBAAmB,QAAQ,8BAA0B;AAC9D,SAASC,eAAe,EAAEC,kBAAkB,QAAgC,oBAAgB;AAE5F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDC,GAED,4EAA4E,GAC5E,MAAMC,aAAa;IAAEC,KAAK;IAAkBC,OAAO;IAAGC,UAAU;AAAO;AAEvE,2DAA2D,GAC3D,OAAO,MAAMC,qBACX,6HAA4H;AAE9H,gEAAgE,GAChE,OAAO,MAAMC,0BACX,wHAAuH;AAEzH,sEAAsE,GACtE,SAASC;IACP,OAAOjB;AACT;AAEA,mEAAmE,GACnE,OAAO,SAASkB,mBAAmBC,KAAc;IAC/C,IAAIC,MAAM;IACV,KAAK,MAAMC,QAAQC,OAAOH,gBAAAA,QAAS,IAAK;QACtC,MAAMI,OAAOF,KAAKG,UAAU,CAAC;QAC7BJ,OAAOG,OAAO,MAAMA,SAAS,OAAOA,SAAS,UAAUA,SAAS,SAAS,MAAMF;IACjF;IACA,OAAOD,IAAIK,OAAO,CAAC,QAAQ,KAAKC,IAAI,GAAGC,KAAK,CAAC,GAAGlC;AAClD;AAEA;;;;CAIC,GACD,OAAO,SAASmC,eAAeC,IAAY;IACzC,MAAMC,QAAQD,KAAKJ,OAAO,CAAC,QAAQ,KAAKC,IAAI;IAC5C,IAAI,CAACI,OAAO,OAAO;IACnB,MAAMC,WAAWD,MAAME,MAAM,CAAC,GAAGC,WAAW,KAAKH,MAAMH,KAAK,CAAC;IAC7D,IAAII,SAASG,MAAM,IAAIlD,sBAAsB,OAAO+C;IACpD,MAAMI,MAAMJ,SAASJ,KAAK,CAAC,GAAG3C,uBAAuB;IACrD,MAAMoD,QAAQD,IAAIE,WAAW,CAAC;IAC9B,OAAO,GAAG,AAACD,CAAAA,QAAQ,KAAKD,IAAIR,KAAK,CAAC,GAAGS,SAASD,GAAE,EAAGT,IAAI,GAAG,CAAC,CAAC;AAC9D;AAEA,MAAMY,aAA+C;IACnD,aAAa;IACb,cAAc;IACd,cAAc;IACd,iBAAiB;AACnB;AAEA,2EAA2E,GAC3E,OAAO,SAASC,gBAAgBC,MAAc,EAAEC,KAAa,EAAEC,QAAgB;QAW/BJ;IAV9C,MAAMK,QAAQH,OACXI,WAAW,GACXC,SAAS,CAAC,QACVpB,OAAO,CAAC,iBAAiB,KACzBqB,KAAK,CAAC,OACNC,MAAM,CAACC,SACPrB,KAAK,CAAC,GAAG,GACTsB,IAAI,CAAC,KACLtB,KAAK,CAAC,GAAG,IACTF,OAAO,CAAC,OAAO;IAClB,OAAO,CAAC,GAAG,EAAEkB,SAAS,QAAQ,CAAC,EAAEF,QAAQ,EAAE,CAAC,GAAEH,uBAAAA,UAAU,CAACI,SAAS,YAApBJ,uBAAwB,OAAO;AAC/E;AAmBA,OAAO,SAASY,uBAAuBC,IAAoC;;IACzE,MAAMtB,OAAO,CAACjB,KAAaC,QAAQ,GAAG;QACpC,MAAMM,QAAQgC,wBAAAA,IAAM,CAACvC,IAAI;QACzB,OAAO,OAAOO,UAAU,WAAWA,MAAMO,IAAI,GAAGC,KAAK,CAAC,GAAGd,SAAS;IACpE;IACA,MAAMuC,QAAQC,KAAKC,KAAK,CAACC,eAAOJ,wBAAAA,IAAM,CAAC,QAAQ,mBAAI;IACnD,MAAMK,UAAUL,wBAAAA,IAAM,CAAC,UAAU;IACjC,MAAMM,SAASC,MAAMC,OAAO,CAACH,2BAAAA,OAAS,CAAC,SAAS,IAAKA,2BAAAA,OAAS,CAAC,SAAS,GAAiB,EAAE;IAC3F,OAAO;QACLI,OAAO/B,KAAK;QACZgC,SAASV,CAAAA,wBAAAA,IAAM,CAAC,UAAU,MAAK,QAAQ,QAAQ;QAC/CW,QAAQjC,KAAK,aAAa;QAC1BkC,UAAUlC,KAAK,YAAY,OAAO;QAClCmC,MAAMb,CAAAA,wBAAAA,IAAM,CAAC,OAAO,MAAK,iBAAiB,iBAAiB;QAC3DX,QAAQtB,mBAAmBiC,wBAAAA,IAAM,CAAC,SAAS;QAC3Cc,aAAapC,KAAK,eAAe;QACjCuB,OAAOG,OAAOW,QAAQ,CAACd,SAASA,QAAQ;QACxCe,OAAOtC,KAAK,SAAS,OAAQsB,CAAAA,CAAAA,wBAAAA,IAAM,CAAC,OAAO,MAAK,iBAAiB,iBAAiB,OAAM;QACxFK,SACEA,CAAAA,2BAAAA,OAAS,CAAC,SAAS,MAAK,WACpB;YACEY,QAAQ;YACRX,QAAQA,OACLY,GAAG,CAAC,CAACC,QAAUhD,OAAOgD,gBAAAA,QAAS,IAAI5C,IAAI,GAAGkB,WAAW,IACrDjB,KAAK,CAAC,GAAGjC,oBAAoB;QAClC,IACA;YAAE0E,QAAQ;QAAQ;IAC1B;AACF;AAKA;;;;CAIC,GACD,eAAeG,wBACbC,OAAgB,EAChBC,KAAwB,EACxBC,KAA2C,EAC3CC,QAAgB;IAEhB,MAAMC,QACJH,MAAMZ,OAAO,KAAK,QACd;QAAED,OAAOa,MAAMb,KAAK;OAAMa,MAAMX,MAAM,GAAG;QAAEe,WAAWJ,MAAMX,MAAM;IAAC,IAAI,CAAC,KACxE;QAAEA,QAAQW,MAAMX,MAAM;IAAC;IAC7B,IAAIgB;IACJ,IAAI;YAIiBN;QAHnBM,WAAW,MAAMC,MAAM,IAAIC,IAAI,qBAAqBR,QAAQS,GAAG,GAAG;YAChEC,QAAQ;YACRC,SAAS;gBACPC,aAAa,GAAEZ,uBAAAA,QAAQW,OAAO,CAACE,GAAG,CAAC,4BAApBb,uBAAwC;gBACvD,gBAAgB;YAClB;YACArB,MAAMmC,KAAKC,SAAS,CAAC,aAChBX;gBACHD;gBACAa,aAAad,MAAMhC,QAAQ;gBAC3BqB,UAAUU,MAAMV,QAAQ;gBACxB0B,MAAMf,MAAMgB,MAAM;;QAEtB;IACF,EAAE,eAAM;QACN,OAAO;YAAEC,OAAO;YAAwDC,QAAQ;QAAI;IACtF;IACA,MAAMC,UAAW,MAAMf,SAASgB,IAAI,GAAGC,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;IACrD,MAAMC,UAAU,OAAOH,OAAO,CAAC,UAAU,KAAK,WAAWA,OAAO,CAAC,UAAU,GAAG;IAC9E,IAAIf,SAASmB,EAAE,IAAID,SAAS,OAAO;QAAEA;IAAQ;IAC7C,OAAO;QACLL,OACE,OAAOE,OAAO,CAAC,QAAQ,KAAK,WACxBA,OAAO,CAAC,QAAQ,GAChB;QACND,QAAQd,SAASmB,EAAE,GAAG,MAAMnB,SAASc,MAAM;IAC7C;AACF;AAeA;;;CAGC,GACD,eAAeM,oBACbC,IAAmB,EACnB1B,KAAwB,EACxBuB,OAAe,EACfI,GAAW,EACXC,UAA6B;QAOb;IALhB,MAAMC,WACJ7B,MAAMZ,OAAO,KAAK,QACdsC,KAAKI,SAAS,CAACC,UAAU,CAAC,QAAQC,GAAG,CAAChC,MAAMb,KAAK,IACjDuC,KAAKI,SAAS,CAACC,UAAU,CAAC,SAASC,GAAG,CAACnF,OAAOmD,MAAMX,MAAM;IAChE,MAAM4C,WAAWJ,SAASE,UAAU,CAAC,SAASC,GAAG,CAACT;IAClD,MAAMW,UAAU,QAAA,AAAC,CAAA,MAAMD,SAASrB,GAAG,EAAC,EAAGI,IAAI,cAA3B,QAAiC,CAAC;IAClD,MAAMiB,SAASE,MAAM,CAAC;QACpBR;QACAS,aAAa;QACbC,aAAa,aAAKT;YAAYU,WAAW,IAAIC;;OAG1ClI,gBAAgB,aAAK6H;QAAQP;;AAEpC;AAEA,4CAA4C,GAC5C,eAAea,oBACbd,IAAmB,EACnB1B,KAAwB;QAKoCyC;QAAnChH;IAHzB,IAAIuE,MAAMjB,OAAO,CAACY,MAAM,KAAK,UAAU,OAAOK,MAAMjB,OAAO,CAACC,MAAM;IAClE,IAAI,CAACgB,MAAMX,MAAM,EAAE,OAAO,EAAE;IAC5B,MAAMoD,WAAW,MAAMf,KAAKI,SAAS,CAACC,UAAU,CAAC,SAASC,GAAG,CAAChC,MAAMX,MAAM,EAAEuB,GAAG;IAC/E,OAAOnG,mBAAkBgB,oBAAAA,iBAAiBgH,SAASC,MAAM,IAAGD,iBAAAA,SAASzB,IAAI,cAAbyB,iBAAmB,OAAO,0BAA7DhH,kBAAoEuD,MAAM;AACrG;AAEA,MAAM2D,aAAsB;IAAEC,aAAa;IAAGC,cAAc;IAAGC,iBAAiB;IAAGC,kBAAkB;AAAE;AAEvG,MAAMC,WAAW,CAACC,SAChBA,OAAOC,MAAM,CACX,CAACC,KAAKC,QAAW,CAAA;YACfR,aAAaO,IAAIP,WAAW,GAAGQ,MAAMR,WAAW;YAChDC,cAAcM,IAAIN,YAAY,GAAGO,MAAMP,YAAY;YACnDC,iBAAiBK,IAAIL,eAAe,GAAGM,MAAMN,eAAe;YAC5DC,kBAAkBI,IAAIJ,gBAAgB,GAAGK,MAAML,gBAAgB;QACjE,CAAA,GACAJ;AAYJ,OAAO,eAAeU,KAAKtD,OAAgB;;IACzC,IAAIrB;IACJ,IAAI;QACFA,OAAQ,MAAMqB,QAAQsB,IAAI;IAC5B,EAAE,eAAM;QACN3C,OAAO;IACT;IACA,MAAMsB,QAAQvB,uBAAuBC;IACrC,MAAM4E,WAAW9G;IACjB,MAAM+G,WAAWvD,MAAMT,IAAI,KAAK,iBAAiBvD,oBAAoBwH;IACrE,wEAAwE;IACxE,2DAA2D;IAC3D,IAAIxD,MAAMT,IAAI,KAAK,WAAW,CAAC+D,SAASG,UAAU,IAAI;QACpD,OAAOC,SAASrC,IAAI,CAAC;YAAEH,OAAO3E;YAAyBoH,QAAQ;QAAc,GAAG;YAAExC,QAAQ;QAAI;IAChG;IACA,IAAInB,MAAMT,IAAI,KAAK,kBAAkB,CAACgE,UAAU;QAC9C,OAAOG,SAASrC,IAAI,CAAC;YAAEH,OAAO;QAAY,GAAG;YAAEC,QAAQ;QAAI;IAC7D;IAEA,MAAMO,OAAO,MAAMlG,aACjB;QAAEuE;QAASZ,OAAOa,MAAMb,KAAK;QAAEE,QAAQW,MAAMX,MAAM;IAAC,GACpD;QACEuE,SAAS;QACTC,aAAa;QACbC,iBAAiB;QACjBC,YAAY;QACZC,WAAW9H;IACb;IAEF,IAAIwF,gBAAgBgC,UAAU,OAAOhC;IACrC,qEAAqE;IACrE,8BAA8B;IAC9B,MAAMuC,UAAU,IACdnI,qBAAqB4F,KAAKI,SAAS,EAAEJ,KAAKvC,KAAK,EAAEuC,KAAKwC,WAAW,EAAE5C,KAAK,CAAC,IAAMkC;IACjF,MAAMW,SAAS,OAAOjD,OAAeC,QAAgBiD,QAAiC,CAAC,CAAC;QACtF,MAAMH;QACN,OAAOP,SAASrC,IAAI,CAAC;YAAEH;WAAUkD,QAAS;YAAEjD;QAAO;IACrD;IAEA,IAAInB,MAAMZ,OAAO,KAAK,UAAU,CAACY,MAAMX,MAAM,EAAE;QAC7C,OAAO8E,OAAO,yDAAyD;IACzE;IACA,IAAI,CAACnE,MAAMjC,MAAM,EAAE,OAAOoG,OAAO,wCAAwC;IACzE,IAAI,CAAC9I,qBAAqB2E,MAAMR,WAAW,GAAG;QAC5C,OAAO2E,OAAO,CAAC,gBAAgB,EAAErJ,uBAAuB0D,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE;IACzE;IACA,MAAMgB,cAAkCQ,MAAMR,WAAW;IACzD,IAAIQ,MAAMrB,KAAK,GAAG,KAAKqB,MAAMrB,KAAK,GAAG5D,oBAAoB;QACvD,OAAOoJ,OAAO,CAAC,mBAAmB,EAAEpJ,mBAAmB,oBAAoB,CAAC,EAAE;IAChF;IACA,IAAIiF,MAAMT,IAAI,KAAK,WAAW,CAACjE,gBAAgB0E,MAAMN,KAAK,GAAG;QAC3D,OAAOyE,OAAO,2CAA2C;IAC3D;IACA,IAAInE,MAAMT,IAAI,KAAK,gBAAgB;QACjC,IAAI,CAAC,AAACrE,cAAoCmJ,QAAQ,CAACrE,MAAMN,KAAK,GAAG;YAC/D,OAAOyE,OAAO,8DAA8D;QAC9E;QACA,IACEnE,MAAMjB,OAAO,CAACY,MAAM,KAAK,YACxB,CAAA,CAACK,MAAMjB,OAAO,CAACC,MAAM,CAACvB,MAAM,IAC3BuC,MAAMjB,OAAO,CAACC,MAAM,CAACvB,MAAM,GAAGxC,qBAC9B,CAAC+E,MAAMjB,OAAO,CAACC,MAAM,CAACsF,KAAK,CAAC5J,aAAY,GAC1C;YACA,OAAOyJ,OAAO,CAAC,mBAAmB,EAAElJ,kBAAkB,uCAAuC,CAAC,EAAE;QAClG;IACF;IAEA,MAAMsJ,QAAQvE,MAAMT,IAAI,KAAK,UAAU+D,SAASkB,YAAY,KAAMjB;IAClE,uEAAuE;IACvE,qEAAqE;IACrE,MAAMkB,YAAYrJ,mBAAmBd,qBAAqBoH,KAAKgD,GAAG,MAAM;IACxE,MAAMC,aAAanK,yBAAyBwF,MAAMT,IAAI,EAAEgF,OAAOE;IAC/D,MAAMG,WAAWD,aAAa3E,MAAMrB,KAAK;IACzC,MAAMkG,UAAUjJ,kBAAkB8F,KAAKwC,WAAW,EAAEW,OAAO;IAC3D,IAAIA,WAAWnJ,kBAAkBgG,KAAKgD,GAAG,KAAKG,QAAQC,SAAS,GAAGF,UAAU;QAC1E,OAAOT,OACLU,QAAQC,SAAS,GAAGH,aAChB,0DACA,CAAC,iBAAiB,EAAEC,SAAS,gCAAgC,EAAEC,QAAQC,SAAS,CAAC,sCAAsC,CAAC,EAC5H,KACA;YAAEnB,QAAQ;YAASoB,OAAOnJ,kBAAkB8F,KAAKwC,WAAW;QAAE;IAElE;IAEA,uEAAuE,GACvE,MAAMc,gBAAgB,CAAC5B,OAAgB6B;YAS7BvD;eARR7F,iBAAiB6F,KAAKI,SAAS,EAAEJ,KAAKvC,KAAK,EAAE;YAC3C+F,OAAO;YACP7F,QAAQW,MAAMX,MAAM;YACpBkF,OAAOU;YACPE,MAAM;YACN/B,OAAO,aAAKA;gBAAOgC,QAAQ;;YAC3BC,WAAW,EAAE;YACbC,YAAY;YACZC,IAAI,GAAE7D,yBAAAA,KAAKwC,WAAW,CAACqB,IAAI,YAArB7D,yBAAyB;YAC/B8D,KAAK9D,KAAK8D,GAAG;YACbC,MAAM;QACR,GAAGnE,KAAK,CAAC,CAACoE,aACRC,QAAQzE,KAAK,CAAC,iCAAiC;gBAAE/B,OAAOuC,KAAKvC,KAAK;gBAAEuG;YAAW;;IAGnF,IAAIE;IACJ,gFAAgF,GAChF,IAAIC;IACJ,kFAAkF,GAClF,IAAIC,aAAsBnD;IAC1B,IAAIoD,gBAA+B;IACnC,IAAIrG;IAEJ,IAAIM,MAAMT,IAAI,KAAK,SAAS;QAC1BG,QAAQM,MAAMN,KAAK;QACnB,IAAI;YACF,MAAMsG,SAAS,MAAM1C,SAAS2C,QAAQ,CAAC;gBACrC1B;gBACAxG,QAAQhC,oBAAoBiE,MAAMjC,MAAM,EAAE2B;gBAC1CF;gBACA0G,MAAMzB;gBACN9F,OAAOqB,MAAMrB,KAAK;YACpB;YACAmH,aAAaE,OAAO5C,KAAK;YACzByC,SAASG,OAAOG,QAAQ;YACxBP,OAAOI,OAAOZ,MAAM,CAACxF,GAAG,CAAC,CAACK,QAAW,aAChCA;oBACH0B,KAAKxE,eAAe6C,MAAMjC,MAAM;oBAChCqF,OAAOT;;QAEX,EAAE,OAAOzB,OAAO;YACd,IAAIA,iBAAiB/F,sBAAsB;gBACzC,kEAAkE;gBAClE,+DAA+D;gBAC/D,aAAa;gBACb,MAAM6J,cAAcrC,YAAY4B;gBAChC,OAAOb,SAASrC,IAAI,CAAC;oBAAEH,OAAOA,MAAMkF,OAAO;oBAAEzC,QAAQ;gBAAS,GAAG;oBAAExC,QAAQ;gBAAI;YACjF;YACA,MAAM8C;YACN0B,QAAQzE,KAAK,CAAC,8BAA8B;gBAAE/B,OAAOuC,KAAKvC,KAAK;gBAAE+B;YAAM;YACvE,MAAMC,SAASD,iBAAiBrG,mBAAmBqG,MAAMmF,cAAc,GAAG,MAAM;YAChF,OAAO3C,SAASrC,IAAI,CAAC;gBAAEH,OAAOtG;YAAyB,GAAG;gBAAEuG;YAAO;QACrE;IACF,OAAO;;YAkCFmF;QAjCH,MAAMC,WAAWvG,MAAMN,KAAK;QAC5BA,QAAQ6G;QACR,MAAMxH,UAAU,MAAMyD,oBAAoBd,MAAM1B;QAChD,MAAMwG,UAAU,MAAMC,QAAQC,UAAU,CACtCzH,MAAM0H,IAAI,CAAC;YAAElJ,QAAQuC,MAAMrB,KAAK;QAAC,GAAG,CAACiI,SAASC,UAC5C5K,mBAAmB;gBACjBsI;gBACAxG,QAAQiC,MAAMjC,MAAM;gBACpB2B,OAAO6G;gBACP/G;gBACAT;gBACA8H;gBACAlI,OAAOqB,MAAMrB,KAAK;YACpB;QAGJ,MAAMmI,WAAWN,QAAQO,OAAO,CAAC,CAACC,QAChCA,MAAM7F,MAAM,KAAK,cAAc;gBAAC6F,MAAMtK,KAAK;aAAC,GAAG,EAAE;QAEnD,IAAI,CAACoK,SAASrJ,MAAM,EAAE;gBAIV+I;YAHV,MAAMvC;YACN0B,QAAQzE,KAAK,CAAC,qCAAqC;gBACjD/B,OAAOuC,KAAKvC,KAAK;gBACjB+B,KAAK,GAAGsF,YAAAA,OAAO,CAAC,EAAE,qBAAX,AAACA,UAAkD7C,MAAM;YAClE;YACA,OAAOD,SAASrC,IAAI,CAAC;gBAAEH,OAAOtG;YAAyB,GAAG;gBAAEuG,QAAQ;YAAI;QAC1E;QACA,MAAM8F,QAAQH,SAASxI,MAAM,CAC3B,CAAC4I,UAAsEA,QAAQzB,IAAI,KAAK;QAE1F,MAAMa,WAAWQ,SAASxI,MAAM,CAAC,CAAC4I,UAAYA,QAAQzB,IAAI,KAAK;QAC/DI,SAAS7F,MAAMrB,KAAK,GAAGsI,MAAMxJ,MAAM;QACnCsI,0BACGO,aAAAA,QAAQ,CAAC,EAAE,qBAAZ,AAACA,WAA8E3C,MAAM,oBAAI;QAC3FiC,OAAOqB,MAAMrH,GAAG,CAAC,CAACsH,UAAa,CAAA;gBAC7BjG,QAAQkG,OAAOR,IAAI,CAACO,QAAQE,GAAG,EAAE,QAAQC,QAAQ,CAAC;gBAClDpJ,UAAU;gBACV0D,KAAKxE,eAAe+J,QAAQvF,GAAG,IAAI3B,MAAMjC,MAAM;gBAC/CqF,OAAO8D,QAAQ9D,KAAK;YACtB,CAAA;QACA,IAAI,CAACwC,KAAKnI,MAAM,EAAE;YAChB,IAAI6I,SAAS7I,MAAM,EAAE;gBACnB,2DAA2D;gBAC3D,MAAMuH,cAAchC,SAASsD,SAAS1G,GAAG,CAAC,CAACsH,UAAYA,QAAQ9D,KAAK,IAAImB;gBACxE,OAAOb,SAASrC,IAAI,CAAC;oBAAEH,OAAO6E;oBAAepC,QAAQ;gBAAS,GAAG;oBAAExC,QAAQ;gBAAI;YACjF;YACA,qEAAqE;YACrE,MAAM8C;YACN,OAAOP,SAASrC,IAAI,CAAC;gBAAEH,OAAO5E;gBAAoBqH,QAAQ;YAAO,GAAG;gBAAExC,QAAQ;YAAI;QACpF;IACF;IAEA,MAAMe,SAA2D,EAAE;IACnE,IAAIoF,UAAoD;IACxD,KAAK,MAAM,CAACtJ,OAAOuJ,QAAQ,IAAI3B,KAAK4B,OAAO,GAAI;QAC7C,MAAMN,UAAU,MAAMpH,wBACpBC,SACAC,OACAuH,SACAzJ,gBAAgBkC,MAAMjC,MAAM,EAAEC,OAAOuJ,QAAQtJ,QAAQ;QAEvD,IAAI,aAAaiJ,SAAShF,OAAOuF,IAAI,CAAC;YAAElG,SAAS2F,QAAQ3F,OAAO;YAAEgG;QAAQ;aACrED,kBAAAA,UAAAA,UAAYJ;IACnB;IAEA,IAAI,CAAChF,OAAOzE,MAAM,EAAE;;QAClB,oEAAoE;QACpE,0DAA0D;QAC1D,MAAMwG;QACN,OAAOP,SAASrC,IAAI,CAClB;YAAEH,KAAK,WAAEoG,2BAAAA,QAASpG,KAAK,oBAAI;QAAmC,GAC9D;YAAEC,MAAM,WAAEmG,2BAAAA,QAASnG,MAAM,oBAAI;QAAI;IAErC;IAEA,yEAAyE;IACzE,yEAAyE;IACzE,MAAMiC,QACJpD,MAAMT,IAAI,KAAK,UACX,aAAKuG;QAAYV,QAAQlD,OAAOzE,MAAM;QAAEgH;SACxCzB,SAASd,OAAOtC,GAAG,CAAC,CAACoH,QAAUA,MAAMO,OAAO,CAACnE,KAAK;IACxD,IAAIsE,WAA0B;IAC9B,IAAI;YASMhG;QARRgG,WAAW,MAAM7L,iBAAiB6F,KAAKI,SAAS,EAAEJ,KAAKvC,KAAK,EAAE;YAC5D+F,OAAO;YACP7F,QAAQW,MAAMX,MAAM;YACpBkF;YACAY,MAAM;YACN/B;YACAiC,WAAW,EAAE;YACbC,YAAY;YACZC,IAAI,GAAE7D,yBAAAA,KAAKwC,WAAW,CAACqB,IAAI,YAArB7D,yBAAyB;YAC/B8D,KAAK9D,KAAK8D,GAAG;YACbC,MAAM;QACR;IACF,EAAE,OAAOvE,OAAO;QACd,mEAAmE;QACnE,0DAA0D;QAC1DyE,QAAQzE,KAAK,CAAC,+BAA+B;YAAE/B,OAAOuC,KAAKvC,KAAK;YAAE+B;QAAM;IAC1E;IAEA,MAAMuF,QAAQkB,GAAG,CACfzF,OAAOtC,GAAG,CAAC,CAAC,EAAE2B,OAAO,EAAEgG,OAAO,EAAE,GAC9B9F,oBAAoBC,MAAM1B,OAAOuB,SAASgG,QAAQ5F,GAAG,EAAE;YACrD4C;YACAxG,QAAQiC,MAAMjC,MAAM;YACpBwB,MAAMS,MAAMT,IAAI;WACZG,QAAQ;YAAEA;QAAM,IAAI,CAAC;YACzBF;YACAkI;YACAE,aAAalG,KAAK8D,GAAG;YACpBlE,KAAK,CAAC,CAACJ,QAAUyE,QAAQzE,KAAK,CAAC,iCAAiC;gBAAEK;gBAASL;YAAM;IAIxF,MAAM2G,kBACJP,2BAAAA,QAASpG,KAAK,mBACb6E,iBAAiBF,SAAS,IAAIE,gBAAgBF,SAAS,KAAK7F,MAAMT,IAAI,KAAK,iBACxEjD,qBACA;IACN,OAAOoH,SAASrC,IAAI,CAAC;QACnByG,UAAU5F,OAAOtC,GAAG,CAAC,CAACoH,QAAUA,MAAMzF,OAAO;QAC7C4E,UAAUN;QACVkC,QAAQnC,KAAKnI,MAAM,GAAGyE,OAAOzE,MAAM;QACnC,4CAA4C;QAC5CoH,SAASlJ,qBAAqBhB,oBAAoByI,OAAOmB;OACrDsD,UAAU;QAAEA;IAAQ,IAAI,CAAC;AAEjC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiRasterStyle } from '../providers/image-contract';
|
|
18
|
+
/**
|
|
19
|
+
* The style wording each image-provider kind adds to a description
|
|
20
|
+
* (AGL-3602). The person's description comes first, so it decides what is
|
|
21
|
+
* in the picture; the wording after it decides how the picture is made — a
|
|
22
|
+
* photograph's lens, light and composition, an art style's medium and
|
|
23
|
+
* strokes, a design asset's layout.
|
|
24
|
+
*
|
|
25
|
+
* It is fixed text of ours and carries nothing of the workspace: what reaches
|
|
26
|
+
* the image provider is still the description and the shape, as the
|
|
27
|
+
* published subprocessor row says.
|
|
28
|
+
*
|
|
29
|
+
* `photo` adds nothing: its description is sent exactly as written, as it
|
|
30
|
+
* was before the other kinds existed.
|
|
31
|
+
*/
|
|
32
|
+
export declare const AI_MEDIA_RASTER_STYLE_WORDING: Readonly<Record<AiRasterStyle, string>>;
|
|
33
|
+
/** The prompt one picture is asked for: the description, then the kind's style wording. */
|
|
34
|
+
export declare function aiMediaRasterPrompt(description: string, style: AiRasterStyle): string;
|