@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235

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 (199) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-job-links.d.ts +6 -0
  28. package/src/lib/components/ai-job-links.js +9 -0
  29. package/src/lib/components/ai-job-links.js.map +1 -1
  30. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  31. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  32. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  33. package/src/lib/components/ai-logic.component.js +5 -3
  34. package/src/lib/components/ai-logic.component.js.map +1 -1
  35. package/src/lib/components/ai-media-create.component.js +88 -16
  36. package/src/lib/components/ai-media-create.component.js.map +1 -1
  37. package/src/lib/components/ai-model-selector.component.js +8 -0
  38. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  39. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  40. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  41. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  42. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  43. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  44. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  45. package/src/lib/components/ai-revise-automation.component.js +1 -1
  46. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  47. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  48. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  50. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.js +48 -6
  52. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  53. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  54. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  55. package/src/lib/components/ai-site-start-card.component.js +152 -36
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  60. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  61. package/src/lib/components/ai-usage-strip.component.js +13 -2
  62. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  63. package/src/lib/components/assist-build-card.component.js +20 -3
  64. package/src/lib/components/assist-build-card.component.js.map +1 -1
  65. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  66. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  67. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  68. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  69. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  70. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  71. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  72. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  73. package/src/lib/components/host-ai-allotments.component.js +2 -2
  74. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  75. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  76. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  77. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  78. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  79. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  80. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  81. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  82. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  83. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  84. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  85. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  86. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  87. package/src/lib/jobs/ai-job-email-step.js +116 -5
  88. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  89. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  90. package/src/lib/jobs/ai-job-layout-language.js +6 -1
  91. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  92. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
  93. package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
  94. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  95. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  96. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  97. package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
  98. package/src/lib/jobs/ai-job-page-language.js +75 -13
  99. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  100. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  101. package/src/lib/jobs/ai-job-page-step.js +32 -7
  102. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  104. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  105. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
  107. package/src/lib/jobs/ai-job-site-content.js +210 -43
  108. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  109. package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
  110. package/src/lib/jobs/ai-job-site-step.js +152 -51
  111. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  112. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  113. package/src/lib/jobs/ai-jobs.js +19 -2
  114. package/src/lib/jobs/ai-jobs.js.map +1 -1
  115. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  116. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  117. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  118. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  119. package/src/lib/jobs/ai-site-publish.js +31 -1
  120. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  121. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  122. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  123. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  124. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  125. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  126. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  127. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  128. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  129. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  130. package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
  131. package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
  132. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  133. package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
  134. package/src/lib/layout-language/ai-layout-design.js +209 -0
  135. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  136. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  137. package/src/lib/layout-language/ai-layout-frame.js +33 -8
  138. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  139. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  140. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  141. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  142. package/src/lib/layout-language/ai-layout-language.js +15 -1
  143. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
  145. package/src/lib/layout-language/ai-layout-links.js +26 -2
  146. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  147. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  148. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  149. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  150. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  151. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  152. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  153. package/src/lib/model/ai-job-notice.d.ts +9 -1
  154. package/src/lib/model/ai-job-notice.js +23 -0
  155. package/src/lib/model/ai-job-notice.js.map +1 -1
  156. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  157. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  158. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  159. package/src/lib/model/ai-site-build-progress.js +104 -15
  160. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  161. package/src/lib/model/ai-site-job.d.ts +236 -24
  162. package/src/lib/model/ai-site-job.js +269 -22
  163. package/src/lib/model/ai-site-job.js.map +1 -1
  164. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  165. package/src/lib/model/ai-site-kinds.js +735 -116
  166. package/src/lib/model/ai-site-kinds.js.map +1 -1
  167. package/src/lib/model/ai-site-look.d.ts +21 -0
  168. package/src/lib/model/ai-site-look.js +63 -14
  169. package/src/lib/model/ai-site-look.js.map +1 -1
  170. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  171. package/src/lib/model/ai-site-start-seo.js +132 -11
  172. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-start.d.ts +2 -2
  174. package/src/lib/model/ai-site-start.js +3 -3
  175. package/src/lib/model/ai-site-start.js.map +1 -1
  176. package/src/lib/plugin.js +15 -3
  177. package/src/lib/plugin.js.map +1 -1
  178. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  179. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  180. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  181. package/src/lib/runtime/ai-doctrine.js +5 -3
  182. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  183. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  184. package/src/lib/runtime/ai-palette.generated.js +31 -5
  185. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  186. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  187. package/src/lib/server/ai-admin-conversations.js +151 -9
  188. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  189. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  190. package/src/lib/server/ai-jobs-route.js +15 -3
  191. package/src/lib/server/ai-jobs-route.js.map +1 -1
  192. package/src/lib/server/assist-edit.js +5 -2
  193. package/src/lib/server/assist-edit.js.map +1 -1
  194. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  195. package/src/lib/usage/free-ai-credits-left.js +62 -0
  196. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
  197. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  198. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  199. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -14,10 +14,16 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ /** The anchor an activity row's "Open the AI job" link lands on. */
18
+ export declare const STAFF_AI_REQUESTS_ANCHOR = "aglyn-ai-requests";
19
+ export interface StaffAiRequestsProps {
20
+ /** The staff org page: this organization's requests. */
21
+ orgId?: string;
22
+ /** The staff account page: this account's requests, in every org. */
23
+ uid?: string;
24
+ }
17
25
  declare const StaffOrgAiConversations: {
18
- ({ orgId }: {
19
- orgId: string;
20
- }): import("react").JSX.Element;
26
+ ({ orgId, uid }: StaffAiRequestsProps): import("react").JSX.Element;
21
27
  displayName: string;
22
28
  };
23
29
  export default StaffOrgAiConversations;
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
17
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
19
  import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn";
19
20
  import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
@@ -22,12 +23,18 @@ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.
22
23
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
23
24
  import { useUser } from "@aglyn/tenant-feature-instance";
24
25
  import { Alert, Box, Button, Chip, Divider, Stack, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
25
- import { useCallback, useRef, useState } from "react";
26
+ import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
27
+ import { AppLink } from "@aglyn/shared-ui-jsx";
28
+ import { useCallback, useEffect, useRef, useState } from "react";
26
29
  import { STAFF_AI_CONVERSATIONS_PAGE } from "../usage/staff-org-ai-conversations.js";
27
30
  /**
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
+ * "Aglyn AI requests" (AGL-3675, AGL-3660): everything people typed to
32
+ * Aglyn AI and what it answered — the Assist chat's questions (in the console
33
+ * and the Besigner) and answers, and each AI job's brief, the fields filled
34
+ * in beside it (a guided start's form, a Create-with-AI dialog's options)
35
+ * and its result — newest first. On the staff org page for one organization,
36
+ * and on the staff account page for one account across every organization
37
+ * it asked in.
31
38
  *
32
39
  * Nothing is read until staff ask: the route writes an audited access row
33
40
  * for every page it returns, so a card that loaded with the page would log
@@ -65,11 +72,14 @@ const Meta = ({ children })=>/*#__PURE__*/ _jsx(Stack, {
65
72
  },
66
73
  children: children
67
74
  });
68
- const AssistRow = ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
75
+ const AssistRow = ({ row, showOrg })=>/*#__PURE__*/ _jsxs(Stack, {
69
76
  spacing: 1,
70
77
  children: [
71
78
  /*#__PURE__*/ _jsxs(Meta, {
72
79
  children: [
80
+ showOrg ? /*#__PURE__*/ _jsx(OrgChip, {
81
+ orgId: row.orgId
82
+ }) : null,
73
83
  /*#__PURE__*/ _jsx(Typography, {
74
84
  variant: "body2",
75
85
  sx: {
@@ -147,11 +157,24 @@ const Result = ({ result })=>{
147
157
  });
148
158
  }
149
159
  };
150
- const JobRow = ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
160
+ /** Where a row was asked, on the account card: the org's staff page. */ const OrgChip = ({ orgId })=>orgId ? /*#__PURE__*/ _jsx(AppLink, {
161
+ href: buildRoute(Route.ADMIN_ORG_DETAIL, {
162
+ orgId
163
+ }),
164
+ variant: "body2",
165
+ underline: "hover",
166
+ children: `Organization ${orgId.slice(0, 8)}…`
167
+ }) : null;
168
+ const JobRow = ({ row, showOrg })=>{
169
+ var _row_inputs;
170
+ return /*#__PURE__*/ _jsxs(Stack, {
151
171
  spacing: 1,
152
172
  children: [
153
173
  /*#__PURE__*/ _jsxs(Meta, {
154
174
  children: [
175
+ showOrg ? /*#__PURE__*/ _jsx(OrgChip, {
176
+ orgId: row.orgId
177
+ }) : null,
155
178
  /*#__PURE__*/ _jsx(Typography, {
156
179
  variant: "body2",
157
180
  sx: {
@@ -185,6 +208,10 @@ const JobRow = ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
185
208
  label: "Brief",
186
209
  children: row.brief
187
210
  }),
211
+ ((_row_inputs = row.inputs) == null ? void 0 : _row_inputs.length) ? /*#__PURE__*/ _jsx(Words, {
212
+ label: "Filled in",
213
+ children: row.inputs.map((input)=>`${input.label}: ${input.value}`).join('\n')
214
+ }) : null,
188
215
  row.result ? /*#__PURE__*/ _jsx(Result, {
189
216
  result: row.result
190
217
  }) : null,
@@ -201,7 +228,25 @@ const JobRow = ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
201
228
  }) : null
202
229
  ]
203
230
  });
204
- const StaffOrgAiConversations = ({ orgId })=>{
231
+ };
232
+ /** The anchor an activity row's "Open the AI job" link lands on. */ export const STAFF_AI_REQUESTS_ANCHOR = 'aglyn-ai-requests';
233
+ const StaffOrgAiConversations = ({ orgId, uid })=>{
234
+ const subject = uid ? {
235
+ uid
236
+ } : orgId ? {
237
+ orgId
238
+ } : null;
239
+ const account = Boolean(uid);
240
+ // `?aiJob=` — an activity or audit row's link to the job behind it opens
241
+ // the card on that one job; asking for it IS the staff member's ask.
242
+ // Read from the location in an effect rather than `useSearchParams`, which
243
+ // would ask every page this zone sits on for a Suspense boundary.
244
+ const [focusJob, setFocusJob] = useState(null);
245
+ useEffect(()=>{
246
+ setFocusJob(new URLSearchParams(window.location.search).get('aiJob'));
247
+ }, []);
248
+ const [focused, setFocused] = useState(null);
249
+ const focusOpened = useRef(false);
205
250
  const { data: user } = useUser();
206
251
  const userRef = useRef(user);
207
252
  userRef.current = user;
@@ -217,17 +262,17 @@ const StaffOrgAiConversations = ({ orgId })=>{
217
262
  const [next, setNext] = useState(null);
218
263
  const [loading, setLoading] = useState(false);
219
264
  const [error, setError] = useState(null);
220
- const load = useCallback(async (target, targetPage, after)=>{
265
+ const load = useCallback(async (target, targetPage, after, jobId = null)=>{
221
266
  const current = userRef.current;
222
- if (!current || !orgId) return;
267
+ if (!current || !subject) return;
223
268
  setLoading(true);
224
269
  setError(null);
225
270
  try {
226
- const params = new URLSearchParams({
227
- orgId,
271
+ const params = new URLSearchParams(_extends({}, subject, {
228
272
  kind: target
229
- });
273
+ }));
230
274
  if (after) params.set('after', after);
275
+ if (jobId) params.set('jobId', jobId);
231
276
  const response = await authorizedFetch(current, `/api/ai/admin/conversations?${params}`);
232
277
  const payload = await response.json().catch(()=>null);
233
278
  if (!response.ok || !payload) {
@@ -250,10 +295,14 @@ const StaffOrgAiConversations = ({ orgId })=>{
250
295
  } finally{
251
296
  setLoading(false);
252
297
  }
253
- }, [
254
- orgId
298
+ }, // `subject` is rebuilt from these two every render.
299
+ // eslint-disable-next-line react-hooks/exhaustive-deps
300
+ [
301
+ orgId,
302
+ uid
255
303
  ]);
256
304
  const show = useCallback((target)=>{
305
+ setFocused(null);
257
306
  setKind(target);
258
307
  setRows([]);
259
308
  setNext(null);
@@ -265,78 +314,120 @@ const StaffOrgAiConversations = ({ orgId })=>{
265
314
  }, [
266
315
  load
267
316
  ]);
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",
317
+ const signedIn = Boolean(user);
318
+ useEffect(()=>{
319
+ var // The card mounts after the page's own hash scroll has come and gone.
320
+ _document_getElementById;
321
+ // Once per visit: Show all jobs clears the focus and must not bring it back.
322
+ if (account || !focusJob || !signedIn || focusOpened.current) return;
323
+ focusOpened.current = true;
324
+ setFocused(focusJob);
325
+ setKind('jobs');
326
+ setRows([]);
327
+ setNext(null);
328
+ setPage(0);
329
+ setCursors([
330
+ null
331
+ ]);
332
+ void load('jobs', 0, null, focusJob);
333
+ (_document_getElementById = document.getElementById(STAFF_AI_REQUESTS_ANCHOR)) == null ? void 0 : _document_getElementById.scrollIntoView({
334
+ block: 'start'
335
+ });
336
+ }, [
337
+ account,
338
+ focusJob,
339
+ signedIn,
340
+ load
341
+ ]);
342
+ return /*#__PURE__*/ _jsx(Box, {
343
+ id: STAFF_AI_REQUESTS_ANCHOR,
344
+ sx: {
345
+ scrollMarginTop: 80
346
+ },
347
+ children: /*#__PURE__*/ _jsx(CardDisplay, {
348
+ header: `${PLATFORM_BRAND_NAME} AI requests`,
349
+ help: pluginDocsHelp('aiMonitoring', {
350
+ anchor: '#ai-conversations',
351
+ excerpt: `What ${account ? 'this account' : 'this organization'} typed to ${PLATFORM_BRAND_NAME} AI and what it answered: Assist chat questions and answers, and each AI job’s brief, the fields filled in beside it, and its result. Each page staff read is recorded in the audit log, without the text.`
352
+ }),
353
+ HeaderProps: {
354
+ action: kind === null ? /*#__PURE__*/ _jsx(Button, {
355
+ size: "small",
356
+ onClick: ()=>show('assist'),
357
+ children: 'Show requests'
358
+ }) : /*#__PURE__*/ _jsxs(ToggleButtonGroup, {
359
+ size: "small",
360
+ exclusive: true,
361
+ value: kind,
362
+ onChange: (_, value)=>value && show(value),
363
+ "aria-label": "Which conversations",
364
+ children: [
365
+ /*#__PURE__*/ _jsx(ToggleButton, {
366
+ value: "assist",
367
+ children: 'Assist chat'
368
+ }),
369
+ /*#__PURE__*/ _jsx(ToggleButton, {
370
+ value: "jobs",
371
+ children: 'AI jobs'
372
+ })
373
+ ]
374
+ })
375
+ },
376
+ contentGutterX: true,
377
+ contentGutterY: true,
378
+ children: kind === null ? /*#__PURE__*/ _jsx(Typography, {
379
+ variant: "body2",
380
+ color: "text.secondary",
381
+ children: account ? `What this account typed to ${PLATFORM_BRAND_NAME} AI in every organization, and what it answered. Opening it is recorded in the audit log, and in this account's data access by staff.` : `What people in this organization typed to ${PLATFORM_BRAND_NAME} AI, and what it answered. Opening it is recorded in the audit log.`
382
+ }) : /*#__PURE__*/ _jsxs(Stack, {
383
+ spacing: 2,
384
+ divider: /*#__PURE__*/ _jsx(Divider, {
385
+ flexItem: true
386
+ }),
285
387
  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
- })
388
+ focused ? /*#__PURE__*/ _jsx(Alert, {
389
+ severity: "info",
390
+ action: /*#__PURE__*/ _jsx(Button, {
391
+ size: "small",
392
+ onClick: ()=>show('jobs'),
393
+ children: 'Show all jobs'
394
+ }),
395
+ children: 'Showing the one AI job the link pointed at.'
396
+ }) : null,
397
+ error ? /*#__PURE__*/ _jsx(Alert, {
398
+ severity: "warning",
399
+ children: error
400
+ }) : null,
401
+ !loading && !error && !rows.length ? /*#__PURE__*/ _jsx(Typography, {
402
+ variant: "body2",
403
+ color: "text.secondary",
404
+ children: kind === 'assist' ? 'No Assist chat in the last 180 days.' : 'No AI jobs yet.'
405
+ }) : null,
406
+ rows.map((row)=>kind === 'assist' ? /*#__PURE__*/ _jsx(AssistRow, {
407
+ row: row,
408
+ showOrg: account
409
+ }, `${row.orgId}:${row.id}`) : /*#__PURE__*/ _jsx(JobRow, {
410
+ row: row,
411
+ showOrg: account
412
+ }, `${row.orgId}:${row.id}`)),
413
+ loading ? /*#__PURE__*/ _jsx(Typography, {
414
+ variant: "body2",
415
+ color: "text.secondary",
416
+ children: 'Loading…'
417
+ }) : null,
418
+ rows.length || page > 0 ? /*#__PURE__*/ _jsx(ListPagination, {
419
+ page: page,
420
+ pageSize: STAFF_AI_CONVERSATIONS_PAGE,
421
+ rowCount: rows.length,
422
+ hasMore: Boolean(next),
423
+ disabled: loading,
424
+ onPageChange: (target)=>{
425
+ var _cursors_target;
426
+ return void load(kind, target, (_cursors_target = cursors[target]) != null ? _cursors_target : null);
427
+ }
428
+ }) : null
294
429
  ]
295
430
  })
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
431
  })
341
432
  });
342
433
  };
@@ -1 +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":"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"}
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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { useCallback, useEffect, 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 * \"Aglyn AI requests\" (AGL-3675, AGL-3660): everything people typed to\n * Aglyn AI and what it answered — the Assist chat's questions (in the console\n * and the Besigner) and answers, and each AI job's brief, the fields filled\n * in beside it (a guided start's form, a Create-with-AI dialog's options)\n * and its result — newest first. On the staff org page for one organization,\n * and on the staff account page for one account across every organization\n * it asked in.\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, showOrg }: { row: StaffAiAssistRow; showOrg?: boolean }) => (\n <Stack spacing={1}>\n <Meta>\n {showOrg ? <OrgChip orgId={row.orgId} /> : null}\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\n/** Where a row was asked, on the account card: the org's staff page. */\nconst OrgChip = ({ orgId }: { orgId: string | null }) =>\n orgId ? (\n <AppLink\n href={buildRoute(Route.ADMIN_ORG_DETAIL, { orgId })}\n variant=\"body2\"\n underline=\"hover\"\n >\n {`Organization ${orgId.slice(0, 8)}…`}\n </AppLink>\n ) : null\n\nconst JobRow = ({ row, showOrg }: { row: StaffAiJobRow; showOrg?: boolean }) => (\n <Stack spacing={1}>\n <Meta>\n {showOrg ? <OrgChip orgId={row.orgId} /> : null}\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.inputs?.length ? (\n <Words label=\"Filled in\">\n {row.inputs.map((input) => `${input.label}: ${input.value}`).join('\\n')}\n </Words>\n ) : null}\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\n/** The anchor an activity row's \"Open the AI job\" link lands on. */\nexport const STAFF_AI_REQUESTS_ANCHOR = 'aglyn-ai-requests'\n\nexport interface StaffAiRequestsProps {\n /** The staff org page: this organization's requests. */\n orgId?: string\n /** The staff account page: this account's requests, in every org. */\n uid?: string\n}\n\nconst StaffOrgAiConversations = ({ orgId, uid }: StaffAiRequestsProps) => {\n const subject = uid ? { uid } : orgId ? { orgId } : null\n const account = Boolean(uid)\n // `?aiJob=` — an activity or audit row's link to the job behind it opens\n // the card on that one job; asking for it IS the staff member's ask.\n // Read from the location in an effect rather than `useSearchParams`, which\n // would ask every page this zone sits on for a Suspense boundary.\n const [focusJob, setFocusJob] = useState<string | null>(null)\n useEffect(() => {\n setFocusJob(new URLSearchParams(window.location.search).get('aiJob'))\n }, [])\n const [focused, setFocused] = useState<string | null>(null)\n const focusOpened = useRef(false)\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 (\n target: StaffAiConversationKind,\n targetPage: number,\n after: string | null,\n jobId: string | null = null,\n ) => {\n const current = userRef.current\n if (!current || !subject) return\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ ...subject, kind: target })\n if (after) params.set('after', after)\n if (jobId) params.set('jobId', jobId)\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 // `subject` is rebuilt from these two every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [orgId, uid],\n )\n\n const show = useCallback(\n (target: StaffAiConversationKind) => {\n setFocused(null)\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 const signedIn = Boolean(user)\n useEffect(() => {\n // Once per visit: Show all jobs clears the focus and must not bring it back.\n if (account || !focusJob || !signedIn || focusOpened.current) return\n focusOpened.current = true\n setFocused(focusJob)\n setKind('jobs')\n setRows([])\n setNext(null)\n setPage(0)\n setCursors([null])\n void load('jobs', 0, null, focusJob)\n // The card mounts after the page's own hash scroll has come and gone.\n document.getElementById(STAFF_AI_REQUESTS_ANCHOR)?.scrollIntoView({ block: 'start' })\n }, [account, focusJob, signedIn, load])\n\n return (\n <Box id={STAFF_AI_REQUESTS_ANCHOR} sx={{ scrollMarginTop: 80 }}>\n <CardDisplay\n header={`${PLATFORM_BRAND_NAME} AI requests`}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#ai-conversations',\n excerpt:\n `What ${account ? 'this account' : 'this organization'} typed to ${PLATFORM_BRAND_NAME} AI and what it answered: Assist chat questions and answers, and each AI job’s brief, the fields filled in beside it, and its 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 requests'}\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 {account\n ? `What this account typed to ${PLATFORM_BRAND_NAME} AI in every organization, and what it answered. Opening it is recorded in the audit log, and in this account's data access by staff.`\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 {focused ? (\n <Alert\n severity=\"info\"\n action={\n <Button size=\"small\" onClick={() => show('jobs')}>\n {'Show all jobs'}\n </Button>\n }\n >\n {'Showing the one AI job the link pointed at.'}\n </Alert>\n ) : null}\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.orgId}:${row.id}`} row={row as StaffAiAssistRow} showOrg={account} />\n ) : (\n <JobRow key={`${row.orgId}:${row.id}`} row={row as StaffAiJobRow} showOrg={account} />\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 </Box>\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","buildRoute","Route","AppLink","useCallback","useEffect","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","showOrg","spacing","OrgChip","orgId","fontWeight","by","at","route","size","model","estCostUsd","feedback","question","answer","Result","result","kind","insights","join","summary","standing","nextStep","filter","Boolean","subject","body","matches","href","ADMIN_ORG_DETAIL","underline","slice","JobRow","status","credits","brief","inputs","length","map","input","outputs","output","note","error","severity","STAFF_AI_REQUESTS_ANCHOR","StaffOrgAiConversations","account","focusJob","setFocusJob","URLSearchParams","window","location","search","get","focused","setFocused","focusOpened","data","user","userRef","current","setKind","rows","setRows","cursors","setCursors","page","setPage","next","setNext","loading","setLoading","setError","load","target","targetPage","after","jobId","params","set","response","payload","json","catch","ok","existing","kept","show","signedIn","document","getElementById","scrollIntoView","block","id","scrollMarginTop","header","help","anchor","excerpt","HeaderProps","action","onClick","exclusive","onChange","_","aria-label","contentGutterX","contentGutterY","divider","flexItem","pageSize","rowCount","hasMore","disabled","onPageChange","displayName"],"mappings":"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"}
@@ -87,7 +87,8 @@ const orgHref = (orgId)=>buildRoute(Route.ADMIN_ORG_DETAIL, {
87
87
  quick: {
88
88
  icon: mdiOpenInNew.path,
89
89
  label: 'View organization',
90
- to: orgHref(row.orgId)
90
+ to: orgHref(row.orgId),
91
+ newTab: true
91
92
  },
92
93
  items: []
93
94
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-user-ai-usage-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiUsageMonthLabel } from '../usage/ai-usage-wire'\nimport type { StaffAiAccountAllowance } from '../usage/staff-org-ai'\nimport {\n StaffCreditReturnActions,\n StaffCreditReturnDialog,\n} from './staff-ai-credit-return.component'\n\n/** One row, as `/api/ai/admin/user` answers it. */\nexport interface StaffUserAiUsageRow {\n orgId: string\n orgName: string | null\n slug: string | null\n month: string\n credits: number\n requests: number\n refusals: number\n}\n\nconst orgHref = (orgId: string) => buildRoute(Route.ADMIN_ORG_DETAIL, { orgId })\n\n/** A row's id: one workspace's one month. */\nconst rowId = (row: StaffUserAiUsageRow) => `${row.orgId}:${row.month}`\n\n/**\n * One row per workspace per month kept. The row opens the workspace; the\n * month sorts by its key, which orders chronologically.\n */\nconst COLUMNS: GridColDef<StaffUserAiUsageRow>[] = [\n {\n field: 'orgName',\n headerName: 'Organization',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, row) => row.orgName ?? row.orgId,\n },\n {\n field: 'month',\n headerName: 'Month',\n width: 140,\n valueFormatter: (value: string) => aiUsageMonthLabel(value),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n listActionsColumn((row: StaffUserAiUsageRow) => (\n <ListRowActions\n label={row.orgName ?? row.orgId}\n quick={{ icon: mdiOpenInNew.path, label: 'View organization', to: orgHref(row.orgId) }}\n items={[]}\n />\n )),\n]\n\n/**\n * ONE ACCOUNT'S AI USAGE ACROSS ORGANIZATIONS (AGL-2928), on the staff user\n * page: workspace, month, credits — every month kept, newest first.\n *\n * Read on mount, and it mounts only for a confirmed staff reader: the\n * `staffUser` zone sits inside the page's `StaffOnly`, which renders nothing\n * while the claim is still loading. The route records an access row about\n * this person on every open, so an open the page did not mean to make would\n * be an access nobody made.\n *\n * Above the table, the account's own Free allowance this month — what every\n * free workspace it owns draws on — and in the header the two acts that give\n * credits back to it (AGL-3595).\n */\nconst StaffUserAiUsageCard = ({ uid }: { uid: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity (AGL-2928).\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [rows, setRows] = useState<StaffUserAiUsageRow[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [ready, setReady] = useState(false)\n const [allowance, setAllowance] = useState<StaffAiAccountAllowance | null>(null)\n // Bumped after a give-back, so the card re-reads the allowance it changed.\n const [version, setVersion] = useState(0)\n const [notice, setNotice] = useState<string | null>(null)\n const [returning, setReturning] = useState<'give' | 'reset' | null>(null)\n const router = useRouter()\n\n useEffect(() => {\n const user = userRef.current\n if (!uid || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/user?uid=${encodeURIComponent(uid)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI usage lookup failed')\n setRows(null)\n } else {\n setRows((payload?.rows ?? []) as StaffUserAiUsageRow[])\n // Absent from a route older than give-backs.\n setAllowance((payload?.allowance ?? null) as StaffAiAccountAllowance | null)\n }\n } catch {\n if (active) {\n setError('AI usage lookup failed')\n setRows(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [uid, signedInUid, version])\n\n return (\n <CardDisplay\n header={`${aiAddonName()} usage across organizations`}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#one-account',\n excerpt:\n 'This account’s AI credits in every workspace it belongs to, month by month. Opening it is recorded as a staff access about this person.',\n })}\n HeaderProps={{\n action: (\n <StaffCreditReturnActions\n disabled={!allowance}\n onGive={() => setReturning('give')}\n onReset={() => setReturning('reset')}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {allowance ? (\n <StaffCreditReturnDialog\n open={returning !== null}\n mode={returning ?? 'give'}\n target={{ uid }}\n workspace={null}\n account={{ used: allowance.usedCredits, limit: allowance.limitCredits }}\n onClose={() => setReturning(null)}\n onReturned={(message) => {\n setReturning(null)\n setNotice(message)\n setVersion((current) => current + 1)\n }}\n />\n ) : null}\n {notice ? (\n <Alert severity=\"success\" onClose={() => setNotice(null)} sx={{ mb: 2 }}>\n {notice}\n </Alert>\n ) : null}\n {allowance ? (\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n {`Free AI allowance this month: ${allowance.usedCredits.toLocaleString()} of ${allowance.limitCredits.toLocaleString()} credits used across the free workspaces this account owns` +\n (allowance.returnedCredits\n ? ` (${allowance.returnedCredits.toLocaleString()} given back)`\n : '') +\n '.'}\n </Typography>\n ) : null}\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !rows ? (\n <Alert severity=\"warning\">\n {`Could not read this account’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to this account in any workspace it belongs to.'}\n </Typography>\n ) : (\n // Every month kept is in hand — at most fifty workspaces times the\n // retention window — so the grid pages and sorts the one read, and\n // offers no filter or search: a bounded readout is read whole, not\n // narrowed over the rows it holds (AGL-3321).\n <ListTable\n rows={rows}\n columns={COLUMNS}\n getRowId={rowId}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row: StaffUserAiUsageRow) => router.push(orgHref(row.orgId))}\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </CardDisplay>\n )\n}\nStaffUserAiUsageCard.displayName = 'StaffUserAiUsageCard'\n\nexport default StaffUserAiUsageCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","authorizedFetch","useUser","Alert","Typography","useRouter","useEffect","useRef","useState","aiUsageMonthLabel","StaffCreditReturnActions","StaffCreditReturnDialog","orgHref","orgId","ADMIN_ORG_DETAIL","rowId","row","month","COLUMNS","field","headerName","flex","minWidth","valueGetter","_value","orgName","width","valueFormatter","value","type","align","headerAlign","toLocaleString","label","quick","icon","path","to","items","StaffUserAiUsageCard","uid","data","user","signedInUid","userRef","current","rows","setRows","error","setError","ready","setReady","allowance","setAllowance","version","setVersion","notice","setNotice","returning","setReturning","router","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","HeaderProps","action","disabled","onGive","onReset","contentGutterX","contentGutterY","open","mode","target","workspace","account","used","usedCredits","limit","limitCredits","onClose","onReturned","message","severity","sx","mb","variant","returnedCredits","color","length","columns","getRowId","rowHeight","onOpen","_id","push","disableColumnFilter","quickFilter","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-user-ai-usage-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiUsageMonthLabel } from '../usage/ai-usage-wire'\nimport type { StaffAiAccountAllowance } from '../usage/staff-org-ai'\nimport {\n StaffCreditReturnActions,\n StaffCreditReturnDialog,\n} from './staff-ai-credit-return.component'\n\n/** One row, as `/api/ai/admin/user` answers it. */\nexport interface StaffUserAiUsageRow {\n orgId: string\n orgName: string | null\n slug: string | null\n month: string\n credits: number\n requests: number\n refusals: number\n}\n\nconst orgHref = (orgId: string) => buildRoute(Route.ADMIN_ORG_DETAIL, { orgId })\n\n/** A row's id: one workspace's one month. */\nconst rowId = (row: StaffUserAiUsageRow) => `${row.orgId}:${row.month}`\n\n/**\n * One row per workspace per month kept. The row opens the workspace; the\n * month sorts by its key, which orders chronologically.\n */\nconst COLUMNS: GridColDef<StaffUserAiUsageRow>[] = [\n {\n field: 'orgName',\n headerName: 'Organization',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, row) => row.orgName ?? row.orgId,\n },\n {\n field: 'month',\n headerName: 'Month',\n width: 140,\n valueFormatter: (value: string) => aiUsageMonthLabel(value),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n listActionsColumn((row: StaffUserAiUsageRow) => (\n <ListRowActions\n label={row.orgName ?? row.orgId}\n quick={{ icon: mdiOpenInNew.path, label: 'View organization', to: orgHref(row.orgId), newTab: true }}\n items={[]}\n />\n )),\n]\n\n/**\n * ONE ACCOUNT'S AI USAGE ACROSS ORGANIZATIONS (AGL-2928), on the staff user\n * page: workspace, month, credits — every month kept, newest first.\n *\n * Read on mount, and it mounts only for a confirmed staff reader: the\n * `staffUser` zone sits inside the page's `StaffOnly`, which renders nothing\n * while the claim is still loading. The route records an access row about\n * this person on every open, so an open the page did not mean to make would\n * be an access nobody made.\n *\n * Above the table, the account's own Free allowance this month — what every\n * free workspace it owns draws on — and in the header the two acts that give\n * credits back to it (AGL-3595).\n */\nconst StaffUserAiUsageCard = ({ uid }: { uid: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity (AGL-2928).\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [rows, setRows] = useState<StaffUserAiUsageRow[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [ready, setReady] = useState(false)\n const [allowance, setAllowance] = useState<StaffAiAccountAllowance | null>(null)\n // Bumped after a give-back, so the card re-reads the allowance it changed.\n const [version, setVersion] = useState(0)\n const [notice, setNotice] = useState<string | null>(null)\n const [returning, setReturning] = useState<'give' | 'reset' | null>(null)\n const router = useRouter()\n\n useEffect(() => {\n const user = userRef.current\n if (!uid || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/user?uid=${encodeURIComponent(uid)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI usage lookup failed')\n setRows(null)\n } else {\n setRows((payload?.rows ?? []) as StaffUserAiUsageRow[])\n // Absent from a route older than give-backs.\n setAllowance((payload?.allowance ?? null) as StaffAiAccountAllowance | null)\n }\n } catch {\n if (active) {\n setError('AI usage lookup failed')\n setRows(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [uid, signedInUid, version])\n\n return (\n <CardDisplay\n header={`${aiAddonName()} usage across organizations`}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#one-account',\n excerpt:\n 'This account’s AI credits in every workspace it belongs to, month by month. Opening it is recorded as a staff access about this person.',\n })}\n HeaderProps={{\n action: (\n <StaffCreditReturnActions\n disabled={!allowance}\n onGive={() => setReturning('give')}\n onReset={() => setReturning('reset')}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {allowance ? (\n <StaffCreditReturnDialog\n open={returning !== null}\n mode={returning ?? 'give'}\n target={{ uid }}\n workspace={null}\n account={{ used: allowance.usedCredits, limit: allowance.limitCredits }}\n onClose={() => setReturning(null)}\n onReturned={(message) => {\n setReturning(null)\n setNotice(message)\n setVersion((current) => current + 1)\n }}\n />\n ) : null}\n {notice ? (\n <Alert severity=\"success\" onClose={() => setNotice(null)} sx={{ mb: 2 }}>\n {notice}\n </Alert>\n ) : null}\n {allowance ? (\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n {`Free AI allowance this month: ${allowance.usedCredits.toLocaleString()} of ${allowance.limitCredits.toLocaleString()} credits used across the free workspaces this account owns` +\n (allowance.returnedCredits\n ? ` (${allowance.returnedCredits.toLocaleString()} given back)`\n : '') +\n '.'}\n </Typography>\n ) : null}\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !rows ? (\n <Alert severity=\"warning\">\n {`Could not read this account’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to this account in any workspace it belongs to.'}\n </Typography>\n ) : (\n // Every month kept is in hand — at most fifty workspaces times the\n // retention window — so the grid pages and sorts the one read, and\n // offers no filter or search: a bounded readout is read whole, not\n // narrowed over the rows it holds (AGL-3321).\n <ListTable\n rows={rows}\n columns={COLUMNS}\n getRowId={rowId}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row: StaffUserAiUsageRow) => router.push(orgHref(row.orgId))}\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </CardDisplay>\n )\n}\nStaffUserAiUsageCard.displayName = 'StaffUserAiUsageCard'\n\nexport default StaffUserAiUsageCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","authorizedFetch","useUser","Alert","Typography","useRouter","useEffect","useRef","useState","aiUsageMonthLabel","StaffCreditReturnActions","StaffCreditReturnDialog","orgHref","orgId","ADMIN_ORG_DETAIL","rowId","row","month","COLUMNS","field","headerName","flex","minWidth","valueGetter","_value","orgName","width","valueFormatter","value","type","align","headerAlign","toLocaleString","label","quick","icon","path","to","newTab","items","StaffUserAiUsageCard","uid","data","user","signedInUid","userRef","current","rows","setRows","error","setError","ready","setReady","allowance","setAllowance","version","setVersion","notice","setNotice","returning","setReturning","router","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","HeaderProps","action","disabled","onGive","onReset","contentGutterX","contentGutterY","open","mode","target","workspace","account","used","usedCredits","limit","limitCredits","onClose","onReturned","message","severity","sx","mb","variant","returnedCredits","color","length","columns","getRowId","rowHeight","onOpen","_id","push","disableColumnFilter","quickFilter","displayName"],"mappings":"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"}
@@ -59,7 +59,8 @@ export interface AiJobAdmissionContext {
59
59
  uid?: string | null;
60
60
  }
61
61
  export interface AiJobAdmissionRefusal {
62
- status: 400 | 403 | 404;
62
+ /** 429: what the month's AI credits have left cannot pay for it (AGL-3660). */
63
+ status: 400 | 403 | 404 | 429;
63
64
  /** Customer-safe: the door answers with it as it stands. */
64
65
  error: string;
65
66
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-admission.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiJobKind, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_OFF_FOR_SITE_COPY,\n AI_OFF_FOR_SITE_STATUS,\n isAiOffForSite,\n} from '../model/ai-site-switch'\n\n/**\n * What a job kind checks before a job of it runs (AGL-2909).\n *\n * The create door parses the body every kind shares — the org, the site, the\n * brief, scalar inputs — and nothing the machine knows says whether THIS kind\n * can finish: a layout needs a site to write to, a page template names the\n * collection it renders, and both write a document the site's plan counts.\n * A job that cannot finish should never start, because a planned kind spends\n * on its plan before it writes anything. So a kind registers one check, the\n * create door asks it after the gate ladder admitted the request and before\n * the job exists, and the resume door asks it again before a confirmed plan\n * runs, since an allowance free at creation may be used by now. Either way\n * a refusal hands the reservation back and spends nothing.\n *\n * The resume door also hands the check the plan being confirmed (AGL-2907),\n * so a kind that can build only some plans — a page job builds one screen\n * from what the site has — refuses the others when a member confirms them,\n * not a beat later from inside the step.\n *\n * The registry lives apart from the machine so the doors read it without the\n * generators, and a generator registers here beside its runner.\n */\n\nexport interface AiJobAdmissionContext {\n /** The Admin SDK handle the door runs on. */\n firestore: FirebaseFirestore.Firestore\n orgId: string\n hostId: string | null\n /** The job's kind-specific inputs, as the door parsed or the job stored them. */\n inputs: Readonly<Record<string, unknown>>\n /** The org document the gate ladder read; a check narrows it to the fields it reads. */\n org: object | null\n /**\n * The job's plan when the door is resuming a job that has one; absent at\n * creation, when no plan exists yet.\n */\n plan?: AiJobPlan | null\n /**\n * The member whose request spends: the creator at the create door, the\n * member confirming at the resume door. A kind whose draft another plugin\n * writes asks that plugin whether this member may create one.\n */\n uid?: string | null\n}\n\nexport interface AiJobAdmissionRefusal {\n status: 400 | 403 | 404\n /** Customer-safe: the door answers with it as it stands. */\n error: string\n}\n\nexport type AiJobAdmission = (\n context: AiJobAdmissionContext,\n) => Promise<AiJobAdmissionRefusal | null>\n\nconst admissions = new Map<AiJobKind, AiJobAdmission>()\n\n/** Idempotent per kind; the last registration wins, and `null` unregisters. */\nexport function registerAiJobAdmission(kind: AiJobKind, admission: AiJobAdmission | null): void {\n if (admission) admissions.set(kind, admission)\n else admissions.delete(kind)\n}\n\n/** The kind's registered admission, or `null` when it has none. */\nexport function aiJobAdmissionFor(kind: AiJobKind): AiJobAdmission | null {\n return admissions.get(kind) ?? null\n}\n\n/** The kind's refusal, or `null` when it admits the job or has nothing to check. */\nexport async function aiJobAdmissionRefusal(\n kind: AiJobKind,\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const admission = admissions.get(kind)\n return admission ? admission(context) : null\n}\n\n/**\n * The refusal for a job on a site that switched AI off (AGL-3028), or `null`.\n *\n * Every kind meets it, and every door asks it BEFORE the kind's own check —\n * the create door, each site of an agency batch, and a resume — so no job\n * comes to exist, or runs another step, for a site that said no, and a\n * switched-off site never pays for a kind's reads. A job with no site is\n * workspace-level and always passes.\n */\nexport async function aiJobSiteRefusal(\n context: Pick<AiJobAdmissionContext, 'firestore' | 'org' | 'hostId'>,\n): Promise<AiJobAdmissionRefusal | null> {\n return (await isAiOffForSite(context.firestore, context.org, context.hostId))\n ? { status: AI_OFF_FOR_SITE_STATUS, error: AI_OFF_FOR_SITE_COPY }\n : null\n}\n"],"names":["AI_OFF_FOR_SITE_COPY","AI_OFF_FOR_SITE_STATUS","isAiOffForSite","admissions","Map","registerAiJobAdmission","kind","admission","set","delete","aiJobAdmissionFor","get","aiJobAdmissionRefusal","context","aiJobSiteRefusal","firestore","org","hostId","status","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,oBAAoB,EACpBC,sBAAsB,EACtBC,cAAc,QACT,6BAAyB;AAyDhC,MAAMC,aAAa,IAAIC;AAEvB,6EAA6E,GAC7E,OAAO,SAASC,uBAAuBC,IAAe,EAAEC,SAAgC;IACtF,IAAIA,WAAWJ,WAAWK,GAAG,CAACF,MAAMC;SAC/BJ,WAAWM,MAAM,CAACH;AACzB;AAEA,iEAAiE,GACjE,OAAO,SAASI,kBAAkBJ,IAAe;QACxCH;IAAP,QAAOA,kBAAAA,WAAWQ,GAAG,CAACL,iBAAfH,kBAAwB;AACjC;AAEA,kFAAkF,GAClF,OAAO,eAAeS,sBACpBN,IAAe,EACfO,OAA8B;IAE9B,MAAMN,YAAYJ,WAAWQ,GAAG,CAACL;IACjC,OAAOC,YAAYA,UAAUM,WAAW;AAC1C;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeC,iBACpBD,OAAoE;IAEpE,OAAO,AAAC,MAAMX,eAAeW,QAAQE,SAAS,EAAEF,QAAQG,GAAG,EAAEH,QAAQI,MAAM,IACvE;QAAEC,QAAQjB;QAAwBkB,OAAOnB;IAAqB,IAC9D;AACN"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-admission.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiJobKind, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_OFF_FOR_SITE_COPY,\n AI_OFF_FOR_SITE_STATUS,\n isAiOffForSite,\n} from '../model/ai-site-switch'\n\n/**\n * What a job kind checks before a job of it runs (AGL-2909).\n *\n * The create door parses the body every kind shares — the org, the site, the\n * brief, scalar inputs — and nothing the machine knows says whether THIS kind\n * can finish: a layout needs a site to write to, a page template names the\n * collection it renders, and both write a document the site's plan counts.\n * A job that cannot finish should never start, because a planned kind spends\n * on its plan before it writes anything. So a kind registers one check, the\n * create door asks it after the gate ladder admitted the request and before\n * the job exists, and the resume door asks it again before a confirmed plan\n * runs, since an allowance free at creation may be used by now. Either way\n * a refusal hands the reservation back and spends nothing.\n *\n * The resume door also hands the check the plan being confirmed (AGL-2907),\n * so a kind that can build only some plans — a page job builds one screen\n * from what the site has — refuses the others when a member confirms them,\n * not a beat later from inside the step.\n *\n * The registry lives apart from the machine so the doors read it without the\n * generators, and a generator registers here beside its runner.\n */\n\nexport interface AiJobAdmissionContext {\n /** The Admin SDK handle the door runs on. */\n firestore: FirebaseFirestore.Firestore\n orgId: string\n hostId: string | null\n /** The job's kind-specific inputs, as the door parsed or the job stored them. */\n inputs: Readonly<Record<string, unknown>>\n /** The org document the gate ladder read; a check narrows it to the fields it reads. */\n org: object | null\n /**\n * The job's plan when the door is resuming a job that has one; absent at\n * creation, when no plan exists yet.\n */\n plan?: AiJobPlan | null\n /**\n * The member whose request spends: the creator at the create door, the\n * member confirming at the resume door. A kind whose draft another plugin\n * writes asks that plugin whether this member may create one.\n */\n uid?: string | null\n}\n\nexport interface AiJobAdmissionRefusal {\n /** 429: what the month's AI credits have left cannot pay for it (AGL-3660). */\n status: 400 | 403 | 404 | 429\n /** Customer-safe: the door answers with it as it stands. */\n error: string\n}\n\nexport type AiJobAdmission = (\n context: AiJobAdmissionContext,\n) => Promise<AiJobAdmissionRefusal | null>\n\nconst admissions = new Map<AiJobKind, AiJobAdmission>()\n\n/** Idempotent per kind; the last registration wins, and `null` unregisters. */\nexport function registerAiJobAdmission(kind: AiJobKind, admission: AiJobAdmission | null): void {\n if (admission) admissions.set(kind, admission)\n else admissions.delete(kind)\n}\n\n/** The kind's registered admission, or `null` when it has none. */\nexport function aiJobAdmissionFor(kind: AiJobKind): AiJobAdmission | null {\n return admissions.get(kind) ?? null\n}\n\n/** The kind's refusal, or `null` when it admits the job or has nothing to check. */\nexport async function aiJobAdmissionRefusal(\n kind: AiJobKind,\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const admission = admissions.get(kind)\n return admission ? admission(context) : null\n}\n\n/**\n * The refusal for a job on a site that switched AI off (AGL-3028), or `null`.\n *\n * Every kind meets it, and every door asks it BEFORE the kind's own check —\n * the create door, each site of an agency batch, and a resume — so no job\n * comes to exist, or runs another step, for a site that said no, and a\n * switched-off site never pays for a kind's reads. A job with no site is\n * workspace-level and always passes.\n */\nexport async function aiJobSiteRefusal(\n context: Pick<AiJobAdmissionContext, 'firestore' | 'org' | 'hostId'>,\n): Promise<AiJobAdmissionRefusal | null> {\n return (await isAiOffForSite(context.firestore, context.org, context.hostId))\n ? { status: AI_OFF_FOR_SITE_STATUS, error: AI_OFF_FOR_SITE_COPY }\n : null\n}\n"],"names":["AI_OFF_FOR_SITE_COPY","AI_OFF_FOR_SITE_STATUS","isAiOffForSite","admissions","Map","registerAiJobAdmission","kind","admission","set","delete","aiJobAdmissionFor","get","aiJobAdmissionRefusal","context","aiJobSiteRefusal","firestore","org","hostId","status","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,oBAAoB,EACpBC,sBAAsB,EACtBC,cAAc,QACT,6BAAyB;AA0DhC,MAAMC,aAAa,IAAIC;AAEvB,6EAA6E,GAC7E,OAAO,SAASC,uBAAuBC,IAAe,EAAEC,SAAgC;IACtF,IAAIA,WAAWJ,WAAWK,GAAG,CAACF,MAAMC;SAC/BJ,WAAWM,MAAM,CAACH;AACzB;AAEA,iEAAiE,GACjE,OAAO,SAASI,kBAAkBJ,IAAe;QACxCH;IAAP,QAAOA,kBAAAA,WAAWQ,GAAG,CAACL,iBAAfH,kBAAwB;AACjC;AAEA,kFAAkF,GAClF,OAAO,eAAeS,sBACpBN,IAAe,EACfO,OAA8B;IAE9B,MAAMN,YAAYJ,WAAWQ,GAAG,CAACL;IACjC,OAAOC,YAAYA,UAAUM,WAAW;AAC1C;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeC,iBACpBD,OAAoE;IAEpE,OAAO,AAAC,MAAMX,eAAeW,QAAQE,SAAS,EAAEF,QAAQG,GAAG,EAAEH,QAAQI,MAAM,IACvE;QAAEC,QAAQjB;QAAwBkB,OAAOnB;IAAqB,IAC9D;AACN"}