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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) 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-assist-provider-on-host.component.d.ts +66 -0
  5. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  6. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  7. package/src/lib/components/ai-assist-provider.component.js +9 -10
  8. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  9. package/src/lib/components/ai-job-follow.component.js +1 -0
  10. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  11. package/src/lib/components/ai-job-links.js +13 -0
  12. package/src/lib/components/ai-job-links.js.map +1 -1
  13. package/src/lib/components/ai-job-plan.component.js +3 -1
  14. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  15. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  16. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  17. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  18. package/src/lib/components/ai-media-create.component.js +107 -55
  19. package/src/lib/components/ai-media-create.component.js.map +1 -1
  20. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  21. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  22. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  23. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  24. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  25. package/src/lib/components/ai-site-build-page.component.js +20 -8
  26. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  27. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  28. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  29. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  30. package/src/lib/components/ai-site-start-card.component.js +80 -77
  31. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  32. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  33. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  34. package/src/lib/components/ai-usage-strip.component.js +1 -0
  35. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  36. package/src/lib/components/assist-panel.component.d.ts +10 -0
  37. package/src/lib/components/assist-panel.component.js +119 -9
  38. package/src/lib/components/assist-panel.component.js.map +1 -1
  39. package/src/lib/components/assist-signals-page.component.js +2 -0
  40. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  41. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  42. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  43. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  44. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  45. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  46. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  47. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  48. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  49. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  50. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  51. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  52. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  53. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  54. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  55. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  56. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  57. package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
  58. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  59. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  60. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  61. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  62. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  63. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  64. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  65. package/src/lib/jobs/ai-job-build-step.js +14 -1
  66. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  68. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  69. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  70. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  71. package/src/lib/jobs/ai-job-generation.js +13 -2
  72. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  73. package/src/lib/jobs/ai-job-insight-step.js +29 -5
  74. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  75. package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
  76. package/src/lib/jobs/ai-job-layout-language.js +200 -0
  77. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  78. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  79. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  80. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  81. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  82. package/src/lib/jobs/ai-job-layout-step.js +107 -6
  83. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  85. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  86. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  87. package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
  88. package/src/lib/jobs/ai-job-page-language.js +418 -0
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  90. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  91. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  92. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  93. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  94. package/src/lib/jobs/ai-job-page-step.js +115 -14
  95. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
  97. package/src/lib/jobs/ai-job-plan-step.js +62 -22
  98. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  99. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  100. package/src/lib/jobs/ai-job-site-content.js +443 -0
  101. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  102. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  103. package/src/lib/jobs/ai-job-site-look.js +322 -0
  104. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  105. package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
  106. package/src/lib/jobs/ai-job-site-step.js +183 -27
  107. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-workflow-step.js +2 -2
  109. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  110. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  111. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  112. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  113. package/src/lib/jobs/ai-jobs.d.ts +1 -1
  114. package/src/lib/jobs/ai-jobs.js +17 -8
  115. package/src/lib/jobs/ai-jobs.js.map +1 -1
  116. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  117. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  118. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  119. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  120. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  121. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  122. package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
  123. package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
  124. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  125. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  126. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  127. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  128. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  129. package/src/lib/layout-language/ai-layout-frame.js +458 -0
  130. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  131. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  132. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  133. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  134. package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
  135. package/src/lib/layout-language/ai-layout-language.js +529 -0
  136. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  137. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  138. package/src/lib/layout-language/ai-layout-links.js +109 -0
  139. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  140. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  141. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  142. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  143. package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
  144. package/src/lib/layout-language/ai-layout-store.js +75 -0
  145. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  146. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  147. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  148. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  149. package/src/lib/lazy-widget.d.ts +45 -0
  150. package/src/lib/lazy-widget.js +53 -0
  151. package/src/lib/lazy-widget.js.map +1 -0
  152. package/src/lib/model/ai-build-job.js +2 -1
  153. package/src/lib/model/ai-build-job.js.map +1 -1
  154. package/src/lib/model/ai-build-progress.d.ts +20 -3
  155. package/src/lib/model/ai-build-progress.js +52 -7
  156. package/src/lib/model/ai-build-progress.js.map +1 -1
  157. package/src/lib/model/ai-edit-job.d.ts +109 -0
  158. package/src/lib/model/ai-edit-job.js +157 -0
  159. package/src/lib/model/ai-edit-job.js.map +1 -0
  160. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  161. package/src/lib/model/ai-host-publish-context.js +104 -0
  162. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  163. package/src/lib/model/ai-job-activity.d.ts +22 -0
  164. package/src/lib/model/ai-job-activity.js +23 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  167. package/src/lib/model/ai-job-failure-copy.js +56 -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 -1
  170. package/src/lib/model/ai-job-notice.js +31 -9
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +22 -1
  173. package/src/lib/model/ai-jobs.types.js.map +1 -1
  174. package/src/lib/model/ai-logic-job.d.ts +18 -1
  175. package/src/lib/model/ai-logic-job.js +24 -3
  176. package/src/lib/model/ai-logic-job.js.map +1 -1
  177. package/src/lib/model/ai-media-credits.d.ts +8 -4
  178. package/src/lib/model/ai-media-credits.js +11 -4
  179. package/src/lib/model/ai-media-credits.js.map +1 -1
  180. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  181. package/src/lib/model/ai-media-kinds.js +98 -0
  182. package/src/lib/model/ai-media-kinds.js.map +1 -0
  183. package/src/lib/model/ai-site-build-progress.d.ts +9 -1
  184. package/src/lib/model/ai-site-build-progress.js +54 -14
  185. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  186. package/src/lib/model/ai-site-context.d.ts +111 -0
  187. package/src/lib/model/ai-site-context.js +222 -0
  188. package/src/lib/model/ai-site-context.js.map +1 -0
  189. package/src/lib/model/ai-site-job.d.ts +18 -2
  190. package/src/lib/model/ai-site-job.js +21 -5
  191. package/src/lib/model/ai-site-job.js.map +1 -1
  192. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  193. package/src/lib/model/ai-site-kinds.js +1942 -0
  194. package/src/lib/model/ai-site-kinds.js.map +1 -0
  195. package/src/lib/model/ai-site-look.d.ts +150 -0
  196. package/src/lib/model/ai-site-look.js +1236 -0
  197. package/src/lib/model/ai-site-look.js.map +1 -0
  198. package/src/lib/model/ai-site-memory.d.ts +80 -0
  199. package/src/lib/model/ai-site-memory.js +171 -0
  200. package/src/lib/model/ai-site-memory.js.map +1 -0
  201. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  202. package/src/lib/model/ai-site-start.d.ts +15 -3
  203. package/src/lib/model/ai-site-start.js +21 -4
  204. package/src/lib/model/ai-site-start.js.map +1 -1
  205. package/src/lib/model/assist-follow-up.d.ts +92 -0
  206. package/src/lib/model/assist-follow-up.js +128 -0
  207. package/src/lib/model/assist-follow-up.js.map +1 -0
  208. package/src/lib/plugin-config.js +2 -0
  209. package/src/lib/plugin-config.js.map +1 -1
  210. package/src/lib/plugin.js +99 -48
  211. package/src/lib/plugin.js.map +1 -1
  212. package/src/lib/providers/anthropic.d.ts +9 -1
  213. package/src/lib/providers/anthropic.js +143 -9
  214. package/src/lib/providers/anthropic.js.map +1 -1
  215. package/src/lib/providers/catalog.d.ts +38 -8
  216. package/src/lib/providers/catalog.js +106 -14
  217. package/src/lib/providers/catalog.js.map +1 -1
  218. package/src/lib/providers/contract.d.ts +29 -0
  219. package/src/lib/providers/contract.js.map +1 -1
  220. package/src/lib/providers/image-contract.d.ts +22 -1
  221. package/src/lib/providers/image-contract.js +40 -1
  222. package/src/lib/providers/image-contract.js.map +1 -1
  223. package/src/lib/providers/image-endpoints.d.ts +24 -0
  224. package/src/lib/providers/image-endpoints.js +28 -0
  225. package/src/lib/providers/image-endpoints.js.map +1 -0
  226. package/src/lib/providers/model-choice.js +23 -2
  227. package/src/lib/providers/model-choice.js.map +1 -1
  228. package/src/lib/providers/routing.js +18 -0
  229. package/src/lib/providers/routing.js.map +1 -1
  230. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  231. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  232. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  233. package/src/lib/providers/vertex-image.d.ts +13 -27
  234. package/src/lib/providers/vertex-image.js +15 -42
  235. package/src/lib/providers/vertex-image.js.map +1 -1
  236. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  237. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  238. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  239. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  240. package/src/lib/runtime/ai-dev-env.js +72 -0
  241. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  242. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  243. package/src/lib/runtime/ai-dev-replay.js +313 -0
  244. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  245. package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
  246. package/src/lib/runtime/ai-doctrine-validators.js +436 -4
  247. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  248. package/src/lib/runtime/ai-doctrine.js +16 -3
  249. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  250. package/src/lib/runtime/ai-eval-live.js +4 -0
  251. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  252. package/src/lib/runtime/ai-eval.d.ts +13 -1
  253. package/src/lib/runtime/ai-eval.js +84 -1
  254. package/src/lib/runtime/ai-eval.js.map +1 -1
  255. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  256. package/src/lib/runtime/ai-icon-library.js +254 -0
  257. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  258. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  259. package/src/lib/runtime/ai-live-batch.js +89 -0
  260. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  261. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  262. package/src/lib/runtime/ai-node-tree.js +30 -4
  263. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  264. package/src/lib/runtime/ai-palette.d.ts +6 -0
  265. package/src/lib/runtime/ai-palette.generated.js +70 -0
  266. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  267. package/src/lib/runtime/ai-palette.js.map +1 -1
  268. package/src/lib/runtime/ai-runtime.js +46 -2
  269. package/src/lib/runtime/ai-runtime.js.map +1 -1
  270. package/src/lib/runtime/site-context.d.ts +58 -0
  271. package/src/lib/runtime/site-context.js +127 -0
  272. package/src/lib/runtime/site-context.js.map +1 -0
  273. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  274. package/src/lib/server/ai-admin-conversations.js +197 -0
  275. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  276. package/src/lib/server/ai-media-image.d.ts +5 -5
  277. package/src/lib/server/ai-media-image.js +25 -11
  278. package/src/lib/server/ai-media-image.js.map +1 -1
  279. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  280. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  281. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  282. package/src/lib/server/assist-chat.d.ts +11 -0
  283. package/src/lib/server/assist-chat.js +57 -18
  284. package/src/lib/server/assist-chat.js.map +1 -1
  285. package/src/lib/server/assist-edit-applied.js +16 -3
  286. package/src/lib/server/assist-edit-applied.js.map +1 -1
  287. package/src/lib/server/assist-view-context.d.ts +19 -1
  288. package/src/lib/server/assist-view-context.js +38 -1
  289. package/src/lib/server/assist-view-context.js.map +1 -1
  290. package/src/lib/server.js +10 -0
  291. package/src/lib/server.js.map +1 -1
  292. package/src/lib/subprocessors.js +17 -1
  293. package/src/lib/subprocessors.js.map +1 -1
  294. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  295. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  296. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  297. package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
  298. package/src/lib/tools/ai-workflow-tool.js +16 -14
  299. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  300. package/src/lib/usage/assist-usage.d.ts +3 -0
  301. package/src/lib/usage/assist-usage.js +4 -2
  302. package/src/lib/usage/assist-usage.js.map +1 -1
  303. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  304. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  305. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -0,0 +1,346 @@
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 { PLATFORM_BRAND_NAME } from "@aglyn/aglyn";
19
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
20
+ import { CardDisplay } from "@aglyn/shared-ui-jsx";
21
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
22
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
23
+ import { useUser } from "@aglyn/tenant-feature-instance";
24
+ import { Alert, Box, Button, Chip, Divider, Stack, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
25
+ import { useCallback, useRef, useState } from "react";
26
+ import { STAFF_AI_CONVERSATIONS_PAGE } from "../usage/staff-org-ai-conversations.js";
27
+ /**
28
+ * What an organization asked Aglyn AI and what it answered (AGL-3675): the
29
+ * Assist chat's questions and answers, and each AI job's brief and result,
30
+ * newest first, on the staff org page.
31
+ *
32
+ * Nothing is read until staff ask: the route writes an audited access row
33
+ * for every page it returns, so a card that loaded with the page would log
34
+ * reads nobody chose to make.
35
+ */ const when = (iso)=>iso ? new Date(iso).toLocaleString() : 'Unknown time';
36
+ const who = (person)=>{
37
+ var _ref, _person_email;
38
+ return (_ref = (_person_email = person.email) != null ? _person_email : person.name) != null ? _ref : person.uid ? `uid ${person.uid}` : 'Unknown member';
39
+ };
40
+ const money = (value)=>value === null ? null : `$${value.toFixed(value < 1 ? 4 : 2)}`;
41
+ /** A block of what someone typed or what came back, kept as written. */ const Words = ({ label, children })=>/*#__PURE__*/ _jsxs(Box, {
42
+ children: [
43
+ /*#__PURE__*/ _jsx(Typography, {
44
+ variant: "overline",
45
+ color: "text.secondary",
46
+ children: label
47
+ }),
48
+ /*#__PURE__*/ _jsx(Typography, {
49
+ variant: "body2",
50
+ sx: {
51
+ whiteSpace: 'pre-wrap',
52
+ overflowWrap: 'anywhere'
53
+ },
54
+ children: children || '—'
55
+ })
56
+ ]
57
+ });
58
+ const Meta = ({ children })=>/*#__PURE__*/ _jsx(Stack, {
59
+ direction: "row",
60
+ useFlexGap: true,
61
+ sx: {
62
+ flexWrap: 'wrap',
63
+ gap: 1,
64
+ alignItems: 'center'
65
+ },
66
+ children: children
67
+ });
68
+ const AssistRow = ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
69
+ spacing: 1,
70
+ children: [
71
+ /*#__PURE__*/ _jsxs(Meta, {
72
+ children: [
73
+ /*#__PURE__*/ _jsx(Typography, {
74
+ variant: "body2",
75
+ sx: {
76
+ fontWeight: 600
77
+ },
78
+ children: who(row.by)
79
+ }),
80
+ /*#__PURE__*/ _jsx(Typography, {
81
+ variant: "body2",
82
+ color: "text.secondary",
83
+ children: when(row.at)
84
+ }),
85
+ row.route ? /*#__PURE__*/ _jsx(Chip, {
86
+ size: "small",
87
+ variant: "outlined",
88
+ label: row.route
89
+ }) : null,
90
+ row.model ? /*#__PURE__*/ _jsx(Chip, {
91
+ size: "small",
92
+ variant: "outlined",
93
+ label: row.model
94
+ }) : null,
95
+ money(row.estCostUsd) ? /*#__PURE__*/ _jsx(Chip, {
96
+ size: "small",
97
+ variant: "outlined",
98
+ label: money(row.estCostUsd)
99
+ }) : null,
100
+ row.feedback ? /*#__PURE__*/ _jsx(Chip, {
101
+ size: "small",
102
+ color: row.feedback === 'up' ? 'success' : 'warning',
103
+ label: `Rated ${row.feedback}`
104
+ }) : null
105
+ ]
106
+ }),
107
+ /*#__PURE__*/ _jsx(Words, {
108
+ label: "Question",
109
+ children: row.question
110
+ }),
111
+ /*#__PURE__*/ _jsx(Words, {
112
+ label: "Answer",
113
+ children: row.answer
114
+ })
115
+ ]
116
+ });
117
+ const Result = ({ result })=>{
118
+ switch(result.kind){
119
+ case 'insight':
120
+ return /*#__PURE__*/ _jsx(Words, {
121
+ label: "Answer",
122
+ children: [
123
+ ...result.insights,
124
+ ...result.gap ? [
125
+ `Not answered: ${result.gap}`
126
+ ] : []
127
+ ].join('\n\n')
128
+ });
129
+ case 'crm-record':
130
+ return /*#__PURE__*/ _jsx(Words, {
131
+ label: "Summary",
132
+ children: [
133
+ result.summary,
134
+ result.standing,
135
+ result.nextStep ? `Next step: ${result.nextStep}` : null
136
+ ].filter(Boolean).join('\n\n')
137
+ });
138
+ case 'crm-email':
139
+ return /*#__PURE__*/ _jsx(Words, {
140
+ label: "Email draft",
141
+ children: `Subject: ${result.subject}\n\n${result.body}`
142
+ });
143
+ case 'crm-mapping':
144
+ return /*#__PURE__*/ _jsx(Words, {
145
+ label: "Column matches",
146
+ children: result.matches.join('\n')
147
+ });
148
+ }
149
+ };
150
+ const JobRow = ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
151
+ spacing: 1,
152
+ children: [
153
+ /*#__PURE__*/ _jsxs(Meta, {
154
+ children: [
155
+ /*#__PURE__*/ _jsx(Typography, {
156
+ variant: "body2",
157
+ sx: {
158
+ fontWeight: 600
159
+ },
160
+ children: who(row.by)
161
+ }),
162
+ /*#__PURE__*/ _jsx(Typography, {
163
+ variant: "body2",
164
+ color: "text.secondary",
165
+ children: when(row.at)
166
+ }),
167
+ /*#__PURE__*/ _jsx(Chip, {
168
+ size: "small",
169
+ variant: "outlined",
170
+ label: row.kind
171
+ }),
172
+ /*#__PURE__*/ _jsx(Chip, {
173
+ size: "small",
174
+ color: row.status === 'done' ? 'success' : row.status === 'failed' ? 'error' : 'default',
175
+ label: row.status
176
+ }),
177
+ /*#__PURE__*/ _jsx(Chip, {
178
+ size: "small",
179
+ variant: "outlined",
180
+ label: `${row.credits.toLocaleString()} credits`
181
+ })
182
+ ]
183
+ }),
184
+ /*#__PURE__*/ _jsx(Words, {
185
+ label: "Brief",
186
+ children: row.brief
187
+ }),
188
+ row.result ? /*#__PURE__*/ _jsx(Result, {
189
+ result: row.result
190
+ }) : null,
191
+ row.outputs.length ? /*#__PURE__*/ _jsx(Words, {
192
+ label: "Made",
193
+ children: row.outputs.map((output)=>[
194
+ output.label,
195
+ output.note
196
+ ].filter(Boolean).join(' — ')).join('\n')
197
+ }) : null,
198
+ row.error ? /*#__PURE__*/ _jsx(Alert, {
199
+ severity: "error",
200
+ children: row.error
201
+ }) : null
202
+ ]
203
+ });
204
+ const StaffOrgAiConversations = ({ orgId })=>{
205
+ const { data: user } = useUser();
206
+ const userRef = useRef(user);
207
+ userRef.current = user;
208
+ const [kind, setKind] = useState(null);
209
+ const [rows, setRows] = useState([]);
210
+ // The cursor that opens each page: `cursors[page]` is the `after` its
211
+ // request carries, so Previous re-reads a page rather than keeping every
212
+ // page read so far in memory.
213
+ const [cursors, setCursors] = useState([
214
+ null
215
+ ]);
216
+ const [page, setPage] = useState(0);
217
+ const [next, setNext] = useState(null);
218
+ const [loading, setLoading] = useState(false);
219
+ const [error, setError] = useState(null);
220
+ const load = useCallback(async (target, targetPage, after)=>{
221
+ const current = userRef.current;
222
+ if (!current || !orgId) return;
223
+ setLoading(true);
224
+ setError(null);
225
+ try {
226
+ const params = new URLSearchParams({
227
+ orgId,
228
+ kind: target
229
+ });
230
+ if (after) params.set('after', after);
231
+ const response = await authorizedFetch(current, `/api/ai/admin/conversations?${params}`);
232
+ const payload = await response.json().catch(()=>null);
233
+ if (!response.ok || !payload) {
234
+ var _ref;
235
+ setError((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'The conversations could not be read.');
236
+ return;
237
+ }
238
+ setRows(payload.rows);
239
+ setPage(targetPage);
240
+ setNext(payload.next);
241
+ setCursors((existing)=>{
242
+ const kept = existing.slice(0, targetPage + 1);
243
+ return payload.next ? [
244
+ ...kept,
245
+ payload.next
246
+ ] : kept;
247
+ });
248
+ } catch (unused) {
249
+ setError('The conversations could not be read.');
250
+ } finally{
251
+ setLoading(false);
252
+ }
253
+ }, [
254
+ orgId
255
+ ]);
256
+ const show = useCallback((target)=>{
257
+ setKind(target);
258
+ setRows([]);
259
+ setNext(null);
260
+ setPage(0);
261
+ setCursors([
262
+ null
263
+ ]);
264
+ void load(target, 0, null);
265
+ }, [
266
+ load
267
+ ]);
268
+ return /*#__PURE__*/ _jsx(CardDisplay, {
269
+ header: "AI conversations",
270
+ help: pluginDocsHelp('aiMonitoring', {
271
+ anchor: '#ai-conversations',
272
+ excerpt: `What this organization asked ${PLATFORM_BRAND_NAME} AI and what it answered: Assist chat questions and answers, and each AI job’s brief and result. Each page staff read is recorded in the audit log, without the text.`
273
+ }),
274
+ HeaderProps: {
275
+ action: kind === null ? /*#__PURE__*/ _jsx(Button, {
276
+ size: "small",
277
+ onClick: ()=>show('assist'),
278
+ children: 'Show conversations'
279
+ }) : /*#__PURE__*/ _jsxs(ToggleButtonGroup, {
280
+ size: "small",
281
+ exclusive: true,
282
+ value: kind,
283
+ onChange: (_, value)=>value && show(value),
284
+ "aria-label": "Which conversations",
285
+ children: [
286
+ /*#__PURE__*/ _jsx(ToggleButton, {
287
+ value: "assist",
288
+ children: 'Assist chat'
289
+ }),
290
+ /*#__PURE__*/ _jsx(ToggleButton, {
291
+ value: "jobs",
292
+ children: 'AI jobs'
293
+ })
294
+ ]
295
+ })
296
+ },
297
+ contentGutterX: true,
298
+ contentGutterY: true,
299
+ children: kind === null ? /*#__PURE__*/ _jsx(Typography, {
300
+ variant: "body2",
301
+ color: "text.secondary",
302
+ children: `What people in this organization typed to ${PLATFORM_BRAND_NAME} AI, and what it answered. Opening it is recorded in the audit log.`
303
+ }) : /*#__PURE__*/ _jsxs(Stack, {
304
+ spacing: 2,
305
+ divider: /*#__PURE__*/ _jsx(Divider, {
306
+ flexItem: true
307
+ }),
308
+ children: [
309
+ error ? /*#__PURE__*/ _jsx(Alert, {
310
+ severity: "warning",
311
+ children: error
312
+ }) : null,
313
+ !loading && !error && !rows.length ? /*#__PURE__*/ _jsx(Typography, {
314
+ variant: "body2",
315
+ color: "text.secondary",
316
+ children: kind === 'assist' ? 'No Assist chat in the last 180 days.' : 'No AI jobs yet.'
317
+ }) : null,
318
+ rows.map((row)=>kind === 'assist' ? /*#__PURE__*/ _jsx(AssistRow, {
319
+ row: row
320
+ }, row.id) : /*#__PURE__*/ _jsx(JobRow, {
321
+ row: row
322
+ }, row.id)),
323
+ loading ? /*#__PURE__*/ _jsx(Typography, {
324
+ variant: "body2",
325
+ color: "text.secondary",
326
+ children: 'Loading…'
327
+ }) : null,
328
+ rows.length || page > 0 ? /*#__PURE__*/ _jsx(ListPagination, {
329
+ page: page,
330
+ pageSize: STAFF_AI_CONVERSATIONS_PAGE,
331
+ rowCount: rows.length,
332
+ hasMore: Boolean(next),
333
+ disabled: loading,
334
+ onPageChange: (target)=>{
335
+ var _cursors_target;
336
+ return void load(kind, target, (_cursors_target = cursors[target]) != null ? _cursors_target : null);
337
+ }
338
+ }) : null
339
+ ]
340
+ })
341
+ });
342
+ };
343
+ StaffOrgAiConversations.displayName = 'StaffOrgAiConversations';
344
+ export default StaffOrgAiConversations;
345
+
346
+ //# sourceMappingURL=staff-org-ai-conversations.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-org-ai-conversations.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 { PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n Divider,\n Stack,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useCallback, useRef, useState, type ReactNode } from 'react'\nimport {\n STAFF_AI_CONVERSATIONS_PAGE,\n type StaffAiAssistRow,\n type StaffAiConversationKind,\n type StaffAiConversationPerson,\n type StaffAiConversationsResponse,\n type StaffAiJobResult,\n type StaffAiJobRow,\n} from '../usage/staff-org-ai-conversations'\n\n/**\n * What an organization asked Aglyn AI and what it answered (AGL-3675): the\n * Assist chat's questions and answers, and each AI job's brief and result,\n * newest first, on the staff org page.\n *\n * Nothing is read until staff ask: the route writes an audited access row\n * for every page it returns, so a card that loaded with the page would log\n * reads nobody chose to make.\n */\n\nconst when = (iso: string | null) => (iso ? new Date(iso).toLocaleString() : 'Unknown time')\n\nconst who = (person: StaffAiConversationPerson) =>\n person.email ?? person.name ?? (person.uid ? `uid ${person.uid}` : 'Unknown member')\n\nconst money = (value: number | null) => (value === null ? null : `$${value.toFixed(value < 1 ? 4 : 2)}`)\n\n/** A block of what someone typed or what came back, kept as written. */\nconst Words = ({ label, children }: { label: string; children: string }) => (\n <Box>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {label}\n </Typography>\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>\n {children || '—'}\n </Typography>\n </Box>\n)\n\nconst Meta = ({ children }: { children: ReactNode }) => (\n <Stack direction=\"row\" useFlexGap sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>\n {children}\n </Stack>\n)\n\nconst AssistRow = ({ row }: { row: StaffAiAssistRow }) => (\n <Stack spacing={1}>\n <Meta>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {who(row.by)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {when(row.at)}\n </Typography>\n {row.route ? <Chip size=\"small\" variant=\"outlined\" label={row.route} /> : null}\n {row.model ? <Chip size=\"small\" variant=\"outlined\" label={row.model} /> : null}\n {money(row.estCostUsd) ? <Chip size=\"small\" variant=\"outlined\" label={money(row.estCostUsd)} /> : null}\n {row.feedback ? (\n <Chip size=\"small\" color={row.feedback === 'up' ? 'success' : 'warning'} label={`Rated ${row.feedback}`} />\n ) : null}\n </Meta>\n <Words label=\"Question\">{row.question}</Words>\n <Words label=\"Answer\">{row.answer}</Words>\n </Stack>\n)\n\nconst Result = ({ result }: { result: StaffAiJobResult }) => {\n switch (result.kind) {\n case 'insight':\n return (\n <Words label=\"Answer\">\n {[...result.insights, ...(result.gap ? [`Not answered: ${result.gap}`] : [])].join('\\n\\n')}\n </Words>\n )\n case 'crm-record':\n return (\n <Words label=\"Summary\">\n {[result.summary, result.standing, result.nextStep ? `Next step: ${result.nextStep}` : null]\n .filter(Boolean)\n .join('\\n\\n')}\n </Words>\n )\n case 'crm-email':\n return <Words label=\"Email draft\">{`Subject: ${result.subject}\\n\\n${result.body}`}</Words>\n case 'crm-mapping':\n return <Words label=\"Column matches\">{result.matches.join('\\n')}</Words>\n }\n}\n\nconst JobRow = ({ row }: { row: StaffAiJobRow }) => (\n <Stack spacing={1}>\n <Meta>\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {who(row.by)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {when(row.at)}\n </Typography>\n <Chip size=\"small\" variant=\"outlined\" label={row.kind} />\n <Chip\n size=\"small\"\n color={row.status === 'done' ? 'success' : row.status === 'failed' ? 'error' : 'default'}\n label={row.status}\n />\n <Chip size=\"small\" variant=\"outlined\" label={`${row.credits.toLocaleString()} credits`} />\n </Meta>\n <Words label=\"Brief\">{row.brief}</Words>\n {row.result ? <Result result={row.result} /> : null}\n {row.outputs.length ? (\n <Words label=\"Made\">\n {row.outputs\n .map((output) => [output.label, output.note].filter(Boolean).join(' — '))\n .join('\\n')}\n </Words>\n ) : null}\n {row.error ? <Alert severity=\"error\">{row.error}</Alert> : null}\n </Stack>\n)\n\nconst StaffOrgAiConversations = ({ orgId }: { orgId: string }) => {\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const [kind, setKind] = useState<StaffAiConversationKind | null>(null)\n const [rows, setRows] = useState<Array<StaffAiAssistRow | StaffAiJobRow>>([])\n // The cursor that opens each page: `cursors[page]` is the `after` its\n // request carries, so Previous re-reads a page rather than keeping every\n // page read so far in memory.\n const [cursors, setCursors] = useState<Array<string | null>>([null])\n const [page, setPage] = useState(0)\n const [next, setNext] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const load = useCallback(\n async (target: StaffAiConversationKind, targetPage: number, after: string | null) => {\n const current = userRef.current\n if (!current || !orgId) return\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ orgId, kind: target })\n if (after) params.set('after', after)\n const response = await authorizedFetch(current, `/api/ai/admin/conversations?${params}`)\n const payload = (await response.json().catch(() => null)) as\n | (StaffAiConversationsResponse & { error?: string })\n | null\n if (!response.ok || !payload) {\n setError(payload?.error ?? 'The conversations could not be read.')\n return\n }\n setRows(payload.rows)\n setPage(targetPage)\n setNext(payload.next)\n setCursors((existing) => {\n const kept = existing.slice(0, targetPage + 1)\n return payload.next ? [...kept, payload.next] : kept\n })\n } catch {\n setError('The conversations could not be read.')\n } finally {\n setLoading(false)\n }\n },\n [orgId],\n )\n\n const show = useCallback(\n (target: StaffAiConversationKind) => {\n setKind(target)\n setRows([])\n setNext(null)\n setPage(0)\n setCursors([null])\n void load(target, 0, null)\n },\n [load],\n )\n\n return (\n <CardDisplay\n header=\"AI conversations\"\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#ai-conversations',\n excerpt:\n `What this organization asked ${PLATFORM_BRAND_NAME} AI and what it answered: Assist chat questions and answers, and each AI job’s brief and result. Each page staff read is recorded in the audit log, without the text.`,\n })}\n HeaderProps={{\n action:\n kind === null ? (\n <Button size=\"small\" onClick={() => show('assist')}>\n {'Show conversations'}\n </Button>\n ) : (\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n value={kind}\n onChange={(_, value: StaffAiConversationKind | null) => value && show(value)}\n aria-label=\"Which conversations\"\n >\n <ToggleButton value=\"assist\">{'Assist chat'}</ToggleButton>\n <ToggleButton value=\"jobs\">{'AI jobs'}</ToggleButton>\n </ToggleButtonGroup>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {kind === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`What people in this organization typed to ${PLATFORM_BRAND_NAME} AI, and what it answered. Opening it is recorded in the audit log.`}\n </Typography>\n ) : (\n <Stack spacing={2} divider={<Divider flexItem />}>\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n {!loading && !error && !rows.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {kind === 'assist'\n ? 'No Assist chat in the last 180 days.'\n : 'No AI jobs yet.'}\n </Typography>\n ) : null}\n {rows.map((row) =>\n kind === 'assist' ? (\n <AssistRow key={row.id} row={row as StaffAiAssistRow} />\n ) : (\n <JobRow key={row.id} row={row as StaffAiJobRow} />\n ),\n )}\n {loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : null}\n {rows.length || page > 0 ? (\n <ListPagination\n page={page}\n pageSize={STAFF_AI_CONVERSATIONS_PAGE}\n rowCount={rows.length}\n hasMore={Boolean(next)}\n disabled={loading}\n onPageChange={(target) => void load(kind, target, cursors[target] ?? null)}\n />\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n )\n}\n\nStaffOrgAiConversations.displayName = 'StaffOrgAiConversations'\n\nexport default StaffOrgAiConversations\n"],"names":["PLATFORM_BRAND_NAME","pluginDocsHelp","CardDisplay","ListPagination","authorizedFetch","useUser","Alert","Box","Button","Chip","Divider","Stack","ToggleButton","ToggleButtonGroup","Typography","useCallback","useRef","useState","STAFF_AI_CONVERSATIONS_PAGE","when","iso","Date","toLocaleString","who","person","email","name","uid","money","value","toFixed","Words","label","children","variant","color","sx","whiteSpace","overflowWrap","Meta","direction","useFlexGap","flexWrap","gap","alignItems","AssistRow","row","spacing","fontWeight","by","at","route","size","model","estCostUsd","feedback","question","answer","Result","result","kind","insights","join","summary","standing","nextStep","filter","Boolean","subject","body","matches","JobRow","status","credits","brief","outputs","length","map","output","note","error","severity","StaffOrgAiConversations","orgId","data","user","userRef","current","setKind","rows","setRows","cursors","setCursors","page","setPage","next","setNext","loading","setLoading","setError","load","target","targetPage","after","params","URLSearchParams","set","response","payload","json","catch","ok","existing","kept","slice","show","header","help","anchor","excerpt","HeaderProps","action","onClick","exclusive","onChange","_","aria-label","contentGutterX","contentGutterY","divider","flexItem","id","pageSize","rowCount","hasMore","disabled","onPageChange","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,mBAAmB,QAAQ,eAAc;AAClD,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,OAAO,EACPC,KAAK,EACLC,YAAY,EACZC,iBAAiB,EACjBC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,MAAM,EAAEC,QAAQ,QAAwB,QAAO;AACrE,SACEC,2BAA2B,QAOtB,yCAAqC;AAE5C;;;;;;;;CAQC,GAED,MAAMC,OAAO,CAACC,MAAwBA,MAAM,IAAIC,KAAKD,KAAKE,cAAc,KAAK;AAE7E,MAAMC,MAAM,CAACC;QACXA,MAAAA;YAAAA,QAAAA,gBAAAA,OAAOC,KAAK,YAAZD,gBAAgBA,OAAOE,IAAI,YAA3BF,OAAgCA,OAAOG,GAAG,GAAG,CAAC,IAAI,EAAEH,OAAOG,GAAG,EAAE,GAAG;;AAErE,MAAMC,QAAQ,CAACC,QAA0BA,UAAU,OAAO,OAAO,CAAC,CAAC,EAAEA,MAAMC,OAAO,CAACD,QAAQ,IAAI,IAAI,IAAI;AAEvG,sEAAsE,GACtE,MAAME,QAAQ,CAAC,EAAEC,KAAK,EAAEC,QAAQ,EAAuC,iBACrE,MAAC1B;;0BACC,KAACO;gBAAWoB,SAAQ;gBAAWC,OAAM;0BAClCH;;0BAEH,KAAClB;gBAAWoB,SAAQ;gBAAQE,IAAI;oBAAEC,YAAY;oBAAYC,cAAc;gBAAW;0BAChFL,YAAY;;;;AAKnB,MAAMM,OAAO,CAAC,EAAEN,QAAQ,EAA2B,iBACjD,KAACtB;QAAM6B,WAAU;QAAMC,UAAU;QAACL,IAAI;YAAEM,UAAU;YAAQC,KAAK;YAAGC,YAAY;QAAS;kBACpFX;;AAIL,MAAMY,YAAY,CAAC,EAAEC,GAAG,EAA6B,iBACnD,MAACnC;QAAMoC,SAAS;;0BACd,MAACR;;kCACC,KAACzB;wBAAWoB,SAAQ;wBAAQE,IAAI;4BAAEY,YAAY;wBAAI;kCAC/CzB,IAAIuB,IAAIG,EAAE;;kCAEb,KAACnC;wBAAWoB,SAAQ;wBAAQC,OAAM;kCAC/BhB,KAAK2B,IAAII,EAAE;;oBAEbJ,IAAIK,KAAK,iBAAG,KAAC1C;wBAAK2C,MAAK;wBAAQlB,SAAQ;wBAAWF,OAAOc,IAAIK,KAAK;yBAAO;oBACzEL,IAAIO,KAAK,iBAAG,KAAC5C;wBAAK2C,MAAK;wBAAQlB,SAAQ;wBAAWF,OAAOc,IAAIO,KAAK;yBAAO;oBACzEzB,MAAMkB,IAAIQ,UAAU,kBAAI,KAAC7C;wBAAK2C,MAAK;wBAAQlB,SAAQ;wBAAWF,OAAOJ,MAAMkB,IAAIQ,UAAU;yBAAQ;oBACjGR,IAAIS,QAAQ,iBACX,KAAC9C;wBAAK2C,MAAK;wBAAQjB,OAAOW,IAAIS,QAAQ,KAAK,OAAO,YAAY;wBAAWvB,OAAO,CAAC,MAAM,EAAEc,IAAIS,QAAQ,EAAE;yBACrG;;;0BAEN,KAACxB;gBAAMC,OAAM;0BAAYc,IAAIU,QAAQ;;0BACrC,KAACzB;gBAAMC,OAAM;0BAAUc,IAAIW,MAAM;;;;AAIrC,MAAMC,SAAS,CAAC,EAAEC,MAAM,EAAgC;IACtD,OAAQA,OAAOC,IAAI;QACjB,KAAK;YACH,qBACE,KAAC7B;gBAAMC,OAAM;0BACV;uBAAI2B,OAAOE,QAAQ;uBAAMF,OAAOhB,GAAG,GAAG;wBAAC,CAAC,cAAc,EAAEgB,OAAOhB,GAAG,EAAE;qBAAC,GAAG,EAAE;iBAAE,CAACmB,IAAI,CAAC;;QAGzF,KAAK;YACH,qBACE,KAAC/B;gBAAMC,OAAM;0BACV;oBAAC2B,OAAOI,OAAO;oBAAEJ,OAAOK,QAAQ;oBAAEL,OAAOM,QAAQ,GAAG,CAAC,WAAW,EAAEN,OAAOM,QAAQ,EAAE,GAAG;iBAAK,CACzFC,MAAM,CAACC,SACPL,IAAI,CAAC;;QAGd,KAAK;YACH,qBAAO,KAAC/B;gBAAMC,OAAM;0BAAe,CAAC,SAAS,EAAE2B,OAAOS,OAAO,CAAC,IAAI,EAAET,OAAOU,IAAI,EAAE;;QACnF,KAAK;YACH,qBAAO,KAACtC;gBAAMC,OAAM;0BAAkB2B,OAAOW,OAAO,CAACR,IAAI,CAAC;;IAC9D;AACF;AAEA,MAAMS,SAAS,CAAC,EAAEzB,GAAG,EAA0B,iBAC7C,MAACnC;QAAMoC,SAAS;;0BACd,MAACR;;kCACC,KAACzB;wBAAWoB,SAAQ;wBAAQE,IAAI;4BAAEY,YAAY;wBAAI;kCAC/CzB,IAAIuB,IAAIG,EAAE;;kCAEb,KAACnC;wBAAWoB,SAAQ;wBAAQC,OAAM;kCAC/BhB,KAAK2B,IAAII,EAAE;;kCAEd,KAACzC;wBAAK2C,MAAK;wBAAQlB,SAAQ;wBAAWF,OAAOc,IAAIc,IAAI;;kCACrD,KAACnD;wBACC2C,MAAK;wBACLjB,OAAOW,IAAI0B,MAAM,KAAK,SAAS,YAAY1B,IAAI0B,MAAM,KAAK,WAAW,UAAU;wBAC/ExC,OAAOc,IAAI0B,MAAM;;kCAEnB,KAAC/D;wBAAK2C,MAAK;wBAAQlB,SAAQ;wBAAWF,OAAO,GAAGc,IAAI2B,OAAO,CAACnD,cAAc,GAAG,QAAQ,CAAC;;;;0BAExF,KAACS;gBAAMC,OAAM;0BAASc,IAAI4B,KAAK;;YAC9B5B,IAAIa,MAAM,iBAAG,KAACD;gBAAOC,QAAQb,IAAIa,MAAM;iBAAO;YAC9Cb,IAAI6B,OAAO,CAACC,MAAM,iBACjB,KAAC7C;gBAAMC,OAAM;0BACVc,IAAI6B,OAAO,CACTE,GAAG,CAAC,CAACC,SAAW;wBAACA,OAAO9C,KAAK;wBAAE8C,OAAOC,IAAI;qBAAC,CAACb,MAAM,CAACC,SAASL,IAAI,CAAC,QACjEA,IAAI,CAAC;iBAER;YACHhB,IAAIkC,KAAK,iBAAG,KAAC1E;gBAAM2E,UAAS;0BAASnC,IAAIkC,KAAK;iBAAY;;;AAI/D,MAAME,0BAA0B,CAAC,EAAEC,KAAK,EAAqB;IAC3D,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGhF;IACvB,MAAMiF,UAAUtE,OAAOqE;IACvBC,QAAQC,OAAO,GAAGF;IAClB,MAAM,CAACzB,MAAM4B,QAAQ,GAAGvE,SAAyC;IACjE,MAAM,CAACwE,MAAMC,QAAQ,GAAGzE,SAAkD,EAAE;IAC5E,sEAAsE;IACtE,yEAAyE;IACzE,8BAA8B;IAC9B,MAAM,CAAC0E,SAASC,WAAW,GAAG3E,SAA+B;QAAC;KAAK;IACnE,MAAM,CAAC4E,MAAMC,QAAQ,GAAG7E,SAAS;IACjC,MAAM,CAAC8E,MAAMC,QAAQ,GAAG/E,SAAwB;IAChD,MAAM,CAACgF,SAASC,WAAW,GAAGjF,SAAS;IACvC,MAAM,CAAC+D,OAAOmB,SAAS,GAAGlF,SAAwB;IAElD,MAAMmF,OAAOrF,YACX,OAAOsF,QAAiCC,YAAoBC;QAC1D,MAAMhB,UAAUD,QAAQC,OAAO;QAC/B,IAAI,CAACA,WAAW,CAACJ,OAAO;QACxBe,WAAW;QACXC,SAAS;QACT,IAAI;YACF,MAAMK,SAAS,IAAIC,gBAAgB;gBAAEtB;gBAAOvB,MAAMyC;YAAO;YACzD,IAAIE,OAAOC,OAAOE,GAAG,CAAC,SAASH;YAC/B,MAAMI,WAAW,MAAMvG,gBAAgBmF,SAAS,CAAC,4BAA4B,EAAEiB,QAAQ;YACvF,MAAMI,UAAW,MAAMD,SAASE,IAAI,GAAGC,KAAK,CAAC,IAAM;YAGnD,IAAI,CAACH,SAASI,EAAE,IAAI,CAACH,SAAS;;gBAC5BT,iBAASS,2BAAAA,QAAS5B,KAAK,mBAAI;gBAC3B;YACF;YACAU,QAAQkB,QAAQnB,IAAI;YACpBK,QAAQQ;YACRN,QAAQY,QAAQb,IAAI;YACpBH,WAAW,CAACoB;gBACV,MAAMC,OAAOD,SAASE,KAAK,CAAC,GAAGZ,aAAa;gBAC5C,OAAOM,QAAQb,IAAI,GAAG;uBAAIkB;oBAAML,QAAQb,IAAI;iBAAC,GAAGkB;YAClD;QACF,EAAE,eAAM;YACNd,SAAS;QACX,SAAU;YACRD,WAAW;QACb;IACF,GACA;QAACf;KAAM;IAGT,MAAMgC,OAAOpG,YACX,CAACsF;QACCb,QAAQa;QACRX,QAAQ,EAAE;QACVM,QAAQ;QACRF,QAAQ;QACRF,WAAW;YAAC;SAAK;QACjB,KAAKQ,KAAKC,QAAQ,GAAG;IACvB,GACA;QAACD;KAAK;IAGR,qBACE,KAAClG;QACCkH,QAAO;QACPC,MAAMpH,eAAe,gBAAgB;YACnCqH,QAAQ;YACRC,SACE,CAAC,6BAA6B,EAAEvH,oBAAoB,qKAAqK,CAAC;QAC9N;QACAwH,aAAa;YACXC,QACE7D,SAAS,qBACP,KAACpD;gBAAO4C,MAAK;gBAAQsE,SAAS,IAAMP,KAAK;0BACtC;+BAGH,MAACtG;gBACCuC,MAAK;gBACLuE,SAAS;gBACT9F,OAAO+B;gBACPgE,UAAU,CAACC,GAAGhG,QAA0CA,SAASsF,KAAKtF;gBACtEiG,cAAW;;kCAEX,KAAClH;wBAAaiB,OAAM;kCAAU;;kCAC9B,KAACjB;wBAAaiB,OAAM;kCAAQ;;;;QAGpC;QACAkG,cAAc;QACdC,cAAc;kBAEbpE,SAAS,qBACR,KAAC9C;YAAWoB,SAAQ;YAAQC,OAAM;sBAC/B,CAAC,0CAA0C,EAAEnC,oBAAoB,mEAAmE,CAAC;2BAGxI,MAACW;YAAMoC,SAAS;YAAGkF,uBAAS,KAACvH;gBAAQwH,QAAQ;;;gBAC1ClD,sBAAQ,KAAC1E;oBAAM2E,UAAS;8BAAWD;qBAAiB;gBACpD,CAACiB,WAAW,CAACjB,SAAS,CAACS,KAAKb,MAAM,iBACjC,KAAC9D;oBAAWoB,SAAQ;oBAAQC,OAAM;8BAC/ByB,SAAS,WACN,yCACA;qBAEJ;gBACH6B,KAAKZ,GAAG,CAAC,CAAC/B,MACTc,SAAS,yBACP,KAACf;wBAAuBC,KAAKA;uBAAbA,IAAIqF,EAAE,kBAEtB,KAAC5D;wBAAoBzB,KAAKA;uBAAbA,IAAIqF,EAAE;gBAGtBlC,wBACC,KAACnF;oBAAWoB,SAAQ;oBAAQC,OAAM;8BAC/B;qBAED;gBACHsD,KAAKb,MAAM,IAAIiB,OAAO,kBACrB,KAAC1F;oBACC0F,MAAMA;oBACNuC,UAAUlH;oBACVmH,UAAU5C,KAAKb,MAAM;oBACrB0D,SAASnE,QAAQ4B;oBACjBwC,UAAUtC;oBACVuC,cAAc,CAACnC;4BAAmCV;+BAAxB,KAAKS,KAAKxC,MAAMyC,SAAQV,kBAAAA,OAAO,CAACU,OAAO,YAAfV,kBAAmB;;qBAErE;;;;AAKd;AAEAT,wBAAwBuD,WAAW,GAAG;AAEtC,eAAevD,wBAAuB"}
@@ -17,8 +17,16 @@
17
17
  import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
18
18
  import { type PluginAiCapability, type ResolvedPluginAiCapability } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
19
19
  import type { AiBuildOps } from '../model/ai-build-job';
20
+ import { type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts';
20
21
  /** The operations this plugin owns, as it registers them. */
21
22
  export declare const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[];
23
+ /**
24
+ * The resource another plugin writes for each operation of this plugin's that
25
+ * cannot be built without it. The operation runs where that writer's owner
26
+ * runs — registered, switched on for the workspace and the site, and past its
27
+ * release flag — exactly as an operation that owner registered would.
28
+ */
29
+ export declare const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>>;
22
30
  /** Registers this plugin's build operations; the console surface calls it. */
23
31
  export declare function registerAiBuildCapabilities(): void;
24
32
  /** What decides which operations a build may use here. */
@@ -37,11 +45,15 @@ export interface AiBuildOpsDeps {
37
45
  releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>;
38
46
  /** Whether a runner is registered for a kind; the machine's otherwise. */
39
47
  hasRunner?: (kind: string) => boolean;
48
+ /** Which plugin writes a resource; the core's registry otherwise. */
49
+ writerOwner?: AiPluginDraftOwnerLookup;
40
50
  }
41
51
  /**
42
52
  * The operations a build may plan on this site, by op (AGL-3616): every
43
53
  * registered capability whose owner runs here — switched on for the
44
- * workspace and the site, past its release flag — whose entitlement the plan
54
+ * workspace and the site, past its release flag; for one of this plugin's
55
+ * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's
56
+ * owner, and a writer registered at all — whose entitlement the plan
45
57
  * holds, which the Free taste may use where the workspace is on it, and,
46
58
  * for an AI-run operation, whose runner this process loaded. One release-flag
47
59
  * read for every owner at once. What a member may do and what an allowance
@@ -19,6 +19,11 @@ import { pluginAiCapabilities, registerPluginAiCapability } from "@aglyn/aglyn/p
19
19
  import { filterEnabledPluginsByReleaseFlags } from "@aglyn/tenant-data-admin/server/release-flags";
20
20
  import { AI_TEMPLATE_SUBJECTS } from "../model/ai-template-subjects.js";
21
21
  import { AI_SITE_PASS_CREDITS } from "../model/ai-site-job.js";
22
+ import { AI_LOGIC_FUNCTION_RESOURCE } from "../model/ai-logic-job.js";
23
+ import { AI_AUTOMATION_RESOURCE } from "../model/ai-workflow-job.js";
24
+ import { AI_EDIT_TARGET_KINDS } from "../model/ai-edit-job.js";
25
+ import { AI_CAMPAIGN_RESOURCE } from "./ai-job-campaign-step.js";
26
+ import { aiPluginDraftOwner } from "./ai-job-plugin-drafts.js";
22
27
  import { aiJobStepRunnerFor } from "./ai-jobs.js";
23
28
  /**
24
29
  * What THIS plugin lets a build make (AGL-3616), registered on the core's
@@ -29,7 +34,10 @@ import { aiJobStepRunnerFor } from "./ai-jobs.js";
29
34
  * pages, layouts, forms, components and email designs are the plan's
30
35
  * creations and screens; templates, campaigns and automations are items with
31
36
  * arguments. A campaign and an automation are still WRITTEN by the marketing
32
- * and workflows plugins, through the writers their runners already call.
37
+ * and workflows plugins, through the writers their runners already call, and
38
+ * a site function by the logic plugin's `function` writer, which the `logic`
39
+ * runner calls when its job is a build's unit. A change to a page or layout
40
+ * the site already has is the `edit` runner's.
33
41
  */ /** This plugin's id, as its registrations name their owner. */ const AI_PLUGIN_ID = 'ai';
34
42
  const NO_ARGS = {
35
43
  type: 'object',
@@ -177,8 +185,78 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
177
185
  runnerKind: 'workflow',
178
186
  estimateCredits: passes(1),
179
187
  degrade: 'omit'
188
+ },
189
+ {
190
+ // A function needs a model, so it is this plugin's operation; the logic
191
+ // plugin owns the record and writes it (AGL-3616). New functions only: a
192
+ // build never changes one the site already has.
193
+ op: 'function',
194
+ noun: 'site function',
195
+ where: 'Logic → Functions',
196
+ intents: [
197
+ 'a site function that works a value out from what it is given: a price, a total, a quote'
198
+ ],
199
+ argsSchema: NO_ARGS,
200
+ maxPerPlan: 2,
201
+ // Free includes one function (`functionsPerHost: 1`), and Logic no feature.
202
+ freeAllowed: true,
203
+ quota: 'functionsPerHost',
204
+ runnerKind: 'logic',
205
+ estimateCredits: passes(1),
206
+ degrade: 'omit'
207
+ },
208
+ {
209
+ // A change to something the site already has (AGL-3616), made by the
210
+ // `edit` runner: the Assist edit rung's protocol against the stored
211
+ // document, saved as a version visitors do not see. Not on the Free
212
+ // taste: without version history only an unpublished page can take a
213
+ // change, which is rarely the page a request means, and its admission
214
+ // would refuse the item after the plan was confirmed.
215
+ op: 'edit',
216
+ noun: 'page change',
217
+ where: 'the page’s or layout’s versions',
218
+ intents: [
219
+ 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published'
220
+ ],
221
+ argsSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ target: {
225
+ type: 'string',
226
+ description: 'The id of an existing page or layout from the site inventory.',
227
+ maxLength: 64
228
+ },
229
+ targetKind: {
230
+ type: 'string',
231
+ description: 'screen when target is a page, layout when it is a layout.',
232
+ enum: AI_EDIT_TARGET_KINDS
233
+ }
234
+ },
235
+ required: [
236
+ 'target'
237
+ ],
238
+ additionalProperties: false
239
+ },
240
+ maxPerPlan: 3,
241
+ freeAllowed: false,
242
+ runnerKind: 'edit',
243
+ estimateCredits: passes(1),
244
+ degrade: 'omit'
180
245
  }
181
246
  ];
247
+ /**
248
+ * The resource another plugin writes for each operation of this plugin's that
249
+ * cannot be built without it. The operation runs where that writer's owner
250
+ * runs — registered, switched on for the workspace and the site, and past its
251
+ * release flag — exactly as an operation that owner registered would.
252
+ */ export const AI_OWNED_OP_WRITERS = {
253
+ function: AI_LOGIC_FUNCTION_RESOURCE,
254
+ // A campaign is written by the marketing plugin's campaign writer, an
255
+ // automation by the workflows plugin's: neither is offered where its
256
+ // writer's plugin is off, rather than planned and then skipped.
257
+ campaign: AI_CAMPAIGN_RESOURCE,
258
+ workflow: AI_AUTOMATION_RESOURCE
259
+ };
182
260
  /** Registers this plugin's build operations; the console surface calls it. */ export function registerAiBuildCapabilities() {
183
261
  for (const capability of AI_OWNED_CAPABILITIES){
184
262
  registerPluginAiCapability(capability, {
@@ -189,17 +267,29 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
189
267
  /**
190
268
  * The operations a build may plan on this site, by op (AGL-3616): every
191
269
  * registered capability whose owner runs here — switched on for the
192
- * workspace and the site, past its release flag — whose entitlement the plan
270
+ * workspace and the site, past its release flag; for one of this plugin's
271
+ * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's
272
+ * owner, and a writer registered at all — whose entitlement the plan
193
273
  * holds, which the Free taste may use where the workspace is on it, and,
194
274
  * for an AI-run operation, whose runner this process loaded. One release-flag
195
275
  * read for every owner at once. What a member may do and what an allowance
196
276
  * has left are the owner's to say when the item runs (`refusal`).
197
277
  */ export async function aiBuildOps(context, deps = {}) {
198
- var _deps_capabilities, _deps_hasRunner, _deps_releasedPlugins;
278
+ var _deps_capabilities, _deps_hasRunner, _deps_writerOwner, _deps_releasedPlugins;
199
279
  const all = ((_deps_capabilities = deps.capabilities) != null ? _deps_capabilities : pluginAiCapabilities)();
200
280
  const hasRunner = (_deps_hasRunner = deps.hasRunner) != null ? _deps_hasRunner : (kind)=>aiJobStepRunnerFor(kind) !== null;
281
+ const writerOwner = (_deps_writerOwner = deps.writerOwner) != null ? _deps_writerOwner : aiPluginDraftOwner;
282
+ // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.
283
+ const writtenBy = new Map();
284
+ for (const { pluginId, capability } of all){
285
+ const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined;
286
+ if (resource) writtenBy.set(capability.op, writerOwner(resource));
287
+ }
201
288
  const owners = [
202
- ...new Set(all.map((one)=>one.pluginId).filter((id)=>id !== AI_PLUGIN_ID))
289
+ ...new Set([
290
+ ...all.map((one)=>one.pluginId),
291
+ ...writtenBy.values()
292
+ ].filter((id)=>!!id && id !== AI_PLUGIN_ID))
203
293
  ];
204
294
  const released = new Set(owners.length ? await ((_deps_releasedPlugins = deps.releasedPlugins) != null ? _deps_releasedPlugins : (ids, orgId)=>filterEnabledPluginsByReleaseFlags(ids, {
205
295
  orgId,
@@ -207,9 +297,13 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
207
297
  }))(owners, context.orgId) : []);
208
298
  const ops = new Map();
209
299
  for (const { pluginId, capability } of all){
210
- if (pluginId !== AI_PLUGIN_ID) {
211
- if (!released.has(pluginId)) continue;
212
- if (!isHostPluginEnabled(context.org, context.host, pluginId)) continue;
300
+ // The plugin that must run here: the owner, or for one of this plugin's
301
+ // operations, the plugin that writes what it makes.
302
+ const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId;
303
+ if (runsOn === null) continue;
304
+ if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {
305
+ if (!released.has(runsOn)) continue;
306
+ if (!isHostPluginEnabled(context.org, context.host, runsOn)) continue;
213
307
  }
214
308
  if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue;
215
309
  if (context.freeTaste && !capability.freeAllowed) continue;