@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.
Files changed (98) hide show
  1. package/dist/components/ui/CardConnector.d.ts +2 -1
  2. package/dist/components/ui/CardConnector.js +3 -3
  3. package/dist/components/ui/CardConnector.js.map +1 -1
  4. package/dist/components/ui/button.js +1 -1
  5. package/dist/components/ui/button.js.map +1 -1
  6. package/dist/components/ui/card.d.ts +7 -1
  7. package/dist/components/ui/card.js +71 -3
  8. package/dist/components/ui/card.js.map +1 -1
  9. package/dist/config/config.js +4 -2
  10. package/dist/config/config.js.map +1 -1
  11. package/dist/config/types.d.ts +1 -0
  12. package/dist/editor/MobileLayout.js +1 -1
  13. package/dist/editor/MobileLayout.js.map +1 -1
  14. package/dist/editor/PictureEditor.js +13 -5
  15. package/dist/editor/PictureEditor.js.map +1 -1
  16. package/dist/editor/client/EditorClient.js +2 -2
  17. package/dist/editor/client/EditorClient.js.map +1 -1
  18. package/dist/editor/client/editContext.d.ts +14 -1
  19. package/dist/editor/client/editContext.js.map +1 -1
  20. package/dist/editor/client/operations.js +1 -1
  21. package/dist/editor/client/operations.js.map +1 -1
  22. package/dist/editor/page-editor-chrome/CommentHighlighting.js +4 -1
  23. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  24. package/dist/editor/ui/Splitter.js +3 -1
  25. package/dist/editor/ui/Splitter.js.map +1 -1
  26. package/dist/page-wizard/WizardBoxConnector.d.ts +2 -1
  27. package/dist/page-wizard/WizardBoxConnector.js +3 -3
  28. package/dist/page-wizard/WizardBoxConnector.js.map +1 -1
  29. package/dist/page-wizard/WizardSteps.js +63 -17
  30. package/dist/page-wizard/WizardSteps.js.map +1 -1
  31. package/dist/page-wizard/service.d.ts +1 -1
  32. package/dist/page-wizard/service.js +1 -1
  33. package/dist/page-wizard/service.js.map +1 -1
  34. package/dist/page-wizard/steps/CollectStep.js +11 -17
  35. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  36. package/dist/page-wizard/steps/ComponentTypesSelector.d.ts +1 -0
  37. package/dist/page-wizard/steps/ComponentTypesSelector.js +53 -78
  38. package/dist/page-wizard/steps/ComponentTypesSelector.js.map +1 -1
  39. package/dist/page-wizard/steps/ContentStep.d.ts +2 -0
  40. package/dist/page-wizard/steps/ContentStep.js +403 -0
  41. package/dist/page-wizard/steps/ContentStep.js.map +1 -0
  42. package/dist/page-wizard/steps/Generate.js +1 -1
  43. package/dist/page-wizard/steps/Generate.js.map +1 -1
  44. package/dist/page-wizard/steps/ImagesStep.js +16 -13
  45. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  46. package/dist/page-wizard/steps/SelectStep.js +1 -1
  47. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  48. package/dist/page-wizard/steps/SetupPageStep.d.ts +2 -0
  49. package/dist/page-wizard/steps/SetupPageStep.js +152 -0
  50. package/dist/page-wizard/steps/SetupPageStep.js.map +1 -0
  51. package/dist/page-wizard/steps/usePageCreator.js +4 -4
  52. package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
  53. package/dist/page-wizard/usePageWizard.d.ts +17 -3
  54. package/dist/page-wizard/usePageWizard.js +62 -2
  55. package/dist/page-wizard/usePageWizard.js.map +1 -1
  56. package/dist/revision.d.ts +2 -2
  57. package/dist/revision.js +2 -2
  58. package/dist/splash-screen/NewPage.js +10 -10
  59. package/dist/splash-screen/NewPage.js.map +1 -1
  60. package/dist/splash-screen/SplashScreen.js +3 -3
  61. package/dist/splash-screen/SplashScreen.js.map +1 -1
  62. package/dist/styles.css +184 -68
  63. package/package.json +1 -1
  64. package/src/components/ui/CardConnector.tsx +50 -15
  65. package/src/components/ui/button.tsx +1 -1
  66. package/src/components/ui/card.tsx +331 -15
  67. package/src/config/config.tsx +4 -2
  68. package/src/config/types.ts +3 -0
  69. package/src/editor/MobileLayout.tsx +7 -9
  70. package/src/editor/PictureEditor.tsx +16 -10
  71. package/src/editor/client/EditorClient.tsx +3 -5
  72. package/src/editor/client/editContext.ts +23 -1
  73. package/src/editor/client/operations.ts +1 -1
  74. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +6 -1
  75. package/src/editor/ui/Splitter.tsx +10 -1
  76. package/src/page-wizard/WizardBoxConnector.tsx +50 -15
  77. package/src/page-wizard/WizardSteps.tsx +163 -34
  78. package/src/page-wizard/service.ts +2 -2
  79. package/src/page-wizard/steps/CollectStep.tsx +95 -141
  80. package/src/page-wizard/steps/ComponentTypesSelector.tsx +225 -245
  81. package/src/page-wizard/steps/ContentStep.tsx +648 -0
  82. package/src/page-wizard/steps/Generate.tsx +3 -3
  83. package/src/page-wizard/steps/ImagesStep.tsx +20 -15
  84. package/src/page-wizard/steps/SelectStep.tsx +4 -4
  85. package/src/page-wizard/steps/SetupPageStep.tsx +329 -0
  86. package/src/page-wizard/steps/usePageCreator.ts +4 -4
  87. package/src/page-wizard/usePageWizard.ts +69 -4
  88. package/src/revision.ts +2 -2
  89. package/src/splash-screen/NewPage.tsx +22 -16
  90. package/src/splash-screen/SplashScreen.tsx +3 -1
  91. package/dist/page-wizard/steps/CreatePage.d.ts +0 -12
  92. package/dist/page-wizard/steps/CreatePage.js +0 -149
  93. package/dist/page-wizard/steps/CreatePage.js.map +0 -1
  94. package/dist/page-wizard/steps/CreatePageAndLayoutStep.d.ts +0 -2
  95. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +0 -235
  96. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +0 -1
  97. package/src/page-wizard/steps/CreatePage.tsx +0 -329
  98. 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-64 w-64 animate-pulse">
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>