@humaan/payload-plugin-ai-assistant 0.0.0-stage → 0.1.1

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 (190) hide show
  1. package/README.md +277 -2
  2. package/dist/admin/AiAssistantControl.d.ts +11 -0
  3. package/dist/admin/AiAssistantControl.js +31 -0
  4. package/dist/admin/AiAssistantControl.js.map +1 -0
  5. package/dist/admin/AiAssistantPanel.d.ts +10 -0
  6. package/dist/admin/AiAssistantPanel.js +208 -0
  7. package/dist/admin/AiAssistantPanel.js.map +1 -0
  8. package/dist/admin/AiAssistantPanel.module.scss +689 -0
  9. package/dist/admin/AiSourceSwitch.d.ts +13 -0
  10. package/dist/admin/AiSourceSwitch.js +14 -0
  11. package/dist/admin/AiSourceSwitch.js.map +1 -0
  12. package/dist/admin/BudgetField.d.ts +8 -0
  13. package/dist/admin/BudgetField.js +145 -0
  14. package/dist/admin/BudgetField.js.map +1 -0
  15. package/dist/admin/BudgetField.module.scss +47 -0
  16. package/dist/admin/Conversation.d.ts +12 -0
  17. package/dist/admin/Conversation.js +469 -0
  18. package/dist/admin/Conversation.js.map +1 -0
  19. package/dist/admin/Markdown.d.ts +9 -0
  20. package/dist/admin/Markdown.js +63 -0
  21. package/dist/admin/Markdown.js.map +1 -0
  22. package/dist/admin/PageContextBridge.d.ts +5 -0
  23. package/dist/admin/PageContextBridge.js +72 -0
  24. package/dist/admin/PageContextBridge.js.map +1 -0
  25. package/dist/admin/Pill.d.ts +11 -0
  26. package/dist/admin/Pill.js +27 -0
  27. package/dist/admin/Pill.js.map +1 -0
  28. package/dist/admin/PlanCard.d.ts +14 -0
  29. package/dist/admin/PlanCard.js +465 -0
  30. package/dist/admin/PlanCard.js.map +1 -0
  31. package/dist/admin/SettingsAlerts.d.ts +7 -0
  32. package/dist/admin/SettingsAlerts.js +37 -0
  33. package/dist/admin/SettingsAlerts.js.map +1 -0
  34. package/dist/admin/UsageSummary.d.ts +11 -0
  35. package/dist/admin/UsageSummary.js +129 -0
  36. package/dist/admin/UsageSummary.js.map +1 -0
  37. package/dist/admin/attachmentUpload.d.ts +25 -0
  38. package/dist/admin/attachmentUpload.js +76 -0
  39. package/dist/admin/attachmentUpload.js.map +1 -0
  40. package/dist/admin/openDocument.d.ts +13 -0
  41. package/dist/admin/openDocument.js +34 -0
  42. package/dist/admin/openDocument.js.map +1 -0
  43. package/dist/admin/percent.d.ts +10 -0
  44. package/dist/admin/percent.js +21 -0
  45. package/dist/admin/percent.js.map +1 -0
  46. package/dist/admin/threads.d.ts +21 -0
  47. package/dist/admin/threads.js +92 -0
  48. package/dist/admin/threads.js.map +1 -0
  49. package/dist/admin/toolSteps.d.ts +12 -0
  50. package/dist/admin/toolSteps.js +57 -0
  51. package/dist/admin/toolSteps.js.map +1 -0
  52. package/dist/admin/transport.d.ts +24 -0
  53. package/dist/admin/transport.js +75 -0
  54. package/dist/admin/transport.js.map +1 -0
  55. package/dist/admin/useAttachments.d.ts +32 -0
  56. package/dist/admin/useAttachments.js +91 -0
  57. package/dist/admin/useAttachments.js.map +1 -0
  58. package/dist/admin/useEntityLabel.d.ts +3 -0
  59. package/dist/admin/useEntityLabel.js +23 -0
  60. package/dist/admin/useEntityLabel.js.map +1 -0
  61. package/dist/attachments/collection.d.ts +15 -0
  62. package/dist/attachments/collection.js +90 -0
  63. package/dist/attachments/collection.js.map +1 -0
  64. package/dist/attachments/conversation.d.ts +25 -0
  65. package/dist/attachments/conversation.js +105 -0
  66. package/dist/attachments/conversation.js.map +1 -0
  67. package/dist/attachments/rules.d.ts +9 -0
  68. package/dist/attachments/rules.js +17 -0
  69. package/dist/attachments/rules.js.map +1 -0
  70. package/dist/attachments/store.d.ts +27 -0
  71. package/dist/attachments/store.js +187 -0
  72. package/dist/attachments/store.js.map +1 -0
  73. package/dist/billing.d.ts +60 -0
  74. package/dist/billing.js +116 -0
  75. package/dist/billing.js.map +1 -0
  76. package/dist/chat/history.d.ts +37 -0
  77. package/dist/chat/history.js +165 -0
  78. package/dist/chat/history.js.map +1 -0
  79. package/dist/chat/messages.d.ts +5 -0
  80. package/dist/chat/messages.js +3 -0
  81. package/dist/chat/messages.js.map +1 -0
  82. package/dist/chat/threads.d.ts +33 -0
  83. package/dist/chat/threads.js +77 -0
  84. package/dist/chat/threads.js.map +1 -0
  85. package/dist/chat/tools.d.ts +76 -0
  86. package/dist/chat/tools.js +596 -0
  87. package/dist/chat/tools.js.map +1 -0
  88. package/dist/chat/usage.d.ts +66 -0
  89. package/dist/chat/usage.js +219 -0
  90. package/dist/chat/usage.js.map +1 -0
  91. package/dist/collectionAccess.d.ts +6 -0
  92. package/dist/collectionAccess.js +32 -0
  93. package/dist/collectionAccess.js.map +1 -0
  94. package/dist/constants.d.ts +64 -0
  95. package/dist/constants.js +51 -0
  96. package/dist/constants.js.map +1 -0
  97. package/dist/endpoints/access.d.ts +4 -0
  98. package/dist/endpoints/access.js +28 -0
  99. package/dist/endpoints/access.js.map +1 -0
  100. package/dist/endpoints/allowance.d.ts +22 -0
  101. package/dist/endpoints/allowance.js +37 -0
  102. package/dist/endpoints/allowance.js.map +1 -0
  103. package/dist/endpoints/applyEndpoint.d.ts +12 -0
  104. package/dist/endpoints/applyEndpoint.js +170 -0
  105. package/dist/endpoints/applyEndpoint.js.map +1 -0
  106. package/dist/endpoints/budget.d.ts +30 -0
  107. package/dist/endpoints/budget.js +153 -0
  108. package/dist/endpoints/budget.js.map +1 -0
  109. package/dist/endpoints/chatEndpoint.d.ts +12 -0
  110. package/dist/endpoints/chatEndpoint.js +295 -0
  111. package/dist/endpoints/chatEndpoint.js.map +1 -0
  112. package/dist/endpoints/form.d.ts +23 -0
  113. package/dist/endpoints/form.js +95 -0
  114. package/dist/endpoints/form.js.map +1 -0
  115. package/dist/endpoints/usageEndpoint.d.ts +13 -0
  116. package/dist/endpoints/usageEndpoint.js +70 -0
  117. package/dist/endpoints/usageEndpoint.js.map +1 -0
  118. package/dist/index.d.ts +4 -0
  119. package/dist/index.js +5 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/lexical/editorConfig.d.ts +4 -0
  122. package/dist/lexical/editorConfig.js +19 -0
  123. package/dist/lexical/editorConfig.js.map +1 -0
  124. package/dist/lexical/lexicalToMarkdown.d.ts +10 -0
  125. package/dist/lexical/lexicalToMarkdown.js +10 -0
  126. package/dist/lexical/lexicalToMarkdown.js.map +1 -0
  127. package/dist/lexical/markdownToLexical.d.ts +7 -0
  128. package/dist/lexical/markdownToLexical.js +10 -0
  129. package/dist/lexical/markdownToLexical.js.map +1 -0
  130. package/dist/plan/allowed.d.ts +7 -0
  131. package/dist/plan/allowed.js +19 -0
  132. package/dist/plan/allowed.js.map +1 -0
  133. package/dist/plan/applyPlan.d.ts +16 -0
  134. package/dist/plan/applyPlan.js +353 -0
  135. package/dist/plan/applyPlan.js.map +1 -0
  136. package/dist/plan/catalogue.d.ts +37 -0
  137. package/dist/plan/catalogue.js +157 -0
  138. package/dist/plan/catalogue.js.map +1 -0
  139. package/dist/plan/diff.d.ts +13 -0
  140. package/dist/plan/diff.js +87 -0
  141. package/dist/plan/diff.js.map +1 -0
  142. package/dist/plan/parsePlan.d.ts +27 -0
  143. package/dist/plan/parsePlan.js +322 -0
  144. package/dist/plan/parsePlan.js.map +1 -0
  145. package/dist/plan/prompt.d.ts +1 -0
  146. package/dist/plan/prompt.js +45 -0
  147. package/dist/plan/prompt.js.map +1 -0
  148. package/dist/plan/stored.d.ts +53 -0
  149. package/dist/plan/stored.js +3 -0
  150. package/dist/plan/stored.js.map +1 -0
  151. package/dist/plan/tokens.d.ts +8 -0
  152. package/dist/plan/tokens.js +79 -0
  153. package/dist/plan/tokens.js.map +1 -0
  154. package/dist/plan/view.d.ts +4 -0
  155. package/dist/plan/view.js +43 -0
  156. package/dist/plan/view.js.map +1 -0
  157. package/dist/plugin.d.ts +3 -0
  158. package/dist/plugin.js +464 -0
  159. package/dist/plugin.js.map +1 -0
  160. package/dist/read/outline.d.ts +7 -0
  161. package/dist/read/outline.js +70 -0
  162. package/dist/read/outline.js.map +1 -0
  163. package/dist/read/readTarget.d.ts +4 -0
  164. package/dist/read/readTarget.js +13 -0
  165. package/dist/read/readTarget.js.map +1 -0
  166. package/dist/read/readable.d.ts +8 -0
  167. package/dist/read/readable.js +131 -0
  168. package/dist/read/readable.js.map +1 -0
  169. package/dist/runtime.d.ts +15 -0
  170. package/dist/runtime.js +14 -0
  171. package/dist/runtime.js.map +1 -0
  172. package/dist/schema/fields.d.ts +113 -0
  173. package/dist/schema/fields.js +1036 -0
  174. package/dist/schema/fields.js.map +1 -0
  175. package/dist/schema/listingFields.d.ts +9 -0
  176. package/dist/schema/listingFields.js +95 -0
  177. package/dist/schema/listingFields.js.map +1 -0
  178. package/dist/schema/slugify.d.ts +2 -0
  179. package/dist/schema/slugify.js +5 -0
  180. package/dist/schema/slugify.js.map +1 -0
  181. package/dist/settings.d.ts +10 -0
  182. package/dist/settings.js +54 -0
  183. package/dist/settings.js.map +1 -0
  184. package/dist/types.d.ts +198 -0
  185. package/dist/types.js +6 -0
  186. package/dist/types.js.map +1 -0
  187. package/dist/uploads/routeAsset.d.ts +27 -0
  188. package/dist/uploads/routeAsset.js +127 -0
  189. package/dist/uploads/routeAsset.js.map +1 -0
  190. package/package.json +122 -3
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+ import { useConfig, useDocumentInfo, useForm, useFormModified } from '@payloadcms/ui';
3
+ import { useEffect, useRef } from 'react';
4
+ import { registerOpenDocument } from './openDocument.js';
5
+ /**
6
+ * Rendered before the document controls on every collection and global the assistant can read.
7
+ * It renders nothing. It tells the assistant drawer which document is open and how to read its form.
8
+ */ export function PageContextBridge() {
9
+ const { collectionSlug, globalSlug, id, title, setData } = useDocumentInfo();
10
+ const { getData, reset } = useForm();
11
+ const modified = useFormModified();
12
+ const { config } = useConfig();
13
+ const apiRoute = config.routes?.api ?? '/api';
14
+ const slug = globalSlug ?? collectionSlug;
15
+ const documentId = id === undefined || id === null ? undefined : String(id);
16
+ // Read when the assistant asks, so the registration stays put while the editor types.
17
+ const latest = useRef({
18
+ getData,
19
+ modified,
20
+ reset,
21
+ setData
22
+ });
23
+ useEffect(()=>{
24
+ latest.current = {
25
+ getData,
26
+ modified,
27
+ reset,
28
+ setData
29
+ };
30
+ });
31
+ useEffect(()=>{
32
+ if (!slug) {
33
+ return;
34
+ }
35
+ return registerOpenDocument({
36
+ context: {
37
+ kind: globalSlug ? 'global' : 'collection',
38
+ slug,
39
+ ...documentId ? {
40
+ id: documentId
41
+ } : {},
42
+ label: title || slug
43
+ },
44
+ getData: ()=>latest.current.getData(),
45
+ isModified: ()=>latest.current.modified,
46
+ // The same reload Payload uses to revert a document: fetch the saved draft, then rebuild the form from it.
47
+ reload: async ()=>{
48
+ const url = globalSlug ? `${apiRoute}/globals/${globalSlug}?depth=0&draft=true` : `${apiRoute}/${slug}/${documentId}?depth=0&draft=true`;
49
+ const response = await fetch(url, {
50
+ credentials: 'include'
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error('The saved changes could not be loaded into the editor.');
54
+ }
55
+ const doc = await response.json();
56
+ await latest.current.reset(doc);
57
+ if (doc && typeof doc === 'object') {
58
+ latest.current.setData(doc);
59
+ }
60
+ }
61
+ });
62
+ }, [
63
+ slug,
64
+ globalSlug,
65
+ documentId,
66
+ title,
67
+ apiRoute
68
+ ]);
69
+ return null;
70
+ }
71
+
72
+ //# sourceMappingURL=PageContextBridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/PageContextBridge.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useDocumentInfo, useForm, useFormModified } from '@payloadcms/ui'\nimport { useEffect, useRef } from 'react'\n\nimport { registerOpenDocument } from './openDocument.js'\n\n/**\n * Rendered before the document controls on every collection and global the assistant can read.\n * It renders nothing. It tells the assistant drawer which document is open and how to read its form.\n */\nexport function PageContextBridge() {\n const { collectionSlug, globalSlug, id, title, setData } = useDocumentInfo()\n const { getData, reset } = useForm()\n const modified = useFormModified()\n const { config } = useConfig()\n const apiRoute = config.routes?.api ?? '/api'\n const slug = globalSlug ?? collectionSlug\n const documentId = id === undefined || id === null ? undefined : String(id)\n\n // Read when the assistant asks, so the registration stays put while the editor types.\n const latest = useRef({ getData, modified, reset, setData })\n useEffect(() => {\n latest.current = { getData, modified, reset, setData }\n })\n\n useEffect(() => {\n if (!slug) {\n return\n }\n return registerOpenDocument({\n context: {\n kind: globalSlug ? 'global' : 'collection',\n slug,\n ...(documentId ? { id: documentId } : {}),\n label: title || slug,\n },\n getData: () => latest.current.getData(),\n isModified: () => latest.current.modified,\n // The same reload Payload uses to revert a document: fetch the saved draft, then rebuild the form from it.\n reload: async () => {\n const url = globalSlug\n ? `${apiRoute}/globals/${globalSlug}?depth=0&draft=true`\n : `${apiRoute}/${slug}/${documentId}?depth=0&draft=true`\n const response = await fetch(url, { credentials: 'include' })\n if (!response.ok) {\n throw new Error('The saved changes could not be loaded into the editor.')\n }\n const doc: unknown = await response.json()\n await latest.current.reset(doc)\n if (doc && typeof doc === 'object') {\n latest.current.setData(doc as Record<string, unknown>)\n }\n },\n })\n }, [slug, globalSlug, documentId, title, apiRoute])\n\n return null\n}\n"],"names":["useConfig","useDocumentInfo","useForm","useFormModified","useEffect","useRef","registerOpenDocument","PageContextBridge","collectionSlug","globalSlug","id","title","setData","getData","reset","modified","config","apiRoute","routes","api","slug","documentId","undefined","String","latest","current","context","kind","label","isModified","reload","url","response","fetch","credentials","ok","Error","doc","json"],"mappings":"AAAA;AAEA,SAASA,SAAS,EAAEC,eAAe,EAAEC,OAAO,EAAEC,eAAe,QAAQ,iBAAgB;AACrF,SAASC,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAEzC,SAASC,oBAAoB,QAAQ,oBAAmB;AAExD;;;CAGC,GACD,OAAO,SAASC;IACd,MAAM,EAAEC,cAAc,EAAEC,UAAU,EAAEC,EAAE,EAAEC,KAAK,EAAEC,OAAO,EAAE,GAAGX;IAC3D,MAAM,EAAEY,OAAO,EAAEC,KAAK,EAAE,GAAGZ;IAC3B,MAAMa,WAAWZ;IACjB,MAAM,EAAEa,MAAM,EAAE,GAAGhB;IACnB,MAAMiB,WAAWD,OAAOE,MAAM,EAAEC,OAAO;IACvC,MAAMC,OAAOX,cAAcD;IAC3B,MAAMa,aAAaX,OAAOY,aAAaZ,OAAO,OAAOY,YAAYC,OAAOb;IAExE,sFAAsF;IACtF,MAAMc,SAASnB,OAAO;QAAEQ;QAASE;QAAUD;QAAOF;IAAQ;IAC1DR,UAAU;QACRoB,OAAOC,OAAO,GAAG;YAAEZ;YAASE;YAAUD;YAAOF;QAAQ;IACvD;IAEAR,UAAU;QACR,IAAI,CAACgB,MAAM;YACT;QACF;QACA,OAAOd,qBAAqB;YAC1BoB,SAAS;gBACPC,MAAMlB,aAAa,WAAW;gBAC9BW;gBACA,GAAIC,aAAa;oBAAEX,IAAIW;gBAAW,IAAI,CAAC,CAAC;gBACxCO,OAAOjB,SAASS;YAClB;YACAP,SAAS,IAAMW,OAAOC,OAAO,CAACZ,OAAO;YACrCgB,YAAY,IAAML,OAAOC,OAAO,CAACV,QAAQ;YACzC,2GAA2G;YAC3Ge,QAAQ;gBACN,MAAMC,MAAMtB,aACR,GAAGQ,SAAS,SAAS,EAAER,WAAW,mBAAmB,CAAC,GACtD,GAAGQ,SAAS,CAAC,EAAEG,KAAK,CAAC,EAAEC,WAAW,mBAAmB,CAAC;gBAC1D,MAAMW,WAAW,MAAMC,MAAMF,KAAK;oBAAEG,aAAa;gBAAU;gBAC3D,IAAI,CAACF,SAASG,EAAE,EAAE;oBAChB,MAAM,IAAIC,MAAM;gBAClB;gBACA,MAAMC,MAAe,MAAML,SAASM,IAAI;gBACxC,MAAMd,OAAOC,OAAO,CAACX,KAAK,CAACuB;gBAC3B,IAAIA,OAAO,OAAOA,QAAQ,UAAU;oBAClCb,OAAOC,OAAO,CAACb,OAAO,CAACyB;gBACzB;YACF;QACF;IACF,GAAG;QAACjB;QAAMX;QAAYY;QAAYV;QAAOM;KAAS;IAElD,OAAO;AACT"}
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ export type PillTone = 'info' | 'neutral' | 'success' | 'warning';
3
+ interface PillProps {
4
+ children: ReactNode;
5
+ /** A collection or global slug. Gives the pill a dot in that collection's colour. */
6
+ entity?: string;
7
+ tone?: PillTone;
8
+ }
9
+ /** A short label. The text carries the meaning; tone and the collection dot only help scanning. */
10
+ export declare function Pill({ tone, entity, children }: PillProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styles from './AiAssistantPanel.module.scss';
3
+ /** A short label. The text carries the meaning; tone and the collection dot only help scanning. */ export function Pill({ tone = 'neutral', entity, children }) {
4
+ return /*#__PURE__*/ _jsxs("span", {
5
+ className: styles.assistant__pill,
6
+ "data-tone": tone,
7
+ style: entity ? {
8
+ '--pill-hue': hueOf(entity)
9
+ } : undefined,
10
+ children: [
11
+ entity ? /*#__PURE__*/ _jsx("span", {
12
+ "aria-hidden": "true",
13
+ className: styles['assistant__pill-dot']
14
+ }) : null,
15
+ children
16
+ ]
17
+ });
18
+ }
19
+ /** The same collection always gets the same colour. */ function hueOf(slug) {
20
+ let hash = 0;
21
+ for (const character of slug){
22
+ hash = (hash * 31 + character.charCodeAt(0)) % 360;
23
+ }
24
+ return hash;
25
+ }
26
+
27
+ //# sourceMappingURL=Pill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/Pill.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react'\n\nimport styles from './AiAssistantPanel.module.scss'\n\nexport type PillTone = 'info' | 'neutral' | 'success' | 'warning'\n\ninterface PillProps {\n children: ReactNode\n /** A collection or global slug. Gives the pill a dot in that collection's colour. */\n entity?: string\n tone?: PillTone\n}\n\n/** A short label. The text carries the meaning; tone and the collection dot only help scanning. */\nexport function Pill({ tone = 'neutral', entity, children }: PillProps) {\n return (\n <span\n className={styles.assistant__pill}\n data-tone={tone}\n style={entity ? ({ '--pill-hue': hueOf(entity) } as CSSProperties) : undefined}\n >\n {entity ? <span aria-hidden=\"true\" className={styles['assistant__pill-dot']} /> : null}\n {children}\n </span>\n )\n}\n\n/** The same collection always gets the same colour. */\nfunction hueOf(slug: string): number {\n let hash = 0\n for (const character of slug) {\n hash = (hash * 31 + character.charCodeAt(0)) % 360\n }\n return hash\n}\n"],"names":["styles","Pill","tone","entity","children","span","className","assistant__pill","data-tone","style","hueOf","undefined","aria-hidden","slug","hash","character","charCodeAt"],"mappings":";AAEA,OAAOA,YAAY,iCAAgC;AAWnD,iGAAiG,GACjG,OAAO,SAASC,KAAK,EAAEC,OAAO,SAAS,EAAEC,MAAM,EAAEC,QAAQ,EAAa;IACpE,qBACE,MAACC;QACCC,WAAWN,OAAOO,eAAe;QACjCC,aAAWN;QACXO,OAAON,SAAU;YAAE,cAAcO,MAAMP;QAAQ,IAAsBQ;;YAEpER,uBAAS,KAACE;gBAAKO,eAAY;gBAAON,WAAWN,MAAM,CAAC,sBAAsB;iBAAO;YACjFI;;;AAGP;AAEA,qDAAqD,GACrD,SAASM,MAAMG,IAAY;IACzB,IAAIC,OAAO;IACX,KAAK,MAAMC,aAAaF,KAAM;QAC5BC,OAAO,AAACA,CAAAA,OAAO,KAAKC,UAAUC,UAAU,CAAC,EAAC,IAAK;IACjD;IACA,OAAOF;AACT"}
@@ -0,0 +1,14 @@
1
+ import type { AssistantChange, PlanStatus, SubmitPlanOutput } from '../types.js';
2
+ interface PlanCardProps {
3
+ apiRoute: string;
4
+ onOutcome: (planId: string, update: {
5
+ changes?: AssistantChange[];
6
+ status: PlanStatus;
7
+ }) => void;
8
+ output: Extract<SubmitPlanOutput, {
9
+ ok: true;
10
+ }>;
11
+ }
12
+ /** A plan the assistant proposed. Nothing is written until the editor chooses Proceed. */
13
+ export declare function PlanCard({ output, apiRoute, onOutcome }: PlanCardProps): import("react").JSX.Element;
14
+ export {};
@@ -0,0 +1,465 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Button } from '@payloadcms/ui';
4
+ import { useId, useState } from 'react';
5
+ import styles from './AiAssistantPanel.module.scss';
6
+ import { currentOpenDocument } from './openDocument.js';
7
+ import { Pill } from './Pill.js';
8
+ import { useEntityLabel } from './useEntityLabel.js';
9
+ /** A plan the assistant proposed. Nothing is written until the editor chooses Proceed. */ export function PlanCard({ output, apiRoute, onOutcome }) {
10
+ const headingId = useId();
11
+ const [pending, setPending] = useState(false);
12
+ const [error, setError] = useState('');
13
+ const [editor, setEditor] = useState();
14
+ const { plan, status } = output;
15
+ const proceed = async ()=>{
16
+ setError('');
17
+ setPending(true);
18
+ try {
19
+ // Only the plan's id: the files it saves are already stored, and the server reads them from there.
20
+ const response = await fetch(`${apiRoute}/ai-assistant/apply`, {
21
+ method: 'POST',
22
+ credentials: 'include',
23
+ headers: {
24
+ 'Content-Type': 'application/json'
25
+ },
26
+ body: JSON.stringify({
27
+ planId: plan.id
28
+ })
29
+ }).catch(()=>{
30
+ throw new Error('The changes could not be sent. Check your connection and choose Proceed again.');
31
+ });
32
+ const payload = await response.json().catch(()=>null);
33
+ if (!response.ok) {
34
+ throw new Error(messageOf(payload) ?? 'The changes could not be saved.');
35
+ }
36
+ const changes = payload && typeof payload === 'object' && 'changes' in payload && Array.isArray(payload.changes) ? payload.changes : [];
37
+ onOutcome(plan.id, {
38
+ status: 'applied',
39
+ changes
40
+ });
41
+ setEditor(await syncOpenEditor(changes));
42
+ } catch (caught) {
43
+ setError(caught instanceof Error && caught.message ? caught.message : 'The changes could not be saved.');
44
+ } finally{
45
+ setPending(false);
46
+ }
47
+ };
48
+ const loadIntoEditor = async ()=>{
49
+ const open = currentOpenDocument();
50
+ if (!open) {
51
+ return;
52
+ }
53
+ setEditor(await reloadEditor(open));
54
+ };
55
+ const cancel = async ()=>{
56
+ setError('');
57
+ setPending(true);
58
+ try {
59
+ const response = await fetch(`${apiRoute}/ai-assistant/cancel`, {
60
+ method: 'POST',
61
+ credentials: 'include',
62
+ headers: {
63
+ 'Content-Type': 'application/json'
64
+ },
65
+ body: JSON.stringify({
66
+ planId: plan.id
67
+ })
68
+ });
69
+ if (!response.ok) {
70
+ throw new Error(messageOf(await response.json().catch(()=>null)) ?? 'The plan could not be cancelled.');
71
+ }
72
+ onOutcome(plan.id, {
73
+ status: 'cancelled'
74
+ });
75
+ } catch (caught) {
76
+ setError(caught instanceof Error && caught.message ? caught.message : 'The plan could not be cancelled.');
77
+ } finally{
78
+ setPending(false);
79
+ }
80
+ };
81
+ return /*#__PURE__*/ _jsxs("section", {
82
+ "aria-labelledby": headingId,
83
+ className: styles.assistant__plan,
84
+ "data-status": status,
85
+ children: [
86
+ /*#__PURE__*/ _jsxs("div", {
87
+ className: styles.assistant__planHeader,
88
+ children: [
89
+ /*#__PURE__*/ _jsx("h4", {
90
+ className: styles.assistant__planTitle,
91
+ id: headingId,
92
+ children: plan.blocked ? 'Needs a decision' : 'Plan'
93
+ }),
94
+ /*#__PURE__*/ _jsx(Pill, {
95
+ tone: statusTone[status],
96
+ children: statusLabel[status]
97
+ })
98
+ ]
99
+ }),
100
+ /*#__PURE__*/ _jsx("p", {
101
+ className: styles.assistant__text,
102
+ children: plan.summary
103
+ }),
104
+ plan.blocked ? /*#__PURE__*/ _jsx(Decision, {
105
+ plan: plan
106
+ }) : /*#__PURE__*/ _jsx(Operations, {
107
+ plan: plan
108
+ }),
109
+ status === 'applied' ? /*#__PURE__*/ _jsx(Changes, {
110
+ changes: output.changes ?? []
111
+ }) : null,
112
+ editor === 'reloaded' ? /*#__PURE__*/ _jsx("p", {
113
+ className: styles.assistant__meta,
114
+ children: "The page behind this panel now shows these changes."
115
+ }) : null,
116
+ editor === 'unsaved' ? /*#__PURE__*/ _jsxs("div", {
117
+ className: styles.assistant__notice,
118
+ children: [
119
+ /*#__PURE__*/ _jsx("p", {
120
+ className: styles.assistant__meta,
121
+ children: "This page has edits you have not saved, so the editor still shows them. Loading the saved changes replaces your unsaved edits."
122
+ }),
123
+ /*#__PURE__*/ _jsx(Button, {
124
+ buttonStyle: "secondary",
125
+ margin: false,
126
+ onClick: ()=>void loadIntoEditor(),
127
+ size: "small",
128
+ type: "button",
129
+ children: "Load saved changes"
130
+ })
131
+ ]
132
+ }) : null,
133
+ editor === 'failed' ? /*#__PURE__*/ _jsx("p", {
134
+ className: styles.assistant__error,
135
+ role: "alert",
136
+ children: "The changes were saved, but the editor could not be refreshed. Reload the page to see them."
137
+ }) : null,
138
+ status === 'cancelled' ? /*#__PURE__*/ _jsx("p", {
139
+ className: styles.assistant__meta,
140
+ children: "Cancelled. Nothing was saved."
141
+ }) : null,
142
+ status === 'proposed' ? /*#__PURE__*/ _jsx(_Fragment, {
143
+ children: /*#__PURE__*/ _jsxs("div", {
144
+ className: styles.assistant__actions,
145
+ children: [
146
+ /*#__PURE__*/ _jsx(Button, {
147
+ buttonStyle: "primary",
148
+ disabled: pending,
149
+ margin: false,
150
+ onClick: ()=>void proceed(),
151
+ size: "small",
152
+ type: "button",
153
+ children: "Proceed"
154
+ }),
155
+ /*#__PURE__*/ _jsx(Button, {
156
+ buttonStyle: "secondary",
157
+ disabled: pending,
158
+ margin: false,
159
+ onClick: ()=>void cancel(),
160
+ size: "small",
161
+ type: "button",
162
+ children: "Cancel"
163
+ })
164
+ ]
165
+ })
166
+ }) : null,
167
+ error ? /*#__PURE__*/ _jsx("p", {
168
+ className: styles.assistant__error,
169
+ role: "alert",
170
+ children: error
171
+ }) : null
172
+ ]
173
+ });
174
+ }
175
+ /**
176
+ * When a plan updates the document that is open, its form still shows the old values. Reload it,
177
+ * unless the editor has unsaved edits there: those are theirs to keep or replace.
178
+ */ async function syncOpenEditor(changes) {
179
+ const open = currentOpenDocument();
180
+ if (!open) {
181
+ return undefined;
182
+ }
183
+ const { kind, slug, id } = open.context;
184
+ const touched = changes.some((change)=>change.action === 'updated' && change.collection === slug && (kind === 'global' || change.id === id));
185
+ if (!touched) {
186
+ return undefined;
187
+ }
188
+ if (open.isModified()) {
189
+ return 'unsaved';
190
+ }
191
+ return reloadEditor(open);
192
+ }
193
+ async function reloadEditor(open) {
194
+ try {
195
+ await open.reload();
196
+ return 'reloaded';
197
+ } catch {
198
+ return 'failed';
199
+ }
200
+ }
201
+ const statusLabel = {
202
+ proposed: 'Ready to review',
203
+ blocked: 'Waiting on you',
204
+ applied: 'Saved',
205
+ cancelled: 'Cancelled'
206
+ };
207
+ const statusTone = {
208
+ proposed: 'info',
209
+ blocked: 'warning',
210
+ applied: 'success',
211
+ cancelled: 'neutral'
212
+ };
213
+ function Decision({ plan }) {
214
+ const entityLabel = useEntityLabel();
215
+ return /*#__PURE__*/ _jsxs(_Fragment, {
216
+ children: [
217
+ /*#__PURE__*/ _jsx("ul", {
218
+ className: styles.assistant__list,
219
+ children: plan.questions.map((question)=>/*#__PURE__*/ _jsx("li", {
220
+ children: question
221
+ }, question))
222
+ }),
223
+ plan.candidates.length > 0 ? /*#__PURE__*/ _jsx("ul", {
224
+ className: styles.assistant__list,
225
+ children: plan.candidates.map((candidate)=>/*#__PURE__*/ _jsxs("li", {
226
+ children: [
227
+ /*#__PURE__*/ _jsx(Pill, {
228
+ entity: candidate.slug,
229
+ children: entityLabel(candidate.slug, candidate.kind)
230
+ }),
231
+ ' ',
232
+ candidate.href ? /*#__PURE__*/ _jsx("a", {
233
+ href: candidate.href,
234
+ children: candidate.label
235
+ }) : candidate.label
236
+ ]
237
+ }, `${candidate.slug}-${candidate.id ?? candidate.label}`))
238
+ }) : null,
239
+ /*#__PURE__*/ _jsx("p", {
240
+ className: styles.assistant__meta,
241
+ children: "Reply below to choose."
242
+ })
243
+ ]
244
+ });
245
+ }
246
+ function Operations({ plan }) {
247
+ const entityLabel = useEntityLabel();
248
+ return /*#__PURE__*/ _jsxs(_Fragment, {
249
+ children: [
250
+ plan.operations.length > 0 ? /*#__PURE__*/ _jsx("ul", {
251
+ className: styles.assistant__ops,
252
+ children: plan.operations.map((operation, index)=>/*#__PURE__*/ _jsxs("li", {
253
+ className: styles.assistant__op,
254
+ children: [
255
+ /*#__PURE__*/ _jsxs("div", {
256
+ className: styles.assistant__opHeader,
257
+ children: [
258
+ /*#__PURE__*/ _jsx(Pill, {
259
+ tone: operation.action === 'create' ? 'success' : 'info',
260
+ children: operation.action === 'create' ? 'Create' : 'Update'
261
+ }),
262
+ /*#__PURE__*/ _jsx(Pill, {
263
+ entity: operation.slug,
264
+ children: entityLabel(operation.slug, operation.kind)
265
+ }),
266
+ /*#__PURE__*/ _jsx(SavePill, {
267
+ immediate: operation.savesImmediately
268
+ })
269
+ ]
270
+ }),
271
+ /*#__PURE__*/ _jsx("p", {
272
+ className: styles.assistant__opLabel,
273
+ children: operation.href ? /*#__PURE__*/ _jsx("a", {
274
+ href: operation.href,
275
+ children: operation.label
276
+ }) : operation.label
277
+ }),
278
+ /*#__PURE__*/ _jsx("p", {
279
+ className: styles.assistant__meta,
280
+ children: operation.summary
281
+ }),
282
+ operation.fieldChanges && operation.fieldChanges.length > 0 ? /*#__PURE__*/ _jsx(FieldChanges, {
283
+ changes: operation.fieldChanges
284
+ }) : operation.fields.length > 0 ? /*#__PURE__*/ _jsxs("p", {
285
+ className: styles.assistant__meta,
286
+ children: [
287
+ "Fields: ",
288
+ operation.fields.join(', ')
289
+ ]
290
+ }) : null
291
+ ]
292
+ }, `${operation.slug}-${operation.id ?? index}`))
293
+ }) : null,
294
+ plan.files.length > 0 ? /*#__PURE__*/ _jsx("ul", {
295
+ className: styles.assistant__ops,
296
+ children: plan.files.map((file)=>/*#__PURE__*/ _jsxs("li", {
297
+ className: styles.assistant__op,
298
+ children: [
299
+ /*#__PURE__*/ _jsxs("div", {
300
+ className: styles.assistant__opHeader,
301
+ children: [
302
+ /*#__PURE__*/ _jsx(Pill, {
303
+ children: "Upload"
304
+ }),
305
+ /*#__PURE__*/ _jsx(Pill, {
306
+ entity: file.collection,
307
+ children: entityLabel(file.collection)
308
+ }),
309
+ /*#__PURE__*/ _jsx(SavePill, {
310
+ immediate: file.savesImmediately
311
+ })
312
+ ]
313
+ }),
314
+ /*#__PURE__*/ _jsx("p", {
315
+ className: styles.assistant__opLabel,
316
+ children: file.name
317
+ }),
318
+ /*#__PURE__*/ _jsx("p", {
319
+ className: styles.assistant__meta,
320
+ children: file.note
321
+ })
322
+ ]
323
+ }, file.index))
324
+ }) : null
325
+ ]
326
+ });
327
+ }
328
+ function SavePill({ immediate }) {
329
+ return immediate ? /*#__PURE__*/ _jsx(Pill, {
330
+ tone: "warning",
331
+ children: "Saves immediately"
332
+ }) : /*#__PURE__*/ _jsx(Pill, {
333
+ children: "Draft"
334
+ });
335
+ }
336
+ /** Native disclosure, so it works with a keyboard and screen reader and the values stay in the page. */ function FieldChanges({ changes }) {
337
+ return /*#__PURE__*/ _jsxs("details", {
338
+ className: styles.assistant__diff,
339
+ open: changes.length <= 3,
340
+ children: [
341
+ /*#__PURE__*/ _jsxs("summary", {
342
+ children: [
343
+ "Show changes ",
344
+ /*#__PURE__*/ _jsxs("span", {
345
+ className: styles.assistant__meta,
346
+ children: [
347
+ "(",
348
+ changes.length,
349
+ ")"
350
+ ]
351
+ })
352
+ ]
353
+ }),
354
+ /*#__PURE__*/ _jsx("dl", {
355
+ className: styles.assistant__diffList,
356
+ children: changes.map((change)=>/*#__PURE__*/ _jsxs("div", {
357
+ className: styles.assistant__diffItem,
358
+ children: [
359
+ /*#__PURE__*/ _jsxs("dt", {
360
+ className: styles.assistant__diffPath,
361
+ children: [
362
+ /*#__PURE__*/ _jsx("code", {
363
+ children: change.path
364
+ }),
365
+ change.added ? /*#__PURE__*/ _jsx(Pill, {
366
+ tone: "success",
367
+ children: "Added blocks"
368
+ }) : null
369
+ ]
370
+ }),
371
+ change.before !== undefined ? /*#__PURE__*/ _jsxs("dd", {
372
+ className: styles.assistant__diffValue,
373
+ "data-side": "before",
374
+ children: [
375
+ /*#__PURE__*/ _jsx("span", {
376
+ className: styles.assistant__diffLabel,
377
+ children: "Now"
378
+ }),
379
+ change.media?.before ? /*#__PURE__*/ _jsx(Media, {
380
+ media: change.media.before
381
+ }) : change.before
382
+ ]
383
+ }) : null,
384
+ /*#__PURE__*/ _jsxs("dd", {
385
+ className: styles.assistant__diffValue,
386
+ "data-side": "after",
387
+ children: [
388
+ /*#__PURE__*/ _jsx("span", {
389
+ className: styles.assistant__diffLabel,
390
+ children: change.before === undefined ? 'New' : 'Proposed'
391
+ }),
392
+ change.media?.after ? /*#__PURE__*/ _jsx(Media, {
393
+ media: change.media.after
394
+ }) : change.after
395
+ ]
396
+ })
397
+ ]
398
+ }, change.path))
399
+ })
400
+ ]
401
+ });
402
+ }
403
+ /** A file in a diff. The thumbnail is decorative; the name says which file it is. */ function Media({ media }) {
404
+ return /*#__PURE__*/ _jsxs("span", {
405
+ className: styles.assistant__media,
406
+ children: [
407
+ media.thumbnail ? // A plain img: an admin preview of an existing upload, already resized by its own URL.
408
+ /*#__PURE__*/ _jsx("img", {
409
+ alt: "",
410
+ className: styles.assistant__mediaThumb,
411
+ height: 40,
412
+ loading: "lazy",
413
+ src: media.thumbnail,
414
+ width: 40
415
+ }) : /*#__PURE__*/ _jsx("span", {
416
+ "aria-hidden": "true",
417
+ className: styles.assistant__mediaThumb
418
+ }),
419
+ /*#__PURE__*/ _jsxs("span", {
420
+ children: [
421
+ media.label,
422
+ media.pending ? /*#__PURE__*/ _jsx("span", {
423
+ className: styles.assistant__meta,
424
+ children: " (attached, saved on Proceed)"
425
+ }) : null
426
+ ]
427
+ })
428
+ ]
429
+ });
430
+ }
431
+ function Changes({ changes }) {
432
+ if (changes.length === 0) {
433
+ return /*#__PURE__*/ _jsx("p", {
434
+ className: styles.assistant__meta,
435
+ children: "Saved. There is no document to open."
436
+ });
437
+ }
438
+ return /*#__PURE__*/ _jsx("ul", {
439
+ className: styles.assistant__list,
440
+ children: changes.map((change)=>/*#__PURE__*/ _jsxs("li", {
441
+ children: [
442
+ /*#__PURE__*/ _jsx("a", {
443
+ href: change.href,
444
+ children: change.label
445
+ }),
446
+ /*#__PURE__*/ _jsxs("span", {
447
+ className: styles.assistant__meta,
448
+ children: [
449
+ ' ',
450
+ change.action,
451
+ change.detail ? `. ${change.detail}` : ''
452
+ ]
453
+ })
454
+ ]
455
+ }, change.href))
456
+ });
457
+ }
458
+ function messageOf(payload) {
459
+ if (payload && typeof payload === 'object' && 'message' in payload && typeof payload.message === 'string') {
460
+ return payload.message;
461
+ }
462
+ return undefined;
463
+ }
464
+
465
+ //# sourceMappingURL=PlanCard.js.map