@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
@@ -28,6 +28,8 @@ export interface StaffAiConversationPerson {
28
28
  }
29
29
  export interface StaffAiAssistRow {
30
30
  id: string;
31
+ /** The organization the exchange is filed under (AGL-3660). */
32
+ orgId: string | null;
31
33
  at: string | null;
32
34
  by: StaffAiConversationPerson;
33
35
  hostId: string | null;
@@ -47,6 +49,12 @@ export interface StaffAiJobOutputRow {
47
49
  resource: string | null;
48
50
  hostSubdomain: string | null;
49
51
  }
52
+ /** One field a job's creator filled in, as staff read it. */
53
+ export interface StaffAiJobInput {
54
+ key: string;
55
+ label: string;
56
+ value: string;
57
+ }
50
58
  /** A result kept beside the job rather than as a draft. */
51
59
  export type StaffAiJobResult = {
52
60
  kind: 'insight';
@@ -67,12 +75,19 @@ export type StaffAiJobResult = {
67
75
  };
68
76
  export interface StaffAiJobRow {
69
77
  id: string;
78
+ /** The organization the job is filed under (AGL-3660). */
79
+ orgId: string | null;
70
80
  at: string | null;
71
81
  by: StaffAiConversationPerson;
72
82
  hostId: string | null;
73
83
  kind: string;
74
84
  status: string;
75
85
  brief: string;
86
+ /**
87
+ * What the person filled in beside the brief (AGL-3660) — the guided
88
+ * start's form fields, a tone, a page to edit — as label and value.
89
+ */
90
+ inputs: StaffAiJobInput[];
76
91
  credits: number;
77
92
  error: string | null;
78
93
  outputs: StaffAiJobOutputRow[];
@@ -98,6 +113,14 @@ export declare function staffAiInsightResult(record: Data | null): StaffAiJobRes
98
113
  export declare function staffAiCrmResult(record: Data | null): StaffAiJobResult | null;
99
114
  /** The collection a job kind keeps its result beside the job in, or `null` for a kind whose result is a draft. */
100
115
  export declare function staffAiJobResultCollection(kind: unknown): 'aiInsights' | 'aiCrmAnswers' | null;
116
+ /** Fields read off one job's inputs, at most. */
117
+ export declare const STAFF_AI_JOB_INPUTS_MAX = 40;
118
+ /**
119
+ * What a job's creator filled in beside the brief (AGL-3660), as fields:
120
+ * scalars and lists as written, one level of a nested object flattened to
121
+ * `parent child`. A value is cut at {@link INPUT_VALUE_MAX} characters.
122
+ */
123
+ export declare function staffAiJobInputs(inputs: unknown): StaffAiJobInput[];
101
124
  /** One job, with the result kept beside it when its kind keeps one. */
102
125
  export declare function composeStaffAiJobRow(id: string, job: Data, result: StaffAiJobResult | null, members: ReadonlyMap<string, Data>): StaffAiJobRow;
103
126
  export {};
@@ -70,6 +70,7 @@ const finite = (value)=>typeof value === 'number' && Number.isFinite(value) ? va
70
70
  const feedback = signal == null ? void 0 : signal['feedback'];
71
71
  return {
72
72
  id,
73
+ orgId: textOrNull(exchange['orgId']),
73
74
  at: staffAiConversationTime(exchange['createdAt']),
74
75
  by: staffAiConversationPerson(exchange['uid'], members),
75
76
  hostId: textOrNull(exchange['hostId']),
@@ -126,17 +127,61 @@ const finite = (value)=>typeof value === 'number' && Number.isFinite(value) ? va
126
127
  if (kind === 'crm') return 'aiCrmAnswers';
127
128
  return null;
128
129
  }
130
+ /** Fields read off one job's inputs, at most. */ export const STAFF_AI_JOB_INPUTS_MAX = 40;
131
+ /** Characters of one field's value shown. */ const INPUT_VALUE_MAX = 2000;
132
+ /** `businessName` → `Business name`. */ function inputLabel(key) {
133
+ const words = key.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_.-]+/g, ' ').trim().toLowerCase();
134
+ return words.charAt(0).toUpperCase() + words.slice(1);
135
+ }
136
+ function inputValue(value) {
137
+ if (value === null || value === undefined) return null;
138
+ if (typeof value === 'string') return value.trim() ? value : null;
139
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
140
+ if (Array.isArray(value)) {
141
+ const parts = value.map(inputValue).filter((part)=>Boolean(part));
142
+ return parts.length ? parts.join(', ') : null;
143
+ }
144
+ return null;
145
+ }
146
+ /**
147
+ * What a job's creator filled in beside the brief (AGL-3660), as fields:
148
+ * scalars and lists as written, one level of a nested object flattened to
149
+ * `parent child`. A value is cut at {@link INPUT_VALUE_MAX} characters.
150
+ */ export function staffAiJobInputs(inputs) {
151
+ if (!inputs || typeof inputs !== 'object' || Array.isArray(inputs)) return [];
152
+ const out = [];
153
+ const push = (key, raw)=>{
154
+ if (out.length >= STAFF_AI_JOB_INPUTS_MAX) return;
155
+ const value = inputValue(raw);
156
+ if (value === null) return;
157
+ out.push({
158
+ key,
159
+ label: inputLabel(key),
160
+ value: value.length > INPUT_VALUE_MAX ? `${value.slice(0, INPUT_VALUE_MAX)}…` : value
161
+ });
162
+ };
163
+ for (const [key, raw] of Object.entries(inputs)){
164
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
165
+ for (const [child, nested] of Object.entries(raw))push(`${key}.${child}`, nested);
166
+ } else {
167
+ push(key, raw);
168
+ }
169
+ }
170
+ return out;
171
+ }
129
172
  /** One job, with the result kept beside it when its kind keeps one. */ export function composeStaffAiJobRow(id, job, result, members) {
130
173
  var _finite;
131
174
  const outputs = Array.isArray(job['outputs']) ? job['outputs'] : [];
132
175
  return {
133
176
  id,
177
+ orgId: textOrNull(job['orgId']),
134
178
  at: staffAiConversationTime(job['createdAt']),
135
179
  by: staffAiConversationPerson(job['createdBy'], members),
136
180
  hostId: textOrNull(job['hostId']),
137
181
  kind: text(job['kind']) || 'unknown',
138
182
  status: text(job['status']) || 'unknown',
139
183
  brief: text(job['brief']),
184
+ inputs: staffAiJobInputs(job['inputs']),
140
185
  credits: (_finite = finite(job['creditsSpent'])) != null ? _finite : 0,
141
186
  error: textOrNull(job['error']),
142
187
  outputs: outputs.map((output)=>({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-ai-conversations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * WHAT A WORKSPACE ASKED AGLYN AI, FOR STAFF (AGL-3675).\n *\n * The staff org page showed what an org's AI cost and how many jobs ran,\n * never what anybody asked or what came back. Those words are kept — an\n * Assist exchange for 180 days, a job's brief and result for as long as the\n * job — and the Privacy policy says they are accessible to us, but nothing\n * read them, so a stalled sign-up's question could only be found in the\n * database by hand.\n *\n * These are the rows the staff card lists, composed from the stored\n * documents by pure functions so the shape is tested without Firestore:\n *\n * - an Assist row joins `assistExchanges/{id}` (the question, the answer,\n * who asked) with `assistSignals/{id}` (the console route, the model, the\n * cost and the thumbs), which share the id by design;\n * - a job row reads `aiJobs/{id}` (the brief, the kind, the status, the\n * credits, the outputs) and, for the kinds whose result is kept beside\n * the job rather than as a draft, that result: an insight's answer or a\n * CRM answer.\n *\n * The text is shown to the staff member who asked for it and goes nowhere\n * else. The audit row the route writes says that it was read, never what.\n *==========================================*/\n\n/** Rows a page lists; the route asks for one more to learn whether there is a next page. */\nexport const STAFF_AI_CONVERSATIONS_PAGE = 25\n\n/** The two lists the card switches between. */\nexport const STAFF_AI_CONVERSATION_KINDS = ['assist', 'jobs'] as const\nexport type StaffAiConversationKind = (typeof STAFF_AI_CONVERSATION_KINDS)[number]\n\nexport function isStaffAiConversationKind(value: unknown): value is StaffAiConversationKind {\n return typeof value === 'string' && (STAFF_AI_CONVERSATION_KINDS as readonly string[]).includes(value)\n}\n\n/** Who a row is from, as the org's member list names them. */\nexport interface StaffAiConversationPerson {\n uid: string | null\n email: string | null\n name: string | null\n}\n\nexport interface StaffAiAssistRow {\n id: string\n at: string | null\n by: StaffAiConversationPerson\n hostId: string | null\n /** The console route the question was asked from; `null` once the signal is gone or never said. */\n route: string | null\n question: string\n answer: string\n model: string | null\n estCostUsd: number | null\n /** The thumbs the person gave: `up`, `down` or `null`. */\n feedback: 'up' | 'down' | null\n}\n\n/** One thing a job made, as its output names it. */\nexport interface StaffAiJobOutputRow {\n label: string\n note: string | null\n resource: string | null\n hostSubdomain: string | null\n}\n\n/** A result kept beside the job rather than as a draft. */\nexport type StaffAiJobResult =\n | { kind: 'insight'; insights: string[]; gap: string | null }\n | { kind: 'crm-record'; summary: string; nextStep: string | null; standing: string | null }\n | { kind: 'crm-email'; subject: string; body: string }\n | { kind: 'crm-mapping'; matches: string[] }\n\nexport interface StaffAiJobRow {\n id: string\n at: string | null\n by: StaffAiConversationPerson\n hostId: string | null\n kind: string\n status: string\n brief: string\n credits: number\n error: string | null\n outputs: StaffAiJobOutputRow[]\n /** `null` when the kind keeps nothing beside the job, or the result has expired. */\n result: StaffAiJobResult | null\n}\n\nexport interface StaffAiConversationsResponse {\n kind: StaffAiConversationKind\n rows: StaffAiAssistRow[] | StaffAiJobRow[]\n /** The id to pass as `after` for the next page; `null` on the last. */\n next: string | null\n}\n\ntype Data = Record<string, unknown>\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\nconst textOrNull = (value: unknown): string | null => (typeof value === 'string' && value.trim() ? value : null)\nconst finite = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\n/** A stored time as ISO: a Firestore timestamp, a `Date`, milliseconds or an ISO string. */\nexport function staffAiConversationTime(value: unknown): string | null {\n if (value == null) return null\n if (typeof (value as { toDate?: unknown }).toDate === 'function') {\n return (value as { toDate: () => Date }).toDate().toISOString()\n }\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString()\n if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString()\n if (typeof value === 'string' && !Number.isNaN(Date.parse(value))) return new Date(value).toISOString()\n return null\n}\n\n/** The person a uid is, from `orgs/{org}/members/{uid}` documents keyed by uid. */\nexport function staffAiConversationPerson(\n uid: unknown,\n members: ReadonlyMap<string, Data>,\n): StaffAiConversationPerson {\n const id = typeof uid === 'string' && uid ? uid : null\n const member = id ? members.get(id) : undefined\n return {\n uid: id,\n email: textOrNull(member?.['email']),\n name: textOrNull(member?.['displayName']),\n }\n}\n\n/** One Assist exchange joined with its signal, which may already be gone or never have been written. */\nexport function composeStaffAiAssistRow(\n id: string,\n exchange: Data,\n signal: Data | null,\n members: ReadonlyMap<string, Data>,\n): StaffAiAssistRow {\n const feedback = signal?.['feedback']\n return {\n id,\n at: staffAiConversationTime(exchange['createdAt']),\n by: staffAiConversationPerson(exchange['uid'], members),\n hostId: textOrNull(exchange['hostId']),\n route: textOrNull(signal?.['route']),\n question: text(exchange['question']),\n answer: text(exchange['answer']),\n model: textOrNull(signal?.['model']),\n estCostUsd: finite(signal?.['estCostUsd']),\n feedback: feedback === 'up' || feedback === 'down' ? feedback : null,\n }\n}\n\n/** An insight's kept answer, as `aiInsights/{jobId}` stores it. */\nexport function staffAiInsightResult(record: Data | null): StaffAiJobResult | null {\n if (!record) return null\n const insights = Array.isArray(record['insights'])\n ? (record['insights'] as Data[]).map((insight) => text(insight?.['text'])).filter(Boolean)\n : []\n return { kind: 'insight', insights, gap: textOrNull(record['gap']) }\n}\n\n/** A CRM answer, as `aiCrmAnswers/{jobId}` stores it, by the proposal it holds. */\nexport function staffAiCrmResult(record: Data | null): StaffAiJobResult | null {\n const proposal = record?.['proposal'] as Data | undefined\n if (!proposal) return null\n switch (proposal['kind']) {\n case 'record': {\n const nextStep = proposal['nextStep'] as Data | null | undefined\n const title = textOrNull(nextStep?.['title'])\n const reason = textOrNull(nextStep?.['reason'])\n return {\n kind: 'crm-record',\n summary: text(proposal['summary']),\n nextStep: title ? (reason ? `${title} — ${reason}` : title) : null,\n standing: textOrNull(proposal['standing']),\n }\n }\n case 'email':\n return { kind: 'crm-email', subject: text(proposal['subject']), body: text(proposal['body']) }\n case 'mapping':\n return {\n kind: 'crm-mapping',\n matches: Array.isArray(proposal['matches'])\n ? (proposal['matches'] as Data[]).map((match) => `${text(match?.['header'])} → ${text(match?.['label'])}`)\n : [],\n }\n default:\n return null\n }\n}\n\n/** The collection a job kind keeps its result beside the job in, or `null` for a kind whose result is a draft. */\nexport function staffAiJobResultCollection(kind: unknown): 'aiInsights' | 'aiCrmAnswers' | null {\n if (kind === 'insight') return 'aiInsights'\n if (kind === 'crm') return 'aiCrmAnswers'\n return null\n}\n\n/** One job, with the result kept beside it when its kind keeps one. */\nexport function composeStaffAiJobRow(\n id: string,\n job: Data,\n result: StaffAiJobResult | null,\n members: ReadonlyMap<string, Data>,\n): StaffAiJobRow {\n const outputs = Array.isArray(job['outputs']) ? (job['outputs'] as Data[]) : []\n return {\n id,\n at: staffAiConversationTime(job['createdAt']),\n by: staffAiConversationPerson(job['createdBy'], members),\n hostId: textOrNull(job['hostId']),\n kind: text(job['kind']) || 'unknown',\n status: text(job['status']) || 'unknown',\n brief: text(job['brief']),\n credits: finite(job['creditsSpent']) ?? 0,\n error: textOrNull(job['error']),\n outputs: outputs.map((output) => ({\n label: text(output?.['label']) || text(output?.['resource']) || 'Output',\n note: textOrNull(output?.['note']),\n resource: textOrNull(output?.['resource']),\n hostSubdomain: textOrNull(output?.['hostSubdomain']),\n })),\n result,\n }\n}\n"],"names":["STAFF_AI_CONVERSATIONS_PAGE","STAFF_AI_CONVERSATION_KINDS","isStaffAiConversationKind","value","includes","text","textOrNull","trim","finite","Number","isFinite","staffAiConversationTime","toDate","toISOString","Date","isNaN","getTime","parse","staffAiConversationPerson","uid","members","id","member","get","undefined","email","name","composeStaffAiAssistRow","exchange","signal","feedback","at","by","hostId","route","question","answer","model","estCostUsd","staffAiInsightResult","record","insights","Array","isArray","map","insight","filter","Boolean","kind","gap","staffAiCrmResult","proposal","nextStep","title","reason","summary","standing","subject","body","matches","match","staffAiJobResultCollection","composeStaffAiJobRow","job","result","outputs","status","brief","credits","error","output","label","note","resource","hostSubdomain"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-ai-conversations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * WHAT A WORKSPACE ASKED AGLYN AI, FOR STAFF (AGL-3675).\n *\n * The staff org page showed what an org's AI cost and how many jobs ran,\n * never what anybody asked or what came back. Those words are kept — an\n * Assist exchange for 180 days, a job's brief and result for as long as the\n * job — and the Privacy policy says they are accessible to us, but nothing\n * read them, so a stalled sign-up's question could only be found in the\n * database by hand.\n *\n * These are the rows the staff card lists, composed from the stored\n * documents by pure functions so the shape is tested without Firestore:\n *\n * - an Assist row joins `assistExchanges/{id}` (the question, the answer,\n * who asked) with `assistSignals/{id}` (the console route, the model, the\n * cost and the thumbs), which share the id by design;\n * - a job row reads `aiJobs/{id}` (the brief, the kind, the status, the\n * credits, the outputs) and, for the kinds whose result is kept beside\n * the job rather than as a draft, that result: an insight's answer or a\n * CRM answer.\n *\n * The text is shown to the staff member who asked for it and goes nowhere\n * else. The audit row the route writes says that it was read, never what.\n *==========================================*/\n\n/** Rows a page lists; the route asks for one more to learn whether there is a next page. */\nexport const STAFF_AI_CONVERSATIONS_PAGE = 25\n\n/** The two lists the card switches between. */\nexport const STAFF_AI_CONVERSATION_KINDS = ['assist', 'jobs'] as const\nexport type StaffAiConversationKind = (typeof STAFF_AI_CONVERSATION_KINDS)[number]\n\nexport function isStaffAiConversationKind(value: unknown): value is StaffAiConversationKind {\n return typeof value === 'string' && (STAFF_AI_CONVERSATION_KINDS as readonly string[]).includes(value)\n}\n\n/** Who a row is from, as the org's member list names them. */\nexport interface StaffAiConversationPerson {\n uid: string | null\n email: string | null\n name: string | null\n}\n\nexport interface StaffAiAssistRow {\n id: string\n /** The organization the exchange is filed under (AGL-3660). */\n orgId: string | null\n at: string | null\n by: StaffAiConversationPerson\n hostId: string | null\n /** The console route the question was asked from; `null` once the signal is gone or never said. */\n route: string | null\n question: string\n answer: string\n model: string | null\n estCostUsd: number | null\n /** The thumbs the person gave: `up`, `down` or `null`. */\n feedback: 'up' | 'down' | null\n}\n\n/** One thing a job made, as its output names it. */\nexport interface StaffAiJobOutputRow {\n label: string\n note: string | null\n resource: string | null\n hostSubdomain: string | null\n}\n\n/** One field a job's creator filled in, as staff read it. */\nexport interface StaffAiJobInput {\n key: string\n label: string\n value: string\n}\n\n/** A result kept beside the job rather than as a draft. */\nexport type StaffAiJobResult =\n | { kind: 'insight'; insights: string[]; gap: string | null }\n | { kind: 'crm-record'; summary: string; nextStep: string | null; standing: string | null }\n | { kind: 'crm-email'; subject: string; body: string }\n | { kind: 'crm-mapping'; matches: string[] }\n\nexport interface StaffAiJobRow {\n id: string\n /** The organization the job is filed under (AGL-3660). */\n orgId: string | null\n at: string | null\n by: StaffAiConversationPerson\n hostId: string | null\n kind: string\n status: string\n brief: string\n /**\n * What the person filled in beside the brief (AGL-3660) — the guided\n * start's form fields, a tone, a page to edit — as label and value.\n */\n inputs: StaffAiJobInput[]\n credits: number\n error: string | null\n outputs: StaffAiJobOutputRow[]\n /** `null` when the kind keeps nothing beside the job, or the result has expired. */\n result: StaffAiJobResult | null\n}\n\nexport interface StaffAiConversationsResponse {\n kind: StaffAiConversationKind\n rows: StaffAiAssistRow[] | StaffAiJobRow[]\n /** The id to pass as `after` for the next page; `null` on the last. */\n next: string | null\n}\n\ntype Data = Record<string, unknown>\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\nconst textOrNull = (value: unknown): string | null => (typeof value === 'string' && value.trim() ? value : null)\nconst finite = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\n/** A stored time as ISO: a Firestore timestamp, a `Date`, milliseconds or an ISO string. */\nexport function staffAiConversationTime(value: unknown): string | null {\n if (value == null) return null\n if (typeof (value as { toDate?: unknown }).toDate === 'function') {\n return (value as { toDate: () => Date }).toDate().toISOString()\n }\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString()\n if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString()\n if (typeof value === 'string' && !Number.isNaN(Date.parse(value))) return new Date(value).toISOString()\n return null\n}\n\n/** The person a uid is, from `orgs/{org}/members/{uid}` documents keyed by uid. */\nexport function staffAiConversationPerson(\n uid: unknown,\n members: ReadonlyMap<string, Data>,\n): StaffAiConversationPerson {\n const id = typeof uid === 'string' && uid ? uid : null\n const member = id ? members.get(id) : undefined\n return {\n uid: id,\n email: textOrNull(member?.['email']),\n name: textOrNull(member?.['displayName']),\n }\n}\n\n/** One Assist exchange joined with its signal, which may already be gone or never have been written. */\nexport function composeStaffAiAssistRow(\n id: string,\n exchange: Data,\n signal: Data | null,\n members: ReadonlyMap<string, Data>,\n): StaffAiAssistRow {\n const feedback = signal?.['feedback']\n return {\n id,\n orgId: textOrNull(exchange['orgId']),\n at: staffAiConversationTime(exchange['createdAt']),\n by: staffAiConversationPerson(exchange['uid'], members),\n hostId: textOrNull(exchange['hostId']),\n route: textOrNull(signal?.['route']),\n question: text(exchange['question']),\n answer: text(exchange['answer']),\n model: textOrNull(signal?.['model']),\n estCostUsd: finite(signal?.['estCostUsd']),\n feedback: feedback === 'up' || feedback === 'down' ? feedback : null,\n }\n}\n\n/** An insight's kept answer, as `aiInsights/{jobId}` stores it. */\nexport function staffAiInsightResult(record: Data | null): StaffAiJobResult | null {\n if (!record) return null\n const insights = Array.isArray(record['insights'])\n ? (record['insights'] as Data[]).map((insight) => text(insight?.['text'])).filter(Boolean)\n : []\n return { kind: 'insight', insights, gap: textOrNull(record['gap']) }\n}\n\n/** A CRM answer, as `aiCrmAnswers/{jobId}` stores it, by the proposal it holds. */\nexport function staffAiCrmResult(record: Data | null): StaffAiJobResult | null {\n const proposal = record?.['proposal'] as Data | undefined\n if (!proposal) return null\n switch (proposal['kind']) {\n case 'record': {\n const nextStep = proposal['nextStep'] as Data | null | undefined\n const title = textOrNull(nextStep?.['title'])\n const reason = textOrNull(nextStep?.['reason'])\n return {\n kind: 'crm-record',\n summary: text(proposal['summary']),\n nextStep: title ? (reason ? `${title} — ${reason}` : title) : null,\n standing: textOrNull(proposal['standing']),\n }\n }\n case 'email':\n return { kind: 'crm-email', subject: text(proposal['subject']), body: text(proposal['body']) }\n case 'mapping':\n return {\n kind: 'crm-mapping',\n matches: Array.isArray(proposal['matches'])\n ? (proposal['matches'] as Data[]).map((match) => `${text(match?.['header'])} → ${text(match?.['label'])}`)\n : [],\n }\n default:\n return null\n }\n}\n\n/** The collection a job kind keeps its result beside the job in, or `null` for a kind whose result is a draft. */\nexport function staffAiJobResultCollection(kind: unknown): 'aiInsights' | 'aiCrmAnswers' | null {\n if (kind === 'insight') return 'aiInsights'\n if (kind === 'crm') return 'aiCrmAnswers'\n return null\n}\n\n/** Fields read off one job's inputs, at most. */\nexport const STAFF_AI_JOB_INPUTS_MAX = 40\n/** Characters of one field's value shown. */\nconst INPUT_VALUE_MAX = 2000\n\n/** `businessName` → `Business name`. */\nfunction inputLabel(key: string): string {\n const words = key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_.-]+/g, ' ')\n .trim()\n .toLowerCase()\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n\nfunction inputValue(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value.trim() ? value : null\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (Array.isArray(value)) {\n const parts = value.map(inputValue).filter((part): part is string => Boolean(part))\n return parts.length ? parts.join(', ') : null\n }\n return null\n}\n\n/**\n * What a job's creator filled in beside the brief (AGL-3660), as fields:\n * scalars and lists as written, one level of a nested object flattened to\n * `parent child`. A value is cut at {@link INPUT_VALUE_MAX} characters.\n */\nexport function staffAiJobInputs(inputs: unknown): StaffAiJobInput[] {\n if (!inputs || typeof inputs !== 'object' || Array.isArray(inputs)) return []\n const out: StaffAiJobInput[] = []\n const push = (key: string, raw: unknown) => {\n if (out.length >= STAFF_AI_JOB_INPUTS_MAX) return\n const value = inputValue(raw)\n if (value === null) return\n out.push({\n key,\n label: inputLabel(key),\n value: value.length > INPUT_VALUE_MAX ? `${value.slice(0, INPUT_VALUE_MAX)}…` : value,\n })\n }\n for (const [key, raw] of Object.entries(inputs as Data)) {\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n for (const [child, nested] of Object.entries(raw as Data)) push(`${key}.${child}`, nested)\n } else {\n push(key, raw)\n }\n }\n return out\n}\n\n/** One job, with the result kept beside it when its kind keeps one. */\nexport function composeStaffAiJobRow(\n id: string,\n job: Data,\n result: StaffAiJobResult | null,\n members: ReadonlyMap<string, Data>,\n): StaffAiJobRow {\n const outputs = Array.isArray(job['outputs']) ? (job['outputs'] as Data[]) : []\n return {\n id,\n orgId: textOrNull(job['orgId']),\n at: staffAiConversationTime(job['createdAt']),\n by: staffAiConversationPerson(job['createdBy'], members),\n hostId: textOrNull(job['hostId']),\n kind: text(job['kind']) || 'unknown',\n status: text(job['status']) || 'unknown',\n brief: text(job['brief']),\n inputs: staffAiJobInputs(job['inputs']),\n credits: finite(job['creditsSpent']) ?? 0,\n error: textOrNull(job['error']),\n outputs: outputs.map((output) => ({\n label: text(output?.['label']) || text(output?.['resource']) || 'Output',\n note: textOrNull(output?.['note']),\n resource: textOrNull(output?.['resource']),\n hostSubdomain: textOrNull(output?.['hostSubdomain']),\n })),\n result,\n }\n}\n"],"names":["STAFF_AI_CONVERSATIONS_PAGE","STAFF_AI_CONVERSATION_KINDS","isStaffAiConversationKind","value","includes","text","textOrNull","trim","finite","Number","isFinite","staffAiConversationTime","toDate","toISOString","Date","isNaN","getTime","parse","staffAiConversationPerson","uid","members","id","member","get","undefined","email","name","composeStaffAiAssistRow","exchange","signal","feedback","orgId","at","by","hostId","route","question","answer","model","estCostUsd","staffAiInsightResult","record","insights","Array","isArray","map","insight","filter","Boolean","kind","gap","staffAiCrmResult","proposal","nextStep","title","reason","summary","standing","subject","body","matches","match","staffAiJobResultCollection","STAFF_AI_JOB_INPUTS_MAX","INPUT_VALUE_MAX","inputLabel","key","words","replace","toLowerCase","charAt","toUpperCase","slice","inputValue","String","parts","part","length","join","staffAiJobInputs","inputs","out","push","raw","label","Object","entries","child","nested","composeStaffAiJobRow","job","result","outputs","status","brief","credits","error","output","note","resource","hostSubdomain"],"mappings":"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"}