@aglyn/plugins-ai 1.0.0-beta.278 → 1.0.0-beta.283

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 (76) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +2 -26
  3. package/src/lib/activity/ai-activity-actions.js +5 -42
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/activity/ai-activity-words.d.ts +42 -0
  6. package/src/lib/activity/ai-activity-words.js +63 -0
  7. package/src/lib/activity/ai-activity-words.js.map +1 -0
  8. package/src/lib/components/ai-insight-card.component.d.ts +2 -6
  9. package/src/lib/components/ai-insight-card.component.js +2 -6
  10. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  11. package/src/lib/components/ai-insight-dialog.component.js +22 -32
  12. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  13. package/src/lib/components/ai-job-links.js +15 -0
  14. package/src/lib/components/ai-job-links.js.map +1 -1
  15. package/src/lib/components/assist-panel.component.js +3 -1
  16. package/src/lib/components/assist-panel.component.js.map +1 -1
  17. package/src/lib/jobs/ai-build-capabilities.js +23 -0
  18. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  19. package/src/lib/jobs/ai-job-build-step.js +2 -1
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-import-step.d.ts +1 -0
  22. package/src/lib/jobs/ai-job-import-step.js +17 -11
  23. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-plan-step.js +3 -2
  25. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-privacy-step.d.ts +184 -0
  27. package/src/lib/jobs/ai-job-privacy-step.js +471 -0
  28. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -0
  29. package/src/lib/jobs/ai-site-inspiration.d.ts +10 -0
  30. package/src/lib/jobs/ai-site-inspiration.js +15 -0
  31. package/src/lib/jobs/ai-site-inspiration.js.map +1 -1
  32. package/src/lib/model/ai-insight-ask.d.ts +57 -0
  33. package/src/lib/model/ai-insight-ask.js +64 -0
  34. package/src/lib/model/ai-insight-ask.js.map +1 -0
  35. package/src/lib/model/ai-insight.d.ts +1 -6
  36. package/src/lib/model/ai-insight.js +2 -8
  37. package/src/lib/model/ai-insight.js.map +1 -1
  38. package/src/lib/model/ai-job-activity.js +1 -0
  39. package/src/lib/model/ai-job-activity.js.map +1 -1
  40. package/src/lib/model/ai-jobs.types.d.ts +5 -1
  41. package/src/lib/model/ai-jobs.types.js +1 -0
  42. package/src/lib/model/ai-jobs.types.js.map +1 -1
  43. package/src/lib/model/ai-permissions.js +6 -2
  44. package/src/lib/model/ai-permissions.js.map +1 -1
  45. package/src/lib/model/ai-site-import.d.ts +2 -0
  46. package/src/lib/model/ai-site-import.js +2 -0
  47. package/src/lib/model/ai-site-import.js.map +1 -1
  48. package/src/lib/model/ai-site-kinds.d.ts +10 -28
  49. package/src/lib/model/ai-site-kinds.js +12 -496
  50. package/src/lib/model/ai-site-kinds.js.map +1 -1
  51. package/src/lib/providers/anthropic.js +23 -14
  52. package/src/lib/providers/anthropic.js.map +1 -1
  53. package/src/lib/providers/contract.d.ts +39 -4
  54. package/src/lib/providers/contract.js +28 -0
  55. package/src/lib/providers/contract.js.map +1 -1
  56. package/src/lib/providers/routing.d.ts +3 -0
  57. package/src/lib/providers/routing.js +4 -3
  58. package/src/lib/providers/routing.js.map +1 -1
  59. package/src/lib/runtime/ai-dev-env.d.ts +3 -2
  60. package/src/lib/runtime/ai-dev-env.js +3 -2
  61. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  62. package/src/lib/runtime/ai-page-links.d.ts +5 -2
  63. package/src/lib/runtime/ai-page-links.js +11 -3
  64. package/src/lib/runtime/ai-page-links.js.map +1 -1
  65. package/src/lib/runtime/ai-runtime.js +5 -2
  66. package/src/lib/runtime/ai-runtime.js.map +1 -1
  67. package/src/lib/server/assist-chat.js +58 -30
  68. package/src/lib/server/assist-chat.js.map +1 -1
  69. package/src/lib/server.js +2 -0
  70. package/src/lib/server.js.map +1 -1
  71. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +3 -1
  72. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +19 -1
  73. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -1
  74. package/src/lib/site-import/site-import-page.d.ts +2 -2
  75. package/src/lib/site-import/site-import-page.js +11 -1
  76. package/src/lib/site-import/site-import-page.js.map +1 -1
@@ -23,6 +23,7 @@ import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogActions, Dial
23
23
  import { doc, getDoc, setDoc } from "firebase/firestore";
24
24
  import { useCallback, useEffect, useRef, useState } from "react";
25
25
  import { AI_INSIGHT_DEFAULT_DAYS, AI_INSIGHT_QUESTION_MAX_CHARS, AI_INSIGHT_WINDOWS, aiInsightRowText, aiInsightSourceHref } from "../model/ai-insight.js";
26
+ import { AI_INSIGHT_DIALOG_COPY as COPY, AI_INSIGHT_PLACEHOLDERS } from "../model/ai-insight-ask.js";
26
27
  import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
27
28
  import { readEventFrames } from "./ai-job-events.js";
28
29
  import { openAiJobs, publishAiJob } from "./ai-jobs-store.js";
@@ -37,15 +38,7 @@ import { openAiJobs, publishAiJob } from "./ai-jobs-store.js";
37
38
  * rows it was traced to and links the console page they come from, so a
38
39
  * person can check a number before acting on it. Nothing here writes anything
39
40
  * but the person's own weekly-insights switch.
40
- */ /** Placeholder questions by surface, in the words a person would use. */ const PLACEHOLDERS = {
41
- analytics: 'Which pages brought in the most visitors, and did anything change?',
42
- datasets: 'What is the average order total by state?',
43
- 'crm-reports': 'Which campaign or form brought in the most people this month?',
44
- marketing: 'Which campaign brought in the most form submissions, and what did it earn?',
45
- automations: 'Which automations failed most this month?',
46
- bookings: 'Which services were booked most, and how many were canceled?',
47
- workspace: 'How much is in the pipeline, and what closed this month?'
48
- };
41
+ */ /** Placeholder questions by surface, in the words a person would use. */ const PLACEHOLDERS = AI_INSIGHT_PLACEHOLDERS;
49
42
  export function AiInsightDialog(props) {
50
43
  const { open, onClose, orgId, orgSlug, hostId, host, surface, jobId, question: startFrom } = props;
51
44
  // Held in a ref: a request reads who is signed in, and nothing keys on the
@@ -71,7 +64,7 @@ export function AiInsightDialog(props) {
71
64
  const payload = await response.json().catch(()=>null);
72
65
  if (!response.ok || !(payload == null ? void 0 : payload.answer)) {
73
66
  var _ref;
74
- setNotice(String((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'The answer could not be loaded. Try again.'));
67
+ setNotice(String((_ref = payload == null ? void 0 : payload.error) != null ? _ref : COPY.answerFailed));
75
68
  setPhase('ask');
76
69
  return;
77
70
  }
@@ -86,7 +79,7 @@ export function AiInsightDialog(props) {
86
79
  if (settled.status === 'done') await readAnswer(settled.id);
87
80
  else {
88
81
  var _settled_error;
89
- setNotice((_settled_error = settled.error) != null ? _settled_error : 'The question could not be answered. Try again.');
82
+ setNotice((_settled_error = settled.error) != null ? _settled_error : COPY.questionFailed);
90
83
  setPhase('ask');
91
84
  }
92
85
  };
@@ -111,7 +104,7 @@ export function AiInsightDialog(props) {
111
104
  publishAiJob(next);
112
105
  if (next.status === 'needs_input') {
113
106
  var _next_error;
114
- setNotice((_next_error = next.error) != null ? _next_error : 'This workspace cannot run AI jobs right now.');
107
+ setNotice((_next_error = next.error) != null ? _next_error : COPY.cannotRun);
115
108
  }
116
109
  if (AI_JOB_TERMINAL_STATUSES.includes(next.status)) settled = next;
117
110
  });
@@ -122,7 +115,7 @@ export function AiInsightDialog(props) {
122
115
  if (settled) return settle(settled);
123
116
  }
124
117
  if (!controller.signal.aborted) {
125
- setNotice('The answer is still being written. Find it in AI jobs in a minute.');
118
+ setNotice(COPY.stillWriting);
126
119
  setStillWriting(job.id);
127
120
  setPhase('ask');
128
121
  }
@@ -185,7 +178,7 @@ export function AiInsightDialog(props) {
185
178
  });
186
179
  } catch (unused) {
187
180
  setSubscribed(!next);
188
- setNotice('Your weekly insights setting could not be saved. Try again.');
181
+ setNotice(COPY.digestFailed);
189
182
  }
190
183
  }, [
191
184
  uid,
@@ -220,14 +213,14 @@ export function AiInsightDialog(props) {
220
213
  if (!response.ok || !(payload == null ? void 0 : payload.job)) {
221
214
  var _ref;
222
215
  const locked = parseLockdownRefusal(response.status, payload);
223
- setNotice(locked ? lockdownRefusalText(locked) : String((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'The question could not be asked. Try again.'));
216
+ setNotice(locked ? lockdownRefusalText(locked) : String((_ref = payload == null ? void 0 : payload.error) != null ? _ref : COPY.askFailed));
224
217
  setPhase('ask');
225
218
  return;
226
219
  }
227
220
  publishAiJob(payload.job);
228
221
  await follow(payload.job);
229
222
  } catch (unused) {
230
- setNotice('The question could not be asked. Try again.');
223
+ setNotice(COPY.askFailed);
231
224
  setPhase('ask');
232
225
  }
233
226
  }, [
@@ -251,7 +244,7 @@ export function AiInsightDialog(props) {
251
244
  children: [
252
245
  /*#__PURE__*/ _jsx(DialogTitle, {
253
246
  id: "ai-insight-title",
254
- children: 'Ask about your numbers'
247
+ children: COPY.title
255
248
  }),
256
249
  /*#__PURE__*/ _jsx(DialogContent, {
257
250
  children: /*#__PURE__*/ _jsxs(Stack, {
@@ -263,7 +256,7 @@ export function AiInsightDialog(props) {
263
256
  phase === 'ask' ? /*#__PURE__*/ _jsxs(_Fragment, {
264
257
  children: [
265
258
  /*#__PURE__*/ _jsx(TextField, {
266
- label: "What do you want to know?",
259
+ label: COPY.questionLabel,
267
260
  placeholder: PLACEHOLDERS[surface],
268
261
  multiline: true,
269
262
  minRows: 2,
@@ -278,7 +271,7 @@ export function AiInsightDialog(props) {
278
271
  variant: "body2",
279
272
  color: "text.secondary",
280
273
  id: "ai-insight-window-label",
281
- children: 'Over the last'
274
+ children: COPY.windowLabel
282
275
  }),
283
276
  /*#__PURE__*/ _jsx(Stack, {
284
277
  direction: "row",
@@ -299,13 +292,10 @@ export function AiInsightDialog(props) {
299
292
  })
300
293
  ]
301
294
  }) : null,
302
- /*#__PURE__*/ _jsxs(Typography, {
295
+ /*#__PURE__*/ _jsx(Typography, {
303
296
  variant: "body2",
304
297
  color: "text.secondary",
305
- children: [
306
- 'The answer is read from your own figures, and every number in it links to where it comes from. ',
307
- 'Nothing is changed or sent.'
308
- ]
298
+ children: COPY.promise
309
299
  })
310
300
  ]
311
301
  }) : null,
@@ -322,7 +312,7 @@ export function AiInsightDialog(props) {
322
312
  }),
323
313
  /*#__PURE__*/ _jsx(Typography, {
324
314
  variant: "body2",
325
- children: 'Reading your figures. This usually takes a minute.'
315
+ children: COPY.working
326
316
  })
327
317
  ]
328
318
  }) : null,
@@ -351,7 +341,7 @@ export function AiInsightDialog(props) {
351
341
  size: "small",
352
342
  onClick: ()=>setOpenCitations((current)=>current === index ? null : index),
353
343
  "aria-expanded": openCitations === index,
354
- children: openCitations === index ? 'Hide the figures' : 'Show the figures'
344
+ children: openCitations === index ? COPY.hideFigures : COPY.showFigures
355
345
  }),
356
346
  openCitations === index ? /*#__PURE__*/ _jsx(Stack, {
357
347
  spacing: 1,
@@ -400,7 +390,7 @@ export function AiInsightDialog(props) {
400
390
  }, index))
401
391
  }) : /*#__PURE__*/ _jsx(Alert, {
402
392
  severity: "info",
403
- children: 'The figures read here did not answer this question.'
393
+ children: COPY.unanswered
404
394
  }),
405
395
  answer.gap ? /*#__PURE__*/ _jsx(Alert, {
406
396
  severity: "info",
@@ -425,7 +415,7 @@ export function AiInsightDialog(props) {
425
415
  });
426
416
  onClose();
427
417
  },
428
- children: 'Open AI jobs'
418
+ children: COPY.openJobs
429
419
  }) : undefined,
430
420
  children: notice
431
421
  }) : null,
@@ -434,7 +424,7 @@ export function AiInsightDialog(props) {
434
424
  checked: subscribed,
435
425
  onChange: ()=>void toggleDigest()
436
426
  }),
437
- label: "Send me weekly insights for this workspace every Monday"
427
+ label: COPY.digest
438
428
  }) : null
439
429
  ]
440
430
  })
@@ -447,17 +437,17 @@ export function AiInsightDialog(props) {
447
437
  setQuestion('');
448
438
  setPhase('ask');
449
439
  },
450
- children: 'Ask another'
440
+ children: COPY.askAnother
451
441
  }) : null,
452
442
  /*#__PURE__*/ _jsx(Button, {
453
443
  onClick: onClose,
454
- children: 'Close'
444
+ children: COPY.close
455
445
  }),
456
446
  phase === 'ask' ? /*#__PURE__*/ _jsx(Button, {
457
447
  variant: "contained",
458
448
  onClick: ()=>void ask(),
459
449
  disabled: !question.trim(),
460
- children: 'Ask'
450
+ children: COPY.ask
461
451
  }) : null
462
452
  ]
463
453
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-insight-dialog.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\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed } from '@aglyn/aglyn/app-utils/notifications'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, getDoc, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_INSIGHT_DEFAULT_DAYS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHT_WINDOWS,\n aiInsightRowText,\n aiInsightSourceHref,\n type AiInsightAnswerWire,\n type AiInsightSurface,\n} from '../model/ai-insight'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { readEventFrames } from './ai-job-events'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\n\n/**\n * \"Ask about your numbers\" (AGL-2915): the Assist panel's question box on a\n * site's Analytics, Data and CRM Reports pages and the workspace's Data page,\n * and the answer an `insight` job wrote.\n *\n * A question starts a job, which reads the figures and answers on the jobs\n * beat; the dialog watches the job and then reads the answer through\n * `/api/ai/insights/{jobId}`, the only door to it. Every insight shows the\n * rows it was traced to and links the console page they come from, so a\n * person can check a number before acting on it. Nothing here writes anything\n * but the person's own weekly-insights switch.\n */\n\n/** Placeholder questions by surface, in the words a person would use. */\nconst PLACEHOLDERS: Record<Exclude<AiInsightSurface, 'digest'>, string> = {\n analytics: 'Which pages brought in the most visitors, and did anything change?',\n datasets: 'What is the average order total by state?',\n 'crm-reports': 'Which campaign or form brought in the most people this month?',\n marketing: 'Which campaign brought in the most form submissions, and what did it earn?',\n automations: 'Which automations failed most this month?',\n bookings: 'Which services were booked most, and how many were canceled?',\n workspace: 'How much is in the pipeline, and what closed this month?',\n}\n\nexport interface AiInsightDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n /** The org's slug, for the links to the pages a table's figures come from. */\n orgSlug: string\n /** The site the question is about; `null` on the workspace's Data page. */\n hostId: string | null\n /** The site's subdomain, which a console link names a site by. */\n host: string | null\n surface: Exclude<AiInsightSurface, 'digest'>\n user: Parameters<typeof authorizedFetch>[0]\n /** The signed-in person, whose weekly-insights switch the dialog shows. */\n uid: string | null\n /** An answered job to show rather than a question to ask. */\n jobId?: string | null\n /**\n * A question to start from, put in the box each time the dialog opens to\n * ask (AGL-3603): the page that opened it knows what it is about. The\n * person may change it before asking; absent, the box keeps what it held.\n */\n question?: string\n}\n\ntype Phase = 'ask' | 'working' | 'answer'\n\nexport function AiInsightDialog(props: AiInsightDialogProps) {\n const { open, onClose, orgId, orgSlug, hostId, host, surface, jobId, question: startFrom } = props\n // Held in a ref: a request reads who is signed in, and nothing keys on the\n // identity of the object that says so.\n const userRef = useRef(props.user)\n userRef.current = props.user\n const uid = props.uid\n const firestore = useFirestore()\n const [phase, setPhase] = useState<Phase>('ask')\n const [question, setQuestion] = useState('')\n const [days, setDays] = useState(AI_INSIGHT_DEFAULT_DAYS)\n const [notice, setNotice] = useState<string | null>(null)\n const [answer, setAnswer] = useState<AiInsightAnswerWire | null>(null)\n const [openCitations, setOpenCitations] = useState<number | null>(null)\n const [subscribed, setSubscribed] = useState<boolean | null>(null)\n /**\n * The job whose answer was still being written when the dialog stopped\n * waiting (AGL-3593): its notice offers to open AI jobs on it.\n */\n const [stillWriting, setStillWriting] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n const readAnswer = useCallback(\n async (id: string) => {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/insights/${encodeURIComponent(id)}?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.answer) {\n setNotice(String(payload?.error ?? 'The answer could not be loaded. Try again.'))\n setPhase('ask')\n return\n }\n setAnswer(payload.answer as AiInsightAnswerWire)\n setPhase('answer')\n },\n [orgId],\n )\n\n /** Follows a job until it settles, then reads its answer or says why there is none. */\n const follow = useCallback(\n async (job: AiJobSummary) => {\n const settle = async (settled: AiJobSummary) => {\n if (settled.status === 'done') await readAnswer(settled.id)\n else {\n setNotice(settled.error ?? 'The question could not be answered. Try again.')\n setPhase('ask')\n }\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) return settle(job)\n setPhase('working')\n const controller = new AbortController()\n watchRef.current?.abort()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n let settled: AiJobSummary | null = null\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) break\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'reconnect') again = true\n if (event['type'] !== 'state') return\n const next = event['job'] as AiJobSummary\n publishAiJob(next)\n if (next.status === 'needs_input') {\n setNotice(next.error ?? 'This workspace cannot run AI jobs right now.')\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(next.status)) settled = next\n })\n } catch {\n if (controller.signal.aborted) return\n break\n }\n if (settled) return settle(settled)\n }\n if (!controller.signal.aborted) {\n setNotice('The answer is still being written. Find it in AI jobs in a minute.')\n setStillWriting(job.id)\n setPhase('ask')\n }\n },\n [orgId, readAnswer],\n )\n\n useEffect(() => {\n if (!open) {\n watchRef.current?.abort()\n return\n }\n setNotice(null)\n setStillWriting(null)\n setOpenCitations(null)\n if (jobId) {\n setPhase('working')\n void readAnswer(jobId)\n } else {\n setAnswer(null)\n setPhase('ask')\n if (startFrom) setQuestion(startFrom.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS))\n }\n }, [open, jobId, readAnswer, startFrom])\n\n // The person's own weekly-insights switch, read when the dialog opens.\n useEffect(() => {\n if (!open || !uid || !orgId) return\n let active = true\n void getDoc(doc(firestore, 'users', uid))\n .then((snapshot) => {\n if (active) {\n setSubscribed(\n insightDigestSubscribed(snapshot.get(INSIGHT_DIGESTS_FIELD) as Record<string, boolean>, orgId),\n )\n }\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [open, uid, orgId, firestore])\n\n const toggleDigest = useCallback(async () => {\n if (!uid || subscribed === null) return\n const next = !subscribed\n setSubscribed(next)\n try {\n await setDoc(doc(firestore, 'users', uid), { [INSIGHT_DIGESTS_FIELD]: { [orgId]: next } }, { merge: true })\n } catch {\n setSubscribed(!next)\n setNotice('Your weekly insights setting could not be saved. Try again.')\n }\n }, [uid, subscribed, firestore, orgId])\n\n const ask = useCallback(async () => {\n const text = question.trim()\n if (!text) return\n setNotice(null)\n setStillWriting(null)\n setPhase('working')\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'insight',\n brief: text,\n inputs: { surface, days },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.job) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? 'The question could not be asked. Try again.'))\n setPhase('ask')\n return\n }\n publishAiJob(payload.job as AiJobSummary)\n await follow(payload.job as AiJobSummary)\n } catch {\n setNotice('The question could not be asked. Try again.')\n setPhase('ask')\n }\n }, [question, orgId, hostId, surface, days, follow])\n\n const tableByRef = (ref: string) => answer?.tables.find((table) => table.ref === ref) ?? null\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"sm\" aria-labelledby=\"ai-insight-title\">\n <DialogTitle id=\"ai-insight-title\">{'Ask about your numbers'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {phase === 'ask' ? (\n <>\n <TextField\n label=\"What do you want to know?\"\n placeholder={PLACEHOLDERS[surface]}\n multiline\n minRows={2}\n value={question}\n onChange={(event) => setQuestion(event.target.value.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS))}\n autoFocus\n />\n {surface !== 'datasets' ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\" id=\"ai-insight-window-label\">\n {'Over the last'}\n </Typography>\n <Stack direction=\"row\" useFlexGap role=\"group\" aria-labelledby=\"ai-insight-window-label\" sx={{ flexWrap: 'wrap', gap: 1 }}>\n {AI_INSIGHT_WINDOWS.map((window) => (\n <Chip\n key={window}\n label={`${window} days`}\n color={days === window ? 'primary' : 'default'}\n variant={days === window ? 'filled' : 'outlined'}\n onClick={() => setDays(window)}\n aria-pressed={days === window}\n />\n ))}\n </Stack>\n </Stack>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The answer is read from your own figures, and every number in it links to where it comes from. '}\n {'Nothing is changed or sent.'}\n </Typography>\n </>\n ) : null}\n {phase === 'working' ? (\n <Stack direction=\"row\" spacing={1.5} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\">{'Reading your figures. This usually takes a minute.'}</Typography>\n </Stack>\n ) : null}\n {phase === 'answer' && answer ? (\n <Stack spacing={1.5}>\n <Typography variant=\"subtitle2\">{answer.question}</Typography>\n {answer.insights.length ? (\n <Stack component=\"ol\" spacing={1.5} sx={{ pl: 2.5, m: 0 }}>\n {answer.insights.map((insight, index) => (\n <Box component=\"li\" key={index}>\n <Typography variant=\"body2\">{insight.text}</Typography>\n <Button\n size=\"small\"\n onClick={() => setOpenCitations((current) => (current === index ? null : index))}\n aria-expanded={openCitations === index}\n >\n {openCitations === index ? 'Hide the figures' : 'Show the figures'}\n </Button>\n {openCitations === index ? (\n <Stack spacing={1} sx={{ pl: 1, borderLeft: 2, borderColor: 'divider' }}>\n {insight.cites.map((cite) => {\n const table = tableByRef(cite.table)\n if (!table) return null\n const href = aiInsightSourceHref(table, { orgSlug, host })\n return (\n <Box key={cite.table}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {table.period\n ? `${table.title} · ${table.period.from} to ${table.period.to}`\n : table.title}\n {href ? (\n <>\n {' · '}\n <AppLink componentVariant=\"naked\" href={href}>\n {`Open ${table.source.label}`}\n </AppLink>\n </>\n ) : null}\n </Typography>\n {cite.rows.map((row) => (\n <Typography key={row} variant=\"caption\" component=\"div\">\n {aiInsightRowText(table, row)}\n </Typography>\n ))}\n </Box>\n )\n })}\n </Stack>\n ) : null}\n </Box>\n ))}\n </Stack>\n ) : (\n <Alert severity=\"info\">{'The figures read here did not answer this question.'}</Alert>\n )}\n {answer.gap ? <Alert severity=\"info\">{answer.gap}</Alert> : null}\n {answer.left > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${answer.left} ${answer.left === 1 ? 'insight was' : 'insights were'} left out because ${\n answer.left === 1 ? 'its numbers' : 'their numbers'\n } could not be traced to the figures.`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n {notice ? (\n <Alert\n severity=\"warning\"\n action={\n stillWriting ? (\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: stillWriting })\n onClose()\n }}\n >\n {'Open AI jobs'}\n </Button>\n ) : undefined\n }\n >\n {notice}\n </Alert>\n ) : null}\n {surface !== 'datasets' && subscribed !== null ? (\n <FormControlLabel\n control={<Switch checked={subscribed} onChange={() => void toggleDigest()} />}\n label=\"Send me weekly insights for this workspace every Monday\"\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n {phase === 'answer' && !jobId ? (\n <Button\n onClick={() => {\n setAnswer(null)\n setQuestion('')\n setPhase('ask')\n }}\n >\n {'Ask another'}\n </Button>\n ) : null}\n <Button onClick={onClose}>{'Close'}</Button>\n {phase === 'ask' ? (\n <Button variant=\"contained\" onClick={() => void ask()} disabled={!question.trim()}>\n {'Ask'}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default AiInsightDialog\n"],"names":["lockdownRefusalText","parseLockdownRefusal","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","AppLink","authorizedFetch","useFirestore","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","Switch","TextField","Typography","doc","getDoc","setDoc","useCallback","useEffect","useRef","useState","AI_INSIGHT_DEFAULT_DAYS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHT_WINDOWS","aiInsightRowText","aiInsightSourceHref","AI_JOB_TERMINAL_STATUSES","readEventFrames","openAiJobs","publishAiJob","PLACEHOLDERS","analytics","datasets","marketing","automations","bookings","workspace","AiInsightDialog","props","open","onClose","orgId","orgSlug","hostId","host","surface","jobId","question","startFrom","userRef","user","current","uid","firestore","phase","setPhase","setQuestion","days","setDays","notice","setNotice","answer","setAnswer","openCitations","setOpenCitations","subscribed","setSubscribed","stillWriting","setStillWriting","watchRef","readAnswer","id","response","encodeURIComponent","payload","json","catch","ok","String","error","follow","job","settle","settled","status","includes","controller","AbortController","abort","again","signal","aborted","body","event","next","slice","active","then","snapshot","get","undefined","toggleDigest","merge","ask","text","trim","method","headers","JSON","stringify","kind","brief","inputs","locked","tableByRef","ref","tables","find","table","fullWidth","maxWidth","aria-labelledby","spacing","sx","pt","label","placeholder","multiline","minRows","value","onChange","target","autoFocus","variant","color","direction","useFlexGap","role","flexWrap","gap","map","window","onClick","aria-pressed","alignItems","size","insights","length","component","pl","m","insight","index","aria-expanded","borderLeft","borderColor","cites","cite","href","period","title","from","to","componentVariant","source","rows","row","severity","left","action","control","checked","disabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-insight-dialog.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\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed } from '@aglyn/aglyn/app-utils/notifications'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, getDoc, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_INSIGHT_DEFAULT_DAYS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHT_WINDOWS,\n aiInsightRowText,\n aiInsightSourceHref,\n type AiInsightAnswerWire,\n type AiInsightSurface,\n} from '../model/ai-insight'\nimport { AI_INSIGHT_DIALOG_COPY as COPY, AI_INSIGHT_PLACEHOLDERS } from '../model/ai-insight-ask'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { readEventFrames } from './ai-job-events'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\n\n/**\n * \"Ask about your numbers\" (AGL-2915): the Assist panel's question box on a\n * site's Analytics, Data and CRM Reports pages and the workspace's Data page,\n * and the answer an `insight` job wrote.\n *\n * A question starts a job, which reads the figures and answers on the jobs\n * beat; the dialog watches the job and then reads the answer through\n * `/api/ai/insights/{jobId}`, the only door to it. Every insight shows the\n * rows it was traced to and links the console page they come from, so a\n * person can check a number before acting on it. Nothing here writes anything\n * but the person's own weekly-insights switch.\n */\n\n/** Placeholder questions by surface, in the words a person would use. */\nconst PLACEHOLDERS: Readonly<Record<string, string>> = AI_INSIGHT_PLACEHOLDERS\n\nexport interface AiInsightDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n /** The org's slug, for the links to the pages a table's figures come from. */\n orgSlug: string\n /** The site the question is about; `null` on the workspace's Data page. */\n hostId: string | null\n /** The site's subdomain, which a console link names a site by. */\n host: string | null\n surface: Exclude<AiInsightSurface, 'digest'>\n user: Parameters<typeof authorizedFetch>[0]\n /** The signed-in person, whose weekly-insights switch the dialog shows. */\n uid: string | null\n /** An answered job to show rather than a question to ask. */\n jobId?: string | null\n /**\n * A question to start from, put in the box each time the dialog opens to\n * ask (AGL-3603): the page that opened it knows what it is about. The\n * person may change it before asking; absent, the box keeps what it held.\n */\n question?: string\n}\n\ntype Phase = 'ask' | 'working' | 'answer'\n\nexport function AiInsightDialog(props: AiInsightDialogProps) {\n const { open, onClose, orgId, orgSlug, hostId, host, surface, jobId, question: startFrom } = props\n // Held in a ref: a request reads who is signed in, and nothing keys on the\n // identity of the object that says so.\n const userRef = useRef(props.user)\n userRef.current = props.user\n const uid = props.uid\n const firestore = useFirestore()\n const [phase, setPhase] = useState<Phase>('ask')\n const [question, setQuestion] = useState('')\n const [days, setDays] = useState(AI_INSIGHT_DEFAULT_DAYS)\n const [notice, setNotice] = useState<string | null>(null)\n const [answer, setAnswer] = useState<AiInsightAnswerWire | null>(null)\n const [openCitations, setOpenCitations] = useState<number | null>(null)\n const [subscribed, setSubscribed] = useState<boolean | null>(null)\n /**\n * The job whose answer was still being written when the dialog stopped\n * waiting (AGL-3593): its notice offers to open AI jobs on it.\n */\n const [stillWriting, setStillWriting] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n const readAnswer = useCallback(\n async (id: string) => {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/insights/${encodeURIComponent(id)}?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.answer) {\n setNotice(String(payload?.error ?? COPY.answerFailed))\n setPhase('ask')\n return\n }\n setAnswer(payload.answer as AiInsightAnswerWire)\n setPhase('answer')\n },\n [orgId],\n )\n\n /** Follows a job until it settles, then reads its answer or says why there is none. */\n const follow = useCallback(\n async (job: AiJobSummary) => {\n const settle = async (settled: AiJobSummary) => {\n if (settled.status === 'done') await readAnswer(settled.id)\n else {\n setNotice(settled.error ?? COPY.questionFailed)\n setPhase('ask')\n }\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) return settle(job)\n setPhase('working')\n const controller = new AbortController()\n watchRef.current?.abort()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n let settled: AiJobSummary | null = null\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) break\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'reconnect') again = true\n if (event['type'] !== 'state') return\n const next = event['job'] as AiJobSummary\n publishAiJob(next)\n if (next.status === 'needs_input') {\n setNotice(next.error ?? COPY.cannotRun)\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(next.status)) settled = next\n })\n } catch {\n if (controller.signal.aborted) return\n break\n }\n if (settled) return settle(settled)\n }\n if (!controller.signal.aborted) {\n setNotice(COPY.stillWriting)\n setStillWriting(job.id)\n setPhase('ask')\n }\n },\n [orgId, readAnswer],\n )\n\n useEffect(() => {\n if (!open) {\n watchRef.current?.abort()\n return\n }\n setNotice(null)\n setStillWriting(null)\n setOpenCitations(null)\n if (jobId) {\n setPhase('working')\n void readAnswer(jobId)\n } else {\n setAnswer(null)\n setPhase('ask')\n if (startFrom) setQuestion(startFrom.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS))\n }\n }, [open, jobId, readAnswer, startFrom])\n\n // The person's own weekly-insights switch, read when the dialog opens.\n useEffect(() => {\n if (!open || !uid || !orgId) return\n let active = true\n void getDoc(doc(firestore, 'users', uid))\n .then((snapshot) => {\n if (active) {\n setSubscribed(\n insightDigestSubscribed(snapshot.get(INSIGHT_DIGESTS_FIELD) as Record<string, boolean>, orgId),\n )\n }\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [open, uid, orgId, firestore])\n\n const toggleDigest = useCallback(async () => {\n if (!uid || subscribed === null) return\n const next = !subscribed\n setSubscribed(next)\n try {\n await setDoc(doc(firestore, 'users', uid), { [INSIGHT_DIGESTS_FIELD]: { [orgId]: next } }, { merge: true })\n } catch {\n setSubscribed(!next)\n setNotice(COPY.digestFailed)\n }\n }, [uid, subscribed, firestore, orgId])\n\n const ask = useCallback(async () => {\n const text = question.trim()\n if (!text) return\n setNotice(null)\n setStillWriting(null)\n setPhase('working')\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'insight',\n brief: text,\n inputs: { surface, days },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.job) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? COPY.askFailed))\n setPhase('ask')\n return\n }\n publishAiJob(payload.job as AiJobSummary)\n await follow(payload.job as AiJobSummary)\n } catch {\n setNotice(COPY.askFailed)\n setPhase('ask')\n }\n }, [question, orgId, hostId, surface, days, follow])\n\n const tableByRef = (ref: string) => answer?.tables.find((table) => table.ref === ref) ?? null\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"sm\" aria-labelledby=\"ai-insight-title\">\n <DialogTitle id=\"ai-insight-title\">{COPY.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {phase === 'ask' ? (\n <>\n <TextField\n label={COPY.questionLabel}\n placeholder={PLACEHOLDERS[surface]}\n multiline\n minRows={2}\n value={question}\n onChange={(event) => setQuestion(event.target.value.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS))}\n autoFocus\n />\n {surface !== 'datasets' ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\" id=\"ai-insight-window-label\">\n {COPY.windowLabel}\n </Typography>\n <Stack direction=\"row\" useFlexGap role=\"group\" aria-labelledby=\"ai-insight-window-label\" sx={{ flexWrap: 'wrap', gap: 1 }}>\n {AI_INSIGHT_WINDOWS.map((window) => (\n <Chip\n key={window}\n label={`${window} days`}\n color={days === window ? 'primary' : 'default'}\n variant={days === window ? 'filled' : 'outlined'}\n onClick={() => setDays(window)}\n aria-pressed={days === window}\n />\n ))}\n </Stack>\n </Stack>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {COPY.promise}\n </Typography>\n </>\n ) : null}\n {phase === 'working' ? (\n <Stack direction=\"row\" spacing={1.5} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\">{COPY.working}</Typography>\n </Stack>\n ) : null}\n {phase === 'answer' && answer ? (\n <Stack spacing={1.5}>\n <Typography variant=\"subtitle2\">{answer.question}</Typography>\n {answer.insights.length ? (\n <Stack component=\"ol\" spacing={1.5} sx={{ pl: 2.5, m: 0 }}>\n {answer.insights.map((insight, index) => (\n <Box component=\"li\" key={index}>\n <Typography variant=\"body2\">{insight.text}</Typography>\n <Button\n size=\"small\"\n onClick={() => setOpenCitations((current) => (current === index ? null : index))}\n aria-expanded={openCitations === index}\n >\n {openCitations === index ? COPY.hideFigures : COPY.showFigures}\n </Button>\n {openCitations === index ? (\n <Stack spacing={1} sx={{ pl: 1, borderLeft: 2, borderColor: 'divider' }}>\n {insight.cites.map((cite) => {\n const table = tableByRef(cite.table)\n if (!table) return null\n const href = aiInsightSourceHref(table, { orgSlug, host })\n return (\n <Box key={cite.table}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {table.period\n ? `${table.title} · ${table.period.from} to ${table.period.to}`\n : table.title}\n {href ? (\n <>\n {' · '}\n <AppLink componentVariant=\"naked\" href={href}>\n {`Open ${table.source.label}`}\n </AppLink>\n </>\n ) : null}\n </Typography>\n {cite.rows.map((row) => (\n <Typography key={row} variant=\"caption\" component=\"div\">\n {aiInsightRowText(table, row)}\n </Typography>\n ))}\n </Box>\n )\n })}\n </Stack>\n ) : null}\n </Box>\n ))}\n </Stack>\n ) : (\n <Alert severity=\"info\">{COPY.unanswered}</Alert>\n )}\n {answer.gap ? <Alert severity=\"info\">{answer.gap}</Alert> : null}\n {answer.left > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${answer.left} ${answer.left === 1 ? 'insight was' : 'insights were'} left out because ${\n answer.left === 1 ? 'its numbers' : 'their numbers'\n } could not be traced to the figures.`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n {notice ? (\n <Alert\n severity=\"warning\"\n action={\n stillWriting ? (\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: stillWriting })\n onClose()\n }}\n >\n {COPY.openJobs}\n </Button>\n ) : undefined\n }\n >\n {notice}\n </Alert>\n ) : null}\n {surface !== 'datasets' && subscribed !== null ? (\n <FormControlLabel\n control={<Switch checked={subscribed} onChange={() => void toggleDigest()} />}\n label={COPY.digest}\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n {phase === 'answer' && !jobId ? (\n <Button\n onClick={() => {\n setAnswer(null)\n setQuestion('')\n setPhase('ask')\n }}\n >\n {COPY.askAnother}\n </Button>\n ) : null}\n <Button onClick={onClose}>{COPY.close}</Button>\n {phase === 'ask' ? (\n <Button variant=\"contained\" onClick={() => void ask()} disabled={!question.trim()}>\n {COPY.ask}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default AiInsightDialog\n"],"names":["lockdownRefusalText","parseLockdownRefusal","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","AppLink","authorizedFetch","useFirestore","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","Switch","TextField","Typography","doc","getDoc","setDoc","useCallback","useEffect","useRef","useState","AI_INSIGHT_DEFAULT_DAYS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHT_WINDOWS","aiInsightRowText","aiInsightSourceHref","AI_INSIGHT_DIALOG_COPY","COPY","AI_INSIGHT_PLACEHOLDERS","AI_JOB_TERMINAL_STATUSES","readEventFrames","openAiJobs","publishAiJob","PLACEHOLDERS","AiInsightDialog","props","open","onClose","orgId","orgSlug","hostId","host","surface","jobId","question","startFrom","userRef","user","current","uid","firestore","phase","setPhase","setQuestion","days","setDays","notice","setNotice","answer","setAnswer","openCitations","setOpenCitations","subscribed","setSubscribed","stillWriting","setStillWriting","watchRef","readAnswer","id","response","encodeURIComponent","payload","json","catch","ok","String","error","answerFailed","follow","job","settle","settled","status","questionFailed","includes","controller","AbortController","abort","again","signal","aborted","body","event","next","cannotRun","slice","active","then","snapshot","get","undefined","toggleDigest","merge","digestFailed","ask","text","trim","method","headers","JSON","stringify","kind","brief","inputs","locked","askFailed","tableByRef","ref","tables","find","table","fullWidth","maxWidth","aria-labelledby","title","spacing","sx","pt","label","questionLabel","placeholder","multiline","minRows","value","onChange","target","autoFocus","variant","color","windowLabel","direction","useFlexGap","role","flexWrap","gap","map","window","onClick","aria-pressed","promise","alignItems","size","working","insights","length","component","pl","m","insight","index","aria-expanded","hideFigures","showFigures","borderLeft","borderColor","cites","cite","href","period","from","to","componentVariant","source","rows","row","severity","unanswered","left","action","openJobs","control","checked","digest","askAnother","close","disabled"],"mappings":"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"}
@@ -96,6 +96,21 @@ import { AI_BUILD_STORE_LINKS_DRAFT_STEP, AI_BUILD_STORE_LINKS_MISSING_STEP } fr
96
96
  host
97
97
  });
98
98
  }
99
+ if (output.resource === 'privacy') {
100
+ // Who can see the site (AGL-4033), each change on the card that makes it.
101
+ if (output.id === 'maintenance') return buildRoute(Route.HOST_ADMIN_ERROR_PAGES, {
102
+ orgSlug,
103
+ host
104
+ });
105
+ if (output.id === 'search') return buildRoute(Route.HOST_SETUP_SEO, {
106
+ orgSlug,
107
+ host
108
+ });
109
+ return buildRoute(Route.HOST_SCREENS, {
110
+ orgSlug,
111
+ host
112
+ });
113
+ }
99
114
  if (output.resource === 'draft' && output.draftResource) {
100
115
  // A draft another plugin wrote — a dataset a site start or a build made
101
116
  // (AGL-3616) — opens where its owner lists it, or nowhere it is not loaded.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from '../model/ai-job-notice'\nimport type { AiJobOutput, AiJobSummary } from '../model/ai-jobs.types'\nimport { AI_STORE_FINISH_STEPS, type AiStoreFinishStep } from '../model/ai-site-store-pages'\nimport { AI_BUILD_STORE_LINKS_DRAFT_STEP, AI_BUILD_STORE_LINKS_MISSING_STEP } from '../model/ai-build-store-pages'\n\n// Where a job's work opens in the console (AGL-2904), shared by the AI jobs\n// drawer and every dialog that follows the job it started (AGL-3593).\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'entry') {\n // A post a guided start wrote (AGL-3676) opens in its blog's list in Content.\n const collectionSlug = output.proposal?.['collectionSlug']\n return typeof collectionSlug === 'string' && collectionSlug\n ? buildRoute(Route.HOST_CONTENT_COLLECTION, { orgSlug, host, collectionSlug })\n : buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'media') {\n // Files a build organized (AGL-3752) open in the site's media library.\n return buildRoute(Route.HOST_MEDIA, { orgSlug, host })\n }\n if (output.resource === 'settings') {\n // Settings a build saved (AGL-3748) open where most of them are edited.\n return buildRoute(Route.HOST_SETUP_SEO, { orgSlug, host })\n }\n if (output.resource === 'tracking') {\n // Tags a build saved (AGL-3751) open on the card that edits them.\n return buildRoute(Route.HOST_SETUP_TRACKING, { orgSlug, host })\n }\n if (output.resource === 'draft' && output.draftResource) {\n // A draft another plugin wrote — a dataset a site start or a build made\n // (AGL-3616) — opens where its owner lists it, or nowhere it is not loaded.\n return pluginRecordListHref(output.draftResource, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\n}\n\n/**\n * The ONE action a finished job's row, its notification and the dialog that\n * started it lead with (AGL-3593): what the job built. Several pages from one\n * job — a site's — open the site's Pages list, where they wait as drafts; one\n * output with a page of its own opens that page; anything else has no single\n * place to go, and the row's own links stand. `null` until the job is done.\n */\nexport function aiJobPrimaryLink(\n job: Pick<AiJobSummary, 'status' | 'outputs'>,\n orgSlug: string,\n): { href: string; label: string } | null {\n if (job.status !== 'done' || !orgSlug) return null\n const screens = job.outputs.filter(\n (output) => output.resource === 'screen' && output.hostSubdomain,\n )\n if (screens.length > 1) {\n return {\n href: buildRoute(Route.HOST_SCREENS, { orgSlug, host: String(screens[0].hostSubdomain) }),\n label: 'Open your draft pages',\n }\n }\n for (const output of job.outputs) {\n const href = aiJobOutputHref(output, orgSlug)\n if (href) {\n return {\n href,\n label: output.resource === 'screen' ? 'Open the draft page' : `Open ${output.label}`,\n }\n }\n }\n return null\n}\n\nexport { AI_SITE_BUILD_HREF }\n\n/**\n * The \"Building your site\" page for one site job (AGL-3594): linkable, so the\n * guided start lands on it, a reload keeps it and a notification opens it.\n * `null` without the org slug or the site's subdomain a console URL needs.\n */\nexport function aiSiteBuildHref(\n orgSlug: string | null | undefined,\n hostSubdomain: string | null | undefined,\n jobId: string,\n): string | null {\n if (!orgSlug || !hostSubdomain || !jobId) return null\n return `/${orgSlug}/hosts/${hostSubdomain}${AI_SITE_BUILD_HREF}/${encodeURIComponent(jobId)}`\n}\n\n/**\n * A finished site's two ways in (AGL-3594): its first page's draft, opened in\n * the console's preview of that version, and the Pages list every draft waits\n * in. Either is `null` where the job reported nothing to open.\n */\nexport function aiSiteBuildDoneLinks(\n job: Pick<AiJobSummary, 'outputs'>,\n orgSlug: string,\n): { view: string | null; pages: string | null } {\n const screens = job.outputs.filter((output) => output.resource === 'screen' && output.hostSubdomain)\n const first = screens[0]\n if (!first || !orgSlug) return { view: null, pages: null }\n const host = String(first.hostSubdomain)\n return {\n view: first.versionId\n ? `/${orgSlug}/hosts/${host}/screens/${first.id}/versions/${first.versionId}/view`\n : null,\n pages: buildRoute(Route.HOST_SCREENS, { orgSlug, host }),\n }\n}\n\n/**\n * Where a person gets more AI credits (AGL-3660): the workspace's Billing\n * page at its plans, where an upgrade and the AI add-on are both sold — the\n * door the AI credits meter's own upgrade link opens.\n */\nexport function aiCreditsBillingHref(orgSlug: string): string {\n return `${buildRoute(Route.MANAGE_BILLING, { orgSlug })}#plans`\n}\n\n/** One thing left to finish an AI-built store, with where it is done. */\nexport interface AiStoreFinishLink extends AiStoreFinishStep {\n /** The console page it is done on; `null` where the owner of that page is not loaded. */\n href: string | null\n}\n\n/**\n * What is left to finish a store a site start built (AGL-3676), or `null`\n * where the start built no store's own pages: its account, cart and policy\n * pages are written, so payments, products, shipping and tax, and the\n * policies' brackets are all that remain. Each links the exact console page:\n * the store's settings and its catalog, as the commerce plugin publishes\n * them, and the site's Pages.\n */\nexport function aiStoreFinishLinks(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'items' | 'outputs'> & Partial<Pick<AiJobSummary, 'sitePublish'>>,\n orgSlug: string,\n): AiStoreFinishLink[] | null {\n // A site start's store, or an Assist build that made the site a store (AGL-3676).\n if ((job.kind !== 'site' && job.kind !== 'build') || job.status !== 'done' || !orgSlug) return null\n const row = (job.items ?? []).find((one) => one.slot === 'store')\n if (!row || (row.status !== 'succeeded' && row.status !== 'degraded')) return null\n const host = job.outputs.find((output) => output.resource === 'screen' && output.hostSubdomain)?.hostSubdomain\n if (!host) return null\n const context = { orgSlug, host: String(host) }\n // A build's products are drafts with no price unless asked, and it may\n // have written none of the policies (the site had them): its list says so.\n const build = job.kind === 'build'\n const mine = job.outputs.filter((output) => row.outputs.includes(output.id))\n const written = new Set(mine.map((output) => output.proposal?.['storePage']))\n const policies = !build || ['shipping', 'privacy', 'terms'].some((key) => written.has(key))\n const steps: AiStoreFinishLink[] = AI_STORE_FINISH_STEPS.filter((step) => step.id !== 'policies' || policies).map((step) => ({\n ...step,\n ...(build && step.id === 'products' ? { text: AI_BUILD_STORE_PRODUCTS_TEXT } : {}),\n href:\n step.opens === 'pages'\n ? buildRoute(Route.HOST_SCREENS, context)\n : step.opens === 'layouts' || step.opens === 'layout'\n ? buildRoute(Route.HOST_LAYOUTS, context)\n : step.record\n ? pluginRecordHref(step.opens, context, step.record)\n : pluginRecordListHref(step.opens, context),\n }))\n if (!build) return steps\n // The site's own header and footer (AGL-3676): links it had no list for are\n // the owner's to add; links drafted into it wait for its publish.\n if (mine.some((output) => output.proposal?.['storeLinks'] === 'missing')) {\n steps.push({ ...AI_BUILD_STORE_LINKS_MISSING_STEP, href: buildRoute(Route.HOST_LAYOUTS, context) })\n }\n const draft = mine.find((output) => output.resource === 'layout' && output.proposal?.['storeLinks'] === 'draft')\n const live = draft && job.sitePublish?.published.length\n if (draft && !live) steps.push({ ...AI_BUILD_STORE_LINKS_DRAFT_STEP, href: aiJobOutputHref(draft, orgSlug) })\n return steps\n}\n\n/**\n * The site an AI-built store is on — the one {@link aiStoreFinishLinks}\n * links — so \"Finish your store\" can read whether its workspace already takes\n * payments (AGL-3982). `null` where no output names a site.\n */\nexport function aiStoreFinishHostId(job: Pick<AiJobSummary, 'outputs'>): string | null {\n return job.outputs.find((output) => output.resource === 'screen' && output.hostSubdomain)?.hostId ?? null\n}\n\n/** What a build's \"Review your products\" step says: its products are drafts, priced only where the request said. */\nexport const AI_BUILD_STORE_PRODUCTS_TEXT =\n 'Your new products are drafts. Set their prices, photos and stock, then make them active so they show in your store.'\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","AI_JOB_BESIGNER_SEGMENT","BESIGNER_SEGMENT","AI_SITE_BUILD_HREF","AI_STORE_FINISH_STEPS","AI_BUILD_STORE_LINKS_DRAFT_STEP","AI_BUILD_STORE_LINKS_MISSING_STEP","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","collectionSlug","proposal","HOST_CONTENT_COLLECTION","HOST_CONTENT","HOST_SETUP_THEME","HOST_MEDIA","HOST_SETUP_SEO","HOST_SETUP_TRACKING","draftResource","aiJobPrimaryLink","job","status","screens","outputs","filter","length","href","HOST_SCREENS","String","label","aiSiteBuildHref","jobId","encodeURIComponent","aiSiteBuildDoneLinks","first","view","pages","aiCreditsBillingHref","MANAGE_BILLING","aiStoreFinishLinks","kind","row","items","find","one","slot","context","build","mine","includes","written","Set","map","policies","some","key","has","steps","step","text","AI_BUILD_STORE_PRODUCTS_TEXT","opens","HOST_LAYOUTS","record","push","draft","live","sitePublish","published","aiStoreFinishHostId","hostId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SACEC,gBAAgB,EAChBC,oBAAoB,QACf,mDAAkD;AACzD,SAASC,2BAA2BC,gBAAgB,EAAEC,kBAAkB,QAAQ,4BAAwB;AAExG,SAASC,qBAAqB,QAAgC,kCAA8B;AAC5F,SAASC,+BAA+B,EAAEC,iCAAiC,QAAQ,mCAA+B;AAElH,4EAA4E;AAC5E,sEAAsE;AAEtE;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,gBAAgBC,MAAmB,EAAEC,OAAe;IAClE,MAAMC,OAAOF,OAAOG,aAAa;IACjC,IAAI,CAACF,WAAW,CAACC,MAAM,OAAO;IAC9B,MAAME,UAAUV,gBAAgB,CAACM,OAAOK,QAAQ,CAAC;IACjD,IAAID,SAAS;QACX,MAAME,OAAO,CAAC,CAAC,EAAEL,QAAQ,OAAO,EAAEC,KAAK,CAAC,EAAEE,QAAQ,CAAC,EAAEJ,OAAOO,EAAE,EAAE;QAChE,OAAOP,OAAOQ,SAAS,GAAG,GAAGF,KAAK,UAAU,EAAEN,OAAOQ,SAAS,CAAC,SAAS,CAAC,GAAGF;IAC9E;IACA,IAAIN,OAAOK,QAAQ,KAAK,WAAW;QACjC,wEAAwE;QACxE,8DAA8D;QAC9D,OAAOb,qBAAqB,WAAW;YAAES;YAASC;QAAK;IACzD;IACA,IAAIF,OAAOK,QAAQ,KAAK,YAAY;QAClC,uEAAuE;QACvE,6EAA6E;QAC7E,OAAOb,qBAAqB,UAAU;YAAES;YAASC;QAAK;IACxD;IACA,IAAIF,OAAOK,QAAQ,KAAK,SAAS;YAERL;QADvB,8EAA8E;QAC9E,MAAMS,kBAAiBT,mBAAAA,OAAOU,QAAQ,qBAAfV,gBAAiB,CAAC,iBAAiB;QAC1D,OAAO,OAAOS,mBAAmB,YAAYA,iBACzCpB,WAAWC,MAAMqB,uBAAuB,EAAE;YAAEV;YAASC;YAAMO;QAAe,KAC1EpB,WAAWC,MAAMsB,YAAY,EAAE;YAAEX;YAASC;QAAK;IACrD;IACA,IAAIF,OAAOK,QAAQ,KAAK,SAAS;QAC/B,OAAOhB,WAAWC,MAAMuB,gBAAgB,EAAE;YAAEZ;YAASC;QAAK;IAC5D;IACA,IAAIF,OAAOK,QAAQ,KAAK,SAAS;QAC/B,uEAAuE;QACvE,OAAOhB,WAAWC,MAAMwB,UAAU,EAAE;YAAEb;YAASC;QAAK;IACtD;IACA,IAAIF,OAAOK,QAAQ,KAAK,YAAY;QAClC,wEAAwE;QACxE,OAAOhB,WAAWC,MAAMyB,cAAc,EAAE;YAAEd;YAASC;QAAK;IAC1D;IACA,IAAIF,OAAOK,QAAQ,KAAK,YAAY;QAClC,kEAAkE;QAClE,OAAOhB,WAAWC,MAAM0B,mBAAmB,EAAE;YAAEf;YAASC;QAAK;IAC/D;IACA,IAAIF,OAAOK,QAAQ,KAAK,WAAWL,OAAOiB,aAAa,EAAE;QACvD,wEAAwE;QACxE,4EAA4E;QAC5E,OAAOzB,qBAAqBQ,OAAOiB,aAAa,EAAE;YAAEhB;YAASC;QAAK;IACpE;IACA,IAAIF,OAAOK,QAAQ,KAAK,QAAQ;QAC9B,yEAAyE;QACzE,qEAAqE;QACrE,mCAAmC;QACnC,OAAOd,iBAAiB,QAAQ;YAAEU;YAASC;QAAK,GAAGF,OAAOO,EAAE;IAC9D;IACA,IAAIP,OAAOK,QAAQ,KAAK,YAAY;QAClC,mEAAmE;QACnE,0EAA0E;QAC1E,OAAOd,iBAAiB,YAAY;YAAEU;YAASC;QAAK,GAAGF,OAAOO,EAAE;IAClE;IACA,OAAO;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASW,iBACdC,GAA6C,EAC7ClB,OAAe;IAEf,IAAIkB,IAAIC,MAAM,KAAK,UAAU,CAACnB,SAAS,OAAO;IAC9C,MAAMoB,UAAUF,IAAIG,OAAO,CAACC,MAAM,CAChC,CAACvB,SAAWA,OAAOK,QAAQ,KAAK,YAAYL,OAAOG,aAAa;IAElE,IAAIkB,QAAQG,MAAM,GAAG,GAAG;QACtB,OAAO;YACLC,MAAMpC,WAAWC,MAAMoC,YAAY,EAAE;gBAAEzB;gBAASC,MAAMyB,OAAON,OAAO,CAAC,EAAE,CAAClB,aAAa;YAAE;YACvFyB,OAAO;QACT;IACF;IACA,KAAK,MAAM5B,UAAUmB,IAAIG,OAAO,CAAE;QAChC,MAAMG,OAAO1B,gBAAgBC,QAAQC;QACrC,IAAIwB,MAAM;YACR,OAAO;gBACLA;gBACAG,OAAO5B,OAAOK,QAAQ,KAAK,WAAW,wBAAwB,CAAC,KAAK,EAAEL,OAAO4B,KAAK,EAAE;YACtF;QACF;IACF;IACA,OAAO;AACT;AAEA,SAASjC,kBAAkB,GAAE;AAE7B;;;;CAIC,GACD,OAAO,SAASkC,gBACd5B,OAAkC,EAClCE,aAAwC,EACxC2B,KAAa;IAEb,IAAI,CAAC7B,WAAW,CAACE,iBAAiB,CAAC2B,OAAO,OAAO;IACjD,OAAO,CAAC,CAAC,EAAE7B,QAAQ,OAAO,EAAEE,gBAAgBR,mBAAmB,CAAC,EAAEoC,mBAAmBD,QAAQ;AAC/F;AAEA;;;;CAIC,GACD,OAAO,SAASE,qBACdb,GAAkC,EAClClB,OAAe;IAEf,MAAMoB,UAAUF,IAAIG,OAAO,CAACC,MAAM,CAAC,CAACvB,SAAWA,OAAOK,QAAQ,KAAK,YAAYL,OAAOG,aAAa;IACnG,MAAM8B,QAAQZ,OAAO,CAAC,EAAE;IACxB,IAAI,CAACY,SAAS,CAAChC,SAAS,OAAO;QAAEiC,MAAM;QAAMC,OAAO;IAAK;IACzD,MAAMjC,OAAOyB,OAAOM,MAAM9B,aAAa;IACvC,OAAO;QACL+B,MAAMD,MAAMzB,SAAS,GACjB,CAAC,CAAC,EAAEP,QAAQ,OAAO,EAAEC,KAAK,SAAS,EAAE+B,MAAM1B,EAAE,CAAC,UAAU,EAAE0B,MAAMzB,SAAS,CAAC,KAAK,CAAC,GAChF;QACJ2B,OAAO9C,WAAWC,MAAMoC,YAAY,EAAE;YAAEzB;YAASC;QAAK;IACxD;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASkC,qBAAqBnC,OAAe;IAClD,OAAO,GAAGZ,WAAWC,MAAM+C,cAAc,EAAE;QAAEpC;IAAQ,GAAG,MAAM,CAAC;AACjE;AAQA;;;;;;;CAOC,GACD,OAAO,SAASqC,mBACdnB,GAA6G,EAC7GlB,OAAe;QAIFkB;QAEAA,mBA4BSA;IAhCtB,kFAAkF;IAClF,IAAI,AAACA,IAAIoB,IAAI,KAAK,UAAUpB,IAAIoB,IAAI,KAAK,WAAYpB,IAAIC,MAAM,KAAK,UAAU,CAACnB,SAAS,OAAO;IAC/F,MAAMuC,MAAM,EAACrB,aAAAA,IAAIsB,KAAK,YAATtB,aAAa,EAAE,EAAEuB,IAAI,CAAC,CAACC,MAAQA,IAAIC,IAAI,KAAK;IACzD,IAAI,CAACJ,OAAQA,IAAIpB,MAAM,KAAK,eAAeoB,IAAIpB,MAAM,KAAK,YAAa,OAAO;IAC9E,MAAMlB,QAAOiB,oBAAAA,IAAIG,OAAO,CAACoB,IAAI,CAAC,CAAC1C,SAAWA,OAAOK,QAAQ,KAAK,YAAYL,OAAOG,aAAa,sBAAjFgB,kBAAoFhB,aAAa;IAC9G,IAAI,CAACD,MAAM,OAAO;IAClB,MAAM2C,UAAU;QAAE5C;QAASC,MAAMyB,OAAOzB;IAAM;IAC9C,uEAAuE;IACvE,2EAA2E;IAC3E,MAAM4C,QAAQ3B,IAAIoB,IAAI,KAAK;IAC3B,MAAMQ,OAAO5B,IAAIG,OAAO,CAACC,MAAM,CAAC,CAACvB,SAAWwC,IAAIlB,OAAO,CAAC0B,QAAQ,CAAChD,OAAOO,EAAE;IAC1E,MAAM0C,UAAU,IAAIC,IAAIH,KAAKI,GAAG,CAAC,CAACnD;YAAWA;gBAAAA,mBAAAA,OAAOU,QAAQ,qBAAfV,gBAAiB,CAAC,YAAY;;IAC3E,MAAMoD,WAAW,CAACN,SAAS;QAAC;QAAY;QAAW;KAAQ,CAACO,IAAI,CAAC,CAACC,MAAQL,QAAQM,GAAG,CAACD;IACtF,MAAME,QAA6B5D,sBAAsB2B,MAAM,CAAC,CAACkC,OAASA,KAAKlD,EAAE,KAAK,cAAc6C,UAAUD,GAAG,CAAC,CAACM,OAAU,aACxHA,MACCX,SAASW,KAAKlD,EAAE,KAAK,aAAa;YAAEmD,MAAMC;QAA6B,IAAI,CAAC;YAChFlC,MACEgC,KAAKG,KAAK,KAAK,UACXvE,WAAWC,MAAMoC,YAAY,EAAEmB,WAC/BY,KAAKG,KAAK,KAAK,aAAaH,KAAKG,KAAK,KAAK,WACzCvE,WAAWC,MAAMuE,YAAY,EAAEhB,WAC/BY,KAAKK,MAAM,GACTvE,iBAAiBkE,KAAKG,KAAK,EAAEf,SAASY,KAAKK,MAAM,IACjDtE,qBAAqBiE,KAAKG,KAAK,EAAEf;;IAE7C,IAAI,CAACC,OAAO,OAAOU;IACnB,4EAA4E;IAC5E,kEAAkE;IAClE,IAAIT,KAAKM,IAAI,CAAC,CAACrD;YAAWA;eAAAA,EAAAA,mBAAAA,OAAOU,QAAQ,qBAAfV,gBAAiB,CAAC,aAAa,MAAK;QAAY;QACxEwD,MAAMO,IAAI,CAAC,aAAKjE;YAAmC2B,MAAMpC,WAAWC,MAAMuE,YAAY,EAAEhB;;IAC1F;IACA,MAAMmB,QAAQjB,KAAKL,IAAI,CAAC,CAAC1C;YAA2CA;eAAhCA,OAAOK,QAAQ,KAAK,YAAYL,EAAAA,mBAAAA,OAAOU,QAAQ,qBAAfV,gBAAiB,CAAC,aAAa,MAAK;;IACxG,MAAMiE,OAAOD,WAAS7C,mBAAAA,IAAI+C,WAAW,qBAAf/C,iBAAiBgD,SAAS,CAAC3C,MAAM;IACvD,IAAIwC,SAAS,CAACC,MAAMT,MAAMO,IAAI,CAAC,aAAKlE;QAAiC4B,MAAM1B,gBAAgBiE,OAAO/D;;IAClG,OAAOuD;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASY,oBAAoBjD,GAAkC;;QAC7DA;IAAP,gBAAOA,oBAAAA,IAAIG,OAAO,CAACoB,IAAI,CAAC,CAAC1C,SAAWA,OAAOK,QAAQ,KAAK,YAAYL,OAAOG,aAAa,sBAAjFgB,kBAAoFkD,MAAM,mBAAI;AACvG;AAEA,kHAAkH,GAClH,OAAO,MAAMV,+BACX,sHAAqH"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from '../model/ai-job-notice'\nimport type { AiJobOutput, AiJobSummary } from '../model/ai-jobs.types'\nimport { AI_STORE_FINISH_STEPS, type AiStoreFinishStep } from '../model/ai-site-store-pages'\nimport { AI_BUILD_STORE_LINKS_DRAFT_STEP, AI_BUILD_STORE_LINKS_MISSING_STEP } from '../model/ai-build-store-pages'\n\n// Where a job's work opens in the console (AGL-2904), shared by the AI jobs\n// drawer and every dialog that follows the job it started (AGL-3593).\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'entry') {\n // A post a guided start wrote (AGL-3676) opens in its blog's list in Content.\n const collectionSlug = output.proposal?.['collectionSlug']\n return typeof collectionSlug === 'string' && collectionSlug\n ? buildRoute(Route.HOST_CONTENT_COLLECTION, { orgSlug, host, collectionSlug })\n : buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'media') {\n // Files a build organized (AGL-3752) open in the site's media library.\n return buildRoute(Route.HOST_MEDIA, { orgSlug, host })\n }\n if (output.resource === 'settings') {\n // Settings a build saved (AGL-3748) open where most of them are edited.\n return buildRoute(Route.HOST_SETUP_SEO, { orgSlug, host })\n }\n if (output.resource === 'tracking') {\n // Tags a build saved (AGL-3751) open on the card that edits them.\n return buildRoute(Route.HOST_SETUP_TRACKING, { orgSlug, host })\n }\n if (output.resource === 'privacy') {\n // Who can see the site (AGL-4033), each change on the card that makes it.\n if (output.id === 'maintenance') return buildRoute(Route.HOST_ADMIN_ERROR_PAGES, { orgSlug, host })\n if (output.id === 'search') return buildRoute(Route.HOST_SETUP_SEO, { orgSlug, host })\n return buildRoute(Route.HOST_SCREENS, { orgSlug, host })\n }\n if (output.resource === 'draft' && output.draftResource) {\n // A draft another plugin wrote — a dataset a site start or a build made\n // (AGL-3616) — opens where its owner lists it, or nowhere it is not loaded.\n return pluginRecordListHref(output.draftResource, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\n}\n\n/**\n * The ONE action a finished job's row, its notification and the dialog that\n * started it lead with (AGL-3593): what the job built. Several pages from one\n * job — a site's — open the site's Pages list, where they wait as drafts; one\n * output with a page of its own opens that page; anything else has no single\n * place to go, and the row's own links stand. `null` until the job is done.\n */\nexport function aiJobPrimaryLink(\n job: Pick<AiJobSummary, 'status' | 'outputs'>,\n orgSlug: string,\n): { href: string; label: string } | null {\n if (job.status !== 'done' || !orgSlug) return null\n const screens = job.outputs.filter(\n (output) => output.resource === 'screen' && output.hostSubdomain,\n )\n if (screens.length > 1) {\n return {\n href: buildRoute(Route.HOST_SCREENS, { orgSlug, host: String(screens[0].hostSubdomain) }),\n label: 'Open your draft pages',\n }\n }\n for (const output of job.outputs) {\n const href = aiJobOutputHref(output, orgSlug)\n if (href) {\n return {\n href,\n label: output.resource === 'screen' ? 'Open the draft page' : `Open ${output.label}`,\n }\n }\n }\n return null\n}\n\nexport { AI_SITE_BUILD_HREF }\n\n/**\n * The \"Building your site\" page for one site job (AGL-3594): linkable, so the\n * guided start lands on it, a reload keeps it and a notification opens it.\n * `null` without the org slug or the site's subdomain a console URL needs.\n */\nexport function aiSiteBuildHref(\n orgSlug: string | null | undefined,\n hostSubdomain: string | null | undefined,\n jobId: string,\n): string | null {\n if (!orgSlug || !hostSubdomain || !jobId) return null\n return `/${orgSlug}/hosts/${hostSubdomain}${AI_SITE_BUILD_HREF}/${encodeURIComponent(jobId)}`\n}\n\n/**\n * A finished site's two ways in (AGL-3594): its first page's draft, opened in\n * the console's preview of that version, and the Pages list every draft waits\n * in. Either is `null` where the job reported nothing to open.\n */\nexport function aiSiteBuildDoneLinks(\n job: Pick<AiJobSummary, 'outputs'>,\n orgSlug: string,\n): { view: string | null; pages: string | null } {\n const screens = job.outputs.filter((output) => output.resource === 'screen' && output.hostSubdomain)\n const first = screens[0]\n if (!first || !orgSlug) return { view: null, pages: null }\n const host = String(first.hostSubdomain)\n return {\n view: first.versionId\n ? `/${orgSlug}/hosts/${host}/screens/${first.id}/versions/${first.versionId}/view`\n : null,\n pages: buildRoute(Route.HOST_SCREENS, { orgSlug, host }),\n }\n}\n\n/**\n * Where a person gets more AI credits (AGL-3660): the workspace's Billing\n * page at its plans, where an upgrade and the AI add-on are both sold — the\n * door the AI credits meter's own upgrade link opens.\n */\nexport function aiCreditsBillingHref(orgSlug: string): string {\n return `${buildRoute(Route.MANAGE_BILLING, { orgSlug })}#plans`\n}\n\n/** One thing left to finish an AI-built store, with where it is done. */\nexport interface AiStoreFinishLink extends AiStoreFinishStep {\n /** The console page it is done on; `null` where the owner of that page is not loaded. */\n href: string | null\n}\n\n/**\n * What is left to finish a store a site start built (AGL-3676), or `null`\n * where the start built no store's own pages: its account, cart and policy\n * pages are written, so payments, products, shipping and tax, and the\n * policies' brackets are all that remain. Each links the exact console page:\n * the store's settings and its catalog, as the commerce plugin publishes\n * them, and the site's Pages.\n */\nexport function aiStoreFinishLinks(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'items' | 'outputs'> & Partial<Pick<AiJobSummary, 'sitePublish'>>,\n orgSlug: string,\n): AiStoreFinishLink[] | null {\n // A site start's store, or an Assist build that made the site a store (AGL-3676).\n if ((job.kind !== 'site' && job.kind !== 'build') || job.status !== 'done' || !orgSlug) return null\n const row = (job.items ?? []).find((one) => one.slot === 'store')\n if (!row || (row.status !== 'succeeded' && row.status !== 'degraded')) return null\n const host = job.outputs.find((output) => output.resource === 'screen' && output.hostSubdomain)?.hostSubdomain\n if (!host) return null\n const context = { orgSlug, host: String(host) }\n // A build's products are drafts with no price unless asked, and it may\n // have written none of the policies (the site had them): its list says so.\n const build = job.kind === 'build'\n const mine = job.outputs.filter((output) => row.outputs.includes(output.id))\n const written = new Set(mine.map((output) => output.proposal?.['storePage']))\n const policies = !build || ['shipping', 'privacy', 'terms'].some((key) => written.has(key))\n const steps: AiStoreFinishLink[] = AI_STORE_FINISH_STEPS.filter((step) => step.id !== 'policies' || policies).map((step) => ({\n ...step,\n ...(build && step.id === 'products' ? { text: AI_BUILD_STORE_PRODUCTS_TEXT } : {}),\n href:\n step.opens === 'pages'\n ? buildRoute(Route.HOST_SCREENS, context)\n : step.opens === 'layouts' || step.opens === 'layout'\n ? buildRoute(Route.HOST_LAYOUTS, context)\n : step.record\n ? pluginRecordHref(step.opens, context, step.record)\n : pluginRecordListHref(step.opens, context),\n }))\n if (!build) return steps\n // The site's own header and footer (AGL-3676): links it had no list for are\n // the owner's to add; links drafted into it wait for its publish.\n if (mine.some((output) => output.proposal?.['storeLinks'] === 'missing')) {\n steps.push({ ...AI_BUILD_STORE_LINKS_MISSING_STEP, href: buildRoute(Route.HOST_LAYOUTS, context) })\n }\n const draft = mine.find((output) => output.resource === 'layout' && output.proposal?.['storeLinks'] === 'draft')\n const live = draft && job.sitePublish?.published.length\n if (draft && !live) steps.push({ ...AI_BUILD_STORE_LINKS_DRAFT_STEP, href: aiJobOutputHref(draft, orgSlug) })\n return steps\n}\n\n/**\n * The site an AI-built store is on — the one {@link aiStoreFinishLinks}\n * links — so \"Finish your store\" can read whether its workspace already takes\n * payments (AGL-3982). `null` where no output names a site.\n */\nexport function aiStoreFinishHostId(job: Pick<AiJobSummary, 'outputs'>): string | null {\n return job.outputs.find((output) => output.resource === 'screen' && output.hostSubdomain)?.hostId ?? null\n}\n\n/** What a build's \"Review your products\" step says: its products are drafts, priced only where the request said. */\nexport const AI_BUILD_STORE_PRODUCTS_TEXT =\n 'Your new products are drafts. Set their prices, photos and stock, then make them active so they show in your store.'\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","AI_JOB_BESIGNER_SEGMENT","BESIGNER_SEGMENT","AI_SITE_BUILD_HREF","AI_STORE_FINISH_STEPS","AI_BUILD_STORE_LINKS_DRAFT_STEP","AI_BUILD_STORE_LINKS_MISSING_STEP","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","collectionSlug","proposal","HOST_CONTENT_COLLECTION","HOST_CONTENT","HOST_SETUP_THEME","HOST_MEDIA","HOST_SETUP_SEO","HOST_SETUP_TRACKING","HOST_ADMIN_ERROR_PAGES","HOST_SCREENS","draftResource","aiJobPrimaryLink","job","status","screens","outputs","filter","length","href","String","label","aiSiteBuildHref","jobId","encodeURIComponent","aiSiteBuildDoneLinks","first","view","pages","aiCreditsBillingHref","MANAGE_BILLING","aiStoreFinishLinks","kind","row","items","find","one","slot","context","build","mine","includes","written","Set","map","policies","some","key","has","steps","step","text","AI_BUILD_STORE_PRODUCTS_TEXT","opens","HOST_LAYOUTS","record","push","draft","live","sitePublish","published","aiStoreFinishHostId","hostId"],"mappings":"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"}
@@ -16,12 +16,13 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
- import { canvas, checkEntitlement, lockdownRefusalText, parseLockdownRefusal, planLabelGrantingFeature } from "@aglyn/aglyn";
19
+ import { canvas, checkEntitlement, isBetaStaged, lockdownRefusalText, parseLockdownRefusal, planLabelGrantingFeature } from "@aglyn/aglyn";
20
20
  import { editorSessionsRevision, openEditorSession, subscribeEditorSessions } from "@aglyn/aglyn/plugin-manager/editor-sessions";
21
21
  import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
22
22
  import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
23
23
  import { mdiChevronDown, mdiCreation, mdiChevronUp, mdiClose, mdiOpenInNew, mdiSend, mdiThumbDownOutline, mdiThumbUpOutline } from "@aglyn/shared-data-mdi";
24
24
  import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
25
+ import { BetaChip } from "@aglyn/shared-ui-jsx/components/beta-chip.component";
25
26
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
26
27
  import { Alert, Badge, Box, Button, Chip, CircularProgress, Collapse, Drawer, Fab, IconButton, Link, Paper, Stack, TextField, Tooltip, Typography } from "@mui/material";
27
28
  import { usePathname } from "next/navigation";
@@ -1119,6 +1120,7 @@ export function AssistPanelComponent(props) {
1119
1120
  color: "warning",
1120
1121
  label: "Staff preview"
1121
1122
  }),
1123
+ verdict.visible && !verdict.staffPreview && isBetaStaged('release_assist') && /*#__PURE__*/ _jsx(BetaChip, {}),
1122
1124
  /*#__PURE__*/ _jsx(HelpTip, _extends({}, pluginDocsHelp('aglynAssist', {
1123
1125
  anchor: '#what-it-can-do'
1124
1126
  }), {