@mvriu5/payload-ai 1.1.1 → 1.2.0

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.
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useConfig } from "@payloadcms/ui";
4
4
  import { formatAdminURL } from "payload/shared";
5
5
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -84,7 +84,7 @@ const parseSSEEvent = (chunk)=>{
84
84
  if (!eventName) return null;
85
85
  try {
86
86
  const data = JSON.parse(dataLines.join("\n"));
87
- if (eventName !== "text" && eventName !== "proposals" && eventName !== "error" && eventName !== "done") {
87
+ if (eventName !== "text" && eventName !== "proposals" && eventName !== "error" && eventName !== "done" && eventName !== "debug") {
88
88
  return null;
89
89
  }
90
90
  return {
@@ -96,6 +96,59 @@ const parseSSEEvent = (chunk)=>{
96
96
  }
97
97
  };
98
98
  const sanitizeResponseText = (value)=>value.replace(/\*\*/g, "");
99
+ const getDebugReasonLabel = (reason)=>{
100
+ switch(reason){
101
+ case "model_did_not_call_tool":
102
+ return "Model did not create a proposal tool call.";
103
+ case "proposal_created":
104
+ return "Proposal created.";
105
+ case "tool_validation_failed":
106
+ return "Tool validation failed before a proposal could be created.";
107
+ case "write_intent_without_tool_call":
108
+ return "The selected model did not produce the required proposal tool call for this content change.";
109
+ default:
110
+ return "Unknown";
111
+ }
112
+ };
113
+ const getApplyDebugReasonLabel = (reason)=>{
114
+ switch(reason){
115
+ case "unauthorized":
116
+ return "Request was not authorized.";
117
+ case "missing_proposal":
118
+ return "No proposal was submitted to apply.";
119
+ case "invalid_signature":
120
+ return "Proposal signature was invalid or expired.";
121
+ case "invalid_proposal_shape":
122
+ return "Proposal shape was invalid.";
123
+ case "sensitive_data_in_data":
124
+ case "sensitive_data_in_localized_data":
125
+ return "Proposal contained sensitive data.";
126
+ case "unknown_global":
127
+ return "Target global was not found.";
128
+ case "unknown_or_disallowed_collection":
129
+ return "Target collection is unknown or not allowed.";
130
+ case "invalid_collection_write_shape":
131
+ return "Proposal data does not match the target collection schema.";
132
+ case "invalid_global_write_shape":
133
+ return "Proposal data does not match the target global schema.";
134
+ case "localized_create_without_locales":
135
+ return "Localized create proposal had no locale entries.";
136
+ case "missing_auth_password":
137
+ return "Auth create proposal was missing a password.";
138
+ case "missing_auth_email":
139
+ return "Auth create proposal was missing an email.";
140
+ case "payload_operation_failed":
141
+ return "Payload rejected the write operation.";
142
+ default:
143
+ return "Unknown";
144
+ }
145
+ };
146
+ const getChatDebugMessage = (debugInfo)=>{
147
+ if (debugInfo.toolFailures?.length) {
148
+ return debugInfo.toolFailures[0]?.message || getDebugReasonLabel(debugInfo.reason);
149
+ }
150
+ return getDebugReasonLabel(debugInfo.reason);
151
+ };
99
152
  const isMentionBoundary = (character)=>{
100
153
  return character === undefined || /\s/.test(character);
101
154
  };
@@ -122,6 +175,7 @@ const getActiveMentionRange = (valueBeforeCaret)=>{
122
175
  };
123
176
  const svgNamespace = "http://www.w3.org/2000/svg";
124
177
  const auditLogCollectionSlug = "payload-ai-auditlog";
178
+ const responseOnlyToastCooldownMs = 10000;
125
179
  const appendSvgPath = (svg, d)=>{
126
180
  const path = document.createElementNS(svgNamespace, "path");
127
181
  path.setAttribute("d", d);
@@ -193,6 +247,8 @@ const AIInput = ()=>{
193
247
  const [appliedProposalIndexes, setAppliedProposalIndexes] = useState([]);
194
248
  const [response, setResponse] = useState("");
195
249
  const [tokenUsage, setTokenUsage] = useState(null);
250
+ const [chatDebugInfo, setChatDebugInfo] = useState(null);
251
+ const [applyDebugInfo, setApplyDebugInfo] = useState(null);
196
252
  const [error, setError] = useState("");
197
253
  const [proposals, setProposals] = useState([]);
198
254
  const [appliedChanges, setAppliedChanges] = useState([]);
@@ -231,7 +287,7 @@ const AIInput = ()=>{
231
287
  const timeout = window.setTimeout(()=>{
232
288
  setResponse("");
233
289
  clearInput();
234
- }, 5000);
290
+ }, responseOnlyToastCooldownMs);
235
291
  return ()=>window.clearTimeout(timeout);
236
292
  }, [
237
293
  error,
@@ -304,6 +360,9 @@ const AIInput = ()=>{
304
360
  ...filteredMentionOptions,
305
361
  ...documentSuggestions
306
362
  ];
363
+ const shouldShowChatDebugInfo = Boolean(chatDebugInfo) && (Boolean(error) || chatDebugInfo?.reason !== "proposal_created");
364
+ const shouldShowApplyDebugInfo = Boolean(applyDebugInfo) && Boolean(error);
365
+ const actionToastDescription = response;
307
366
  const getTextBeforeCaret = (element)=>{
308
367
  const selection = window.getSelection();
309
368
  if (!selection || selection.rangeCount === 0) return "";
@@ -474,6 +533,8 @@ const AIInput = ()=>{
474
533
  setProposals([]);
475
534
  setResponse("");
476
535
  setTokenUsage(null);
536
+ setChatDebugInfo(null);
537
+ setApplyDebugInfo(null);
477
538
  try {
478
539
  const res = await fetch(formatAdminURL({
479
540
  apiRoute: config.routes.api,
@@ -499,7 +560,10 @@ const AIInput = ()=>{
499
560
  const reader = res.body.getReader();
500
561
  const decoder = new TextDecoder();
501
562
  let buffer = "";
563
+ let finalDebugInfo = null;
502
564
  let receivedProposals = [];
565
+ let receivedText = "";
566
+ let receivedVisibleText = "";
503
567
  while(true){
504
568
  const { done, value } = await reader.read();
505
569
  if (done) break;
@@ -513,7 +577,10 @@ const AIInput = ()=>{
513
577
  if (!event) continue;
514
578
  if (event.event === "text") {
515
579
  if (event.data.delta) {
516
- setResponse((current)=>current + sanitizeResponseText(event.data.delta || ""));
580
+ const nextDelta = sanitizeResponseText(event.data.delta || "");
581
+ receivedText += nextDelta;
582
+ receivedVisibleText += nextDelta.replace(/\s+/g, "");
583
+ setResponse((current)=>current + nextDelta);
517
584
  }
518
585
  continue;
519
586
  }
@@ -523,6 +590,22 @@ const AIInput = ()=>{
523
590
  setTokenUsage(event.data.usage || null);
524
591
  continue;
525
592
  }
593
+ if (event.event === "debug") {
594
+ finalDebugInfo = event.data;
595
+ setChatDebugInfo(event.data);
596
+ if ((event.data.proposalCount || 0) === 0 && !receivedVisibleText) {
597
+ // Show specific tool validation message if present, otherwise a generic no‑action message
598
+ if (event.data.reason === "tool_validation_failed") {
599
+ const msg = getChatDebugMessage(event.data);
600
+ setResponse(msg);
601
+ // Auto‑clear after the standard toast timeout
602
+ window.setTimeout(()=>setResponse(""), responseOnlyToastCooldownMs);
603
+ } else {
604
+ setResponse("No action needed");
605
+ }
606
+ }
607
+ continue;
608
+ }
526
609
  if (event.event === "error") {
527
610
  throw new Error(event.data.error || "AI request failed");
528
611
  }
@@ -534,16 +617,42 @@ const AIInput = ()=>{
534
617
  setProposals(receivedProposals);
535
618
  setTokenUsage(finalEvent.data.usage || null);
536
619
  }
620
+ if (finalEvent?.event === "debug") {
621
+ finalDebugInfo = finalEvent.data;
622
+ setChatDebugInfo(finalEvent.data);
623
+ if ((finalEvent.data.proposalCount || 0) === 0 && !receivedVisibleText) {
624
+ // Same logic as above for the final event
625
+ if (finalEvent.data.reason === "tool_validation_failed") {
626
+ setResponse(getChatDebugMessage(finalEvent.data));
627
+ } else {
628
+ setResponse("No action needed");
629
+ }
630
+ }
631
+ }
537
632
  if (finalEvent?.event === "error") {
538
633
  throw new Error(finalEvent.data.error || "AI request failed");
539
634
  }
540
635
  if (receivedProposals.length === 0) {
636
+ if (finalDebugInfo) {
637
+ const debugMessage = getChatDebugMessage(finalDebugInfo);
638
+ const isMeaningfulVisibleText = receivedVisibleText.length >= 12;
639
+ const trimmedReceivedText = receivedText.trim();
640
+ if (!isMeaningfulVisibleText || trimmedReceivedText.length < 12) {
641
+ setResponse(debugMessage);
642
+ } else {
643
+ setResponse((current)=>current.trim() || debugMessage);
644
+ }
645
+ } else {
646
+ // Fallback when no debug info is provided (e.g., tool validation failures without a debug event)
647
+ setResponse("No action needed");
648
+ }
541
649
  clearInput();
542
650
  }
543
651
  } catch (err) {
544
652
  setProposals([]);
545
653
  setResponse("");
546
654
  setTokenUsage(null);
655
+ setApplyDebugInfo(null);
547
656
  setError(err instanceof Error ? err.message : "AI request failed");
548
657
  } finally{
549
658
  setIsLoading(false);
@@ -572,6 +681,7 @@ const AIInput = ()=>{
572
681
  if (!res.ok) {
573
682
  setProposals([]);
574
683
  setResponse("");
684
+ setApplyDebugInfo(result.debug || null);
575
685
  throw new Error(result.error || "Could not apply proposal");
576
686
  }
577
687
  setAppliedProposalIndexes([]);
@@ -579,6 +689,8 @@ const AIInput = ()=>{
579
689
  setProposals([]);
580
690
  setResponse("");
581
691
  setTokenUsage(null);
692
+ setChatDebugInfo(null);
693
+ setApplyDebugInfo(null);
582
694
  if (result.change) {
583
695
  setAppliedChanges((current)=>[
584
696
  result.change,
@@ -700,7 +812,7 @@ const AIInput = ()=>{
700
812
  }),
701
813
  /*#__PURE__*/ _jsxs("button", {
702
814
  className: styles.chatButton,
703
- disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading,
815
+ disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading || Boolean(error) || Boolean(actionToastDescription) || proposals.length > 0,
704
816
  onClick: ()=>void handleSubmit(),
705
817
  type: "button",
706
818
  children: [
@@ -716,7 +828,7 @@ const AIInput = ()=>{
716
828
  /*#__PURE__*/ _jsx(ActionToast, {
717
829
  apiRoute: config.routes.api,
718
830
  appliedProposalIndexes: appliedProposalIndexes,
719
- description: response,
831
+ description: actionToastDescription,
720
832
  error: error,
721
833
  getViewURL: getProposalViewURL,
722
834
  isApplying: isApplying,
@@ -726,6 +838,8 @@ const AIInput = ()=>{
726
838
  setProposals([]);
727
839
  setResponse("");
728
840
  setTokenUsage(null);
841
+ setChatDebugInfo(null);
842
+ setApplyDebugInfo(null);
729
843
  clearInput();
730
844
  },
731
845
  onDismissError: ()=>{
@@ -733,7 +847,42 @@ const AIInput = ()=>{
733
847
  },
734
848
  onApply: (proposal, _index)=>void handleApplyProposal(proposal),
735
849
  proposals: proposals,
850
+ prompt: prompt,
736
851
  tokenUsage: tokenUsage
852
+ }),
853
+ shouldShowApplyDebugInfo && applyDebugInfo && /*#__PURE__*/ _jsxs("div", {
854
+ className: styles.debugInfo,
855
+ children: [
856
+ /*#__PURE__*/ _jsx("strong", {
857
+ children: "Apply debug"
858
+ }),
859
+ /*#__PURE__*/ _jsx("br", {}),
860
+ "Reason: ",
861
+ getApplyDebugReasonLabel(applyDebugInfo.reason),
862
+ /*#__PURE__*/ _jsx("br", {}),
863
+ "Phase: ",
864
+ applyDebugInfo.phase,
865
+ /*#__PURE__*/ _jsx("br", {}),
866
+ "Target: ",
867
+ applyDebugInfo.collection || applyDebugInfo.slug || "unknown",
868
+ applyDebugInfo.id ? /*#__PURE__*/ _jsxs(_Fragment, {
869
+ children: [
870
+ /*#__PURE__*/ _jsx("br", {}),
871
+ "ID: ",
872
+ applyDebugInfo.id
873
+ ]
874
+ }) : null,
875
+ applyDebugInfo.details ? /*#__PURE__*/ _jsxs(_Fragment, {
876
+ children: [
877
+ /*#__PURE__*/ _jsx("br", {}),
878
+ "Details:",
879
+ /*#__PURE__*/ _jsx("pre", {
880
+ className: styles.debugDetails,
881
+ children: JSON.stringify(applyDebugInfo.details, null, 2)
882
+ })
883
+ ]
884
+ }) : null
885
+ ]
737
886
  })
738
887
  ]
739
888
  }),
@@ -101,8 +101,10 @@
101
101
  background: transparent;
102
102
  border: 0;
103
103
  color: var(--theme-text);
104
+ display: block;
104
105
  font: inherit;
105
- min-height: 84px;
106
+ height: 84px;
107
+ overflow-y: auto;
106
108
  padding: 0;
107
109
  white-space: pre-wrap;
108
110
  width: 100%;
@@ -120,7 +122,10 @@
120
122
  .chatInputSurface {
121
123
  background: var(--theme-input-bg);
122
124
  border: 1px solid var(--theme-elevation-150);
125
+ box-sizing: border-box;
123
126
  border-radius: 4px;
127
+ height: 108px;
128
+ overflow: hidden;
124
129
  padding: 12px;
125
130
  width: 100%;
126
131
  }
@@ -239,6 +244,11 @@
239
244
  white-space: pre-wrap;
240
245
  }
241
246
 
247
+ .debugDetails {
248
+ margin: 8px 0 0;
249
+ white-space: pre-wrap;
250
+ }
251
+
242
252
  .chatResponse {
243
253
  background: var(--theme-elevation-50);
244
254
  border: 1px solid var(--theme-elevation-100);
@@ -285,5 +295,4 @@
285
295
  .chatButton {
286
296
  align-self: stretch;
287
297
  }
288
-
289
298
  }
@@ -19,6 +19,7 @@ type ActionToastProps = {
19
19
  onDismiss?: () => void;
20
20
  onDismissError?: () => void;
21
21
  onApply: (proposal: ActionProposal, index: number) => void;
22
+ prompt?: string;
22
23
  proposals: ActionProposal[];
23
24
  tokenUsage?: {
24
25
  inputTokens?: number;
@@ -26,5 +27,5 @@ type ActionToastProps = {
26
27
  totalTokens?: number;
27
28
  } | null;
28
29
  };
29
- export declare const ActionToast: ({ apiRoute, appliedProposalIndexes, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, proposals, tokenUsage, }: ActionToastProps) => import("react").JSX.Element | null;
30
+ export declare const ActionToast: ({ apiRoute, appliedProposalIndexes, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, prompt, proposals, tokenUsage, }: ActionToastProps) => import("react").JSX.Element | null;
30
31
  export {};
@@ -20,7 +20,7 @@ const getSafeProposalDetails = (proposal)=>{
20
20
  }
21
21
  return redactedProposal;
22
22
  };
23
- export const ActionToast = ({ apiRoute, appliedProposalIndexes, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, proposals, tokenUsage })=>{
23
+ export const ActionToast = ({ apiRoute, appliedProposalIndexes, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, prompt, proposals, tokenUsage })=>{
24
24
  const [activeDiff, setActiveDiff] = useState(null);
25
25
  const [diffError, setDiffError] = useState("");
26
26
  const [loadingDiffIndex, setLoadingDiffIndex] = useState(null);
@@ -36,7 +36,8 @@ export const ActionToast = ({ apiRoute, appliedProposalIndexes, description, err
36
36
  path: "/ai-proposal-diff"
37
37
  }), {
38
38
  body: JSON.stringify({
39
- proposal
39
+ proposal,
40
+ prompt
40
41
  }),
41
42
  headers: {
42
43
  "Content-Type": "application/json"