@alpaca-editor/core 1.0.3997 → 1.0.4007

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 (171) hide show
  1. package/dist/client-components/index.d.ts +1 -0
  2. package/dist/client-components/index.js +1 -0
  3. package/dist/client-components/index.js.map +1 -1
  4. package/dist/components/ui/calendar.d.ts +7 -0
  5. package/dist/components/ui/calendar.js +62 -0
  6. package/dist/components/ui/calendar.js.map +1 -0
  7. package/dist/config/config.js +11 -0
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/editor/ContentTree.d.ts +2 -1
  10. package/dist/editor/ContentTree.js +2 -2
  11. package/dist/editor/ContentTree.js.map +1 -1
  12. package/dist/editor/FieldList.js +37 -10
  13. package/dist/editor/FieldList.js.map +1 -1
  14. package/dist/editor/FieldListField.js +21 -10
  15. package/dist/editor/FieldListField.js.map +1 -1
  16. package/dist/editor/client/EditorClient.js +5 -3
  17. package/dist/editor/client/EditorClient.js.map +1 -1
  18. package/dist/editor/client/itemsRepository.js +57 -9
  19. package/dist/editor/client/itemsRepository.js.map +1 -1
  20. package/dist/editor/client/pageModelBuilder.js +1 -1
  21. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  22. package/dist/editor/commands/itemCommands.js +1 -1
  23. package/dist/editor/commands/itemCommands.js.map +1 -1
  24. package/dist/editor/field-types/DateFieldEditor.d.ts +5 -0
  25. package/dist/editor/field-types/DateFieldEditor.js +93 -0
  26. package/dist/editor/field-types/DateFieldEditor.js.map +1 -0
  27. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  28. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  29. package/dist/editor/field-types/InternalLinkFieldEditor.js +29 -9
  30. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  31. package/dist/editor/field-types/LinkFieldEditor.js +1 -1
  32. package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
  33. package/dist/editor/field-types/MultiLineText.js +3 -1
  34. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  35. package/dist/editor/field-types/PictureFieldEditor.js +1 -1
  36. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  37. package/dist/editor/field-types/SingleLineText.js +3 -1
  38. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  39. package/dist/editor/field-types/TreeListEditor.js +90 -17
  40. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  41. package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +3 -3
  42. package/dist/editor/field-types/richtext/components/ReactSlate.js +195 -172
  43. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  44. package/dist/editor/fieldTypes.d.ts +1 -1
  45. package/dist/editor/media-selector/AiImageSearch.js +2 -1
  46. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  47. package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -1
  48. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  49. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +1 -1
  50. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  51. package/dist/editor/page-viewer/DeviceToolbar.js +3 -2
  52. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  53. package/dist/editor/page-viewer/MiniMap.js +6 -1
  54. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  55. package/dist/editor/services/aiService.d.ts +9 -1
  56. package/dist/editor/services/aiService.js.map +1 -1
  57. package/dist/editor/sidebar/Insert.js +1 -1
  58. package/dist/editor/sidebar/Insert.js.map +1 -1
  59. package/dist/editor/sidebar/MainContentTree.d.ts +1 -1
  60. package/dist/editor/sidebar/MainContentTree.js +19 -7
  61. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  62. package/dist/editor/ui/Icons.js +1 -1
  63. package/dist/editor/ui/Icons.js.map +1 -1
  64. package/dist/editor/ui/ItemNameDialogNew.js +14 -10
  65. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  66. package/dist/editor/ui/PerfectTree.d.ts +2 -0
  67. package/dist/editor/ui/PerfectTree.js +1 -1
  68. package/dist/editor/ui/PerfectTree.js.map +1 -1
  69. package/dist/editor/ui/Spinner.js +1 -1
  70. package/dist/editor/ui/Spinner.js.map +1 -1
  71. package/dist/editor/utils.d.ts +1 -0
  72. package/dist/editor/utils.js +3 -0
  73. package/dist/editor/utils.js.map +1 -1
  74. package/dist/editor/views/CompareView.js.map +1 -1
  75. package/dist/page-wizard/PageWizard.d.ts +5 -1
  76. package/dist/page-wizard/PageWizard.js.map +1 -1
  77. package/dist/page-wizard/WizardSteps.js +1 -0
  78. package/dist/page-wizard/WizardSteps.js.map +1 -1
  79. package/dist/page-wizard/steps/ComponentTypesSelector.d.ts +2 -1
  80. package/dist/page-wizard/steps/ComponentTypesSelector.js +27 -90
  81. package/dist/page-wizard/steps/ComponentTypesSelector.js.map +1 -1
  82. package/dist/page-wizard/steps/ContentStep.js +118 -27
  83. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  84. package/dist/page-wizard/steps/FindItemsStep.d.ts +2 -0
  85. package/dist/page-wizard/steps/FindItemsStep.js +293 -0
  86. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -0
  87. package/dist/page-wizard/steps/ImagesStep.js +9 -1
  88. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  89. package/dist/page-wizard/steps/LayoutStep.js +24 -14
  90. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  91. package/dist/page-wizard/steps/MetaDataStep.js +21 -11
  92. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  93. package/dist/page-wizard/steps/SelectStep.js +38 -31
  94. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  95. package/dist/page-wizard/steps/StructureStep.d.ts +2 -0
  96. package/dist/page-wizard/steps/StructureStep.js +156 -0
  97. package/dist/page-wizard/steps/StructureStep.js.map +1 -0
  98. package/dist/page-wizard/steps/schema.js +4 -4
  99. package/dist/page-wizard/steps/schema.js.map +1 -1
  100. package/dist/page-wizard/steps/usePageCreator.d.ts +2 -1
  101. package/dist/page-wizard/steps/usePageCreator.js +62 -8
  102. package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
  103. package/dist/page-wizard/usePageWizard.js +2 -0
  104. package/dist/page-wizard/usePageWizard.js.map +1 -1
  105. package/dist/page-wizard/utils/dataAccessor.d.ts +39 -0
  106. package/dist/page-wizard/utils/dataAccessor.js +222 -0
  107. package/dist/page-wizard/utils/dataAccessor.js.map +1 -0
  108. package/dist/revision.d.ts +2 -2
  109. package/dist/revision.js +2 -2
  110. package/dist/splash-screen/RecentPages.js +14 -3
  111. package/dist/splash-screen/RecentPages.js.map +1 -1
  112. package/dist/styles.css +180 -2
  113. package/package.json +3 -1
  114. package/src/client-components/index.ts +1 -3
  115. package/src/components/ui/calendar.tsx +175 -0
  116. package/src/config/config.tsx +11 -1
  117. package/src/editor/ContentTree.tsx +4 -1
  118. package/src/editor/FieldList.tsx +44 -18
  119. package/src/editor/FieldListField.tsx +98 -23
  120. package/src/editor/client/EditorClient.tsx +6 -3
  121. package/src/editor/client/itemsRepository.ts +74 -10
  122. package/src/editor/client/pageModelBuilder.ts +1 -1
  123. package/src/editor/commands/itemCommands.tsx +2 -3
  124. package/src/editor/field-types/DateFieldEditor.tsx +132 -0
  125. package/src/editor/field-types/ImageFieldEditor.tsx +1 -1
  126. package/src/editor/field-types/InternalLinkFieldEditor.tsx +99 -73
  127. package/src/editor/field-types/LinkFieldEditor.tsx +2 -2
  128. package/src/editor/field-types/MultiLineText.tsx +5 -1
  129. package/src/editor/field-types/PictureFieldEditor.tsx +1 -1
  130. package/src/editor/field-types/SingleLineText.tsx +5 -1
  131. package/src/editor/field-types/TreeListEditor.tsx +149 -56
  132. package/src/editor/field-types/richtext/components/ReactSlate.tsx +537 -446
  133. package/src/editor/fieldTypes.ts +1 -1
  134. package/src/editor/media-selector/AiImageSearch.tsx +2 -1
  135. package/src/editor/media-selector/AiImageSearchPrompt.tsx +2 -1
  136. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
  137. package/src/editor/page-viewer/DeviceToolbar.tsx +17 -5
  138. package/src/editor/page-viewer/MiniMap.tsx +7 -2
  139. package/src/editor/services/aiService.ts +10 -1
  140. package/src/editor/sidebar/Insert.tsx +1 -1
  141. package/src/editor/sidebar/MainContentTree.tsx +27 -14
  142. package/src/editor/ui/Icons.tsx +16 -5
  143. package/src/editor/ui/ItemNameDialogNew.tsx +42 -30
  144. package/src/editor/ui/PerfectTree.tsx +3 -1
  145. package/src/editor/ui/Spinner.tsx +3 -1
  146. package/src/editor/utils.ts +4 -0
  147. package/src/editor/views/CompareView.tsx +1 -1
  148. package/src/page-wizard/PageWizard.tsx +5 -1
  149. package/src/page-wizard/WizardSteps.tsx +1 -0
  150. package/src/page-wizard/steps/ComponentTypesSelector.tsx +34 -115
  151. package/src/page-wizard/steps/ContentStep.tsx +149 -34
  152. package/src/page-wizard/steps/FindItemsStep.tsx +546 -0
  153. package/src/page-wizard/steps/ImagesStep.tsx +13 -1
  154. package/src/page-wizard/steps/LayoutStep.tsx +27 -16
  155. package/src/page-wizard/steps/MetaDataStep.tsx +26 -13
  156. package/src/page-wizard/steps/SelectStep.tsx +61 -32
  157. package/src/page-wizard/steps/StructureStep.tsx +350 -0
  158. package/src/page-wizard/steps/schema.ts +4 -4
  159. package/src/page-wizard/steps/usePageCreator.ts +84 -9
  160. package/src/page-wizard/usePageWizard.ts +2 -0
  161. package/src/page-wizard/utils/dataAccessor.ts +275 -0
  162. package/src/revision.ts +2 -2
  163. package/src/splash-screen/RecentPages.tsx +22 -3
  164. package/dist/editor/ai/GhostWriter.d.ts +0 -1
  165. package/dist/editor/ai/GhostWriter.js +0 -347
  166. package/dist/editor/ai/GhostWriter.js.map +0 -1
  167. package/dist/editor/component-designer/ComponentDesignerAiTerminal.d.ts +0 -1
  168. package/dist/editor/component-designer/ComponentDesignerAiTerminal.js +0 -7
  169. package/dist/editor/component-designer/ComponentDesignerAiTerminal.js.map +0 -1
  170. /package/src/editor/ai/{GhostWriter.tsx → GhostWriter.tsx_} +0 -0
  171. /package/src/editor/component-designer/{ComponentDesignerAiTerminal.tsx → ComponentDesignerAiTerminal.tsx_} +0 -0
@@ -0,0 +1,546 @@
1
+ import { useState, useEffect } from "react";
2
+ import { StepComponentProps } from "../../config/types";
3
+ import { useEditContext } from "../../editor/client/editContext";
4
+ import ContentTree from "../../editor/ContentTree";
5
+ import { Button } from "../../components/ui/button";
6
+ import { normalizeGuid } from "../../editor/utils";
7
+ import {
8
+ ItemTreeNodeData,
9
+ fetchItemStubs,
10
+ } from "../../editor/services/contentService";
11
+ import { ItemDescriptor } from "../../editor/pageModel";
12
+ import { evaluateDataExpression, setDataProperty } from "../utils/dataAccessor";
13
+ import { executePrompt } from "../../editor/services/aiService";
14
+ import { createWizardAiContext } from "../service";
15
+ import { ActionButton } from "../../components/ActionButton";
16
+ import Generate from "./Generate";
17
+ import { Sparkles } from "lucide-react";
18
+
19
+ export function FindItemsStep({
20
+ step,
21
+ data,
22
+ setData,
23
+ setStepCompleted,
24
+ internalState,
25
+ setInternalState,
26
+ }: StepComponentProps) {
27
+ const editContext = useEditContext();
28
+ const [selectedItems, setSelectedItems] = useState<ItemTreeNodeData[]>([]);
29
+ const [rootItemIds, setRootItemIds] = useState<string[]>([]);
30
+ const [selectedValues, setSelectedValues] = useState<string[]>([]);
31
+ const [aiSuggestions, setAiSuggestions] = useState<string[]>([]);
32
+ const [aiSuggestedItems, setAiSuggestedItems] = useState<ItemTreeNodeData[]>(
33
+ [],
34
+ );
35
+ const [loadingAI, setLoadingAI] = useState(false);
36
+ const [loadingAIItems, setLoadingAIItems] = useState(false);
37
+ const [aiError, setAiError] = useState<string | null>(null);
38
+ const [hasInputData, setHasInputData] = useState(false);
39
+
40
+ // Get configuration from step fields
41
+ const sources = step.fields?.sources || "";
42
+ const multiSelect = step.fields?.multiSelect === "1" ? true : false;
43
+ const inputProperties =
44
+ (typeof step.fields?.inputProperties === "string"
45
+ ? step.fields.inputProperties
46
+ : null) ||
47
+ (typeof step.fields?.propertyName === "string"
48
+ ? step.fields.propertyName
49
+ : null) ||
50
+ "selectedItems";
51
+ const propertyName =
52
+ (typeof step.fields?.propertyName === "string"
53
+ ? step.fields.propertyName
54
+ : null) || "selectedItems";
55
+ const displayProperty = step.fields?.displayProperty || "selectedItemIds";
56
+
57
+ useEffect(() => {
58
+ // Initialize root item IDs from sources configuration
59
+ if (typeof sources === "string") {
60
+ const rootIds = sources.split("|").map((source: string) => {
61
+ return normalizeGuid(source);
62
+ });
63
+ setRootItemIds(rootIds);
64
+ } else {
65
+ // Default root if no sources specified
66
+ setRootItemIds(["{11111111-1111-1111-1111-111111111111}"]);
67
+ }
68
+ }, [sources]);
69
+
70
+ useEffect(() => {
71
+ // Only restore selectedItems from internalState (user's manual selections)
72
+ const stepKey = `selectedItems_${step.id}`;
73
+ const storedItems = internalState?.[stepKey];
74
+ if (storedItems && Array.isArray(storedItems)) {
75
+ setSelectedItems(storedItems);
76
+ // Also restore selected values from stored items
77
+ const storedIds = storedItems.map((item) => item.id);
78
+ setSelectedValues(storedIds);
79
+ }
80
+ }, [data, internalState, step.id]);
81
+
82
+ useEffect(() => {
83
+ // Mark step as completed if we have selections (for multi-select) or exactly one (for single-select)
84
+ const hasValidSelection = multiSelect
85
+ ? selectedValues.length > 0
86
+ : selectedValues.length === 1;
87
+
88
+ setStepCompleted(hasValidSelection);
89
+ }, [selectedValues, multiSelect, setStepCompleted]);
90
+
91
+ useEffect(() => {
92
+ // Check if there's meaningful input data that could benefit from AI suggestions
93
+ const hasData = data && Object.keys(data).length > 0;
94
+ const hasTextualData = Object.values(data || {}).some(
95
+ (value) => typeof value === "string" && value.trim().length > 0,
96
+ );
97
+ setHasInputData(hasData && hasTextualData);
98
+ }, [data]);
99
+
100
+ useEffect(() => {
101
+ // Auto-generate AI suggestions when the step opens and has meaningful input data
102
+ if (
103
+ hasInputData &&
104
+ step.fields?.systemInstructions &&
105
+ selectedItems.length === 0
106
+ ) {
107
+ generateAISuggestions();
108
+ }
109
+ }, [hasInputData, step.fields?.systemInstructions]);
110
+
111
+ // Fetch item details from AI-suggested IDs
112
+ const fetchAISuggestedItems = async (itemIds: string[]) => {
113
+ if (
114
+ !editContext ||
115
+ !editContext.currentItemDescriptor?.language ||
116
+ itemIds.length === 0
117
+ ) {
118
+ return;
119
+ }
120
+
121
+ setLoadingAIItems(true);
122
+ try {
123
+ // Convert item IDs to ItemDescriptors
124
+ const itemDescriptors: ItemDescriptor[] = itemIds.map((id) => ({
125
+ id: normalizeGuid(id),
126
+ language: editContext.currentItemDescriptor!.language,
127
+ version: 0, // Use latest version
128
+ }));
129
+
130
+ // Fetch item stubs to get names and display data
131
+ const itemStubs = await fetchItemStubs(itemDescriptors);
132
+
133
+ if (itemStubs) {
134
+ // Convert ItemStubs to ItemTreeNodeData format
135
+ const suggestedItems: ItemTreeNodeData[] = itemStubs.map((stub) => ({
136
+ id: stub.id,
137
+ name: stub.name || stub.id,
138
+ displayName: stub.name,
139
+ templateId: stub.templateId || "",
140
+ icon: stub.icon || "",
141
+ hasChildren: false,
142
+ isComponent: false,
143
+ thumbUrl: "",
144
+ previewUrl: "",
145
+ language: stub.language,
146
+ version: stub.version,
147
+ hasLayout: false,
148
+ }));
149
+
150
+ setAiSuggestedItems(suggestedItems);
151
+
152
+ // Auto-apply suggestions
153
+ const itemsToApply = multiSelect
154
+ ? suggestedItems
155
+ : suggestedItems.slice(0, 1);
156
+
157
+ if (itemsToApply.length > 0) {
158
+ handleSelectionChange(itemsToApply);
159
+
160
+ // Clear suggestions after applying them
161
+ setTimeout(() => {
162
+ setAiSuggestions([]);
163
+ setAiSuggestedItems([]);
164
+ }, 2000); // Show the "applied" message for 2 seconds before clearing
165
+ }
166
+ }
167
+ } catch (error) {
168
+ console.error("Error fetching AI suggested items:", error);
169
+ setAiError("Failed to load item details for suggestions.");
170
+ } finally {
171
+ setLoadingAIItems(false);
172
+ }
173
+ };
174
+
175
+ // Generate AI suggestions for item selection
176
+ const generateAISuggestions = async () => {
177
+ if (!editContext) {
178
+ setAiError("No edit context defined.");
179
+ return;
180
+ }
181
+ if (!step.fields?.systemInstructions) {
182
+ setAiError("No system instructions defined in step configuration.");
183
+ return;
184
+ }
185
+
186
+ setLoadingAI(true);
187
+ setAiError(null);
188
+
189
+ // Evaluate only the input properties that should be included
190
+ const evaluatedInputData = evaluateDataExpression(inputProperties, data);
191
+
192
+ const basePrompt = `
193
+ ${step.fields.systemInstructions}
194
+
195
+ You need to suggest relevant content items from a content tree. Here are the details:
196
+
197
+ Input Data: ${JSON.stringify(evaluatedInputData)}
198
+ Source Root IDs: ${JSON.stringify(rootItemIds)}
199
+ Multi-select: ${multiSelect}
200
+ Max selections needed: ${multiSelect ? "multiple" : "1"}
201
+
202
+ You can use the get-children function to traverse the content tree and find relevant items.
203
+ Start from the root IDs provided and explore the tree to find items that match the input data.
204
+
205
+ Return a JSON object with suggested item IDs in this format:
206
+ {
207
+ "suggestions": ["item-id-1", "item-id-2", ...],
208
+ "reasoning": "Brief explanation of why these items were selected"
209
+ } Do not add comments to the JSON.`;
210
+
211
+ // Log input data for AI processing but don't auto-populate selections
212
+ console.log(
213
+ "INPUT DATA FOR AI PROCESSING: ",
214
+ evaluatedInputData,
215
+ basePrompt,
216
+ );
217
+
218
+ try {
219
+ const result = await executePrompt(
220
+ [
221
+ {
222
+ content: `You are an expert content curator helping to find relevant items from a content tree. You can traverse the tree using the get-children function. Return ONLY a valid JSON object with suggestions and reasoning.`,
223
+ name: "system",
224
+ role: "system",
225
+ },
226
+ {
227
+ content: basePrompt,
228
+ name: "user",
229
+ role: "user",
230
+ },
231
+ ],
232
+ editContext,
233
+ createWizardAiContext,
234
+ { allowedFunctions: ["get-children"] },
235
+ undefined,
236
+ step.fields.aiModel || "gpt-4.1",
237
+ );
238
+
239
+ console.log("AI RESULT: ", result);
240
+
241
+ // Parse the result and set suggestions
242
+ if (result && result.messages && result.messages.length > 0) {
243
+ try {
244
+ const lastMessage = result.messages[result.messages.length - 1];
245
+ if (lastMessage && lastMessage.content) {
246
+ const suggestions = JSON.parse(lastMessage.content) as {
247
+ suggestions: string[];
248
+ reasoning?: string;
249
+ };
250
+
251
+ const suggestionIds = suggestions.suggestions || [];
252
+ setAiSuggestions(suggestionIds);
253
+ setAiError(null); // Clear any previous errors
254
+ console.log("AI Reasoning:", suggestions.reasoning);
255
+
256
+ // Fetch full item details for the suggestions
257
+ await fetchAISuggestedItems(suggestionIds);
258
+ }
259
+ } catch (parseError) {
260
+ console.error("Error parsing AI suggestions:", parseError);
261
+ setAiError(
262
+ "Failed to parse AI suggestions. The response was not in the expected format.",
263
+ );
264
+ }
265
+ } else {
266
+ setAiError("No suggestions were generated. Please try again.");
267
+ }
268
+ } catch (err) {
269
+ setAiError("Failed to generate suggestions. Please try again.");
270
+ console.error("Error generating AI suggestions:", err);
271
+ } finally {
272
+ setLoadingAI(false);
273
+ }
274
+ };
275
+
276
+ const handleSelectionChange = (selectedNodes: ItemTreeNodeData[]) => {
277
+ const nodes = selectedNodes || [];
278
+ setSelectedItems(nodes);
279
+
280
+ // Store selectedItems in internalState for persistence using step-specific key
281
+ const stepKey = `selectedItems_${step.id}`;
282
+ setInternalState((prev: any) => ({
283
+ ...prev,
284
+ [stepKey]: nodes,
285
+ }));
286
+
287
+ let newValues: string[];
288
+ if (multiSelect) {
289
+ // For multi-select, keep all selected items
290
+ newValues = nodes.map((node) => node.id);
291
+ } else {
292
+ // For single-select, keep only the last selected item
293
+ const lastNode = nodes[nodes.length - 1];
294
+ newValues = lastNode ? [lastNode.id] : [];
295
+ }
296
+
297
+ setSelectedValues(newValues);
298
+
299
+ // Update data with selected item IDs
300
+ setData((prevData) => {
301
+ const updatedData = setDataProperty(
302
+ propertyName,
303
+ multiSelect ? newValues : newValues[0] || null,
304
+ prevData,
305
+ );
306
+ return setDataProperty(displayProperty, newValues, updatedData);
307
+ });
308
+ };
309
+
310
+ const handleAddItem = (node: ItemTreeNodeData) => {
311
+ if (multiSelect) {
312
+ // Check if item is already selected
313
+ const isAlreadySelected = selectedItems.some(
314
+ (item) => item.id === node.id,
315
+ );
316
+ if (!isAlreadySelected) {
317
+ const newItems = [...selectedItems, node];
318
+ handleSelectionChange(newItems);
319
+ }
320
+ } else {
321
+ // For single select, replace the selection
322
+ handleSelectionChange([node]);
323
+ }
324
+ };
325
+
326
+ const handleRemoveItem = (itemId: string) => {
327
+ const newItems = selectedItems.filter((item) => item.id !== itemId);
328
+ handleSelectionChange(newItems);
329
+ };
330
+
331
+ const handleClearSelection = () => {
332
+ setSelectedItems([]);
333
+ setSelectedValues([]);
334
+
335
+ // Clear from internalState too using step-specific key
336
+ const stepKey = `selectedItems_${step.id}`;
337
+ setInternalState((prev: any) => ({
338
+ ...prev,
339
+ [stepKey]: [],
340
+ }));
341
+
342
+ setData((prevData) => {
343
+ const updatedData = setDataProperty(
344
+ propertyName,
345
+ multiSelect ? [] : null,
346
+ prevData,
347
+ );
348
+ return setDataProperty(displayProperty, [], updatedData);
349
+ });
350
+ };
351
+
352
+ if (!editContext) {
353
+ return <div>Loading...</div>;
354
+ }
355
+
356
+ if (loadingAI) {
357
+ return (
358
+ <Generate title="AI is analyzing content and finding suggestions..." />
359
+ );
360
+ }
361
+
362
+ if (loadingAIItems) {
363
+ return <Generate title="Loading item details for AI suggestions..." />;
364
+ }
365
+
366
+ return (
367
+ <div className="flex h-full flex-col gap-4">
368
+ <div className="flex-shrink-0">
369
+ <div className="flex items-center justify-end gap-2">
370
+ <span className="text-sm text-gray-600">
371
+ {multiSelect ? "Multi-select enabled" : "Single selection"}
372
+ </span>
373
+ {selectedValues.length > 0 && (
374
+ <Button variant="outline" size="sm" onClick={handleClearSelection}>
375
+ Clear Selection
376
+ </Button>
377
+ )}
378
+ {hasInputData && step.fields?.systemInstructions && (
379
+ <ActionButton onClick={generateAISuggestions} disabled={loadingAI}>
380
+ <div className="flex items-center gap-2">
381
+ <Sparkles className="h-4 w-4" />
382
+ Get AI Suggestions
383
+ </div>
384
+ </ActionButton>
385
+ )}
386
+ </div>
387
+
388
+ {selectedValues.length > 0 && (
389
+ <div className="mt-4 rounded-md bg-blue-50 p-3">
390
+ <h4 className="text-sm font-medium text-blue-900">
391
+ Selected Items ({selectedValues.length}):
392
+ </h4>
393
+ <div className="mt-2 flex flex-wrap gap-2">
394
+ {selectedItems.length > 0
395
+ ? selectedItems.map((item) => (
396
+ <span
397
+ key={item.id}
398
+ className="inline-flex items-center gap-1 rounded-md bg-blue-100 px-2 py-1 text-xs text-blue-800"
399
+ >
400
+ {item.icon && (
401
+ <img src={item.icon} alt="" className="h-3 w-3" />
402
+ )}
403
+ {item.name}
404
+ {multiSelect && (
405
+ <button
406
+ onClick={() => handleRemoveItem(item.id)}
407
+ className="ml-1 rounded-full p-0.5 hover:bg-blue-200 focus:ring-1 focus:ring-blue-500 focus:outline-none"
408
+ title="Remove item"
409
+ >
410
+ <svg
411
+ className="h-3 w-3"
412
+ fill="none"
413
+ stroke="currentColor"
414
+ viewBox="0 0 24 24"
415
+ >
416
+ <path
417
+ strokeLinecap="round"
418
+ strokeLinejoin="round"
419
+ strokeWidth={2}
420
+ d="M6 18L18 6M6 6l12 12"
421
+ />
422
+ </svg>
423
+ </button>
424
+ )}
425
+ </span>
426
+ ))
427
+ : // Fallback to show IDs if full items aren't loaded yet
428
+ selectedValues.map((id) => (
429
+ <span
430
+ key={typeof id === "string" ? id : JSON.stringify(id)}
431
+ className="inline-flex items-center gap-1 rounded-md bg-blue-100 px-2 py-1 text-xs text-blue-800"
432
+ >
433
+ {typeof id === "string" ? id : JSON.stringify(id)}
434
+ {multiSelect && (
435
+ <button
436
+ onClick={() => handleRemoveItem(id)}
437
+ className="ml-1 rounded-full p-0.5 hover:bg-blue-200 focus:ring-1 focus:ring-blue-500 focus:outline-none"
438
+ title="Remove item"
439
+ >
440
+ <svg
441
+ className="h-3 w-3"
442
+ fill="none"
443
+ stroke="currentColor"
444
+ viewBox="0 0 24 24"
445
+ >
446
+ <path
447
+ strokeLinecap="round"
448
+ strokeLinejoin="round"
449
+ strokeWidth={2}
450
+ d="M6 18L18 6M6 6l12 12"
451
+ />
452
+ </svg>
453
+ </button>
454
+ )}
455
+ </span>
456
+ ))}
457
+ </div>
458
+ </div>
459
+ )}
460
+
461
+ {/* AI Error Message */}
462
+ {aiError && (
463
+ <div className="rounded-md bg-red-50 p-3 text-sm text-red-700">
464
+ {aiError}
465
+ </div>
466
+ )}
467
+
468
+ {/* AI Suggestions Applied */}
469
+ {(aiSuggestions.length > 0 || aiSuggestedItems.length > 0) && (
470
+ <div className="rounded-md bg-green-50 p-3">
471
+ <h4 className="text-sm font-medium text-green-900">
472
+ AI Suggestions Applied (
473
+ {Math.max(aiSuggestions.length, aiSuggestedItems.length)}):
474
+ </h4>
475
+ <div className="mt-2 flex flex-wrap gap-2">
476
+ {aiSuggestedItems.length > 0
477
+ ? aiSuggestedItems.map((item) => (
478
+ <span
479
+ key={item.id}
480
+ className="inline-flex items-center gap-1 rounded-md bg-green-100 px-2 py-1 text-xs text-green-800"
481
+ >
482
+ {item.icon && (
483
+ <img src={item.icon} alt="" className="h-3 w-3" />
484
+ )}
485
+ {item.name}
486
+ </span>
487
+ ))
488
+ : aiSuggestions.map((suggestionId) => (
489
+ <span
490
+ key={suggestionId}
491
+ className="inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs text-green-800"
492
+ >
493
+ {suggestionId}
494
+ </span>
495
+ ))}
496
+ </div>
497
+ <p className="mt-2 text-xs text-green-700">
498
+ These items were automatically selected by AI based on your input
499
+ data.
500
+ </p>
501
+ </div>
502
+ )}
503
+ </div>
504
+
505
+ <div className="flex-1 overflow-hidden rounded-md border border-gray-300">
506
+ <div className="h-full overflow-auto bg-white p-2">
507
+ <ContentTree
508
+ language={editContext.currentItemDescriptor?.language ?? "en"}
509
+ selectionMode={multiSelect ? "multiple" : "single"}
510
+ selectedItemIds={selectedValues}
511
+ rootItemIds={rootItemIds}
512
+ includeItemPath={true}
513
+ onDoubleClick={handleAddItem}
514
+ />
515
+ </div>
516
+ </div>
517
+
518
+ <div className="flex-shrink-0 rounded-md bg-gray-50 p-3">
519
+ <div className="text-sm text-gray-600">
520
+ <strong>Instructions:</strong>
521
+ <ul className="mt-1 list-inside list-disc space-y-1">
522
+ <li>Browse the content tree to find the items you need</li>
523
+ {multiSelect ? (
524
+ <>
525
+ <li>Double-click on an item to add it to your selection</li>
526
+ <li>Click the × button on selected items to remove them</li>
527
+ </>
528
+ ) : (
529
+ <li>Double-click on an item to select it</li>
530
+ )}
531
+ {hasInputData && step.fields?.systemInstructions && (
532
+ <li>
533
+ AI will automatically analyze your input data and suggest
534
+ relevant items, or use "Get AI Suggestions" to regenerate
535
+ suggestions
536
+ </li>
537
+ )}
538
+ <li>
539
+ Selected items will be saved to the "{propertyName}" property
540
+ </li>
541
+ </ul>
542
+ </div>
543
+ </div>
544
+ </div>
545
+ );
546
+ }
@@ -118,7 +118,19 @@ export const ImagesStep = (props: StepComponentProps) => {
118
118
  undefined,
119
119
  "gpt-4.1",
120
120
  );
121
- const images = JSON.parse(aiPromptResult.content).images;
121
+
122
+ let images: Image[] = [];
123
+ if (
124
+ aiPromptResult &&
125
+ aiPromptResult.messages &&
126
+ aiPromptResult.messages.length > 0
127
+ ) {
128
+ const lastMessage =
129
+ aiPromptResult.messages[aiPromptResult.messages.length - 1];
130
+ if (lastMessage && lastMessage.content) {
131
+ images = JSON.parse(lastMessage.content).images;
132
+ }
133
+ }
122
134
 
123
135
  // Create an array of promises for all search operations
124
136
  const searchPromises = images.map(async (image: Image) => {
@@ -92,29 +92,40 @@ export function LayoutStep({
92
92
  "gpt-4.1",
93
93
  (response) => {
94
94
  try {
95
- const newLayout = JSON.parse(response.content) as WizardPageModel;
96
-
97
- if (newLayout) {
98
- setData((prev: WizardData) => ({
99
- ...prev,
100
- pageModel: newLayout,
101
- }));
95
+ // Handle streaming response - might have different structure during streaming
96
+ const content =
97
+ response.content ||
98
+ response.messages?.[response.messages.length - 1]?.content;
99
+ if (content) {
100
+ const newLayout = JSON.parse(content) as WizardPageModel;
101
+
102
+ if (newLayout) {
103
+ setData((prev: WizardData) => ({
104
+ ...prev,
105
+ pageModel: newLayout,
106
+ }));
107
+ }
108
+
109
+ setMessage(newLayout.message);
102
110
  }
103
-
104
- setMessage(newLayout.message);
105
111
  } catch (parseError: unknown) {}
106
112
  },
107
113
  );
108
114
 
109
- const pageModel = JSON.parse(result.content);
115
+ if (result && result.messages && result.messages.length > 0) {
116
+ const lastMessage = result.messages[result.messages.length - 1];
117
+ if (lastMessage && lastMessage.content) {
118
+ const pageModel = JSON.parse(lastMessage.content);
110
119
 
111
- setData({
112
- ...data,
113
- pageModel: JSON.parse(result.content),
114
- customInstructions,
115
- });
120
+ setData({
121
+ ...data,
122
+ pageModel: JSON.parse(lastMessage.content),
123
+ customInstructions,
124
+ });
116
125
 
117
- setMessage(pageModel.message);
126
+ setMessage(pageModel.message);
127
+ }
128
+ }
118
129
  } catch (error) {
119
130
  console.error(error);
120
131
  } finally {
@@ -75,23 +75,36 @@ export function MetaDataStep({
75
75
  "gpt-4.1",
76
76
  (response) => {
77
77
  try {
78
- const newLayout = JSON.parse(response.content) as WizardPageModel;
79
-
80
- setPageModel({
81
- ...pageModel,
82
- metaDescription: newLayout.metaDescription,
83
- metaKeywords: newLayout.metaKeywords,
84
- });
78
+ // Handle streaming response - might have different structure during streaming
79
+ const content =
80
+ response.content ||
81
+ response.messages?.[response.messages.length - 1]?.content;
82
+ if (content) {
83
+ const newLayout = JSON.parse(content) as WizardPageModel;
84
+
85
+ setPageModel({
86
+ ...pageModel,
87
+ metaDescription: newLayout.metaDescription,
88
+ metaKeywords: newLayout.metaKeywords,
89
+ });
90
+ }
85
91
  } catch (parseError: unknown) {}
86
92
  },
87
93
  );
88
94
 
89
- const pageModelResult = JSON.parse(result.content) as WizardPageModel;
90
- setPageModel({
91
- ...pageModel,
92
- metaDescription: pageModelResult.metaDescription,
93
- metaKeywords: pageModelResult.metaKeywords,
94
- });
95
+ if (result && result.messages && result.messages.length > 0) {
96
+ const lastMessage = result.messages[result.messages.length - 1];
97
+ if (lastMessage && lastMessage.content) {
98
+ const pageModelResult = JSON.parse(
99
+ lastMessage.content,
100
+ ) as WizardPageModel;
101
+ setPageModel({
102
+ ...pageModel,
103
+ metaDescription: pageModelResult.metaDescription,
104
+ metaKeywords: pageModelResult.metaKeywords,
105
+ });
106
+ }
107
+ }
95
108
  } catch (error) {
96
109
  console.error("Error generating meta fields", error);
97
110
  } finally {