@alpaca-editor/core 1.0.3942 → 1.0.3943
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/.prettierrc +3 -0
- package/build.css +3 -0
- package/components.json +21 -0
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +23 -21
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/FieldActionsOverlay.js +0 -2
- package/dist/editor/FieldActionsOverlay.js.map +1 -1
- package/dist/editor/ScrollingContentTree.js +1 -1
- package/dist/editor/ScrollingContentTree.js.map +1 -1
- package/dist/editor/Titlebar.js +1 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/GhostWriter.js +24 -3
- package/dist/editor/ai/GhostWriter.js.map +1 -1
- package/dist/editor/client/EditorClient.js +7 -7
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js +60 -10
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +48 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +116 -65
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +5 -2
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/ui/ItemSearch.js +14 -8
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/PerfectTree.d.ts +4 -2
- package/dist/editor/ui/PerfectTree.js +78 -4
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/Splitter.js +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +8 -2
- package/eslint.config.mjs +4 -0
- package/images/bg-shape-black.webp +0 -0
- package/images/wizard-bg.png +0 -0
- package/images/wizard-tour.png +0 -0
- package/images/wizard.png +0 -0
- package/package.json +2 -8
- package/src/client-components/api.ts +6 -0
- package/src/client-components/index.ts +19 -0
- package/src/components/ActionButton.tsx +50 -0
- package/src/components/Error.tsx +57 -0
- package/src/components/ui/CardConnector.tsx +56 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/card.tsx +372 -0
- package/src/components/ui/context-menu.tsx +250 -0
- package/src/config/config.tsx +917 -0
- package/src/config/types.ts +286 -0
- package/src/editor/ComponentInfo.tsx +90 -0
- package/src/editor/ConfirmationDialog.tsx +103 -0
- package/src/editor/ContentTree.tsx +733 -0
- package/src/editor/ContextMenu.tsx +230 -0
- package/src/editor/Editor.tsx +90 -0
- package/src/editor/EditorWarning.tsx +34 -0
- package/src/editor/EditorWarnings.tsx +33 -0
- package/src/editor/FieldActionsOverlay.tsx +296 -0
- package/src/editor/FieldEditorPopup.tsx +65 -0
- package/src/editor/FieldHistory.tsx +75 -0
- package/src/editor/FieldList.tsx +190 -0
- package/src/editor/FieldListField.tsx +391 -0
- package/src/editor/FieldListFieldWithFallbacks.tsx +217 -0
- package/src/editor/FloatingToolbar.tsx +163 -0
- package/src/editor/ImageEditor.tsx +128 -0
- package/src/editor/ItemInfo.tsx +90 -0
- package/src/editor/LinkEditorDialog.tsx +196 -0
- package/src/editor/MainLayout.tsx +95 -0
- package/src/editor/MobileLayout.tsx +68 -0
- package/src/editor/NewEditorClient.tsx +11 -0
- package/src/editor/PictureCropper.tsx +568 -0
- package/src/editor/PictureEditor.tsx +301 -0
- package/src/editor/PictureEditorDialog.tsx +381 -0
- package/src/editor/PublishDialog.ignore +74 -0
- package/src/editor/ScrollingContentTree.tsx +68 -0
- package/src/editor/Terminal.tsx +227 -0
- package/src/editor/Titlebar.tsx +104 -0
- package/src/editor/ai/AiPopup.tsx +59 -0
- package/src/editor/ai/AiResponseMessage.tsx +106 -0
- package/src/editor/ai/AiTerminal.tsx +503 -0
- package/src/editor/ai/AiToolCall.tsx +61 -0
- package/src/editor/ai/EditorAiTerminal.tsx +20 -0
- package/src/editor/ai/GhostWriter.tsx +480 -0
- package/src/editor/ai/aiPageModel.ts +108 -0
- package/src/editor/ai/editorAiContext.ts +18 -0
- package/src/editor/client/AboutDialog.tsx +44 -0
- package/src/editor/client/EditorClient.tsx +2241 -0
- package/src/editor/client/GenericDialog.tsx +50 -0
- package/src/editor/client/editContext.ts +416 -0
- package/src/editor/client/helpers.ts +44 -0
- package/src/editor/client/itemsRepository.ts +574 -0
- package/src/editor/client/operations.ts +768 -0
- package/src/editor/client/pageModelBuilder.ts +219 -0
- package/src/editor/commands/commands.ts +22 -0
- package/src/editor/commands/componentCommands.tsx +431 -0
- package/src/editor/commands/createVersionCommand.ts +33 -0
- package/src/editor/commands/deleteVersionCommand.ts +71 -0
- package/src/editor/commands/itemCommands.tsx +351 -0
- package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
- package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +27 -0
- package/src/editor/commands/undo.ts +39 -0
- package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
- package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
- package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
- package/src/editor/component-designer/ComponentEditor.tsx +97 -0
- package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
- package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
- package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
- package/src/editor/component-designer/PlaceholdersEditor.tsx +179 -0
- package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
- package/src/editor/component-designer/TemplateEditor.tsx +236 -0
- package/src/editor/component-designer/aiContext.ts +23 -0
- package/src/editor/componentTreeHelper.tsx +116 -0
- package/src/editor/context-menu/CopyMoveMenu.tsx +103 -0
- package/src/editor/context-menu/InsertMenu.tsx +347 -0
- package/src/editor/control-center/About.tsx +342 -0
- package/src/editor/control-center/ControlCenterMenu.tsx +76 -0
- package/src/editor/control-center/IndexOverview.tsx +50 -0
- package/src/editor/control-center/IndexSettings.tsx +266 -0
- package/src/editor/control-center/Info.tsx +104 -0
- package/src/editor/control-center/QuotaInfo.tsx +301 -0
- package/src/editor/control-center/Status.tsx +113 -0
- package/src/editor/control-center/WebSocketMessages.tsx +155 -0
- package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
- package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
- package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
- package/src/editor/editor-warnings/NoWriteAccess.tsx +16 -0
- package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
- package/src/editor/field-types/AttachmentEditor.tsx +9 -0
- package/src/editor/field-types/CheckboxEditor.tsx +47 -0
- package/src/editor/field-types/DropLinkEditor.tsx +80 -0
- package/src/editor/field-types/DropListEditor.tsx +84 -0
- package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +188 -0
- package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
- package/src/editor/field-types/MultiLineText.tsx +82 -0
- package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
- package/src/editor/field-types/RawEditor.tsx +53 -0
- package/src/editor/field-types/ReactQuill.tsx +580 -0
- package/src/editor/field-types/RichTextEditor.tsx +22 -0
- package/src/editor/field-types/RichTextEditorComponent.tsx +127 -0
- package/src/editor/field-types/SingleLineText.tsx +174 -0
- package/src/editor/field-types/TreeListEditor.tsx +261 -0
- package/src/editor/fieldTypes.ts +140 -0
- package/src/editor/media-selector/AiImageSearch.tsx +185 -0
- package/src/editor/media-selector/AiImageSearchPrompt.tsx +94 -0
- package/src/editor/media-selector/MediaFolderBrowser.tsx +321 -0
- package/src/editor/media-selector/MediaSelector.tsx +42 -0
- package/src/editor/media-selector/Preview.tsx +14 -0
- package/src/editor/media-selector/Thumbnails.tsx +48 -0
- package/src/editor/media-selector/TreeSelector.tsx +292 -0
- package/src/editor/media-selector/UploadZone.tsx +137 -0
- package/src/editor/media-selector/index.ts +8 -0
- package/src/editor/menubar/ActionsMenu.tsx +94 -0
- package/src/editor/menubar/ActiveUsers.tsx +17 -0
- package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
- package/src/editor/menubar/BrowseHistory.tsx +28 -0
- package/src/editor/menubar/ItemLanguageVersion.tsx +76 -0
- package/src/editor/menubar/LanguageSelector.tsx +226 -0
- package/src/editor/menubar/Menu.tsx +83 -0
- package/src/editor/menubar/NavButtons.tsx +74 -0
- package/src/editor/menubar/PageSelector.tsx +278 -0
- package/src/editor/menubar/PageViewerControls.tsx +120 -0
- package/src/editor/menubar/PreviewSecondaryControls.tsx +18 -0
- package/src/editor/menubar/SecondaryControls.tsx +45 -0
- package/src/editor/menubar/Separator.tsx +12 -0
- package/src/editor/menubar/SiteInfo.tsx +53 -0
- package/src/editor/menubar/User.tsx +27 -0
- package/src/editor/menubar/VersionSelector.tsx +142 -0
- package/src/editor/page-editor-chrome/CommentHighlighting.tsx +307 -0
- package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
- package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +59 -0
- package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
- package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
- package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
- package/src/editor/page-editor-chrome/FrameMenu.tsx +338 -0
- package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
- package/src/editor/page-editor-chrome/InlineEditor.tsx +765 -0
- package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
- package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
- package/src/editor/page-editor-chrome/PageEditorChrome.tsx +122 -0
- package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +161 -0
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +169 -0
- package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +315 -0
- package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +300 -0
- package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +40 -0
- package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +828 -0
- package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
- package/src/editor/page-viewer/EditorForm.tsx +262 -0
- package/src/editor/page-viewer/MiniMap.tsx +362 -0
- package/src/editor/page-viewer/PageViewer.tsx +169 -0
- package/src/editor/page-viewer/PageViewerFrame.tsx +1022 -0
- package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +412 -0
- package/src/editor/page-viewer/pageViewContext.ts +186 -0
- package/src/editor/pageModel.ts +220 -0
- package/src/editor/picture-shared.tsx +53 -0
- package/src/editor/reviews/Comment.tsx +308 -0
- package/src/editor/reviews/Comments.tsx +125 -0
- package/src/editor/reviews/DiffView.tsx +109 -0
- package/src/editor/reviews/PreviewInfo.tsx +35 -0
- package/src/editor/reviews/Reviews.tsx +280 -0
- package/src/editor/reviews/SuggestedEdit.tsx +316 -0
- package/src/editor/reviews/reviewCommands.tsx +47 -0
- package/src/editor/reviews/useReviews.tsx +70 -0
- package/src/editor/services/aiService.ts +173 -0
- package/src/editor/services/componentDesignerService.ts +151 -0
- package/src/editor/services/contentService.ts +180 -0
- package/src/editor/services/editService.ts +488 -0
- package/src/editor/services/indexService.ts +24 -0
- package/src/editor/services/reviewsService.ts +53 -0
- package/src/editor/services/serviceHelper.ts +95 -0
- package/src/editor/services/suggestedEditsService.ts +39 -0
- package/src/editor/services/systemService.ts +5 -0
- package/src/editor/services/translationService.ts +21 -0
- package/src/editor/services-server/api.ts +150 -0
- package/src/editor/services-server/graphQL.ts +106 -0
- package/src/editor/sidebar/ComponentPalette.tsx +161 -0
- package/src/editor/sidebar/ComponentTree.tsx +549 -0
- package/src/editor/sidebar/Debug.tsx +111 -0
- package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
- package/src/editor/sidebar/EditHistory.tsx +134 -0
- package/src/editor/sidebar/GraphQL.tsx +164 -0
- package/src/editor/sidebar/Insert.tsx +35 -0
- package/src/editor/sidebar/MainContentTree.tsx +102 -0
- package/src/editor/sidebar/Performance.tsx +53 -0
- package/src/editor/sidebar/Sessions.tsx +35 -0
- package/src/editor/sidebar/Sidebar.tsx +20 -0
- package/src/editor/sidebar/SidebarView.tsx +152 -0
- package/src/editor/sidebar/Translations.tsx +295 -0
- package/src/editor/sidebar/Validation.tsx +102 -0
- package/src/editor/sidebar/ViewSelector.tsx +60 -0
- package/src/editor/sidebar/Workbox.tsx +209 -0
- package/src/editor/ui/CenteredMessage.tsx +7 -0
- package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +81 -0
- package/src/editor/ui/CopyToClipboardButton.tsx +24 -0
- package/src/editor/ui/DialogButtons.tsx +11 -0
- package/src/editor/ui/Icons.tsx +709 -0
- package/src/editor/ui/ItemList.tsx +76 -0
- package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
- package/src/editor/ui/ItemSearch.tsx +159 -0
- package/src/editor/ui/PerfectTree.tsx +676 -0
- package/src/editor/ui/Section.tsx +35 -0
- package/src/editor/ui/SimpleIconButton.tsx +54 -0
- package/src/editor/ui/SimpleMenu.tsx +40 -0
- package/src/editor/ui/SimpleTable.tsx +60 -0
- package/src/editor/ui/SimpleTabs.tsx +60 -0
- package/src/editor/ui/SimpleToolbar.tsx +7 -0
- package/src/editor/ui/Spinner.tsx +9 -0
- package/src/editor/ui/Splitter.tsx +420 -0
- package/src/editor/ui/StackedPanels.tsx +134 -0
- package/src/editor/ui/Toolbar.tsx +7 -0
- package/src/editor/utils/id-helper.ts +3 -0
- package/src/editor/utils/insertOptions.ts +69 -0
- package/src/editor/utils/itemutils.ts +29 -0
- package/src/editor/utils/useMemoDebug.ts +28 -0
- package/src/editor/utils.ts +486 -0
- package/src/editor/views/CompareView.tsx +245 -0
- package/src/editor/views/EditView.tsx +27 -0
- package/src/editor/views/ItemEditor.tsx +58 -0
- package/src/editor/views/MediaFolderEditView.tsx +66 -0
- package/src/editor/views/SingleEditView.tsx +57 -0
- package/src/fonts/Geist-Black.woff2 +0 -0
- package/src/fonts/Geist-Bold.woff2 +0 -0
- package/src/fonts/Geist-ExtraBold.woff2 +0 -0
- package/src/fonts/Geist-ExtraLight.woff2 +0 -0
- package/src/fonts/Geist-Light.woff2 +0 -0
- package/src/fonts/Geist-Medium.woff2 +0 -0
- package/src/fonts/Geist-Regular.woff2 +0 -0
- package/src/fonts/Geist-SemiBold.woff2 +0 -0
- package/src/fonts/Geist-Thin.woff2 +0 -0
- package/src/fonts/Geist[wght].woff2 +0 -0
- package/src/fonts/index.ts +10 -0
- package/src/index.ts +23 -0
- package/src/lib/safelist.tsx +16 -0
- package/src/lib/utils.ts +6 -0
- package/src/page-wizard/PageWizard.tsx +139 -0
- package/src/page-wizard/WizardBox.tsx +4 -0
- package/src/page-wizard/WizardBoxConnector.tsx +56 -0
- package/src/page-wizard/WizardSteps.tsx +458 -0
- package/src/page-wizard/service.ts +35 -0
- package/src/page-wizard/startPageWizardCommand.ts +26 -0
- package/src/page-wizard/steps/BuildPageStep.tsx +259 -0
- package/src/page-wizard/steps/CollectStep.tsx +296 -0
- package/src/page-wizard/steps/ComponentTypesSelector.tsx +454 -0
- package/src/page-wizard/steps/Components.tsx +193 -0
- package/src/page-wizard/steps/ContentStep.tsx +890 -0
- package/src/page-wizard/steps/EditButton.tsx +34 -0
- package/src/page-wizard/steps/FieldEditor.tsx +102 -0
- package/src/page-wizard/steps/Generate.tsx +60 -0
- package/src/page-wizard/steps/ImagesStep.tsx +382 -0
- package/src/page-wizard/steps/LayoutStep.tsx +227 -0
- package/src/page-wizard/steps/MetaDataStep.tsx +173 -0
- package/src/page-wizard/steps/SelectStep.tsx +281 -0
- package/src/page-wizard/steps/schema.ts +180 -0
- package/src/page-wizard/steps/usePageCreator.ts +325 -0
- package/src/page-wizard/usePageWizard.ts +79 -0
- package/src/revision.ts +2 -0
- package/src/splash-screen/NewPage.tsx +294 -0
- package/src/splash-screen/OpenPage.tsx +113 -0
- package/src/splash-screen/RecentPages.tsx +123 -0
- package/src/splash-screen/SectionHeadline.tsx +21 -0
- package/src/splash-screen/SplashScreen.tsx +195 -0
- package/src/tour/Tour.tsx +566 -0
- package/src/tour/default-tour.tsx +301 -0
- package/src/tour/preview-tour.tsx +128 -0
- package/src/types.ts +335 -0
- package/styles.css +765 -0
- package/tsconfig.build.json +31 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,890 @@
|
|
|
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
|
+
import { InputText } from "primereact/inputtext";
|
|
28
|
+
import { LanguageSelector } from "../../editor/menubar/LanguageSelector";
|
|
29
|
+
|
|
30
|
+
import { convertToAiPageModel } from "../../editor/ai/aiPageModel";
|
|
31
|
+
import { WizardBox } from "../WizardBox";
|
|
32
|
+
import { Wand2, PanelsTopLeft, Settings, FileText } from "lucide-react";
|
|
33
|
+
import { cn } from "../../lib/utils";
|
|
34
|
+
import { WizardBoxConnector } from "../WizardBoxConnector";
|
|
35
|
+
import { ItemDescriptor, FullItem } from "../../editor/pageModel";
|
|
36
|
+
import Generate from "./Generate";
|
|
37
|
+
|
|
38
|
+
export function ContentStep({
|
|
39
|
+
wizard,
|
|
40
|
+
step,
|
|
41
|
+
parentItem,
|
|
42
|
+
pageModel,
|
|
43
|
+
setPageModel,
|
|
44
|
+
data,
|
|
45
|
+
setData,
|
|
46
|
+
internalState,
|
|
47
|
+
setInternalState,
|
|
48
|
+
setStepCompleted,
|
|
49
|
+
}: StepComponentProps) {
|
|
50
|
+
const [pageLoaded, setPageLoaded] = useState(false);
|
|
51
|
+
const pageLoadedRef = useRef(false);
|
|
52
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
53
|
+
const [availableComponentTypes, setAvailableComponentTypes] = useState<
|
|
54
|
+
string[]
|
|
55
|
+
>([]);
|
|
56
|
+
|
|
57
|
+
const editContext = useEditContext();
|
|
58
|
+
const editContextRef = useEditContextRef();
|
|
59
|
+
const [abortController, setAbortController] = useState<AbortController>();
|
|
60
|
+
const [message, setMessage] = useState<string>();
|
|
61
|
+
const [isCreatingComponents, setIsCreatingComponents] = useState(false);
|
|
62
|
+
const [pageItem, setPageItem] = useState<ItemDescriptor>();
|
|
63
|
+
|
|
64
|
+
// New state for name and language functionality
|
|
65
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
66
|
+
const [language, setLanguage] = useState<string>(
|
|
67
|
+
parentItem?.language || "en",
|
|
68
|
+
);
|
|
69
|
+
const [fullParentItem, setFullParentItem] = useState<FullItem>();
|
|
70
|
+
const [nameValidation, setNameValidation] = useState<{
|
|
71
|
+
isValid: boolean;
|
|
72
|
+
message?: string;
|
|
73
|
+
}>({ isValid: false });
|
|
74
|
+
|
|
75
|
+
const modifiedFieldsContext = useModifiedFieldsContext();
|
|
76
|
+
|
|
77
|
+
const pageCreator = usePageCreator(pageItem, wizard, setPageModel);
|
|
78
|
+
|
|
79
|
+
if (!parentItem) return "No parent item";
|
|
80
|
+
|
|
81
|
+
const checkPageName = useCallback(async () => {
|
|
82
|
+
if (!parentItem) {
|
|
83
|
+
setNameValidation({ isValid: false, message: "No parent item" });
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if page name is valid
|
|
88
|
+
if (!pageModel.name || pageModel.name.trim().length < 3) {
|
|
89
|
+
setNameValidation({
|
|
90
|
+
isValid: false,
|
|
91
|
+
message: "Page name must be at least 3 characters long",
|
|
92
|
+
});
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Check if page with same name already exists
|
|
98
|
+
const children = await getChildren(
|
|
99
|
+
parentItem.id,
|
|
100
|
+
editContext?.sessionId ?? "",
|
|
101
|
+
[],
|
|
102
|
+
false,
|
|
103
|
+
editContext?.contentEditorItem?.language || "en",
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
children.find(
|
|
108
|
+
(x) =>
|
|
109
|
+
x.name.toLocaleLowerCase() ===
|
|
110
|
+
pageModel.name.trim().toLocaleLowerCase(),
|
|
111
|
+
)
|
|
112
|
+
) {
|
|
113
|
+
setNameValidation({
|
|
114
|
+
isValid: false,
|
|
115
|
+
message: "A page with this name already exists",
|
|
116
|
+
});
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setNameValidation({ isValid: true });
|
|
121
|
+
return true;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
setNameValidation({
|
|
124
|
+
isValid: false,
|
|
125
|
+
message: "Error checking page name",
|
|
126
|
+
});
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}, [
|
|
130
|
+
parentItem,
|
|
131
|
+
pageModel.name,
|
|
132
|
+
editContext?.sessionId,
|
|
133
|
+
editContext?.contentEditorItem?.language,
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
const handleInputChange = (field: keyof typeof pageModel, value: string) => {
|
|
137
|
+
const updatedPageModel = {
|
|
138
|
+
...pageModel,
|
|
139
|
+
[field]: value,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
setPageModel(updatedPageModel);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const createPageIfNeeded = useCallback(async (): Promise<boolean> => {
|
|
146
|
+
if (!editContext || !parentItem || pageItem) return true; // Page already exists
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
if (!(await checkPageName())) {
|
|
150
|
+
console.error("Page name validation failed");
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const result = await editContext.operations.createItem(
|
|
155
|
+
{
|
|
156
|
+
...getItemDescriptor(parentItem),
|
|
157
|
+
language: parentItem.language || "en",
|
|
158
|
+
},
|
|
159
|
+
wizard.templateId,
|
|
160
|
+
pageModel.name,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (result) {
|
|
164
|
+
editContext?.loadItem(result, { addToBrowseHistory: true });
|
|
165
|
+
const item = await editContext?.itemsRepository.getItem(result);
|
|
166
|
+
if (item && setPageItem) {
|
|
167
|
+
setPageItem(item);
|
|
168
|
+
return true;
|
|
169
|
+
} else {
|
|
170
|
+
console.error("Failed to load newly created item", result);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("Error creating page", error);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}, [
|
|
180
|
+
editContext,
|
|
181
|
+
parentItem,
|
|
182
|
+
pageItem,
|
|
183
|
+
checkPageName,
|
|
184
|
+
wizard.templateId,
|
|
185
|
+
pageModel.name,
|
|
186
|
+
setPageItem,
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
editContext?.setMode("edit");
|
|
191
|
+
if (!internalState.layoutInstructions) {
|
|
192
|
+
setInternalState({
|
|
193
|
+
...internalState,
|
|
194
|
+
layoutInstructions: step.fields.instructions,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Extract available component types from schema
|
|
199
|
+
if (wizard.schema) {
|
|
200
|
+
try {
|
|
201
|
+
const schema =
|
|
202
|
+
typeof wizard.schema === "string"
|
|
203
|
+
? JSON.parse(wizard.schema)
|
|
204
|
+
: wizard.schema;
|
|
205
|
+
|
|
206
|
+
// Extract component types (reusing logic from ComponentTypeSelector)
|
|
207
|
+
const extractComponentTypes = (items: any): string[] => {
|
|
208
|
+
if (!items || !Array.isArray(items)) return [];
|
|
209
|
+
let types: string[] = [];
|
|
210
|
+
for (const item of items) {
|
|
211
|
+
if ("type" in item) {
|
|
212
|
+
types.push(item.type);
|
|
213
|
+
}
|
|
214
|
+
if ("placeholders" in item && Array.isArray(item.placeholders)) {
|
|
215
|
+
for (const placeholder of item.placeholders) {
|
|
216
|
+
if (
|
|
217
|
+
placeholder.components &&
|
|
218
|
+
Array.isArray(placeholder.components)
|
|
219
|
+
) {
|
|
220
|
+
types = [
|
|
221
|
+
...types,
|
|
222
|
+
...extractComponentTypes(placeholder.components),
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if ("components" in item && Array.isArray(item.components)) {
|
|
228
|
+
types = [...types, ...extractComponentTypes(item.components)];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return types;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const componentTypes = extractComponentTypes(schema);
|
|
235
|
+
const uniqueTypes = Array.from(new Set(componentTypes)).sort();
|
|
236
|
+
setAvailableComponentTypes(uniqueTypes);
|
|
237
|
+
|
|
238
|
+
// Initialize selected component types if not already set
|
|
239
|
+
if (
|
|
240
|
+
!data.selectedComponentTypes ||
|
|
241
|
+
!Array.isArray(data.selectedComponentTypes)
|
|
242
|
+
) {
|
|
243
|
+
// Parse preselected components from step if available
|
|
244
|
+
let stepPreselectedTypes: string[] = [];
|
|
245
|
+
if (step.fields["preselectedComponents"]) {
|
|
246
|
+
stepPreselectedTypes = step.fields["preselectedComponents"]
|
|
247
|
+
.split(/[,\n\r]+/)
|
|
248
|
+
.map((type: string) => type.trim())
|
|
249
|
+
.filter((type: string) => type && uniqueTypes.includes(type));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Set initial selection
|
|
253
|
+
const initialSelection =
|
|
254
|
+
stepPreselectedTypes.length > 0
|
|
255
|
+
? stepPreselectedTypes
|
|
256
|
+
: uniqueTypes;
|
|
257
|
+
|
|
258
|
+
setData({
|
|
259
|
+
...data,
|
|
260
|
+
selectedComponentTypes: initialSelection,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error("Error extracting component types:", error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}, []);
|
|
268
|
+
|
|
269
|
+
// Check page name whenever it changes
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
if (pageModel.name && !pageItem) {
|
|
272
|
+
checkPageName();
|
|
273
|
+
}
|
|
274
|
+
}, [pageModel.name, checkPageName]);
|
|
275
|
+
|
|
276
|
+
// Load parent item details
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
const loadParentItem = async () => {
|
|
279
|
+
if (!parentItem) return;
|
|
280
|
+
const item = await editContext?.itemsRepository.getItem(parentItem);
|
|
281
|
+
setFullParentItem(item);
|
|
282
|
+
};
|
|
283
|
+
loadParentItem();
|
|
284
|
+
}, [parentItem]);
|
|
285
|
+
|
|
286
|
+
// Auto-generate page name and meta description if not set
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (!editContext) return;
|
|
289
|
+
|
|
290
|
+
const generatePageName = async () => {
|
|
291
|
+
const metaInstructions =
|
|
292
|
+
step.fields["Instructions for generating item name and meta fields"];
|
|
293
|
+
|
|
294
|
+
setIsGenerating(true);
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const abortController = new AbortController();
|
|
298
|
+
|
|
299
|
+
const result = await executePrompt(
|
|
300
|
+
[
|
|
301
|
+
{
|
|
302
|
+
content: `${metaInstructions?.trim()} Reply with a json object of type PageModel = { name: string; };
|
|
303
|
+
The item name should be a valid sitecore item name. Spaces are allowed but no special characters or Umlaute.
|
|
304
|
+
The language of the page is ${language}.
|
|
305
|
+
Input data: ${JSON.stringify(data)}`,
|
|
306
|
+
name: "system",
|
|
307
|
+
role: "system",
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
editContext,
|
|
311
|
+
createWizardAiContext,
|
|
312
|
+
{},
|
|
313
|
+
{ signal: abortController.signal },
|
|
314
|
+
"gpt-4.1",
|
|
315
|
+
(response) => {
|
|
316
|
+
try {
|
|
317
|
+
const newLayout = JSON.parse(response.content) as WizardPageModel;
|
|
318
|
+
|
|
319
|
+
setPageModel({
|
|
320
|
+
...pageModel,
|
|
321
|
+
name: newLayout.name,
|
|
322
|
+
});
|
|
323
|
+
} catch (parseError: unknown) {}
|
|
324
|
+
},
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const pageModelResult = JSON.parse(result.content) as WizardPageModel;
|
|
328
|
+
setPageModel({
|
|
329
|
+
...pageModel,
|
|
330
|
+
name: pageModelResult.name,
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error("Error generating page name", error);
|
|
334
|
+
} finally {
|
|
335
|
+
setIsGenerating(false);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
if (!pageModel.name) generatePageName();
|
|
340
|
+
}, []);
|
|
341
|
+
|
|
342
|
+
const createComponents = async () => {
|
|
343
|
+
if (!pageLoaded) return;
|
|
344
|
+
if (pageModel?.components && !isCreatingComponents) {
|
|
345
|
+
try {
|
|
346
|
+
setIsCreatingComponents(true);
|
|
347
|
+
|
|
348
|
+
await pageCreator.createComponentsRecursively(
|
|
349
|
+
pageModel.components,
|
|
350
|
+
"root",
|
|
351
|
+
);
|
|
352
|
+
setInternalState((prev: any) => ({
|
|
353
|
+
...prev,
|
|
354
|
+
componentsCreated: true,
|
|
355
|
+
}));
|
|
356
|
+
} finally {
|
|
357
|
+
setIsCreatingComponents(false);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
if (
|
|
364
|
+
internalState.componentsCreated &&
|
|
365
|
+
!isCreatingComponents &&
|
|
366
|
+
!isLoading
|
|
367
|
+
) {
|
|
368
|
+
editContext?.requestRefresh("immediate");
|
|
369
|
+
}
|
|
370
|
+
}, [isCreatingComponents, isLoading]);
|
|
371
|
+
|
|
372
|
+
const createComponentsThrottled = useThrottledCallback(createComponents, 300);
|
|
373
|
+
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
createComponentsThrottled();
|
|
376
|
+
}, [pageModel]);
|
|
377
|
+
|
|
378
|
+
// useEffect(() => {
|
|
379
|
+
// if (!pageItem) {
|
|
380
|
+
// setPageItem(pageItem);
|
|
381
|
+
// }
|
|
382
|
+
// }, [pageItem]);
|
|
383
|
+
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
//console.log("$$$$ Active field actions: ", editContext?.activeFieldActions);
|
|
386
|
+
if (
|
|
387
|
+
editContext &&
|
|
388
|
+
editContext.activeFieldActions &&
|
|
389
|
+
!editContext.activeFieldActions.find((x) => x.state === "running")
|
|
390
|
+
) {
|
|
391
|
+
console.log("$$$ Refreshing!");
|
|
392
|
+
editContext.requestRefresh("immediate");
|
|
393
|
+
}
|
|
394
|
+
}, [editContext?.activeFieldActions]);
|
|
395
|
+
|
|
396
|
+
useEffect(() => {
|
|
397
|
+
if (
|
|
398
|
+
editContext &&
|
|
399
|
+
editContext.page &&
|
|
400
|
+
editContext.page.item &&
|
|
401
|
+
pageItem &&
|
|
402
|
+
pageItem.id === editContext.page.item.descriptor.id
|
|
403
|
+
) {
|
|
404
|
+
if (!pageLoaded) {
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
setPageLoaded(true);
|
|
407
|
+
pageLoadedRef.current = true;
|
|
408
|
+
}, 1000);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}, [editContext?.page, pageItem]);
|
|
412
|
+
|
|
413
|
+
const createLayout = async () => {
|
|
414
|
+
try {
|
|
415
|
+
if (!editContext) return;
|
|
416
|
+
setMessage("");
|
|
417
|
+
setIsLoading(true);
|
|
418
|
+
setStepCompleted(false);
|
|
419
|
+
|
|
420
|
+
// Create page if it doesn't exist yet
|
|
421
|
+
let currentPageItem = pageItem;
|
|
422
|
+
// Wait for pageLoaded to become true before continuing
|
|
423
|
+
const waitForPageLoaded = async (): Promise<boolean> => {
|
|
424
|
+
const maxWaitTime = 10000; // 10 seconds max wait
|
|
425
|
+
const pollInterval = 100; // Check every 100ms
|
|
426
|
+
let elapsed = 0;
|
|
427
|
+
|
|
428
|
+
while (elapsed < maxWaitTime) {
|
|
429
|
+
// Check if page is loaded using the ref which gets updated by the useEffect
|
|
430
|
+
if (pageLoadedRef.current) {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
435
|
+
elapsed += pollInterval;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return false;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
if (!currentPageItem) {
|
|
442
|
+
const pageCreated = await createPageIfNeeded();
|
|
443
|
+
if (!pageCreated) {
|
|
444
|
+
setMessage(
|
|
445
|
+
"Failed to create page. Please check the page name and try again.",
|
|
446
|
+
);
|
|
447
|
+
setIsLoading(false);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const isPageLoaded = await waitForPageLoaded();
|
|
452
|
+
if (!isPageLoaded) {
|
|
453
|
+
setMessage("Page creation timed out. Please try again.");
|
|
454
|
+
setIsLoading(false);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (internalState.componentsCreated) {
|
|
460
|
+
console.log("Wiping components");
|
|
461
|
+
if (currentPageItem) {
|
|
462
|
+
const wipeResult: ExecutionResult<any> = await wipeComponents(
|
|
463
|
+
getItemDescriptor(currentPageItem),
|
|
464
|
+
);
|
|
465
|
+
if (wipeResult.type !== "success") {
|
|
466
|
+
console.error("Failed to wipe components:", wipeResult);
|
|
467
|
+
setMessage(
|
|
468
|
+
wipeResult.summary ||
|
|
469
|
+
"Failed to clear existing components. Please try again.",
|
|
470
|
+
);
|
|
471
|
+
setIsLoading(false);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
console.log("Wiping components 2");
|
|
476
|
+
editContextRef.current?.itemsRepository.clear();
|
|
477
|
+
modifiedFieldsContext?.clear();
|
|
478
|
+
setInternalState((prev: any) => ({
|
|
479
|
+
...prev,
|
|
480
|
+
componentsCreated: false,
|
|
481
|
+
}));
|
|
482
|
+
|
|
483
|
+
setPageModel((prev) => ({
|
|
484
|
+
...prev,
|
|
485
|
+
components: [],
|
|
486
|
+
message: "",
|
|
487
|
+
}));
|
|
488
|
+
|
|
489
|
+
pageLoadedRef.current = false;
|
|
490
|
+
|
|
491
|
+
console.log("Wiping components 3");
|
|
492
|
+
editContextRef.current?.requestRefresh("immediate");
|
|
493
|
+
await waitForPageLoaded();
|
|
494
|
+
console.log("Wiping components 4");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Parse schema if it's a string
|
|
498
|
+
const schema =
|
|
499
|
+
typeof wizard.schema === "string"
|
|
500
|
+
? JSON.parse(wizard.schema)
|
|
501
|
+
: wizard.schema;
|
|
502
|
+
|
|
503
|
+
// Filter the schema based on selected component types and placeholders
|
|
504
|
+
const filteredSchema = convertPageSchemaToWizardComponents(
|
|
505
|
+
schema,
|
|
506
|
+
data.selectedComponentTypes,
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const localAbortController = new AbortController();
|
|
510
|
+
setAbortController(localAbortController);
|
|
511
|
+
|
|
512
|
+
// Get the existing page model
|
|
513
|
+
const existingPageModel = await convertToAiPageModel(
|
|
514
|
+
editContextRef.current?.page!,
|
|
515
|
+
editContextRef.current!,
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
console.log("Existing page model: ", existingPageModel);
|
|
519
|
+
|
|
520
|
+
const prompt = [
|
|
521
|
+
{
|
|
522
|
+
content: `${internalState.layoutInstructions ? internalState.layoutInstructions?.trim() : ""} Reply with a json object of type PageModel = { components: Component[]; message: string; };
|
|
523
|
+
Component = { id: string | undefined, name: string, type: string; fields: Field[]; placeholder?: string; children?: Component[]; };
|
|
524
|
+
Field = { name: string; value: string; type: string; };
|
|
525
|
+
Generate a descriptive name for each component including the topic.
|
|
526
|
+
Only use component types that are in the page schema.
|
|
527
|
+
Keep existing components with their ids. Leave id field empty for new components.
|
|
528
|
+
Existing page model: ${JSON.stringify(existingPageModel)}
|
|
529
|
+
Fill empty fields of existing components before you insert new components.
|
|
530
|
+
Component types: ${JSON.stringify(
|
|
531
|
+
filteredSchema,
|
|
532
|
+
)} Root level component types ${filteredSchema
|
|
533
|
+
.filter((c) => c.allowedOnRoot)
|
|
534
|
+
.map((c) => c.type)
|
|
535
|
+
.join(", ")}
|
|
536
|
+
Tell the user your reasoning in the message field.
|
|
537
|
+
If the user provided image ids, fill them into picture / image fields with an "id:" prefix. The image id needs to be guid.
|
|
538
|
+
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.
|
|
539
|
+
In any case the field value must start with "id:" or "generate:", nothing else. Do not inject json into picture fields even if the existing value is json.
|
|
540
|
+
The language of the page is ${editContextRef.current!.page!.item!.language}.
|
|
541
|
+
Input data: ${JSON.stringify(data)}`,
|
|
542
|
+
|
|
543
|
+
name: "system",
|
|
544
|
+
role: "system",
|
|
545
|
+
},
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
console.log("PAGE WIZARD PROMPT: ", prompt);
|
|
549
|
+
|
|
550
|
+
const result = await executePrompt(
|
|
551
|
+
prompt,
|
|
552
|
+
editContextRef.current!,
|
|
553
|
+
createWizardAiContext,
|
|
554
|
+
{ allowedFunctions: [] },
|
|
555
|
+
{ signal: localAbortController.signal },
|
|
556
|
+
step.fields.aiModel || "gpt-4.1",
|
|
557
|
+
(response) => {
|
|
558
|
+
try {
|
|
559
|
+
const newLayout = JSON.parse(response.content) as WizardPageModel;
|
|
560
|
+
|
|
561
|
+
if (newLayout) {
|
|
562
|
+
setPageModel((prev) => mergeLayout(prev, newLayout));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
setMessage(newLayout.message);
|
|
566
|
+
} catch (parseError: unknown) {}
|
|
567
|
+
},
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
const pageModel = result;
|
|
571
|
+
|
|
572
|
+
const finalLayout = JSON.parse(pageModel.content);
|
|
573
|
+
console.log("RESULT LAYOUT: ", pageModel, finalLayout);
|
|
574
|
+
setPageModel((prev) => mergeLayout(prev, finalLayout));
|
|
575
|
+
setStepCompleted(true);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
console.error(error);
|
|
578
|
+
} finally {
|
|
579
|
+
setIsLoading(false);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Custom instructions panel
|
|
584
|
+
const customInstructionsPanel = () => {
|
|
585
|
+
return (
|
|
586
|
+
<InputTextarea
|
|
587
|
+
value={internalState.layoutInstructions}
|
|
588
|
+
onChange={(e) =>
|
|
589
|
+
setInternalState({
|
|
590
|
+
...internalState,
|
|
591
|
+
layoutInstructions: e.target.value,
|
|
592
|
+
})
|
|
593
|
+
}
|
|
594
|
+
placeholder="Example: Make it modern and minimalist. Focus on images. Include a hero section at the top."
|
|
595
|
+
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"
|
|
596
|
+
/>
|
|
597
|
+
);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<div className="flex h-full flex-col md:flex-row">
|
|
602
|
+
<div className="flex flex-col items-stretch overflow-y-auto md:h-full">
|
|
603
|
+
<WizardBox
|
|
604
|
+
title="Page Setup"
|
|
605
|
+
icon={<Settings />}
|
|
606
|
+
description="Configure the page name, target location and language for your new page"
|
|
607
|
+
collapsible="yes"
|
|
608
|
+
summary={
|
|
609
|
+
<div className="text-xs text-gray-500">
|
|
610
|
+
Folder: {fullParentItem?.path}, Language: {language}, Name:{" "}
|
|
611
|
+
{pageModel.name || "Not set"}
|
|
612
|
+
</div>
|
|
613
|
+
}
|
|
614
|
+
className="w-full md:w-96"
|
|
615
|
+
>
|
|
616
|
+
<div className="flex flex-col items-stretch">
|
|
617
|
+
<div className="relative flex-1">
|
|
618
|
+
{isGenerating ? (
|
|
619
|
+
<div className="flex h-full items-center justify-center">
|
|
620
|
+
<Generate title="Generating page name..." />
|
|
621
|
+
</div>
|
|
622
|
+
) : (
|
|
623
|
+
<>
|
|
624
|
+
<div className="mb-4">
|
|
625
|
+
<label
|
|
626
|
+
htmlFor="pageName"
|
|
627
|
+
className="mb-1 block text-sm font-medium"
|
|
628
|
+
>
|
|
629
|
+
Page Name
|
|
630
|
+
</label>
|
|
631
|
+
<InputText
|
|
632
|
+
id="pageName"
|
|
633
|
+
type="text"
|
|
634
|
+
disabled={!!pageItem}
|
|
635
|
+
className={`w-full rounded border p-2 text-sm ${
|
|
636
|
+
!nameValidation.isValid
|
|
637
|
+
? "border-red-500"
|
|
638
|
+
: nameValidation.isValid && pageModel.name
|
|
639
|
+
? "border-green-500"
|
|
640
|
+
: ""
|
|
641
|
+
}`}
|
|
642
|
+
value={pageModel.name}
|
|
643
|
+
onChange={(e) =>
|
|
644
|
+
handleInputChange("name", e.target.value)
|
|
645
|
+
}
|
|
646
|
+
placeholder="Enter page name"
|
|
647
|
+
/>
|
|
648
|
+
{nameValidation.message && !nameValidation.isValid && (
|
|
649
|
+
<div className="mt-1 text-xs text-red-600">
|
|
650
|
+
{nameValidation.message}
|
|
651
|
+
</div>
|
|
652
|
+
)}
|
|
653
|
+
{nameValidation.isValid && pageModel.name && (
|
|
654
|
+
<div className="mt-1 text-xs text-green-600">
|
|
655
|
+
✓ Page name is available
|
|
656
|
+
</div>
|
|
657
|
+
)}
|
|
658
|
+
</div>
|
|
659
|
+
|
|
660
|
+
<div className="mb-4">
|
|
661
|
+
<div className="mb-1 text-sm font-medium">
|
|
662
|
+
Target Parent Item
|
|
663
|
+
</div>
|
|
664
|
+
<div className="mb-4 break-after-all text-xs text-gray-600">
|
|
665
|
+
{fullParentItem?.path || "Loading..."}
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
|
|
669
|
+
<div className="relative mb-4">
|
|
670
|
+
<label
|
|
671
|
+
htmlFor="language"
|
|
672
|
+
className="mb-1 block text-sm font-medium"
|
|
673
|
+
>
|
|
674
|
+
Language
|
|
675
|
+
</label>
|
|
676
|
+
<div className="w-fit">
|
|
677
|
+
<LanguageSelector
|
|
678
|
+
selectedLanguage={language}
|
|
679
|
+
darkMode={true}
|
|
680
|
+
disabled={isGenerating}
|
|
681
|
+
onLanguageSelected={(language) =>
|
|
682
|
+
setLanguage(language.languageCode)
|
|
683
|
+
}
|
|
684
|
+
showAllLanguages={true}
|
|
685
|
+
/>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
</>
|
|
689
|
+
)}
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
</WizardBox>
|
|
693
|
+
<WizardBoxConnector direction="vertical" />
|
|
694
|
+
<WizardBox
|
|
695
|
+
title="Content modules"
|
|
696
|
+
icon={<Wand2 />}
|
|
697
|
+
description="Select the component types the AI can choose from"
|
|
698
|
+
className="w-full md:w-96"
|
|
699
|
+
collapsible="yes"
|
|
700
|
+
defaultCollapsed="yes"
|
|
701
|
+
summary=<div className="text-xs text-gray-500">
|
|
702
|
+
{getComponentTypeSelectorSummary(
|
|
703
|
+
data.selectedComponentTypes,
|
|
704
|
+
availableComponentTypes,
|
|
705
|
+
)}
|
|
706
|
+
</div>
|
|
707
|
+
>
|
|
708
|
+
<ComponentTypeSelector
|
|
709
|
+
selectedComponentTypes={data.selectedComponentTypes}
|
|
710
|
+
setSelectedComponentTypes={(selectedTypes) => {
|
|
711
|
+
setData({
|
|
712
|
+
...data,
|
|
713
|
+
selectedComponentTypes: selectedTypes,
|
|
714
|
+
});
|
|
715
|
+
}}
|
|
716
|
+
schema={wizard.schema}
|
|
717
|
+
data={data}
|
|
718
|
+
setData={setData}
|
|
719
|
+
step={step}
|
|
720
|
+
/>
|
|
721
|
+
</WizardBox>
|
|
722
|
+
<WizardBoxConnector direction="vertical" />
|
|
723
|
+
<WizardBox
|
|
724
|
+
title="Generate content"
|
|
725
|
+
icon={<Wand2 />}
|
|
726
|
+
collapsible="yes"
|
|
727
|
+
defaultCollapsed="mobileOnly"
|
|
728
|
+
description="Refine instructions and generate content"
|
|
729
|
+
className="w-full md:w-96"
|
|
730
|
+
footer={
|
|
731
|
+
<div>
|
|
732
|
+
<div className="flex w-full gap-2">
|
|
733
|
+
<ActionButton
|
|
734
|
+
onClick={createLayout}
|
|
735
|
+
disabled={
|
|
736
|
+
isLoading ||
|
|
737
|
+
isCreatingComponents ||
|
|
738
|
+
!data.selectedComponentTypes ||
|
|
739
|
+
data.selectedComponentTypes.length === 0 ||
|
|
740
|
+
(!pageItem &&
|
|
741
|
+
(!pageModel.name ||
|
|
742
|
+
pageModel.name.trim().length < 3 ||
|
|
743
|
+
!nameValidation.isValid))
|
|
744
|
+
}
|
|
745
|
+
isLoading={
|
|
746
|
+
isLoading ||
|
|
747
|
+
isCreatingComponents ||
|
|
748
|
+
!!editContext?.activeFieldActions.find(
|
|
749
|
+
(action) => action.state === "running",
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
loadingText={"Working"}
|
|
753
|
+
className="w-full flex-1"
|
|
754
|
+
>
|
|
755
|
+
{!pageItem ? "Create page" : "Delete & Regenerate content"}
|
|
756
|
+
</ActionButton>
|
|
757
|
+
|
|
758
|
+
{isLoading && (
|
|
759
|
+
<ActionButton
|
|
760
|
+
isLoading={false}
|
|
761
|
+
onClick={() => abortController?.abort("User aborted")}
|
|
762
|
+
>
|
|
763
|
+
Abort
|
|
764
|
+
</ActionButton>
|
|
765
|
+
)}
|
|
766
|
+
</div>
|
|
767
|
+
{message && <div className="mt-2 text-xs">{message}</div>}
|
|
768
|
+
</div>
|
|
769
|
+
}
|
|
770
|
+
>
|
|
771
|
+
<div>{customInstructionsPanel()}</div>
|
|
772
|
+
</WizardBox>
|
|
773
|
+
</div>
|
|
774
|
+
{pageItem && (
|
|
775
|
+
<>
|
|
776
|
+
<WizardBoxConnector />
|
|
777
|
+
<WizardBox
|
|
778
|
+
title="Page preview"
|
|
779
|
+
icon={<PanelsTopLeft />}
|
|
780
|
+
description="After finalizing the page, you can edit, further enhance the content and share the page with your team for collaboration."
|
|
781
|
+
noPadding={true}
|
|
782
|
+
className="flex-1"
|
|
783
|
+
showFullscreenButton={true}
|
|
784
|
+
>
|
|
785
|
+
<div
|
|
786
|
+
className={cn(
|
|
787
|
+
"min-h-[300px] border-t border-gray-200",
|
|
788
|
+
pageLoaded ? "h-full" : "h-0",
|
|
789
|
+
)}
|
|
790
|
+
>
|
|
791
|
+
<PageViewer
|
|
792
|
+
name="single"
|
|
793
|
+
compareView={false}
|
|
794
|
+
showFormEditor={false}
|
|
795
|
+
followEditsDefault={true}
|
|
796
|
+
pageViewContext={editContext!.pageView}
|
|
797
|
+
/>
|
|
798
|
+
</div>
|
|
799
|
+
</WizardBox>
|
|
800
|
+
</>
|
|
801
|
+
)}
|
|
802
|
+
</div>
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Merges a new layout with the previous layout
|
|
808
|
+
* - Keeps all previous components
|
|
809
|
+
* - Adds new components
|
|
810
|
+
* - Updates existing fields with new values
|
|
811
|
+
* - Adds new fields
|
|
812
|
+
*/
|
|
813
|
+
const mergeLayout = (
|
|
814
|
+
prev: WizardPageModel | null,
|
|
815
|
+
newLayout: WizardPageModel,
|
|
816
|
+
): WizardPageModel => {
|
|
817
|
+
if (!prev) return newLayout;
|
|
818
|
+
|
|
819
|
+
// Merge top-level properties
|
|
820
|
+
const result: WizardPageModel = {
|
|
821
|
+
name: newLayout.name || prev.name,
|
|
822
|
+
message: newLayout.message,
|
|
823
|
+
metaDescription: prev.metaDescription,
|
|
824
|
+
metaKeywords: prev.metaKeywords,
|
|
825
|
+
components: [...(prev.components || [])],
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// Function to merge components recursively
|
|
829
|
+
const mergeComponents = (
|
|
830
|
+
existingComponents: any[],
|
|
831
|
+
newComponents: any[],
|
|
832
|
+
): any[] => {
|
|
833
|
+
if (!newComponents?.length) return existingComponents;
|
|
834
|
+
|
|
835
|
+
const result = [...existingComponents];
|
|
836
|
+
|
|
837
|
+
for (const newComp of newComponents) {
|
|
838
|
+
// Try to find a matching component by type and name
|
|
839
|
+
const existingIndex = result.findIndex(
|
|
840
|
+
(comp) => comp.type === newComp.type && comp.name === newComp.name,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
if (existingIndex >= 0) {
|
|
844
|
+
// Update existing component
|
|
845
|
+
const existing = result[existingIndex];
|
|
846
|
+
|
|
847
|
+
// Merge fields - update existing fields and add new ones
|
|
848
|
+
const mergedFields = [...(existing.fields || [])];
|
|
849
|
+
for (const newField of newComp.fields || []) {
|
|
850
|
+
const fieldIndex = mergedFields.findIndex(
|
|
851
|
+
(f) => f.name === newField.name,
|
|
852
|
+
);
|
|
853
|
+
if (fieldIndex >= 0) {
|
|
854
|
+
mergedFields[fieldIndex].value = newField.value; // Update existing field
|
|
855
|
+
mergedFields[fieldIndex].type = newField.type;
|
|
856
|
+
} else {
|
|
857
|
+
mergedFields.push(newField); // Add new field
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Recursive merge of children components
|
|
862
|
+
const mergedChildren = mergeComponents(
|
|
863
|
+
existing.children || [],
|
|
864
|
+
newComp.children || [],
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
// Create updated component
|
|
868
|
+
result[existingIndex] = {
|
|
869
|
+
...existing,
|
|
870
|
+
fields: mergedFields,
|
|
871
|
+
children: mergedChildren,
|
|
872
|
+
placeholder: newComp.placeholder || existing.placeholder,
|
|
873
|
+
};
|
|
874
|
+
} else {
|
|
875
|
+
// Add new component
|
|
876
|
+
result.push(newComp);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return result;
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// Merge components recursively
|
|
884
|
+
result.components = mergeComponents(
|
|
885
|
+
result.components,
|
|
886
|
+
newLayout.components || [],
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
return result;
|
|
890
|
+
};
|