@mvriu5/payload-ai 0.5.9 → 1.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.
- package/README.md +62 -6
- package/dist/ai/{proposals.d.ts → proposalSigning.d.ts} +1 -1
- package/dist/ai/{proposals.js → proposalSigning.js} +6 -11
- package/dist/ai/providerOptions.d.ts +17 -1
- package/dist/ai/providerOptions.js +35 -7
- package/dist/ai/providerRuntime.d.ts +2 -2
- package/dist/ai/providerRuntime.js +50 -16
- package/dist/ai/sensitiveData.js +2 -6
- package/dist/components/AIInput.d.ts +2 -1
- package/dist/components/AIInput.js +363 -135
- package/dist/components/AIInput.module.css +15 -1
- package/dist/components/APIKeyField.d.ts +3 -0
- package/dist/components/APIKeyField.js +63 -0
- package/dist/components/ActionToast.d.ts +30 -0
- package/dist/components/ActionToast.js +263 -0
- package/dist/components/AuditLogList.d.ts +32 -0
- package/dist/components/AuditLogList.js +104 -0
- package/dist/components/{RecentChangesList.module.css → AuditLogList.module.css} +18 -1
- package/dist/components/DiffDialog.d.ts +10 -3
- package/dist/components/DiffDialog.js +284 -97
- package/dist/components/DiffDialog.module.css +86 -2
- package/dist/components/Icons.d.ts +5 -0
- package/dist/components/Icons.js +436 -214
- package/dist/components/MentionPopover.d.ts +19 -0
- package/dist/components/MentionPopover.js +95 -0
- package/dist/components/{CollectionMentionPopover.module.css → MentionPopover.module.css} +0 -2
- package/dist/components/hooks/useAISettings.d.ts +6 -4
- package/dist/components/hooks/useAISettings.js +18 -3
- package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +3 -3
- package/dist/components/hooks/useDocumentMentionSuggestions.js +2 -2
- package/dist/exports/client.d.ts +4 -2
- package/dist/exports/client.js +4 -2
- package/dist/handlers/applyActionHandler.d.ts +8 -0
- package/dist/handlers/applyActionHandler.js +563 -0
- package/dist/handlers/auditLogHandler.d.ts +6 -0
- package/dist/{endpoints/aiRecentChangesEndpointHandler.js → handlers/auditLogHandler.js} +12 -8
- package/dist/handlers/chatHandler.d.ts +42 -0
- package/dist/handlers/chatHandler.js +721 -0
- package/dist/handlers/mentionSuggestionHandler.d.ts +7 -0
- package/dist/handlers/mentionSuggestionHandler.js +97 -0
- package/dist/handlers/proposalDiffHandler.d.ts +7 -0
- package/dist/handlers/proposalDiffHandler.js +208 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +52 -20
- package/dist/payload/collectionPermissions.d.ts +14 -13
- package/dist/payload/collectionPermissions.js +3 -2
- package/dist/payload/normalizeData.d.ts +8 -0
- package/dist/payload/normalizeData.js +25 -3
- package/dist/payload/schemaContext.d.ts +43 -16
- package/dist/payload/schemaContext.js +139 -18
- package/dist/payload/shared.d.ts +33 -0
- package/dist/payload/shared.js +114 -0
- package/package.json +45 -9
- package/dist/components/AIActionProposalList.d.ts +0 -26
- package/dist/components/AIActionProposalList.js +0 -162
- package/dist/components/AIApiKeyField.d.ts +0 -2
- package/dist/components/AIApiKeyField.js +0 -47
- package/dist/components/CollectionMentionPopover.d.ts +0 -16
- package/dist/components/CollectionMentionPopover.js +0 -77
- package/dist/components/RecentChangesList.d.ts +0 -18
- package/dist/components/RecentChangesList.js +0 -66
- package/dist/components/hooks/utils.d.ts +0 -1
- package/dist/components/hooks/utils.js +0 -3
- package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +0 -8
- package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -365
- package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
- package/dist/endpoints/aiChatEndpointHandler.js +0 -382
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
- package/dist/endpoints/aiProposalDiffEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiProposalDiffEndpointHandler.js +0 -138
- package/dist/endpoints/aiRecentChangesEndpointHandler.d.ts +0 -6
- /package/dist/components/{AIActionProposalList.module.css → ActionToast.module.css} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useConfig } from "@payloadcms/ui";
|
|
3
4
|
import { formatAdminURL } from "payload/shared";
|
|
4
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
6
|
import { getResolvedAIModelConfig } from "../ai/providerOptions.js";
|
|
6
7
|
import { getSerializableLabel, isInternalCollection } from "../payload/shared.js";
|
|
7
|
-
import {
|
|
8
|
+
import { ActionToast } from "./ActionToast.js";
|
|
8
9
|
import styles from "./AIInput.module.css";
|
|
9
|
-
import {
|
|
10
|
-
import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, Send } from "./Icons.js";
|
|
11
|
-
import { RecentChangesList } from "./
|
|
10
|
+
import { MentionPopover } from "./MentionPopover.js";
|
|
11
|
+
import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, OpenrouterIcon, Send } from "./Icons.js";
|
|
12
|
+
import { RecentChangesList } from "./AuditLogList.js";
|
|
12
13
|
import { useAISettings } from "./hooks/useAISettings.js";
|
|
13
14
|
import { useDocumentMentionSuggestions } from "./hooks/useDocumentMentionSuggestions.js";
|
|
14
15
|
const collectBlockOptions = ({ fields, parent })=>{
|
|
@@ -44,13 +45,25 @@ const getProviderIcon = (provider)=>{
|
|
|
44
45
|
};
|
|
45
46
|
switch(provider){
|
|
46
47
|
case "claude":
|
|
47
|
-
return /*#__PURE__*/
|
|
48
|
+
return /*#__PURE__*/ _jsx(ClaudeIcon, {
|
|
49
|
+
...iconProps
|
|
50
|
+
});
|
|
48
51
|
case "google":
|
|
49
|
-
return /*#__PURE__*/
|
|
52
|
+
return /*#__PURE__*/ _jsx(GoogleGeminiIcon, {
|
|
53
|
+
...iconProps
|
|
54
|
+
});
|
|
50
55
|
case "mistral":
|
|
51
|
-
return /*#__PURE__*/
|
|
56
|
+
return /*#__PURE__*/ _jsx(MistralAiIcon, {
|
|
57
|
+
...iconProps
|
|
58
|
+
});
|
|
52
59
|
case "openai":
|
|
53
|
-
return /*#__PURE__*/
|
|
60
|
+
return /*#__PURE__*/ _jsx(OpenaiIcon, {
|
|
61
|
+
...iconProps
|
|
62
|
+
});
|
|
63
|
+
case "openrouter":
|
|
64
|
+
return /*#__PURE__*/ _jsx(OpenrouterIcon, {
|
|
65
|
+
...iconProps
|
|
66
|
+
});
|
|
54
67
|
default:
|
|
55
68
|
return null;
|
|
56
69
|
}
|
|
@@ -83,7 +96,83 @@ const parseSSEEvent = (chunk)=>{
|
|
|
83
96
|
}
|
|
84
97
|
};
|
|
85
98
|
const sanitizeResponseText = (value)=>value.replace(/\*\*/g, "");
|
|
86
|
-
|
|
99
|
+
const isMentionBoundary = (character)=>{
|
|
100
|
+
return character === undefined || /\s/.test(character);
|
|
101
|
+
};
|
|
102
|
+
const getActiveMentionRange = (valueBeforeCaret)=>{
|
|
103
|
+
const caretPosition = valueBeforeCaret.length;
|
|
104
|
+
for(let index = valueBeforeCaret.length - 1; index >= 0; index -= 1){
|
|
105
|
+
const character = valueBeforeCaret[index];
|
|
106
|
+
if (character === "@") {
|
|
107
|
+
const previousCharacter = valueBeforeCaret[index - 1];
|
|
108
|
+
const query = valueBeforeCaret.slice(index + 1);
|
|
109
|
+
if (!isMentionBoundary(previousCharacter)) return null;
|
|
110
|
+
if (!/^[\w-]*$/.test(query)) return null;
|
|
111
|
+
return {
|
|
112
|
+
query,
|
|
113
|
+
range: {
|
|
114
|
+
end: caretPosition,
|
|
115
|
+
start: index
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (isMentionBoundary(character)) break;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
const svgNamespace = "http://www.w3.org/2000/svg";
|
|
124
|
+
const auditLogCollectionSlug = "payload-ai-auditlog";
|
|
125
|
+
const appendSvgPath = (svg, d)=>{
|
|
126
|
+
const path = document.createElementNS(svgNamespace, "path");
|
|
127
|
+
path.setAttribute("d", d);
|
|
128
|
+
svg.append(path);
|
|
129
|
+
};
|
|
130
|
+
const createBadgeIcon = (type)=>{
|
|
131
|
+
if (type === "locale") return null;
|
|
132
|
+
const svg = document.createElementNS(svgNamespace, "svg");
|
|
133
|
+
svg.setAttribute("aria-hidden", "true");
|
|
134
|
+
svg.setAttribute("class", styles.badgeIcon);
|
|
135
|
+
svg.setAttribute("fill", "none");
|
|
136
|
+
svg.setAttribute("stroke", "currentColor");
|
|
137
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
138
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
139
|
+
svg.setAttribute("stroke-width", "2");
|
|
140
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
141
|
+
if (type === "collection") {
|
|
142
|
+
appendSvgPath(svg, "M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2");
|
|
143
|
+
}
|
|
144
|
+
if (type === "doc") {
|
|
145
|
+
appendSvgPath(svg, "M14 3v4a1 1 0 0 0 1 1h4");
|
|
146
|
+
appendSvgPath(svg, "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2");
|
|
147
|
+
}
|
|
148
|
+
if (type === "global") {
|
|
149
|
+
appendSvgPath(svg, "M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0");
|
|
150
|
+
appendSvgPath(svg, "M3.6 9h16.8");
|
|
151
|
+
appendSvgPath(svg, "M3.6 15h16.8");
|
|
152
|
+
appendSvgPath(svg, "M11.5 3a17 17 0 0 0 0 18");
|
|
153
|
+
appendSvgPath(svg, "M12.5 3a17 17 0 0 1 0 18");
|
|
154
|
+
}
|
|
155
|
+
if (type === "block") {
|
|
156
|
+
appendSvgPath(svg, "M14 4a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1l0 -5");
|
|
157
|
+
appendSvgPath(svg, "M3 14h12a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h3a2 2 0 0 1 2 2v12");
|
|
158
|
+
}
|
|
159
|
+
return svg;
|
|
160
|
+
};
|
|
161
|
+
const createBadgePrefix = (suggestion)=>{
|
|
162
|
+
const prefix = document.createElement("span");
|
|
163
|
+
const icon = createBadgeIcon(suggestion.type);
|
|
164
|
+
prefix.className = styles.prefix;
|
|
165
|
+
if (icon) {
|
|
166
|
+
prefix.append(icon);
|
|
167
|
+
}
|
|
168
|
+
if (suggestion.type === "doc") {
|
|
169
|
+
prefix.append(document.createTextNode(`${suggestion.collection || "document"}:`));
|
|
170
|
+
} else if (suggestion.type === "locale") {
|
|
171
|
+
prefix.textContent = "locale:";
|
|
172
|
+
}
|
|
173
|
+
return prefix;
|
|
174
|
+
};
|
|
175
|
+
const AIInput = ()=>{
|
|
87
176
|
const { config } = useConfig();
|
|
88
177
|
const editorRef = useRef(null);
|
|
89
178
|
const mentionPopoverRef = useRef(null);
|
|
@@ -99,9 +188,11 @@ export const AIInput = ()=>{
|
|
|
99
188
|
});
|
|
100
189
|
const [mentionQuery, setMentionQuery] = useState("");
|
|
101
190
|
const [mentionRange, setMentionRange] = useState(null);
|
|
191
|
+
const [mentionPopoverPosition, setMentionPopoverPosition] = useState(null);
|
|
102
192
|
const [mentions, setMentions] = useState([]);
|
|
103
193
|
const [appliedProposalIndexes, setAppliedProposalIndexes] = useState([]);
|
|
104
194
|
const [response, setResponse] = useState("");
|
|
195
|
+
const [tokenUsage, setTokenUsage] = useState(null);
|
|
105
196
|
const [error, setError] = useState("");
|
|
106
197
|
const [proposals, setProposals] = useState([]);
|
|
107
198
|
const [appliedChanges, setAppliedChanges] = useState([]);
|
|
@@ -114,15 +205,18 @@ export const AIInput = ()=>{
|
|
|
114
205
|
const isCollectionMentionEnabled = (slug)=>!enabledCollectionSlugSet || enabledCollectionSlugSet.has(slug);
|
|
115
206
|
const recentChangesEndpoint = useMemo(()=>formatAdminURL({
|
|
116
207
|
apiRoute: config.routes.api,
|
|
117
|
-
path: "/ai-
|
|
208
|
+
path: "/ai-audit-log"
|
|
118
209
|
}), [
|
|
119
210
|
config.routes.api
|
|
120
211
|
]);
|
|
212
|
+
const allChangesURL = useMemo(()=>`${config.routes.admin || "/admin"}/collections/${auditLogCollectionSlug}`, [
|
|
213
|
+
config.routes.admin
|
|
214
|
+
]);
|
|
121
215
|
const loadRecentChanges = useCallback(async ()=>{
|
|
122
216
|
const res = await fetch(recentChangesEndpoint);
|
|
123
217
|
const result = await res.json().catch(()=>null);
|
|
124
218
|
if (res.ok && result?.changes) {
|
|
125
|
-
setAppliedChanges(result.changes);
|
|
219
|
+
setAppliedChanges(result.changes.slice(0, 10));
|
|
126
220
|
}
|
|
127
221
|
}, [
|
|
128
222
|
recentChangesEndpoint
|
|
@@ -155,6 +249,31 @@ export const AIInput = ()=>{
|
|
|
155
249
|
slug: global.slug,
|
|
156
250
|
type: "global"
|
|
157
251
|
})) || [];
|
|
252
|
+
const localizationConfig = config.localization;
|
|
253
|
+
const localesConfig = localizationConfig?.locales ?? [];
|
|
254
|
+
const locales = localesConfig.flatMap((locale)=>{
|
|
255
|
+
if (typeof locale === "string") {
|
|
256
|
+
return [
|
|
257
|
+
{
|
|
258
|
+
isDefault: locale === localizationConfig?.defaultLocale,
|
|
259
|
+
label: locale,
|
|
260
|
+
slug: locale,
|
|
261
|
+
type: "locale"
|
|
262
|
+
}
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
if (!locale || typeof locale !== "object") return [];
|
|
266
|
+
const slug = typeof locale.code === "string" ? locale.code : typeof locale.label === "string" ? locale.label : null;
|
|
267
|
+
if (!slug) return [];
|
|
268
|
+
return [
|
|
269
|
+
{
|
|
270
|
+
isDefault: slug === localizationConfig?.defaultLocale,
|
|
271
|
+
label: getSerializableLabel(locale.label, slug),
|
|
272
|
+
slug,
|
|
273
|
+
type: "locale"
|
|
274
|
+
}
|
|
275
|
+
];
|
|
276
|
+
});
|
|
158
277
|
const blocks = [
|
|
159
278
|
...config.collections.filter((collection)=>isCollectionMentionEnabled(collection.slug)).flatMap((collection)=>collectBlockOptions({
|
|
160
279
|
fields: collection.fields,
|
|
@@ -168,11 +287,12 @@ export const AIInput = ()=>{
|
|
|
168
287
|
const mentionOptions = [
|
|
169
288
|
...collections,
|
|
170
289
|
...globals,
|
|
171
|
-
...blocks
|
|
290
|
+
...blocks,
|
|
291
|
+
...locales
|
|
172
292
|
];
|
|
173
293
|
const normalizedMentionQuery = mentionQuery.toLowerCase();
|
|
174
294
|
const filteredCollections = collections.filter((collection)=>collection.slug.toLowerCase().includes(normalizedMentionQuery) || collection.label.toLowerCase().includes(normalizedMentionQuery));
|
|
175
|
-
const filteredMentionOptions = mentionOptions.filter((option)=>option.slug.toLowerCase().includes(normalizedMentionQuery));
|
|
295
|
+
const filteredMentionOptions = mentionOptions.filter((option)=>option.slug.toLowerCase().includes(normalizedMentionQuery) || option.label.toLowerCase().includes(normalizedMentionQuery));
|
|
176
296
|
const documentSuggestionCollection = filteredCollections.length === 1 ? filteredCollections[0]?.slug : null;
|
|
177
297
|
const { documentSuggestions, resetDocumentSuggestions } = useDocumentMentionSuggestions({
|
|
178
298
|
apiRoute: config.routes.api,
|
|
@@ -184,14 +304,14 @@ export const AIInput = ()=>{
|
|
|
184
304
|
...filteredMentionOptions,
|
|
185
305
|
...documentSuggestions
|
|
186
306
|
];
|
|
187
|
-
const
|
|
307
|
+
const getTextBeforeCaret = (element)=>{
|
|
188
308
|
const selection = window.getSelection();
|
|
189
|
-
if (!selection || selection.rangeCount === 0) return
|
|
309
|
+
if (!selection || selection.rangeCount === 0) return "";
|
|
190
310
|
const range = selection.getRangeAt(0);
|
|
191
311
|
const clonedRange = range.cloneRange();
|
|
192
312
|
clonedRange.selectNodeContents(element);
|
|
193
313
|
clonedRange.setEnd(range.endContainer, range.endOffset);
|
|
194
|
-
return clonedRange.toString()
|
|
314
|
+
return clonedRange.toString();
|
|
195
315
|
};
|
|
196
316
|
const getTextNodeAtOffset = (element, offset)=>{
|
|
197
317
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
@@ -215,6 +335,28 @@ export const AIInput = ()=>{
|
|
|
215
335
|
offset: 0
|
|
216
336
|
};
|
|
217
337
|
};
|
|
338
|
+
const updateMentionPopoverPosition = useCallback((range)=>{
|
|
339
|
+
const editor = editorRef.current;
|
|
340
|
+
const popover = mentionPopoverRef.current;
|
|
341
|
+
if (!editor || !popover || !range) {
|
|
342
|
+
setMentionPopoverPosition(null);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const startPosition = getTextNodeAtOffset(editor, range.start);
|
|
346
|
+
const anchorRange = document.createRange();
|
|
347
|
+
anchorRange.setStart(startPosition.node, startPosition.offset);
|
|
348
|
+
anchorRange.setEnd(startPosition.node, startPosition.offset);
|
|
349
|
+
const editorRect = editor.getBoundingClientRect();
|
|
350
|
+
const anchorRect = anchorRange.getBoundingClientRect();
|
|
351
|
+
const fallbackLeft = Math.max(0, anchorRect.left - editorRect.left + 12);
|
|
352
|
+
const fallbackTop = Math.max(0, anchorRect.bottom - editorRect.top + 20);
|
|
353
|
+
const popoverWidth = popover.offsetWidth || 260;
|
|
354
|
+
const maxLeft = Math.max(0, editor.clientWidth - popoverWidth);
|
|
355
|
+
setMentionPopoverPosition({
|
|
356
|
+
left: Math.min(fallbackLeft, maxLeft),
|
|
357
|
+
top: fallbackTop
|
|
358
|
+
});
|
|
359
|
+
}, []);
|
|
218
360
|
const replaceTextRangeWithBadge = ({ badge, editor, end, start })=>{
|
|
219
361
|
const startPosition = getTextNodeAtOffset(editor, start);
|
|
220
362
|
const endPosition = getTextNodeAtOffset(editor, end);
|
|
@@ -232,21 +374,41 @@ export const AIInput = ()=>{
|
|
|
232
374
|
selection?.removeAllRanges();
|
|
233
375
|
selection?.addRange(caretRange);
|
|
234
376
|
};
|
|
235
|
-
const updateMentionState = (
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
if (!match || typeof match.index !== "number") {
|
|
377
|
+
const updateMentionState = (valueBeforeCaret)=>{
|
|
378
|
+
const activeMention = getActiveMentionRange(valueBeforeCaret);
|
|
379
|
+
if (!activeMention) {
|
|
239
380
|
setMentionQuery("");
|
|
240
381
|
setMentionRange(null);
|
|
382
|
+
setMentionPopoverPosition(null);
|
|
241
383
|
return;
|
|
242
384
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
setMentionRange({
|
|
246
|
-
end: caretPosition,
|
|
247
|
-
start: atIndex
|
|
248
|
-
});
|
|
385
|
+
setMentionQuery(activeMention.query);
|
|
386
|
+
setMentionRange(activeMention.range);
|
|
249
387
|
};
|
|
388
|
+
useEffect(()=>{
|
|
389
|
+
if (!mentionRange) {
|
|
390
|
+
setMentionPopoverPosition(null);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
updateMentionPopoverPosition(mentionRange);
|
|
394
|
+
}, [
|
|
395
|
+
mentionRange,
|
|
396
|
+
mentionSuggestions.length,
|
|
397
|
+
updateMentionPopoverPosition
|
|
398
|
+
]);
|
|
399
|
+
useEffect(()=>{
|
|
400
|
+
if (!mentionRange) return;
|
|
401
|
+
const updatePosition = ()=>updateMentionPopoverPosition(mentionRange);
|
|
402
|
+
window.addEventListener("resize", updatePosition);
|
|
403
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
404
|
+
return ()=>{
|
|
405
|
+
window.removeEventListener("resize", updatePosition);
|
|
406
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
407
|
+
};
|
|
408
|
+
}, [
|
|
409
|
+
mentionRange,
|
|
410
|
+
updateMentionPopoverPosition
|
|
411
|
+
]);
|
|
250
412
|
const clearInput = ()=>{
|
|
251
413
|
setPrompt("");
|
|
252
414
|
setMentions([]);
|
|
@@ -265,12 +427,12 @@ export const AIInput = ()=>{
|
|
|
265
427
|
const insertMention = (suggestion)=>{
|
|
266
428
|
const editor = editorRef.current;
|
|
267
429
|
if (!mentionRange || !editor) return;
|
|
268
|
-
const
|
|
269
|
-
const
|
|
430
|
+
const currentValue = editor.innerText;
|
|
431
|
+
const beforeMention = currentValue.slice(0, mentionRange.start);
|
|
432
|
+
const afterMention = currentValue.slice(mentionRange.end);
|
|
270
433
|
const badgeType = suggestion.type === "doc" ? "document" : suggestion.type;
|
|
271
434
|
const badgePrefix = `${badgeType}:`;
|
|
272
435
|
const badgeText = `${badgePrefix} ${suggestion.label}`;
|
|
273
|
-
const promptText = suggestion.type === "doc" ? `${badgeText} (${suggestion.collection}/${suggestion.id})` : badgeText;
|
|
274
436
|
const badge = document.createElement("span");
|
|
275
437
|
badge.className = [
|
|
276
438
|
styles.badge,
|
|
@@ -278,10 +440,7 @@ export const AIInput = ()=>{
|
|
|
278
440
|
styles.inlineBadge
|
|
279
441
|
].join(" ");
|
|
280
442
|
badge.contentEditable = "false";
|
|
281
|
-
badge.append(Object.assign(document.createElement("span"), {
|
|
282
|
-
className: styles.prefix,
|
|
283
|
-
textContent: `${badgePrefix} `
|
|
284
|
-
}), Object.assign(document.createElement("span"), {
|
|
443
|
+
badge.append(createBadgePrefix(suggestion), Object.assign(document.createElement("span"), {
|
|
285
444
|
className: styles.name,
|
|
286
445
|
textContent: suggestion.label
|
|
287
446
|
}));
|
|
@@ -292,7 +451,7 @@ export const AIInput = ()=>{
|
|
|
292
451
|
start: mentionRange.start
|
|
293
452
|
});
|
|
294
453
|
editor.focus();
|
|
295
|
-
setPrompt(`${beforeMention}${
|
|
454
|
+
setPrompt(`${beforeMention}${badgeText} ${afterMention}`);
|
|
296
455
|
setMentions((currentMentions)=>{
|
|
297
456
|
const mentionExists = currentMentions.some((mention)=>mention.type === suggestion.type && mention.slug === suggestion.slug && mention.parent === suggestion.parent && mention.collection === suggestion.collection && mention.id === suggestion.id);
|
|
298
457
|
if (mentionExists) return currentMentions;
|
|
@@ -303,6 +462,7 @@ export const AIInput = ()=>{
|
|
|
303
462
|
});
|
|
304
463
|
setMentionQuery("");
|
|
305
464
|
setMentionRange(null);
|
|
465
|
+
setMentionPopoverPosition(null);
|
|
306
466
|
resetDocumentSuggestions();
|
|
307
467
|
};
|
|
308
468
|
const handleSubmit = async ()=>{
|
|
@@ -313,6 +473,7 @@ export const AIInput = ()=>{
|
|
|
313
473
|
setError("");
|
|
314
474
|
setProposals([]);
|
|
315
475
|
setResponse("");
|
|
476
|
+
setTokenUsage(null);
|
|
316
477
|
try {
|
|
317
478
|
const res = await fetch(formatAdminURL({
|
|
318
479
|
apiRoute: config.routes.api,
|
|
@@ -338,6 +499,7 @@ export const AIInput = ()=>{
|
|
|
338
499
|
const reader = res.body.getReader();
|
|
339
500
|
const decoder = new TextDecoder();
|
|
340
501
|
let buffer = "";
|
|
502
|
+
let receivedProposals = [];
|
|
341
503
|
while(true){
|
|
342
504
|
const { done, value } = await reader.read();
|
|
343
505
|
if (done) break;
|
|
@@ -356,7 +518,9 @@ export const AIInput = ()=>{
|
|
|
356
518
|
continue;
|
|
357
519
|
}
|
|
358
520
|
if (event.event === "proposals") {
|
|
359
|
-
|
|
521
|
+
receivedProposals = event.data.proposals || [];
|
|
522
|
+
setProposals(receivedProposals);
|
|
523
|
+
setTokenUsage(event.data.usage || null);
|
|
360
524
|
continue;
|
|
361
525
|
}
|
|
362
526
|
if (event.event === "error") {
|
|
@@ -366,14 +530,20 @@ export const AIInput = ()=>{
|
|
|
366
530
|
}
|
|
367
531
|
const finalEvent = buffer.trim() ? parseSSEEvent(buffer.trim()) : null;
|
|
368
532
|
if (finalEvent?.event === "proposals") {
|
|
369
|
-
|
|
533
|
+
receivedProposals = finalEvent.data.proposals || [];
|
|
534
|
+
setProposals(receivedProposals);
|
|
535
|
+
setTokenUsage(finalEvent.data.usage || null);
|
|
370
536
|
}
|
|
371
537
|
if (finalEvent?.event === "error") {
|
|
372
538
|
throw new Error(finalEvent.data.error || "AI request failed");
|
|
373
539
|
}
|
|
540
|
+
if (receivedProposals.length === 0) {
|
|
541
|
+
clearInput();
|
|
542
|
+
}
|
|
374
543
|
} catch (err) {
|
|
375
544
|
setProposals([]);
|
|
376
545
|
setResponse("");
|
|
546
|
+
setTokenUsage(null);
|
|
377
547
|
setError(err instanceof Error ? err.message : "AI request failed");
|
|
378
548
|
} finally{
|
|
379
549
|
setIsLoading(false);
|
|
@@ -388,7 +558,10 @@ export const AIInput = ()=>{
|
|
|
388
558
|
path: "/ai-apply-action"
|
|
389
559
|
}), {
|
|
390
560
|
body: JSON.stringify({
|
|
391
|
-
|
|
561
|
+
aiResponse: response,
|
|
562
|
+
prompt,
|
|
563
|
+
proposal,
|
|
564
|
+
tokenUsage
|
|
392
565
|
}),
|
|
393
566
|
headers: {
|
|
394
567
|
"Content-Type": "application/json"
|
|
@@ -405,11 +578,12 @@ export const AIInput = ()=>{
|
|
|
405
578
|
setError("");
|
|
406
579
|
setProposals([]);
|
|
407
580
|
setResponse("");
|
|
581
|
+
setTokenUsage(null);
|
|
408
582
|
if (result.change) {
|
|
409
583
|
setAppliedChanges((current)=>[
|
|
410
584
|
result.change,
|
|
411
585
|
...current
|
|
412
|
-
].slice(0,
|
|
586
|
+
].slice(0, 10));
|
|
413
587
|
}
|
|
414
588
|
void loadRecentChanges().catch(()=>undefined);
|
|
415
589
|
clearInput();
|
|
@@ -419,101 +593,155 @@ export const AIInput = ()=>{
|
|
|
419
593
|
setIsApplying(false);
|
|
420
594
|
}
|
|
421
595
|
};
|
|
422
|
-
return /*#__PURE__*/
|
|
423
|
-
className: styles.chatLayout
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
596
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
597
|
+
className: styles.chatLayout,
|
|
598
|
+
children: [
|
|
599
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
600
|
+
className: styles.chat,
|
|
601
|
+
children: [
|
|
602
|
+
/*#__PURE__*/ _jsx("div", {
|
|
603
|
+
className: styles.chatHeader,
|
|
604
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
605
|
+
children: [
|
|
606
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
607
|
+
className: styles.chatTitle,
|
|
608
|
+
children: "AI Assistant"
|
|
609
|
+
}),
|
|
610
|
+
/*#__PURE__*/ _jsx("p", {
|
|
611
|
+
className: styles.chatDescription,
|
|
612
|
+
children: "Ask AI to draft, improve, or analyze content."
|
|
613
|
+
})
|
|
614
|
+
]
|
|
615
|
+
})
|
|
616
|
+
}),
|
|
617
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
618
|
+
className: styles.chatInputRow,
|
|
619
|
+
children: [
|
|
620
|
+
/*#__PURE__*/ _jsx("div", {
|
|
621
|
+
className: styles.chatInputSurface,
|
|
622
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
623
|
+
className: styles.chatInput,
|
|
624
|
+
contentEditable: true,
|
|
625
|
+
"data-placeholder": "Ask AI...",
|
|
626
|
+
onInput: (event)=>{
|
|
627
|
+
const value = event.currentTarget.innerText;
|
|
628
|
+
setPrompt(value);
|
|
629
|
+
if (!value.trim()) {
|
|
630
|
+
setMentions([]);
|
|
631
|
+
}
|
|
632
|
+
updateMentionState(getTextBeforeCaret(event.currentTarget));
|
|
633
|
+
},
|
|
634
|
+
onKeyDown: (event)=>{
|
|
635
|
+
if (event.key === "ArrowDown" && mentionRange && mentionSuggestions.length > 0) {
|
|
636
|
+
const firstOption = mentionPopoverRef.current?.querySelector("button");
|
|
637
|
+
if (firstOption) {
|
|
638
|
+
event.preventDefault();
|
|
639
|
+
firstOption.focus();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
644
|
+
event.preventDefault();
|
|
645
|
+
void handleSubmit();
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
ref: editorRef,
|
|
649
|
+
role: "textbox",
|
|
650
|
+
suppressContentEditableWarning: true
|
|
651
|
+
})
|
|
652
|
+
}),
|
|
653
|
+
mentionRange && /*#__PURE__*/ _jsx(MentionPopover, {
|
|
654
|
+
containerRef: mentionPopoverRef,
|
|
655
|
+
onSelect: insertMention,
|
|
656
|
+
style: mentionPopoverPosition ? {
|
|
657
|
+
left: `${mentionPopoverPosition.left}px`,
|
|
658
|
+
top: `${mentionPopoverPosition.top}px`
|
|
659
|
+
} : undefined,
|
|
660
|
+
suggestions: mentionSuggestions
|
|
661
|
+
})
|
|
662
|
+
]
|
|
663
|
+
}),
|
|
664
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
665
|
+
className: styles.chatActionsRow,
|
|
666
|
+
children: [
|
|
667
|
+
/*#__PURE__*/ _jsx("div", {
|
|
668
|
+
className: styles.settings,
|
|
669
|
+
children: /*#__PURE__*/ _jsxs("label", {
|
|
670
|
+
className: styles.setting,
|
|
671
|
+
children: [
|
|
672
|
+
/*#__PURE__*/ _jsx("span", {
|
|
673
|
+
className: styles.settingLabel,
|
|
674
|
+
children: "Model"
|
|
675
|
+
}),
|
|
676
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
677
|
+
className: styles.selectWrapper,
|
|
678
|
+
children: [
|
|
679
|
+
getProviderIcon(settingsProvider),
|
|
680
|
+
/*#__PURE__*/ _jsxs("select", {
|
|
681
|
+
className: styles.select,
|
|
682
|
+
disabled: !settingsProvider,
|
|
683
|
+
onChange: (event)=>setSelectedModel(event.target.value),
|
|
684
|
+
value: selectedModel,
|
|
685
|
+
children: [
|
|
686
|
+
!settingsProvider && /*#__PURE__*/ _jsx("option", {
|
|
687
|
+
value: "",
|
|
688
|
+
children: "Select provider in account settings"
|
|
689
|
+
}),
|
|
690
|
+
settingsProvider && aiModelConfig.providers[settingsProvider].map((model)=>/*#__PURE__*/ _jsx("option", {
|
|
691
|
+
value: model.value,
|
|
692
|
+
children: model.label
|
|
693
|
+
}, model.value))
|
|
694
|
+
]
|
|
695
|
+
})
|
|
696
|
+
]
|
|
697
|
+
})
|
|
698
|
+
]
|
|
699
|
+
})
|
|
700
|
+
}),
|
|
701
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
702
|
+
className: styles.chatButton,
|
|
703
|
+
disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading,
|
|
704
|
+
onClick: ()=>void handleSubmit(),
|
|
705
|
+
type: "button",
|
|
706
|
+
children: [
|
|
707
|
+
/*#__PURE__*/ _jsx(Send, {
|
|
708
|
+
width: 14,
|
|
709
|
+
height: 14
|
|
710
|
+
}),
|
|
711
|
+
isLoading ? "Sending..." : "Send"
|
|
712
|
+
]
|
|
713
|
+
})
|
|
714
|
+
]
|
|
715
|
+
}),
|
|
716
|
+
/*#__PURE__*/ _jsx(ActionToast, {
|
|
717
|
+
apiRoute: config.routes.api,
|
|
718
|
+
appliedProposalIndexes: appliedProposalIndexes,
|
|
719
|
+
description: response,
|
|
720
|
+
error: error,
|
|
721
|
+
getViewURL: getProposalViewURL,
|
|
722
|
+
isApplying: isApplying,
|
|
723
|
+
onDismiss: ()=>{
|
|
724
|
+
setAppliedProposalIndexes([]);
|
|
725
|
+
setError("");
|
|
726
|
+
setProposals([]);
|
|
727
|
+
setResponse("");
|
|
728
|
+
setTokenUsage(null);
|
|
729
|
+
clearInput();
|
|
730
|
+
},
|
|
731
|
+
onDismissError: ()=>{
|
|
732
|
+
setError("");
|
|
733
|
+
},
|
|
734
|
+
onApply: (proposal, _index)=>void handleApplyProposal(proposal),
|
|
735
|
+
proposals: proposals,
|
|
736
|
+
tokenUsage: tokenUsage
|
|
737
|
+
})
|
|
738
|
+
]
|
|
739
|
+
}),
|
|
740
|
+
/*#__PURE__*/ _jsx(RecentChangesList, {
|
|
741
|
+
allChangesURL: allChangesURL,
|
|
742
|
+
changes: appliedChanges
|
|
743
|
+
})
|
|
744
|
+
]
|
|
745
|
+
});
|
|
519
746
|
};
|
|
747
|
+
export default AIInput;
|