@parhelia/page-wizard 0.1.11004 → 0.1.11005

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 (91) hide show
  1. package/dist/SplashScreen.d.ts +1 -0
  2. package/dist/SplashScreen.js +110 -0
  3. package/dist/SplashScreen.js.map +1 -0
  4. package/dist/dist/PageWizard.d.ts +78 -0
  5. package/dist/dist/PageWizard.js +57 -0
  6. package/dist/dist/PageWizard.js.map +1 -0
  7. package/dist/dist/SplashScreen.d.ts +1 -0
  8. package/dist/dist/SplashScreen.js +110 -0
  9. package/dist/dist/SplashScreen.js.map +1 -0
  10. package/dist/dist/WizardBoxConnector.d.ts +4 -0
  11. package/dist/dist/WizardBoxConnector.js +6 -0
  12. package/dist/dist/WizardBoxConnector.js.map +1 -0
  13. package/dist/dist/WizardSteps.d.ts +8 -0
  14. package/dist/dist/WizardSteps.js +174 -0
  15. package/dist/dist/WizardSteps.js.map +1 -0
  16. package/dist/dist/config.d.ts +2 -0
  17. package/dist/dist/config.js +86 -0
  18. package/dist/dist/config.js.map +1 -0
  19. package/dist/dist/index.d.ts +2 -0
  20. package/dist/dist/index.js +2 -0
  21. package/dist/dist/index.js.map +1 -0
  22. package/dist/dist/service.d.ts +15 -0
  23. package/dist/dist/service.js +29 -0
  24. package/dist/dist/service.js.map +1 -0
  25. package/dist/dist/startPageWizardCommand.d.ts +13 -0
  26. package/dist/dist/startPageWizardCommand.js +31 -0
  27. package/dist/dist/startPageWizardCommand.js.map +1 -0
  28. package/dist/dist/steps/BuildPageStep.d.ts +2 -0
  29. package/dist/dist/steps/BuildPageStep.js +138 -0
  30. package/dist/dist/steps/BuildPageStep.js.map +1 -0
  31. package/dist/dist/steps/CollectStep.d.ts +2 -0
  32. package/dist/dist/steps/CollectStep.js +115 -0
  33. package/dist/dist/steps/CollectStep.js.map +1 -0
  34. package/dist/dist/steps/ComponentTypesSelector.d.ts +13 -0
  35. package/dist/dist/steps/ComponentTypesSelector.js +155 -0
  36. package/dist/dist/steps/ComponentTypesSelector.js.map +1 -0
  37. package/dist/dist/steps/Components.d.ts +9 -0
  38. package/dist/dist/steps/Components.js +89 -0
  39. package/dist/dist/steps/Components.js.map +1 -0
  40. package/dist/dist/steps/ContentStep.d.ts +2 -0
  41. package/dist/dist/steps/ContentStep.js +808 -0
  42. package/dist/dist/steps/ContentStep.js.map +1 -0
  43. package/dist/dist/steps/EditButton.d.ts +8 -0
  44. package/dist/dist/steps/EditButton.js +5 -0
  45. package/dist/dist/steps/EditButton.js.map +1 -0
  46. package/dist/dist/steps/FieldEditor.d.ts +5 -0
  47. package/dist/dist/steps/FieldEditor.js +27 -0
  48. package/dist/dist/steps/FieldEditor.js.map +1 -0
  49. package/dist/dist/steps/FindItemsStep.d.ts +2 -0
  50. package/dist/dist/steps/FindItemsStep.js +294 -0
  51. package/dist/dist/steps/FindItemsStep.js.map +1 -0
  52. package/dist/dist/steps/Generate.d.ts +6 -0
  53. package/dist/dist/steps/Generate.js +31 -0
  54. package/dist/dist/steps/Generate.js.map +1 -0
  55. package/dist/dist/steps/ImagesStep.d.ts +2 -0
  56. package/dist/dist/steps/ImagesStep.js +178 -0
  57. package/dist/dist/steps/ImagesStep.js.map +1 -0
  58. package/dist/dist/steps/LayoutStep.d.ts +2 -0
  59. package/dist/dist/steps/LayoutStep.js +128 -0
  60. package/dist/dist/steps/LayoutStep.js.map +1 -0
  61. package/dist/dist/steps/MetaDataStep.d.ts +2 -0
  62. package/dist/dist/steps/MetaDataStep.js +112 -0
  63. package/dist/dist/steps/MetaDataStep.js.map +1 -0
  64. package/dist/dist/steps/SchottSelectImagesStep.d.ts +2 -0
  65. package/dist/dist/steps/SchottSelectImagesStep.js +55 -0
  66. package/dist/dist/steps/SchottSelectImagesStep.js.map +1 -0
  67. package/dist/dist/steps/SelectStep.d.ts +2 -0
  68. package/dist/dist/steps/SelectStep.js +151 -0
  69. package/dist/dist/steps/SelectStep.js.map +1 -0
  70. package/dist/dist/steps/StructureStep.d.ts +2 -0
  71. package/dist/dist/steps/StructureStep.js +205 -0
  72. package/dist/dist/steps/StructureStep.js.map +1 -0
  73. package/dist/dist/steps/TranslateStep.d.ts +2 -0
  74. package/dist/dist/steps/TranslateStep.js +613 -0
  75. package/dist/dist/steps/TranslateStep.js.map +1 -0
  76. package/dist/dist/steps/schema.d.ts +13 -0
  77. package/dist/dist/steps/schema.js +139 -0
  78. package/dist/dist/steps/schema.js.map +1 -0
  79. package/dist/dist/steps/usePageCreator.d.ts +7 -0
  80. package/dist/dist/steps/usePageCreator.js +285 -0
  81. package/dist/dist/steps/usePageCreator.js.map +1 -0
  82. package/dist/dist/types.d.ts +27 -0
  83. package/dist/dist/types.js +2 -0
  84. package/dist/dist/types.js.map +1 -0
  85. package/dist/dist/usePageWizard.d.ts +22 -0
  86. package/dist/dist/usePageWizard.js +69 -0
  87. package/dist/dist/usePageWizard.js.map +1 -0
  88. package/dist/dist/utils/dataAccessor.d.ts +57 -0
  89. package/dist/dist/utils/dataAccessor.js +323 -0
  90. package/dist/dist/utils/dataAccessor.js.map +1 -0
  91. package/package.json +1 -1
@@ -0,0 +1,808 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState, useCallback, useRef } from "react";
3
+ import { Card, Textarea, Input, useEditContext, useEditContextRef, useFieldsEditContext, getChildren, getItemDescriptor, convertToAiPageModel, executePromptWithJsonResult, useItemNameValidation, ActionButton, getLanguagesAndVersions, Button, Popover, PopoverTrigger, cn, EditControls, LanguageSelector, PageViewer, PopoverContent, ScrollingContentTree, normalizeGuid, ItemSearch } from "@parhelia/core";
4
+ import { convertPageSchemaToWizardComponents } from "./schema";
5
+ import { createWizardAiContext, wipeComponents } from "../service";
6
+ import { Blocks, ChevronDown, Folder, PanelsTopLeft, RotateCcw, Settings, Trash, Wand2, } from "lucide-react";
7
+ import Generate from "./Generate";
8
+ import { usePageCreator } from "./usePageCreator";
9
+ import { getFilteredInputData, processPromptTemplate, } from "../utils/dataAccessor";
10
+ import { useThrottledCallback } from "use-debounce";
11
+ import { WizardBoxConnector } from "../WizardBoxConnector";
12
+ import { ComponentTypeSelector, getComponentTypeSelectorSummary } from "./ComponentTypesSelector";
13
+ function mergeLayout(previous, incoming) {
14
+ const next = {
15
+ ...previous,
16
+ ...incoming,
17
+ name: incoming.name ?? previous.name ?? "",
18
+ metaDescription: incoming.metaDescription ?? previous.metaDescription ?? "",
19
+ metaKeywords: incoming.metaKeywords ?? previous.metaKeywords ?? "",
20
+ fields: Array.isArray(incoming.fields) ? incoming.fields : previous.fields ?? [],
21
+ components: Array.isArray(incoming.components)
22
+ ? incoming.components
23
+ : previous.components ?? [],
24
+ };
25
+ if (incoming?.message)
26
+ next.message = incoming.message;
27
+ return next;
28
+ }
29
+ export function ContentStep({ wizard, step, parentItem, pageModel, setPageModel, data, setData, internalState, setInternalState, setStepCompleted, }) {
30
+ const [pageLoaded, setPageLoaded] = useState(false);
31
+ const pageLoadedRef = useRef(false);
32
+ const [isLoading, setIsLoading] = useState(false);
33
+ const [availableComponentTypes, setAvailableComponentTypes] = useState([]);
34
+ const editContext = useEditContext();
35
+ const editContextRef = useEditContextRef();
36
+ const [abortController, setAbortController] = useState();
37
+ const [message, setMessage] = useState();
38
+ const [isCreatingComponents, setIsCreatingComponents] = useState(false);
39
+ const [pageItem, setPageItem] = useState();
40
+ const [showDataObject, setShowDataObject] = useState(false);
41
+ // New state for name and language functionality
42
+ const [isGenerating, setIsGenerating] = useState(false);
43
+ const [language, setLanguage] = useState(data.language || parentItem?.language || "en");
44
+ const [fullParentItem, setFullParentItem] = useState();
45
+ const [nameValidation, setNameValidation] = useState({ isValid: false });
46
+ // Add target folder selection state
47
+ const [targetFolder, setTargetFolder] = useState(parentItem || null);
48
+ const [isSelectingFolder, setIsSelectingFolder] = useState(false);
49
+ const [folderSelectionResult, setFolderSelectionResult] = useState(null);
50
+ const [showFolderTree, setShowFolderTree] = useState(false);
51
+ const hasRunInitialGenerationRef = useRef(false);
52
+ const modifiedFieldsContext = useFieldsEditContext();
53
+ const { validateItemName } = useItemNameValidation();
54
+ if (!parentItem)
55
+ return "No parent item";
56
+ // Ensure a non-null page model locally and synchronize with upstream when provided
57
+ const defaultPageModel = {
58
+ components: [],
59
+ name: "",
60
+ metaDescription: "",
61
+ metaKeywords: "",
62
+ fields: [],
63
+ };
64
+ const [model, setModel] = useState(() => (pageModel ? { ...defaultPageModel, ...pageModel } : defaultPageModel));
65
+ useEffect(() => {
66
+ if (pageModel) {
67
+ setModel((prev) => ({ ...prev, ...pageModel }));
68
+ }
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps
70
+ }, [pageModel]);
71
+ const updateModel = useCallback((value) => {
72
+ setModel((prev) => {
73
+ const next = typeof value === "function" ? value(prev) : value;
74
+ setPageModel?.(next);
75
+ return next;
76
+ });
77
+ }, [setPageModel]);
78
+ const pageCreator = usePageCreator(pageItem, wizard, updateModel);
79
+ // Add target folder selection function
80
+ const selectTargetItem = useCallback(async () => {
81
+ if (!editContext || !step.fields.targetItemPrompt)
82
+ return;
83
+ setIsSelectingFolder(true);
84
+ try {
85
+ const abortController = new AbortController();
86
+ const prompt = step.fields.targetItemPrompt;
87
+ // Process template expressions in the folder selection prompt
88
+ const templateResult = processPromptTemplate(prompt, data);
89
+ if (templateResult.error) {
90
+ console.error("Template processing error in target item prompt:", templateResult.error);
91
+ setMessage(`Template Error: ${templateResult.error}`);
92
+ setIsSelectingFolder(false);
93
+ return;
94
+ }
95
+ const processedPrompt = templateResult.value;
96
+ const promptContent = `${processedPrompt?.trim()}
97
+ You can use get-children to explore existing items and create-item to create new items if needed.
98
+ Reply with a json object of type { id: string; path: string; } containing the ID and path of the target item.
99
+ The language of the content is ${language}.
100
+ Current parent item: ${fullParentItem?.path || parentItem?.id}
101
+ `;
102
+ console.log("PROMPT (selectTargetItem):", promptContent);
103
+ const result = await executePromptWithJsonResult([
104
+ {
105
+ content: promptContent,
106
+ name: "system",
107
+ role: "system",
108
+ id: crypto.randomUUID(),
109
+ },
110
+ ], { editContext, createAiContext: createWizardAiContext }, {
111
+ allowedFunctions: ["get-children", "create-item"],
112
+ model: step.fields.aiModel || undefined,
113
+ }, { signal: abortController.signal }, (response) => {
114
+ try {
115
+ if (response.id && response.path) {
116
+ setFolderSelectionResult(response);
117
+ // Update target folder with the selected folder
118
+ setTargetFolder({
119
+ id: response.id,
120
+ language: language,
121
+ });
122
+ }
123
+ }
124
+ catch (parseError) { }
125
+ });
126
+ if (result && result.id && result.path) {
127
+ setFolderSelectionResult(result);
128
+ // Update target folder with the selected folder
129
+ setTargetFolder({
130
+ id: result.id,
131
+ language: language,
132
+ });
133
+ }
134
+ }
135
+ catch (error) {
136
+ console.error("Error selecting target folder", error);
137
+ }
138
+ finally {
139
+ setIsSelectingFolder(false);
140
+ }
141
+ }, [
142
+ editContext,
143
+ step.fields.selectTargetFolderPrompt,
144
+ language,
145
+ parentItem,
146
+ data,
147
+ ]);
148
+ const handleFolderSelection = useCallback(async (folderId) => {
149
+ if (!editContext)
150
+ return;
151
+ try {
152
+ const folderItem = await editContext.itemsRepository.getItem({
153
+ id: folderId,
154
+ language: language,
155
+ version: 0,
156
+ });
157
+ if (folderItem) {
158
+ setTargetFolder({
159
+ id: folderId,
160
+ language: language,
161
+ });
162
+ setFolderSelectionResult({
163
+ id: folderId,
164
+ path: folderItem.path,
165
+ });
166
+ setFullParentItem(folderItem);
167
+ setShowFolderTree(false);
168
+ }
169
+ }
170
+ catch (error) {
171
+ console.error("Error loading selected folder:", error);
172
+ }
173
+ }, [editContext, language]);
174
+ const clearFolderSelection = useCallback(() => {
175
+ setTargetFolder(parentItem);
176
+ setFolderSelectionResult(null);
177
+ setFullParentItem(undefined);
178
+ setShowFolderTree(false);
179
+ }, [parentItem]);
180
+ const generatePageName = useCallback(async () => {
181
+ if (!editContext)
182
+ return;
183
+ const pageNameInstructions = step.fields.pageNamePrompt ||
184
+ "The item name should be a valid sitecore item name. Spaces are allowed but no special characters or Umlaute.";
185
+ setIsGenerating(true);
186
+ try {
187
+ const abortController = new AbortController();
188
+ const templateResult = processPromptTemplate(pageNameInstructions, data);
189
+ if (templateResult.error) {
190
+ console.error("Template processing error in page name prompt:", templateResult.error);
191
+ setMessage(`Template Error: ${templateResult.error}`);
192
+ setIsGenerating(false);
193
+ return;
194
+ }
195
+ const processedInstructions = templateResult.value;
196
+ const promptContent = `${processedInstructions?.trim()} Reply with a json object of type PageModel = { name: string; };
197
+ The language of the page is ${language}.
198
+ `;
199
+ console.log("PROMPT (generatePageName):", promptContent);
200
+ const pageModelResult = await executePromptWithJsonResult([
201
+ {
202
+ content: `${processedInstructions?.trim()} Reply with a json object of type PageModel = { name: string; };
203
+ The language of the page is ${language}.`,
204
+ name: "system",
205
+ role: "system",
206
+ id: crypto.randomUUID(),
207
+ },
208
+ {
209
+ content: promptContent,
210
+ name: "user",
211
+ role: "user",
212
+ id: crypto.randomUUID(),
213
+ },
214
+ ], { editContext, createAiContext: createWizardAiContext }, { model: step.fields.aiModel || undefined }, { signal: abortController.signal });
215
+ if (pageModelResult && pageModelResult.name) {
216
+ setPageModel?.((prev) => ({ ...prev, name: pageModelResult.name }));
217
+ }
218
+ }
219
+ catch (error) {
220
+ console.error("Error generating page name", error);
221
+ }
222
+ finally {
223
+ setIsGenerating(false);
224
+ }
225
+ }, [
226
+ editContext,
227
+ language,
228
+ updateModel,
229
+ step.fields.aiModel,
230
+ step.fields.pageNamePrompt,
231
+ data,
232
+ ]);
233
+ const checkPageName = useCallback(async () => {
234
+ if (!targetFolder) {
235
+ setNameValidation({ isValid: false, message: "No target folder" });
236
+ return false;
237
+ }
238
+ // Check if page name is valid
239
+ if (!model.name || model.name.trim().length < 3) {
240
+ setNameValidation({
241
+ isValid: false,
242
+ message: "Page name must be at least 3 characters long",
243
+ });
244
+ return false;
245
+ }
246
+ // Validate against editor settings (regex, invalid chars, max length)
247
+ const basicValidation = validateItemName
248
+ ? validateItemName(model.name.trim())
249
+ : { isValid: true };
250
+ if (!basicValidation.isValid) {
251
+ setNameValidation(basicValidation);
252
+ return false;
253
+ }
254
+ try {
255
+ // Check if page with same name already exists
256
+ const children = await getChildren(targetFolder.id, editContext?.sessionId ?? "", [], false, editContext?.contentEditorItem?.language || "en");
257
+ if (children.find((x) => x.name.toLocaleLowerCase() ===
258
+ model.name.trim().toLocaleLowerCase())) {
259
+ setNameValidation({
260
+ isValid: false,
261
+ message: "A page with this name already exists",
262
+ });
263
+ return false;
264
+ }
265
+ setNameValidation({ isValid: true });
266
+ return true;
267
+ }
268
+ catch (error) {
269
+ setNameValidation({
270
+ isValid: false,
271
+ message: "Error checking page name",
272
+ });
273
+ return false;
274
+ }
275
+ }, [
276
+ targetFolder,
277
+ model.name,
278
+ editContext?.sessionId,
279
+ editContext?.contentEditorItem?.language,
280
+ ]);
281
+ const handleInputChange = (field, value) => {
282
+ const updatedPageModel = {
283
+ ...model,
284
+ [field]: value,
285
+ };
286
+ updateModel(updatedPageModel);
287
+ };
288
+ const createPageIfNeeded = useCallback(async () => {
289
+ if (!editContext || !targetFolder || pageItem)
290
+ return true; // Page already exists
291
+ try {
292
+ if (!(await checkPageName())) {
293
+ console.error("Page name validation failed");
294
+ return false;
295
+ }
296
+ const result = await editContext.operations.createItem({
297
+ ...getItemDescriptor(targetFolder),
298
+ language: language || targetFolder.language || "en",
299
+ }, wizard.templateId, model.name);
300
+ if (result) {
301
+ // Ensure the result has the correct language
302
+ const resultWithLanguage = {
303
+ ...result,
304
+ language: language || targetFolder.language || "en",
305
+ version: 0,
306
+ };
307
+ editContext?.loadItem(resultWithLanguage, { addToBrowseHistory: true });
308
+ const item = await editContext?.itemsRepository.getItem(resultWithLanguage);
309
+ if (item && setPageItem) {
310
+ setPageItem(item);
311
+ return true;
312
+ }
313
+ else {
314
+ console.error("Failed to load newly created item", result);
315
+ return false;
316
+ }
317
+ }
318
+ return false;
319
+ }
320
+ catch (error) {
321
+ console.error("Error creating page", error);
322
+ return false;
323
+ }
324
+ }, [
325
+ editContext,
326
+ targetFolder,
327
+ pageItem,
328
+ checkPageName,
329
+ wizard.templateId,
330
+ model.name,
331
+ setPageItem,
332
+ language,
333
+ ]);
334
+ useEffect(() => {
335
+ editContext?.setMode("preview");
336
+ if (!internalState?.layoutInstructions) {
337
+ setInternalState?.({
338
+ ...(internalState ?? {}),
339
+ layoutInstructions: step.fields.instructions,
340
+ });
341
+ }
342
+ // Extract available component types from schema
343
+ if (wizard.schema) {
344
+ try {
345
+ const schema = typeof wizard.schema === "string"
346
+ ? JSON.parse(wizard.schema)
347
+ : wizard.schema;
348
+ // Extract component types (updated logic from ComponentTypeSelector)
349
+ const extractComponentTypes = (items) => {
350
+ if (!items)
351
+ return [];
352
+ let types = [];
353
+ if ("placeholders" in items && Array.isArray(items.placeholders)) {
354
+ for (const placeholder of items.placeholders) {
355
+ if (placeholder.components &&
356
+ Array.isArray(placeholder.components)) {
357
+ types = [
358
+ ...types,
359
+ ...extractComponentTypes(placeholder.components),
360
+ ];
361
+ }
362
+ }
363
+ return types;
364
+ }
365
+ if (!Array.isArray(items))
366
+ return [];
367
+ for (const item of items) {
368
+ // Add the current component type if it exists
369
+ if ("type" in item) {
370
+ types.push(item.type);
371
+ }
372
+ // Process components in placeholders recursively
373
+ if ("placeholders" in item && Array.isArray(item.placeholders)) {
374
+ for (const placeholder of item.placeholders) {
375
+ if (placeholder.components &&
376
+ Array.isArray(placeholder.components)) {
377
+ types = [
378
+ ...types,
379
+ ...extractComponentTypes(placeholder.components),
380
+ ];
381
+ }
382
+ }
383
+ }
384
+ // Process components directly (for top-level structure)
385
+ if ("components" in item && Array.isArray(item.components)) {
386
+ types = [...types, ...extractComponentTypes(item.components)];
387
+ }
388
+ }
389
+ return types;
390
+ };
391
+ const componentTypes = extractComponentTypes(schema);
392
+ const uniqueTypes = Array.from(new Set(componentTypes)).sort();
393
+ setAvailableComponentTypes(uniqueTypes);
394
+ // Initialize selected component types if not already set
395
+ if (!data.selectedComponentTypes ||
396
+ !Array.isArray(data.selectedComponentTypes)) {
397
+ // Parse preselected components from step if available
398
+ let stepPreselectedTypes = [];
399
+ if (step.fields["preselectedComponents"]) {
400
+ stepPreselectedTypes = step.fields["preselectedComponents"]
401
+ .split(/[,\n\r]+/)
402
+ .map((type) => type.trim())
403
+ .filter((type) => type && uniqueTypes.includes(type));
404
+ }
405
+ // Set initial selection
406
+ const initialSelection = stepPreselectedTypes.length > 0
407
+ ? stepPreselectedTypes
408
+ : uniqueTypes;
409
+ setData({
410
+ ...data,
411
+ selectedComponentTypes: initialSelection,
412
+ });
413
+ }
414
+ }
415
+ catch (error) {
416
+ console.error("Error extracting component types:", error);
417
+ }
418
+ }
419
+ }, []);
420
+ // Check page name whenever it changes
421
+ useEffect(() => {
422
+ if (model.name && !pageItem) {
423
+ checkPageName();
424
+ }
425
+ }, [model.name, checkPageName]);
426
+ // Load target folder details
427
+ useEffect(() => {
428
+ const loadTargetItem = async () => {
429
+ if (!targetFolder)
430
+ return;
431
+ const item = await editContext?.itemsRepository.getItem(targetFolder);
432
+ setFullParentItem(item);
433
+ // Also set fullParentItem for backward compatibility
434
+ setFullParentItem(item);
435
+ };
436
+ loadTargetItem();
437
+ }, [parentItem, editContext]);
438
+ // Auto-generate page name and select target folder only once on initial load
439
+ useEffect(() => {
440
+ if (!editContext)
441
+ return;
442
+ if (hasRunInitialGenerationRef.current)
443
+ return;
444
+ hasRunInitialGenerationRef.current = true;
445
+ const runInitialGeneration = async () => {
446
+ const promises = [];
447
+ if (!model.name) {
448
+ promises.push(generatePageName());
449
+ }
450
+ if (step.fields.targetItemPrompt && !folderSelectionResult) {
451
+ promises.push(selectTargetItem());
452
+ }
453
+ if (promises.length > 0) {
454
+ await Promise.all(promises);
455
+ }
456
+ };
457
+ runInitialGeneration();
458
+ }, []);
459
+ const createComponents = async () => {
460
+ if (!pageLoaded)
461
+ return;
462
+ if (model && !isCreatingComponents) {
463
+ try {
464
+ setIsCreatingComponents(true);
465
+ // Update page-level fields first
466
+ if (model.fields && model.fields.length > 0) {
467
+ await pageCreator.updatePageFields(model.fields);
468
+ }
469
+ // Then create components
470
+ if (model.components) {
471
+ console.log("Creating components", model.components);
472
+ await pageCreator.createComponentsRecursively(model.components, "root");
473
+ }
474
+ setInternalState?.((prev) => ({
475
+ ...(prev ?? {}),
476
+ componentsCreated: true,
477
+ }));
478
+ }
479
+ finally {
480
+ setIsCreatingComponents(false);
481
+ }
482
+ }
483
+ };
484
+ useEffect(() => {
485
+ if (internalState?.componentsCreated &&
486
+ !isCreatingComponents &&
487
+ !isLoading) {
488
+ editContext?.requestRefresh("immediate");
489
+ }
490
+ }, [isCreatingComponents, isLoading]);
491
+ const createComponentsThrottled = useThrottledCallback(createComponents, 300);
492
+ useEffect(() => {
493
+ createComponentsThrottled();
494
+ }, [model]);
495
+ useEffect(() => {
496
+ if (editContext &&
497
+ editContext.activeFieldActions &&
498
+ !editContext.activeFieldActions.find((x) => x.state === "running")) {
499
+ console.log("$$$ Refreshing!");
500
+ editContext.requestRefresh("immediate");
501
+ }
502
+ }, [editContext?.activeFieldActions]);
503
+ useEffect(() => {
504
+ if (editContext &&
505
+ editContext.page &&
506
+ editContext.page.item &&
507
+ pageItem &&
508
+ pageItem.id === editContext.page.item.descriptor.id) {
509
+ if (!pageLoaded) {
510
+ setTimeout(() => {
511
+ setPageLoaded(true);
512
+ pageLoadedRef.current = true;
513
+ }, 1000);
514
+ }
515
+ }
516
+ }, [editContext?.page, pageItem]);
517
+ const createLayout = async () => {
518
+ try {
519
+ if (!editContext)
520
+ return;
521
+ setMessage("");
522
+ setIsLoading(true);
523
+ setStepCompleted(false);
524
+ // Create page if it doesn't exist yet
525
+ let currentPageItem = pageItem;
526
+ // Wait for pageLoaded to become true before continuing
527
+ const waitForPageLoaded = async () => {
528
+ const maxWaitTime = 10000; // 10 seconds max wait
529
+ const pollInterval = 100; // Check every 100ms
530
+ let elapsed = 0;
531
+ while (elapsed < maxWaitTime) {
532
+ // Check if page is loaded using the ref which gets updated by the useEffect
533
+ if (pageLoadedRef.current) {
534
+ return true;
535
+ }
536
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
537
+ elapsed += pollInterval;
538
+ }
539
+ return false;
540
+ };
541
+ if (!currentPageItem) {
542
+ const pageCreated = await createPageIfNeeded();
543
+ if (!pageCreated) {
544
+ setMessage("Failed to create page. Please check the page name and try again.");
545
+ setIsLoading(false);
546
+ return;
547
+ }
548
+ const isPageLoaded = await waitForPageLoaded();
549
+ if (!isPageLoaded) {
550
+ setMessage("Page creation timed out. Please try again.");
551
+ setIsLoading(false);
552
+ return;
553
+ }
554
+ // Update currentPageItem from the loaded page, ensuring it has the correct language
555
+ if (editContextRef.current?.page?.item) {
556
+ const loadedItem = editContextRef.current.page.item;
557
+ // Ensure the loaded item matches the selected language
558
+ if (language && language !== loadedItem.language) {
559
+ // Try to load in the correct language
560
+ const itemInLanguage = {
561
+ id: loadedItem.descriptor.id,
562
+ language: language,
563
+ version: 0,
564
+ };
565
+ editContextRef.current?.loadItem(itemInLanguage, {
566
+ addToBrowseHistory: false,
567
+ });
568
+ await waitForPageLoaded();
569
+ const correctedItem = await editContextRef.current?.itemsRepository.getItem(itemInLanguage);
570
+ if (correctedItem) {
571
+ currentPageItem = correctedItem;
572
+ setPageItem(correctedItem);
573
+ }
574
+ else {
575
+ currentPageItem = loadedItem;
576
+ setPageItem(loadedItem);
577
+ }
578
+ }
579
+ else {
580
+ currentPageItem = loadedItem;
581
+ setPageItem(loadedItem);
582
+ }
583
+ }
584
+ }
585
+ if (internalState.componentsCreated) {
586
+ console.log("Wiping components");
587
+ if (currentPageItem) {
588
+ const wipeResult = await wipeComponents(getItemDescriptor(currentPageItem));
589
+ if (wipeResult.type !== "success") {
590
+ console.error("Failed to wipe components:", wipeResult);
591
+ setMessage(wipeResult.summary ||
592
+ "Failed to clear existing components. Please try again.");
593
+ setIsLoading(false);
594
+ return;
595
+ }
596
+ }
597
+ editContextRef.current?.itemsRepository.clear();
598
+ modifiedFieldsContext?.clearModifiedFields();
599
+ modifiedFieldsContext?.clearRecentEdits();
600
+ setInternalState?.((prev) => ({
601
+ ...(prev ?? {}),
602
+ componentsCreated: false,
603
+ }));
604
+ updateModel((prev) => ({
605
+ ...prev,
606
+ components: [],
607
+ message: "",
608
+ }));
609
+ pageLoadedRef.current = false;
610
+ // Reload the current page item to ensure we stay on the same page
611
+ if (currentPageItem) {
612
+ editContextRef.current?.loadItem(getItemDescriptor(currentPageItem), {
613
+ addToBrowseHistory: false,
614
+ });
615
+ }
616
+ editContextRef.current?.requestRefresh("immediate");
617
+ await waitForPageLoaded();
618
+ console.log("Page loaded after wipe");
619
+ }
620
+ // Parse schema if it's a string
621
+ const schema = typeof wizard.schema === "string"
622
+ ? JSON.parse(wizard.schema)
623
+ : wizard.schema;
624
+ // Filter the schema based on selected component types and placeholders
625
+ const filteredSchema = convertPageSchemaToWizardComponents(schema, data.selectedComponentTypes);
626
+ const localAbortController = new AbortController();
627
+ setAbortController(localAbortController);
628
+ // Get the existing page model; TODO make optional
629
+ const existingPageModel = await convertToAiPageModel(editContextRef.current?.page, editContextRef.current, filteredSchema);
630
+ console.log("Existing page model: ", existingPageModel);
631
+ // Filter input data based on "Input Properties" field if specified
632
+ const inputData = getFilteredInputData(step, data);
633
+ // Process template expressions in the layout instructions
634
+ const templateResult = processPromptTemplate(internalState.layoutInstructions || "", data);
635
+ if (templateResult.error) {
636
+ console.error("Template processing error in layout instructions:", templateResult.error);
637
+ setMessage(`Template Error: ${templateResult.error}`);
638
+ setIsLoading(false);
639
+ return;
640
+ }
641
+ const processedInstructions = templateResult.value;
642
+ const prompt = [
643
+ {
644
+ content: `${processedInstructions ? processedInstructions?.trim() : ""} Reply with a json object of type PageModel = { fields: Field[]; components: Component[]; message: string; };
645
+ Component = { id: string | undefined, name: string, type: string; fields: Field[]; placeholder?: string; children?: Component[]; };
646
+ Field = { name: string; value: string; type: string; };
647
+ Generate a descriptive name for each component including the topic.
648
+ Only use component types that are in the page schema.
649
+ Keep existing components with their ids. Leave id field empty for new components.
650
+ To generate images write generate: <keywords> into the picture / image field value.
651
+ ${(existingPageModel && `Existing page model: ${JSON.stringify(existingPageModel)}`) || ""}
652
+ Fill empty fields of existing components before you insert new components. Page fields: ${JSON.stringify(schema.pageFields.map((f) => {
653
+ return {
654
+ name: f.name,
655
+ type: f.type,
656
+ };
657
+ }))}
658
+ Available component types: ${JSON.stringify(filteredSchema)} Root level component types ${filteredSchema
659
+ .filter((c) => c.allowedOnRoot)
660
+ .map((c) => c.type)
661
+ .join(", ")}
662
+ Tell the user your reasoning in the message field.
663
+
664
+ The language of the page is ${language || editContextRef.current.page.item.language}.
665
+ `,
666
+ name: "system",
667
+ role: "system",
668
+ id: crypto.randomUUID(), // Add required id property
669
+ },
670
+ ];
671
+ console.log("PROMPT: ", prompt, filteredSchema, existingPageModel);
672
+ const result = await executePromptWithJsonResult(prompt, {
673
+ editContext: editContextRef.current,
674
+ createAiContext: createWizardAiContext,
675
+ }, {
676
+ allowedFunctions: [],
677
+ model: step.fields.aiModel || undefined,
678
+ endpoint: "/parhelia/page-wizard/prompt",
679
+ }, { signal: localAbortController.signal }, (response) => {
680
+ try {
681
+ setPageModel?.((prev) => mergeLayout(prev, response));
682
+ setMessage(response.message);
683
+ }
684
+ catch (parseError) { }
685
+ });
686
+ console.log("RESULT LAYOUT: ", result);
687
+ // Before setting pageModel (which triggers component creation), ensure we're on the correct language version
688
+ if (language && editContextRef.current?.page?.item) {
689
+ const currentPageLanguage = editContextRef.current.page.item.language;
690
+ if (language !== currentPageLanguage) {
691
+ console.log(`Language mismatch detected. Current: ${currentPageLanguage}, Selected: ${language}. Switching...`);
692
+ try {
693
+ // Get available language versions for the item
694
+ const languagesData = await getLanguagesAndVersions(getItemDescriptor(editContextRef.current.page.item));
695
+ if (languagesData?.data) {
696
+ const targetLanguageInfo = languagesData.data.languages.find((lang) => lang.languageCode === language);
697
+ // If the language version doesn't exist, create it
698
+ if (!targetLanguageInfo || targetLanguageInfo.versions === 0) {
699
+ console.log(`Creating new language version for ${language}...`);
700
+ await editContextRef.current?.operations.createVersion({
701
+ id: editContextRef.current.page.item.id,
702
+ language: language,
703
+ version: 0,
704
+ });
705
+ }
706
+ // Load the item in the selected language
707
+ const languageVersionItem = {
708
+ id: editContextRef.current.page.item.id,
709
+ language: language,
710
+ version: 0,
711
+ };
712
+ editContextRef.current?.loadItem(languageVersionItem, {
713
+ addToBrowseHistory: false,
714
+ });
715
+ // Wait for the page to load in the new language
716
+ const isPageLoaded = await waitForPageLoaded();
717
+ if (!isPageLoaded) {
718
+ setMessage("Failed to load page in selected language.");
719
+ setIsLoading(false);
720
+ return;
721
+ }
722
+ // Update pageItem to the new language version
723
+ const updatedItem = await editContextRef.current?.itemsRepository.getItem(languageVersionItem);
724
+ if (updatedItem) {
725
+ setPageItem(updatedItem);
726
+ }
727
+ console.log(`Successfully switched to language version: ${language}`);
728
+ }
729
+ }
730
+ catch (error) {
731
+ console.error("Error switching language version:", error);
732
+ setMessage(`Failed to switch to language ${language}. Please try again.`);
733
+ setIsLoading(false);
734
+ return;
735
+ }
736
+ }
737
+ }
738
+ setPageModel?.((prev) => mergeLayout(prev, result));
739
+ setStepCompleted(true);
740
+ }
741
+ catch (error) {
742
+ console.error(error);
743
+ }
744
+ finally {
745
+ setIsLoading(false);
746
+ }
747
+ };
748
+ // Custom instructions panel
749
+ const customInstructionsPanel = () => {
750
+ return (_jsx(Textarea, { value: internalState?.layoutInstructions ?? "", onChange: (e) => setInternalState?.((prev) => ({
751
+ ...(prev ?? {}),
752
+ layoutInstructions: e.target.value,
753
+ })), placeholder: "Example: Make it modern and minimalist. Focus on images. Include a hero section at the top.", 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" }));
754
+ };
755
+ return (_jsxs("div", { className: "flex h-full flex-col md:flex-row", children: [_jsxs("div", { className: "flex flex-col items-stretch overflow-y-auto md:h-full", children: [_jsx(Card, { title: "Page Setup", icon: _jsx(Settings, {}), description: "Configure the page name, target location and language for your new page", collapsible: "yes", summary: _jsxs("div", { className: "flex flex-col gap-2 text-xs break-all text-gray-500", children: [_jsxs("div", { children: ["Folder: ", folderSelectionResult?.path || fullParentItem?.path] }), _jsxs("div", { children: ["Language: ", language] }), _jsxs("div", { children: ["Name: ", model.name || "Not set"] })] }), className: "w-full md:w-96", children: _jsx("div", { className: "flex flex-col items-stretch", children: _jsx("div", { className: "relative flex-1", children: isGenerating || isSelectingFolder ? (_jsx("div", { className: "mt-4 flex h-full items-center justify-center pb-4", children: _jsx(Generate, { title: isGenerating && isSelectingFolder
756
+ ? "Generating page name and selecting folder..."
757
+ : isGenerating
758
+ ? "Generating page name..."
759
+ : "Selecting folder...", size: "medium" }) })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-4", children: [_jsx("label", { htmlFor: "pageName", className: "mb-1 block text-xs font-medium", children: "Page Name" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { id: "pageName", type: "text", disabled: !!pageItem, className: `bg-gray-5 w-full rounded border md:text-xs ${!nameValidation.isValid
760
+ ? "border-red-500"
761
+ : nameValidation.isValid && model.name
762
+ ? "border-green-500"
763
+ : ""}`, value: pageModel?.name, onChange: (e) => handleInputChange("name", e.target.value), placeholder: "Enter page name" }), !pageItem && (_jsx(Button, { size: "sm", onClick: () => generatePageName(), disabled: isGenerating, title: "Recreate name", children: _jsx(RotateCcw, { className: "h-4 w-4", strokeWidth: 1 }) }))] }), nameValidation.message && !nameValidation.isValid && (_jsx("div", { className: "mt-1 text-xs text-red-600", children: nameValidation.message })), nameValidation.isValid && pageModel?.name && (_jsx("div", { className: "mt-1 text-xs text-green-600", children: "\u2713 Page name is available" }))] }), _jsxs("div", { className: "mb-4", children: [_jsx("div", { className: "mb-1 text-xs font-medium", children: "Target Folder" }), isSelectingFolder ? (_jsxs("div", { className: "mb-4 flex items-center gap-2 text-xs text-gray-600", children: [_jsx(Generate, { title: "Selecting folder...", size: "medium" }), "Selecting folder..."] })) : (_jsxs("div", { className: "mb-4", children: [_jsx("div", { className: "relative", children: _jsxs(Popover, { open: showFolderTree, onOpenChange: setShowFolderTree, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("div", { className: cn("focus-shadow bg-gray-5 flex cursor-pointer justify-between border p-1.5 text-xs hover:bg-gray-100"), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Folder, { className: "h-4 w-4", strokeWidth: 1 }), _jsx("span", { className: "break-all", children: folderSelectionResult?.path ||
764
+ fullParentItem?.path ||
765
+ "Loading..." })] }), _jsx(ChevronDown, { strokeWidth: 1, className: `h-4 w-4 transition-transform duration-200 ${showFolderTree ? "rotate-180" : "rotate-0"}` })] }) }), _jsxs(PopoverContent, { className: "w-96 p-0", align: "start", side: "bottom", children: [_jsx("div", { className: "border-b border-gray-200 bg-white p-2", children: _jsx(ItemSearch, { rootItemIds: [
766
+ normalizeGuid(parentItem?.id ||
767
+ "11111111-1111-1111-1111-111111111111"),
768
+ ], autoFocus: true, itemSelected: (item) => {
769
+ handleFolderSelection(item.id);
770
+ } }) }), folderSelectionResult && (_jsx("button", { className: "w-full cursor-pointer rounded border-b bg-gray-50 p-1 px-4 text-left text-xs hover:bg-gray-100", onClick: clearFolderSelection, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Trash, { className: "h-4 w-4", strokeWidth: 1 }), " ", "Reset to original folder"] }) })), _jsx("div", { className: "relative h-64 border-x border-b", children: _jsx(ScrollingContentTree, { selectedItemId: targetFolder?.id, rootItemIds: [
771
+ parentItem?.id ||
772
+ "11111111-1111-1111-1111-111111111111",
773
+ ], expandedItemId: targetFolder?.id, scrollToSelected: true, onSelectionChange: (nodes) => {
774
+ if (nodes[0]?.id) {
775
+ handleFolderSelection(nodes[0].id);
776
+ }
777
+ } }, `folder-tree-${showFolderTree ? "open" : "closed"}-${targetFolder?.id || "none"}`) })] })] }) }), step.fields.targetItemPrompt && parentItem && (_jsx("div", { className: "mt-1 text-xs text-green-600", children: "\u2713 Folder selected by AI" }))] }))] }), _jsxs("div", { className: "relative mb-4", children: [_jsx("label", { htmlFor: "language", className: "mb-1 block text-xs font-medium", children: "Language" }), _jsx("div", { className: "w-fit", children: _jsx(LanguageSelector, { selectedLanguage: language, disabled: isGenerating, onLanguageSelected: (language) => {
778
+ const languageCode = language.languageCode;
779
+ setLanguage(languageCode);
780
+ setData({
781
+ ...data,
782
+ language: languageCode,
783
+ });
784
+ }, showAllLanguages: true }) })] })] })) }) }) }), _jsx(WizardBoxConnector, { direction: "vertical" }), _jsx(Card, { title: "Content modules", icon: _jsx(Blocks, {}), description: "Select the component types the AI can choose from", className: "w-full md:w-96", collapsible: "yes", defaultCollapsed: "yes", summary: _jsx("div", { className: "text-xs text-gray-500", children: getComponentTypeSelectorSummary(data.selectedComponentTypes, availableComponentTypes) }), children: _jsx(ComponentTypeSelector, { selectedComponentTypes: data.selectedComponentTypes, setSelectedComponentTypes: (selectedTypes) => {
785
+ setData({
786
+ ...data,
787
+ selectedComponentTypes: selectedTypes,
788
+ });
789
+ }, schema: wizard.schema, data: data, setData: setData, step: step, availableComponentTypes: availableComponentTypes }) }), _jsx(WizardBoxConnector, { direction: "vertical" }), _jsxs(Card, { title: "Generate content", icon: _jsx(Wand2, {}), collapsible: "yes", defaultCollapsed: "mobileOnly", description: "Refine instructions and generate content", className: "w-full md:w-96", footer: _jsxs("div", { children: [_jsxs("div", { className: "flex w-full gap-2", children: [_jsx(ActionButton, { onClick: createLayout, disabled: isLoading ||
790
+ isCreatingComponents ||
791
+ isSelectingFolder ||
792
+ !data.selectedComponentTypes ||
793
+ data.selectedComponentTypes.length === 0 ||
794
+ (!pageItem &&
795
+ (!pageModel?.name ||
796
+ pageModel?.name.trim().length < 3 ||
797
+ !nameValidation.isValid)), isLoading: isLoading ||
798
+ isCreatingComponents ||
799
+ isSelectingFolder ||
800
+ !!editContext?.activeFieldActions.find((action) => action.state === "running"), loadingText: isSelectingFolder
801
+ ? "Selecting folder..."
802
+ : isGenerating
803
+ ? "Generating..."
804
+ : "Working", className: "w-full flex-1", children: !pageItem ? "Create page" : "Delete & Regenerate content" }), isLoading && (_jsx(ActionButton, { isLoading: false, onClick: () => abortController?.abort("User aborted"), children: "Abort" }))] }), message && _jsx("div", { className: "mt-2 text-xs", children: message })] }), children: [_jsx("div", { children: customInstructionsPanel() }), _jsxs("div", { className: "mt-2", children: [_jsxs("button", { className: "flex items-center text-xs text-blue-600 hover:underline", onClick: () => setShowDataObject(!showDataObject), children: [_jsx(ChevronDown, { strokeWidth: 1, className: `mr-1 h-4 w-4 transition-transform duration-200 ${showDataObject ? "rotate-180" : "rotate-0"}` }), showDataObject
805
+ ? "Hide data available to instructions"
806
+ : "Show data available to instructions"] }), _jsxs("div", { className: "mt-2 text-xs text-gray-600", children: [_jsx("div", { className: "font-medium", children: "Referencing data in instructions" }), _jsx("div", { children: "Use curly braces to inject values from the data object:" }), _jsxs("div", { className: "mt-1 grid gap-1", children: [_jsxs("div", { children: [_jsx("code", { children: "{title}" }), " inserts ", _jsx("code", { children: "data.title" })] }), _jsxs("div", { children: [_jsx("code", { children: "{data.user.name}" }), " inserts a nested property"] }), _jsxs("div", { children: [_jsx("code", { children: "{items.filter(x => x.active).length}" }), " supports JavaScript expressions"] }), _jsxs("div", { children: [_jsx("code", { children: "{data}" }), " inserts the entire data object as JSON"] })] }), _jsx("div", { className: "mt-1", children: "Arrays/objects are inserted as JSON text." })] }), showDataObject && (_jsx("div", { className: "mt-2 rounded border border-gray-200 bg-gray-50 p-2", children: _jsx("pre", { className: "max-h-64 overflow-auto text-xs break-words whitespace-pre-wrap", children: JSON.stringify(data, null, 2) }) }))] })] })] }), pageItem && (_jsxs(_Fragment, { children: [_jsx(WizardBoxConnector, {}), _jsx(Card, { title: "Page preview", icon: _jsx(PanelsTopLeft, {}), description: "After finalizing the page, you can edit, further enhance the content and share the page with your team for collaboration.", noPadding: true, className: "flex-1", showFullscreenButton: true, headerActions: _jsx(EditControls, { hideSuggestions: true }), children: _jsx("div", { className: cn("min-h-[300px] border-t border-gray-200", pageLoaded ? "h-full" : "h-0"), children: _jsx("div", { className: "bg-gray-5 h-full", children: _jsx(PageViewer, { name: "single", compareView: false, showFormEditor: editContext?.mode === "edit", followEditsDefault: true, pageViewContext: editContext.pageView, noMargins: true }) }) }) })] }))] }));
807
+ }
808
+ //# sourceMappingURL=ContentStep.js.map