@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-ai",
3
- "version": "1.0.0-beta.231",
3
+ "version": "1.0.0-beta.232",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,22 +25,22 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.231",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.231",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.231",
31
- "@aglyn/shared-data-types": "1.0.0-beta.231",
32
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.231",
33
- "@aglyn/shared-ui-jsx": "1.0.0-beta.231",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.231",
35
- "@aglyn/shared-ui-theme": "1.0.0-beta.231",
36
- "@aglyn/shared-util-email": "1.0.0-beta.231",
37
- "@aglyn/shared-util-errors": "1.0.0-beta.231",
38
- "@aglyn/shared-util-http": "1.0.0-beta.231",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.231",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.231",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.231",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.231",
43
- "@aglyn/tenant-runtime": "1.0.0-beta.231",
28
+ "@aglyn/aglyn": "1.0.0-beta.232",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.232",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.232",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.232",
32
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.232",
33
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.232",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.232",
35
+ "@aglyn/shared-ui-theme": "1.0.0-beta.232",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.232",
37
+ "@aglyn/shared-util-errors": "1.0.0-beta.232",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.232",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.232",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.232",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.232",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.232",
43
+ "@aglyn/tenant-runtime": "1.0.0-beta.232",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -110,7 +110,9 @@ export const AI_JOB_NEEDS_INPUT_REASON_LABELS = {
110
110
  // A logic proposal (AGL-3603) writes nothing either.
111
111
  logic: 'content',
112
112
  // So does an org automation's (AGL-3603): the editor's Save is the write.
113
- orgAutomation: 'content'
113
+ orgAutomation: 'content',
114
+ // A blog post a guided start wrote (AGL-3676) is the site's content.
115
+ entry: 'content'
114
116
  };
115
117
  export function aiOutputTargetType(resource) {
116
118
  return AI_OUTPUT_TARGET_TYPES[resource];
@@ -186,11 +188,13 @@ export function aiOutputTargetType(resource) {
186
188
  label: AI_ACTIVITY_FILTER_LABEL,
187
189
  staffAuditPrefixes: AI_STAFF_AUDIT_ACTION_PREFIXES.filter((prefix)=>prefix !== 'ai.'),
188
190
  // The staff cards opening on an org (AGL-2930) and on one account
189
- // (AGL-2928). Neither writes these any more; the rows already in the
190
- // log still classify as reads rather than changes.
191
+ // (AGL-2928), which no longer write these, so the rows already in the
192
+ // log still classify as reads; and a staff member reading what an org
193
+ // asked Aglyn AI (AGL-3675), which writes one per page.
191
194
  staffAuditAccessActions: [
192
195
  'org.ai-viewed',
193
- 'user.ai-usage-viewed'
196
+ 'user.ai-usage-viewed',
197
+ 'org.ai-conversations-viewed'
194
198
  ]
195
199
  },
196
200
  actions: AI_ACTIVITY_ACTION_LIST.map((key)=>({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/activity/ai-activity-actions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The AI actions an activity log can carry (AGL-2929).\n *\n * Every other action in `orgs/{orgId}/activity` and `hosts/{hostId}/activity`\n * is a prose sentence written at the call site — \"Saved the screen\". The AI\n * rows are CODES instead, because they are read by more than a person: the\n * org feed's \"AI\" chip, the staff audit facet and the actor table all need\n * to recognize an AI row without parsing a sentence, and a sentence that\n * three writers phrase three ways is three rows that look unrelated.\n *\n * One list, so the writers in `ai-activity.ts`, the feed presenter and the\n * staff facet cannot disagree about what counts as AI. A code missing from\n * `AI_ACTIVITY_ACTION_LABELS` fails the type, not the reader.\n */\n\nimport type { AiJobOutputResource } from '../model/ai-jobs.types'\nimport {\n pluginStaffAuditActionGroup,\n pluginStaffAuditActionGroupLabel,\n registerPluginActivityActions,\n type PluginActivityScope,\n} from '@aglyn/aglyn/plugin-manager/plugin-activity-actions'\n\n/** The action code each AI writer stores. */\nexport const AI_ACTIVITY_ACTIONS = {\n jobCreated: 'ai.job.created',\n jobOutput: 'ai.job.output',\n jobCanceled: 'ai.job.canceled',\n jobNeedsInput: 'ai.job.needs_input',\n editApplied: 'ai.edit.applied',\n seoApplied: 'ai.seo.applied',\n assistSection: 'ai.assist.section',\n overageHardCap: 'ai.overage.hardCap',\n overageCap: 'ai.overage.cap',\n allotmentChanged: 'ai.allotment.changed',\n permissionChanged: 'ai.permission.changed',\n addonPurchased: 'ai.addon.purchased',\n addonRemoved: 'ai.addon.removed',\n} as const\n\nexport type AiActivityAction =\n (typeof AI_ACTIVITY_ACTIONS)[keyof typeof AI_ACTIVITY_ACTIONS]\n\n/** What a person reads for each code, in the feed and the actor table. */\nexport const AI_ACTIVITY_ACTION_LABELS: Record<AiActivityAction, string> = {\n 'ai.job.created': 'Started an AI generation',\n 'ai.job.output': 'AI generated',\n 'ai.job.canceled': 'Canceled an AI generation',\n 'ai.job.needs_input': 'AI generation paused for input',\n 'ai.edit.applied': 'Applied AI edits',\n 'ai.seo.applied': 'Applied AI SEO fixes as drafts',\n 'ai.assist.section': 'AI generated a section',\n 'ai.overage.hardCap': 'AI stop-at-band switch',\n 'ai.overage.cap': 'AI overage ceiling',\n 'ai.allotment.changed': 'AI allotment changed',\n 'ai.permission.changed': 'AI permission changed',\n 'ai.addon.purchased': 'Added the AI add-on',\n 'ai.addon.removed': 'Removed the AI add-on',\n}\n\n/** Every code, in catalog order — what a filter sends as `isAnyOf`. */\nexport const AI_ACTIVITY_ACTION_LIST: readonly AiActivityAction[] =\n Object.values(AI_ACTIVITY_ACTIONS)\n\n/** The feed's filter chip and the staff facet's group, by one name. */\nexport const AI_ACTIVITY_FILTER_LABEL = 'AI'\n\n/**\n * Why a generation stopped and asked for a person (AGL-2904): the plan's\n * band ran out, the org's own ceiling refused, the monthly message cap or\n * the job's own token budget did — or, on the Free taste (AGL-2925), one of\n * its own precautions: the account's credits, its daily request cap, a\n * pause after declined briefs, or the platform's day of free spend. Those\n * are the meter's `AssistRefusedBy` less its `null`, so a ceiling the meter\n * can name is one the feed can label. The last three are the job's own: its\n * plan is ready for review, or an answer broke a building rule on its re-ask\n * (AGL-2935), or the site is at an allowance its plan sets for what the job\n * writes (AGL-2909).\n */\nexport type AiJobNeedsInputReason =\n | 'band'\n | 'cap'\n | 'messages'\n | 'budget'\n | 'allotment'\n | 'account'\n | 'requests'\n | 'refusals'\n | 'platform'\n | 'plan'\n | 'doctrine'\n | 'limit'\n\nexport const AI_JOB_NEEDS_INPUT_REASON_LABELS: Record<\n AiJobNeedsInputReason,\n string\n> = {\n plan: 'its plan is ready for review',\n doctrine: 'its answer broke a building rule twice',\n limit: 'the site reached an allowance of its plan',\n band: 'the included AI band is used up',\n cap: 'the overage ceiling was reached',\n messages: 'the monthly message cap was reached',\n budget: 'the job budget was reached',\n allotment: 'the creator’s or the site’s AI allotment is used up',\n account: 'the account’s free AI credits are used up',\n requests: 'the daily free request cap was reached',\n refusals: 'free generation is paused for the day after declined briefs',\n platform: 'free AI generation is paused for the day',\n}\n\n/**\n * The feed target types a generation job's output can be filed under: the\n * kinds both activity logs know, so the org row and its host copy share a\n * shape.\n */\nexport type AiOutputTargetType =\n | 'screen'\n | 'layout'\n | 'component'\n | 'template'\n | 'workflow'\n | 'content'\n | 'theme'\n\n/**\n * Where the feed files each resource kind a job can write (AGL-2904).\n *\n * A job names its output by the resource it wrote, and the activity logs\n * know a narrower set of targets. The map is total, so a resource kind\n * added to the job document must say where the feed shows it before the\n * machine can log it. `reusableComponent` is the feed's `component`; a\n * `text` output is copy, which the feed files as `content`; a `theme`\n * proposal is filed under the site's theme, which the host feed links to the\n * Theme section (AGL-2938); the kinds whose runners have not shipped are\n * content of the site too, and are filed there until a target of their own\n * exists.\n */\nconst AI_OUTPUT_TARGET_TYPES: Record<AiJobOutputResource, AiOutputTargetType> = {\n screen: 'screen',\n reusableComponent: 'component',\n layout: 'layout',\n template: 'template',\n form: 'content',\n emailScreen: 'content',\n campaign: 'content',\n product: 'content',\n experiment: 'content',\n workflow: 'workflow',\n // An SEO proposal (AGL-2910) is about the site's content, and is filed there.\n seo: 'content',\n text: 'content',\n theme: 'theme',\n // An insight answer (AGL-2915) is about the site's figures; the feed row\n // names it and never what it says.\n insight: 'content',\n // A CRM proposal (AGL-2917) writes nothing; the feed files it with content.\n crm: 'content',\n // Another plugin's draft a build asked its writer for (AGL-3616).\n draft: 'content',\n // A logic proposal (AGL-3603) writes nothing either.\n logic: 'content',\n // So does an org automation's (AGL-3603): the editor's Save is the write.\n orgAutomation: 'content',\n}\n\nexport function aiOutputTargetType(resource: AiJobOutputResource): AiOutputTargetType {\n return AI_OUTPUT_TARGET_TYPES[resource]\n}\n\n/** Whether a stored action is one of the AI codes above. */\nexport function isAiActivityAction(action: unknown): action is AiActivityAction {\n return (\n typeof action === 'string' &&\n (AI_ACTIVITY_ACTION_LIST as readonly string[]).includes(action)\n )\n}\n\n/** The readable label for an AI code; `undefined` for any other action. */\nexport function aiActivityActionLabel(action: unknown): string | undefined {\n return isAiActivityAction(action)\n ? AI_ACTIVITY_ACTION_LABELS[action]\n : undefined\n}\n\n/**\n * Staff audit rows that are ABOUT AI without carrying an `ai.` code: the\n * customer's overage controls already write `billing.assistOverage.*`, and\n * the platform's free-spend auto-pause writes `platform.aiFreeSpend.*`\n * (AGL-2925). The audit page's facet folds them under the same group as the\n * codes, because \"what did we do about AI this week\" is one question.\n */\nexport const AI_STAFF_AUDIT_ACTION_PREFIXES: readonly string[] = [\n 'ai.',\n 'billing.assistOverage.',\n 'platform.aiFreeSpend.',\n]\n\n/** The facet group an `adminAudit` action belongs to. */\nexport const STAFF_AUDIT_AI_GROUP = 'ai'\n\n/**\n * Which log each code is written to. The job and edit codes land in both\n * the org feed and the host's copy; the billing and permission codes are\n * org-level facts and land in the org feed alone.\n */\nconst AI_ACTIVITY_ACTION_SCOPES: Record<\n AiActivityAction,\n PluginActivityScope | readonly PluginActivityScope[]\n> = {\n 'ai.job.created': ['org', 'host'],\n 'ai.job.output': ['org', 'host'],\n 'ai.job.canceled': ['org', 'host'],\n 'ai.job.needs_input': ['org', 'host'],\n 'ai.edit.applied': ['org', 'host'],\n 'ai.seo.applied': ['org', 'host'],\n 'ai.assist.section': ['org', 'host'],\n 'ai.overage.hardCap': 'org',\n 'ai.overage.cap': 'org',\n 'ai.allotment.changed': 'org',\n 'ai.permission.changed': 'org',\n 'ai.addon.purchased': 'org',\n 'ai.addon.removed': 'org',\n}\n\n/**\n * The catalog, declared through the generic activity-action seam\n * (AGL-2940) so the feed's chip, the actor table's filter and the staff\n * facet read it from the registry beside every other plugin's codes.\n * `registerAiDeclarations` calls it, in both apps and in the browser.\n */\nexport function registerAiActivityActions(): void {\n registerPluginActivityActions({\n pluginId: 'ai',\n group: {\n id: STAFF_AUDIT_AI_GROUP,\n label: AI_ACTIVITY_FILTER_LABEL,\n staffAuditPrefixes: AI_STAFF_AUDIT_ACTION_PREFIXES.filter(\n (prefix) => prefix !== 'ai.',\n ),\n // The staff cards opening on an org (AGL-2930) and on one account\n // (AGL-2928). Neither writes these any more; the rows already in the\n // log still classify as reads rather than changes.\n staffAuditAccessActions: ['org.ai-viewed', 'user.ai-usage-viewed'],\n },\n actions: AI_ACTIVITY_ACTION_LIST.map((key) => ({\n key,\n label: AI_ACTIVITY_ACTION_LABELS[key],\n scope: AI_ACTIVITY_ACTION_SCOPES[key],\n })),\n // A generation job (AGL-2904): the org feed names the job, and each of\n // its outputs names the resource it produced. Filed as `aiJob` since\n // before plugin target types were namespaced, so the noun is declared\n // here rather than read off the type (AGL-3080).\n targetTypes: { aiJob: 'AI generation' },\n })\n}\n\n/**\n * The group the staff audit facet files an action under: `ai` for every\n * AI row, otherwise the action's leading namespace (`billing`, `org`,\n * `plugins`). Roughly seventy distinct actions write to `adminAudit`, so\n * the facet offers namespaces rather than a seventy-entry menu. Answered\n * by the registry, so every plugin's group is filed the same way.\n */\nexport function staffAuditActionGroup(action: unknown): string {\n return pluginStaffAuditActionGroup(action)\n}\n\n/** How a facet group reads in the menu: `ai` → `AI`, `billing` → `billing`. */\nexport function staffAuditActionGroupLabel(group: string): string {\n return pluginStaffAuditActionGroupLabel(group)\n}\n"],"names":["pluginStaffAuditActionGroup","pluginStaffAuditActionGroupLabel","registerPluginActivityActions","AI_ACTIVITY_ACTIONS","jobCreated","jobOutput","jobCanceled","jobNeedsInput","editApplied","seoApplied","assistSection","overageHardCap","overageCap","allotmentChanged","permissionChanged","addonPurchased","addonRemoved","AI_ACTIVITY_ACTION_LABELS","AI_ACTIVITY_ACTION_LIST","Object","values","AI_ACTIVITY_FILTER_LABEL","AI_JOB_NEEDS_INPUT_REASON_LABELS","plan","doctrine","limit","band","cap","messages","budget","allotment","account","requests","refusals","platform","AI_OUTPUT_TARGET_TYPES","screen","reusableComponent","layout","template","form","emailScreen","campaign","product","experiment","workflow","seo","text","theme","insight","crm","draft","logic","orgAutomation","aiOutputTargetType","resource","isAiActivityAction","action","includes","aiActivityActionLabel","undefined","AI_STAFF_AUDIT_ACTION_PREFIXES","STAFF_AUDIT_AI_GROUP","AI_ACTIVITY_ACTION_SCOPES","registerAiActivityActions","pluginId","group","id","label","staffAuditPrefixes","filter","prefix","staffAuditAccessActions","actions","map","key","scope","targetTypes","aiJob","staffAuditActionGroup","staffAuditActionGroupLabel"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/activity/ai-activity-actions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The AI actions an activity log can carry (AGL-2929).\n *\n * Every other action in `orgs/{orgId}/activity` and `hosts/{hostId}/activity`\n * is a prose sentence written at the call site — \"Saved the screen\". The AI\n * rows are CODES instead, because they are read by more than a person: the\n * org feed's \"AI\" chip, the staff audit facet and the actor table all need\n * to recognize an AI row without parsing a sentence, and a sentence that\n * three writers phrase three ways is three rows that look unrelated.\n *\n * One list, so the writers in `ai-activity.ts`, the feed presenter and the\n * staff facet cannot disagree about what counts as AI. A code missing from\n * `AI_ACTIVITY_ACTION_LABELS` fails the type, not the reader.\n */\n\nimport type { AiJobOutputResource } from '../model/ai-jobs.types'\nimport {\n pluginStaffAuditActionGroup,\n pluginStaffAuditActionGroupLabel,\n registerPluginActivityActions,\n type PluginActivityScope,\n} from '@aglyn/aglyn/plugin-manager/plugin-activity-actions'\n\n/** The action code each AI writer stores. */\nexport const AI_ACTIVITY_ACTIONS = {\n jobCreated: 'ai.job.created',\n jobOutput: 'ai.job.output',\n jobCanceled: 'ai.job.canceled',\n jobNeedsInput: 'ai.job.needs_input',\n editApplied: 'ai.edit.applied',\n seoApplied: 'ai.seo.applied',\n assistSection: 'ai.assist.section',\n overageHardCap: 'ai.overage.hardCap',\n overageCap: 'ai.overage.cap',\n allotmentChanged: 'ai.allotment.changed',\n permissionChanged: 'ai.permission.changed',\n addonPurchased: 'ai.addon.purchased',\n addonRemoved: 'ai.addon.removed',\n} as const\n\nexport type AiActivityAction =\n (typeof AI_ACTIVITY_ACTIONS)[keyof typeof AI_ACTIVITY_ACTIONS]\n\n/** What a person reads for each code, in the feed and the actor table. */\nexport const AI_ACTIVITY_ACTION_LABELS: Record<AiActivityAction, string> = {\n 'ai.job.created': 'Started an AI generation',\n 'ai.job.output': 'AI generated',\n 'ai.job.canceled': 'Canceled an AI generation',\n 'ai.job.needs_input': 'AI generation paused for input',\n 'ai.edit.applied': 'Applied AI edits',\n 'ai.seo.applied': 'Applied AI SEO fixes as drafts',\n 'ai.assist.section': 'AI generated a section',\n 'ai.overage.hardCap': 'AI stop-at-band switch',\n 'ai.overage.cap': 'AI overage ceiling',\n 'ai.allotment.changed': 'AI allotment changed',\n 'ai.permission.changed': 'AI permission changed',\n 'ai.addon.purchased': 'Added the AI add-on',\n 'ai.addon.removed': 'Removed the AI add-on',\n}\n\n/** Every code, in catalog order — what a filter sends as `isAnyOf`. */\nexport const AI_ACTIVITY_ACTION_LIST: readonly AiActivityAction[] =\n Object.values(AI_ACTIVITY_ACTIONS)\n\n/** The feed's filter chip and the staff facet's group, by one name. */\nexport const AI_ACTIVITY_FILTER_LABEL = 'AI'\n\n/**\n * Why a generation stopped and asked for a person (AGL-2904): the plan's\n * band ran out, the org's own ceiling refused, the monthly message cap or\n * the job's own token budget did — or, on the Free taste (AGL-2925), one of\n * its own precautions: the account's credits, its daily request cap, a\n * pause after declined briefs, or the platform's day of free spend. Those\n * are the meter's `AssistRefusedBy` less its `null`, so a ceiling the meter\n * can name is one the feed can label. The last three are the job's own: its\n * plan is ready for review, or an answer broke a building rule on its re-ask\n * (AGL-2935), or the site is at an allowance its plan sets for what the job\n * writes (AGL-2909).\n */\nexport type AiJobNeedsInputReason =\n | 'band'\n | 'cap'\n | 'messages'\n | 'budget'\n | 'allotment'\n | 'account'\n | 'requests'\n | 'refusals'\n | 'platform'\n | 'plan'\n | 'doctrine'\n | 'limit'\n\nexport const AI_JOB_NEEDS_INPUT_REASON_LABELS: Record<\n AiJobNeedsInputReason,\n string\n> = {\n plan: 'its plan is ready for review',\n doctrine: 'its answer broke a building rule twice',\n limit: 'the site reached an allowance of its plan',\n band: 'the included AI band is used up',\n cap: 'the overage ceiling was reached',\n messages: 'the monthly message cap was reached',\n budget: 'the job budget was reached',\n allotment: 'the creator’s or the site’s AI allotment is used up',\n account: 'the account’s free AI credits are used up',\n requests: 'the daily free request cap was reached',\n refusals: 'free generation is paused for the day after declined briefs',\n platform: 'free AI generation is paused for the day',\n}\n\n/**\n * The feed target types a generation job's output can be filed under: the\n * kinds both activity logs know, so the org row and its host copy share a\n * shape.\n */\nexport type AiOutputTargetType =\n | 'screen'\n | 'layout'\n | 'component'\n | 'template'\n | 'workflow'\n | 'content'\n | 'theme'\n\n/**\n * Where the feed files each resource kind a job can write (AGL-2904).\n *\n * A job names its output by the resource it wrote, and the activity logs\n * know a narrower set of targets. The map is total, so a resource kind\n * added to the job document must say where the feed shows it before the\n * machine can log it. `reusableComponent` is the feed's `component`; a\n * `text` output is copy, which the feed files as `content`; a `theme`\n * proposal is filed under the site's theme, which the host feed links to the\n * Theme section (AGL-2938); the kinds whose runners have not shipped are\n * content of the site too, and are filed there until a target of their own\n * exists.\n */\nconst AI_OUTPUT_TARGET_TYPES: Record<AiJobOutputResource, AiOutputTargetType> = {\n screen: 'screen',\n reusableComponent: 'component',\n layout: 'layout',\n template: 'template',\n form: 'content',\n emailScreen: 'content',\n campaign: 'content',\n product: 'content',\n experiment: 'content',\n workflow: 'workflow',\n // An SEO proposal (AGL-2910) is about the site's content, and is filed there.\n seo: 'content',\n text: 'content',\n theme: 'theme',\n // An insight answer (AGL-2915) is about the site's figures; the feed row\n // names it and never what it says.\n insight: 'content',\n // A CRM proposal (AGL-2917) writes nothing; the feed files it with content.\n crm: 'content',\n // Another plugin's draft a build asked its writer for (AGL-3616).\n draft: 'content',\n // A logic proposal (AGL-3603) writes nothing either.\n logic: 'content',\n // So does an org automation's (AGL-3603): the editor's Save is the write.\n orgAutomation: 'content',\n // A blog post a guided start wrote (AGL-3676) is the site's content.\n entry: 'content',\n}\n\nexport function aiOutputTargetType(resource: AiJobOutputResource): AiOutputTargetType {\n return AI_OUTPUT_TARGET_TYPES[resource]\n}\n\n/** Whether a stored action is one of the AI codes above. */\nexport function isAiActivityAction(action: unknown): action is AiActivityAction {\n return (\n typeof action === 'string' &&\n (AI_ACTIVITY_ACTION_LIST as readonly string[]).includes(action)\n )\n}\n\n/** The readable label for an AI code; `undefined` for any other action. */\nexport function aiActivityActionLabel(action: unknown): string | undefined {\n return isAiActivityAction(action)\n ? AI_ACTIVITY_ACTION_LABELS[action]\n : undefined\n}\n\n/**\n * Staff audit rows that are ABOUT AI without carrying an `ai.` code: the\n * customer's overage controls already write `billing.assistOverage.*`, and\n * the platform's free-spend auto-pause writes `platform.aiFreeSpend.*`\n * (AGL-2925). The audit page's facet folds them under the same group as the\n * codes, because \"what did we do about AI this week\" is one question.\n */\nexport const AI_STAFF_AUDIT_ACTION_PREFIXES: readonly string[] = [\n 'ai.',\n 'billing.assistOverage.',\n 'platform.aiFreeSpend.',\n]\n\n/** The facet group an `adminAudit` action belongs to. */\nexport const STAFF_AUDIT_AI_GROUP = 'ai'\n\n/**\n * Which log each code is written to. The job and edit codes land in both\n * the org feed and the host's copy; the billing and permission codes are\n * org-level facts and land in the org feed alone.\n */\nconst AI_ACTIVITY_ACTION_SCOPES: Record<\n AiActivityAction,\n PluginActivityScope | readonly PluginActivityScope[]\n> = {\n 'ai.job.created': ['org', 'host'],\n 'ai.job.output': ['org', 'host'],\n 'ai.job.canceled': ['org', 'host'],\n 'ai.job.needs_input': ['org', 'host'],\n 'ai.edit.applied': ['org', 'host'],\n 'ai.seo.applied': ['org', 'host'],\n 'ai.assist.section': ['org', 'host'],\n 'ai.overage.hardCap': 'org',\n 'ai.overage.cap': 'org',\n 'ai.allotment.changed': 'org',\n 'ai.permission.changed': 'org',\n 'ai.addon.purchased': 'org',\n 'ai.addon.removed': 'org',\n}\n\n/**\n * The catalog, declared through the generic activity-action seam\n * (AGL-2940) so the feed's chip, the actor table's filter and the staff\n * facet read it from the registry beside every other plugin's codes.\n * `registerAiDeclarations` calls it, in both apps and in the browser.\n */\nexport function registerAiActivityActions(): void {\n registerPluginActivityActions({\n pluginId: 'ai',\n group: {\n id: STAFF_AUDIT_AI_GROUP,\n label: AI_ACTIVITY_FILTER_LABEL,\n staffAuditPrefixes: AI_STAFF_AUDIT_ACTION_PREFIXES.filter(\n (prefix) => prefix !== 'ai.',\n ),\n // The staff cards opening on an org (AGL-2930) and on one account\n // (AGL-2928), which no longer write these, so the rows already in the\n // log still classify as reads; and a staff member reading what an org\n // asked Aglyn AI (AGL-3675), which writes one per page.\n staffAuditAccessActions: ['org.ai-viewed', 'user.ai-usage-viewed', 'org.ai-conversations-viewed'],\n },\n actions: AI_ACTIVITY_ACTION_LIST.map((key) => ({\n key,\n label: AI_ACTIVITY_ACTION_LABELS[key],\n scope: AI_ACTIVITY_ACTION_SCOPES[key],\n })),\n // A generation job (AGL-2904): the org feed names the job, and each of\n // its outputs names the resource it produced. Filed as `aiJob` since\n // before plugin target types were namespaced, so the noun is declared\n // here rather than read off the type (AGL-3080).\n targetTypes: { aiJob: 'AI generation' },\n })\n}\n\n/**\n * The group the staff audit facet files an action under: `ai` for every\n * AI row, otherwise the action's leading namespace (`billing`, `org`,\n * `plugins`). Roughly seventy distinct actions write to `adminAudit`, so\n * the facet offers namespaces rather than a seventy-entry menu. Answered\n * by the registry, so every plugin's group is filed the same way.\n */\nexport function staffAuditActionGroup(action: unknown): string {\n return pluginStaffAuditActionGroup(action)\n}\n\n/** How a facet group reads in the menu: `ai` → `AI`, `billing` → `billing`. */\nexport function staffAuditActionGroupLabel(group: string): string {\n return pluginStaffAuditActionGroupLabel(group)\n}\n"],"names":["pluginStaffAuditActionGroup","pluginStaffAuditActionGroupLabel","registerPluginActivityActions","AI_ACTIVITY_ACTIONS","jobCreated","jobOutput","jobCanceled","jobNeedsInput","editApplied","seoApplied","assistSection","overageHardCap","overageCap","allotmentChanged","permissionChanged","addonPurchased","addonRemoved","AI_ACTIVITY_ACTION_LABELS","AI_ACTIVITY_ACTION_LIST","Object","values","AI_ACTIVITY_FILTER_LABEL","AI_JOB_NEEDS_INPUT_REASON_LABELS","plan","doctrine","limit","band","cap","messages","budget","allotment","account","requests","refusals","platform","AI_OUTPUT_TARGET_TYPES","screen","reusableComponent","layout","template","form","emailScreen","campaign","product","experiment","workflow","seo","text","theme","insight","crm","draft","logic","orgAutomation","entry","aiOutputTargetType","resource","isAiActivityAction","action","includes","aiActivityActionLabel","undefined","AI_STAFF_AUDIT_ACTION_PREFIXES","STAFF_AUDIT_AI_GROUP","AI_ACTIVITY_ACTION_SCOPES","registerAiActivityActions","pluginId","group","id","label","staffAuditPrefixes","filter","prefix","staffAuditAccessActions","actions","map","key","scope","targetTypes","aiJob","staffAuditActionGroup","staffAuditActionGroupLabel"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;CAaC,GAGD,SACEA,2BAA2B,EAC3BC,gCAAgC,EAChCC,6BAA6B,QAExB,sDAAqD;AAE5D,2CAA2C,GAC3C,OAAO,MAAMC,sBAAsB;IACjCC,YAAY;IACZC,WAAW;IACXC,aAAa;IACbC,eAAe;IACfC,aAAa;IACbC,YAAY;IACZC,eAAe;IACfC,gBAAgB;IAChBC,YAAY;IACZC,kBAAkB;IAClBC,mBAAmB;IACnBC,gBAAgB;IAChBC,cAAc;AAChB,EAAU;AAKV,wEAAwE,GACxE,OAAO,MAAMC,4BAA8D;IACzE,kBAAkB;IAClB,iBAAiB;IACjB,mBAAmB;IACnB,sBAAsB;IACtB,mBAAmB;IACnB,kBAAkB;IAClB,qBAAqB;IACrB,sBAAsB;IACtB,kBAAkB;IAClB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;IACtB,oBAAoB;AACtB,EAAC;AAED,qEAAqE,GACrE,OAAO,MAAMC,0BACXC,OAAOC,MAAM,CAACjB,qBAAoB;AAEpC,qEAAqE,GACrE,OAAO,MAAMkB,2BAA2B,KAAI;AA4B5C,OAAO,MAAMC,mCAGT;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,KAAK;IACLC,UAAU;IACVC,QAAQ;IACRC,WAAW;IACXC,SAAS;IACTC,UAAU;IACVC,UAAU;IACVC,UAAU;AACZ,EAAC;AAgBD;;;;;;;;;;;;CAYC,GACD,MAAMC,yBAA0E;IAC9EC,QAAQ;IACRC,mBAAmB;IACnBC,QAAQ;IACRC,UAAU;IACVC,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,SAAS;IACTC,YAAY;IACZC,UAAU;IACV,8EAA8E;IAC9EC,KAAK;IACLC,MAAM;IACNC,OAAO;IACP,yEAAyE;IACzE,mCAAmC;IACnCC,SAAS;IACT,4EAA4E;IAC5EC,KAAK;IACL,kEAAkE;IAClEC,OAAO;IACP,qDAAqD;IACrDC,OAAO;IACP,0EAA0E;IAC1EC,eAAe;IACf,qEAAqE;IACrEC,OAAO;AACT;AAEA,OAAO,SAASC,mBAAmBC,QAA6B;IAC9D,OAAOrB,sBAAsB,CAACqB,SAAS;AACzC;AAEA,0DAA0D,GAC1D,OAAO,SAASC,mBAAmBC,MAAe;IAChD,OACE,OAAOA,WAAW,YAClB,AAACxC,wBAA8CyC,QAAQ,CAACD;AAE5D;AAEA,yEAAyE,GACzE,OAAO,SAASE,sBAAsBF,MAAe;IACnD,OAAOD,mBAAmBC,UACtBzC,yBAAyB,CAACyC,OAAO,GACjCG;AACN;AAEA;;;;;;CAMC,GACD,OAAO,MAAMC,iCAAoD;IAC/D;IACA;IACA;CACD,CAAA;AAED,uDAAuD,GACvD,OAAO,MAAMC,uBAAuB,KAAI;AAExC;;;;CAIC,GACD,MAAMC,4BAGF;IACF,kBAAkB;QAAC;QAAO;KAAO;IACjC,iBAAiB;QAAC;QAAO;KAAO;IAChC,mBAAmB;QAAC;QAAO;KAAO;IAClC,sBAAsB;QAAC;QAAO;KAAO;IACrC,mBAAmB;QAAC;QAAO;KAAO;IAClC,kBAAkB;QAAC;QAAO;KAAO;IACjC,qBAAqB;QAAC;QAAO;KAAO;IACpC,sBAAsB;IACtB,kBAAkB;IAClB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;IACtB,oBAAoB;AACtB;AAEA;;;;;CAKC,GACD,OAAO,SAASC;IACd/D,8BAA8B;QAC5BgE,UAAU;QACVC,OAAO;YACLC,IAAIL;YACJM,OAAOhD;YACPiD,oBAAoBR,+BAA+BS,MAAM,CACvD,CAACC,SAAWA,WAAW;YAEzB,kEAAkE;YAClE,sEAAsE;YACtE,sEAAsE;YACtE,wDAAwD;YACxDC,yBAAyB;gBAAC;gBAAiB;gBAAwB;aAA8B;QACnG;QACAC,SAASxD,wBAAwByD,GAAG,CAAC,CAACC,MAAS,CAAA;gBAC7CA;gBACAP,OAAOpD,yBAAyB,CAAC2D,IAAI;gBACrCC,OAAOb,yBAAyB,CAACY,IAAI;YACvC,CAAA;QACA,uEAAuE;QACvE,qEAAqE;QACrE,sEAAsE;QACtE,iDAAiD;QACjDE,aAAa;YAAEC,OAAO;QAAgB;IACxC;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,sBAAsBtB,MAAe;IACnD,OAAO1D,4BAA4B0D;AACrC;AAEA,6EAA6E,GAC7E,OAAO,SAASuB,2BAA2Bd,KAAa;IACtD,OAAOlE,iCAAiCkE;AAC1C"}
@@ -146,6 +146,7 @@ export function AiJobFollow({ job: initial, orgId, orgSlug, user, intro, onOpenJ
146
146
  }),
147
147
  children: [
148
148
  /*#__PURE__*/ _jsxs(Stack, {
149
+ useFlexGap: true,
149
150
  direction: "row",
150
151
  spacing: 1,
151
152
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-follow.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 { AppLink } from '@aglyn/shared-ui-jsx'\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { Alert, Box, Button, Chip, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobPhase,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { cancelAiJobRequest, resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\nimport { followAiJobEvents } from './ai-job-events'\nimport { aiJobPrimaryLink } from './ai-job-links'\nimport { aiJobProblem } from './use-ai-job-run'\n\n/**\n * A JOB, WATCHED AND DECIDED WHERE IT WAS STARTED (AGL-3593).\n *\n * The dialog that starts a planned job — \"Plan my site\", \"Plan the page\" —\n * used to end on a sentence pointing at a drawer a person had to go and find,\n * and a person who could not find it never got their site built. This is\n * what such a dialog shows instead, once its job exists:\n *\n * - where the job stands, live — queued, planning, plan ready, building,\n * then done or failed — read from the same events route the AI jobs\n * drawer follows;\n * - the plan, once it is ready, with the drawer's own plan component and its\n * Confirm, through the same request the drawer sends\n * ({@link resumeAiJobRequest}), and Cancel beside it;\n * - once it is done, the one action that opens what it built;\n * - and always \"Open AI jobs\", which follows the job anywhere else in the\n * console.\n *\n * Closing the dialog stops following; the job carries on, and the top-bar\n * indicator, the Assist launcher and the notification the machine raises say\n * when it needs the person again.\n */\n\nexport interface AiJobFollowProps {\n /** The job as the create door answered with it. */\n job: AiJobSummary\n orgId: string\n /**\n * Path slug for the link to what the job built. A dialog mounted on a\n * console page that names its org can leave it out: the slug is the first\n * segment of every org-scoped console address.\n */\n orgSlug?: string | null\n user: MaybeTokenSource\n /** What the dialog says about the job it started, above its live status. */\n intro: string\n /**\n * Closes the dialog. Called beside opening AI jobs, because a dialog\n * covers the panel AI jobs opens in.\n */\n onOpenJobs?: () => void\n /** The viewer is staff (AGL-3078): a refused answer's details are theirs to open. */\n staff?: boolean\n}\n\n/** What the status line says in each phase, and how loudly. */\nconst PHASE_LINE: Readonly<\n Record<AiJobPhase, { severity: 'info' | 'warning' | 'success' | 'error'; text: string }>\n> = {\n queued: { severity: 'info', text: 'Queued. It starts in a moment.' },\n planning: { severity: 'info', text: 'Planning. The plan appears here when it is ready.' },\n 'plan-ready': {\n severity: 'warning',\n text: 'Your plan is ready. Read it below and confirm it to build — nothing is built until you do.',\n },\n building: { severity: 'info', text: 'Building. Everything it builds is an unpublished draft.' },\n attention: { severity: 'warning', text: 'The job stopped and needs you.' },\n done: { severity: 'success', text: 'Done. Your drafts are ready to open.' },\n failed: { severity: 'error', text: 'The job stopped before it finished.' },\n canceled: { severity: 'info', text: 'The job was canceled.' },\n}\n\n/** The org slug a console address names: its first segment. */\nfunction orgSlugFromLocation(): string {\n const path = typeof globalThis.location?.pathname === 'string' ? globalThis.location.pathname : ''\n return path.split('/')[1] ?? ''\n}\n\n/** Follows `initial` through the events route while mounted, and lets a decision replace it. */\nexport function useFollowedAiJob(\n user: MaybeTokenSource,\n orgId: string,\n initial: AiJobSummary,\n): [AiJobSummary, (job: AiJobSummary) => void] {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState(initial)\n useEffect(() => setJob(initial), [initial])\n\n // One stream while the job is moving. A job parked for the person settles\n // the stream; confirming moves it again, and the status change re-opens it.\n const moving = job.status === 'queued' || job.status === 'running'\n useEffect(() => {\n if (!moving) return undefined\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, job.id, controller.signal, setJob)\n return () => controller.abort()\n }, [moving, orgId, job.id])\n\n const replace = useCallback((next: AiJobSummary) => {\n publishAiJob(next)\n setJob(next)\n }, [])\n return [job, replace]\n}\n\nexport function AiJobFollow({\n job: initial,\n orgId,\n orgSlug,\n user,\n intro,\n onOpenJobs,\n staff = false,\n}: AiJobFollowProps) {\n const [job, replace] = useFollowedAiJob(user, orgId, initial)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const phase = aiJobPhase(job)\n const line = PHASE_LINE[phase]\n const moving = phase === 'queued' || phase === 'planning' || phase === 'building'\n const settled = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const primary = aiJobPrimaryLink(job, orgSlug || orgSlugFromLocation())\n const problem = phase === 'attention' || phase === 'failed' ? aiJobProblem(job, line.text) : null\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n\n const decide = useCallback(\n async (request: () => ReturnType<typeof resumeAiJobRequest>) => {\n setBusy(true)\n setNotice(null)\n const { job: next, error } = await request()\n if (next) replace(next)\n if (error) setNotice(error)\n setBusy(false)\n },\n [replace],\n )\n\n const openJobs = () => {\n openAiJobs({ jobId: job.id })\n onOpenJobs?.()\n }\n\n return (\n <Stack spacing={2} aria-live=\"polite\">\n <Typography variant=\"body2\" color=\"text.secondary\">\n {intro}\n </Typography>\n <Alert\n severity={line.severity}\n icon={moving ? <CircularProgress size={18} aria-label={AI_JOB_PHASE_LABELS[phase]} /> : undefined}\n action={\n <Button color=\"inherit\" size=\"small\" variant=\"outlined\" onClick={openJobs}>\n {'Open AI jobs'}\n </Button>\n }\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" label={AI_JOB_PHASE_LABELS[phase]} />\n {job.steps.length > 1 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${stepsDone} of ${job.steps.length} steps`}\n </Typography>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {problem ?? line.text}\n </Typography>\n </Alert>\n {primary ? (\n <Box>\n <Button variant=\"contained\" component={AppLink} href={primary.href}>\n {primary.label}\n </Button>\n </Box>\n ) : null}\n {/* The drawer's own plan and its Confirm, on the drawer's request. */}\n <AiJobPlan\n job={job}\n onResume={(target) => void decide(() => resumeAiJobRequest(user, orgId, target))}\n busy={busy}\n staff={staff}\n // \"Use the starter site instead\" on a guided start that did not work out (AGL-3594).\n user={user ?? undefined}\n />\n {!settled ? (\n <Box>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => void decide(() => cancelAiJobRequest(user, orgId, job.id))}\n >\n {'Cancel job'}\n </Button>\n </Box>\n ) : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )\n}\n\nexport default AiJobFollow\n"],"names":["AppLink","Alert","Box","Button","Chip","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobPhase","AI_JOB_TERMINAL_STATUSES","AiJobPlan","cancelAiJobRequest","resumeAiJobRequest","openAiJobs","publishAiJob","followAiJobEvents","aiJobPrimaryLink","aiJobProblem","PHASE_LINE","queued","severity","text","planning","building","attention","done","failed","canceled","orgSlugFromLocation","path","globalThis","location","pathname","split","useFollowedAiJob","user","orgId","initial","userRef","current","job","setJob","moving","status","undefined","controller","AbortController","id","signal","abort","replace","next","AiJobFollow","orgSlug","intro","onOpenJobs","staff","busy","setBusy","notice","setNotice","phase","line","settled","includes","primary","problem","stepsDone","steps","filter","step","length","decide","request","error","openJobs","jobId","spacing","aria-live","variant","color","icon","size","aria-label","action","onClick","direction","sx","alignItems","flexWrap","rowGap","label","mt","component","href","onResume","target","disabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-follow.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 { AppLink } from '@aglyn/shared-ui-jsx'\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { Alert, Box, Button, Chip, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobPhase,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { cancelAiJobRequest, resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\nimport { followAiJobEvents } from './ai-job-events'\nimport { aiJobPrimaryLink } from './ai-job-links'\nimport { aiJobProblem } from './use-ai-job-run'\n\n/**\n * A JOB, WATCHED AND DECIDED WHERE IT WAS STARTED (AGL-3593).\n *\n * The dialog that starts a planned job — \"Plan my site\", \"Plan the page\" —\n * used to end on a sentence pointing at a drawer a person had to go and find,\n * and a person who could not find it never got their site built. This is\n * what such a dialog shows instead, once its job exists:\n *\n * - where the job stands, live — queued, planning, plan ready, building,\n * then done or failed — read from the same events route the AI jobs\n * drawer follows;\n * - the plan, once it is ready, with the drawer's own plan component and its\n * Confirm, through the same request the drawer sends\n * ({@link resumeAiJobRequest}), and Cancel beside it;\n * - once it is done, the one action that opens what it built;\n * - and always \"Open AI jobs\", which follows the job anywhere else in the\n * console.\n *\n * Closing the dialog stops following; the job carries on, and the top-bar\n * indicator, the Assist launcher and the notification the machine raises say\n * when it needs the person again.\n */\n\nexport interface AiJobFollowProps {\n /** The job as the create door answered with it. */\n job: AiJobSummary\n orgId: string\n /**\n * Path slug for the link to what the job built. A dialog mounted on a\n * console page that names its org can leave it out: the slug is the first\n * segment of every org-scoped console address.\n */\n orgSlug?: string | null\n user: MaybeTokenSource\n /** What the dialog says about the job it started, above its live status. */\n intro: string\n /**\n * Closes the dialog. Called beside opening AI jobs, because a dialog\n * covers the panel AI jobs opens in.\n */\n onOpenJobs?: () => void\n /** The viewer is staff (AGL-3078): a refused answer's details are theirs to open. */\n staff?: boolean\n}\n\n/** What the status line says in each phase, and how loudly. */\nconst PHASE_LINE: Readonly<\n Record<AiJobPhase, { severity: 'info' | 'warning' | 'success' | 'error'; text: string }>\n> = {\n queued: { severity: 'info', text: 'Queued. It starts in a moment.' },\n planning: { severity: 'info', text: 'Planning. The plan appears here when it is ready.' },\n 'plan-ready': {\n severity: 'warning',\n text: 'Your plan is ready. Read it below and confirm it to build — nothing is built until you do.',\n },\n building: { severity: 'info', text: 'Building. Everything it builds is an unpublished draft.' },\n attention: { severity: 'warning', text: 'The job stopped and needs you.' },\n done: { severity: 'success', text: 'Done. Your drafts are ready to open.' },\n failed: { severity: 'error', text: 'The job stopped before it finished.' },\n canceled: { severity: 'info', text: 'The job was canceled.' },\n}\n\n/** The org slug a console address names: its first segment. */\nfunction orgSlugFromLocation(): string {\n const path = typeof globalThis.location?.pathname === 'string' ? globalThis.location.pathname : ''\n return path.split('/')[1] ?? ''\n}\n\n/** Follows `initial` through the events route while mounted, and lets a decision replace it. */\nexport function useFollowedAiJob(\n user: MaybeTokenSource,\n orgId: string,\n initial: AiJobSummary,\n): [AiJobSummary, (job: AiJobSummary) => void] {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState(initial)\n useEffect(() => setJob(initial), [initial])\n\n // One stream while the job is moving. A job parked for the person settles\n // the stream; confirming moves it again, and the status change re-opens it.\n const moving = job.status === 'queued' || job.status === 'running'\n useEffect(() => {\n if (!moving) return undefined\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, job.id, controller.signal, setJob)\n return () => controller.abort()\n }, [moving, orgId, job.id])\n\n const replace = useCallback((next: AiJobSummary) => {\n publishAiJob(next)\n setJob(next)\n }, [])\n return [job, replace]\n}\n\nexport function AiJobFollow({\n job: initial,\n orgId,\n orgSlug,\n user,\n intro,\n onOpenJobs,\n staff = false,\n}: AiJobFollowProps) {\n const [job, replace] = useFollowedAiJob(user, orgId, initial)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const phase = aiJobPhase(job)\n const line = PHASE_LINE[phase]\n const moving = phase === 'queued' || phase === 'planning' || phase === 'building'\n const settled = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const primary = aiJobPrimaryLink(job, orgSlug || orgSlugFromLocation())\n const problem = phase === 'attention' || phase === 'failed' ? aiJobProblem(job, line.text) : null\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n\n const decide = useCallback(\n async (request: () => ReturnType<typeof resumeAiJobRequest>) => {\n setBusy(true)\n setNotice(null)\n const { job: next, error } = await request()\n if (next) replace(next)\n if (error) setNotice(error)\n setBusy(false)\n },\n [replace],\n )\n\n const openJobs = () => {\n openAiJobs({ jobId: job.id })\n onOpenJobs?.()\n }\n\n return (\n <Stack spacing={2} aria-live=\"polite\">\n <Typography variant=\"body2\" color=\"text.secondary\">\n {intro}\n </Typography>\n <Alert\n severity={line.severity}\n icon={moving ? <CircularProgress size={18} aria-label={AI_JOB_PHASE_LABELS[phase]} /> : undefined}\n action={\n <Button color=\"inherit\" size=\"small\" variant=\"outlined\" onClick={openJobs}>\n {'Open AI jobs'}\n </Button>\n }\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" label={AI_JOB_PHASE_LABELS[phase]} />\n {job.steps.length > 1 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${stepsDone} of ${job.steps.length} steps`}\n </Typography>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {problem ?? line.text}\n </Typography>\n </Alert>\n {primary ? (\n <Box>\n <Button variant=\"contained\" component={AppLink} href={primary.href}>\n {primary.label}\n </Button>\n </Box>\n ) : null}\n {/* The drawer's own plan and its Confirm, on the drawer's request. */}\n <AiJobPlan\n job={job}\n onResume={(target) => void decide(() => resumeAiJobRequest(user, orgId, target))}\n busy={busy}\n staff={staff}\n // \"Use the starter site instead\" on a guided start that did not work out (AGL-3594).\n user={user ?? undefined}\n />\n {!settled ? (\n <Box>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => void decide(() => cancelAiJobRequest(user, orgId, job.id))}\n >\n {'Cancel job'}\n </Button>\n </Box>\n ) : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )\n}\n\nexport default AiJobFollow\n"],"names":["AppLink","Alert","Box","Button","Chip","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobPhase","AI_JOB_TERMINAL_STATUSES","AiJobPlan","cancelAiJobRequest","resumeAiJobRequest","openAiJobs","publishAiJob","followAiJobEvents","aiJobPrimaryLink","aiJobProblem","PHASE_LINE","queued","severity","text","planning","building","attention","done","failed","canceled","orgSlugFromLocation","path","globalThis","location","pathname","split","useFollowedAiJob","user","orgId","initial","userRef","current","job","setJob","moving","status","undefined","controller","AbortController","id","signal","abort","replace","next","AiJobFollow","orgSlug","intro","onOpenJobs","staff","busy","setBusy","notice","setNotice","phase","line","settled","includes","primary","problem","stepsDone","steps","filter","step","length","decide","request","error","openJobs","jobId","spacing","aria-live","variant","color","icon","size","aria-label","action","onClick","useFlexGap","direction","sx","alignItems","flexWrap","rowGap","label","mt","component","href","onResume","target","disabled"],"mappings":"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"}
@@ -53,6 +53,19 @@ import { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from
53
53
  host
54
54
  });
55
55
  }
56
+ if (output.resource === 'entry') {
57
+ var _output_proposal;
58
+ // A post a guided start wrote (AGL-3676) opens in its blog's list in Content.
59
+ const collectionSlug = (_output_proposal = output.proposal) == null ? void 0 : _output_proposal['collectionSlug'];
60
+ return typeof collectionSlug === 'string' && collectionSlug ? buildRoute(Route.HOST_CONTENT_COLLECTION, {
61
+ orgSlug,
62
+ host,
63
+ collectionSlug
64
+ }) : buildRoute(Route.HOST_CONTENT, {
65
+ orgSlug,
66
+ host
67
+ });
68
+ }
56
69
  if (output.resource === 'theme') {
57
70
  return buildRoute(Route.HOST_SETUP_THEME, {
58
71
  orgSlug,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from '../model/ai-job-notice'\nimport type { AiJobOutput, AiJobSummary } from '../model/ai-jobs.types'\n\n// Where a job's work opens in the console (AGL-2904), shared by the AI jobs\n// drawer and every dialog that follows the job it started (AGL-3593).\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\n}\n\n/**\n * The ONE action a finished job's row, its notification and the dialog that\n * started it lead with (AGL-3593): what the job built. Several pages from one\n * job — a site's — open the site's Pages list, where they wait as drafts; one\n * output with a page of its own opens that page; anything else has no single\n * place to go, and the row's own links stand. `null` until the job is done.\n */\nexport function aiJobPrimaryLink(\n job: Pick<AiJobSummary, 'status' | 'outputs'>,\n orgSlug: string,\n): { href: string; label: string } | null {\n if (job.status !== 'done' || !orgSlug) return null\n const screens = job.outputs.filter(\n (output) => output.resource === 'screen' && output.hostSubdomain,\n )\n if (screens.length > 1) {\n return {\n href: buildRoute(Route.HOST_SCREENS, { orgSlug, host: String(screens[0].hostSubdomain) }),\n label: 'Open your draft pages',\n }\n }\n for (const output of job.outputs) {\n const href = aiJobOutputHref(output, orgSlug)\n if (href) {\n return {\n href,\n label: output.resource === 'screen' ? 'Open the draft page' : `Open ${output.label}`,\n }\n }\n }\n return null\n}\n\nexport { AI_SITE_BUILD_HREF }\n\n/**\n * The \"Building your site\" page for one site job (AGL-3594): linkable, so the\n * guided start lands on it, a reload keeps it and a notification opens it.\n * `null` without the org slug or the site's subdomain a console URL needs.\n */\nexport function aiSiteBuildHref(\n orgSlug: string | null | undefined,\n hostSubdomain: string | null | undefined,\n jobId: string,\n): string | null {\n if (!orgSlug || !hostSubdomain || !jobId) return null\n return `/${orgSlug}/hosts/${hostSubdomain}${AI_SITE_BUILD_HREF}/${encodeURIComponent(jobId)}`\n}\n\n/**\n * A finished site's two ways in (AGL-3594): its first page's draft, opened in\n * the console's preview of that version, and the Pages list every draft waits\n * in. Either is `null` where the job reported nothing to open.\n */\nexport function aiSiteBuildDoneLinks(\n job: Pick<AiJobSummary, 'outputs'>,\n orgSlug: string,\n): { view: string | null; pages: string | null } {\n const screens = job.outputs.filter((output) => output.resource === 'screen' && output.hostSubdomain)\n const first = screens[0]\n if (!first || !orgSlug) return { view: null, pages: null }\n const host = String(first.hostSubdomain)\n return {\n view: first.versionId\n ? `/${orgSlug}/hosts/${host}/screens/${first.id}/versions/${first.versionId}/view`\n : null,\n pages: buildRoute(Route.HOST_SCREENS, { orgSlug, host }),\n }\n}\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","AI_JOB_BESIGNER_SEGMENT","BESIGNER_SEGMENT","AI_SITE_BUILD_HREF","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","HOST_SETUP_THEME","aiJobPrimaryLink","job","status","screens","outputs","filter","length","href","HOST_SCREENS","String","label","aiSiteBuildHref","jobId","encodeURIComponent","aiSiteBuildDoneLinks","first","view","pages"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from '../model/ai-job-notice'\nimport type { AiJobOutput, AiJobSummary } from '../model/ai-jobs.types'\n\n// Where a job's work opens in the console (AGL-2904), shared by the AI jobs\n// drawer and every dialog that follows the job it started (AGL-3593).\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'entry') {\n // A post a guided start wrote (AGL-3676) opens in its blog's list in Content.\n const collectionSlug = output.proposal?.['collectionSlug']\n return typeof collectionSlug === 'string' && collectionSlug\n ? buildRoute(Route.HOST_CONTENT_COLLECTION, { orgSlug, host, collectionSlug })\n : buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\n}\n\n/**\n * The ONE action a finished job's row, its notification and the dialog that\n * started it lead with (AGL-3593): what the job built. Several pages from one\n * job — a site's — open the site's Pages list, where they wait as drafts; one\n * output with a page of its own opens that page; anything else has no single\n * place to go, and the row's own links stand. `null` until the job is done.\n */\nexport function aiJobPrimaryLink(\n job: Pick<AiJobSummary, 'status' | 'outputs'>,\n orgSlug: string,\n): { href: string; label: string } | null {\n if (job.status !== 'done' || !orgSlug) return null\n const screens = job.outputs.filter(\n (output) => output.resource === 'screen' && output.hostSubdomain,\n )\n if (screens.length > 1) {\n return {\n href: buildRoute(Route.HOST_SCREENS, { orgSlug, host: String(screens[0].hostSubdomain) }),\n label: 'Open your draft pages',\n }\n }\n for (const output of job.outputs) {\n const href = aiJobOutputHref(output, orgSlug)\n if (href) {\n return {\n href,\n label: output.resource === 'screen' ? 'Open the draft page' : `Open ${output.label}`,\n }\n }\n }\n return null\n}\n\nexport { AI_SITE_BUILD_HREF }\n\n/**\n * The \"Building your site\" page for one site job (AGL-3594): linkable, so the\n * guided start lands on it, a reload keeps it and a notification opens it.\n * `null` without the org slug or the site's subdomain a console URL needs.\n */\nexport function aiSiteBuildHref(\n orgSlug: string | null | undefined,\n hostSubdomain: string | null | undefined,\n jobId: string,\n): string | null {\n if (!orgSlug || !hostSubdomain || !jobId) return null\n return `/${orgSlug}/hosts/${hostSubdomain}${AI_SITE_BUILD_HREF}/${encodeURIComponent(jobId)}`\n}\n\n/**\n * A finished site's two ways in (AGL-3594): its first page's draft, opened in\n * the console's preview of that version, and the Pages list every draft waits\n * in. Either is `null` where the job reported nothing to open.\n */\nexport function aiSiteBuildDoneLinks(\n job: Pick<AiJobSummary, 'outputs'>,\n orgSlug: string,\n): { view: string | null; pages: string | null } {\n const screens = job.outputs.filter((output) => output.resource === 'screen' && output.hostSubdomain)\n const first = screens[0]\n if (!first || !orgSlug) return { view: null, pages: null }\n const host = String(first.hostSubdomain)\n return {\n view: first.versionId\n ? `/${orgSlug}/hosts/${host}/screens/${first.id}/versions/${first.versionId}/view`\n : null,\n pages: buildRoute(Route.HOST_SCREENS, { orgSlug, host }),\n }\n}\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","AI_JOB_BESIGNER_SEGMENT","BESIGNER_SEGMENT","AI_SITE_BUILD_HREF","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","collectionSlug","proposal","HOST_CONTENT_COLLECTION","HOST_CONTENT","HOST_SETUP_THEME","aiJobPrimaryLink","job","status","screens","outputs","filter","length","href","HOST_SCREENS","String","label","aiSiteBuildHref","jobId","encodeURIComponent","aiSiteBuildDoneLinks","first","view","pages"],"mappings":"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"}
@@ -244,6 +244,7 @@ export function AiJobsListPage({ hostId, basePath }) {
244
244
  /*#__PURE__*/ _jsxs(Stack, {
245
245
  direction: "row",
246
246
  spacing: 1,
247
+ useFlexGap: true,
247
248
  sx: {
248
249
  alignItems: 'center',
249
250
  flexWrap: 'wrap',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-jobs-list-page.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 { PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Card,\n Chip,\n CircularProgress,\n Container,\n List,\n ListItemButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_PHASE_LABELS, aiJobKindNoun, aiJobPhase, type AiJobPhase } from '../model/ai-job-activity'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { publishAiJob } from './ai-jobs-store'\nimport { useAiJobSite } from './ai-job-site'\n\n/**\n * A site's AI jobs (AGL-3596), at `/ai-jobs` under the site: every job that\n * worked on this site, newest first, each linking to its own page.\n *\n * Read through the jobs route, never Firestore, so the membership, flag and\n * plan rungs that answer every other jobs read answer this one. The site is\n * a query on the route (`hostId`), not a filter of the workspace's list.\n *\n * Every wait ends: the site's read and the list's each settle on content,\n * the empty state, or a sentence with Try again.\n */\n\n/** The most jobs the page lists; the newest, which is what a person comes for. */\nexport const AI_JOBS_PAGE_LIMIT = 50\n\n/** How long the list may take before the page says it could not be loaded. */\nexport const AI_JOBS_LIST_TIMEOUT_MS = 20_000\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 120\n\n/** A row's chip, colored as the AI jobs drawer colors it. */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\nconst WHEN = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' })\n\n/** What a job cost the person: what it spent, less what we refunded. */\nexport function aiJobNetCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/** The kind as a row names it: `Site`, `Page`, `AI job`. */\nexport function aiJobKindLabel(kind: AiJobSummary['kind']): string {\n const noun = aiJobKindNoun(kind)\n return noun === 'AI job' ? noun : noun.charAt(0).toUpperCase() + noun.slice(1)\n}\n\ntype ListState =\n | { status: 'loading' }\n | { status: 'ready'; jobs: AiJobSummary[] }\n | { status: 'error'; message: string }\n\nconst LOAD_ERROR = 'This site’s AI jobs could not be loaded. Try again in a moment.'\n\nasync function readSiteJobs(\n user: MaybeTokenSource,\n orgId: string,\n hostId: string,\n signal: AbortSignal,\n): Promise<ListState> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&hostId=${encodeURIComponent(hostId)}&limit=${AI_JOBS_PAGE_LIMIT}`,\n { signal },\n )\n const payload = (await response.json().catch(() => null)) as { jobs?: AiJobSummary[]; error?: string } | null\n if (!response.ok) return { status: 'error', message: String(payload?.error ?? LOAD_ERROR) }\n const jobs = payload?.jobs ?? []\n // A job the top-bar indicator still counts that has settled since leaves it.\n for (const job of jobs) publishAiJob(job)\n return { status: 'ready', jobs }\n } catch {\n return { status: 'error', message: LOAD_ERROR }\n }\n}\n\nexport function AiJobsListPage({ hostId, basePath }: ConsolePluginPageProps) {\n const { data: user } = useUser()\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const site = useAiJobSite(hostId)\n const orgId = site.status === 'ready' ? site.orgId : null\n const [list, setList] = useState<ListState>({ status: 'loading' })\n const [attempt, setAttempt] = useState(0)\n const uid = user?.uid ?? null\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return undefined\n const controller = new AbortController()\n let settled = false\n setList({ status: 'loading' })\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n controller.abort()\n setList({ status: 'error', message: LOAD_ERROR })\n }, AI_JOBS_LIST_TIMEOUT_MS)\n void readSiteJobs(userRef.current, orgId, hostId, controller.signal).then((next) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n setList(next)\n })\n return () => {\n settled = true\n clearTimeout(timer)\n controller.abort()\n }\n }, [orgId, hostId, uid, attempt])\n\n const retry = useCallback(() => setAttempt((count) => count + 1), [])\n\n const siteName = site.status === 'ready' ? site.name : ''\n const state: ListState =\n site.status === 'error'\n ? { status: 'error', message: 'This site could not be read. Check your connection and reload the page.' }\n : list\n\n return (\n <Container maxWidth=\"md\" sx={{ py: { xs: 3, sm: 5 }, px: { xs: 2, sm: 3 } }}>\n <Stack spacing={3}>\n <Stack spacing={1}>\n {siteName ? (\n <Typography variant=\"overline\" color=\"text.secondary\">\n {siteName}\n </Typography>\n ) : null}\n <Typography variant=\"h4\" component=\"h1\">\n {'AI jobs'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`What ${PLATFORM_BRAND_NAME} AI has done and is doing on this site, newest first.`}\n </Typography>\n </Stack>\n {state.status === 'loading' ? (\n <Stack sx={{ alignItems: 'center', py: 4 }}>\n <CircularProgress aria-label=\"Loading AI jobs\" />\n </Stack>\n ) : state.status === 'error' ? (\n <Alert\n severity=\"warning\"\n action={\n site.status === 'error' ? undefined : (\n <Button color=\"inherit\" size=\"small\" onClick={retry}>\n {'Try again'}\n </Button>\n )\n }\n >\n {state.message}\n </Alert>\n ) : state.jobs.length === 0 ? (\n <Card variant=\"outlined\" sx={{ borderRadius: 2, p: { xs: 2, sm: 3 } }}>\n <Stack spacing={1}>\n <Typography variant=\"h6\" component=\"h2\">\n {'No AI jobs on this site yet'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`An AI job is ${PLATFORM_BRAND_NAME} AI building something for this site from a short ` +\n 'description you write: a page, a page template, a layout, a form or a reusable component. ' +\n 'Each one gets a page here that shows its progress and what it built.'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {'To start one, choose Create with AI on this site’s Pages, Templates, Layouts, Forms or ' +\n 'Components page. A new site can also be built with AI when it is created.'}\n </Typography>\n </Stack>\n </Card>\n ) : (\n <Card variant=\"outlined\" sx={{ borderRadius: 2 }}>\n <List disablePadding aria-label=\"AI jobs\">\n {state.jobs.map((job, index) => {\n const phase = aiJobPhase(job)\n const credits = aiJobNetCredits(job)\n const refund = aiJobRefundCopy(job)\n const brief =\n job.brief.length > BRIEF_PREVIEW_CHARS ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…` : job.brief\n const created = new Date(job.createdAt)\n return (\n <ListItemButton\n key={job.id}\n component={AppLink}\n href={`${basePath ?? ''}/${encodeURIComponent(job.id)}`}\n divider={index < state.jobs.length - 1}\n sx={{ display: 'block', py: 1.5 }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" variant=\"outlined\" label={aiJobKindLabel(job.kind)} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Box sx={{ flexGrow: 1 }} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {credits > 0 ? `${credits.toLocaleString('en-US')} credits · ` : ''}\n {Number.isNaN(created.getTime()) ? null : (\n <time dateTime={job.createdAt}>{WHEN.format(created)}</time>\n )}\n </Typography>\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.75, wordBreak: 'break-word' }}>\n {brief}\n </Typography>\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {refund ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {refund}\n </Typography>\n ) : null}\n </ListItemButton>\n )\n })}\n </List>\n </Card>\n )}\n {state.status === 'ready' && state.jobs.length >= AI_JOBS_PAGE_LIMIT ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Showing the ${AI_JOBS_PAGE_LIMIT} most recent jobs.`}\n </Typography>\n ) : null}\n </Stack>\n </Container>\n )\n}\n\nexport default AiJobsListPage\n"],"names":["PLATFORM_BRAND_NAME","aiJobRefundCopy","AppLink","authorizedFetch","useUser","Alert","Box","Button","Card","Chip","CircularProgress","Container","List","ListItemButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobKindNoun","aiJobPhase","publishAiJob","useAiJobSite","AI_JOBS_PAGE_LIMIT","AI_JOBS_LIST_TIMEOUT_MS","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","WHEN","Intl","DateTimeFormat","dateStyle","timeStyle","aiJobNetCredits","job","Math","max","creditsSpent","refundedCredits","aiJobKindLabel","kind","noun","charAt","toUpperCase","slice","LOAD_ERROR","readSiteJobs","user","orgId","hostId","signal","response","encodeURIComponent","payload","json","catch","ok","status","message","String","error","jobs","AiJobsListPage","basePath","data","userRef","current","site","list","setList","attempt","setAttempt","uid","undefined","controller","AbortController","settled","timer","setTimeout","abort","then","next","clearTimeout","retry","count","siteName","name","state","maxWidth","sx","py","xs","sm","px","spacing","variant","color","component","alignItems","aria-label","severity","action","size","onClick","length","borderRadius","p","disablePadding","map","index","phase","credits","refund","brief","created","Date","createdAt","href","id","divider","display","direction","flexWrap","rowGap","label","flexGrow","toLocaleString","Number","isNaN","getTime","time","dateTime","format","mt","wordBreak"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-jobs-list-page.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 { PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Card,\n Chip,\n CircularProgress,\n Container,\n List,\n ListItemButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_PHASE_LABELS, aiJobKindNoun, aiJobPhase, type AiJobPhase } from '../model/ai-job-activity'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { publishAiJob } from './ai-jobs-store'\nimport { useAiJobSite } from './ai-job-site'\n\n/**\n * A site's AI jobs (AGL-3596), at `/ai-jobs` under the site: every job that\n * worked on this site, newest first, each linking to its own page.\n *\n * Read through the jobs route, never Firestore, so the membership, flag and\n * plan rungs that answer every other jobs read answer this one. The site is\n * a query on the route (`hostId`), not a filter of the workspace's list.\n *\n * Every wait ends: the site's read and the list's each settle on content,\n * the empty state, or a sentence with Try again.\n */\n\n/** The most jobs the page lists; the newest, which is what a person comes for. */\nexport const AI_JOBS_PAGE_LIMIT = 50\n\n/** How long the list may take before the page says it could not be loaded. */\nexport const AI_JOBS_LIST_TIMEOUT_MS = 20_000\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 120\n\n/** A row's chip, colored as the AI jobs drawer colors it. */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\nconst WHEN = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' })\n\n/** What a job cost the person: what it spent, less what we refunded. */\nexport function aiJobNetCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/** The kind as a row names it: `Site`, `Page`, `AI job`. */\nexport function aiJobKindLabel(kind: AiJobSummary['kind']): string {\n const noun = aiJobKindNoun(kind)\n return noun === 'AI job' ? noun : noun.charAt(0).toUpperCase() + noun.slice(1)\n}\n\ntype ListState =\n | { status: 'loading' }\n | { status: 'ready'; jobs: AiJobSummary[] }\n | { status: 'error'; message: string }\n\nconst LOAD_ERROR = 'This site’s AI jobs could not be loaded. Try again in a moment.'\n\nasync function readSiteJobs(\n user: MaybeTokenSource,\n orgId: string,\n hostId: string,\n signal: AbortSignal,\n): Promise<ListState> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&hostId=${encodeURIComponent(hostId)}&limit=${AI_JOBS_PAGE_LIMIT}`,\n { signal },\n )\n const payload = (await response.json().catch(() => null)) as { jobs?: AiJobSummary[]; error?: string } | null\n if (!response.ok) return { status: 'error', message: String(payload?.error ?? LOAD_ERROR) }\n const jobs = payload?.jobs ?? []\n // A job the top-bar indicator still counts that has settled since leaves it.\n for (const job of jobs) publishAiJob(job)\n return { status: 'ready', jobs }\n } catch {\n return { status: 'error', message: LOAD_ERROR }\n }\n}\n\nexport function AiJobsListPage({ hostId, basePath }: ConsolePluginPageProps) {\n const { data: user } = useUser()\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const site = useAiJobSite(hostId)\n const orgId = site.status === 'ready' ? site.orgId : null\n const [list, setList] = useState<ListState>({ status: 'loading' })\n const [attempt, setAttempt] = useState(0)\n const uid = user?.uid ?? null\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return undefined\n const controller = new AbortController()\n let settled = false\n setList({ status: 'loading' })\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n controller.abort()\n setList({ status: 'error', message: LOAD_ERROR })\n }, AI_JOBS_LIST_TIMEOUT_MS)\n void readSiteJobs(userRef.current, orgId, hostId, controller.signal).then((next) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n setList(next)\n })\n return () => {\n settled = true\n clearTimeout(timer)\n controller.abort()\n }\n }, [orgId, hostId, uid, attempt])\n\n const retry = useCallback(() => setAttempt((count) => count + 1), [])\n\n const siteName = site.status === 'ready' ? site.name : ''\n const state: ListState =\n site.status === 'error'\n ? { status: 'error', message: 'This site could not be read. Check your connection and reload the page.' }\n : list\n\n return (\n <Container maxWidth=\"md\" sx={{ py: { xs: 3, sm: 5 }, px: { xs: 2, sm: 3 } }}>\n <Stack spacing={3}>\n <Stack spacing={1}>\n {siteName ? (\n <Typography variant=\"overline\" color=\"text.secondary\">\n {siteName}\n </Typography>\n ) : null}\n <Typography variant=\"h4\" component=\"h1\">\n {'AI jobs'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`What ${PLATFORM_BRAND_NAME} AI has done and is doing on this site, newest first.`}\n </Typography>\n </Stack>\n {state.status === 'loading' ? (\n <Stack sx={{ alignItems: 'center', py: 4 }}>\n <CircularProgress aria-label=\"Loading AI jobs\" />\n </Stack>\n ) : state.status === 'error' ? (\n <Alert\n severity=\"warning\"\n action={\n site.status === 'error' ? undefined : (\n <Button color=\"inherit\" size=\"small\" onClick={retry}>\n {'Try again'}\n </Button>\n )\n }\n >\n {state.message}\n </Alert>\n ) : state.jobs.length === 0 ? (\n <Card variant=\"outlined\" sx={{ borderRadius: 2, p: { xs: 2, sm: 3 } }}>\n <Stack spacing={1}>\n <Typography variant=\"h6\" component=\"h2\">\n {'No AI jobs on this site yet'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`An AI job is ${PLATFORM_BRAND_NAME} AI building something for this site from a short ` +\n 'description you write: a page, a page template, a layout, a form or a reusable component. ' +\n 'Each one gets a page here that shows its progress and what it built.'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {'To start one, choose Create with AI on this site’s Pages, Templates, Layouts, Forms or ' +\n 'Components page. A new site can also be built with AI when it is created.'}\n </Typography>\n </Stack>\n </Card>\n ) : (\n <Card variant=\"outlined\" sx={{ borderRadius: 2 }}>\n <List disablePadding aria-label=\"AI jobs\">\n {state.jobs.map((job, index) => {\n const phase = aiJobPhase(job)\n const credits = aiJobNetCredits(job)\n const refund = aiJobRefundCopy(job)\n const brief =\n job.brief.length > BRIEF_PREVIEW_CHARS ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…` : job.brief\n const created = new Date(job.createdAt)\n return (\n <ListItemButton\n key={job.id}\n component={AppLink}\n href={`${basePath ?? ''}/${encodeURIComponent(job.id)}`}\n divider={index < state.jobs.length - 1}\n sx={{ display: 'block', py: 1.5 }}\n >\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" variant=\"outlined\" label={aiJobKindLabel(job.kind)} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Box sx={{ flexGrow: 1 }} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {credits > 0 ? `${credits.toLocaleString('en-US')} credits · ` : ''}\n {Number.isNaN(created.getTime()) ? null : (\n <time dateTime={job.createdAt}>{WHEN.format(created)}</time>\n )}\n </Typography>\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.75, wordBreak: 'break-word' }}>\n {brief}\n </Typography>\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {refund ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {refund}\n </Typography>\n ) : null}\n </ListItemButton>\n )\n })}\n </List>\n </Card>\n )}\n {state.status === 'ready' && state.jobs.length >= AI_JOBS_PAGE_LIMIT ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Showing the ${AI_JOBS_PAGE_LIMIT} most recent jobs.`}\n </Typography>\n ) : null}\n </Stack>\n </Container>\n )\n}\n\nexport default AiJobsListPage\n"],"names":["PLATFORM_BRAND_NAME","aiJobRefundCopy","AppLink","authorizedFetch","useUser","Alert","Box","Button","Card","Chip","CircularProgress","Container","List","ListItemButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobKindNoun","aiJobPhase","publishAiJob","useAiJobSite","AI_JOBS_PAGE_LIMIT","AI_JOBS_LIST_TIMEOUT_MS","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","WHEN","Intl","DateTimeFormat","dateStyle","timeStyle","aiJobNetCredits","job","Math","max","creditsSpent","refundedCredits","aiJobKindLabel","kind","noun","charAt","toUpperCase","slice","LOAD_ERROR","readSiteJobs","user","orgId","hostId","signal","response","encodeURIComponent","payload","json","catch","ok","status","message","String","error","jobs","AiJobsListPage","basePath","data","userRef","current","site","list","setList","attempt","setAttempt","uid","undefined","controller","AbortController","settled","timer","setTimeout","abort","then","next","clearTimeout","retry","count","siteName","name","state","maxWidth","sx","py","xs","sm","px","spacing","variant","color","component","alignItems","aria-label","severity","action","size","onClick","length","borderRadius","p","disablePadding","map","index","phase","credits","refund","brief","created","Date","createdAt","href","id","divider","display","direction","useFlexGap","flexWrap","rowGap","label","flexGrow","toLocaleString","Number","isNaN","getTime","time","dateTime","format","mt","wordBreak"],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { ConsoleMediaLibraryZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
18
- import { type AiImageMode, type AiSvgStyle } from '../providers/image-contract';
18
+ import { type AiImageMode, type AiImageSize } from '../providers/image-contract';
19
19
  /**
20
20
  * "Create with AI" in Media (AGL-3602): pictures from a description, added to
21
21
  * the library that is open, through the `mediaLibrary` zone beside Upload
@@ -24,9 +24,9 @@ import { type AiImageMode, type AiSvgStyle } from '../providers/image-contract';
24
24
  * It draws from the shell's gates alone — the plan, the member's
25
25
  * `ai.generate`, the plugin being on for the workspace and the site — and asks
26
26
  * no server anything until someone creates a picture: the door decides then,
27
- * and the dialog says what it decided. Two modes: an Illustration (an SVG the
28
- * text AI draws, wherever text AI runs) and, where the deployment makes them,
29
- * a Photo.
27
+ * and the dialog says what it decided. One Kind menu, in sections: Vector
28
+ * kinds (an SVG the text AI draws, wherever text AI runs) and, where the
29
+ * deployment makes them, Photo, Art and Design kinds from the image provider.
30
30
  */
31
31
  /** Where the shell sends a reader to buy what the plan lacks (AGL-3601). */
32
32
  export interface AiMediaUpgrade {
@@ -41,10 +41,23 @@ export interface AiMediaCreateButtonProps extends ConsoleMediaLibraryZoneProps {
41
41
  entitled?: boolean;
42
42
  upgrade?: AiMediaUpgrade;
43
43
  }
44
- /** The words each kind of illustration is offered in. */
45
- export declare const AI_SVG_STYLE_LABELS: Record<AiSvgStyle, string>;
44
+ /**
45
+ * What a picture from the image provider may be declined for, as the code
46
+ * and the provider decide it: Google's filters, and its policies on real
47
+ * people and brands. A declined picture is never charged.
48
+ */
49
+ export declare 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.";
50
+ /** The line under the description for a kind whose colors are described rather than picked. */
51
+ export declare const AI_MEDIA_DESCRIBE_COLORS_NOTE = "To steer the colors, name them in the description, such as navy and coral.";
52
+ /**
53
+ * The size the door will make a picture at for this workspace (AGL-3602):
54
+ * 512 px on the Free plan, 1K on a paid one. The door decides from the plan
55
+ * itself; this reads the same plan so the estimate matches, and reads 1K —
56
+ * the dearer estimate — until the workspace has arrived.
57
+ */
58
+ export declare function useAiMediaImageSize(orgId: string): AiImageSize;
46
59
  /** The sentence the estimate reads as, before anything is spent. */
47
- export declare function aiMediaCreditEstimate(mode: AiImageMode, count: number): string;
60
+ export declare function aiMediaCreditEstimate(mode: AiImageMode, count: number, size?: AiImageSize): string;
48
61
  export declare function AiMediaCreateButton(props: AiMediaCreateButtonProps): import("react").JSX.Element;
49
62
  export interface AiMediaCreateDialogProps extends Pick<ConsoleMediaLibraryZoneProps, 'hostId' | 'library' | 'folderId' | 'onCreated'> {
50
63
  open: boolean;