@mvriu5/payload-ai 1.3.2 → 1.4.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/README.md +76 -13
- package/dist/ai/providerOptions.d.ts +24 -1
- package/dist/ai/providerOptions.js +81 -0
- package/dist/ai/providerRuntime.d.ts +2 -1
- package/dist/ai/providerRuntime.js +5 -2
- package/dist/ai/tokenUsage.d.ts +38 -0
- package/dist/ai/tokenUsage.js +106 -0
- package/dist/components/Icons.d.ts +1 -0
- package/dist/components/Icons.js +15 -0
- package/dist/components/action-toast/ActionToast.d.ts +5 -1
- package/dist/components/action-toast/ActionToast.js +57 -21
- package/dist/components/ai-input/AIInput.d.ts +4 -1
- package/dist/components/ai-input/AIInput.js +165 -50
- package/dist/components/ai-input/AIInput.module.css +40 -2
- package/dist/components/audit-log-list/AuditLogList.js +9 -2
- package/dist/components/dashboard/Dashboard.js +3 -1
- package/dist/components/hooks/useAIChatStream.d.ts +12 -1
- package/dist/components/hooks/useAIChatStream.js +13 -4
- package/dist/components/hooks/useAISettings.d.ts +6 -4
- package/dist/components/hooks/useAISettings.js +62 -22
- package/dist/components/hooks/usePluginConfig.d.ts +8 -20
- package/dist/components/hooks/usePluginConfig.js +9 -2
- package/dist/components/text-shimmer/TextShimmer.d.ts +9 -0
- package/dist/components/text-shimmer/TextShimmer.js +34 -0
- package/dist/components/text-shimmer/TextShimmer.module.css +19 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +2 -0
- package/dist/handlers/chatHandler.d.ts +4 -1
- package/dist/handlers/chatHandler.js +198 -17
- package/dist/index.d.ts +6 -1
- package/dist/index.js +92 -5
- package/package.json +18 -16
|
@@ -4,7 +4,28 @@ import { formatAdminURL } from "payload/shared";
|
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import { redactSensitiveData } from "../../ai/sensitiveData.js";
|
|
6
6
|
import { DiffDialog } from "../diff-dialog/DiffDialog.js";
|
|
7
|
+
import { TextShimmer } from "../text-shimmer/TextShimmer.js";
|
|
7
8
|
import styles from "./ActionToast.module.css";
|
|
9
|
+
const ACTION_TOAST_TEXT = {
|
|
10
|
+
aiRequestFailed: "AI request failed",
|
|
11
|
+
aiResponse: "AI response",
|
|
12
|
+
applyProposal: "Apply proposal",
|
|
13
|
+
details: "Details",
|
|
14
|
+
diffReviewFailed: "Diff review failed",
|
|
15
|
+
dismiss: "Dismiss",
|
|
16
|
+
dismissProposals: "Dismiss proposals",
|
|
17
|
+
fullResponse: "Full response",
|
|
18
|
+
goToSource: "Go to source",
|
|
19
|
+
loading: "Loading",
|
|
20
|
+
proposalDiffError: "Could not load proposal diff.",
|
|
21
|
+
redacted: "[redacted]",
|
|
22
|
+
review: "Review",
|
|
23
|
+
reviewProposal: "Review proposal",
|
|
24
|
+
waitingForResponse: "Please wait, until the Response is received"
|
|
25
|
+
};
|
|
26
|
+
const PROPOSAL_DIFF_ENDPOINT = "/ai-proposal-diff";
|
|
27
|
+
const JSON_CONTENT_TYPE = "application/json";
|
|
28
|
+
const POST_METHOD = "POST";
|
|
8
29
|
const maxDescriptionLength = 220;
|
|
9
30
|
const getDescriptionPreview = (description)=>{
|
|
10
31
|
if (description.length <= maxDescriptionLength) return description;
|
|
@@ -15,16 +36,16 @@ const getSafeProposalDetails = (proposal)=>{
|
|
|
15
36
|
if (redactedProposal._aiSignature) {
|
|
16
37
|
redactedProposal._aiSignature = {
|
|
17
38
|
expiresAt: redactedProposal._aiSignature.expiresAt,
|
|
18
|
-
value:
|
|
39
|
+
value: ACTION_TOAST_TEXT.redacted
|
|
19
40
|
};
|
|
20
41
|
}
|
|
21
42
|
return redactedProposal;
|
|
22
43
|
};
|
|
23
|
-
export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, prompt, proposals, tokenUsage })=>{
|
|
44
|
+
export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplying, isLoading, onDismiss, onDismissError, onApply, prompt, proposals, tokenUsage })=>{
|
|
24
45
|
const [activeDiff, setActiveDiff] = useState(null);
|
|
25
46
|
const [diffError, setDiffError] = useState("");
|
|
26
47
|
const [loadingDiffIndex, setLoadingDiffIndex] = useState(null);
|
|
27
|
-
if (proposals.length === 0 && !error && !description) return null;
|
|
48
|
+
if (proposals.length === 0 && !error && !description && !isLoading) return null;
|
|
28
49
|
const descriptionPreview = description ? getDescriptionPreview(description) : "";
|
|
29
50
|
const isDescriptionTruncated = Boolean(description) && descriptionPreview !== description;
|
|
30
51
|
const openDiff = async (proposal, index)=>{
|
|
@@ -33,20 +54,20 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
33
54
|
try {
|
|
34
55
|
const res = await fetch(formatAdminURL({
|
|
35
56
|
apiRoute,
|
|
36
|
-
path:
|
|
57
|
+
path: PROPOSAL_DIFF_ENDPOINT
|
|
37
58
|
}), {
|
|
38
59
|
body: JSON.stringify({
|
|
39
60
|
proposal,
|
|
40
61
|
prompt
|
|
41
62
|
}),
|
|
42
63
|
headers: {
|
|
43
|
-
"Content-Type":
|
|
64
|
+
"Content-Type": JSON_CONTENT_TYPE
|
|
44
65
|
},
|
|
45
|
-
method:
|
|
66
|
+
method: POST_METHOD
|
|
46
67
|
});
|
|
47
68
|
const result = await res.json().catch(()=>null);
|
|
48
69
|
if (!res.ok || !result) {
|
|
49
|
-
throw new Error(result?.error ||
|
|
70
|
+
throw new Error(result?.error || ACTION_TOAST_TEXT.proposalDiffError);
|
|
50
71
|
}
|
|
51
72
|
setActiveDiff({
|
|
52
73
|
change: null,
|
|
@@ -57,7 +78,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
57
78
|
proposal
|
|
58
79
|
});
|
|
59
80
|
} catch (err) {
|
|
60
|
-
setDiffError(err instanceof Error ? err.message :
|
|
81
|
+
setDiffError(err instanceof Error ? err.message : ACTION_TOAST_TEXT.proposalDiffError);
|
|
61
82
|
} finally{
|
|
62
83
|
setLoadingDiffIndex(null);
|
|
63
84
|
}
|
|
@@ -65,6 +86,21 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
65
86
|
return /*#__PURE__*/ _jsxs("div", {
|
|
66
87
|
className: styles.list,
|
|
67
88
|
children: [
|
|
89
|
+
proposals.length === 0 && isLoading && /*#__PURE__*/ _jsxs("div", {
|
|
90
|
+
className: styles.item,
|
|
91
|
+
children: [
|
|
92
|
+
/*#__PURE__*/ _jsx("div", {
|
|
93
|
+
className: styles.label,
|
|
94
|
+
children: ACTION_TOAST_TEXT.aiResponse
|
|
95
|
+
}),
|
|
96
|
+
/*#__PURE__*/ _jsx("div", {
|
|
97
|
+
className: styles.description,
|
|
98
|
+
children: /*#__PURE__*/ _jsx(TextShimmer, {
|
|
99
|
+
children: ACTION_TOAST_TEXT.waitingForResponse
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
}),
|
|
68
104
|
error && /*#__PURE__*/ _jsxs("div", {
|
|
69
105
|
className: `${styles.item} ${styles.errorItem}`,
|
|
70
106
|
children: [
|
|
@@ -72,7 +108,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
72
108
|
children: [
|
|
73
109
|
/*#__PURE__*/ _jsx("div", {
|
|
74
110
|
className: styles.label,
|
|
75
|
-
children:
|
|
111
|
+
children: ACTION_TOAST_TEXT.aiRequestFailed
|
|
76
112
|
}),
|
|
77
113
|
/*#__PURE__*/ _jsx("div", {
|
|
78
114
|
className: styles.description,
|
|
@@ -84,7 +120,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
84
120
|
className: styles.button,
|
|
85
121
|
onClick: onDismissError,
|
|
86
122
|
type: "button",
|
|
87
|
-
children:
|
|
123
|
+
children: ACTION_TOAST_TEXT.dismiss
|
|
88
124
|
})
|
|
89
125
|
]
|
|
90
126
|
}),
|
|
@@ -94,7 +130,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
94
130
|
children: [
|
|
95
131
|
/*#__PURE__*/ _jsx("div", {
|
|
96
132
|
className: styles.label,
|
|
97
|
-
children:
|
|
133
|
+
children: ACTION_TOAST_TEXT.aiResponse
|
|
98
134
|
}),
|
|
99
135
|
/*#__PURE__*/ _jsx("div", {
|
|
100
136
|
className: styles.description,
|
|
@@ -105,7 +141,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
105
141
|
children: [
|
|
106
142
|
/*#__PURE__*/ _jsx("summary", {
|
|
107
143
|
className: styles.summary,
|
|
108
|
-
children:
|
|
144
|
+
children: ACTION_TOAST_TEXT.fullResponse
|
|
109
145
|
}),
|
|
110
146
|
/*#__PURE__*/ _jsx("pre", {
|
|
111
147
|
className: styles.proposalDetails,
|
|
@@ -146,7 +182,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
146
182
|
children: [
|
|
147
183
|
/*#__PURE__*/ _jsx("summary", {
|
|
148
184
|
className: styles.summary,
|
|
149
|
-
children:
|
|
185
|
+
children: ACTION_TOAST_TEXT.fullResponse
|
|
150
186
|
}),
|
|
151
187
|
/*#__PURE__*/ _jsx("pre", {
|
|
152
188
|
className: styles.proposalDetails,
|
|
@@ -159,7 +195,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
159
195
|
children: [
|
|
160
196
|
/*#__PURE__*/ _jsx("summary", {
|
|
161
197
|
className: styles.summary,
|
|
162
|
-
children:
|
|
198
|
+
children: ACTION_TOAST_TEXT.details
|
|
163
199
|
}),
|
|
164
200
|
/*#__PURE__*/ _jsx("pre", {
|
|
165
201
|
className: styles.proposalDetails,
|
|
@@ -176,14 +212,14 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
176
212
|
className: styles.viewAction,
|
|
177
213
|
children: [
|
|
178
214
|
/*#__PURE__*/ _jsxs(Button, {
|
|
179
|
-
"aria-label":
|
|
215
|
+
"aria-label": `${ACTION_TOAST_TEXT.reviewProposal}: ${proposal.label}`,
|
|
180
216
|
margin: false,
|
|
181
217
|
buttonStyle: "subtle",
|
|
182
218
|
disabled: loadingDiffIndex === index,
|
|
183
219
|
onClick: ()=>void openDiff(proposal, index),
|
|
184
220
|
children: [
|
|
185
221
|
/*#__PURE__*/ _jsx(SwapIcon, {}),
|
|
186
|
-
loadingDiffIndex === index ?
|
|
222
|
+
loadingDiffIndex === index ? ACTION_TOAST_TEXT.loading : ACTION_TOAST_TEXT.review
|
|
187
223
|
]
|
|
188
224
|
}),
|
|
189
225
|
viewURL && /*#__PURE__*/ _jsx(Button, {
|
|
@@ -192,7 +228,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
192
228
|
url: viewURL,
|
|
193
229
|
newTab: true,
|
|
194
230
|
margin: false,
|
|
195
|
-
children:
|
|
231
|
+
children: ACTION_TOAST_TEXT.goToSource
|
|
196
232
|
})
|
|
197
233
|
]
|
|
198
234
|
}),
|
|
@@ -201,13 +237,13 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
201
237
|
children: [
|
|
202
238
|
onDismiss && /*#__PURE__*/ _jsx(Button, {
|
|
203
239
|
icon: "x",
|
|
204
|
-
"aria-label":
|
|
240
|
+
"aria-label": ACTION_TOAST_TEXT.dismissProposals,
|
|
205
241
|
buttonStyle: "subtle",
|
|
206
242
|
margin: false,
|
|
207
243
|
onClick: onDismiss
|
|
208
244
|
}),
|
|
209
245
|
/*#__PURE__*/ _jsx(Button, {
|
|
210
|
-
"aria-label":
|
|
246
|
+
"aria-label": `${ACTION_TOAST_TEXT.applyProposal}: ${proposal.label}`,
|
|
211
247
|
margin: false,
|
|
212
248
|
buttonStyle: "primary",
|
|
213
249
|
disabled: isApplying,
|
|
@@ -228,7 +264,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
228
264
|
children: [
|
|
229
265
|
/*#__PURE__*/ _jsx("div", {
|
|
230
266
|
className: styles.label,
|
|
231
|
-
children:
|
|
267
|
+
children: ACTION_TOAST_TEXT.diffReviewFailed
|
|
232
268
|
}),
|
|
233
269
|
/*#__PURE__*/ _jsx("div", {
|
|
234
270
|
className: styles.description,
|
|
@@ -240,7 +276,7 @@ export const ActionToast = ({ apiRoute, description, error, getViewURL, isApplyi
|
|
|
240
276
|
className: styles.button,
|
|
241
277
|
onClick: ()=>setDiffError(""),
|
|
242
278
|
type: "button",
|
|
243
|
-
children:
|
|
279
|
+
children: ACTION_TOAST_TEXT.dismiss
|
|
244
280
|
})
|
|
245
281
|
]
|
|
246
282
|
}),
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Button,
|
|
3
|
+
import { Button, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
|
|
4
4
|
import { formatAdminURL } from "payload/shared";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { mergeData } from "../../payload/shared.js";
|
|
6
7
|
import { ActionToast } from "../action-toast/ActionToast.js";
|
|
7
8
|
import { useAIChatStream } from "../hooks/useAIChatStream.js";
|
|
8
9
|
import { useAISettings } from "../hooks/useAISettings.js";
|
|
9
10
|
import { useAuditLog } from "../hooks/useAuditLog.js";
|
|
10
11
|
import { getTextBeforeCaret, useMentions } from "../hooks/useMentions.js";
|
|
11
12
|
import { usePluginConfig } from "../hooks/usePluginConfig.js";
|
|
12
|
-
import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, OpenrouterIcon } from "../Icons.js";
|
|
13
|
+
import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, OpenrouterIcon, PaperclipIcon } from "../Icons.js";
|
|
13
14
|
import { MentionPopover } from "../mention-popover/MentionPopover.js";
|
|
14
15
|
import styles from "./AIInput.module.css";
|
|
15
16
|
const getProviderIcon = (provider)=>{
|
|
@@ -43,7 +44,32 @@ const getProviderIcon = (provider)=>{
|
|
|
43
44
|
}
|
|
44
45
|
};
|
|
45
46
|
const getFileSignature = (file)=>`${file.name}:${file.size}:${file.lastModified}:${file.type}`;
|
|
46
|
-
const
|
|
47
|
+
const getModelSelectionValue = (provider, model)=>JSON.stringify([
|
|
48
|
+
provider,
|
|
49
|
+
model
|
|
50
|
+
]);
|
|
51
|
+
const fallbackConfig = {
|
|
52
|
+
admin: {},
|
|
53
|
+
collections: [],
|
|
54
|
+
globals: [],
|
|
55
|
+
routes: {
|
|
56
|
+
admin: "/admin",
|
|
57
|
+
api: "/api"
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const parseModelSelectionValue = (value)=>{
|
|
61
|
+
try {
|
|
62
|
+
const selection = JSON.parse(value);
|
|
63
|
+
if (!Array.isArray(selection) || selection.length !== 2 || selection.some((part)=>typeof part !== "string")) return null;
|
|
64
|
+
return {
|
|
65
|
+
model: selection[1],
|
|
66
|
+
provider: selection[0]
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const AIInputCore = ({ applyProposalLocally, documentScope, isDashboard })=>{
|
|
47
73
|
const editorRef = useRef(null);
|
|
48
74
|
const fileInputRef = useRef(null);
|
|
49
75
|
const [prompt, setPrompt] = useState("");
|
|
@@ -54,19 +80,24 @@ const AIInput = ()=>{
|
|
|
54
80
|
const selectedFilesRef = useRef([]);
|
|
55
81
|
const mediaAttachmentsRef = useRef([]);
|
|
56
82
|
const uploadedFileSignaturesRef = useRef(new Set());
|
|
57
|
-
const
|
|
58
|
-
const
|
|
83
|
+
const payloadConfigContext = useConfig();
|
|
84
|
+
const config = payloadConfigContext?.config ?? fallbackConfig;
|
|
85
|
+
const hasPayloadConfigContext = Boolean(payloadConfigContext?.config);
|
|
86
|
+
const { isCollectionMentionEnabled, locales, defaultLocale, managedProviders, media, providerProfiles } = usePluginConfig(config);
|
|
59
87
|
const acceptedMimeTypes = media?.acceptedMimeTypes?.join(",");
|
|
60
88
|
const mediaEnabled = Boolean(media?.enabled);
|
|
61
89
|
const { loadRecentChanges, prependChange } = useAuditLog({
|
|
62
90
|
adminRoute: config.routes.admin,
|
|
63
91
|
apiRoute: config.routes.api
|
|
64
92
|
});
|
|
65
|
-
const { selectedModel,
|
|
93
|
+
const { selectedModel, setSelectedProviderModel, settingsProvider } = useAISettings({
|
|
66
94
|
adminUserSlug: config.admin?.user,
|
|
67
95
|
apiRoute: config.routes.api,
|
|
68
|
-
|
|
96
|
+
managedProviders,
|
|
97
|
+
providerProfiles
|
|
69
98
|
});
|
|
99
|
+
const selectedProviderProfile = providerProfiles.find((profile)=>profile.id === settingsProvider);
|
|
100
|
+
const selectableProviderProfiles = managedProviders ? providerProfiles : providerProfiles.filter((profile)=>profile.id === settingsProvider);
|
|
70
101
|
const { clearMentions, insertMention, mentionPopoverPosition, mentionRange, mentionSuggestions, mentionsRef, updateMentionState } = useMentions({
|
|
71
102
|
apiRoute: config.routes.api,
|
|
72
103
|
config,
|
|
@@ -118,12 +149,14 @@ const AIInput = ()=>{
|
|
|
118
149
|
const removeMediaAttachment = (attachmentIndex)=>{
|
|
119
150
|
updateMediaAttachments((currentAttachments)=>currentAttachments.filter((_, index)=>index !== attachmentIndex));
|
|
120
151
|
};
|
|
121
|
-
const { dismissChat, error, isLoading, proposals, resetChatState, response, setError, setProposals, setResponse, submit, tokenUsage } = useAIChatStream({
|
|
152
|
+
const { dismissChat, error, isLoading, setIsLoading, proposals, resetChatState, response, setError, setProposals, setResponse, submit, tokenUsage } = useAIChatStream({
|
|
122
153
|
apiRoute: config.routes.api,
|
|
123
154
|
clearInput,
|
|
155
|
+
documentScope,
|
|
124
156
|
mentionsRef,
|
|
125
157
|
prompt,
|
|
126
|
-
selectedModel
|
|
158
|
+
selectedModel,
|
|
159
|
+
selectedProvider: settingsProvider
|
|
127
160
|
});
|
|
128
161
|
const uploadSelectedFiles = async ()=>{
|
|
129
162
|
const filesToUpload = selectedFilesRef.current;
|
|
@@ -153,6 +186,7 @@ const AIInput = ()=>{
|
|
|
153
186
|
};
|
|
154
187
|
const handleSubmit = async ()=>{
|
|
155
188
|
if (isUploadingMedia) return;
|
|
189
|
+
setIsLoading(true);
|
|
156
190
|
setError("");
|
|
157
191
|
setIsUploadingMedia(true);
|
|
158
192
|
try {
|
|
@@ -201,6 +235,12 @@ const AIInput = ()=>{
|
|
|
201
235
|
setIsApplying(true);
|
|
202
236
|
setError("");
|
|
203
237
|
try {
|
|
238
|
+
if (applyProposalLocally) {
|
|
239
|
+
await applyProposalLocally(proposal);
|
|
240
|
+
resetChatState();
|
|
241
|
+
clearInput();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
204
244
|
const res = await fetch(formatAdminURL({
|
|
205
245
|
apiRoute: config.routes.api,
|
|
206
246
|
path: "/ai-apply-action"
|
|
@@ -235,8 +275,33 @@ const AIInput = ()=>{
|
|
|
235
275
|
setIsApplying(false);
|
|
236
276
|
}
|
|
237
277
|
};
|
|
278
|
+
if (!hasPayloadConfigContext) {
|
|
279
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
280
|
+
className: styles.chatLayout,
|
|
281
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
282
|
+
className: styles.chat,
|
|
283
|
+
children: [
|
|
284
|
+
/*#__PURE__*/ _jsx("div", {
|
|
285
|
+
className: styles.chatHeader,
|
|
286
|
+
children: /*#__PURE__*/ _jsx("h2", {
|
|
287
|
+
className: styles.chatTitle,
|
|
288
|
+
children: "AI Assistant"
|
|
289
|
+
})
|
|
290
|
+
}),
|
|
291
|
+
/*#__PURE__*/ _jsx("div", {
|
|
292
|
+
className: styles.chatError,
|
|
293
|
+
children: "Payload config context is unavailable. Make sure the app and this plugin resolve the same @payloadcms/ui version."
|
|
294
|
+
})
|
|
295
|
+
]
|
|
296
|
+
})
|
|
297
|
+
});
|
|
298
|
+
}
|
|
238
299
|
return /*#__PURE__*/ _jsx("div", {
|
|
239
300
|
className: styles.chatLayout,
|
|
301
|
+
style: {
|
|
302
|
+
marginBottom: isDashboard ? "0" : "20px",
|
|
303
|
+
height: isDashboard ? "332px" : "220px"
|
|
304
|
+
},
|
|
240
305
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
241
306
|
className: styles.chat,
|
|
242
307
|
children: [
|
|
@@ -325,10 +390,37 @@ const AIInput = ()=>{
|
|
|
325
390
|
]
|
|
326
391
|
}, `${attachment.collection}-${attachment.id}`))
|
|
327
392
|
]
|
|
393
|
+
}),
|
|
394
|
+
mediaEnabled && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
395
|
+
children: [
|
|
396
|
+
/*#__PURE__*/ _jsx("input", {
|
|
397
|
+
ref: fileInputRef,
|
|
398
|
+
type: "file",
|
|
399
|
+
accept: acceptedMimeTypes,
|
|
400
|
+
className: styles.fileInput,
|
|
401
|
+
multiple: true,
|
|
402
|
+
onChange: handleSelectFile
|
|
403
|
+
}),
|
|
404
|
+
/*#__PURE__*/ _jsx("button", {
|
|
405
|
+
type: "button",
|
|
406
|
+
className: styles.mediaButton,
|
|
407
|
+
"aria-label": "Attach media",
|
|
408
|
+
title: "Attach media",
|
|
409
|
+
disabled: isLoading || isUploadingMedia || proposals.length > 0,
|
|
410
|
+
onClick: (event)=>{
|
|
411
|
+
event.stopPropagation();
|
|
412
|
+
fileInputRef.current?.click();
|
|
413
|
+
},
|
|
414
|
+
children: /*#__PURE__*/ _jsx(PaperclipIcon, {
|
|
415
|
+
width: 16,
|
|
416
|
+
height: 16
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
]
|
|
328
420
|
})
|
|
329
421
|
]
|
|
330
422
|
}),
|
|
331
|
-
mentionRange && /*#__PURE__*/ _jsx(MentionPopover, {
|
|
423
|
+
mentionRange && isDashboard && /*#__PURE__*/ _jsx(MentionPopover, {
|
|
332
424
|
onSelect: insertMention,
|
|
333
425
|
style: mentionPopoverPosition ? {
|
|
334
426
|
left: `${mentionPopoverPosition.left}px`,
|
|
@@ -353,24 +445,30 @@ const AIInput = ()=>{
|
|
|
353
445
|
/*#__PURE__*/ _jsxs("div", {
|
|
354
446
|
className: styles.selectWrapper,
|
|
355
447
|
children: [
|
|
356
|
-
|
|
448
|
+
selectedProviderProfile && getProviderIcon(selectedProviderProfile.provider),
|
|
357
449
|
/*#__PURE__*/ _jsxs("select", {
|
|
358
450
|
className: styles.select,
|
|
359
451
|
style: {
|
|
360
|
-
paddingLeft:
|
|
452
|
+
paddingLeft: selectedProviderProfile ? "34px" : "12px"
|
|
361
453
|
},
|
|
362
454
|
disabled: !settingsProvider,
|
|
363
|
-
onChange: (event)=>
|
|
364
|
-
|
|
455
|
+
onChange: (event)=>{
|
|
456
|
+
const selection = parseModelSelectionValue(event.target.value);
|
|
457
|
+
if (selection) setSelectedProviderModel(selection.provider, selection.model);
|
|
458
|
+
},
|
|
459
|
+
value: settingsProvider && selectedModel ? getModelSelectionValue(settingsProvider, selectedModel) : "",
|
|
365
460
|
children: [
|
|
366
461
|
!settingsProvider && /*#__PURE__*/ _jsx("option", {
|
|
367
462
|
value: "",
|
|
368
463
|
children: "No provider selected"
|
|
369
464
|
}),
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
children: model
|
|
373
|
-
|
|
465
|
+
selectableProviderProfiles.map((profile)=>/*#__PURE__*/ _jsx("optgroup", {
|
|
466
|
+
label: profile.label,
|
|
467
|
+
children: profile.models.map((model)=>/*#__PURE__*/ _jsx("option", {
|
|
468
|
+
value: getModelSelectionValue(profile.id, model.value),
|
|
469
|
+
children: model.label
|
|
470
|
+
}, `${profile.id}-${model.value}`))
|
|
471
|
+
}, profile.id))
|
|
374
472
|
]
|
|
375
473
|
})
|
|
376
474
|
]
|
|
@@ -378,47 +476,26 @@ const AIInput = ()=>{
|
|
|
378
476
|
]
|
|
379
477
|
})
|
|
380
478
|
}),
|
|
381
|
-
/*#__PURE__*/
|
|
479
|
+
/*#__PURE__*/ _jsx("div", {
|
|
382
480
|
className: styles.actions,
|
|
383
|
-
children:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
multiple: true,
|
|
392
|
-
onChange: handleSelectFile
|
|
393
|
-
}),
|
|
394
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
395
|
-
buttonStyle: "tab",
|
|
396
|
-
"aria-label": "Attach media",
|
|
397
|
-
margin: false,
|
|
398
|
-
disabled: isLoading || isUploadingMedia || proposals.length > 0,
|
|
399
|
-
onClick: ()=>fileInputRef.current?.click(),
|
|
400
|
-
children: /*#__PURE__*/ _jsx(PlusIcon, {})
|
|
401
|
-
})
|
|
402
|
-
]
|
|
403
|
-
}),
|
|
404
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
405
|
-
buttonStyle: "primary",
|
|
406
|
-
"aria-label": "Send",
|
|
407
|
-
margin: false,
|
|
408
|
-
disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading || isUploadingMedia || Boolean(error) || Boolean(response) || proposals.length > 0,
|
|
409
|
-
onClick: ()=>void handleSubmit(),
|
|
410
|
-
children: isUploadingMedia || isLoading ? "Sending..." : "Send"
|
|
411
|
-
})
|
|
412
|
-
]
|
|
481
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
482
|
+
buttonStyle: "primary",
|
|
483
|
+
"aria-label": "Send",
|
|
484
|
+
margin: false,
|
|
485
|
+
disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading || isUploadingMedia || Boolean(error) || Boolean(response) || proposals.length > 0,
|
|
486
|
+
onClick: ()=>void handleSubmit(),
|
|
487
|
+
children: isUploadingMedia || isLoading ? "Sending..." : "Send"
|
|
488
|
+
})
|
|
413
489
|
})
|
|
414
490
|
]
|
|
415
491
|
}),
|
|
416
|
-
(proposals.length > 0 || response) && /*#__PURE__*/ _jsx(ActionToast, {
|
|
492
|
+
(proposals.length > 0 || response || isLoading) && /*#__PURE__*/ _jsx(ActionToast, {
|
|
417
493
|
apiRoute: config.routes.api,
|
|
418
494
|
description: response,
|
|
419
495
|
error: error,
|
|
420
496
|
getViewURL: getProposalViewURL,
|
|
421
497
|
isApplying: isApplying,
|
|
498
|
+
isLoading: isLoading,
|
|
422
499
|
onDismiss: ()=>dismissChat(),
|
|
423
500
|
onDismissError: ()=>setError(""),
|
|
424
501
|
onApply: (proposal, _index)=>void handleApplyProposal(proposal),
|
|
@@ -430,4 +507,42 @@ const AIInput = ()=>{
|
|
|
430
507
|
})
|
|
431
508
|
});
|
|
432
509
|
};
|
|
510
|
+
const DocumentAIInput = ()=>{
|
|
511
|
+
const documentInfo = useDocumentInfo();
|
|
512
|
+
const documentForm = useDocumentForm();
|
|
513
|
+
const locale = useLocale();
|
|
514
|
+
const documentScope = documentInfo.collectionSlug ? {
|
|
515
|
+
collection: documentInfo.collectionSlug,
|
|
516
|
+
...documentInfo.id === undefined ? {} : {
|
|
517
|
+
id: String(documentInfo.id)
|
|
518
|
+
},
|
|
519
|
+
type: "collection"
|
|
520
|
+
} : documentInfo.globalSlug ? {
|
|
521
|
+
slug: documentInfo.globalSlug,
|
|
522
|
+
type: "global"
|
|
523
|
+
} : undefined;
|
|
524
|
+
const applyProposalLocally = async (proposal)=>{
|
|
525
|
+
if (proposal.action === "delete") {
|
|
526
|
+
throw new Error("Delete proposals cannot be applied without saving.");
|
|
527
|
+
}
|
|
528
|
+
const proposalData = proposal.localizedData ? proposal.localizedData[proposal.locale || locale.code] : proposal.data;
|
|
529
|
+
if (!proposalData) {
|
|
530
|
+
throw new Error(`This proposal has no changes for the active locale "${locale.code}".`);
|
|
531
|
+
}
|
|
532
|
+
const currentData = documentForm.getData();
|
|
533
|
+
await documentForm.reset(mergeData(currentData, proposalData));
|
|
534
|
+
documentForm.setModified(true);
|
|
535
|
+
};
|
|
536
|
+
return /*#__PURE__*/ _jsx(AIInputCore, {
|
|
537
|
+
applyProposalLocally: applyProposalLocally,
|
|
538
|
+
documentScope: documentScope,
|
|
539
|
+
isDashboard: false
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
const AIInput = ({ isDashboard = false })=>{
|
|
543
|
+
if (isDashboard) return /*#__PURE__*/ _jsx(AIInputCore, {
|
|
544
|
+
isDashboard: true
|
|
545
|
+
});
|
|
546
|
+
return /*#__PURE__*/ _jsx(DocumentAIInput, {});
|
|
547
|
+
};
|
|
433
548
|
export default AIInput;
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
max-width: none;
|
|
4
4
|
display: flex;
|
|
5
5
|
flex-direction: column;
|
|
6
|
-
height: 332px;
|
|
7
6
|
overflow: hidden;
|
|
8
7
|
}
|
|
9
8
|
|
|
@@ -108,9 +107,47 @@
|
|
|
108
107
|
display: none;
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
.mediaButton {
|
|
111
|
+
align-items: center;
|
|
112
|
+
background: transparent;
|
|
113
|
+
border: 0;
|
|
114
|
+
bottom: 8px;
|
|
115
|
+
color: var(--theme-elevation-600);
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
height: 28px;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
padding: 0;
|
|
121
|
+
position: absolute;
|
|
122
|
+
right: 8px;
|
|
123
|
+
width: 28px;
|
|
124
|
+
z-index: 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.mediaButton:hover,
|
|
128
|
+
.mediaButton:focus-visible {
|
|
129
|
+
color: var(--theme-text);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.mediaButton:focus-visible {
|
|
133
|
+
outline: 1px solid currentColor;
|
|
134
|
+
outline-offset: 1px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.mediaButton:disabled {
|
|
138
|
+
cursor: not-allowed;
|
|
139
|
+
opacity: 0.45;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.mediaButton svg {
|
|
143
|
+
height: 16px;
|
|
144
|
+
width: 16px;
|
|
145
|
+
}
|
|
146
|
+
|
|
111
147
|
.chatInput {
|
|
112
148
|
background: transparent;
|
|
113
149
|
border: 0;
|
|
150
|
+
box-sizing: border-box;
|
|
114
151
|
color: var(--theme-text);
|
|
115
152
|
font: inherit;
|
|
116
153
|
flex: 1 1 0;
|
|
@@ -118,7 +155,7 @@
|
|
|
118
155
|
min-height: 0;
|
|
119
156
|
overflow-y: auto;
|
|
120
157
|
overscroll-behavior: contain;
|
|
121
|
-
padding: 0;
|
|
158
|
+
padding: 0 32px 32px 0;
|
|
122
159
|
white-space: pre-wrap;
|
|
123
160
|
word-break: break-word;
|
|
124
161
|
width: 100%;
|
|
@@ -161,6 +198,7 @@
|
|
|
161
198
|
flex-wrap: wrap;
|
|
162
199
|
gap: 6px;
|
|
163
200
|
min-height: 22px;
|
|
201
|
+
padding-right: 32px;
|
|
164
202
|
}
|
|
165
203
|
|
|
166
204
|
.attachmentPill {
|
|
@@ -12,8 +12,15 @@ const getChangeProposal = (change)=>({
|
|
|
12
12
|
label: change.title,
|
|
13
13
|
slug: change.slug || undefined
|
|
14
14
|
});
|
|
15
|
+
const fallbackConfig = {
|
|
16
|
+
routes: {
|
|
17
|
+
admin: "/admin",
|
|
18
|
+
api: "/api"
|
|
19
|
+
}
|
|
20
|
+
};
|
|
15
21
|
const AuditLogList = ()=>{
|
|
16
|
-
const
|
|
22
|
+
const payloadConfigContext = useConfig();
|
|
23
|
+
const config = payloadConfigContext?.config ?? fallbackConfig;
|
|
17
24
|
const { loadRecentChanges, allChangesURL, appliedChanges: changes } = useAuditLog({
|
|
18
25
|
adminRoute: config.routes.admin,
|
|
19
26
|
apiRoute: config.routes.api
|
|
@@ -43,7 +50,7 @@ const AuditLogList = ()=>{
|
|
|
43
50
|
allChangesURL && /*#__PURE__*/ _jsx(Button, {
|
|
44
51
|
url: allChangesURL,
|
|
45
52
|
el: "anchor",
|
|
46
|
-
"aria-
|
|
53
|
+
"aria-label": "View all",
|
|
47
54
|
margin: false,
|
|
48
55
|
buttonStyle: "tab",
|
|
49
56
|
size: "small",
|