@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.
Files changed (73) hide show
  1. package/README.md +62 -6
  2. package/dist/ai/{proposals.d.ts → proposalSigning.d.ts} +1 -1
  3. package/dist/ai/{proposals.js → proposalSigning.js} +6 -11
  4. package/dist/ai/providerOptions.d.ts +17 -1
  5. package/dist/ai/providerOptions.js +35 -7
  6. package/dist/ai/providerRuntime.d.ts +2 -2
  7. package/dist/ai/providerRuntime.js +50 -16
  8. package/dist/ai/sensitiveData.js +2 -6
  9. package/dist/components/AIInput.d.ts +2 -1
  10. package/dist/components/AIInput.js +363 -135
  11. package/dist/components/AIInput.module.css +15 -1
  12. package/dist/components/APIKeyField.d.ts +3 -0
  13. package/dist/components/APIKeyField.js +63 -0
  14. package/dist/components/ActionToast.d.ts +30 -0
  15. package/dist/components/ActionToast.js +263 -0
  16. package/dist/components/AuditLogList.d.ts +32 -0
  17. package/dist/components/AuditLogList.js +104 -0
  18. package/dist/components/{RecentChangesList.module.css → AuditLogList.module.css} +18 -1
  19. package/dist/components/DiffDialog.d.ts +10 -3
  20. package/dist/components/DiffDialog.js +284 -97
  21. package/dist/components/DiffDialog.module.css +86 -2
  22. package/dist/components/Icons.d.ts +5 -0
  23. package/dist/components/Icons.js +436 -214
  24. package/dist/components/MentionPopover.d.ts +19 -0
  25. package/dist/components/MentionPopover.js +95 -0
  26. package/dist/components/{CollectionMentionPopover.module.css → MentionPopover.module.css} +0 -2
  27. package/dist/components/hooks/useAISettings.d.ts +6 -4
  28. package/dist/components/hooks/useAISettings.js +18 -3
  29. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +3 -3
  30. package/dist/components/hooks/useDocumentMentionSuggestions.js +2 -2
  31. package/dist/exports/client.d.ts +4 -2
  32. package/dist/exports/client.js +4 -2
  33. package/dist/handlers/applyActionHandler.d.ts +8 -0
  34. package/dist/handlers/applyActionHandler.js +563 -0
  35. package/dist/handlers/auditLogHandler.d.ts +6 -0
  36. package/dist/{endpoints/aiRecentChangesEndpointHandler.js → handlers/auditLogHandler.js} +12 -8
  37. package/dist/handlers/chatHandler.d.ts +42 -0
  38. package/dist/handlers/chatHandler.js +721 -0
  39. package/dist/handlers/mentionSuggestionHandler.d.ts +7 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +97 -0
  41. package/dist/handlers/proposalDiffHandler.d.ts +7 -0
  42. package/dist/handlers/proposalDiffHandler.js +208 -0
  43. package/dist/index.d.ts +6 -6
  44. package/dist/index.js +52 -20
  45. package/dist/payload/collectionPermissions.d.ts +14 -13
  46. package/dist/payload/collectionPermissions.js +3 -2
  47. package/dist/payload/normalizeData.d.ts +8 -0
  48. package/dist/payload/normalizeData.js +25 -3
  49. package/dist/payload/schemaContext.d.ts +43 -16
  50. package/dist/payload/schemaContext.js +139 -18
  51. package/dist/payload/shared.d.ts +33 -0
  52. package/dist/payload/shared.js +114 -0
  53. package/package.json +45 -9
  54. package/dist/components/AIActionProposalList.d.ts +0 -26
  55. package/dist/components/AIActionProposalList.js +0 -162
  56. package/dist/components/AIApiKeyField.d.ts +0 -2
  57. package/dist/components/AIApiKeyField.js +0 -47
  58. package/dist/components/CollectionMentionPopover.d.ts +0 -16
  59. package/dist/components/CollectionMentionPopover.js +0 -77
  60. package/dist/components/RecentChangesList.d.ts +0 -18
  61. package/dist/components/RecentChangesList.js +0 -66
  62. package/dist/components/hooks/utils.d.ts +0 -1
  63. package/dist/components/hooks/utils.js +0 -3
  64. package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +0 -8
  65. package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -365
  66. package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
  67. package/dist/endpoints/aiChatEndpointHandler.js +0 -382
  68. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
  69. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
  70. package/dist/endpoints/aiProposalDiffEndpointHandler.d.ts +0 -7
  71. package/dist/endpoints/aiProposalDiffEndpointHandler.js +0 -138
  72. package/dist/endpoints/aiRecentChangesEndpointHandler.d.ts +0 -6
  73. /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 { AIActionProposalList } from "./AIActionProposalList.js";
8
+ import { ActionToast } from "./ActionToast.js";
8
9
  import styles from "./AIInput.module.css";
9
- import { CollectionMentionPopover } from "./CollectionMentionPopover.js";
10
- import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, Send } from "./Icons.js";
11
- import { RecentChangesList } from "./RecentChangesList.js";
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__*/ React.createElement(ClaudeIcon, iconProps);
48
+ return /*#__PURE__*/ _jsx(ClaudeIcon, {
49
+ ...iconProps
50
+ });
48
51
  case "google":
49
- return /*#__PURE__*/ React.createElement(GoogleGeminiIcon, iconProps);
52
+ return /*#__PURE__*/ _jsx(GoogleGeminiIcon, {
53
+ ...iconProps
54
+ });
50
55
  case "mistral":
51
- return /*#__PURE__*/ React.createElement(MistralAiIcon, iconProps);
56
+ return /*#__PURE__*/ _jsx(MistralAiIcon, {
57
+ ...iconProps
58
+ });
52
59
  case "openai":
53
- return /*#__PURE__*/ React.createElement(OpenaiIcon, iconProps);
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
- export const AIInput = ()=>{
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-recent-changes"
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 getCaretOffset = (element)=>{
307
+ const getTextBeforeCaret = (element)=>{
188
308
  const selection = window.getSelection();
189
- if (!selection || selection.rangeCount === 0) return 0;
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().length;
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 = (value, caretPosition)=>{
236
- const valueBeforeCaret = value.slice(0, caretPosition);
237
- const match = /(?:^|\s)@([\w-]*)$/.exec(valueBeforeCaret);
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
- const atIndex = valueBeforeCaret.lastIndexOf("@");
244
- setMentionQuery(match[1] || "");
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 beforeMention = prompt.slice(0, mentionRange.start);
269
- const afterMention = prompt.slice(mentionRange.end);
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}${promptText} ${afterMention}`);
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
- setProposals(event.data.proposals || []);
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
- setProposals(finalEvent.data.proposals || []);
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
- proposal
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, 12));
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__*/ React.createElement("div", {
423
- className: styles.chatLayout
424
- }, /*#__PURE__*/ React.createElement("div", {
425
- className: styles.chat
426
- }, /*#__PURE__*/ React.createElement("div", {
427
- className: styles.chatHeader
428
- }, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
429
- className: styles.chatTitle
430
- }, "AI Assistant"), /*#__PURE__*/ React.createElement("p", {
431
- className: styles.chatDescription
432
- }, "Ask AI to draft, improve, or analyze content."))), /*#__PURE__*/ React.createElement("div", {
433
- className: styles.chatInputRow
434
- }, /*#__PURE__*/ React.createElement("div", {
435
- className: styles.chatInputSurface
436
- }, /*#__PURE__*/ React.createElement("div", {
437
- className: styles.chatInput,
438
- contentEditable: true,
439
- "data-placeholder": "Ask AI...",
440
- onInput: (event)=>{
441
- const value = event.currentTarget.innerText;
442
- setPrompt(value);
443
- if (!value.trim()) {
444
- setMentions([]);
445
- }
446
- updateMentionState(value, getCaretOffset(event.currentTarget));
447
- },
448
- onKeyDown: (event)=>{
449
- if (event.key === "ArrowDown" && mentionRange && mentionSuggestions.length > 0) {
450
- const firstOption = mentionPopoverRef.current?.querySelector("button");
451
- if (firstOption) {
452
- event.preventDefault();
453
- firstOption.focus();
454
- return;
455
- }
456
- }
457
- if (event.key === "Enter" && !event.shiftKey) {
458
- event.preventDefault();
459
- void handleSubmit();
460
- }
461
- },
462
- ref: editorRef,
463
- role: "textbox",
464
- suppressContentEditableWarning: true
465
- })), mentionRange ? /*#__PURE__*/ React.createElement(CollectionMentionPopover, {
466
- containerRef: mentionPopoverRef,
467
- onSelect: insertMention,
468
- suggestions: mentionSuggestions
469
- }) : null), /*#__PURE__*/ React.createElement("div", {
470
- className: styles.chatActionsRow
471
- }, /*#__PURE__*/ React.createElement("div", {
472
- className: styles.settings
473
- }, /*#__PURE__*/ React.createElement("label", {
474
- className: styles.setting
475
- }, /*#__PURE__*/ React.createElement("span", {
476
- className: styles.settingLabel
477
- }, "Model"), /*#__PURE__*/ React.createElement("div", {
478
- className: styles.selectWrapper
479
- }, getProviderIcon(settingsProvider), /*#__PURE__*/ React.createElement("select", {
480
- className: styles.select,
481
- disabled: !settingsProvider,
482
- onChange: (event)=>setSelectedModel(event.target.value),
483
- value: selectedModel
484
- }, !settingsProvider && /*#__PURE__*/ React.createElement("option", {
485
- value: ""
486
- }, "Select provider in account settings"), settingsProvider && aiModelConfig.providers[settingsProvider].map((model)=>/*#__PURE__*/ React.createElement("option", {
487
- key: model.value,
488
- value: model.value
489
- }, model.label)))))), /*#__PURE__*/ React.createElement("button", {
490
- className: styles.chatButton,
491
- disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading,
492
- onClick: ()=>void handleSubmit(),
493
- type: "button"
494
- }, /*#__PURE__*/ React.createElement(Send, {
495
- width: 14,
496
- height: 14
497
- }), isLoading ? "Sending..." : "Send")), /*#__PURE__*/ React.createElement(AIActionProposalList, {
498
- apiRoute: config.routes.api,
499
- appliedProposalIndexes: appliedProposalIndexes,
500
- description: response,
501
- error: error,
502
- getViewURL: getProposalViewURL,
503
- isApplying: isApplying,
504
- onDismiss: ()=>{
505
- setAppliedProposalIndexes([]);
506
- setError("");
507
- setProposals([]);
508
- setResponse("");
509
- clearInput();
510
- },
511
- onDismissError: ()=>{
512
- setError("");
513
- },
514
- onApply: (proposal, _index)=>void handleApplyProposal(proposal),
515
- proposals: proposals
516
- })), /*#__PURE__*/ React.createElement(RecentChangesList, {
517
- changes: appliedChanges
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;