@alpaca-editor/core 1.0.3902 → 1.0.3904
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/dist/components/ui/CardConnector.d.ts +2 -1
- package/dist/components/ui/CardConnector.js +3 -3
- package/dist/components/ui/CardConnector.js.map +1 -1
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/card.d.ts +7 -1
- package/dist/components/ui/card.js +71 -3
- package/dist/components/ui/card.js.map +1 -1
- package/dist/config/config.js +4 -2
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/editor/MobileLayout.js +1 -1
- package/dist/editor/MobileLayout.js.map +1 -1
- package/dist/editor/PictureEditor.js +13 -5
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/client/EditorClient.js +2 -2
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +14 -1
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/operations.js +1 -1
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js +4 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
- package/dist/editor/ui/Splitter.js +3 -1
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/page-wizard/WizardBoxConnector.d.ts +2 -1
- package/dist/page-wizard/WizardBoxConnector.js +3 -3
- package/dist/page-wizard/WizardBoxConnector.js.map +1 -1
- package/dist/page-wizard/WizardSteps.js +63 -17
- package/dist/page-wizard/WizardSteps.js.map +1 -1
- package/dist/page-wizard/service.d.ts +1 -1
- package/dist/page-wizard/service.js +1 -1
- package/dist/page-wizard/service.js.map +1 -1
- package/dist/page-wizard/steps/CollectStep.js +11 -17
- package/dist/page-wizard/steps/CollectStep.js.map +1 -1
- package/dist/page-wizard/steps/ComponentTypesSelector.d.ts +1 -0
- package/dist/page-wizard/steps/ComponentTypesSelector.js +53 -78
- package/dist/page-wizard/steps/ComponentTypesSelector.js.map +1 -1
- package/dist/page-wizard/steps/ContentStep.d.ts +2 -0
- package/dist/page-wizard/steps/ContentStep.js +403 -0
- package/dist/page-wizard/steps/ContentStep.js.map +1 -0
- package/dist/page-wizard/steps/Generate.js +1 -1
- package/dist/page-wizard/steps/Generate.js.map +1 -1
- package/dist/page-wizard/steps/ImagesStep.js +16 -13
- package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
- package/dist/page-wizard/steps/SelectStep.js +1 -1
- package/dist/page-wizard/steps/SelectStep.js.map +1 -1
- package/dist/page-wizard/steps/SetupPageStep.d.ts +2 -0
- package/dist/page-wizard/steps/SetupPageStep.js +152 -0
- package/dist/page-wizard/steps/SetupPageStep.js.map +1 -0
- package/dist/page-wizard/steps/usePageCreator.js +4 -4
- package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
- package/dist/page-wizard/usePageWizard.d.ts +17 -3
- package/dist/page-wizard/usePageWizard.js +62 -2
- package/dist/page-wizard/usePageWizard.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +10 -10
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/SplashScreen.js +3 -3
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +184 -68
- package/package.json +1 -1
- package/src/components/ui/CardConnector.tsx +50 -15
- package/src/components/ui/button.tsx +1 -1
- package/src/components/ui/card.tsx +331 -15
- package/src/config/config.tsx +4 -2
- package/src/config/types.ts +3 -0
- package/src/editor/MobileLayout.tsx +7 -9
- package/src/editor/PictureEditor.tsx +16 -10
- package/src/editor/client/EditorClient.tsx +3 -5
- package/src/editor/client/editContext.ts +23 -1
- package/src/editor/client/operations.ts +1 -1
- package/src/editor/page-editor-chrome/CommentHighlighting.tsx +6 -1
- package/src/editor/ui/Splitter.tsx +10 -1
- package/src/page-wizard/WizardBoxConnector.tsx +50 -15
- package/src/page-wizard/WizardSteps.tsx +163 -34
- package/src/page-wizard/service.ts +2 -2
- package/src/page-wizard/steps/CollectStep.tsx +95 -141
- package/src/page-wizard/steps/ComponentTypesSelector.tsx +225 -245
- package/src/page-wizard/steps/ContentStep.tsx +648 -0
- package/src/page-wizard/steps/Generate.tsx +3 -3
- package/src/page-wizard/steps/ImagesStep.tsx +20 -15
- package/src/page-wizard/steps/SelectStep.tsx +4 -4
- package/src/page-wizard/steps/SetupPageStep.tsx +329 -0
- package/src/page-wizard/steps/usePageCreator.ts +4 -4
- package/src/page-wizard/usePageWizard.ts +69 -4
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +22 -16
- package/src/splash-screen/SplashScreen.tsx +3 -1
- package/dist/page-wizard/steps/CreatePage.d.ts +0 -12
- package/dist/page-wizard/steps/CreatePage.js +0 -149
- package/dist/page-wizard/steps/CreatePage.js.map +0 -1
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.d.ts +0 -2
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +0 -235
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +0 -1
- package/src/page-wizard/steps/CreatePage.tsx +0 -329
- package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +0 -430
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import { StepComponentProps } from "../../config/types";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
4
|
+
import { getChildren } from "../../editor/services/contentService";
|
|
5
|
+
import { getItemDescriptor } from "../../editor/utils";
|
|
6
|
+
import { PageViewer } from "../../editor/page-viewer/PageViewer";
|
|
7
|
+
import {
|
|
8
|
+
useEditContext,
|
|
9
|
+
useEditContextRef,
|
|
10
|
+
useModifiedFieldsContext,
|
|
11
|
+
} from "../../editor/client/editContext";
|
|
12
|
+
import { ExecutionResult } from "../../editor/services/serviceHelper";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ComponentTypeSelector,
|
|
16
|
+
getComponentTypeSelectorSummary,
|
|
17
|
+
} from "./ComponentTypesSelector";
|
|
18
|
+
import { ActionButton } from "../../components/ActionButton";
|
|
19
|
+
import { convertPageSchemaToWizardComponents } from "./schema";
|
|
20
|
+
import { WizardPageModel } from "../PageWizard";
|
|
21
|
+
import { createWizardAiContext, wipeComponents } from "../service";
|
|
22
|
+
|
|
23
|
+
import { executePrompt } from "../../editor/services/aiService";
|
|
24
|
+
import { usePageCreator } from "./usePageCreator";
|
|
25
|
+
import { useThrottledCallback } from "use-debounce";
|
|
26
|
+
import { InputTextarea } from "primereact/inputtextarea";
|
|
27
|
+
|
|
28
|
+
import { convertToAiPageModel } from "../../editor/ai/aiPageModel";
|
|
29
|
+
import { WizardBox } from "../WizardBox";
|
|
30
|
+
import { Wand2, PanelsTopLeft } from "lucide-react";
|
|
31
|
+
import { cn } from "../../lib/utils";
|
|
32
|
+
import { WizardBoxConnector } from "../WizardBoxConnector";
|
|
33
|
+
import { ItemDescriptor } from "@/editor/pageModel";
|
|
34
|
+
|
|
35
|
+
export function ContentStep({
|
|
36
|
+
wizard,
|
|
37
|
+
step,
|
|
38
|
+
parentItem,
|
|
39
|
+
pageModel,
|
|
40
|
+
setPageModel,
|
|
41
|
+
data,
|
|
42
|
+
setData,
|
|
43
|
+
internalState,
|
|
44
|
+
setInternalState,
|
|
45
|
+
setStepCompleted,
|
|
46
|
+
}: StepComponentProps) {
|
|
47
|
+
const [pageLoaded, setPageLoaded] = useState(false);
|
|
48
|
+
const pageLoadedRef = useRef(false);
|
|
49
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
50
|
+
const [availableComponentTypes, setAvailableComponentTypes] = useState<
|
|
51
|
+
string[]
|
|
52
|
+
>([]);
|
|
53
|
+
|
|
54
|
+
const editContext = useEditContext();
|
|
55
|
+
const editContextRef = useEditContextRef();
|
|
56
|
+
const [abortController, setAbortController] = useState<AbortController>();
|
|
57
|
+
const [message, setMessage] = useState<string>();
|
|
58
|
+
const [isCreatingComponents, setIsCreatingComponents] = useState(false);
|
|
59
|
+
const [pageItem, setPageItem] = useState<ItemDescriptor>();
|
|
60
|
+
|
|
61
|
+
const modifiedFieldsContext = useModifiedFieldsContext();
|
|
62
|
+
|
|
63
|
+
const pageCreator = usePageCreator(pageItem, wizard, setPageModel);
|
|
64
|
+
|
|
65
|
+
if (!parentItem) return "No parent item";
|
|
66
|
+
|
|
67
|
+
const checkPageName = useCallback(async () => {
|
|
68
|
+
if (!parentItem) return false;
|
|
69
|
+
|
|
70
|
+
// Check if page name is valid
|
|
71
|
+
if (!pageModel.name || pageModel.name.trim().length < 3) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if page with same name already exists
|
|
76
|
+
const children = await getChildren(
|
|
77
|
+
parentItem.id,
|
|
78
|
+
editContext?.sessionId ?? "",
|
|
79
|
+
[],
|
|
80
|
+
false,
|
|
81
|
+
editContext?.contentEditorItem?.language || "en",
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
children.find(
|
|
86
|
+
(x) =>
|
|
87
|
+
x.name.toLocaleLowerCase() ===
|
|
88
|
+
pageModel.name.trim().toLocaleLowerCase(),
|
|
89
|
+
)
|
|
90
|
+
) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
}, [
|
|
96
|
+
parentItem,
|
|
97
|
+
pageModel.name,
|
|
98
|
+
editContext?.sessionId,
|
|
99
|
+
editContext?.contentEditorItem?.language,
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const createPageIfNeeded = useCallback(async (): Promise<boolean> => {
|
|
103
|
+
if (!editContext || !parentItem || pageItem) return true; // Page already exists
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (!(await checkPageName())) {
|
|
107
|
+
console.error("Page name validation failed");
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = await editContext.operations.createItem(
|
|
112
|
+
{
|
|
113
|
+
...getItemDescriptor(parentItem),
|
|
114
|
+
language: parentItem.language || "en",
|
|
115
|
+
},
|
|
116
|
+
wizard.templateId,
|
|
117
|
+
pageModel.name,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (result) {
|
|
121
|
+
editContext?.loadItem(result, { addToBrowseHistory: true });
|
|
122
|
+
const item = await editContext?.itemsRepository.getItem(result);
|
|
123
|
+
if (item && setPageItem) {
|
|
124
|
+
setPageItem(item);
|
|
125
|
+
return true;
|
|
126
|
+
} else {
|
|
127
|
+
console.error("Failed to load newly created item", result);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("Error creating page", error);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}, [
|
|
137
|
+
editContext,
|
|
138
|
+
parentItem,
|
|
139
|
+
pageItem,
|
|
140
|
+
checkPageName,
|
|
141
|
+
wizard.templateId,
|
|
142
|
+
pageModel.name,
|
|
143
|
+
setPageItem,
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
editContext?.setMode("edit");
|
|
148
|
+
if (!internalState.layoutInstructions) {
|
|
149
|
+
setInternalState({
|
|
150
|
+
...internalState,
|
|
151
|
+
layoutInstructions: step.instructions,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Extract available component types from schema
|
|
156
|
+
if (wizard.schema) {
|
|
157
|
+
try {
|
|
158
|
+
const schema =
|
|
159
|
+
typeof wizard.schema === "string"
|
|
160
|
+
? JSON.parse(wizard.schema)
|
|
161
|
+
: wizard.schema;
|
|
162
|
+
|
|
163
|
+
// Extract component types (reusing logic from ComponentTypeSelector)
|
|
164
|
+
const extractComponentTypes = (items: any): string[] => {
|
|
165
|
+
if (!items || !Array.isArray(items)) return [];
|
|
166
|
+
let types: string[] = [];
|
|
167
|
+
for (const item of items) {
|
|
168
|
+
if ("type" in item) {
|
|
169
|
+
types.push(item.type);
|
|
170
|
+
}
|
|
171
|
+
if ("placeholders" in item && Array.isArray(item.placeholders)) {
|
|
172
|
+
for (const placeholder of item.placeholders) {
|
|
173
|
+
if (
|
|
174
|
+
placeholder.components &&
|
|
175
|
+
Array.isArray(placeholder.components)
|
|
176
|
+
) {
|
|
177
|
+
types = [
|
|
178
|
+
...types,
|
|
179
|
+
...extractComponentTypes(placeholder.components),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if ("components" in item && Array.isArray(item.components)) {
|
|
185
|
+
types = [...types, ...extractComponentTypes(item.components)];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return types;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const componentTypes = extractComponentTypes(schema);
|
|
192
|
+
const uniqueTypes = Array.from(new Set(componentTypes)).sort();
|
|
193
|
+
setAvailableComponentTypes(uniqueTypes);
|
|
194
|
+
|
|
195
|
+
// Initialize selected component types if not already set
|
|
196
|
+
if (
|
|
197
|
+
!data.selectedComponentTypes ||
|
|
198
|
+
!Array.isArray(data.selectedComponentTypes)
|
|
199
|
+
) {
|
|
200
|
+
// Parse preselected components from step if available
|
|
201
|
+
let stepPreselectedTypes: string[] = [];
|
|
202
|
+
if (step["preselectedComponents"]) {
|
|
203
|
+
stepPreselectedTypes = step["preselectedComponents"]
|
|
204
|
+
.split(/[,\n\r]+/)
|
|
205
|
+
.map((type: string) => type.trim())
|
|
206
|
+
.filter((type: string) => type && uniqueTypes.includes(type));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Set initial selection
|
|
210
|
+
const initialSelection =
|
|
211
|
+
stepPreselectedTypes.length > 0
|
|
212
|
+
? stepPreselectedTypes
|
|
213
|
+
: uniqueTypes;
|
|
214
|
+
|
|
215
|
+
setData({
|
|
216
|
+
...data,
|
|
217
|
+
selectedComponentTypes: initialSelection,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error("Error extracting component types:", error);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}, []);
|
|
225
|
+
|
|
226
|
+
const createComponents = async () => {
|
|
227
|
+
if (!pageLoaded) return;
|
|
228
|
+
if (pageModel?.components && !isCreatingComponents) {
|
|
229
|
+
try {
|
|
230
|
+
setIsCreatingComponents(true);
|
|
231
|
+
|
|
232
|
+
await pageCreator.createComponentsRecursively(
|
|
233
|
+
pageModel.components,
|
|
234
|
+
"root",
|
|
235
|
+
);
|
|
236
|
+
setInternalState((prev: any) => ({
|
|
237
|
+
...prev,
|
|
238
|
+
componentsCreated: true,
|
|
239
|
+
}));
|
|
240
|
+
} finally {
|
|
241
|
+
setIsCreatingComponents(false);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (
|
|
248
|
+
internalState.componentsCreated &&
|
|
249
|
+
!isCreatingComponents &&
|
|
250
|
+
!isLoading
|
|
251
|
+
) {
|
|
252
|
+
editContext?.requestRefresh("immediate");
|
|
253
|
+
}
|
|
254
|
+
}, [isCreatingComponents, isLoading]);
|
|
255
|
+
|
|
256
|
+
const createComponentsThrottled = useThrottledCallback(createComponents, 300);
|
|
257
|
+
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
createComponentsThrottled();
|
|
260
|
+
}, [pageModel]);
|
|
261
|
+
|
|
262
|
+
// useEffect(() => {
|
|
263
|
+
// if (!pageItem) {
|
|
264
|
+
// setPageItem(pageItem);
|
|
265
|
+
// }
|
|
266
|
+
// }, [pageItem]);
|
|
267
|
+
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (
|
|
270
|
+
editContext &&
|
|
271
|
+
editContext.page &&
|
|
272
|
+
editContext.page.item &&
|
|
273
|
+
pageItem &&
|
|
274
|
+
pageItem.id === editContext.page.item.descriptor.id
|
|
275
|
+
) {
|
|
276
|
+
setPageLoaded(true);
|
|
277
|
+
pageLoadedRef.current = true;
|
|
278
|
+
}
|
|
279
|
+
}, [editContext?.page, pageItem]);
|
|
280
|
+
|
|
281
|
+
const createLayout = async () => {
|
|
282
|
+
try {
|
|
283
|
+
if (!editContext) return;
|
|
284
|
+
setMessage("");
|
|
285
|
+
setIsLoading(true);
|
|
286
|
+
setStepCompleted(false);
|
|
287
|
+
|
|
288
|
+
// Create page if it doesn't exist yet
|
|
289
|
+
let currentPageItem = pageItem;
|
|
290
|
+
if (!currentPageItem) {
|
|
291
|
+
const pageCreated = await createPageIfNeeded();
|
|
292
|
+
if (!pageCreated) {
|
|
293
|
+
setMessage(
|
|
294
|
+
"Failed to create page. Please check the page name and try again.",
|
|
295
|
+
);
|
|
296
|
+
setIsLoading(false);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Wait for pageLoaded to become true before continuing
|
|
301
|
+
const waitForPageLoaded = async (): Promise<boolean> => {
|
|
302
|
+
const maxWaitTime = 10000; // 10 seconds max wait
|
|
303
|
+
const pollInterval = 100; // Check every 100ms
|
|
304
|
+
let elapsed = 0;
|
|
305
|
+
|
|
306
|
+
while (elapsed < maxWaitTime) {
|
|
307
|
+
// Check if page is loaded using the ref which gets updated by the useEffect
|
|
308
|
+
if (pageLoadedRef.current) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
313
|
+
elapsed += pollInterval;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return false;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const isPageLoaded = await waitForPageLoaded();
|
|
320
|
+
if (!isPageLoaded) {
|
|
321
|
+
setMessage("Page creation timed out. Please try again.");
|
|
322
|
+
setIsLoading(false);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (internalState.componentsCreated) {
|
|
328
|
+
if (currentPageItem) {
|
|
329
|
+
const wipeResult: ExecutionResult<any> = await wipeComponents(
|
|
330
|
+
getItemDescriptor(currentPageItem),
|
|
331
|
+
);
|
|
332
|
+
if (wipeResult.type !== "success") {
|
|
333
|
+
console.error("Failed to wipe components:", wipeResult);
|
|
334
|
+
setMessage(
|
|
335
|
+
wipeResult.summary ||
|
|
336
|
+
"Failed to clear existing components. Please try again.",
|
|
337
|
+
);
|
|
338
|
+
setIsLoading(false);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
editContextRef.current?.itemsRepository.clear();
|
|
343
|
+
modifiedFieldsContext?.clear();
|
|
344
|
+
|
|
345
|
+
setPageModel((prev) => ({
|
|
346
|
+
...prev,
|
|
347
|
+
components: [],
|
|
348
|
+
message: "",
|
|
349
|
+
}));
|
|
350
|
+
|
|
351
|
+
editContextRef.current?.requestRefresh("immediate");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Parse schema if it's a string
|
|
355
|
+
const schema =
|
|
356
|
+
typeof wizard.schema === "string"
|
|
357
|
+
? JSON.parse(wizard.schema)
|
|
358
|
+
: wizard.schema;
|
|
359
|
+
|
|
360
|
+
// Filter the schema based on selected component types and placeholders
|
|
361
|
+
const filteredSchema = convertPageSchemaToWizardComponents(
|
|
362
|
+
schema,
|
|
363
|
+
data.selectedComponentTypes,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const localAbortController = new AbortController();
|
|
367
|
+
setAbortController(localAbortController);
|
|
368
|
+
|
|
369
|
+
// Get the existing page model
|
|
370
|
+
const existingPageModel = await convertToAiPageModel(
|
|
371
|
+
editContextRef.current?.page!,
|
|
372
|
+
editContextRef.current!,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const result = await executePrompt(
|
|
376
|
+
[
|
|
377
|
+
{
|
|
378
|
+
content: `${internalState.layoutInstructions?.trim()} Reply with a json object of type PageModel = { components: Component[]; message: string; };
|
|
379
|
+
Component = { id: string | undefined, name: string, type: string; fields: Field[]; placeholder?: string; children?: Component[]; };
|
|
380
|
+
Field = { name: string; value: string; type: string; };
|
|
381
|
+
Generate a descriptive name for each component including the topic.
|
|
382
|
+
Only use component types that are in the page schema.
|
|
383
|
+
Keep existing components with their ids. Leave id field empty for new components.
|
|
384
|
+
Existing page model: ${JSON.stringify(existingPageModel)}
|
|
385
|
+
Fill empty fields of existing components before you insert new components.
|
|
386
|
+
Component types: ${JSON.stringify(
|
|
387
|
+
filteredSchema,
|
|
388
|
+
)} Root level component types ${filteredSchema
|
|
389
|
+
.filter((c) => c.allowedOnRoot)
|
|
390
|
+
.map((c) => c.type)
|
|
391
|
+
.join(", ")}
|
|
392
|
+
Tell the user your reasoning in the message field.
|
|
393
|
+
If the user provided image ids, fill them into picture / image fields with an "id:" prefix. The image id needs to be guid.
|
|
394
|
+
If you dont have a matching image id, provide a description of a picture that you would like to use with a "generate:" prefix instead.
|
|
395
|
+
The language of the page is ${editContextRef.current!.contentEditorItem!.language}.
|
|
396
|
+
Input data: ${JSON.stringify(data)}`,
|
|
397
|
+
|
|
398
|
+
name: "system",
|
|
399
|
+
role: "system",
|
|
400
|
+
},
|
|
401
|
+
], //Fill image ids into picture / image fields.
|
|
402
|
+
editContextRef.current!,
|
|
403
|
+
createWizardAiContext,
|
|
404
|
+
{ allowedFunctions: [] },
|
|
405
|
+
{ signal: localAbortController.signal },
|
|
406
|
+
step.aiModel || "o3-mini-low",
|
|
407
|
+
(response) => {
|
|
408
|
+
try {
|
|
409
|
+
const newLayout = JSON.parse(response.content) as WizardPageModel;
|
|
410
|
+
|
|
411
|
+
if (newLayout) {
|
|
412
|
+
setPageModel((prev) => mergeLayout(prev, newLayout));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
setMessage(newLayout.message);
|
|
416
|
+
} catch (parseError: unknown) {}
|
|
417
|
+
},
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const pageModel = result;
|
|
421
|
+
|
|
422
|
+
console.log("RESULT MODEL: ", pageModel);
|
|
423
|
+
|
|
424
|
+
setPageModel((prev) => mergeLayout(prev, pageModel));
|
|
425
|
+
//setMessage(pageModel.message);
|
|
426
|
+
setStepCompleted(true);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error(error);
|
|
429
|
+
} finally {
|
|
430
|
+
setIsLoading(false);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// Custom instructions panel
|
|
435
|
+
const customInstructionsPanel = () => {
|
|
436
|
+
return (
|
|
437
|
+
<InputTextarea
|
|
438
|
+
value={internalState.layoutInstructions}
|
|
439
|
+
onChange={(e) =>
|
|
440
|
+
setInternalState({
|
|
441
|
+
...internalState,
|
|
442
|
+
layoutInstructions: e.target.value,
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
placeholder="Example: Make it modern and minimalist. Focus on images. Include a hero section at the top."
|
|
446
|
+
className="h-48 w-full rounded border border-gray-300 px-3 py-2 text-sm focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
|
447
|
+
/>
|
|
448
|
+
);
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<div className="flex h-full flex-col md:flex-row">
|
|
453
|
+
<div className="flex flex-col items-stretch">
|
|
454
|
+
<WizardBox
|
|
455
|
+
title="Content modules"
|
|
456
|
+
icon={<Wand2 />}
|
|
457
|
+
description="Select the component types the AI can choose from"
|
|
458
|
+
className="w-full md:w-96"
|
|
459
|
+
collapsible="yes"
|
|
460
|
+
defaultCollapsed="yes"
|
|
461
|
+
summary=<div className="text-xs">
|
|
462
|
+
{getComponentTypeSelectorSummary(
|
|
463
|
+
data.selectedComponentTypes,
|
|
464
|
+
availableComponentTypes,
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
>
|
|
468
|
+
<ComponentTypeSelector
|
|
469
|
+
selectedComponentTypes={data.selectedComponentTypes}
|
|
470
|
+
setSelectedComponentTypes={(selectedTypes) => {
|
|
471
|
+
setData({
|
|
472
|
+
...data,
|
|
473
|
+
selectedComponentTypes: selectedTypes,
|
|
474
|
+
});
|
|
475
|
+
}}
|
|
476
|
+
schema={wizard.schema}
|
|
477
|
+
data={data}
|
|
478
|
+
setData={setData}
|
|
479
|
+
step={step}
|
|
480
|
+
/>
|
|
481
|
+
</WizardBox>
|
|
482
|
+
<WizardBoxConnector direction="vertical" />
|
|
483
|
+
<WizardBox
|
|
484
|
+
title="Generate content"
|
|
485
|
+
icon={<Wand2 />}
|
|
486
|
+
collapsible="yes"
|
|
487
|
+
defaultCollapsed="mobileOnly"
|
|
488
|
+
description="Refine instructions and generate content"
|
|
489
|
+
className="w-full md:w-96"
|
|
490
|
+
footer={
|
|
491
|
+
<div>
|
|
492
|
+
<div className="flex w-full gap-2">
|
|
493
|
+
<ActionButton
|
|
494
|
+
onClick={createLayout}
|
|
495
|
+
disabled={
|
|
496
|
+
isLoading ||
|
|
497
|
+
isCreatingComponents ||
|
|
498
|
+
!data.selectedComponentTypes ||
|
|
499
|
+
data.selectedComponentTypes.length === 0 ||
|
|
500
|
+
(!pageItem &&
|
|
501
|
+
(!pageModel.name || pageModel.name.trim().length < 3))
|
|
502
|
+
}
|
|
503
|
+
isLoading={
|
|
504
|
+
isLoading ||
|
|
505
|
+
isCreatingComponents ||
|
|
506
|
+
!!editContext?.activeFieldActions.find(
|
|
507
|
+
(action) => action.state === "running",
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
loadingText={"Working"}
|
|
511
|
+
className="w-full flex-1"
|
|
512
|
+
>
|
|
513
|
+
{!pageItem ? "Create page" : "Delete & Regenerate content"}
|
|
514
|
+
</ActionButton>
|
|
515
|
+
|
|
516
|
+
{isLoading && (
|
|
517
|
+
<ActionButton
|
|
518
|
+
isLoading={false}
|
|
519
|
+
onClick={() => abortController?.abort("User aborted")}
|
|
520
|
+
>
|
|
521
|
+
Abort
|
|
522
|
+
</ActionButton>
|
|
523
|
+
)}
|
|
524
|
+
</div>
|
|
525
|
+
{message && <div className="mt-2 text-xs">{message}</div>}
|
|
526
|
+
</div>
|
|
527
|
+
}
|
|
528
|
+
>
|
|
529
|
+
<div>{customInstructionsPanel()}</div>
|
|
530
|
+
</WizardBox>
|
|
531
|
+
</div>
|
|
532
|
+
{pageItem && (
|
|
533
|
+
<>
|
|
534
|
+
<WizardBoxConnector />
|
|
535
|
+
<WizardBox
|
|
536
|
+
title="Page preview"
|
|
537
|
+
icon={<PanelsTopLeft />}
|
|
538
|
+
description="After finalizing the page, you can edit, further enhance the content and share the page with your team for collaboration."
|
|
539
|
+
noPadding={true}
|
|
540
|
+
className="flex-1"
|
|
541
|
+
showFullscreenButton={true}
|
|
542
|
+
>
|
|
543
|
+
<div
|
|
544
|
+
className={cn(
|
|
545
|
+
"min-h-[300px] border-t border-gray-200",
|
|
546
|
+
pageLoaded ? "h-full" : "h-0",
|
|
547
|
+
)}
|
|
548
|
+
>
|
|
549
|
+
<PageViewer
|
|
550
|
+
name="single"
|
|
551
|
+
compareView={false}
|
|
552
|
+
showFormEditor={false}
|
|
553
|
+
followEditsDefault={true}
|
|
554
|
+
pageViewContext={editContext!.pageView}
|
|
555
|
+
/>
|
|
556
|
+
</div>
|
|
557
|
+
</WizardBox>
|
|
558
|
+
</>
|
|
559
|
+
)}
|
|
560
|
+
</div>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Merges a new layout with the previous layout
|
|
566
|
+
* - Keeps all previous components
|
|
567
|
+
* - Adds new components
|
|
568
|
+
* - Updates existing fields with new values
|
|
569
|
+
* - Adds new fields
|
|
570
|
+
*/
|
|
571
|
+
const mergeLayout = (
|
|
572
|
+
prev: WizardPageModel | null,
|
|
573
|
+
newLayout: WizardPageModel,
|
|
574
|
+
): WizardPageModel => {
|
|
575
|
+
if (!prev) return newLayout;
|
|
576
|
+
|
|
577
|
+
// Merge top-level properties
|
|
578
|
+
const result: WizardPageModel = {
|
|
579
|
+
name: newLayout.name || prev.name,
|
|
580
|
+
message: newLayout.message,
|
|
581
|
+
metaDescription: prev.metaDescription,
|
|
582
|
+
metaKeywords: prev.metaKeywords,
|
|
583
|
+
components: [...(prev.components || [])],
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Function to merge components recursively
|
|
587
|
+
const mergeComponents = (
|
|
588
|
+
existingComponents: any[],
|
|
589
|
+
newComponents: any[],
|
|
590
|
+
): any[] => {
|
|
591
|
+
if (!newComponents?.length) return existingComponents;
|
|
592
|
+
|
|
593
|
+
const result = [...existingComponents];
|
|
594
|
+
|
|
595
|
+
for (const newComp of newComponents) {
|
|
596
|
+
// Try to find a matching component by type and name
|
|
597
|
+
const existingIndex = result.findIndex(
|
|
598
|
+
(comp) => comp.type === newComp.type && comp.name === newComp.name,
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
if (existingIndex >= 0) {
|
|
602
|
+
// Update existing component
|
|
603
|
+
const existing = result[existingIndex];
|
|
604
|
+
|
|
605
|
+
// Merge fields - update existing fields and add new ones
|
|
606
|
+
const mergedFields = [...(existing.fields || [])];
|
|
607
|
+
for (const newField of newComp.fields || []) {
|
|
608
|
+
const fieldIndex = mergedFields.findIndex(
|
|
609
|
+
(f) => f.name === newField.name,
|
|
610
|
+
);
|
|
611
|
+
if (fieldIndex >= 0) {
|
|
612
|
+
mergedFields[fieldIndex].value = newField.value; // Update existing field
|
|
613
|
+
mergedFields[fieldIndex].type = newField.type;
|
|
614
|
+
} else {
|
|
615
|
+
mergedFields.push(newField); // Add new field
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Recursive merge of children components
|
|
620
|
+
const mergedChildren = mergeComponents(
|
|
621
|
+
existing.children || [],
|
|
622
|
+
newComp.children || [],
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
// Create updated component
|
|
626
|
+
result[existingIndex] = {
|
|
627
|
+
...existing,
|
|
628
|
+
fields: mergedFields,
|
|
629
|
+
children: mergedChildren,
|
|
630
|
+
placeholder: newComp.placeholder || existing.placeholder,
|
|
631
|
+
};
|
|
632
|
+
} else {
|
|
633
|
+
// Add new component
|
|
634
|
+
result.push(newComp);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return result;
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Merge components recursively
|
|
642
|
+
result.components = mergeComponents(
|
|
643
|
+
result.components,
|
|
644
|
+
newLayout.components || [],
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
return result;
|
|
648
|
+
};
|
|
@@ -40,9 +40,9 @@ function Generate({ title }: GenerateProps) {
|
|
|
40
40
|
"animate-fadeIn flex h-full flex-col items-center justify-center transition-all duration-300",
|
|
41
41
|
)}
|
|
42
42
|
>
|
|
43
|
-
<div className="mb-20">
|
|
44
|
-
<div className="flex justify-center">
|
|
45
|
-
<div className="h-
|
|
43
|
+
<div className="md:mb-20">
|
|
44
|
+
<div className="flex items-center justify-center">
|
|
45
|
+
<div className="h-48 w-48 animate-pulse md:h-64 md:w-64">
|
|
46
46
|
<div className="magic-logo h-full w-full">
|
|
47
47
|
<Logo />
|
|
48
48
|
</div>
|