@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.
- package/dist/components/AIInput.js +155 -6
- package/dist/components/AIInput.module.css +11 -2
- package/dist/components/ActionToast.d.ts +2 -1
- package/dist/components/ActionToast.js +3 -2
- package/dist/handlers/applyActionHandler.js +316 -107
- package/dist/handlers/chatHandler.js +660 -48
- package/dist/handlers/proposalDiffHandler.js +81 -26
- package/dist/payload/logging.d.ts +9 -0
- package/dist/payload/logging.js +14 -0
- package/dist/payload/normalizeData.d.ts +17 -7
- package/dist/payload/normalizeData.js +22 -1
- package/dist/payload/proposalData.d.ts +31 -0
- package/dist/payload/proposalData.js +575 -0
- package/dist/payload/schemaContext.d.ts +3 -7
- package/dist/payload/schemaContext.js +49 -23
- package/package.json +1 -1
|
@@ -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
|
-
},
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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"
|