@mvriu5/payload-ai 1.2.0 → 1.3.2

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 (51) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Icons.d.ts +0 -8
  3. package/dist/components/Icons.js +4 -216
  4. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +1 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +25 -34
  6. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  7. package/dist/components/ai-input/AIInput.js +433 -0
  8. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +111 -43
  9. package/dist/components/ai-input/badge.d.ts +14 -0
  10. package/dist/components/ai-input/badge.js +85 -0
  11. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  12. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
  13. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  14. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  15. package/dist/components/dashboard/Dashboard.js +15 -0
  16. package/dist/components/dashboard/Dashboard.module.css +13 -0
  17. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  18. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  19. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  20. package/dist/components/hooks/useAIChatStream.d.ts +39 -0
  21. package/dist/components/hooks/useAIChatStream.js +217 -0
  22. package/dist/components/hooks/useAISettings.js +26 -25
  23. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  24. package/dist/components/hooks/useAuditLog.js +41 -0
  25. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  26. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  27. package/dist/components/hooks/useMentions.d.ts +58 -0
  28. package/dist/components/hooks/useMentions.js +276 -0
  29. package/dist/components/hooks/usePluginConfig.d.ts +52 -0
  30. package/dist/components/hooks/usePluginConfig.js +20 -0
  31. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  32. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  33. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  34. package/dist/exports/client.d.ts +2 -2
  35. package/dist/exports/client.js +2 -2
  36. package/dist/handlers/applyActionHandler.js +27 -7
  37. package/dist/handlers/chatHandler.js +302 -58
  38. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  39. package/dist/handlers/mediaUploadHandler.js +99 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  41. package/dist/handlers/proposalDiffHandler.js +41 -29
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +39 -7
  44. package/dist/payload/collectionPermissions.js +3 -1
  45. package/dist/payload/normalizeData.d.ts +0 -6
  46. package/dist/payload/normalizeData.js +25 -13
  47. package/dist/payload/proposalData.js +4 -1
  48. package/dist/payload/schemaContext.js +98 -43
  49. package/package.json +6 -6
  50. package/dist/components/AIInput.js +0 -896
  51. /package/dist/components/{AIInput.d.ts → ai-input/AIInput.d.ts} +0 -0
@@ -0,0 +1,276 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { getSerializableLabel, isInternalCollection } from "../../payload/shared.js";
4
+ import { createBadgePrefix, getTextNodeAtOffset, replaceTextRangeWithBadge } from "../ai-input/badge.js";
5
+ import { useDocumentMentionSuggestions } from "../hooks/useDocumentMentionSuggestions.js";
6
+ const isMentionBoundary = (character)=>{
7
+ return character === undefined || /\s/.test(character);
8
+ };
9
+ const collectBlockOptions = ({ fields, parent })=>{
10
+ const options = [];
11
+ for (const field of fields){
12
+ if (!field.fields) continue;
13
+ options.push(...collectBlockOptions({
14
+ fields: field.fields,
15
+ parent
16
+ }));
17
+ }
18
+ return options;
19
+ };
20
+ const getActiveMentionRange = (valueBeforeCaret)=>{
21
+ const caretPosition = valueBeforeCaret.length;
22
+ for(let index = valueBeforeCaret.length - 1; index >= 0; index -= 1){
23
+ const character = valueBeforeCaret[index];
24
+ if (character === "@") {
25
+ const previousCharacter = valueBeforeCaret[index - 1];
26
+ const query = valueBeforeCaret.slice(index + 1);
27
+ if (!isMentionBoundary(previousCharacter)) return null;
28
+ if (!/^[\w-]*$/.test(query)) return null;
29
+ return {
30
+ query,
31
+ range: {
32
+ end: caretPosition,
33
+ start: index
34
+ }
35
+ };
36
+ }
37
+ if (isMentionBoundary(character)) break;
38
+ }
39
+ return null;
40
+ };
41
+ export const getTextBeforeCaret = (element)=>{
42
+ const selection = window.getSelection();
43
+ if (!selection || selection.rangeCount === 0) return "";
44
+ const range = selection.getRangeAt(0);
45
+ const clonedRange = range.cloneRange();
46
+ clonedRange.selectNodeContents(element);
47
+ clonedRange.setEnd(range.endContainer, range.endOffset);
48
+ return clonedRange.toString();
49
+ };
50
+ export const useMentions = ({ apiRoute, config, defaultLocale, editorRef, isCollectionMentionEnabled, locales, setPrompt, styles })=>{
51
+ const mentionsRef = useRef([]);
52
+ const [mentionQuery, setMentionQuery] = useState("");
53
+ const [mentionRange, setMentionRange] = useState(null);
54
+ const [mentionPopoverPosition, setMentionPopoverPosition] = useState(null);
55
+ const collections = useMemo(()=>config.collections.flatMap((collection)=>{
56
+ if (isInternalCollection(collection.slug) || !isCollectionMentionEnabled(collection.slug)) return [];
57
+ return [
58
+ {
59
+ label: getSerializableLabel(collection.labels?.singular, collection.slug),
60
+ slug: collection.slug,
61
+ type: "collection"
62
+ }
63
+ ];
64
+ }), [
65
+ config.collections,
66
+ isCollectionMentionEnabled
67
+ ]);
68
+ const globals = useMemo(()=>config.globals?.map((global)=>({
69
+ label: getSerializableLabel(global.label, global.slug),
70
+ slug: global.slug,
71
+ type: "global"
72
+ })) || [], [
73
+ config.globals
74
+ ]);
75
+ const blocks = useMemo(()=>[
76
+ ...config.collections.flatMap((collection)=>{
77
+ if (!isCollectionMentionEnabled(collection.slug)) return [];
78
+ return collectBlockOptions({
79
+ fields: collection.fields,
80
+ parent: collection.slug
81
+ });
82
+ }),
83
+ ...config.globals?.flatMap((global)=>collectBlockOptions({
84
+ fields: global.fields,
85
+ parent: global.slug
86
+ })) || []
87
+ ], [
88
+ config.collections,
89
+ config.globals,
90
+ isCollectionMentionEnabled
91
+ ]);
92
+ const localeOptions = useMemo(()=>locales.flatMap((locale)=>{
93
+ if (typeof locale === "string") {
94
+ return [
95
+ {
96
+ isDefault: locale === defaultLocale,
97
+ label: locale,
98
+ slug: locale,
99
+ type: "locale"
100
+ }
101
+ ];
102
+ }
103
+ if (!locale || typeof locale !== "object") return [];
104
+ const slug = typeof locale.code === "string" ? locale.code : null;
105
+ if (!slug) return [];
106
+ return [
107
+ {
108
+ isDefault: slug === defaultLocale,
109
+ label: getSerializableLabel(locale.label, slug),
110
+ slug,
111
+ type: "locale"
112
+ }
113
+ ];
114
+ }), [
115
+ defaultLocale,
116
+ locales
117
+ ]);
118
+ const mentionOptions = useMemo(()=>[
119
+ ...collections,
120
+ ...globals,
121
+ ...blocks,
122
+ ...localeOptions
123
+ ], [
124
+ blocks,
125
+ collections,
126
+ globals,
127
+ localeOptions
128
+ ]);
129
+ const normalizedMentionQuery = mentionQuery.toLowerCase();
130
+ const filteredCollections = useMemo(()=>collections.filter((collection)=>collection.slug.toLowerCase().includes(normalizedMentionQuery) || collection.label.toLowerCase().includes(normalizedMentionQuery)), [
131
+ collections,
132
+ normalizedMentionQuery
133
+ ]);
134
+ const filteredMentionOptions = useMemo(()=>mentionOptions.filter((option)=>option.slug.toLowerCase().includes(normalizedMentionQuery) || option.label.toLowerCase().includes(normalizedMentionQuery)), [
135
+ mentionOptions,
136
+ normalizedMentionQuery
137
+ ]);
138
+ const documentSuggestionCollection = filteredCollections.length === 1 ? filteredCollections[0]?.slug : null;
139
+ const { documentSuggestions, resetDocumentSuggestions } = useDocumentMentionSuggestions({
140
+ apiRoute,
141
+ documentSuggestionCollection,
142
+ mentionQuery,
143
+ mentionRange
144
+ });
145
+ const mentionSuggestions = useMemo(()=>[
146
+ ...filteredMentionOptions,
147
+ ...documentSuggestions
148
+ ], [
149
+ documentSuggestions,
150
+ filteredMentionOptions
151
+ ]);
152
+ const updateMentionPopoverPosition = useCallback((range)=>{
153
+ const editor = editorRef.current;
154
+ if (!editor || !range) {
155
+ setMentionPopoverPosition(null);
156
+ return;
157
+ }
158
+ const startPosition = getTextNodeAtOffset(editor, range.start);
159
+ const anchorRange = document.createRange();
160
+ anchorRange.setStart(startPosition.node, startPosition.offset);
161
+ anchorRange.collapse(true);
162
+ const marker = document.createElement("span");
163
+ marker.textContent = "\u200b";
164
+ anchorRange.insertNode(marker);
165
+ const rect = anchorRange.getBoundingClientRect();
166
+ marker.remove();
167
+ if (rect.left === 0 && rect.top === 0) {
168
+ setMentionPopoverPosition(null);
169
+ return;
170
+ }
171
+ setMentionPopoverPosition({
172
+ left: rect.left,
173
+ top: rect.bottom + 4
174
+ });
175
+ }, [
176
+ editorRef
177
+ ]);
178
+ const resetMentionState = useCallback(()=>{
179
+ setMentionQuery("");
180
+ setMentionRange(null);
181
+ setMentionPopoverPosition(null);
182
+ }, []);
183
+ const updateMentionState = useCallback((valueBeforeCaret)=>{
184
+ const activeMention = getActiveMentionRange(valueBeforeCaret);
185
+ if (!activeMention) {
186
+ resetMentionState();
187
+ return;
188
+ }
189
+ setMentionQuery(activeMention.query);
190
+ setMentionRange(activeMention.range);
191
+ updateMentionPopoverPosition(activeMention.range);
192
+ }, [
193
+ resetMentionState,
194
+ updateMentionPopoverPosition
195
+ ]);
196
+ useEffect(()=>{
197
+ if (!mentionRange) return;
198
+ const updatePosition = ()=>{
199
+ updateMentionPopoverPosition(mentionRange);
200
+ };
201
+ window.addEventListener("resize", updatePosition);
202
+ window.addEventListener("scroll", updatePosition, true);
203
+ return ()=>{
204
+ window.removeEventListener("resize", updatePosition);
205
+ window.removeEventListener("scroll", updatePosition, true);
206
+ };
207
+ }, [
208
+ mentionRange,
209
+ updateMentionPopoverPosition
210
+ ]);
211
+ const insertMention = useCallback((suggestion)=>{
212
+ const editor = editorRef.current;
213
+ if (!mentionRange || !editor) return;
214
+ const currentValue = editor.textContent || "";
215
+ const beforeMention = currentValue.slice(0, mentionRange.start);
216
+ const afterMention = currentValue.slice(mentionRange.end);
217
+ const badgeType = suggestion.type === "doc" ? "document" : suggestion.type;
218
+ const badgePrefix = `${badgeType}:`;
219
+ const badgeText = `${badgePrefix} ${suggestion.label}`;
220
+ const badge = document.createElement("span");
221
+ badge.className = [
222
+ styles.badge,
223
+ styles[suggestion.type],
224
+ styles.inlineBadge
225
+ ].join(" ");
226
+ badge.contentEditable = "false";
227
+ badge.append(createBadgePrefix(suggestion, styles), Object.assign(document.createElement("span"), {
228
+ className: styles.name,
229
+ textContent: suggestion.label
230
+ }));
231
+ const nextPrompt = `${beforeMention}${badgeText} ${afterMention}`;
232
+ replaceTextRangeWithBadge({
233
+ badge,
234
+ editor,
235
+ end: mentionRange.end,
236
+ start: mentionRange.start
237
+ });
238
+ editor.focus();
239
+ setPrompt(nextPrompt);
240
+ const mentionExists = mentionsRef.current.some((mention)=>mention.type === suggestion.type && mention.slug === suggestion.slug && mention.parent === suggestion.parent && mention.collection === suggestion.collection && mention.id === suggestion.id);
241
+ if (!mentionExists) {
242
+ mentionsRef.current = [
243
+ ...mentionsRef.current,
244
+ suggestion
245
+ ];
246
+ }
247
+ resetMentionState();
248
+ resetDocumentSuggestions();
249
+ }, [
250
+ editorRef,
251
+ mentionRange,
252
+ resetDocumentSuggestions,
253
+ resetMentionState,
254
+ setPrompt,
255
+ styles
256
+ ]);
257
+ const clearMentions = useCallback(()=>{
258
+ mentionsRef.current = [];
259
+ resetMentionState();
260
+ resetDocumentSuggestions();
261
+ }, [
262
+ resetDocumentSuggestions,
263
+ resetMentionState
264
+ ]);
265
+ return {
266
+ clearMentions,
267
+ getTextBeforeCaret,
268
+ insertMention,
269
+ mentionPopoverPosition,
270
+ mentionQuery,
271
+ mentionRange,
272
+ mentionSuggestions,
273
+ mentionsRef,
274
+ updateMentionState
275
+ };
276
+ };
@@ -0,0 +1,52 @@
1
+ type MediaConfig = {
2
+ acceptedMimeTypes?: string[];
3
+ collectionSlug: string;
4
+ enabled: boolean;
5
+ maxFileSize?: number;
6
+ };
7
+ type LocaleConfig = string | {
8
+ code?: string;
9
+ label?: unknown;
10
+ };
11
+ type LocalizationConfig = false | {
12
+ defaultLocale?: string;
13
+ locales?: LocaleConfig[];
14
+ };
15
+ export declare const usePluginConfig: (config: {
16
+ admin?: {
17
+ custom?: unknown;
18
+ };
19
+ localization?: LocalizationConfig;
20
+ }) => {
21
+ aiModelConfig: {
22
+ defaults: Record<"claude" | "google" | "mistral" | "openai" | "openrouter", string>;
23
+ providers: {
24
+ claude: {
25
+ label: string;
26
+ value: string;
27
+ }[];
28
+ google: {
29
+ label: string;
30
+ value: string;
31
+ }[];
32
+ mistral: {
33
+ label: string;
34
+ value: string;
35
+ }[];
36
+ openai: {
37
+ label: string;
38
+ value: string;
39
+ }[];
40
+ openrouter: {
41
+ label: string;
42
+ value: string;
43
+ }[];
44
+ };
45
+ };
46
+ defaultLocale: string | undefined;
47
+ enabledCollectionSlugSet: Set<string> | null;
48
+ isCollectionMentionEnabled: (slug: string) => boolean;
49
+ locales: LocaleConfig[];
50
+ media: MediaConfig | undefined;
51
+ };
52
+ export {};
@@ -0,0 +1,20 @@
1
+ import { useMemo } from "react";
2
+ import { getResolvedAIModelConfig } from "../../ai/providerOptions.js";
3
+ export const usePluginConfig = (config)=>{
4
+ const pluginConfig = config.admin?.custom?.payloadAiPlugin;
5
+ const aiModelConfig = useMemo(()=>getResolvedAIModelConfig(pluginConfig?.models), [
6
+ pluginConfig?.models
7
+ ]);
8
+ const enabledCollectionSlugSet = useMemo(()=>pluginConfig?.collectionSlugs ? new Set(pluginConfig.collectionSlugs) : null, [
9
+ pluginConfig?.collectionSlugs
10
+ ]);
11
+ const localization = config.localization && typeof config.localization === "object" ? config.localization : null;
12
+ return {
13
+ aiModelConfig,
14
+ defaultLocale: localization?.defaultLocale,
15
+ enabledCollectionSlugSet,
16
+ isCollectionMentionEnabled: (slug)=>!enabledCollectionSlugSet || enabledCollectionSlugSet.has(slug),
17
+ locales: localization?.locales ?? [],
18
+ media: pluginConfig?.media
19
+ };
20
+ };
@@ -1,5 +1,4 @@
1
1
  import type { CSSProperties } from "react";
2
- import type { RefObject } from "react";
3
2
  export type MentionOption = {
4
3
  collection?: string;
5
4
  id?: string;
@@ -7,13 +6,12 @@ export type MentionOption = {
7
6
  label: string;
8
7
  parent?: string;
9
8
  slug: string;
10
- type: "block" | "collection" | "doc" | "global" | "locale";
9
+ type: "collection" | "doc" | "global" | "locale";
11
10
  };
12
11
  type MentionPopoverProps = {
13
- containerRef?: RefObject<HTMLDivElement | null>;
14
12
  suggestions: MentionOption[];
15
13
  onSelect: (suggestion: MentionOption) => void;
16
14
  style?: CSSProperties;
17
15
  };
18
- export declare const MentionPopover: ({ containerRef, onSelect, style, suggestions }: MentionPopoverProps) => import("react").JSX.Element | null;
16
+ export declare const MentionPopover: ({ onSelect, style, suggestions }: MentionPopoverProps) => import("react").JSX.Element | null;
19
17
  export {};
@@ -13,10 +13,6 @@ const suggestionGroups = [
13
13
  label: "Globals",
14
14
  type: "global"
15
15
  },
16
- {
17
- label: "Blocks",
18
- type: "block"
19
- },
20
16
  {
21
17
  label: "Locales",
22
18
  type: "locale"
@@ -25,18 +21,16 @@ const suggestionGroups = [
25
21
  const getSuggestionTitle = (suggestion)=>{
26
22
  if (suggestion.type === "collection") return `@${suggestion.slug}`;
27
23
  if (suggestion.type === "global") return `@${suggestion.slug}`;
28
- if (suggestion.type === "block") return `@${suggestion.slug}`;
29
24
  if (suggestion.type === "locale") return `@${suggestion.slug}`;
30
25
  return suggestion.label;
31
26
  };
32
27
  const getSuggestionLabel = (suggestion)=>{
33
28
  if (suggestion.type === "collection") return suggestion.label;
34
29
  if (suggestion.type === "global") return "global";
35
- if (suggestion.type === "block") return `${suggestion.parent} block`;
36
30
  if (suggestion.type === "locale") return suggestion.isDefault ? "default locale" : "locale";
37
31
  return `${suggestion.collection} item`;
38
32
  };
39
- export const MentionPopover = ({ containerRef, onSelect, style, suggestions })=>{
33
+ export const MentionPopover = ({ onSelect, style, suggestions })=>{
40
34
  if (suggestions.length === 0) return null;
41
35
  const onKeyDown = (event, suggestion)=>{
42
36
  if (event.key === "Enter" || event.key === " ") {
@@ -60,7 +54,6 @@ export const MentionPopover = ({ containerRef, onSelect, style, suggestions })=>
60
54
  };
61
55
  return /*#__PURE__*/ _jsx("div", {
62
56
  className: styles.popover,
63
- ref: containerRef,
64
57
  style: style,
65
58
  children: suggestionGroups.map((group)=>{
66
59
  const groupSuggestions = suggestions.filter((s)=>s.type === group.type);
@@ -8,7 +8,7 @@
8
8
  min-width: 260px;
9
9
  overflow: auto;
10
10
  padding: 6px;
11
- position: absolute;
11
+ position: fixed;
12
12
  z-index: 10;
13
13
  }
14
14
 
@@ -1,4 +1,4 @@
1
- import AIInput from "../components/AIInput.js";
1
+ import Dashboard from "../components/dashboard/Dashboard.js";
2
2
  import APIKeyField from "../components/APIKeyField.js";
3
- export { AIInput };
3
+ export { Dashboard };
4
4
  export { APIKeyField as AIApiKeyField };
@@ -1,4 +1,4 @@
1
- import AIInput from "../components/AIInput.js";
1
+ import Dashboard from "../components/dashboard/Dashboard.js";
2
2
  import APIKeyField from "../components/APIKeyField.js";
3
- export { AIInput };
3
+ export { Dashboard };
4
4
  export { APIKeyField as AIApiKeyField };
@@ -342,7 +342,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
342
342
  req,
343
343
  slug: proposal.slug
344
344
  }) : null;
345
- for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
345
+ const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
346
346
  const before = await req.payload.findGlobal({
347
347
  depth: 2,
348
348
  fallbackLocale: false,
@@ -362,9 +362,16 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
362
362
  req,
363
363
  slug: proposal.slug
364
364
  });
365
+ return {
366
+ before,
367
+ completedData,
368
+ locale
369
+ };
370
+ }));
371
+ localizedResults.forEach(({ before, completedData, locale })=>{
365
372
  beforeByLocale[locale] = before;
366
373
  afterByLocale[locale] = mergeData(before, completedData);
367
- }
374
+ });
368
375
  const change = await logAIChange({
369
376
  changeLogCollection: options.changeLogCollection,
370
377
  context: logContext,
@@ -546,8 +553,8 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
546
553
  const afterByLocale = {
547
554
  [firstLocale]: doc
548
555
  };
549
- let fallbackSource = doc;
550
- for (const [locale, localeData] of localeEntries.slice(1)){
556
+ const fallbackSource = doc;
557
+ const localizedResults = await Promise.all(localeEntries.slice(1).map(async ([locale, localeData])=>{
551
558
  const completedData = applyLocalizedRequiredFallbackToPreparedData({
552
559
  fallbackSource,
553
560
  fields: collectionFields,
@@ -561,9 +568,15 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
561
568
  overrideAccess: false,
562
569
  req
563
570
  });
571
+ return {
572
+ completedData,
573
+ locale
574
+ };
575
+ }));
576
+ localizedResults.forEach(({ completedData, locale })=>{
564
577
  beforeByLocale[locale] = {};
565
578
  afterByLocale[locale] = completedData;
566
- }
579
+ });
567
580
  const change = await logAIChange({
568
581
  changeLogCollection: options.changeLogCollection,
569
582
  context: logContext,
@@ -598,7 +611,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
598
611
  locale: defaultLocale,
599
612
  req
600
613
  }) : null;
601
- for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
614
+ const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
602
615
  const before = await req.payload.findByID({
603
616
  collection: proposal.collection,
604
617
  depth: 2,
@@ -620,9 +633,16 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
620
633
  overrideAccess: false,
621
634
  req
622
635
  });
636
+ return {
637
+ before,
638
+ completedData,
639
+ locale
640
+ };
641
+ }));
642
+ localizedResults.forEach(({ before, completedData, locale })=>{
623
643
  beforeByLocale[locale] = before;
624
644
  afterByLocale[locale] = mergeData(before, completedData);
625
- }
645
+ });
626
646
  const change = await logAIChange({
627
647
  changeLogCollection: options.changeLogCollection,
628
648
  context: logContext,