@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.
- package/dist/SplashScreen.d.ts +1 -0
- package/dist/SplashScreen.js +110 -0
- package/dist/SplashScreen.js.map +1 -0
- package/dist/dist/PageWizard.d.ts +78 -0
- package/dist/dist/PageWizard.js +57 -0
- package/dist/dist/PageWizard.js.map +1 -0
- package/dist/dist/SplashScreen.d.ts +1 -0
- package/dist/dist/SplashScreen.js +110 -0
- package/dist/dist/SplashScreen.js.map +1 -0
- package/dist/dist/WizardBoxConnector.d.ts +4 -0
- package/dist/dist/WizardBoxConnector.js +6 -0
- package/dist/dist/WizardBoxConnector.js.map +1 -0
- package/dist/dist/WizardSteps.d.ts +8 -0
- package/dist/dist/WizardSteps.js +174 -0
- package/dist/dist/WizardSteps.js.map +1 -0
- package/dist/dist/config.d.ts +2 -0
- package/dist/dist/config.js +86 -0
- package/dist/dist/config.js.map +1 -0
- package/dist/dist/index.d.ts +2 -0
- package/dist/dist/index.js +2 -0
- package/dist/dist/index.js.map +1 -0
- package/dist/dist/service.d.ts +15 -0
- package/dist/dist/service.js +29 -0
- package/dist/dist/service.js.map +1 -0
- package/dist/dist/startPageWizardCommand.d.ts +13 -0
- package/dist/dist/startPageWizardCommand.js +31 -0
- package/dist/dist/startPageWizardCommand.js.map +1 -0
- package/dist/dist/steps/BuildPageStep.d.ts +2 -0
- package/dist/dist/steps/BuildPageStep.js +138 -0
- package/dist/dist/steps/BuildPageStep.js.map +1 -0
- package/dist/dist/steps/CollectStep.d.ts +2 -0
- package/dist/dist/steps/CollectStep.js +115 -0
- package/dist/dist/steps/CollectStep.js.map +1 -0
- package/dist/dist/steps/ComponentTypesSelector.d.ts +13 -0
- package/dist/dist/steps/ComponentTypesSelector.js +155 -0
- package/dist/dist/steps/ComponentTypesSelector.js.map +1 -0
- package/dist/dist/steps/Components.d.ts +9 -0
- package/dist/dist/steps/Components.js +89 -0
- package/dist/dist/steps/Components.js.map +1 -0
- package/dist/dist/steps/ContentStep.d.ts +2 -0
- package/dist/dist/steps/ContentStep.js +808 -0
- package/dist/dist/steps/ContentStep.js.map +1 -0
- package/dist/dist/steps/EditButton.d.ts +8 -0
- package/dist/dist/steps/EditButton.js +5 -0
- package/dist/dist/steps/EditButton.js.map +1 -0
- package/dist/dist/steps/FieldEditor.d.ts +5 -0
- package/dist/dist/steps/FieldEditor.js +27 -0
- package/dist/dist/steps/FieldEditor.js.map +1 -0
- package/dist/dist/steps/FindItemsStep.d.ts +2 -0
- package/dist/dist/steps/FindItemsStep.js +294 -0
- package/dist/dist/steps/FindItemsStep.js.map +1 -0
- package/dist/dist/steps/Generate.d.ts +6 -0
- package/dist/dist/steps/Generate.js +31 -0
- package/dist/dist/steps/Generate.js.map +1 -0
- package/dist/dist/steps/ImagesStep.d.ts +2 -0
- package/dist/dist/steps/ImagesStep.js +178 -0
- package/dist/dist/steps/ImagesStep.js.map +1 -0
- package/dist/dist/steps/LayoutStep.d.ts +2 -0
- package/dist/dist/steps/LayoutStep.js +128 -0
- package/dist/dist/steps/LayoutStep.js.map +1 -0
- package/dist/dist/steps/MetaDataStep.d.ts +2 -0
- package/dist/dist/steps/MetaDataStep.js +112 -0
- package/dist/dist/steps/MetaDataStep.js.map +1 -0
- package/dist/dist/steps/SchottSelectImagesStep.d.ts +2 -0
- package/dist/dist/steps/SchottSelectImagesStep.js +55 -0
- package/dist/dist/steps/SchottSelectImagesStep.js.map +1 -0
- package/dist/dist/steps/SelectStep.d.ts +2 -0
- package/dist/dist/steps/SelectStep.js +151 -0
- package/dist/dist/steps/SelectStep.js.map +1 -0
- package/dist/dist/steps/StructureStep.d.ts +2 -0
- package/dist/dist/steps/StructureStep.js +205 -0
- package/dist/dist/steps/StructureStep.js.map +1 -0
- package/dist/dist/steps/TranslateStep.d.ts +2 -0
- package/dist/dist/steps/TranslateStep.js +613 -0
- package/dist/dist/steps/TranslateStep.js.map +1 -0
- package/dist/dist/steps/schema.d.ts +13 -0
- package/dist/dist/steps/schema.js +139 -0
- package/dist/dist/steps/schema.js.map +1 -0
- package/dist/dist/steps/usePageCreator.d.ts +7 -0
- package/dist/dist/steps/usePageCreator.js +285 -0
- package/dist/dist/steps/usePageCreator.js.map +1 -0
- package/dist/dist/types.d.ts +27 -0
- package/dist/dist/types.js +2 -0
- package/dist/dist/types.js.map +1 -0
- package/dist/dist/usePageWizard.d.ts +22 -0
- package/dist/dist/usePageWizard.js +69 -0
- package/dist/dist/usePageWizard.js.map +1 -0
- package/dist/dist/utils/dataAccessor.d.ts +57 -0
- package/dist/dist/utils/dataAccessor.js +323 -0
- package/dist/dist/utils/dataAccessor.js.map +1 -0
- 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
|