@mvriu5/payload-ai 1.2.0 → 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.
Files changed (63) hide show
  1. package/README.md +118 -13
  2. package/dist/ai/providerOptions.d.ts +24 -1
  3. package/dist/ai/providerOptions.js +81 -0
  4. package/dist/ai/providerRuntime.d.ts +2 -1
  5. package/dist/ai/providerRuntime.js +5 -2
  6. package/dist/ai/tokenUsage.d.ts +38 -0
  7. package/dist/ai/tokenUsage.js +106 -0
  8. package/dist/components/Icons.d.ts +1 -8
  9. package/dist/components/Icons.js +10 -207
  10. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +5 -2
  11. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +81 -54
  12. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  13. package/dist/components/ai-input/AIInput.d.ts +5 -0
  14. package/dist/components/ai-input/AIInput.js +548 -0
  15. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +150 -44
  16. package/dist/components/ai-input/badge.d.ts +14 -0
  17. package/dist/components/ai-input/badge.js +85 -0
  18. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  19. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +56 -21
  20. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  21. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  22. package/dist/components/dashboard/Dashboard.js +17 -0
  23. package/dist/components/dashboard/Dashboard.module.css +13 -0
  24. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  25. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  26. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  27. package/dist/components/hooks/useAIChatStream.d.ts +50 -0
  28. package/dist/components/hooks/useAIChatStream.js +226 -0
  29. package/dist/components/hooks/useAISettings.d.ts +6 -4
  30. package/dist/components/hooks/useAISettings.js +68 -27
  31. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  32. package/dist/components/hooks/useAuditLog.js +41 -0
  33. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  34. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  35. package/dist/components/hooks/useMentions.d.ts +58 -0
  36. package/dist/components/hooks/useMentions.js +276 -0
  37. package/dist/components/hooks/usePluginConfig.d.ts +40 -0
  38. package/dist/components/hooks/usePluginConfig.js +27 -0
  39. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  40. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  41. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  42. package/dist/components/text-shimmer/TextShimmer.d.ts +9 -0
  43. package/dist/components/text-shimmer/TextShimmer.js +34 -0
  44. package/dist/components/text-shimmer/TextShimmer.module.css +19 -0
  45. package/dist/exports/client.d.ts +4 -2
  46. package/dist/exports/client.js +4 -2
  47. package/dist/handlers/applyActionHandler.js +27 -7
  48. package/dist/handlers/chatHandler.d.ts +4 -1
  49. package/dist/handlers/chatHandler.js +499 -74
  50. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  51. package/dist/handlers/mediaUploadHandler.js +99 -0
  52. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  53. package/dist/handlers/proposalDiffHandler.js +41 -29
  54. package/dist/index.d.ts +12 -1
  55. package/dist/index.js +131 -12
  56. package/dist/payload/collectionPermissions.js +3 -1
  57. package/dist/payload/normalizeData.d.ts +0 -6
  58. package/dist/payload/normalizeData.js +25 -13
  59. package/dist/payload/proposalData.js +4 -1
  60. package/dist/payload/schemaContext.js +98 -43
  61. package/package.json +22 -20
  62. package/dist/components/AIInput.d.ts +0 -2
  63. package/dist/components/AIInput.js +0 -896
@@ -0,0 +1,548 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Button, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
4
+ import { formatAdminURL } from "payload/shared";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { mergeData } from "../../payload/shared.js";
7
+ import { ActionToast } from "../action-toast/ActionToast.js";
8
+ import { useAIChatStream } from "../hooks/useAIChatStream.js";
9
+ import { useAISettings } from "../hooks/useAISettings.js";
10
+ import { useAuditLog } from "../hooks/useAuditLog.js";
11
+ import { getTextBeforeCaret, useMentions } from "../hooks/useMentions.js";
12
+ import { usePluginConfig } from "../hooks/usePluginConfig.js";
13
+ import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, OpenrouterIcon, PaperclipIcon } from "../Icons.js";
14
+ import { MentionPopover } from "../mention-popover/MentionPopover.js";
15
+ import styles from "./AIInput.module.css";
16
+ const getProviderIcon = (provider)=>{
17
+ const iconProps = {
18
+ "aria-hidden": true,
19
+ className: styles.selectProviderIcon
20
+ };
21
+ switch(provider){
22
+ case "claude":
23
+ return /*#__PURE__*/ _jsx(ClaudeIcon, {
24
+ ...iconProps
25
+ });
26
+ case "google":
27
+ return /*#__PURE__*/ _jsx(GoogleGeminiIcon, {
28
+ ...iconProps
29
+ });
30
+ case "mistral":
31
+ return /*#__PURE__*/ _jsx(MistralAiIcon, {
32
+ ...iconProps
33
+ });
34
+ case "openai":
35
+ return /*#__PURE__*/ _jsx(OpenaiIcon, {
36
+ ...iconProps
37
+ });
38
+ case "openrouter":
39
+ return /*#__PURE__*/ _jsx(OpenrouterIcon, {
40
+ ...iconProps
41
+ });
42
+ default:
43
+ return null;
44
+ }
45
+ };
46
+ const getFileSignature = (file)=>`${file.name}:${file.size}:${file.lastModified}:${file.type}`;
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 })=>{
73
+ const editorRef = useRef(null);
74
+ const fileInputRef = useRef(null);
75
+ const [prompt, setPrompt] = useState("");
76
+ const [isApplying, setIsApplying] = useState(false);
77
+ const [isUploadingMedia, setIsUploadingMedia] = useState(false);
78
+ const [selectedFiles, setSelectedFiles] = useState([]);
79
+ const [mediaAttachments, setMediaAttachments] = useState([]);
80
+ const selectedFilesRef = useRef([]);
81
+ const mediaAttachmentsRef = useRef([]);
82
+ const uploadedFileSignaturesRef = useRef(new Set());
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);
87
+ const acceptedMimeTypes = media?.acceptedMimeTypes?.join(",");
88
+ const mediaEnabled = Boolean(media?.enabled);
89
+ const { loadRecentChanges, prependChange } = useAuditLog({
90
+ adminRoute: config.routes.admin,
91
+ apiRoute: config.routes.api
92
+ });
93
+ const { selectedModel, setSelectedProviderModel, settingsProvider } = useAISettings({
94
+ adminUserSlug: config.admin?.user,
95
+ apiRoute: config.routes.api,
96
+ managedProviders,
97
+ providerProfiles
98
+ });
99
+ const selectedProviderProfile = providerProfiles.find((profile)=>profile.id === settingsProvider);
100
+ const selectableProviderProfiles = managedProviders ? providerProfiles : providerProfiles.filter((profile)=>profile.id === settingsProvider);
101
+ const { clearMentions, insertMention, mentionPopoverPosition, mentionRange, mentionSuggestions, mentionsRef, updateMentionState } = useMentions({
102
+ apiRoute: config.routes.api,
103
+ config,
104
+ defaultLocale,
105
+ editorRef,
106
+ isCollectionMentionEnabled,
107
+ locales,
108
+ setPrompt,
109
+ styles
110
+ });
111
+ const updateSelectedFiles = (updater)=>{
112
+ const nextFiles = typeof updater === "function" ? updater(selectedFilesRef.current) : updater;
113
+ selectedFilesRef.current = nextFiles;
114
+ setSelectedFiles(nextFiles);
115
+ };
116
+ const updateMediaAttachments = (updater)=>{
117
+ const nextAttachments = typeof updater === "function" ? updater(mediaAttachmentsRef.current) : updater;
118
+ mediaAttachmentsRef.current = nextAttachments;
119
+ setMediaAttachments(nextAttachments);
120
+ };
121
+ const clearInput = ()=>{
122
+ setPrompt("");
123
+ updateSelectedFiles([]);
124
+ updateMediaAttachments([]);
125
+ uploadedFileSignaturesRef.current = new Set();
126
+ clearMentions();
127
+ if (editorRef.current) editorRef.current.textContent = "";
128
+ };
129
+ const handleSelectFile = (event)=>{
130
+ const files = Array.from(event.target.files || []);
131
+ updateSelectedFiles((currentFiles)=>{
132
+ const selectedFileSignatures = new Set(currentFiles.map(getFileSignature));
133
+ const nextFiles = [
134
+ ...currentFiles
135
+ ];
136
+ for (const file of files){
137
+ const fileSignature = getFileSignature(file);
138
+ if (selectedFileSignatures.has(fileSignature)) continue;
139
+ selectedFileSignatures.add(fileSignature);
140
+ nextFiles.push(file);
141
+ }
142
+ return nextFiles;
143
+ });
144
+ event.target.value = "";
145
+ };
146
+ const removeSelectedFile = (fileIndex)=>{
147
+ updateSelectedFiles((currentFiles)=>currentFiles.filter((_, index)=>index !== fileIndex));
148
+ };
149
+ const removeMediaAttachment = (attachmentIndex)=>{
150
+ updateMediaAttachments((currentAttachments)=>currentAttachments.filter((_, index)=>index !== attachmentIndex));
151
+ };
152
+ const { dismissChat, error, isLoading, setIsLoading, proposals, resetChatState, response, setError, setProposals, setResponse, submit, tokenUsage } = useAIChatStream({
153
+ apiRoute: config.routes.api,
154
+ clearInput,
155
+ documentScope,
156
+ mentionsRef,
157
+ prompt,
158
+ selectedModel,
159
+ selectedProvider: settingsProvider
160
+ });
161
+ const uploadSelectedFiles = async ()=>{
162
+ const filesToUpload = selectedFilesRef.current;
163
+ if (filesToUpload.length === 0) return [];
164
+ const uploadedAttachments = [];
165
+ for (const file of filesToUpload){
166
+ const fileSignature = getFileSignature(file);
167
+ if (uploadedFileSignaturesRef.current.has(fileSignature)) continue;
168
+ uploadedFileSignaturesRef.current.add(fileSignature);
169
+ const formData = new FormData();
170
+ formData.append("file", file);
171
+ const res = await fetch(formatAdminURL({
172
+ apiRoute: config.routes.api,
173
+ path: "/ai-upload-media"
174
+ }), {
175
+ body: formData,
176
+ method: "POST"
177
+ });
178
+ const result = await res.json().catch(()=>null);
179
+ if (!res.ok || !result?.attachment) {
180
+ uploadedFileSignaturesRef.current.delete(fileSignature);
181
+ throw new Error(result?.error || `Could not upload ${file.name}`);
182
+ }
183
+ uploadedAttachments.push(result.attachment);
184
+ }
185
+ return uploadedAttachments;
186
+ };
187
+ const handleSubmit = async ()=>{
188
+ if (isUploadingMedia) return;
189
+ setIsLoading(true);
190
+ setError("");
191
+ setIsUploadingMedia(true);
192
+ try {
193
+ const uploadedAttachments = await uploadSelectedFiles();
194
+ const nextAttachments = [
195
+ ...mediaAttachmentsRef.current,
196
+ ...uploadedAttachments
197
+ ];
198
+ if (uploadedAttachments.length > 0) {
199
+ updateMediaAttachments(nextAttachments);
200
+ updateSelectedFiles([]);
201
+ }
202
+ await submit({
203
+ attachments: nextAttachments
204
+ });
205
+ } catch (err) {
206
+ setError(err instanceof Error ? err.message : "Media upload failed");
207
+ } finally{
208
+ setIsUploadingMedia(false);
209
+ }
210
+ };
211
+ useEffect(()=>{
212
+ if (isLoading || error || proposals.length > 0 || !response) return;
213
+ const timeout = window.setTimeout(()=>{
214
+ setResponse("");
215
+ clearInput();
216
+ }, 10000);
217
+ return ()=>window.clearTimeout(timeout);
218
+ }, [
219
+ error,
220
+ isLoading,
221
+ proposals.length,
222
+ response
223
+ ]);
224
+ const getProposalViewURL = (proposal)=>{
225
+ const adminRoute = config.routes.admin || "/admin";
226
+ if (proposal.action === "updateGlobal" && proposal.slug) {
227
+ return `${adminRoute}/globals/${proposal.slug}`;
228
+ }
229
+ if (proposal.collection && proposal.id) {
230
+ return `${adminRoute}/collections/${proposal.collection}/${proposal.id}`;
231
+ }
232
+ return null;
233
+ };
234
+ const handleApplyProposal = async (proposal)=>{
235
+ setIsApplying(true);
236
+ setError("");
237
+ try {
238
+ if (applyProposalLocally) {
239
+ await applyProposalLocally(proposal);
240
+ resetChatState();
241
+ clearInput();
242
+ return;
243
+ }
244
+ const res = await fetch(formatAdminURL({
245
+ apiRoute: config.routes.api,
246
+ path: "/ai-apply-action"
247
+ }), {
248
+ body: JSON.stringify({
249
+ aiResponse: response,
250
+ prompt,
251
+ proposal,
252
+ tokenUsage
253
+ }),
254
+ headers: {
255
+ "Content-Type": "application/json"
256
+ },
257
+ method: "POST"
258
+ });
259
+ const result = await res.json();
260
+ if (!res.ok) {
261
+ setProposals([]);
262
+ setResponse("");
263
+ throw new Error(result.error || "Could not apply proposal");
264
+ }
265
+ resetChatState();
266
+ if (result.change) {
267
+ prependChange(result.change);
268
+ window.dispatchEvent(new CustomEvent("payload-ai:audit-log-updated"));
269
+ }
270
+ void loadRecentChanges().catch(()=>undefined);
271
+ clearInput();
272
+ } catch (err) {
273
+ setError(err instanceof Error ? err.message : "Could not apply proposal");
274
+ } finally{
275
+ setIsApplying(false);
276
+ }
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
+ }
299
+ return /*#__PURE__*/ _jsx("div", {
300
+ className: styles.chatLayout,
301
+ style: {
302
+ marginBottom: isDashboard ? "0" : "20px",
303
+ height: isDashboard ? "332px" : "220px"
304
+ },
305
+ children: /*#__PURE__*/ _jsxs("div", {
306
+ className: styles.chat,
307
+ children: [
308
+ /*#__PURE__*/ _jsx("div", {
309
+ className: styles.chatHeader,
310
+ children: /*#__PURE__*/ _jsx("h2", {
311
+ className: styles.chatTitle,
312
+ children: "AI Assistant"
313
+ })
314
+ }),
315
+ /*#__PURE__*/ _jsxs("div", {
316
+ className: styles.chatInputRow,
317
+ children: [
318
+ /*#__PURE__*/ _jsxs("div", {
319
+ className: styles.chatInputSurface,
320
+ onClick: ()=>editorRef.current?.focus(),
321
+ children: [
322
+ /*#__PURE__*/ _jsx("div", {
323
+ id: "ai-input",
324
+ className: styles.chatInput,
325
+ ref: editorRef,
326
+ role: "textbox",
327
+ "aria-label": "AIInput",
328
+ "data-placeholder": "Ask AI...",
329
+ tabIndex: 0,
330
+ contentEditable: proposals.length === 0 && !isLoading && Boolean(settingsProvider),
331
+ onInput: (event)=>{
332
+ const value = event.target.innerText;
333
+ setPrompt(value);
334
+ if (!value.trim()) clearMentions();
335
+ updateMentionState(getTextBeforeCaret(event.target));
336
+ },
337
+ onKeyDown: (event)=>{
338
+ if (event.key === "ArrowDown" && mentionRange && mentionSuggestions.length > 0) {
339
+ const firstOption = editorRef.current?.querySelector("button");
340
+ if (firstOption) {
341
+ event.preventDefault();
342
+ firstOption.focus();
343
+ return;
344
+ }
345
+ }
346
+ if (event.key === "Enter" && !event.shiftKey) {
347
+ event.preventDefault();
348
+ void handleSubmit();
349
+ }
350
+ }
351
+ }),
352
+ (selectedFiles.length > 0 || mediaAttachments.length > 0) && /*#__PURE__*/ _jsxs("div", {
353
+ className: styles.attachmentTray,
354
+ "aria-label": "Attached media",
355
+ children: [
356
+ selectedFiles.map((file, index)=>/*#__PURE__*/ _jsxs("span", {
357
+ className: styles.attachmentPill,
358
+ children: [
359
+ /*#__PURE__*/ _jsx("span", {
360
+ className: styles.attachmentName,
361
+ title: file.name,
362
+ children: file.name
363
+ }),
364
+ /*#__PURE__*/ _jsx("button", {
365
+ type: "button",
366
+ className: styles.attachmentRemove,
367
+ disabled: isUploadingMedia,
368
+ onClick: ()=>removeSelectedFile(index),
369
+ "aria-label": `Remove ${file.name}`,
370
+ children: "X"
371
+ })
372
+ ]
373
+ }, `${file.name}-${file.size}-${file.lastModified}-${index}`)),
374
+ mediaAttachments.map((attachment, index)=>/*#__PURE__*/ _jsxs("span", {
375
+ className: styles.attachmentPill,
376
+ children: [
377
+ /*#__PURE__*/ _jsx("span", {
378
+ className: styles.attachmentName,
379
+ title: attachment.filename,
380
+ children: attachment.filename
381
+ }),
382
+ /*#__PURE__*/ _jsx("button", {
383
+ type: "button",
384
+ className: styles.attachmentRemove,
385
+ disabled: isUploadingMedia,
386
+ onClick: ()=>removeMediaAttachment(index),
387
+ "aria-label": `Remove ${attachment.filename}`,
388
+ children: "X"
389
+ })
390
+ ]
391
+ }, `${attachment.collection}-${attachment.id}`))
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
+ ]
420
+ })
421
+ ]
422
+ }),
423
+ mentionRange && isDashboard && /*#__PURE__*/ _jsx(MentionPopover, {
424
+ onSelect: insertMention,
425
+ style: mentionPopoverPosition ? {
426
+ left: `${mentionPopoverPosition.left}px`,
427
+ top: `${mentionPopoverPosition.top}px`
428
+ } : undefined,
429
+ suggestions: mentionSuggestions
430
+ })
431
+ ]
432
+ }),
433
+ /*#__PURE__*/ _jsxs("div", {
434
+ className: styles.chatActionsRow,
435
+ children: [
436
+ /*#__PURE__*/ _jsx("div", {
437
+ className: styles.settings,
438
+ children: /*#__PURE__*/ _jsxs("label", {
439
+ className: styles.setting,
440
+ children: [
441
+ /*#__PURE__*/ _jsx("span", {
442
+ className: styles.settingLabel,
443
+ children: "Model"
444
+ }),
445
+ /*#__PURE__*/ _jsxs("div", {
446
+ className: styles.selectWrapper,
447
+ children: [
448
+ selectedProviderProfile && getProviderIcon(selectedProviderProfile.provider),
449
+ /*#__PURE__*/ _jsxs("select", {
450
+ className: styles.select,
451
+ style: {
452
+ paddingLeft: selectedProviderProfile ? "34px" : "12px"
453
+ },
454
+ disabled: !settingsProvider,
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) : "",
460
+ children: [
461
+ !settingsProvider && /*#__PURE__*/ _jsx("option", {
462
+ value: "",
463
+ children: "No provider selected"
464
+ }),
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))
472
+ ]
473
+ })
474
+ ]
475
+ })
476
+ ]
477
+ })
478
+ }),
479
+ /*#__PURE__*/ _jsx("div", {
480
+ className: styles.actions,
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
+ })
489
+ })
490
+ ]
491
+ }),
492
+ (proposals.length > 0 || response || isLoading) && /*#__PURE__*/ _jsx(ActionToast, {
493
+ apiRoute: config.routes.api,
494
+ description: response,
495
+ error: error,
496
+ getViewURL: getProposalViewURL,
497
+ isApplying: isApplying,
498
+ isLoading: isLoading,
499
+ onDismiss: ()=>dismissChat(),
500
+ onDismissError: ()=>setError(""),
501
+ onApply: (proposal, _index)=>void handleApplyProposal(proposal),
502
+ proposals: proposals,
503
+ prompt: prompt,
504
+ tokenUsage: tokenUsage
505
+ })
506
+ ]
507
+ })
508
+ });
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
+ };
548
+ export default AIInput;