@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.
Files changed (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. 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 { useCallback, useEffect, useRef, useState } from "react";
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 { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS, AI_SVG_MAX_COLORS, AI_SVG_STYLES, aiImagePhotosOffered } from "../providers/image-contract.js";
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
- /** The words each kind of illustration is offered in. */ export const AI_SVG_STYLE_LABELS = {
35
- illustration: 'Illustration',
36
- icon: 'Icon',
37
- pattern: 'Pattern',
38
- logo: 'Logo mark'
39
- };
40
- /** The sentence the estimate reads as, before anything is spent. */ export function aiMediaCreditEstimate(mode, count) {
41
- const each = aiMediaCreditsPerPicture(mode);
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 [mode, setMode] = useState(photos ? 'photo' : 'illustration');
112
- const [style, setStyle] = useState('illustration');
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('1:1');
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
- }, mode === 'illustration' ? {
151
- style,
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
- style,
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
- photos ? /*#__PURE__*/ _jsxs(ToggleButtonGroup, {
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: style,
246
- onChange: (event)=>setStyle(event.target.value),
279
+ value: kind.id,
280
+ onChange: (event)=>chooseKind(event.target.value),
247
281
  disabled: busy,
248
- sx: {
249
- maxWidth: 240
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
- helperText: style === 'logo' ? 'A simple symbol, without lettering. Not a real brand’s logo.' : style === 'pattern' ? 'A seamless background that tiles.' : 'Drawn as an SVG, sharp at any size.',
252
- children: AI_SVG_STYLES.map((value)=>/*#__PURE__*/ _jsx(MenuItem, {
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: mode === 'photo' ? 'A sunlit bakery counter with fresh sourdough loaves, warm morning light' : 'A friendly delivery van with a parcel, simple and flat',
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
- mode === 'illustration' ? /*#__PURE__*/ _jsxs(Stack, {
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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"}
@@ -59,6 +59,7 @@ const isTerminal = (job)=>AI_JOB_TERMINAL_STATUSES.includes(job.status);
59
59
  function Coverage({ keywords }) {
60
60
  if (!keywords.length) return null;
61
61
  return /*#__PURE__*/ _jsx(Stack, {
62
+ useFlexGap: true,
62
63
  direction: "row",
63
64
  spacing: 0.5,
64
65
  sx: {
@@ -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":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,mBAAmB,EAAEC,oBAAoB,QAAQ,eAAc;AACxE,SAASC,cAAc,QAAQ,mCAAkC;AAEjE,SACEC,kBAAkB,EAClBC,oBAAoB,EACpBC,sBAAsB,QAEjB,4CAA2C;AAKlD,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,YAAY,EAAEC,OAAO,QAAQ,iCAAgC;AACtE,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,gBAAgB,EAChBC,IAAI,EACJC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAChE,SAASC,wBAAwB,QAA2B,4BAAwB;AACpF,SACEC,cAAc,EACdC,qBAAqB,QAEhB,qBAAiB;AACxB,SAASC,eAAe,QAAQ,oCAAgC;AAEhE;;;;;;;;;;;;;;;;CAgBC,GAED,0EAA0E,GAC1E,MAAMC,mBAAmB;AAEzB,uDAAuD,GACvD,MAAMC,yBAAyB;AAE/B,MAAMC,qBAAqB;AAI3B,MAAMC,aAAa,CAACC,MAAsBR,yBAAyBS,QAAQ,CAACD,IAAIE,MAAM;AAEtF,6GAA6G,GAC7G,OAAO,SAASC,qBACdC,MAAwB,EACxBC,MAAqC,EACrCC,QAAiB;IAEjB,MAAMC,MAA6B,CAAC;IACpC,KAAK,MAAMC,OAAOH,OAAQ;QACxB,MAAMI,QAAQL,MAAM,CAACI,IAAI;QACzB,IAAI,OAAOC,UAAU,YAAY,CAACA,MAAMC,IAAI,IAAI;QAChD,IAAIF,QAAQ,cAAc,CAACF,UAAU;QACrCC,GAAG,CAACC,IAAI,GAAGC;IACb;IACA,OAAOF;AACT;AAEA,SAASI,SAAS,EAAEC,QAAQ,EAA+C;IACzE,IAAI,CAACA,SAASC,MAAM,EAAE,OAAO;IAC7B,qBACE,KAAC5B;QAAM6B,WAAU;QAAMC,SAAS;QAAKC,IAAI;YAAEC,UAAU;YAAQC,QAAQ;QAAI;QAAGC,cAAW;kBACpFP,SAASQ,GAAG,CAAC,CAACC;YACb,MAAMC,UAAUD,MAAME,OAAO,IAAIF,MAAMG,aAAa;YACpD,qBACE,KAAC1C;gBAEC2C,MAAK;gBACLC,SAAQ;gBACRC,OAAOL,UAAU,YAAY;gBAC7BM,OAAON,UAAUD,MAAMQ,OAAO,GAAG,GAAGR,MAAMQ,OAAO,CAAC,WAAW,CAAC;eAJzDR,MAAMQ,OAAO;QAOxB;;AAGN;AAEA,SAASC,eAAeC,KAIvB;IACC,qBACE,MAAC9C;QAAM8B,SAAS;;0BACd,KAAC5B;gBAAWuC,SAAQ;0BAAaK,MAAMC,OAAO;;YAC7CD,MAAM1B,MAAM,CACV4B,MAAM,CAAC,CAACzB,MAAQuB,MAAM3B,MAAM,CAACI,IAAI,EACjCY,GAAG,CAAC,CAACZ,oBACJ,MAACvB;oBAAgB8B,SAAS;;sCACxB,MAAC9B;4BAAM6B,WAAU;4BAAMC,SAAS;4BAAGC,IAAI;gCAAEkB,YAAY;4BAAS;;8CAC5D,KAAC/C;oCAAWuC,SAAQ;oCAAUC,OAAM;oCAAiBX,IAAI;wCAAEmB,UAAU;oCAAE;8CACpE9D,kBAAkB,CAACmC,IAAI,CAACoB,KAAK;;8CAEhC,KAAC9C;oCACC2C,MAAK;oCACLC,SAAQ;oCACRC,OAAOpD,uBAAuBiC,KAAKuB,MAAM3B,MAAM,CAACI,IAAI,IAAI,UAAU;oCAClEoB,OAAOtD,qBAAqBkC,KAAKuB,MAAM3B,MAAM,CAACI,IAAI;;;;sCAGtD,KAACrB;4BAAWuC,SAAQ;4BAAQV,IAAI;gCAAEoB,WAAW;4BAAa;sCACvDL,MAAM3B,MAAM,CAACI,IAAI;;;mBAbVA;;;AAmBtB;AAEA,OAAO,SAAS6B,gBAAgBN,KAAgC;QAIhDA,MAAAA,qBAQIO;IAXlB,MAAM,EAAEC,MAAM,EAAED,OAAO,EAAEjC,MAAM,EAAED,MAAM,EAAEE,QAAQ,EAAEkC,aAAa,EAAE,GAAGT;IACrE,8DAA8D;IAC9D,MAAMU,YAAYhE,aAAasD,MAAMW,KAAK,GAAGC,YAAYJ;IACzD,MAAMG,SAAQX,QAAAA,eAAAA,MAAMW,KAAK,YAAXX,eAAeU,qBAAfV,OAA4BY;IAC1C,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGnE;IACvB,yEAAyE;IACzE,uCAAuC;IACvC,MAAMoE,UAAUxD,OAAOuD;IACvBC,QAAQC,OAAO,GAAGF;IAClB,MAAMG,eAAMH,wBAAAA,KAAMG,GAAG,oBAAI;IACzB,MAAMC,cAAcX,QAAQY,IAAI;IAChC,MAAMC,aAAYb,cAAAA,QAAQc,EAAE,YAAVd,cAAc;IAEhC,MAAM,CAACe,SAASC,WAAW,GAAG/D,SAAkB;IAChD,MAAM,CAACgE,QAAQC,UAAU,GAAGjE,SAAwB;IACpD,MAAM,CAACqB,UAAU6C,YAAY,GAAGlE,SAAS;IACzC,MAAM,CAACmE,MAAMC,QAAQ,GAAGpE,SAAS;IACjC,MAAM,CAACS,KAAK4D,OAAO,GAAGrE,SAA8B;IACpD,MAAM,CAACsE,WAAWC,aAAa,GAAGvE,SAA6D;IAC/F,MAAM,CAACwE,QAAQC,UAAU,GAAGzE,SAAwB;IACpD,MAAM0E,WAAW3E,OAA+B;IAEhDD,UAAU,IAAM;gBAAM4E;oBAAAA,oBAAAA,SAASlB,OAAO,qBAAhBkB,kBAAkBC,KAAK;WAAI,EAAE;IAEnD7E,UAAU;QACR,IAAI,CAACqD,SAAS,CAACH,UAAU,CAACS,KAAK;QAC/B,IAAImB,SAAS;QACbb,WAAW;QACXM,OAAO;QACPE,aAAa;QACb,KAAK,AAAC,CAAA;YACJ,IAAI;gBACF,MAAMM,WAAW,MAAM5F,gBACrBsE,QAAQC,OAAO,EACf,CAAC,mBAAmB,EAAEsB,mBAAmB3B,OAAO,OAAO,EAAE9C,kBAAkB;gBAE7E,IAAI,CAACuE,QAAQ;gBACb,gEAAgE;gBAChE,mEAAmE;gBACnE,IAAIC,SAASlE,MAAM,KAAK,OAAOkE,SAASlE,MAAM,KAAK,KAAK;oBACtDoD,WAAW;oBACX;gBACF;gBACA,MAAMgB,UAAU,MAAMF,SAASG,IAAI,GAAGC,KAAK,CAAC,IAAM;gBAClD,IAAI,CAACL,QAAQ;gBACb,IAAI,CAACC,SAASK,EAAE,EAAE;;oBAChB,MAAMC,SAASvG,qBAAqBiG,SAASlE,MAAM,EAAEoE;oBACrDd,UACEkB,SACIxG,oBAAoBwG,UACpBC,eAAOL,2BAAAA,QAASM,KAAK,mBAAI;gBAEjC,OAAO,IAAIzB,WAAW;;oBACpB,MAAM0B,OAAO,UAAEP,2BAAAA,QAASO,IAAI,oBAAI,EAAE,EAAqB5C,MAAM,CAC3D,CAACZ,QAAUA,MAAM6B,IAAI,KAAK,SAAS7B,MAAMkB,MAAM,KAAKA;oBAEtD,MAAMuC,SAASD,KAAKE,IAAI,CAAC,CAAC1D;wBACxB,MAAM2D,WAAWtF,sBAAsB2B;wBACvC,OAAO2D,CAAAA,4BAAAA,SAAU1C,OAAO,CAACY,IAAI,MAAKD,eAAe+B,SAAS1C,OAAO,CAACc,EAAE,KAAKD;oBAC3E;oBACA,IAAI2B,QAAQlB,OAAOkB;oBACnB,IAAI7B,gBAAgB,UAAU;4BAERxD;wBADpB,MAAMwF,QAAQJ,KAAKE,IAAI,CAAC,CAAC1D;gCAAUA;oCAAAA,iBAAAA,MAAM6D,OAAO,qBAAb7D,eAAe0C,MAAM,CAAC9D,QAAQ,CAACkD;;wBAClE,MAAMgC,MAAMF,SAAQxF,kBAAAA,eAAewF,MAAMG,OAAO,sBAA5B3F,gBAA+B4F,KAAK,CAAClC,UAAU,GAAGR;wBACtE,IAAIsC,SAASE,OAAOG,OAAOC,IAAI,CAACJ,IAAI/E,MAAM,EAAES,MAAM,EAAE;4BAClDiD,aAAa;gCAAE0B,OAAOP,MAAM7B,EAAE;gCAAEhD,QAAQ+E,IAAI/E,MAAM;4BAAC;wBACrD;oBACF;gBACF;gBACAkD,WAAW;YACb,EAAE,eAAM;gBACN,IAAI,CAACa,QAAQ;gBACbX,UAAU;gBACVF,WAAW;YACb;QACF,CAAA;QACA,OAAO;YACLa,SAAS;QACX;IACF,GAAG;QAACzB;QAAOH;QAAQS;QAAKC;QAAaE;KAAU;IAE/C,6DAA6D,GAC7D,MAAMsC,QAAQrG,YAAY,OAAOoG,OAAeE;YAC9CzB;SAAAA,oBAAAA,SAASlB,OAAO,qBAAhBkB,kBAAkBC,KAAK;QACvB,MAAMyB,aAAa,IAAIC;QACvB3B,SAASlB,OAAO,GAAG4C;QACnB,IAAIE,QAAQ;QACZ,MAAOA,SAAS,CAACF,WAAWG,MAAM,CAACC,OAAO,CAAE;YAC1CF,QAAQ;YACR,IAAI;gBACF,MAAMzB,WAAW,MAAM5F,gBACrBsE,QAAQC,OAAO,EACf,CAAC,aAAa,EAAEsB,mBAAmBmB,OAAO,cAAc,EAAEnB,mBAAmBqB,MAAM,EACnF;oBAAEI,QAAQH,WAAWG,MAAM;gBAAC;gBAE9B,IAAI,CAAC1B,SAASK,EAAE,IAAI,CAACL,SAAS4B,IAAI,EAAE;gBACpC,MAAMrG,gBAAgByE,SAAS4B,IAAI,EAAE,CAACC;oBACpC,IAAIA,KAAK,CAAC,OAAO,KAAK,SAASrC,OAAOqC,KAAK,CAAC,MAAM;yBAC7C,IAAIA,KAAK,CAAC,OAAO,KAAK,aAAaJ,QAAQ;gBAClD;YACF,EAAE,eAAM;gBACN;YACF;QACF;IACF,GAAG,EAAE;IAEL,MAAMK,QAAQ9G,YAAY;YAQmCkD,oBAGxCA,aAGGlC,eACMA;QAd5B,IAAI,CAACsC,OAAO;QACZiB,QAAQ;QACRH,UAAU;QACVQ,UAAU;QACV,MAAMmC,SAAS;YAAE9F,QAAQA,OAAO+F,IAAI,CAAC;YAAMxF,UAAUA,SAASF,IAAI;QAAG;QACrE,MAAM2F,SACJ/D,QAAQY,IAAI,KAAK,WACb;YAAEoD,QAAQ;YAAUC,UAAUjE,QAAQc,EAAE;YAAEoD,SAAS,GAAElE,qBAAAA,QAAQkE,SAAS,YAAjBlE,qBAAqB;WAAO6D,UACjF;YACEG,QAAQ;YACRG,SAAS,GAAEnE,cAAAA,QAAQc,EAAE,YAAVd,cAAc;YACzBoE,MAAMpE,QAAQoE,IAAI,CAAChG,IAAI;YACvBiG,MAAMrE,QAAQsE,WAAW,CAACC,KAAK,CAAC,GAAGhH;YACnCiH,YAAY,GAAE1G,gBAAAA,OAAO2G,KAAK,YAAZ3G,gBAAgB;YAC9B4G,kBAAkB,GAAE5G,sBAAAA,OAAOwG,WAAW,YAAlBxG,sBAAsB;WACvC+F;QAEX,IAAI;YACF,MAAM/B,WAAW,MAAM5F,gBAAgBsE,QAAQC,OAAO,EAAE,gBAAgB;gBACtEkE,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9ClB,MAAMmB,KAAKC,SAAS,CAAC;oBACnB1E;oBACAH;oBACAW,MAAM;oBACNmE,OAAO,CAAC,6BAA6B,EAAE/E,QAAQoE,IAAI,CAAChG,IAAI,MAAM,aAAa;oBAC3E2F;gBACF;YACF;YACA,MAAM/B,UAAU,MAAMF,SAASG,IAAI,GAAGC,KAAK,CAAC,IAAM;YAClD,IAAI,CAACJ,SAASK,EAAE,EAAE;;gBAChB,MAAMC,SAASvG,qBAAqBiG,SAASlE,MAAM,EAAEoE;gBACrDd,UACEkB,SACIxG,oBAAoBwG,UACpBC,eAAOL,2BAAAA,QAASM,KAAK,mBAAI;gBAE/B;YACF;YACA,MAAM0C,OAAOhD,2BAAAA,QAAStE,GAAG;YACzB,IAAI,CAACsH,MAAM;YACX1D,OAAO0D;YACP,IAAI,CAACvH,WAAWuH,OAAO,MAAM7B,MAAM6B,KAAKlE,EAAE,EAAEV;QAC9C,EAAE,eAAM;YACNc,UAAU;QACZ,SAAU;YACRG,QAAQ;QACV;IACF,GAAG;QAACjB;QAAOH;QAAQD;QAASjC;QAAQO;QAAUR,OAAO2G,KAAK;QAAE3G,OAAOwG,WAAW;QAAEnB;KAAM;IAEtF,IAAIpC,YAAY,SAAS,OAAO;IAEhC,MAAM2B,WAAWtF,sBAAsBM;IACvC,MAAMuH,UAAUvC,WAAW7E,qBAAqB6E,SAAS5E,MAAM,EAAEC,QAAQC,YAAY,CAAC;IACtF,MAAMkH,UAAU3D,YAAY1D,qBAAqB0D,UAAUzD,MAAM,EAAEC,QAAQC,YAAY,CAAC;IACxF,MAAMmH,UAAUC,QAAQ1H,OAAO,CAACD,WAAWC;IAC3C,MAAM2H,WAAWrF,QAAQY,IAAI,KAAK,YAAY,iBAAiB;IAC/D,MAAM0E,UAAUtF,QAAQY,IAAI,KAAK,aAAa,CAACZ,QAAQoE,IAAI,CAAChG,IAAI;IAChE,MAAMmH,OAAOzJ,eAAe,SAAS;QACnC0J,SAAS;IACX;IAEA,MAAMC,QAAQ,CAACC,OAA8BxH;QAC3CgC,cAAcwF,OAAOxH;QACrBwD,UAAUxD;IACZ;IAEA,qBACE,KAAC5B;QAAIoC,IAAI;YAAEiH,QAAQ;YAAGC,aAAa;YAAWC,cAAc;YAAGC,GAAG;QAAI;QAAGjH,cAAW;kBAClF,cAAA,MAAClC;YAAM8B,SAAS;;8BACd,MAAC9B;oBAAM6B,WAAU;oBAAMC,SAAS;oBAAGC,IAAI;wBAAEkB,YAAY;oBAAW;;sCAC9D,KAAC/C;4BAAWuC,SAAQ;4BAAYV,IAAI;gCAAEmB,UAAU;4BAAE;sCAC/C;;sCAEH,KAACnD;4BAAKqJ,MAAMR,KAAKQ,IAAI;4BAAE/B,QAAO;4BAASgC,KAAI;4BAAW5G,SAAQ;4BAAUqF,OAAOc,KAAKC,OAAO;sCACxF;;;;8BAGL,KAAC5I;oBACCuC,MAAK;oBACLG,OAAM;oBACN2G,aAAY;oBACZC,YAAW;oBACX/H,OAAOG;oBACP6H,UAAU,CAACxC,QAAUxC,YAAYwC,MAAMK,MAAM,CAAC7F,KAAK,CAACoG,KAAK,CAAC,GAAG/G;oBAC7D4I,UAAUhF,QAAQ+D;;8BAEpB,MAACxI;oBAAM6B,WAAU;oBAAMC,SAAS;oBAAGC,IAAI;wBAAEkB,YAAY;oBAAS;;sCAC5D,KAACrD;4BAAO4C,MAAK;4BAAQC,SAAQ;4BAAWgH,UAAUhF,QAAQ+D,WAAWG,WAAW,CAAClF;4BAAOiG,SAAS,IAAM,KAAKzC;sCACzGlB,WAAW,gBAAgB;;wBAE7ByC,wBAAU,KAAC1I;4BAAiB0C,MAAM;4BAAIN,cAAW;6BAA2B;wBAC5EyG,wBACC,KAACzI;4BAAWuC,SAAQ;4BAAUC,OAAM;sCACjC;6BAED;;;gBAEL4B,uBAAS,KAAC5E;oBAAMiK,UAAS;8BAAWrF;qBAAkB;gBACtDvD,CAAAA,uBAAAA,IAAK4E,KAAK,KAAI,CAACI,yBACd,KAACrG;oBAAMiK,UAAU5I,IAAIE,MAAM,KAAK,WAAW,UAAU;8BAAYF,IAAI4E,KAAK;qBACxE;gBAEH,CAACI,YAAYnB,aAAayB,OAAOC,IAAI,CAACiC,SAAS3G,MAAM,iBACpD,MAAC5B;oBAAM8B,SAAS;;sCACd,KAACe;4BAAeE,SAAQ;4BAA6B5B,QAAQoH;4BAASnH,QAAQA;;sCAC9E,KAACxB;4BACC4C,MAAK;4BACLC,SAAQ;4BACRV,IAAI;gCAAE6H,WAAW;4BAAa;4BAC9BH,UAAU3E,WAAW,CAAC,MAAM,EAAEF,UAAU2B,KAAK,EAAE;4BAC/CmD,SAAS,IAAMZ,MAAMP,SAAS,CAAC,MAAM,EAAE3D,UAAU2B,KAAK,EAAE;sCAEvD;;;qBAGH;gBAEHR,YAAYhF,oBACX,MAACf;oBAAM8B,SAAS;;sCACd,KAACe;4BAAeE,SAAQ;4BAAmB5B,QAAQmH;4BAASlH,QAAQA;;sCACpE,KAACM;4BAASC,UAAUoE,SAASpE,QAAQ;;wBACpCoE,SAAS8D,KAAK,CAAC1H,GAAG,CAAC,CAAC2H,qBACnB,KAAC5J;gCAAsBuC,SAAQ;gCAAUC,OAAM;0CAC5CoH;+BADcA;wBAIlB/I,IAAIgJ,YAAY,GAAG,kBAClB,KAAC7J;4BAAWuC,SAAQ;4BAAUC,OAAM;sCACjC,CAAC,KAAK,EAAE3B,IAAIgJ,YAAY,CAAC,CAAC,EAAEhJ,IAAIgJ,YAAY,KAAK,IAAI,WAAW,UAAU,CAAC,CAAC;6BAE7E;sCACJ,KAACnK;4BACC4C,MAAK;4BACLC,SAAQ;4BACRV,IAAI;gCAAE6H,WAAW;4BAAa;4BAC9BH,UAAU,CAACpD,OAAOC,IAAI,CAACgC,SAAS1G,MAAM,IAAIkD,WAAW/D,IAAIoD,EAAE;4BAC3DuF,SAAS,IAAMZ,MAAMR,SAASvH,IAAIoD,EAAE;sCAEnC;;;qBAGH;gBAEHW,uBACC,KAACpF;oBAAMiK,UAAS;8BACb,CAAC,0DAA0D,EAAEjB,SAAS,CAAC,CAAC;qBAEzE;;;;AAIZ;AACAtF,gBAAgB4G,WAAW,GAAG;AAE9B,eAAe5G,gBAAe"}
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;