@aglyn/plugins-ai 1.0.0-beta.228 → 1.0.0-beta.230

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 (274) hide show
  1. package/README.md +3 -3
  2. package/package.json +17 -17
  3. package/src/lib/activity/ai-activity-actions.js +7 -1
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-automation-zones.d.ts +38 -0
  6. package/src/lib/components/ai-automation-zones.js +5 -1
  7. package/src/lib/components/ai-automation-zones.js.map +1 -1
  8. package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
  9. package/src/lib/components/ai-brief-dialog.component.js +87 -8
  10. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  11. package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
  12. package/src/lib/components/ai-campaign-create.component.js +270 -0
  13. package/src/lib/components/ai-campaign-create.component.js.map +1 -0
  14. package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
  15. package/src/lib/components/ai-describe-automation.component.js +15 -6
  16. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-button.component.d.ts +20 -8
  18. package/src/lib/components/ai-describe-button.component.js +19 -36
  19. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  20. package/src/lib/components/ai-describe-email.component.d.ts +31 -0
  21. package/src/lib/components/ai-describe-email.component.js +22 -0
  22. package/src/lib/components/ai-describe-email.component.js.map +1 -0
  23. package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
  24. package/src/lib/components/ai-describe-org-automation.component.js +238 -0
  25. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
  26. package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
  27. package/src/lib/components/ai-experiment-cards.component.js +104 -18
  28. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  29. package/src/lib/components/ai-explain-automation.component.js +1 -5
  30. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
  32. package/src/lib/components/ai-funnel-zones.component.js +178 -0
  33. package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
  34. package/src/lib/components/ai-insight-card.component.d.ts +42 -0
  35. package/src/lib/components/ai-insight-card.component.js +137 -0
  36. package/src/lib/components/ai-insight-card.component.js.map +1 -0
  37. package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
  38. package/src/lib/components/ai-insight-dialog.component.js +9 -3
  39. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  40. package/src/lib/components/ai-job-plan.component.d.ts +7 -2
  41. package/src/lib/components/ai-job-plan.component.js +35 -5
  42. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  43. package/src/lib/components/ai-job-requests.d.ts +17 -1
  44. package/src/lib/components/ai-job-requests.js +39 -3
  45. package/src/lib/components/ai-job-requests.js.map +1 -1
  46. package/src/lib/components/ai-jobs-list-page.component.js +12 -1
  47. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  48. package/src/lib/components/ai-logic-zones.d.ts +64 -0
  49. package/src/lib/components/ai-logic-zones.js +18 -0
  50. package/src/lib/components/ai-logic-zones.js.map +1 -0
  51. package/src/lib/components/ai-logic.component.d.ts +77 -0
  52. package/src/lib/components/ai-logic.component.js +553 -0
  53. package/src/lib/components/ai-logic.component.js.map +1 -0
  54. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  55. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  56. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  57. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  58. package/src/lib/components/ai-media-create.component.js +433 -0
  59. package/src/lib/components/ai-media-create.component.js.map +1 -0
  60. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  61. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  62. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  63. package/src/lib/components/ai-product-zones.d.ts +5 -0
  64. package/src/lib/components/ai-product-zones.js +1 -1
  65. package/src/lib/components/ai-product-zones.js.map +1 -1
  66. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  67. package/src/lib/components/ai-products-create-button.component.js +67 -0
  68. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  69. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  70. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  71. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  72. package/src/lib/components/ai-revise-automation.component.js +207 -0
  73. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  74. package/src/lib/components/ai-site-build-page.component.d.ts +1 -1
  75. package/src/lib/components/ai-site-build-page.component.js +177 -20
  76. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  77. package/src/lib/components/ai-site-start-card.component.d.ts +10 -1
  78. package/src/lib/components/ai-site-start-card.component.js +7 -4
  79. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  80. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  81. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  82. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  83. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  84. package/src/lib/components/assist-build-card.component.js +183 -0
  85. package/src/lib/components/assist-build-card.component.js.map +1 -0
  86. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  87. package/src/lib/components/assist-edit-canvas.js +56 -1
  88. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  89. package/src/lib/components/assist-jobs-drawer.component.d.ts +11 -0
  90. package/src/lib/components/assist-jobs-drawer.component.js +85 -4
  91. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  92. package/src/lib/components/assist-panel.component.js +51 -2
  93. package/src/lib/components/assist-panel.component.js.map +1 -1
  94. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  95. package/src/lib/components/use-ai-job-run.js +0 -50
  96. package/src/lib/components/use-ai-job-run.js.map +1 -1
  97. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  98. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  99. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  100. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  101. package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
  102. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  103. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  104. package/src/lib/jobs/ai-job-build-step.js +657 -0
  105. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  106. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  107. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  108. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  109. package/src/lib/jobs/ai-job-drafts.js +25 -4
  110. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  111. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +70 -0
  112. package/src/lib/jobs/ai-job-layout-site-pages.js +216 -0
  113. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -0
  114. package/src/lib/jobs/ai-job-layout-step.d.ts +2 -2
  115. package/src/lib/jobs/ai-job-layout-step.js +19 -1
  116. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  117. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  118. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  119. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  120. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  121. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  122. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  123. package/src/lib/jobs/ai-job-page-step.js +6 -6
  124. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  125. package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
  126. package/src/lib/jobs/ai-job-plan-step.js +79 -11
  127. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  128. package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
  129. package/src/lib/jobs/ai-job-site-step.js +207 -58
  130. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  131. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  132. package/src/lib/jobs/ai-job-text-step.js +12 -0
  133. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  134. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  135. package/src/lib/jobs/ai-job-workflow-step.js +318 -13
  136. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  137. package/src/lib/jobs/ai-jobs.d.ts +90 -2
  138. package/src/lib/jobs/ai-jobs.js +495 -61
  139. package/src/lib/jobs/ai-jobs.js.map +1 -1
  140. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  141. package/src/lib/jobs/ai-logic-records.js +55 -0
  142. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  143. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  144. package/src/lib/jobs/ai-site-publish.js +9 -0
  145. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  146. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  147. package/src/lib/jobs/ai-workflow-records.js +40 -1
  148. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  149. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  150. package/src/lib/model/ai-automation-draft.js +86 -0
  151. package/src/lib/model/ai-automation-draft.js.map +1 -1
  152. package/src/lib/model/ai-build-job.d.ts +201 -0
  153. package/src/lib/model/ai-build-job.js +471 -0
  154. package/src/lib/model/ai-build-job.js.map +1 -0
  155. package/src/lib/model/ai-build-plan.d.ts +61 -0
  156. package/src/lib/model/ai-build-plan.js +154 -9
  157. package/src/lib/model/ai-build-plan.js.map +1 -1
  158. package/src/lib/model/ai-build-progress.d.ts +56 -0
  159. package/src/lib/model/ai-build-progress.js +128 -0
  160. package/src/lib/model/ai-build-progress.js.map +1 -0
  161. package/src/lib/model/ai-insight.d.ts +20 -10
  162. package/src/lib/model/ai-insight.js +72 -9
  163. package/src/lib/model/ai-insight.js.map +1 -1
  164. package/src/lib/model/ai-job-activity.js +3 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +20 -1
  167. package/src/lib/model/ai-job-failure-copy.js +28 -1
  168. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  169. package/src/lib/model/ai-job-notice.d.ts +3 -0
  170. package/src/lib/model/ai-job-notice.js +11 -2
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +87 -2
  173. package/src/lib/model/ai-jobs.types.js +3 -1
  174. package/src/lib/model/ai-jobs.types.js.map +1 -1
  175. package/src/lib/model/ai-logic-job.d.ts +108 -0
  176. package/src/lib/model/ai-logic-job.js +327 -0
  177. package/src/lib/model/ai-logic-job.js.map +1 -0
  178. package/src/lib/model/ai-media-credits.d.ts +52 -0
  179. package/src/lib/model/ai-media-credits.js +61 -0
  180. package/src/lib/model/ai-media-credits.js.map +1 -0
  181. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  182. package/src/lib/model/ai-overlay-copy.js +175 -0
  183. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  184. package/src/lib/model/ai-site-build-progress.d.ts +28 -3
  185. package/src/lib/model/ai-site-build-progress.js +231 -17
  186. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  187. package/src/lib/model/ai-site-start-seo.d.ts +16 -0
  188. package/src/lib/model/ai-site-start-seo.js +54 -4
  189. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  190. package/src/lib/model/ai-site-start.d.ts +14 -0
  191. package/src/lib/model/ai-site-start.js +43 -0
  192. package/src/lib/model/ai-site-start.js.map +1 -1
  193. package/src/lib/model/ai-svg.d.ts +69 -0
  194. package/src/lib/model/ai-svg.js +265 -0
  195. package/src/lib/model/ai-svg.js.map +1 -0
  196. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  197. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  198. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  199. package/src/lib/model/ai-workflow-job.js +124 -7
  200. package/src/lib/model/ai-workflow-job.js.map +1 -1
  201. package/src/lib/model/assist-build.d.ts +58 -0
  202. package/src/lib/model/assist-build.js +52 -0
  203. package/src/lib/model/assist-build.js.map +1 -0
  204. package/src/lib/model/assist-edit.d.ts +48 -3
  205. package/src/lib/model/assist-edit.js +48 -4
  206. package/src/lib/model/assist-edit.js.map +1 -1
  207. package/src/lib/plugin-config.js +2 -0
  208. package/src/lib/plugin-config.js.map +1 -1
  209. package/src/lib/plugin.js +240 -11
  210. package/src/lib/plugin.js.map +1 -1
  211. package/src/lib/providers/catalog.d.ts +81 -4
  212. package/src/lib/providers/catalog.js +80 -9
  213. package/src/lib/providers/catalog.js.map +1 -1
  214. package/src/lib/providers/image-contract.d.ts +116 -0
  215. package/src/lib/providers/image-contract.js +74 -0
  216. package/src/lib/providers/image-contract.js.map +1 -0
  217. package/src/lib/providers/model-choice.js +11 -0
  218. package/src/lib/providers/model-choice.js.map +1 -1
  219. package/src/lib/providers/routing.js +18 -0
  220. package/src/lib/providers/routing.js.map +1 -1
  221. package/src/lib/providers/vertex-image.d.ts +121 -0
  222. package/src/lib/providers/vertex-image.js +312 -0
  223. package/src/lib/providers/vertex-image.js.map +1 -0
  224. package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
  225. package/src/lib/runtime/ai-doctrine-validators.js +89 -0
  226. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  227. package/src/lib/runtime/ai-doctrine.d.ts +6 -0
  228. package/src/lib/runtime/ai-doctrine.js +16 -6
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-eval-live.js +4 -0
  231. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  232. package/src/lib/runtime/ai-eval.d.ts +15 -1
  233. package/src/lib/runtime/ai-eval.js +52 -1
  234. package/src/lib/runtime/ai-eval.js.map +1 -1
  235. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  236. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  237. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  238. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  239. package/src/lib/server/ai-experiment-versions.js +270 -0
  240. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  241. package/src/lib/server/ai-jobs-resume.js +74 -4
  242. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  243. package/src/lib/server/ai-media-image.d.ts +66 -0
  244. package/src/lib/server/ai-media-image.js +521 -0
  245. package/src/lib/server/ai-media-image.js.map +1 -0
  246. package/src/lib/server/ai-media-svg.d.ts +91 -0
  247. package/src/lib/server/ai-media-svg.js +186 -0
  248. package/src/lib/server/ai-media-svg.js.map +1 -0
  249. package/src/lib/server/assist-build.d.ts +54 -0
  250. package/src/lib/server/assist-build.js +121 -0
  251. package/src/lib/server/assist-build.js.map +1 -0
  252. package/src/lib/server/assist-chat.js +108 -12
  253. package/src/lib/server/assist-chat.js.map +1 -1
  254. package/src/lib/server/assist-edit.js +213 -3
  255. package/src/lib/server/assist-edit.js.map +1 -1
  256. package/src/lib/server/assist-view-context.js +242 -4
  257. package/src/lib/server/assist-view-context.js.map +1 -1
  258. package/src/lib/server.js +26 -0
  259. package/src/lib/server.js.map +1 -1
  260. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  261. package/src/lib/tools/ai-logic-tool.js +207 -0
  262. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  263. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  264. package/src/lib/tools/ai-overlay-tool.js +107 -0
  265. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  266. package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
  267. package/src/lib/tools/ai-workflow-tool.js +31 -0
  268. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  269. package/src/lib/usage/assist-job-refund.d.ts +31 -3
  270. package/src/lib/usage/assist-job-refund.js +46 -10
  271. package/src/lib/usage/assist-job-refund.js.map +1 -1
  272. package/src/lib/usage/assist-usage.d.ts +5 -0
  273. package/src/lib/usage/assist-usage.js +2 -0
  274. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -0,0 +1,177 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ import { aiAddonName } from "@aglyn/aglyn";
19
+ import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
20
+ import { mdiCreation } from "@aglyn/shared-data-mdi";
21
+ import { MdiIcon } from "@aglyn/shared-ui-jsx";
22
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material";
23
+ import { useEffect, useState } from "react";
24
+ /** The label every "Create with AI" entry reads. */ export const AI_UPSELL_CREATE_LABEL = 'Create with AI';
25
+ /** What the dialog says one entry would do here. */ export const AI_UPSELL_COPY = {
26
+ page: {
27
+ title: 'Create a page with AI',
28
+ does: 'Describe the page you need and AI plans it from your brief, using your theme and ' + 'what the site already has. You confirm the plan, and the page is built as an ' + 'unpublished draft.'
29
+ },
30
+ template: {
31
+ title: 'Create a page template with AI',
32
+ does: 'Describe what each page should show and AI plans the template, filling what ' + 'changes from page to page from each record. You confirm the plan, and the template ' + 'is built as a draft in your library.'
33
+ },
34
+ layout: {
35
+ title: 'Create a layout with AI',
36
+ does: 'Describe what the layout should hold and AI plans it, linking your pages and placing ' + 'your navigation or menu component when the site has one. You confirm the plan, and ' + 'the layout is built as a draft no page uses until you assign it.'
37
+ },
38
+ form: {
39
+ title: 'Create a form with AI',
40
+ does: 'Describe what the form is for and AI plans its fields, its marketing consent and ' + 'where each submission goes. You confirm the plan, and the form is built as a draft.'
41
+ },
42
+ component: {
43
+ title: 'Create a reusable component with AI',
44
+ does: 'Describe what the component should show and AI plans it, making what each page can ' + 'change into its properties. You confirm the plan, and the component is built as a ' + 'draft.'
45
+ },
46
+ workflow: {
47
+ title: 'Create an automation with AI',
48
+ does: 'Describe what should happen, and when, and AI drafts the automation from the ' + 'triggers and steps your plan includes. It is saved switched off for you to review.'
49
+ },
50
+ email: {
51
+ title: 'Create an email with AI',
52
+ does: 'Describe the email you need and AI plans a design from the email blocks, with three ' + 'subject lines and three preheaders to choose between. You confirm the plan, and the ' + 'design is built as a draft that is sent to nobody.'
53
+ },
54
+ campaign: {
55
+ title: 'Create a campaign with AI',
56
+ does: 'Describe what the campaign is for and AI writes its email design and a draft campaign ' + 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' + 'until you pick its lists.'
57
+ },
58
+ product: {
59
+ title: 'Create products with AI',
60
+ does: 'Describe what the store sells and AI proposes up to twelve products. You review them ' + 'before any is created, and each is created as a draft that is not on your storefront ' + 'until you price it and activate it.'
61
+ },
62
+ logic: {
63
+ title: 'Create a function or variable with AI',
64
+ does: 'Describe what it should work out or hold and AI drafts the function or variable. It ' + 'opens unsaved in the editor for you to check and save.'
65
+ },
66
+ overlay: {
67
+ title: 'Create an overlay with AI',
68
+ does: 'Describe the announcement bar or popup you want and AI writes its copy within the ' + 'overlay’s limits. It is saved switched off, for you to check and turn on.'
69
+ },
70
+ funnel: {
71
+ title: 'Create a funnel with AI',
72
+ does: 'Describe the journey you want to measure and AI proposes its steps from the site’s ' + 'real pages, forms and products. It opens in the funnel editor for you to check and save.'
73
+ },
74
+ insight: {
75
+ title: 'Ask AI about these numbers',
76
+ label: 'Ask a question',
77
+ does: 'Ask a question about the figures on this page and AI answers from them, every answer ' + 'traced to the figures it is built from. It reads; it changes nothing.'
78
+ }
79
+ };
80
+ /** The label an entry's button reads. */ export function aiUpsellLabel(kind) {
81
+ var _AI_UPSELL_COPY_kind_label;
82
+ return (_AI_UPSELL_COPY_kind_label = AI_UPSELL_COPY[kind].label) != null ? _AI_UPSELL_COPY_kind_label : AI_UPSELL_CREATE_LABEL;
83
+ }
84
+ /**
85
+ * The sentence that says where the feature comes from, and who can add it.
86
+ * `feature` is what the entry is called: "Create with AI" unless it is an ask.
87
+ */ export function aiUpsellOffer(canManageBilling, feature = AI_UPSELL_CREATE_LABEL) {
88
+ const addon = aiAddonName();
89
+ return canManageBilling ? `${feature} comes with the ${addon} add-on, which you can add to this ` + 'workspace from Billing.' : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` + 'to add it from Billing.';
90
+ }
91
+ export function AiUpsellDialog({ kind, open, onClose, upgrade }) {
92
+ const copy = AI_UPSELL_COPY[kind];
93
+ const { canManageBilling, billingHref } = upgrade;
94
+ // Counted once per opening: the denominator the click is a rate against.
95
+ useEffect(()=>{
96
+ if (open) trackEvent('ai_upsell_shown', {
97
+ kind,
98
+ can_manage: canManageBilling
99
+ });
100
+ }, [
101
+ open,
102
+ kind,
103
+ canManageBilling
104
+ ]);
105
+ return /*#__PURE__*/ _jsxs(Dialog, {
106
+ open: open,
107
+ onClose: onClose,
108
+ fullWidth: true,
109
+ maxWidth: "xs",
110
+ children: [
111
+ /*#__PURE__*/ _jsx(DialogTitle, {
112
+ children: copy.title
113
+ }),
114
+ /*#__PURE__*/ _jsx(DialogContent, {
115
+ children: /*#__PURE__*/ _jsxs(Stack, {
116
+ spacing: 2,
117
+ children: [
118
+ /*#__PURE__*/ _jsx(Typography, {
119
+ variant: "body2",
120
+ children: copy.does
121
+ }),
122
+ /*#__PURE__*/ _jsx(Typography, {
123
+ variant: "body2",
124
+ color: "text.secondary",
125
+ children: aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)
126
+ })
127
+ ]
128
+ })
129
+ }),
130
+ /*#__PURE__*/ _jsxs(DialogActions, {
131
+ children: [
132
+ /*#__PURE__*/ _jsx(Button, {
133
+ onClick: onClose,
134
+ children: canManageBilling ? 'Not now' : 'Close'
135
+ }),
136
+ canManageBilling ? /*#__PURE__*/ _jsx(Button, {
137
+ variant: "contained",
138
+ href: billingHref,
139
+ onClick: ()=>trackEvent('ai_upsell_clicked', {
140
+ kind
141
+ }),
142
+ children: `Add ${aiAddonName()}`
143
+ }) : null
144
+ ]
145
+ })
146
+ ]
147
+ });
148
+ }
149
+ /**
150
+ * The entry itself on a plan without the add-on: the same button, label and
151
+ * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that
152
+ * draws it renders from the shell's client-side gates alone and asks the
153
+ * server nothing until it is used (AGL-3601).
154
+ */ export function AiUpsellButton({ kind, upgrade, label }) {
155
+ const [open, setOpen] = useState(false);
156
+ return /*#__PURE__*/ _jsxs(_Fragment, {
157
+ children: [
158
+ /*#__PURE__*/ _jsx(Button, {
159
+ size: "small",
160
+ variant: "outlined",
161
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
162
+ path: mdiCreation.path
163
+ }),
164
+ onClick: ()=>setOpen(true),
165
+ children: label != null ? label : aiUpsellLabel(kind)
166
+ }),
167
+ /*#__PURE__*/ _jsx(AiUpsellDialog, {
168
+ kind: kind,
169
+ open: open,
170
+ onClose: ()=>setOpen(false),
171
+ upgrade: upgrade
172
+ })
173
+ ]
174
+ });
175
+ }
176
+
177
+ //# sourceMappingURL=ai-upsell-dialog.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft that is not on your storefront ' +\n 'until you price it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"AAAA;;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,UAAU,QAAQ,0CAAyC;AAEpE,SAASC,WAAW,QAAQ,yBAAwB;AACpD,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SACEC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,QAAQ,QAAwB,QAAO;AAiC3D,kDAAkD,GAClD,OAAO,MAAMC,yBAAyB,iBAAgB;AAEtD,kDAAkD,GAClD,OAAO,MAAMC,iBAA+D;IAC1EC,MAAM;QACJC,OAAO;QACPC,MACE,sFACA,kFACA;IACJ;IACAC,UAAU;QACRF,OAAO;QACPC,MACE,iFACA,wFACA;IACJ;IACAE,QAAQ;QACNH,OAAO;QACPC,MACE,0FACA,wFACA;IACJ;IACAG,MAAM;QACJJ,OAAO;QACPC,MACE,sFACA;IACJ;IACAI,WAAW;QACTL,OAAO;QACPC,MACE,wFACA,uFACA;IACJ;IACAK,UAAU;QACRN,OAAO;QACPC,MACE,kFACA;IACJ;IACAM,OAAO;QACLP,OAAO;QACPC,MACE,yFACA,yFACA;IACJ;IACAO,UAAU;QACRR,OAAO;QACPC,MACE,2FACA,2FACA;IACJ;IACAQ,SAAS;QACPT,OAAO;QACPC,MACE,0FACA,0FACA;IACJ;IACAS,OAAO;QACLV,OAAO;QACPC,MACE,yFACA;IACJ;IACAU,SAAS;QACPX,OAAO;QACPC,MACE,uFACA;IACJ;IACAW,QAAQ;QACNZ,OAAO;QACPC,MACE,wFACA;IACJ;IACAY,SAAS;QACPb,OAAO;QACPc,OAAO;QACPb,MACE,0FACA;IACJ;AACF,EAAC;AAED,uCAAuC,GACvC,OAAO,SAASc,cAAcC,IAAkB;QACvClB;IAAP,QAAOA,6BAAAA,cAAc,CAACkB,KAAK,CAACF,KAAK,YAA1BhB,6BAA8BD;AACvC;AAEA;;;CAGC,GACD,OAAO,SAASoB,cAAcC,gBAAyB,EAAEC,UAAUtB,sBAAsB;IACvF,MAAMuB,QAAQpC;IACd,OAAOkC,mBACH,GAAGC,QAAQ,gBAAgB,EAAEC,MAAM,mCAAmC,CAAC,GACrE,4BACF,GAAGD,QAAQ,gBAAgB,EAAEC,MAAM,wCAAwC,CAAC,GAC1E;AACR;AASA,OAAO,SAASC,eAAe,EAAEL,IAAI,EAAEM,IAAI,EAAEC,OAAO,EAAEC,OAAO,EAAuB;IAClF,MAAMC,OAAO3B,cAAc,CAACkB,KAAK;IACjC,MAAM,EAAEE,gBAAgB,EAAEQ,WAAW,EAAE,GAAGF;IAE1C,yEAAyE;IACzE7B,UAAU;QACR,IAAI2B,MAAMrC,WAAW,mBAAmB;YAAE+B;YAAMW,YAAYT;QAAiB;IAC/E,GAAG;QAACI;QAAMN;QAAME;KAAiB;IAEjC,qBACE,MAAC7B;QAAOiC,MAAMA;QAAMC,SAASA;QAASK,SAAS;QAACC,UAAS;;0BACvD,KAACrC;0BAAaiC,KAAKzB,KAAK;;0BACxB,KAACT;0BACC,cAAA,MAACE;oBAAMqC,SAAS;;sCACd,KAACpC;4BAAWqC,SAAQ;sCAASN,KAAKxB,IAAI;;sCACtC,KAACP;4BAAWqC,SAAQ;4BAAQC,OAAM;sCAC/Bf,cAAcC,kBAAkBF,SAAS,YAAYS,KAAKzB,KAAK,GAAGH;;;;;0BAIzE,MAACP;;kCACC,KAACF;wBAAO6C,SAASV;kCAAUL,mBAAmB,YAAY;;oBACzDA,iCACC,KAAC9B;wBACC2C,SAAQ;wBACRG,MAAMR;wBACNO,SAAS,IAAMhD,WAAW,qBAAqB;gCAAE+B;4BAAK;kCAErD,CAAC,IAAI,EAAEhC,eAAe;yBAEvB;;;;;AAIZ;AAEA;;;;;CAKC,GACD,OAAO,SAASmD,eAAe,EAC7BnB,IAAI,EACJQ,OAAO,EACPV,KAAK,EAMN;IACC,MAAM,CAACQ,MAAMc,QAAQ,GAAGxC,SAAS;IACjC,qBACE;;0BACE,KAACR;gBACCiD,MAAK;gBACLN,SAAQ;gBACRO,yBAAW,KAACnD;oBAAQoD,MAAMrD,YAAYqD,IAAI;;gBAC1CN,SAAS,IAAMG,QAAQ;0BAEtBtB,gBAAAA,QAASC,cAAcC;;0BAE1B,KAACK;gBAAeL,MAAMA;gBAAMM,MAAMA;gBAAMC,SAAS,IAAMa,QAAQ;gBAAQZ,SAASA;;;;AAGtF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
18
+ import { type AiJobSummary } from '../model/ai-jobs.types';
19
+ import type { AssistBuildProposal } from '../model/assist-build';
20
+ /**
21
+ * The one card a build proposed in the chat becomes (AGL-3616): while its
22
+ * job plans, a line saying so; then the job's own plan card, with its one
23
+ * price and Confirm — the same `AiJobPlan` the AI jobs drawer shows, so
24
+ * confirming here and there is one door; then where the build stands, item
25
+ * by item, with AI jobs a click away. The job itself is started by the
26
+ * panel, once, when the turn that proposed it ends; this card only follows
27
+ * it, so re-rendering a stored thread never starts a second build.
28
+ */
29
+ export interface AssistBuildCardProps {
30
+ proposal: AssistBuildProposal;
31
+ /** The job the proposal started, as last seen; `null` while it is being created. */
32
+ job: AiJobSummary | null;
33
+ /** Why the job could not be created or confirmed, when it could not. */
34
+ notice: string | null;
35
+ orgId: string;
36
+ user: MaybeTokenSource;
37
+ staff?: boolean;
38
+ /** The job as it moved, for the thread to keep. */
39
+ onJob: (job: AiJobSummary) => void;
40
+ onNotice: (notice: string | null) => void;
41
+ }
42
+ export declare function AssistBuildCard({ proposal, job, notice, orgId, user, staff, onJob, onNotice, }: AssistBuildCardProps): JSX.Element;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material";
19
+ import { useEffect, useRef, useState } from "react";
20
+ import { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from "../model/ai-build-progress.js";
21
+ import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
22
+ import { followAiJobEvents } from "./ai-job-events.js";
23
+ import { AiJobPlan } from "./ai-job-plan.component.js";
24
+ import { resumeAiJobRequest } from "./ai-job-requests.js";
25
+ import { openAiJobs } from "./ai-jobs-store.js";
26
+ const ITEM_STATES = {
27
+ done: 'built',
28
+ active: 'building',
29
+ waiting: 'waiting',
30
+ failed: 'failed',
31
+ skipped: 'not built'
32
+ };
33
+ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = false, onJob, onNotice }) {
34
+ var _ref, _ref1, _ref2;
35
+ var _job_plan, _job_items;
36
+ const [busy, setBusy] = useState(false);
37
+ const userRef = useRef(user);
38
+ userRef.current = user;
39
+ const onJobRef = useRef(onJob);
40
+ onJobRef.current = onJob;
41
+ // Follow a job that is still planning or building; a plan waiting for its
42
+ // confirmation and a settled job need nothing more from the stream.
43
+ const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review');
44
+ const jobId = (_ref = job == null ? void 0 : job.id) != null ? _ref : null;
45
+ useEffect(()=>{
46
+ if (!following || !jobId) return;
47
+ const controller = new AbortController();
48
+ void followAiJobEvents(()=>userRef.current, orgId, jobId, controller.signal, (next)=>onJobRef.current(next));
49
+ return ()=>controller.abort();
50
+ }, [
51
+ following,
52
+ jobId,
53
+ orgId
54
+ ]);
55
+ const resume = async (target, options)=>{
56
+ setBusy(true);
57
+ onNotice(null);
58
+ const decision = await resumeAiJobRequest(user, orgId, target, options);
59
+ if (decision.job) onJob(decision.job);
60
+ if (decision.error) onNotice(decision.error);
61
+ setBusy(false);
62
+ };
63
+ const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false;
64
+ const outcome = job ? aiBuildOutcomeLine(job) : null;
65
+ return /*#__PURE__*/ _jsxs(Paper, {
66
+ variant: "outlined",
67
+ sx: {
68
+ mt: 1,
69
+ p: 1.5
70
+ },
71
+ "aria-label": "Build plan",
72
+ children: [
73
+ /*#__PURE__*/ _jsx(Typography, {
74
+ variant: "subtitle2",
75
+ children: proposal.summary
76
+ }),
77
+ !job && !notice && /*#__PURE__*/ _jsxs(Stack, {
78
+ direction: "row",
79
+ spacing: 1,
80
+ sx: {
81
+ mt: 0.5,
82
+ alignItems: 'center'
83
+ },
84
+ children: [
85
+ /*#__PURE__*/ _jsx(CircularProgress, {
86
+ size: 14
87
+ }),
88
+ /*#__PURE__*/ _jsx(Typography, {
89
+ variant: "body2",
90
+ color: "text.secondary",
91
+ children: 'Planning what to build on this site…'
92
+ })
93
+ ]
94
+ }),
95
+ notice && /*#__PURE__*/ _jsx(Typography, {
96
+ variant: "body2",
97
+ color: "error",
98
+ sx: {
99
+ mt: 0.5
100
+ },
101
+ role: "alert",
102
+ children: notice
103
+ }),
104
+ job && job.status === 'needs_review' && /*#__PURE__*/ _jsx(AiJobPlan, {
105
+ job: job,
106
+ busy: busy,
107
+ staff: staff,
108
+ user: user,
109
+ onResume: (target, options)=>void resume(target, options)
110
+ }),
111
+ job && following && /*#__PURE__*/ _jsxs(Stack, {
112
+ direction: "row",
113
+ spacing: 1,
114
+ sx: {
115
+ mt: 0.5,
116
+ alignItems: 'center'
117
+ },
118
+ children: [
119
+ /*#__PURE__*/ _jsx(CircularProgress, {
120
+ size: 14
121
+ }),
122
+ /*#__PURE__*/ _jsx(Typography, {
123
+ variant: "body2",
124
+ color: "text.secondary",
125
+ children: ((_job_plan = job.plan) == null ? void 0 : _job_plan.status) === 'confirmed' ? 'Building each part in turn. What it makes is an unpublished draft unless you chose to publish.' : 'Planning what to build on this site…'
126
+ })
127
+ ]
128
+ }),
129
+ job && ((_ref1 = (_job_items = job.items) == null ? void 0 : _job_items.length) != null ? _ref1 : 0) > 0 && job.status !== 'needs_review' && /*#__PURE__*/ _jsx(Box, {
130
+ component: "ul",
131
+ "aria-label": "Items",
132
+ sx: {
133
+ m: 0,
134
+ mt: 0.5,
135
+ pl: 2
136
+ },
137
+ children: aiBuildItemRows(job).map((row)=>/*#__PURE__*/ _jsxs(Typography, {
138
+ component: "li",
139
+ variant: "caption",
140
+ "data-item-state": row.state,
141
+ children: [
142
+ `${row.label} — ${ITEM_STATES[row.state]}`,
143
+ row.detail ? `. ${row.detail}` : ''
144
+ ]
145
+ }, row.slot))
146
+ }),
147
+ settled && /*#__PURE__*/ _jsx(Typography, {
148
+ variant: "body2",
149
+ sx: {
150
+ mt: 0.5
151
+ },
152
+ children: (_ref2 = outcome != null ? outcome : job == null ? void 0 : job.error) != null ? _ref2 : ''
153
+ }),
154
+ job && /*#__PURE__*/ _jsxs(Stack, {
155
+ direction: "row",
156
+ spacing: 1,
157
+ sx: {
158
+ mt: 1
159
+ },
160
+ children: [
161
+ job && aiBuildCanRetry(job) && /*#__PURE__*/ _jsx(Button, {
162
+ size: "small",
163
+ variant: "contained",
164
+ disabled: busy,
165
+ onClick: ()=>void resume(job, {
166
+ retry: 'failed-items'
167
+ }),
168
+ children: 'Try again what failed'
169
+ }),
170
+ /*#__PURE__*/ _jsx(Button, {
171
+ size: "small",
172
+ onClick: ()=>openAiJobs({
173
+ jobId: job.id
174
+ }),
175
+ children: 'Open in AI jobs'
176
+ })
177
+ ]
178
+ })
179
+ ]
180
+ });
181
+ }
182
+
183
+ //# sourceMappingURL=assist-build-card.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-build-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { Box, Button, CircularProgress, Paper, Stack, Typography } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from '../model/ai-build-progress'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport type { AssistBuildProposal } from '../model/assist-build'\nimport { followAiJobEvents } from './ai-job-events'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs } from './ai-jobs-store'\n\n/**\n * The one card a build proposed in the chat becomes (AGL-3616): while its\n * job plans, a line saying so; then the job's own plan card, with its one\n * price and Confirm — the same `AiJobPlan` the AI jobs drawer shows, so\n * confirming here and there is one door; then where the build stands, item\n * by item, with AI jobs a click away. The job itself is started by the\n * panel, once, when the turn that proposed it ends; this card only follows\n * it, so re-rendering a stored thread never starts a second build.\n */\nexport interface AssistBuildCardProps {\n proposal: AssistBuildProposal\n /** The job the proposal started, as last seen; `null` while it is being created. */\n job: AiJobSummary | null\n /** Why the job could not be created or confirmed, when it could not. */\n notice: string | null\n orgId: string\n user: MaybeTokenSource\n staff?: boolean\n /** The job as it moved, for the thread to keep. */\n onJob: (job: AiJobSummary) => void\n onNotice: (notice: string | null) => void\n}\n\nconst ITEM_STATES = { done: 'built', active: 'building', waiting: 'waiting', failed: 'failed', skipped: 'not built' } as const\n\nexport function AssistBuildCard({\n proposal,\n job,\n notice,\n orgId,\n user,\n staff = false,\n onJob,\n onNotice,\n}: AssistBuildCardProps): JSX.Element {\n const [busy, setBusy] = useState(false)\n const userRef = useRef(user)\n userRef.current = user\n const onJobRef = useRef(onJob)\n onJobRef.current = onJob\n\n // Follow a job that is still planning or building; a plan waiting for its\n // confirmation and a settled job need nothing more from the stream.\n const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review')\n const jobId = job?.id ?? null\n useEffect(() => {\n if (!following || !jobId) return\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, (next) => onJobRef.current(next))\n return () => controller.abort()\n }, [following, jobId, orgId])\n\n const resume = async (target: AiJobSummary, options?: { publish?: boolean; retry?: 'failed-items' }) => {\n setBusy(true)\n onNotice(null)\n const decision = await resumeAiJobRequest(user, orgId, target, options)\n if (decision.job) onJob(decision.job)\n if (decision.error) onNotice(decision.error)\n setBusy(false)\n }\n\n const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false\n const outcome = job ? aiBuildOutcomeLine(job) : null\n return (\n <Paper variant=\"outlined\" sx={{ mt: 1, p: 1.5 }} aria-label=\"Build plan\">\n <Typography variant=\"subtitle2\">{proposal.summary}</Typography>\n {!job && !notice && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {notice && (\n <Typography variant=\"body2\" color=\"error\" sx={{ mt: 0.5 }} role=\"alert\">\n {notice}\n </Typography>\n )}\n {job && job.status === 'needs_review' && (\n <AiJobPlan job={job} busy={busy} staff={staff} user={user} onResume={(target, options) => void resume(target, options)} />\n )}\n {job && following && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {job.plan?.status === 'confirmed'\n ? 'Building each part in turn. What it makes is an unpublished draft unless you chose to publish.'\n : 'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {job && (job.items?.length ?? 0) > 0 && job.status !== 'needs_review' && (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATES[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n )}\n {settled && (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {outcome ?? job?.error ?? ''}\n </Typography>\n )}\n {job && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n {job && aiBuildCanRetry(job) && (\n <Button size=\"small\" variant=\"contained\" disabled={busy} onClick={() => void resume(job, { retry: 'failed-items' })}>\n {'Try again what failed'}\n </Button>\n )}\n <Button size=\"small\" onClick={() => openAiJobs({ jobId: job.id })}>\n {'Open in AI jobs'}\n </Button>\n </Stack>\n )}\n </Paper>\n )\n}\n"],"names":["Box","Button","CircularProgress","Paper","Stack","Typography","useEffect","useRef","useState","aiBuildCanRetry","aiBuildItemRows","aiBuildOutcomeLine","AI_JOB_TERMINAL_STATUSES","followAiJobEvents","AiJobPlan","resumeAiJobRequest","openAiJobs","ITEM_STATES","done","active","waiting","failed","skipped","AssistBuildCard","proposal","job","notice","orgId","user","staff","onJob","onNotice","outcome","busy","setBusy","userRef","current","onJobRef","following","Boolean","includes","status","jobId","id","controller","AbortController","signal","next","abort","resume","target","options","decision","error","settled","variant","sx","mt","p","aria-label","summary","direction","spacing","alignItems","size","color","role","onResume","plan","items","length","component","m","pl","map","row","data-item-state","state","label","detail","slot","disabled","onClick","retry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAGA,SAASA,GAAG,EAAEC,MAAM,EAAEC,gBAAgB,EAAEC,KAAK,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACvF,SAASC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACnD,SAASC,eAAe,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,gCAA4B;AACjG,SAASC,wBAAwB,QAA2B,4BAAwB;AAEpF,SAASC,iBAAiB,QAAQ,qBAAiB;AACnD,SAASC,SAAS,QAAQ,6BAAyB;AACnD,SAASC,kBAAkB,QAAQ,uBAAmB;AACtD,SAASC,UAAU,QAAQ,qBAAiB;AAyB5C,MAAMC,cAAc;IAAEC,MAAM;IAASC,QAAQ;IAAYC,SAAS;IAAWC,QAAQ;IAAUC,SAAS;AAAY;AAEpH,OAAO,SAASC,gBAAgB,EAC9BC,QAAQ,EACRC,GAAG,EACHC,MAAM,EACNC,KAAK,EACLC,IAAI,EACJC,QAAQ,KAAK,EACbC,KAAK,EACLC,QAAQ,EACa;qBAsEZC;QAlBEP,WAMEA;IAzDb,MAAM,CAACQ,MAAMC,QAAQ,GAAG1B,SAAS;IACjC,MAAM2B,UAAU5B,OAAOqB;IACvBO,QAAQC,OAAO,GAAGR;IAClB,MAAMS,WAAW9B,OAAOuB;IACxBO,SAASD,OAAO,GAAGN;IAEnB,0EAA0E;IAC1E,oEAAoE;IACpE,MAAMQ,YAAYC,QAAQd,OAAO,CAACb,yBAAyB4B,QAAQ,CAACf,IAAIgB,MAAM,KAAKhB,IAAIgB,MAAM,KAAK;IAClG,MAAMC,gBAAQjB,uBAAAA,IAAKkB,EAAE,mBAAI;IACzBrC,UAAU;QACR,IAAI,CAACgC,aAAa,CAACI,OAAO;QAC1B,MAAME,aAAa,IAAIC;QACvB,KAAKhC,kBAAkB,IAAMsB,QAAQC,OAAO,EAAET,OAAOe,OAAOE,WAAWE,MAAM,EAAE,CAACC,OAASV,SAASD,OAAO,CAACW;QAC1G,OAAO,IAAMH,WAAWI,KAAK;IAC/B,GAAG;QAACV;QAAWI;QAAOf;KAAM;IAE5B,MAAMsB,SAAS,OAAOC,QAAsBC;QAC1CjB,QAAQ;QACRH,SAAS;QACT,MAAMqB,WAAW,MAAMrC,mBAAmBa,MAAMD,OAAOuB,QAAQC;QAC/D,IAAIC,SAAS3B,GAAG,EAAEK,MAAMsB,SAAS3B,GAAG;QACpC,IAAI2B,SAASC,KAAK,EAAEtB,SAASqB,SAASC,KAAK;QAC3CnB,QAAQ;IACV;IAEA,MAAMoB,UAAU7B,MAAMb,yBAAyB4B,QAAQ,CAACf,IAAIgB,MAAM,IAAI;IACtE,MAAMT,UAAUP,MAAMd,mBAAmBc,OAAO;IAChD,qBACE,MAACtB;QAAMoD,SAAQ;QAAWC,IAAI;YAAEC,IAAI;YAAGC,GAAG;QAAI;QAAGC,cAAW;;0BAC1D,KAACtD;gBAAWkD,SAAQ;0BAAa/B,SAASoC,OAAO;;YAChD,CAACnC,OAAO,CAACC,wBACR,MAACtB;gBAAMyD,WAAU;gBAAMC,SAAS;gBAAGN,IAAI;oBAAEC,IAAI;oBAAKM,YAAY;gBAAS;;kCACrE,KAAC7D;wBAAiB8D,MAAM;;kCACxB,KAAC3D;wBAAWkD,SAAQ;wBAAQU,OAAM;kCAC/B;;;;YAINvC,wBACC,KAACrB;gBAAWkD,SAAQ;gBAAQU,OAAM;gBAAQT,IAAI;oBAAEC,IAAI;gBAAI;gBAAGS,MAAK;0BAC7DxC;;YAGJD,OAAOA,IAAIgB,MAAM,KAAK,gCACrB,KAAC3B;gBAAUW,KAAKA;gBAAKQ,MAAMA;gBAAMJ,OAAOA;gBAAOD,MAAMA;gBAAMuC,UAAU,CAACjB,QAAQC,UAAY,KAAKF,OAAOC,QAAQC;;YAE/G1B,OAAOa,2BACN,MAAClC;gBAAMyD,WAAU;gBAAMC,SAAS;gBAAGN,IAAI;oBAAEC,IAAI;oBAAKM,YAAY;gBAAS;;kCACrE,KAAC7D;wBAAiB8D,MAAM;;kCACxB,KAAC3D;wBAAWkD,SAAQ;wBAAQU,OAAM;kCAC/BxC,EAAAA,YAAAA,IAAI2C,IAAI,qBAAR3C,UAAUgB,MAAM,MAAK,cAClB,mGACA;;;;YAIThB,OAAO,WAACA,aAAAA,IAAI4C,KAAK,qBAAT5C,WAAW6C,MAAM,oBAAI,KAAK,KAAK7C,IAAIgB,MAAM,KAAK,gCACrD,KAACzC;gBAAIuE,WAAU;gBAAKZ,cAAW;gBAAQH,IAAI;oBAAEgB,GAAG;oBAAGf,IAAI;oBAAKgB,IAAI;gBAAE;0BAC/D/D,gBAAgBe,KAAKiD,GAAG,CAAC,CAACC,oBACzB,MAACtE;wBAA0BkE,WAAU;wBAAKhB,SAAQ;wBAAUqB,mBAAiBD,IAAIE,KAAK;;4BACnF,GAAGF,IAAIG,KAAK,CAAC,GAAG,EAAE7D,WAAW,CAAC0D,IAAIE,KAAK,CAAC,EAAE;4BAC1CF,IAAII,MAAM,GAAG,CAAC,EAAE,EAAEJ,IAAII,MAAM,EAAE,GAAG;;uBAFnBJ,IAAIK,IAAI;;YAO9B1B,yBACC,KAACjD;gBAAWkD,SAAQ;gBAAQC,IAAI;oBAAEC,IAAI;gBAAI;2BACvCzB,QAAAA,kBAAAA,UAAWP,uBAAAA,IAAK4B,KAAK,YAArBrB,QAAyB;;YAG7BP,qBACC,MAACrB;gBAAMyD,WAAU;gBAAMC,SAAS;gBAAGN,IAAI;oBAAEC,IAAI;gBAAE;;oBAC5ChC,OAAOhB,gBAAgBgB,sBACtB,KAACxB;wBAAO+D,MAAK;wBAAQT,SAAQ;wBAAY0B,UAAUhD;wBAAMiD,SAAS,IAAM,KAAKjC,OAAOxB,KAAK;gCAAE0D,OAAO;4BAAe;kCAC9G;;kCAGL,KAAClF;wBAAO+D,MAAK;wBAAQkB,SAAS,IAAMlE,WAAW;gCAAE0B,OAAOjB,IAAIkB,EAAE;4BAAC;kCAC5D;;;;;;AAMb"}
@@ -39,6 +39,8 @@ export interface AssistEditCanvasNodeLike {
39
39
  name?: string;
40
40
  props?: unknown;
41
41
  sx?: unknown;
42
+ /** The element's own interactions (AGL-3603), as the canvas holds them. */
43
+ interactions?: unknown;
42
44
  }
43
45
  /**
44
46
  * The slice of the besigner canvas the edit rung reads and drives. Every
@@ -17,6 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { screenSeoStageKey } from "@aglyn/aglyn/app-utils/screen-seo-fields";
18
18
  import { NODE_HIDE_IF_PROP, replaceSubtreeWithInstance, reusableComponentDefinitionFrom } from "@aglyn/aglyn/app-utils/compose-reusable-components";
19
19
  import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
20
+ import { NODE_MAX_INTERACTIONS, upsertNodeInteraction } from "@aglyn/aglyn/app-utils/node-interactions";
20
21
  import { stageEditorSessionFields } from "@aglyn/aglyn/plugin-manager/editor-sessions";
21
22
  import { ASSIST_EDIT_CONTEXT_MAX_NODES, ASSIST_EDIT_CONTEXT_MAX_PROPS, ASSIST_EDIT_OUTLINE_TEXT_CHARS, ASSIST_EDIT_SELECTED_TEXT_CHARS, assistEditOpCounts } from "../model/assist-edit.js";
22
23
  const UNDESCRIBED_PROP = /^(html|sx|style|className|dangerouslySetInnerHTML|on[A-Z].*)$/;
@@ -268,7 +269,7 @@ function describeSx(raw) {
268
269
  brief: true
269
270
  } : {}, model ? {
270
271
  like: model
271
- } : {});
272
+ } : {}, isSelected ? summarizeInteractions(node.interactions) : {});
272
273
  });
273
274
  return {
274
275
  selectedId: (selected == null ? void 0 : selected.$id) && seen.has(selected.$id) ? selected.$id : null,
@@ -276,6 +277,27 @@ function describeSx(raw) {
276
277
  nodes
277
278
  };
278
279
  }
280
+ /**
281
+ * The selected element's interactions as the outline carries them
282
+ * (AGL-3603): name, event and the kinds of its steps — never a selector.
283
+ */ function summarizeInteractions(raw) {
284
+ if (!Array.isArray(raw) || !raw.length) return {};
285
+ const interactions = raw.filter(isRecord).slice(0, NODE_MAX_INTERACTIONS).map((entry)=>{
286
+ var _entry_name, _entry_trigger_event;
287
+ return {
288
+ name: String((_entry_name = entry['name']) != null ? _entry_name : ''),
289
+ event: String(isRecord(entry['trigger']) ? (_entry_trigger_event = entry['trigger']['event']) != null ? _entry_trigger_event : '' : ''),
290
+ steps: (Array.isArray(entry['steps']) ? entry['steps'] : []).filter(isRecord).map((step)=>{
291
+ var _step_type;
292
+ return String((_step_type = step['type']) != null ? _step_type : '');
293
+ }),
294
+ enabled: entry['enabled'] !== false
295
+ };
296
+ }).filter((entry)=>entry.event);
297
+ return interactions.length ? {
298
+ interactions
299
+ } : {};
300
+ }
279
301
  /** Ids of the subtree at `rootId`, the root included, through child lists only. */ function subtreeIds(canvas, rootId) {
280
302
  var _ref;
281
303
  var _canvas_getNode;
@@ -373,6 +395,24 @@ const STALE = {
373
395
  }
374
396
  break;
375
397
  }
398
+ case 'addInteraction':
399
+ {
400
+ const node = canvas.getNode(op.nodeId);
401
+ if (!node || node.componentId !== op.componentId) return STALE;
402
+ if (Array.isArray(node.interactions) && node.interactions.length >= NODE_MAX_INTERACTIONS) {
403
+ return {
404
+ ok: false,
405
+ reason: 'refused',
406
+ message: `That element already does ${NODE_MAX_INTERACTIONS} things, the most one can. Remove one in Interactions first.`
407
+ };
408
+ }
409
+ // Every element a step acts on is still there, as it was.
410
+ for (const target of op.targets){
411
+ const live = canvas.getNode(target.nodeId);
412
+ if (!live || live.componentId !== target.componentId) return STALE;
413
+ }
414
+ break;
415
+ }
376
416
  default:
377
417
  {
378
418
  const node = canvas.getNode(op.nodeId);
@@ -463,6 +503,21 @@ function applyOne(canvas, op) {
463
503
  name: op.name
464
504
  });
465
505
  return;
506
+ case 'addInteraction':
507
+ {
508
+ // Added beside what the element does already, never in place of it:
509
+ // an id the element already uses is re-minted, so nothing is replaced.
510
+ const node = live(canvas, op.nodeId);
511
+ const current = Array.isArray(node.interactions) ? node.interactions : [];
512
+ let id = op.interaction.id;
513
+ while(current.some((entry)=>entry.id === id))id = `${op.interaction.id}${current.length}x`;
514
+ canvas.updateNodeFields(node, {
515
+ interactions: upsertNodeInteraction(current, _extends({}, op.interaction, {
516
+ id
517
+ }))
518
+ });
519
+ return;
520
+ }
466
521
  case 'insertSubtree':
467
522
  var _op_index1;
468
523
  canvas.addNodeFromNested(nestSubtree(op), live(canvas, op.parentId), (_op_index1 = op.index) != null ? _op_index1 : NaN);