@aglyn/plugins-ai 1.0.0-beta.231 → 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-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-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-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-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-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-build-step.js +3 -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-insight-step.js +21 -4
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
- package/src/lib/jobs/ai-job-layout-language.js +8 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +7 -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 +52 -7
- package/src/lib/jobs/ai-job-page-language.js +72 -9
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
- package/src/lib/jobs/ai-job-plan-step.js +54 -20
- 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 +26 -5
- package/src/lib/jobs/ai-job-site-step.js +163 -24
- package/src/lib/jobs/ai-job-site-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/layout-language/ai-layout-compiler.d.ts +9 -0
- package/src/lib/layout-language/ai-layout-compiler.js +120 -33
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +5 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
- package/src/lib/layout-language/ai-layout-language.js +92 -9
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- 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 +2 -0
- package/src/lib/layout-language/ai-layout-store.js +7 -1
- package/src/lib/layout-language/ai-layout-store.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +4 -1
- 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-job-activity.js +1 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +3 -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 +4 -0
- package/src/lib/model/ai-site-build-progress.js +28 -11
- 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 +10 -2
- package/src/lib/model/ai-site-job.js +12 -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.d.ts +10 -1
- package/src/lib/model/ai-site-start.js +16 -2
- 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 +20 -1
- 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 +37 -7
- package/src/lib/providers/catalog.js +103 -11
- 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.js +5 -1
- 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-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-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/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
|
@@ -15,15 +15,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
|
-
*/ import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
|
|
18
|
+
*/ import { lockdownRefusalText, parseLockdownRefusal, resolveEffectivePlan } from "@aglyn/aglyn";
|
|
19
19
|
import { mdiCreation } from "@aglyn/shared-data-mdi";
|
|
20
20
|
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
21
21
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
22
|
-
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
23
|
-
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, IconButton, LinearProgress, MenuItem, Radio, RadioGroup, Stack, TextField, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
|
|
24
|
-
import {
|
|
22
|
+
import { useFirestore, useFirestoreDoc, useUser } from "@aglyn/tenant-feature-instance";
|
|
23
|
+
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, IconButton, LinearProgress, ListItemText, ListSubheader, MenuItem, Radio, RadioGroup, Stack, TextField, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
|
|
24
|
+
import { doc } from "firebase/firestore";
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
25
26
|
import { aiMediaCreditsPerPicture } from "../model/ai-media-credits.js";
|
|
26
|
-
import {
|
|
27
|
+
import { AI_MEDIA_KIND_GROUP_LABELS, AI_MEDIA_KIND_GROUPS, aiMediaKind, aiMediaKindsOffered } from "../model/ai-media-kinds.js";
|
|
28
|
+
import { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS, aiImageSizeForPlan, AI_SVG_MAX_COLORS, aiImagePhotosOffered } from "../providers/image-contract.js";
|
|
27
29
|
/** The words each shape is offered in. */ const ASPECT_LABELS = {
|
|
28
30
|
'1:1': 'Square',
|
|
29
31
|
'4:3': 'Landscape',
|
|
@@ -31,14 +33,55 @@ import { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS,
|
|
|
31
33
|
'16:9': 'Wide',
|
|
32
34
|
'9:16': 'Tall'
|
|
33
35
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/** The
|
|
41
|
-
const
|
|
36
|
+
/**
|
|
37
|
+
* What a picture from the image provider may be declined for, as the code
|
|
38
|
+
* and the provider decide it: Google's filters, and its policies on real
|
|
39
|
+
* people and brands. A declined picture is never charged.
|
|
40
|
+
*/ export const AI_MEDIA_RASTER_SAFETY_NOTE = 'Pictures of real, identifiable people, celebrities or brands may be declined by the image service. A declined picture is not charged.';
|
|
41
|
+
/** The line under the description for a kind whose colors are described rather than picked. */ export const AI_MEDIA_DESCRIBE_COLORS_NOTE = 'To steer the colors, name them in the description, such as navy and coral.';
|
|
42
|
+
/** The menu's items: each section's heading, then its kinds with what one costs. */ function kindMenuItems(kinds, size) {
|
|
43
|
+
const groups = AI_MEDIA_KIND_GROUPS.filter((group)=>kinds.some((kind)=>kind.group === group));
|
|
44
|
+
const item = (kind)=>/*#__PURE__*/ _jsxs(MenuItem, {
|
|
45
|
+
value: kind.id,
|
|
46
|
+
sx: {
|
|
47
|
+
gap: 2
|
|
48
|
+
},
|
|
49
|
+
children: [
|
|
50
|
+
/*#__PURE__*/ _jsx(ListItemText, {
|
|
51
|
+
primary: kind.label
|
|
52
|
+
}),
|
|
53
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
54
|
+
variant: "caption",
|
|
55
|
+
color: "text.secondary",
|
|
56
|
+
children: `about ${aiMediaCreditsPerPicture(kind.mode, undefined, size).toLocaleString()} credits`
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}, kind.id);
|
|
60
|
+
// One section needs no heading: a deployment without the image provider
|
|
61
|
+
// lists the vector kinds alone, as it always has.
|
|
62
|
+
if (groups.length < 2) return kinds.map(item);
|
|
63
|
+
return groups.flatMap((group)=>[
|
|
64
|
+
/*#__PURE__*/ _jsx(ListSubheader, {
|
|
65
|
+
children: AI_MEDIA_KIND_GROUP_LABELS[group]
|
|
66
|
+
}, `group:${group}`),
|
|
67
|
+
...kinds.filter((kind)=>kind.group === group).map(item)
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The size the door will make a picture at for this workspace (AGL-3602):
|
|
72
|
+
* 512 px on the Free plan, 1K on a paid one. The door decides from the plan
|
|
73
|
+
* itself; this reads the same plan so the estimate matches, and reads 1K —
|
|
74
|
+
* the dearer estimate — until the workspace has arrived.
|
|
75
|
+
*/ export function useAiMediaImageSize(orgId) {
|
|
76
|
+
const firestore = useFirestore();
|
|
77
|
+
const { data: org, status } = useFirestoreDoc(()=>doc(firestore, 'orgs', orgId), [
|
|
78
|
+
firestore,
|
|
79
|
+
orgId
|
|
80
|
+
]);
|
|
81
|
+
return aiImageSizeForPlan(status === 'success' && resolveEffectivePlan(org) === 'free');
|
|
82
|
+
}
|
|
83
|
+
/** The sentence the estimate reads as, before anything is spent. */ export function aiMediaCreditEstimate(mode, count, size = '1K') {
|
|
84
|
+
const each = aiMediaCreditsPerPicture(mode, undefined, size);
|
|
42
85
|
const pictures = count === 1 ? '1 picture' : `${count} pictures`;
|
|
43
86
|
return `${pictures} uses about ${(count * each).toLocaleString()} AI credits ` + `(about ${each.toLocaleString()} each). A picture that is held back or does not come out is not charged.`;
|
|
44
87
|
}
|
|
@@ -104,12 +147,18 @@ export function AiMediaCreateButton(props) {
|
|
|
104
147
|
});
|
|
105
148
|
}
|
|
106
149
|
export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, folderId, onCreated }) {
|
|
150
|
+
var _kinds_find;
|
|
107
151
|
const { data: user } = useUser();
|
|
108
152
|
const userRef = useRef(user);
|
|
109
153
|
userRef.current = user;
|
|
110
154
|
const photos = aiImagePhotosOffered();
|
|
111
|
-
const
|
|
112
|
-
const
|
|
155
|
+
const imageSize = useAiMediaImageSize(orgId);
|
|
156
|
+
const kinds = useMemo(()=>aiMediaKindsOffered(photos), [
|
|
157
|
+
photos
|
|
158
|
+
]);
|
|
159
|
+
const [kindId, setKindId] = useState(photos ? 'photo' : 'illustration');
|
|
160
|
+
const kind = (_kinds_find = kinds.find((entry)=>entry.id === kindId)) != null ? _kinds_find : kinds[0];
|
|
161
|
+
const mode = kind.mode;
|
|
113
162
|
// The site theme's colors need a site; the organization's library names none.
|
|
114
163
|
const [paletteSource, setPaletteSource] = useState(hostId ? 'theme' : 'custom');
|
|
115
164
|
const [colors, setColors] = useState([
|
|
@@ -117,7 +166,7 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
117
166
|
'#fbbc04'
|
|
118
167
|
]);
|
|
119
168
|
const [prompt, setPrompt] = useState('');
|
|
120
|
-
const [aspectRatio, setAspectRatio] = useState(
|
|
169
|
+
const [aspectRatio, setAspectRatio] = useState(kind.aspectRatio);
|
|
121
170
|
const [count, setCount] = useState(1);
|
|
122
171
|
const [busy, setBusy] = useState(false);
|
|
123
172
|
const [notice, setNotice] = useState(null);
|
|
@@ -126,6 +175,11 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
126
175
|
}, [
|
|
127
176
|
open
|
|
128
177
|
]);
|
|
178
|
+
/** A new kind brings its own shape; the person may still change it. */ const chooseKind = (next)=>{
|
|
179
|
+
setKindId(next);
|
|
180
|
+
const chosen = aiMediaKind(next);
|
|
181
|
+
if (chosen) setAspectRatio(chosen.aspectRatio);
|
|
182
|
+
};
|
|
129
183
|
const create = useCallback(async ()=>{
|
|
130
184
|
const description = prompt.trim();
|
|
131
185
|
if (!description) return;
|
|
@@ -146,9 +200,9 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
146
200
|
mode,
|
|
147
201
|
prompt: description,
|
|
148
202
|
aspectRatio,
|
|
149
|
-
count
|
|
150
|
-
|
|
151
|
-
|
|
203
|
+
count,
|
|
204
|
+
style: kind.id
|
|
205
|
+
}, kind.colors === 'palette' ? {
|
|
152
206
|
palette: paletteSource === 'theme' ? {
|
|
153
207
|
source: 'theme'
|
|
154
208
|
} : {
|
|
@@ -196,7 +250,7 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
196
250
|
mode,
|
|
197
251
|
aspectRatio,
|
|
198
252
|
count,
|
|
199
|
-
|
|
253
|
+
kind,
|
|
200
254
|
paletteSource,
|
|
201
255
|
colors,
|
|
202
256
|
onCreated,
|
|
@@ -218,53 +272,51 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
218
272
|
pt: 1
|
|
219
273
|
},
|
|
220
274
|
children: [
|
|
221
|
-
|
|
222
|
-
size: "small",
|
|
223
|
-
exclusive: true,
|
|
224
|
-
value: mode,
|
|
225
|
-
onChange: (_event, next)=>{
|
|
226
|
-
if (next) setMode(next);
|
|
227
|
-
},
|
|
228
|
-
"aria-label": "What to make",
|
|
229
|
-
disabled: busy,
|
|
230
|
-
children: [
|
|
231
|
-
/*#__PURE__*/ _jsx(ToggleButton, {
|
|
232
|
-
value: "photo",
|
|
233
|
-
children: 'Photo'
|
|
234
|
-
}),
|
|
235
|
-
/*#__PURE__*/ _jsx(ToggleButton, {
|
|
236
|
-
value: "illustration",
|
|
237
|
-
children: 'Illustration or icon'
|
|
238
|
-
})
|
|
239
|
-
]
|
|
240
|
-
}) : null,
|
|
241
|
-
mode === 'illustration' ? /*#__PURE__*/ _jsx(TextField, {
|
|
275
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
242
276
|
select: true,
|
|
243
277
|
size: "small",
|
|
244
278
|
label: "Kind",
|
|
245
|
-
value:
|
|
246
|
-
onChange: (event)=>
|
|
279
|
+
value: kind.id,
|
|
280
|
+
onChange: (event)=>chooseKind(event.target.value),
|
|
247
281
|
disabled: busy,
|
|
248
|
-
|
|
249
|
-
|
|
282
|
+
fullWidth: true,
|
|
283
|
+
helperText: kind.hint,
|
|
284
|
+
slotProps: {
|
|
285
|
+
select: {
|
|
286
|
+
renderValue: (value)=>{
|
|
287
|
+
var _ref;
|
|
288
|
+
var _aiMediaKind;
|
|
289
|
+
return (_ref = (_aiMediaKind = aiMediaKind(String(value))) == null ? void 0 : _aiMediaKind.label) != null ? _ref : String(value);
|
|
290
|
+
},
|
|
291
|
+
MenuProps: {
|
|
292
|
+
slotProps: {
|
|
293
|
+
paper: {
|
|
294
|
+
sx: {
|
|
295
|
+
maxHeight: 420
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
250
301
|
},
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
value: value,
|
|
254
|
-
children: AI_SVG_STYLE_LABELS[value]
|
|
255
|
-
}, value))
|
|
256
|
-
}) : null,
|
|
302
|
+
children: kindMenuItems(kinds, imageSize)
|
|
303
|
+
}),
|
|
257
304
|
/*#__PURE__*/ _jsx(TextField, {
|
|
258
305
|
label: mode === 'photo' ? 'Describe the picture' : 'Describe what to draw',
|
|
259
|
-
placeholder:
|
|
306
|
+
placeholder: kind.placeholder,
|
|
260
307
|
value: prompt,
|
|
261
308
|
onChange: (event)=>setPrompt(event.target.value.slice(0, AI_IMAGE_PROMPT_MAX_CHARS)),
|
|
262
309
|
multiline: true,
|
|
263
310
|
minRows: 3,
|
|
264
311
|
autoFocus: true,
|
|
265
312
|
disabled: busy,
|
|
266
|
-
helperText: "The description becomes each picture's alt text, which you can edit."
|
|
313
|
+
helperText: kind.colors === 'describe' ? `${AI_MEDIA_DESCRIBE_COLORS_NOTE} The description becomes each picture's alt text, which you can edit.` : "The description becomes each picture's alt text, which you can edit."
|
|
267
314
|
}),
|
|
315
|
+
mode === 'photo' ? /*#__PURE__*/ _jsx(Typography, {
|
|
316
|
+
variant: "body2",
|
|
317
|
+
color: "text.secondary",
|
|
318
|
+
children: AI_MEDIA_RASTER_SAFETY_NOTE
|
|
319
|
+
}) : null,
|
|
268
320
|
/*#__PURE__*/ _jsxs(Stack, {
|
|
269
321
|
spacing: 0.5,
|
|
270
322
|
children: [
|
|
@@ -293,7 +345,7 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
293
345
|
})
|
|
294
346
|
]
|
|
295
347
|
}),
|
|
296
|
-
|
|
348
|
+
kind.colors === 'palette' ? /*#__PURE__*/ _jsxs(Stack, {
|
|
297
349
|
spacing: 1,
|
|
298
350
|
children: [
|
|
299
351
|
/*#__PURE__*/ _jsxs(RadioGroup, {
|
|
@@ -390,7 +442,7 @@ export function AiMediaCreateDialog({ open, onClose, orgId, hostId, library, fol
|
|
|
390
442
|
/*#__PURE__*/ _jsx(Typography, {
|
|
391
443
|
variant: "body2",
|
|
392
444
|
color: "text.secondary",
|
|
393
|
-
children: aiMediaCreditEstimate(mode, count)
|
|
445
|
+
children: aiMediaCreditEstimate(mode, count, imageSize)
|
|
394
446
|
}),
|
|
395
447
|
busy ? /*#__PURE__*/ _jsxs(Stack, {
|
|
396
448
|
spacing: 1,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-media-create.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport type { ConsoleMediaLibraryZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n IconButton,\n LinearProgress,\n MenuItem,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { aiMediaCreditsPerPicture } from '../model/ai-media-credits'\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 aiImagePhotosOffered,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiSvgStyle,\n} from '../providers/image-contract'\n\n/**\n * \"Create with AI\" in Media (AGL-3602): pictures from a description, added to\n * the library that is open, through the `mediaLibrary` zone beside Upload\n * media and in the library's empty state.\n *\n * It draws from the shell's gates alone — the plan, the member's\n * `ai.generate`, the plugin being on for the workspace and the site — and asks\n * no server anything until someone creates a picture: the door decides then,\n * and the dialog says what it decided. Two modes: an Illustration (an SVG the\n * text AI draws, wherever text AI runs) and, where the deployment makes them,\n * a Photo.\n */\n\n/** Where the shell sends a reader to buy what the plan lacks (AGL-3601). */\nexport interface AiMediaUpgrade {\n billingHref: string\n canManageBilling: boolean\n}\n\nexport interface AiMediaCreateButtonProps extends ConsoleMediaLibraryZoneProps {\n /**\n * `false` when the shell mounted this as its own upsell: the plan lacks AI\n * generation and an add-on would grant it. Absent means entitled.\n */\n entitled?: boolean\n upgrade?: AiMediaUpgrade\n}\n\n/** The words each shape is offered in. */\nconst ASPECT_LABELS: Record<AiImageAspectRatio, string> = {\n '1:1': 'Square',\n '4:3': 'Landscape',\n '3:4': 'Portrait',\n '16:9': 'Wide',\n '9:16': 'Tall',\n}\n\n/** The words each kind of illustration is offered in. */\nexport const AI_SVG_STYLE_LABELS: Record<AiSvgStyle, string> = {\n illustration: 'Illustration',\n icon: 'Icon',\n pattern: 'Pattern',\n logo: 'Logo mark',\n}\n\n/** The sentence the estimate reads as, before anything is spent. */\nexport function aiMediaCreditEstimate(mode: AiImageMode, count: number): string {\n const each = aiMediaCreditsPerPicture(mode)\n const pictures = count === 1 ? '1 picture' : `${count} pictures`\n return (\n `${pictures} uses about ${(count * each).toLocaleString()} AI credits ` +\n `(about ${each.toLocaleString()} each). A picture that is held back or does not come out is not charged.`\n )\n}\n\nexport function AiMediaCreateButton(props: AiMediaCreateButtonProps) {\n const { orgId, entitled, upgrade } = props\n const [open, setOpen] = useState(false)\n if (!orgId) return null\n const button = (\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n )\n if (entitled === false) {\n if (!upgrade) return null\n return (\n <>\n {button}\n <Dialog open={open} onClose={() => setOpen(false)} fullWidth maxWidth=\"xs\">\n <DialogTitle>{'Create images with AI'}</DialogTitle>\n <DialogContent>\n <Typography>\n {upgrade.canManageBilling\n ? 'Creating pictures and illustrations comes with the AI add-on.'\n : 'Creating pictures and illustrations comes with the AI add-on. Ask an owner or admin of this workspace to add it.'}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpen(false)}>{'Close'}</Button>\n {upgrade.canManageBilling ? (\n <Button variant=\"contained\" href={upgrade.billingHref}>\n {'See the AI add-on'}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n }\n return (\n <>\n {button}\n <AiMediaCreateDialog {...props} orgId={orgId} open={open} onClose={() => setOpen(false)} />\n </>\n )\n}\n\nexport interface AiMediaCreateDialogProps\n extends Pick<ConsoleMediaLibraryZoneProps, 'hostId' | 'library' | 'folderId' | 'onCreated'> {\n open: boolean\n onClose: () => void\n orgId: string\n}\n\nexport function AiMediaCreateDialog({\n open,\n onClose,\n orgId,\n hostId,\n library,\n folderId,\n onCreated,\n}: AiMediaCreateDialogProps) {\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const photos = aiImagePhotosOffered()\n const [mode, setMode] = useState<AiImageMode>(photos ? 'photo' : 'illustration')\n const [style, setStyle] = useState<AiSvgStyle>('illustration')\n // The site theme's colors need a site; the organization's library names none.\n const [paletteSource, setPaletteSource] = useState<'theme' | 'custom'>(hostId ? 'theme' : 'custom')\n const [colors, setColors] = useState<string[]>(['#1a73e8', '#fbbc04'])\n const [prompt, setPrompt] = useState('')\n const [aspectRatio, setAspectRatio] = useState<AiImageAspectRatio>('1:1')\n const [count, setCount] = useState(1)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<{ severity: 'error' | 'warning'; text: string } | null>(null)\n\n useEffect(() => {\n if (open) setNotice(null)\n }, [open])\n\n const create = useCallback(async () => {\n const description = prompt.trim()\n if (!description) return\n setBusy(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/media/images', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n library,\n hostId,\n folderId,\n mode,\n prompt: description,\n aspectRatio,\n count,\n ...(mode === 'illustration'\n ? {\n style,\n palette:\n paletteSource === 'theme' ? { source: 'theme' } : { source: 'custom', colors },\n }\n : {}),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice({\n severity: 'error',\n text: locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The pictures could not be made — try again.'),\n })\n return\n }\n const ids: string[] = Array.isArray(payload?.mediaIds) ? payload.mediaIds : []\n onCreated(ids)\n const missing = Number(payload?.filtered ?? 0) + Number(payload?.failed ?? 0)\n if (missing > 0) {\n setNotice({\n severity: 'warning',\n text:\n `Added ${ids.length} of ${count} to the library. ` +\n (payload?.warning\n ? String(payload.warning)\n : 'The rest were held back and were not charged.'),\n })\n return\n }\n setPrompt('')\n onClose()\n } catch {\n setNotice({ severity: 'error', text: 'The pictures could not be made — try again.' })\n } finally {\n setBusy(false)\n }\n }, [prompt, orgId, library, hostId, folderId, mode, aspectRatio, count, style, paletteSource, colors, onCreated, onClose])\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{'Create images with AI'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {photos ? (\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n value={mode}\n onChange={(_event, next) => {\n if (next) setMode(next)\n }}\n aria-label=\"What to make\"\n disabled={busy}\n >\n <ToggleButton value=\"photo\">{'Photo'}</ToggleButton>\n <ToggleButton value=\"illustration\">{'Illustration or icon'}</ToggleButton>\n </ToggleButtonGroup>\n ) : null}\n {mode === 'illustration' ? (\n <TextField\n select\n size=\"small\"\n label=\"Kind\"\n value={style}\n onChange={(event) => setStyle(event.target.value as AiSvgStyle)}\n disabled={busy}\n sx={{ maxWidth: 240 }}\n helperText={\n style === 'logo'\n ? 'A simple symbol, without lettering. Not a real brand’s logo.'\n : style === 'pattern'\n ? 'A seamless background that tiles.'\n : 'Drawn as an SVG, sharp at any size.'\n }\n >\n {AI_SVG_STYLES.map((value) => (\n <MenuItem key={value} value={value}>\n {AI_SVG_STYLE_LABELS[value]}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label={mode === 'photo' ? 'Describe the picture' : 'Describe what to draw'}\n placeholder={\n mode === 'photo'\n ? 'A sunlit bakery counter with fresh sourdough loaves, warm morning light'\n : 'A friendly delivery van with a parcel, simple and flat'\n }\n value={prompt}\n onChange={(event) => setPrompt(event.target.value.slice(0, AI_IMAGE_PROMPT_MAX_CHARS))}\n multiline\n minRows={3}\n autoFocus\n disabled={busy}\n helperText=\"The description becomes each picture's alt text, which you can edit.\"\n />\n <Stack spacing={0.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Shape'}\n </Typography>\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n value={aspectRatio}\n onChange={(_event, next) => {\n if (next) setAspectRatio(next)\n }}\n aria-label=\"Shape\"\n disabled={busy}\n sx={{ flexWrap: 'wrap' }}\n >\n {AI_IMAGE_ASPECT_RATIOS.map((ratio) => (\n <ToggleButton key={ratio} value={ratio} aria-label={`${ASPECT_LABELS[ratio]} ${ratio}`}>\n {`${ASPECT_LABELS[ratio]} ${ratio}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Stack>\n {mode === 'illustration' ? (\n <Stack spacing={1}>\n <RadioGroup\n value={paletteSource}\n onChange={(event) => setPaletteSource(event.target.value as 'theme' | 'custom')}\n aria-label=\"Colors\"\n >\n <FormControlLabel\n value=\"theme\"\n control={<Radio size=\"small\" />}\n label=\"Use my site's theme colors\"\n disabled={busy || !hostId}\n />\n <FormControlLabel\n value=\"custom\"\n control={<Radio size=\"small\" />}\n label=\"Choose colors\"\n disabled={busy}\n />\n </RadioGroup>\n {paletteSource === 'custom' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n {colors.map((color, index) => (\n <Stack key={index} direction=\"row\" sx={{ alignItems: 'center' }}>\n <TextField\n type=\"color\"\n size=\"small\"\n value={color}\n onChange={(event) =>\n setColors((current) =>\n current.map((value, at) => (at === index ? event.target.value : value)),\n )\n }\n disabled={busy}\n slotProps={{ htmlInput: { 'aria-label': `Color ${index + 1}` } }}\n sx={{ width: 64 }}\n />\n {colors.length > 1 ? (\n <IconButton\n size=\"small\"\n aria-label={`Remove color ${index + 1}`}\n onClick={() => setColors((current) => current.filter((_value, at) => at !== index))}\n disabled={busy}\n >\n {'×'}\n </IconButton>\n ) : null}\n </Stack>\n ))}\n {colors.length < AI_SVG_MAX_COLORS ? (\n <Button size=\"small\" onClick={() => setColors((current) => [...current, '#333333'])} disabled={busy}>\n {'Add a color'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"How many\"\n value={count}\n onChange={(event) => setCount(Number(event.target.value))}\n disabled={busy}\n sx={{ maxWidth: 160 }}\n >\n {Array.from({ length: AI_IMAGE_MAX_COUNT }, (_unused, index) => index + 1).map((value) => (\n <MenuItem key={value} value={value}>\n {value}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiMediaCreditEstimate(mode, count)}\n </Typography>\n {busy ? (\n <Stack spacing={1}>\n <LinearProgress />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Making your pictures and adding them to the library…'}\n </Typography>\n </Stack>\n ) : null}\n {notice ? <Alert severity={notice.severity}>{notice.text}</Alert> : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {'Close'}\n </Button>\n <Button variant=\"contained\" onClick={() => void create()} disabled={busy || !prompt.trim()}>\n {busy ? 'Creating…' : 'Create'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default AiMediaCreateButton\n"],"names":["lockdownRefusalText","parseLockdownRefusal","mdiCreation","MdiIcon","authorizedFetch","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","IconButton","LinearProgress","MenuItem","Radio","RadioGroup","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useCallback","useEffect","useRef","useState","aiMediaCreditsPerPicture","AI_IMAGE_ASPECT_RATIOS","AI_IMAGE_MAX_COUNT","AI_IMAGE_PROMPT_MAX_CHARS","AI_SVG_MAX_COLORS","AI_SVG_STYLES","aiImagePhotosOffered","ASPECT_LABELS","AI_SVG_STYLE_LABELS","illustration","icon","pattern","logo","aiMediaCreditEstimate","mode","count","each","pictures","toLocaleString","AiMediaCreateButton","props","orgId","entitled","upgrade","open","setOpen","button","size","variant","startIcon","path","onClick","onClose","fullWidth","maxWidth","canManageBilling","href","billingHref","AiMediaCreateDialog","hostId","library","folderId","onCreated","data","user","userRef","current","photos","setMode","style","setStyle","paletteSource","setPaletteSource","colors","setColors","prompt","setPrompt","aspectRatio","setAspectRatio","setCount","busy","setBusy","notice","setNotice","create","description","trim","response","method","headers","body","JSON","stringify","palette","source","payload","json","catch","ok","locked","status","severity","text","String","error","ids","Array","isArray","mediaIds","missing","Number","filtered","failed","length","warning","undefined","spacing","sx","pt","exclusive","value","onChange","_event","next","aria-label","disabled","select","label","event","target","helperText","map","placeholder","slice","multiline","minRows","autoFocus","color","flexWrap","ratio","control","direction","alignItems","index","type","at","slotProps","htmlInput","width","filter","_value","from","_unused"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-media-create.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal, resolveEffectivePlan } from '@aglyn/aglyn'\nimport type { ConsoleMediaLibraryZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useFirestoreDoc, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n IconButton,\n LinearProgress,\n ListItemText,\n ListSubheader,\n MenuItem,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { aiMediaCreditsPerPicture } from '../model/ai-media-credits'\nimport {\n AI_MEDIA_KIND_GROUP_LABELS,\n AI_MEDIA_KIND_GROUPS,\n aiMediaKind,\n aiMediaKindsOffered,\n type AiMediaKind,\n type AiMediaKindId,\n} from '../model/ai-media-kinds'\nimport {\n AI_IMAGE_ASPECT_RATIOS,\n AI_IMAGE_MAX_COUNT,\n AI_IMAGE_PROMPT_MAX_CHARS,\n aiImageSizeForPlan,\n AI_SVG_MAX_COLORS,\n aiImagePhotosOffered,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiImageSize,\n} from '../providers/image-contract'\n\n/**\n * \"Create with AI\" in Media (AGL-3602): pictures from a description, added to\n * the library that is open, through the `mediaLibrary` zone beside Upload\n * media and in the library's empty state.\n *\n * It draws from the shell's gates alone — the plan, the member's\n * `ai.generate`, the plugin being on for the workspace and the site — and asks\n * no server anything until someone creates a picture: the door decides then,\n * and the dialog says what it decided. One Kind menu, in sections: Vector\n * kinds (an SVG the text AI draws, wherever text AI runs) and, where the\n * deployment makes them, Photo, Art and Design kinds from the image provider.\n */\n\n/** Where the shell sends a reader to buy what the plan lacks (AGL-3601). */\nexport interface AiMediaUpgrade {\n billingHref: string\n canManageBilling: boolean\n}\n\nexport interface AiMediaCreateButtonProps extends ConsoleMediaLibraryZoneProps {\n /**\n * `false` when the shell mounted this as its own upsell: the plan lacks AI\n * generation and an add-on would grant it. Absent means entitled.\n */\n entitled?: boolean\n upgrade?: AiMediaUpgrade\n}\n\n/** The words each shape is offered in. */\nconst ASPECT_LABELS: Record<AiImageAspectRatio, string> = {\n '1:1': 'Square',\n '4:3': 'Landscape',\n '3:4': 'Portrait',\n '16:9': 'Wide',\n '9:16': 'Tall',\n}\n\n/**\n * What a picture from the image provider may be declined for, as the code\n * and the provider decide it: Google's filters, and its policies on real\n * people and brands. A declined picture is never charged.\n */\nexport const AI_MEDIA_RASTER_SAFETY_NOTE =\n 'Pictures of real, identifiable people, celebrities or brands may be declined by the image service. A declined picture is not charged.'\n\n/** The line under the description for a kind whose colors are described rather than picked. */\nexport const AI_MEDIA_DESCRIBE_COLORS_NOTE =\n 'To steer the colors, name them in the description, such as navy and coral.'\n\n/** The menu's items: each section's heading, then its kinds with what one costs. */\nfunction kindMenuItems(kinds: readonly AiMediaKind[], size: AiImageSize): ReactNode[] {\n const groups = AI_MEDIA_KIND_GROUPS.filter((group) => kinds.some((kind) => kind.group === group))\n const item = (kind: AiMediaKind) => (\n <MenuItem key={kind.id} value={kind.id} sx={{ gap: 2 }}>\n <ListItemText primary={kind.label} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`about ${aiMediaCreditsPerPicture(kind.mode, undefined, size).toLocaleString()} credits`}\n </Typography>\n </MenuItem>\n )\n // One section needs no heading: a deployment without the image provider\n // lists the vector kinds alone, as it always has.\n if (groups.length < 2) return kinds.map(item)\n return groups.flatMap((group) => [\n <ListSubheader key={`group:${group}`}>{AI_MEDIA_KIND_GROUP_LABELS[group]}</ListSubheader>,\n ...kinds.filter((kind) => kind.group === group).map(item),\n ])\n}\n\n/**\n * The size the door will make a picture at for this workspace (AGL-3602):\n * 512 px on the Free plan, 1K on a paid one. The door decides from the plan\n * itself; this reads the same plan so the estimate matches, and reads 1K —\n * the dearer estimate — until the workspace has arrived.\n */\nexport function useAiMediaImageSize(orgId: string): AiImageSize {\n const firestore = useFirestore()\n const { data: org, status } = useFirestoreDoc<Record<string, unknown>>(\n () => doc(firestore, 'orgs', orgId),\n [firestore, orgId],\n )\n return aiImageSizeForPlan(status === 'success' && resolveEffectivePlan(org as never) === 'free')\n}\n\n/** The sentence the estimate reads as, before anything is spent. */\nexport function aiMediaCreditEstimate(\n mode: AiImageMode,\n count: number,\n size: AiImageSize = '1K',\n): string {\n const each = aiMediaCreditsPerPicture(mode, undefined, size)\n const pictures = count === 1 ? '1 picture' : `${count} pictures`\n return (\n `${pictures} uses about ${(count * each).toLocaleString()} AI credits ` +\n `(about ${each.toLocaleString()} each). A picture that is held back or does not come out is not charged.`\n )\n}\n\nexport function AiMediaCreateButton(props: AiMediaCreateButtonProps) {\n const { orgId, entitled, upgrade } = props\n const [open, setOpen] = useState(false)\n if (!orgId) return null\n const button = (\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n )\n if (entitled === false) {\n if (!upgrade) return null\n return (\n <>\n {button}\n <Dialog open={open} onClose={() => setOpen(false)} fullWidth maxWidth=\"xs\">\n <DialogTitle>{'Create images with AI'}</DialogTitle>\n <DialogContent>\n <Typography>\n {upgrade.canManageBilling\n ? 'Creating pictures and illustrations comes with the AI add-on.'\n : 'Creating pictures and illustrations comes with the AI add-on. Ask an owner or admin of this workspace to add it.'}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpen(false)}>{'Close'}</Button>\n {upgrade.canManageBilling ? (\n <Button variant=\"contained\" href={upgrade.billingHref}>\n {'See the AI add-on'}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n }\n return (\n <>\n {button}\n <AiMediaCreateDialog {...props} orgId={orgId} open={open} onClose={() => setOpen(false)} />\n </>\n )\n}\n\nexport interface AiMediaCreateDialogProps\n extends Pick<ConsoleMediaLibraryZoneProps, 'hostId' | 'library' | 'folderId' | 'onCreated'> {\n open: boolean\n onClose: () => void\n orgId: string\n}\n\nexport function AiMediaCreateDialog({\n open,\n onClose,\n orgId,\n hostId,\n library,\n folderId,\n onCreated,\n}: AiMediaCreateDialogProps) {\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const photos = aiImagePhotosOffered()\n const imageSize = useAiMediaImageSize(orgId)\n const kinds = useMemo(() => aiMediaKindsOffered(photos), [photos])\n const [kindId, setKindId] = useState<AiMediaKindId>(photos ? 'photo' : 'illustration')\n const kind = kinds.find((entry) => entry.id === kindId) ?? kinds[0]\n const mode = kind.mode\n // The site theme's colors need a site; the organization's library names none.\n const [paletteSource, setPaletteSource] = useState<'theme' | 'custom'>(hostId ? 'theme' : 'custom')\n const [colors, setColors] = useState<string[]>(['#1a73e8', '#fbbc04'])\n const [prompt, setPrompt] = useState('')\n const [aspectRatio, setAspectRatio] = useState<AiImageAspectRatio>(kind.aspectRatio)\n const [count, setCount] = useState(1)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<{ severity: 'error' | 'warning'; text: string } | null>(null)\n\n useEffect(() => {\n if (open) setNotice(null)\n }, [open])\n\n /** A new kind brings its own shape; the person may still change it. */\n const chooseKind = (next: AiMediaKindId) => {\n setKindId(next)\n const chosen = aiMediaKind(next)\n if (chosen) setAspectRatio(chosen.aspectRatio)\n }\n\n const create = useCallback(async () => {\n const description = prompt.trim()\n if (!description) return\n setBusy(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/media/images', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n library,\n hostId,\n folderId,\n mode,\n prompt: description,\n aspectRatio,\n count,\n style: kind.id,\n ...(kind.colors === 'palette'\n ? {\n palette:\n paletteSource === 'theme' ? { source: 'theme' } : { source: 'custom', colors },\n }\n : {}),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice({\n severity: 'error',\n text: locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The pictures could not be made — try again.'),\n })\n return\n }\n const ids: string[] = Array.isArray(payload?.mediaIds) ? payload.mediaIds : []\n onCreated(ids)\n const missing = Number(payload?.filtered ?? 0) + Number(payload?.failed ?? 0)\n if (missing > 0) {\n setNotice({\n severity: 'warning',\n text:\n `Added ${ids.length} of ${count} to the library. ` +\n (payload?.warning\n ? String(payload.warning)\n : 'The rest were held back and were not charged.'),\n })\n return\n }\n setPrompt('')\n onClose()\n } catch {\n setNotice({ severity: 'error', text: 'The pictures could not be made — try again.' })\n } finally {\n setBusy(false)\n }\n }, [prompt, orgId, library, hostId, folderId, mode, aspectRatio, count, kind, paletteSource, colors, onCreated, onClose])\n\n return (\n <Dialog open={open} onClose={busy ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{'Create images with AI'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n select\n size=\"small\"\n label=\"Kind\"\n value={kind.id}\n onChange={(event) => chooseKind(event.target.value as AiMediaKindId)}\n disabled={busy}\n fullWidth\n helperText={kind.hint}\n slotProps={{\n select: {\n renderValue: (value) => aiMediaKind(String(value))?.label ?? String(value),\n MenuProps: { slotProps: { paper: { sx: { maxHeight: 420 } } } },\n },\n }}\n >\n {kindMenuItems(kinds, imageSize)}\n </TextField>\n <TextField\n label={mode === 'photo' ? 'Describe the picture' : 'Describe what to draw'}\n placeholder={kind.placeholder}\n value={prompt}\n onChange={(event) => setPrompt(event.target.value.slice(0, AI_IMAGE_PROMPT_MAX_CHARS))}\n multiline\n minRows={3}\n autoFocus\n disabled={busy}\n helperText={\n kind.colors === 'describe'\n ? `${AI_MEDIA_DESCRIBE_COLORS_NOTE} The description becomes each picture's alt text, which you can edit.`\n : \"The description becomes each picture's alt text, which you can edit.\"\n }\n />\n {mode === 'photo' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {AI_MEDIA_RASTER_SAFETY_NOTE}\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Shape'}\n </Typography>\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n value={aspectRatio}\n onChange={(_event, next) => {\n if (next) setAspectRatio(next)\n }}\n aria-label=\"Shape\"\n disabled={busy}\n sx={{ flexWrap: 'wrap' }}\n >\n {AI_IMAGE_ASPECT_RATIOS.map((ratio) => (\n <ToggleButton key={ratio} value={ratio} aria-label={`${ASPECT_LABELS[ratio]} ${ratio}`}>\n {`${ASPECT_LABELS[ratio]} ${ratio}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Stack>\n {kind.colors === 'palette' ? (\n <Stack spacing={1}>\n <RadioGroup\n value={paletteSource}\n onChange={(event) => setPaletteSource(event.target.value as 'theme' | 'custom')}\n aria-label=\"Colors\"\n >\n <FormControlLabel\n value=\"theme\"\n control={<Radio size=\"small\" />}\n label=\"Use my site's theme colors\"\n disabled={busy || !hostId}\n />\n <FormControlLabel\n value=\"custom\"\n control={<Radio size=\"small\" />}\n label=\"Choose colors\"\n disabled={busy}\n />\n </RadioGroup>\n {paletteSource === 'custom' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n {colors.map((color, index) => (\n <Stack key={index} direction=\"row\" sx={{ alignItems: 'center' }}>\n <TextField\n type=\"color\"\n size=\"small\"\n value={color}\n onChange={(event) =>\n setColors((current) =>\n current.map((value, at) => (at === index ? event.target.value : value)),\n )\n }\n disabled={busy}\n slotProps={{ htmlInput: { 'aria-label': `Color ${index + 1}` } }}\n sx={{ width: 64 }}\n />\n {colors.length > 1 ? (\n <IconButton\n size=\"small\"\n aria-label={`Remove color ${index + 1}`}\n onClick={() => setColors((current) => current.filter((_value, at) => at !== index))}\n disabled={busy}\n >\n {'×'}\n </IconButton>\n ) : null}\n </Stack>\n ))}\n {colors.length < AI_SVG_MAX_COLORS ? (\n <Button size=\"small\" onClick={() => setColors((current) => [...current, '#333333'])} disabled={busy}>\n {'Add a color'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"How many\"\n value={count}\n onChange={(event) => setCount(Number(event.target.value))}\n disabled={busy}\n sx={{ maxWidth: 160 }}\n >\n {Array.from({ length: AI_IMAGE_MAX_COUNT }, (_unused, index) => index + 1).map((value) => (\n <MenuItem key={value} value={value}>\n {value}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiMediaCreditEstimate(mode, count, imageSize)}\n </Typography>\n {busy ? (\n <Stack spacing={1}>\n <LinearProgress />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Making your pictures and adding them to the library…'}\n </Typography>\n </Stack>\n ) : null}\n {notice ? <Alert severity={notice.severity}>{notice.text}</Alert> : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={busy}>\n {'Close'}\n </Button>\n <Button variant=\"contained\" onClick={() => void create()} disabled={busy || !prompt.trim()}>\n {busy ? 'Creating…' : 'Create'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default AiMediaCreateButton\n"],"names":["lockdownRefusalText","parseLockdownRefusal","resolveEffectivePlan","mdiCreation","MdiIcon","authorizedFetch","useFirestore","useFirestoreDoc","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","IconButton","LinearProgress","ListItemText","ListSubheader","MenuItem","Radio","RadioGroup","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","doc","useCallback","useEffect","useMemo","useRef","useState","aiMediaCreditsPerPicture","AI_MEDIA_KIND_GROUP_LABELS","AI_MEDIA_KIND_GROUPS","aiMediaKind","aiMediaKindsOffered","AI_IMAGE_ASPECT_RATIOS","AI_IMAGE_MAX_COUNT","AI_IMAGE_PROMPT_MAX_CHARS","aiImageSizeForPlan","AI_SVG_MAX_COLORS","aiImagePhotosOffered","ASPECT_LABELS","AI_MEDIA_RASTER_SAFETY_NOTE","AI_MEDIA_DESCRIBE_COLORS_NOTE","kindMenuItems","kinds","size","groups","filter","group","some","kind","item","value","id","sx","gap","primary","label","variant","color","mode","undefined","toLocaleString","length","map","flatMap","useAiMediaImageSize","orgId","firestore","data","org","status","aiMediaCreditEstimate","count","each","pictures","AiMediaCreateButton","props","entitled","upgrade","open","setOpen","button","startIcon","path","onClick","onClose","fullWidth","maxWidth","canManageBilling","href","billingHref","AiMediaCreateDialog","hostId","library","folderId","onCreated","user","userRef","current","photos","imageSize","kindId","setKindId","find","entry","paletteSource","setPaletteSource","colors","setColors","prompt","setPrompt","aspectRatio","setAspectRatio","setCount","busy","setBusy","notice","setNotice","chooseKind","next","chosen","create","description","trim","response","method","headers","body","JSON","stringify","style","palette","source","payload","json","catch","ok","locked","severity","text","String","error","ids","Array","isArray","mediaIds","missing","Number","filtered","failed","warning","spacing","pt","select","onChange","event","target","disabled","helperText","hint","slotProps","renderValue","MenuProps","paper","maxHeight","placeholder","slice","multiline","minRows","autoFocus","exclusive","_event","aria-label","flexWrap","ratio","control","direction","alignItems","index","type","at","htmlInput","width","_value","from","_unused"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-seo-fields-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { SeoKeywordCoverage } from '@aglyn/aglyn/app-utils/seo-keywords'\nimport {\n SEO_LISTING_FIELDS,\n seoListingFieldCount,\n seoListingFieldTooLong,\n type SeoListingFieldKey,\n} from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport type {\n ConsoleSeoFieldsZoneProps,\n ConsoleSeoFieldValues,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport {\n aiSeoAuditView,\n aiSeoFieldsProposalOf,\n type AiSeoFieldValues,\n} from '../model/ai-seo'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * \"Write SEO\" (AGL-2910), inside a search listing editor through the\n * `seoFields` zone: the screen detail page's SEO card, and the product\n * editor's search engine listing. A person asks; an `seo` job writes the\n * listing from what the page or the product says; the proposal is shown with\n * each field's count, and \"Put in the fields\" stages it in the editor above\n * as unsaved changes. The editor's own Save is the write — this card never\n * writes a listing, and a page's listing is what its live head serves.\n *\n * On a page, the card also offers what a site audit proposed for it, once\n * that audit was applied: its listing values wait here rather than being\n * written by the audit.\n *\n * It renders nothing until the jobs route has answered for this workspace:\n * the shell has decided the plan and the permission, but the release flag is\n * the route's, and a 404 there is this card staying absent.\n */\n\n/** How many recent jobs the card reads to find this listing's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How much of a product's description a job carries. */\nconst PRODUCT_TEXT_MAX_CHARS = 2_000\n\nconst KEYWORDS_MAX_CHARS = 300\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposed values this editor can take: its own fields, and an image description only beside an image. */\nexport function aiSeoValuesForEditor(\n values: AiSeoFieldValues,\n fields: readonly SeoListingFieldKey[],\n hasImage: boolean,\n): ConsoleSeoFieldValues {\n const out: ConsoleSeoFieldValues = {}\n for (const key of fields) {\n const value = values[key]\n if (typeof value !== 'string' || !value.trim()) continue\n if (key === 'imageAlt' && !hasImage) continue\n out[key] = value\n }\n return out\n}\n\nfunction Coverage({ keywords }: { keywords: readonly SeoKeywordCoverage[] }) {\n if (!keywords.length) return null\n return (\n <Stack direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', rowGap: 0.5 }} aria-label=\"Keyword coverage\">\n {keywords.map((entry) => {\n const covered = entry.inTitle || entry.inDescription\n return (\n <Chip\n key={entry.keyword}\n size=\"small\"\n variant=\"outlined\"\n color={covered ? 'success' : 'default'}\n label={covered ? entry.keyword : `${entry.keyword} — not used`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction ProposedValues(props: {\n heading: string\n values: ConsoleSeoFieldValues\n fields: readonly SeoListingFieldKey[]\n}) {\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{props.heading}</Typography>\n {props.fields\n .filter((key) => props.values[key])\n .map((key) => (\n <Stack key={key} spacing={0.25}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {SEO_LISTING_FIELDS[key].label}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={seoListingFieldTooLong(key, props.values[key]) ? 'error' : 'default'}\n label={seoListingFieldCount(key, props.values[key])}\n />\n </Stack>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {props.values[key]}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )\n}\n\nexport function AiSeoFieldsCard(props: ConsoleSeoFieldsZoneProps) {\n const { hostId, subject, fields, values, hasImage, proposeValues } = props\n // A plugin-hosted editor may not know the org; the site does.\n const hostOrgId = useHostOrgId(props.orgId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const subjectKind = subject.kind\n const subjectId = subject.id ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [keywords, setKeywords] = useState('')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [fromAudit, setFromAudit] = useState<{ jobId: string; values: AiSeoFieldValues } | null>(null)\n const [staged, setStaged] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n setJob(null)\n setFromAudit(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent SEO proposals could not be loaded.'),\n )\n } else if (subjectId) {\n const jobs = ((payload?.jobs ?? []) as AiJobSummary[]).filter(\n (entry) => entry.kind === 'seo' && entry.hostId === hostId,\n )\n const latest = jobs.find((entry) => {\n const proposal = aiSeoFieldsProposalOf(entry)\n return proposal?.subject.kind === subjectKind && proposal.subject.id === subjectId\n })\n if (latest) setJob(latest)\n if (subjectKind === 'screen') {\n const audit = jobs.find((entry) => entry.applied?.staged.includes(subjectId))\n const fix = audit ? aiSeoAuditView(audit.outputs)?.fixes[subjectId] : undefined\n if (audit && fix && Object.keys(fix.values).length) {\n setFromAudit({ jobId: audit.id, values: fix.values })\n }\n }\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent SEO proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid, subjectKind, subjectId])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const write = useCallback(async () => {\n if (!orgId) return\n setBusy(true)\n setNotice(null)\n setStaged(null)\n const common = { fields: fields.join(','), keywords: keywords.trim() }\n const inputs =\n subject.kind === 'screen'\n ? { target: 'screen', screenId: subject.id, versionId: subject.versionId ?? '', ...common }\n : {\n target: 'product',\n productId: subject.id ?? '',\n name: subject.name.trim(),\n text: subject.description.slice(0, PRODUCT_TEXT_MAX_CHARS),\n currentTitle: values.title ?? '',\n currentDescription: values.description ?? '',\n ...common,\n }\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'seo',\n brief: `Write the search listing for ${subject.name.trim() || 'this page'}`,\n inputs,\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The SEO proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The SEO proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, subject, fields, keywords, values.title, values.description, watch])\n\n if (verdict !== 'ready') return null\n\n const proposal = aiSeoFieldsProposalOf(job)\n const offered = proposal ? aiSeoValuesForEditor(proposal.values, fields, hasImage) : {}\n const audited = fromAudit ? aiSeoValuesForEditor(fromAudit.values, fields, hasImage) : {}\n const running = Boolean(job && !isTerminal(job))\n const saveWord = subject.kind === 'product' ? 'Save product' : 'Save SEO'\n const unnamed = subject.kind === 'product' && !subject.name.trim()\n const help = pluginDocsHelp('aiSeo', {\n excerpt: 'Write a search listing from what the page says. It is put in the fields as unsaved changes; nothing is saved until you save.',\n })\n\n const stage = (offer: ConsoleSeoFieldValues, key: string) => {\n proposeValues(offer, key)\n setStaged(key)\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"SEO assistant\">\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Write with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Target keywords (optional)\"\n placeholder=\"brass desk lamps, dimmable\"\n helperText=\"Comma-separated. Used only where the page is about them, never repeated to rank.\"\n value={keywords}\n onChange={(event) => setKeywords(event.target.value.slice(0, KEYWORDS_MAX_CHARS))}\n disabled={busy || running}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || running || unnamed || !orgId} onClick={() => void write()}>\n {proposal ? 'Write again' : 'Write SEO'}\n </Button>\n {running ? <CircularProgress size={16} aria-label=\"Writing the listing\" /> : null}\n {unnamed ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Name the product first.'}\n </Typography>\n ) : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n {job?.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!proposal && fromAudit && Object.keys(audited).length ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed by the site audit\" values={audited} fields={fields} />\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={staged === `audit:${fromAudit.jobId}`}\n onClick={() => stage(audited, `audit:${fromAudit.jobId}`)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {proposal && job ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed listing\" values={offered} fields={fields} />\n <Coverage keywords={proposal.keywords} />\n {proposal.notes.map((note) => (\n <Typography key={note} variant=\"caption\" color=\"text.secondary\">\n {note}\n </Typography>\n ))}\n {job.creditsSpent > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Used ${job.creditsSpent} ${job.creditsSpent === 1 ? 'credit' : 'credits'}.`}\n </Typography>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={!Object.keys(offered).length || staged === job.id}\n onClick={() => stage(offered, job.id)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {staged ? (\n <Alert severity=\"success\">\n {`In the fields as unsaved changes. Review them, then press ${saveWord}.`}\n </Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiSeoFieldsCard.displayName = 'AiSeoFieldsCard'\n\nexport default AiSeoFieldsCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","SEO_LISTING_FIELDS","seoListingFieldCount","seoListingFieldTooLong","authorizedFetch","useHostOrgId","useUser","Alert","Box","Button","Chip","CircularProgress","Link","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiSeoAuditView","aiSeoFieldsProposalOf","readEventFrames","RECENT_JOBS_READ","PRODUCT_TEXT_MAX_CHARS","KEYWORDS_MAX_CHARS","isTerminal","job","includes","status","aiSeoValuesForEditor","values","fields","hasImage","out","key","value","trim","Coverage","keywords","length","direction","spacing","sx","flexWrap","rowGap","aria-label","map","entry","covered","inTitle","inDescription","size","variant","color","label","keyword","ProposedValues","props","heading","filter","alignItems","flexGrow","wordBreak","AiSeoFieldsCard","subject","hostId","proposeValues","hostOrgId","orgId","undefined","data","user","userRef","current","uid","subjectKind","kind","subjectId","id","verdict","setVerdict","notice","setNotice","setKeywords","busy","setBusy","setJob","fromAudit","setFromAudit","staged","setStaged","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","String","error","jobs","latest","find","proposal","audit","applied","fix","outputs","fixes","Object","keys","jobId","watch","org","controller","AbortController","again","signal","aborted","body","event","write","common","join","inputs","target","screenId","versionId","productId","name","text","description","slice","currentTitle","title","currentDescription","method","headers","JSON","stringify","brief","next","offered","audited","running","Boolean","saveWord","unnamed","help","excerpt","stage","offer","border","borderColor","borderRadius","p","href","rel","placeholder","helperText","onChange","disabled","onClick","severity","alignSelf","notes","note","creditsSpent","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-seo-fields-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { SeoKeywordCoverage } from '@aglyn/aglyn/app-utils/seo-keywords'\nimport {\n SEO_LISTING_FIELDS,\n seoListingFieldCount,\n seoListingFieldTooLong,\n type SeoListingFieldKey,\n} from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport type {\n ConsoleSeoFieldsZoneProps,\n ConsoleSeoFieldValues,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport {\n aiSeoAuditView,\n aiSeoFieldsProposalOf,\n type AiSeoFieldValues,\n} from '../model/ai-seo'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * \"Write SEO\" (AGL-2910), inside a search listing editor through the\n * `seoFields` zone: the screen detail page's SEO card, and the product\n * editor's search engine listing. A person asks; an `seo` job writes the\n * listing from what the page or the product says; the proposal is shown with\n * each field's count, and \"Put in the fields\" stages it in the editor above\n * as unsaved changes. The editor's own Save is the write — this card never\n * writes a listing, and a page's listing is what its live head serves.\n *\n * On a page, the card also offers what a site audit proposed for it, once\n * that audit was applied: its listing values wait here rather than being\n * written by the audit.\n *\n * It renders nothing until the jobs route has answered for this workspace:\n * the shell has decided the plan and the permission, but the release flag is\n * the route's, and a 404 there is this card staying absent.\n */\n\n/** How many recent jobs the card reads to find this listing's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How much of a product's description a job carries. */\nconst PRODUCT_TEXT_MAX_CHARS = 2_000\n\nconst KEYWORDS_MAX_CHARS = 300\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposed values this editor can take: its own fields, and an image description only beside an image. */\nexport function aiSeoValuesForEditor(\n values: AiSeoFieldValues,\n fields: readonly SeoListingFieldKey[],\n hasImage: boolean,\n): ConsoleSeoFieldValues {\n const out: ConsoleSeoFieldValues = {}\n for (const key of fields) {\n const value = values[key]\n if (typeof value !== 'string' || !value.trim()) continue\n if (key === 'imageAlt' && !hasImage) continue\n out[key] = value\n }\n return out\n}\n\nfunction Coverage({ keywords }: { keywords: readonly SeoKeywordCoverage[] }) {\n if (!keywords.length) return null\n return (\n <Stack useFlexGap direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', rowGap: 0.5 }} aria-label=\"Keyword coverage\">\n {keywords.map((entry) => {\n const covered = entry.inTitle || entry.inDescription\n return (\n <Chip\n key={entry.keyword}\n size=\"small\"\n variant=\"outlined\"\n color={covered ? 'success' : 'default'}\n label={covered ? entry.keyword : `${entry.keyword} — not used`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction ProposedValues(props: {\n heading: string\n values: ConsoleSeoFieldValues\n fields: readonly SeoListingFieldKey[]\n}) {\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{props.heading}</Typography>\n {props.fields\n .filter((key) => props.values[key])\n .map((key) => (\n <Stack key={key} spacing={0.25}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {SEO_LISTING_FIELDS[key].label}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={seoListingFieldTooLong(key, props.values[key]) ? 'error' : 'default'}\n label={seoListingFieldCount(key, props.values[key])}\n />\n </Stack>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {props.values[key]}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )\n}\n\nexport function AiSeoFieldsCard(props: ConsoleSeoFieldsZoneProps) {\n const { hostId, subject, fields, values, hasImage, proposeValues } = props\n // A plugin-hosted editor may not know the org; the site does.\n const hostOrgId = useHostOrgId(props.orgId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const subjectKind = subject.kind\n const subjectId = subject.id ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [keywords, setKeywords] = useState('')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [fromAudit, setFromAudit] = useState<{ jobId: string; values: AiSeoFieldValues } | null>(null)\n const [staged, setStaged] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n setJob(null)\n setFromAudit(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent SEO proposals could not be loaded.'),\n )\n } else if (subjectId) {\n const jobs = ((payload?.jobs ?? []) as AiJobSummary[]).filter(\n (entry) => entry.kind === 'seo' && entry.hostId === hostId,\n )\n const latest = jobs.find((entry) => {\n const proposal = aiSeoFieldsProposalOf(entry)\n return proposal?.subject.kind === subjectKind && proposal.subject.id === subjectId\n })\n if (latest) setJob(latest)\n if (subjectKind === 'screen') {\n const audit = jobs.find((entry) => entry.applied?.staged.includes(subjectId))\n const fix = audit ? aiSeoAuditView(audit.outputs)?.fixes[subjectId] : undefined\n if (audit && fix && Object.keys(fix.values).length) {\n setFromAudit({ jobId: audit.id, values: fix.values })\n }\n }\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent SEO proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid, subjectKind, subjectId])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const write = useCallback(async () => {\n if (!orgId) return\n setBusy(true)\n setNotice(null)\n setStaged(null)\n const common = { fields: fields.join(','), keywords: keywords.trim() }\n const inputs =\n subject.kind === 'screen'\n ? { target: 'screen', screenId: subject.id, versionId: subject.versionId ?? '', ...common }\n : {\n target: 'product',\n productId: subject.id ?? '',\n name: subject.name.trim(),\n text: subject.description.slice(0, PRODUCT_TEXT_MAX_CHARS),\n currentTitle: values.title ?? '',\n currentDescription: values.description ?? '',\n ...common,\n }\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'seo',\n brief: `Write the search listing for ${subject.name.trim() || 'this page'}`,\n inputs,\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The SEO proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The SEO proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, subject, fields, keywords, values.title, values.description, watch])\n\n if (verdict !== 'ready') return null\n\n const proposal = aiSeoFieldsProposalOf(job)\n const offered = proposal ? aiSeoValuesForEditor(proposal.values, fields, hasImage) : {}\n const audited = fromAudit ? aiSeoValuesForEditor(fromAudit.values, fields, hasImage) : {}\n const running = Boolean(job && !isTerminal(job))\n const saveWord = subject.kind === 'product' ? 'Save product' : 'Save SEO'\n const unnamed = subject.kind === 'product' && !subject.name.trim()\n const help = pluginDocsHelp('aiSeo', {\n excerpt: 'Write a search listing from what the page says. It is put in the fields as unsaved changes; nothing is saved until you save.',\n })\n\n const stage = (offer: ConsoleSeoFieldValues, key: string) => {\n proposeValues(offer, key)\n setStaged(key)\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"SEO assistant\">\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Write with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Target keywords (optional)\"\n placeholder=\"brass desk lamps, dimmable\"\n helperText=\"Comma-separated. Used only where the page is about them, never repeated to rank.\"\n value={keywords}\n onChange={(event) => setKeywords(event.target.value.slice(0, KEYWORDS_MAX_CHARS))}\n disabled={busy || running}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || running || unnamed || !orgId} onClick={() => void write()}>\n {proposal ? 'Write again' : 'Write SEO'}\n </Button>\n {running ? <CircularProgress size={16} aria-label=\"Writing the listing\" /> : null}\n {unnamed ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Name the product first.'}\n </Typography>\n ) : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n {job?.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!proposal && fromAudit && Object.keys(audited).length ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed by the site audit\" values={audited} fields={fields} />\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={staged === `audit:${fromAudit.jobId}`}\n onClick={() => stage(audited, `audit:${fromAudit.jobId}`)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {proposal && job ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed listing\" values={offered} fields={fields} />\n <Coverage keywords={proposal.keywords} />\n {proposal.notes.map((note) => (\n <Typography key={note} variant=\"caption\" color=\"text.secondary\">\n {note}\n </Typography>\n ))}\n {job.creditsSpent > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Used ${job.creditsSpent} ${job.creditsSpent === 1 ? 'credit' : 'credits'}.`}\n </Typography>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={!Object.keys(offered).length || staged === job.id}\n onClick={() => stage(offered, job.id)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {staged ? (\n <Alert severity=\"success\">\n {`In the fields as unsaved changes. Review them, then press ${saveWord}.`}\n </Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiSeoFieldsCard.displayName = 'AiSeoFieldsCard'\n\nexport default AiSeoFieldsCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","SEO_LISTING_FIELDS","seoListingFieldCount","seoListingFieldTooLong","authorizedFetch","useHostOrgId","useUser","Alert","Box","Button","Chip","CircularProgress","Link","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiSeoAuditView","aiSeoFieldsProposalOf","readEventFrames","RECENT_JOBS_READ","PRODUCT_TEXT_MAX_CHARS","KEYWORDS_MAX_CHARS","isTerminal","job","includes","status","aiSeoValuesForEditor","values","fields","hasImage","out","key","value","trim","Coverage","keywords","length","useFlexGap","direction","spacing","sx","flexWrap","rowGap","aria-label","map","entry","covered","inTitle","inDescription","size","variant","color","label","keyword","ProposedValues","props","heading","filter","alignItems","flexGrow","wordBreak","AiSeoFieldsCard","subject","hostId","proposeValues","hostOrgId","orgId","undefined","data","user","userRef","current","uid","subjectKind","kind","subjectId","id","verdict","setVerdict","notice","setNotice","setKeywords","busy","setBusy","setJob","fromAudit","setFromAudit","staged","setStaged","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","String","error","jobs","latest","find","proposal","audit","applied","fix","outputs","fixes","Object","keys","jobId","watch","org","controller","AbortController","again","signal","aborted","body","event","write","common","join","inputs","target","screenId","versionId","productId","name","text","description","slice","currentTitle","title","currentDescription","method","headers","JSON","stringify","brief","next","offered","audited","running","Boolean","saveWord","unnamed","help","excerpt","stage","offer","border","borderColor","borderRadius","p","href","rel","placeholder","helperText","onChange","disabled","onClick","severity","alignSelf","notes","note","creditsSpent","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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 { ConsoleHostBusinessProfileZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
|
|
18
|
+
/**
|
|
19
|
+
* What Aglyn AI learned from this site's edits (AGL-3661), on Setup →
|
|
20
|
+
* Business profile: the short preferences the assist edit route distilled
|
|
21
|
+
* from the edits the owner applied, which every later AI job for the site
|
|
22
|
+
* reads. The owner reads them here and forgets one, or all of them.
|
|
23
|
+
*
|
|
24
|
+
* Read and deleted on the client: the rules let a site's members read and
|
|
25
|
+
* delete `aiMemory` and refuse them a create or an update, because a client
|
|
26
|
+
* write would put words of its choosing into later prompts.
|
|
27
|
+
*/
|
|
28
|
+
export declare function AiSiteMemoryCard(props: ConsoleHostBusinessProfileZoneProps): import("react").JSX.Element;
|
|
29
|
+
export declare namespace AiSiteMemoryCard {
|
|
30
|
+
var displayName: string;
|
|
31
|
+
}
|
|
32
|
+
export default AiSiteMemoryCard;
|