astro-tractstack 2.1.3 → 2.2.1
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/README.md +54 -266
- package/bin/create-tractstack.js +9 -6
- package/dist/index.js +109 -71
- package/package.json +4 -2
- package/templates/css/custom.css +5 -0
- package/templates/icons/code.svg +18 -0
- package/templates/icons/li.svg +4 -0
- package/templates/icons/link.svg +22 -0
- package/templates/icons/p.svg +3 -0
- package/templates/src/client/app.js +80 -1
- package/templates/src/components/Footer.astro +1 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
- package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
- package/templates/src/components/compositor/Compositor.tsx +25 -9
- package/templates/src/components/compositor/Node.tsx +168 -496
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
- package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
- package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
- package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
- package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
- package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
- package/templates/src/components/edit/Header.tsx +68 -9
- package/templates/src/components/edit/PanelSwitch.tsx +42 -4
- package/templates/src/components/edit/SettingsPanel.tsx +2 -3
- package/templates/src/components/edit/ToolMode.tsx +1 -31
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
- package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
- package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
- package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +19 -787
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
- package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
- package/templates/src/components/fields/ArtpackImage.tsx +11 -3
- package/templates/src/components/fields/BackgroundImage.tsx +8 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
- package/templates/src/components/fields/ImageUpload.tsx +6 -0
- package/templates/src/components/form/ActionBuilderField.tsx +15 -5
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
- package/templates/src/components/form/ColorPicker.tsx +1 -1
- package/templates/src/components/form/EnumSelect.tsx +1 -1
- package/templates/src/components/form/NumberInput.tsx +1 -1
- package/templates/src/components/form/StringArrayInput.tsx +1 -1
- package/templates/src/components/form/StringInput.tsx +1 -1
- package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
- package/templates/src/components/profile/ProfileCreate.tsx +1 -1
- package/templates/src/components/profile/ProfileEdit.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
- package/templates/src/constants/prompts.json +18 -10
- package/templates/src/constants.ts +3 -0
- package/templates/src/hooks/usePaneFragments.ts +60 -0
- package/templates/src/lib/session.ts +71 -16
- package/templates/src/pages/[...slug].astro +4 -46
- package/templates/src/pages/api/css.ts +149 -0
- package/templates/src/pages/maint.astro +1 -1
- package/templates/src/pages/storykeep/login.astro +2 -2
- package/templates/src/stores/nodes.ts +162 -49
- package/templates/src/stores/orphanAnalysis.ts +6 -30
- package/templates/src/stores/previews.ts +7 -0
- package/templates/src/stores/storykeep.ts +0 -8
- package/templates/src/types/compositorTypes.ts +53 -10
- package/templates/src/utils/compositor/aiGeneration.ts +93 -0
- package/templates/src/utils/compositor/allowInsert.ts +2 -0
- package/templates/src/utils/compositor/htmlAst.ts +704 -0
- package/templates/src/utils/compositor/nodesHelper.ts +281 -102
- package/templates/src/utils/compositor/savePipeline.ts +893 -0
- package/templates/src/utils/etl/index.ts +3 -0
- package/templates/src/utils/etl/transformer.ts +10 -0
- package/templates/src/utils/helpers.ts +101 -0
- package/utils/inject-files.ts +100 -62
- package/templates/icons/text.svg +0 -6
- package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
type SetStateAction,
|
|
5
|
+
type Dispatch,
|
|
6
|
+
type MouseEvent,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { useStore } from '@nanostores/react';
|
|
3
9
|
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
4
10
|
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
5
|
-
import ArrowDownIcon from '@heroicons/react/24/outline/ArrowDownIcon';
|
|
6
|
-
import ArrowUpIcon from '@heroicons/react/24/outline/ArrowUpIcon';
|
|
7
11
|
import PaintBrushIcon from '@heroicons/react/24/outline/PaintBrushIcon';
|
|
12
|
+
import ArchiveBoxArrowDownIcon from '@heroicons/react/24/outline/ArchiveBoxArrowDownIcon';
|
|
13
|
+
import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
|
|
14
|
+
import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon';
|
|
15
|
+
import SparklesIcon from '@heroicons/react/24/solid/SparklesIcon';
|
|
8
16
|
import {
|
|
9
17
|
isContextPaneNode,
|
|
10
18
|
hasBeliefPayload,
|
|
11
19
|
} from '@/utils/compositor/typeGuards';
|
|
12
20
|
import { settingsPanelStore, fullContentMapStore } from '@/stores/storykeep';
|
|
21
|
+
import { selectionStore } from '@/stores/selection';
|
|
13
22
|
import { getCtx } from '@/stores/nodes';
|
|
23
|
+
import { copyPaneToClipboard } from '@/utils/compositor/designLibraryHelper';
|
|
24
|
+
import { SaveToLibraryModal } from '@/components/edit/state/SaveToLibraryModal';
|
|
25
|
+
import { RestylePaneModal } from '@/components/edit/pane/RestylePaneModal';
|
|
26
|
+
import { AiRestylePaneModal } from '@/components/edit/pane/AiRestylePaneModal';
|
|
14
27
|
import PaneTitlePanel from './PanePanel_title';
|
|
15
28
|
import PaneMagicPathPanel from './PanePanel_path';
|
|
16
29
|
import PaneImpressionPanel from './PanePanel_impression';
|
|
@@ -18,18 +31,23 @@ import { PaneConfigMode, type PaneNode } from '@/types/compositorTypes';
|
|
|
18
31
|
|
|
19
32
|
interface ConfigPanePanelProps {
|
|
20
33
|
nodeId: string;
|
|
34
|
+
isHtmlAstPane: boolean;
|
|
35
|
+
isSandboxMode?: boolean;
|
|
21
36
|
}
|
|
22
37
|
|
|
23
|
-
const ConfigPanePanel = ({
|
|
38
|
+
const ConfigPanePanel = ({
|
|
39
|
+
nodeId,
|
|
40
|
+
isHtmlAstPane,
|
|
41
|
+
isSandboxMode,
|
|
42
|
+
}: ConfigPanePanelProps) => {
|
|
24
43
|
const ctx = getCtx();
|
|
25
44
|
const isTemplate = useStore(ctx.isTemplate);
|
|
26
45
|
const bgColorStyles = ctx.getNodeCSSPropertiesStyles(nodeId);
|
|
27
46
|
const activePaneMode = useStore(ctx.activePaneMode);
|
|
28
|
-
const toolMode = useStore(ctx.toolModeValStore);
|
|
29
|
-
const reorderMode = toolMode.value === `move`;
|
|
30
47
|
const isActiveMode =
|
|
31
48
|
activePaneMode.panel === 'settings' && activePaneMode.paneId === nodeId;
|
|
32
49
|
const $contentMap = useStore(fullContentMapStore);
|
|
50
|
+
const { isRestyleModalOpen, isAiRestyleModalOpen } = useStore(selectionStore);
|
|
33
51
|
|
|
34
52
|
const allNodes = ctx.allNodes.get();
|
|
35
53
|
const paneNode = allNodes.get(nodeId) as PaneNode;
|
|
@@ -49,6 +67,9 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
49
67
|
: PaneConfigMode.DEFAULT
|
|
50
68
|
);
|
|
51
69
|
|
|
70
|
+
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
|
|
71
|
+
const [wasCopied, setWasCopied] = useState(false);
|
|
72
|
+
|
|
52
73
|
useEffect(() => {
|
|
53
74
|
if (isActiveMode && activePaneMode.mode) {
|
|
54
75
|
setMode(activePaneMode.mode as PaneConfigMode);
|
|
@@ -65,7 +86,7 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
65
86
|
};
|
|
66
87
|
|
|
67
88
|
const handleCodeHookConfig = () => {
|
|
68
|
-
ctx.toolModeValStore.set({ value: '
|
|
89
|
+
ctx.toolModeValStore.set({ value: 'text' });
|
|
69
90
|
settingsPanelStore.set({
|
|
70
91
|
action: 'setup-codehook',
|
|
71
92
|
nodeId: nodeId,
|
|
@@ -75,7 +96,7 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
75
96
|
|
|
76
97
|
const handleEditStyles = () => {
|
|
77
98
|
ctx.closeAllPanels();
|
|
78
|
-
ctx.toolModeValStore.set({ value: '
|
|
99
|
+
ctx.toolModeValStore.set({ value: 'text' });
|
|
79
100
|
if (paneNode.isDecorative) {
|
|
80
101
|
const childNodeIds = ctx.getChildNodeIDs(nodeId);
|
|
81
102
|
const bgPaneId = childNodeIds.find((id) => {
|
|
@@ -99,6 +120,33 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
99
120
|
}
|
|
100
121
|
};
|
|
101
122
|
|
|
123
|
+
// Design Library Handlers
|
|
124
|
+
const handleRestyleClick = (e: MouseEvent) => {
|
|
125
|
+
e.stopPropagation();
|
|
126
|
+
selectionStore.setKey('paneToRestyleId', nodeId);
|
|
127
|
+
selectionStore.setKey('isRestyleModalOpen', true);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleAiRestyleClick = (e: MouseEvent) => {
|
|
131
|
+
e.stopPropagation();
|
|
132
|
+
selectionStore.setKey('paneToRestyleId', nodeId);
|
|
133
|
+
selectionStore.setKey('isAiRestyleModalOpen', true);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const handleSaveClick = (e: MouseEvent) => {
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
setIsSaveModalOpen(true);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const handleCopyToClipboard = async (e: MouseEvent) => {
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
const success = await copyPaneToClipboard(nodeId);
|
|
144
|
+
if (success) {
|
|
145
|
+
setWasCopied(true);
|
|
146
|
+
setTimeout(() => setWasCopied(false), 2000);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
102
150
|
if (mode === PaneConfigMode.TITLE) {
|
|
103
151
|
return <PaneTitlePanel nodeId={nodeId} setMode={setSaveMode} />;
|
|
104
152
|
} else if (mode === PaneConfigMode.PATH) {
|
|
@@ -112,8 +160,8 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
112
160
|
className="border-t border-dotted border-mylightgrey bg-myoffwhite"
|
|
113
161
|
style={bgColorStyles}
|
|
114
162
|
>
|
|
115
|
-
<div className="group w-full rounded-t-md px-1.5
|
|
116
|
-
<div className="flex flex-wrap gap-2">
|
|
163
|
+
<div className="group w-full rounded-t-md px-1.5 py-1.5">
|
|
164
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
117
165
|
<div className={`flex flex-wrap gap-2 transition-opacity`}>
|
|
118
166
|
{paneNode.isDecorative ? (
|
|
119
167
|
<>
|
|
@@ -142,24 +190,26 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
142
190
|
ID
|
|
143
191
|
</button>
|
|
144
192
|
)}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
{!isHtmlAstPane && (
|
|
194
|
+
<button
|
|
195
|
+
onClick={() => setSaveMode(PaneConfigMode.IMPRESSION)}
|
|
196
|
+
className={buttonClass}
|
|
197
|
+
>
|
|
198
|
+
{impressionNodes.length ? (
|
|
199
|
+
<>
|
|
200
|
+
<CheckIcon className="inline h-4 w-4" />
|
|
201
|
+
{` `}
|
|
202
|
+
<span className="font-bold">Has Impression</span>
|
|
203
|
+
</>
|
|
204
|
+
) : (
|
|
205
|
+
<>
|
|
206
|
+
<XMarkIcon className="inline h-4 w-4" />
|
|
207
|
+
{` `}
|
|
208
|
+
<span>No Impression</span>
|
|
209
|
+
</>
|
|
210
|
+
)}
|
|
211
|
+
</button>
|
|
212
|
+
)}
|
|
163
213
|
</>
|
|
164
214
|
)}
|
|
165
215
|
{isCodeHook && (
|
|
@@ -170,7 +220,7 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
170
220
|
Configure Code Hook
|
|
171
221
|
</button>
|
|
172
222
|
)}
|
|
173
|
-
{!isCodeHook && (
|
|
223
|
+
{!isCodeHook && !isHtmlAstPane && (
|
|
174
224
|
<button onClick={handleEditStyles} className={buttonClass}>
|
|
175
225
|
<PaintBrushIcon className="inline h-4 w-4" />
|
|
176
226
|
{` `}
|
|
@@ -179,7 +229,7 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
179
229
|
)}
|
|
180
230
|
</>
|
|
181
231
|
)}
|
|
182
|
-
{!isContextPane && !isTemplate && (
|
|
232
|
+
{!isContextPane && !isTemplate && !isHtmlAstPane && (
|
|
183
233
|
<button
|
|
184
234
|
onClick={() => setSaveMode(PaneConfigMode.PATH)}
|
|
185
235
|
className={buttonClass}
|
|
@@ -199,29 +249,67 @@ const ConfigPanePanel = ({ nodeId }: ConfigPanePanelProps) => {
|
|
|
199
249
|
)}
|
|
200
250
|
</button>
|
|
201
251
|
)}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
{/* Design Library Tools (Right Aligned) */}
|
|
255
|
+
<div className="ml-auto flex items-center gap-2 border-l border-gray-300 pl-2">
|
|
256
|
+
{!isHtmlAstPane && !isSandboxMode && (
|
|
257
|
+
<button
|
|
258
|
+
title="Save Pane to Design Library"
|
|
259
|
+
onClick={handleSaveClick}
|
|
260
|
+
className="flex h-7 w-7 items-center justify-center rounded-full bg-cyan-600 p-1 shadow-sm hover:bg-cyan-700"
|
|
261
|
+
>
|
|
262
|
+
<ArchiveBoxArrowDownIcon className="h-4 w-4 text-white" />
|
|
263
|
+
</button>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
<button
|
|
267
|
+
title={isHtmlAstPane ? 'Re-Style' : 'Re-Color'}
|
|
268
|
+
onClick={handleAiRestyleClick}
|
|
269
|
+
className="flex h-7 w-7 items-center justify-center rounded-full bg-purple-600 p-1 shadow-sm hover:bg-purple-700"
|
|
270
|
+
>
|
|
271
|
+
<SparklesIcon className="h-3.5 w-3.5 text-white" />
|
|
272
|
+
</button>
|
|
273
|
+
|
|
274
|
+
{!isHtmlAstPane && (
|
|
275
|
+
<button
|
|
276
|
+
title="Restyle Pane from Design Library"
|
|
277
|
+
onClick={handleRestyleClick}
|
|
278
|
+
className="flex h-7 w-7 items-center justify-center rounded-full bg-blue-600 p-1 shadow-sm hover:bg-blue-700"
|
|
279
|
+
>
|
|
280
|
+
<ArrowPathRoundedSquareIcon className="h-4 w-4 text-white" />
|
|
281
|
+
</button>
|
|
282
|
+
)}
|
|
283
|
+
{import.meta.env.DEV && (
|
|
284
|
+
<button
|
|
285
|
+
title="Copy Pane Design to Clipboard"
|
|
286
|
+
onClick={handleCopyToClipboard}
|
|
287
|
+
className={`flex h-7 w-7 items-center justify-center rounded-full p-1 shadow-sm transition-colors ${
|
|
288
|
+
wasCopied ? 'bg-green-500' : 'bg-gray-600 hover:bg-gray-700'
|
|
289
|
+
}`}
|
|
290
|
+
>
|
|
291
|
+
{wasCopied ? (
|
|
292
|
+
<CheckIcon className="h-4 w-4 text-white" />
|
|
293
|
+
) : (
|
|
294
|
+
<ArrowDownTrayIcon className="h-4 w-4 text-white" />
|
|
295
|
+
)}
|
|
296
|
+
</button>
|
|
221
297
|
)}
|
|
222
298
|
</div>
|
|
223
299
|
</div>
|
|
224
300
|
</div>
|
|
301
|
+
|
|
302
|
+
{/* Modals */}
|
|
303
|
+
{isSaveModalOpen && (
|
|
304
|
+
<SaveToLibraryModal
|
|
305
|
+
paneId={nodeId}
|
|
306
|
+
onClose={() => setIsSaveModalOpen(false)}
|
|
307
|
+
/>
|
|
308
|
+
)}
|
|
309
|
+
{isRestyleModalOpen && <RestylePaneModal />}
|
|
310
|
+
{isAiRestyleModalOpen && (
|
|
311
|
+
<AiRestylePaneModal isSandboxMode={isSandboxMode} />
|
|
312
|
+
)}
|
|
225
313
|
</div>
|
|
226
314
|
);
|
|
227
315
|
};
|
|
@@ -450,7 +450,7 @@ export const RestylePaneModal = () => {
|
|
|
450
450
|
<p className="text-gray-500">No designs found.</p>
|
|
451
451
|
</div>
|
|
452
452
|
) : (
|
|
453
|
-
<div className="
|
|
453
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
454
454
|
{mergedTemplates.map(({ template }) => (
|
|
455
455
|
<TemplatePreviewItem
|
|
456
456
|
key={template.id}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
|
|
3
|
+
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
4
|
+
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
5
|
+
import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
|
|
6
|
+
import prompts from '@/constants/prompts.json';
|
|
7
|
+
import { getCtx } from '@/stores/nodes';
|
|
8
|
+
import { htmlToHtmlAst } from '@/utils/compositor/htmlAst';
|
|
9
|
+
import type { TemplatePane } from '@/types/compositorTypes';
|
|
10
|
+
import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
|
|
11
|
+
import BooleanToggle from '@/components/form/BooleanToggle';
|
|
12
|
+
import { AiDesignStep, type AiDesignConfig } from './AiDesignStep';
|
|
13
|
+
|
|
14
|
+
interface AiCreativeDesignStepProps {
|
|
15
|
+
onBack: () => void;
|
|
16
|
+
onSuccess: () => void;
|
|
17
|
+
onDirectInject: () => void;
|
|
18
|
+
onCreatePane: (template: TemplatePane) => void;
|
|
19
|
+
isSandboxMode?: boolean;
|
|
20
|
+
initialTopic?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const AiCreativeDesignStep = ({
|
|
24
|
+
onBack,
|
|
25
|
+
onSuccess,
|
|
26
|
+
onDirectInject,
|
|
27
|
+
onCreatePane,
|
|
28
|
+
isSandboxMode = false,
|
|
29
|
+
initialTopic = '',
|
|
30
|
+
}: AiCreativeDesignStepProps) => {
|
|
31
|
+
const [topic, setTopic] = useState(initialTopic);
|
|
32
|
+
const [designNotes, setDesignNotes] = useState('');
|
|
33
|
+
const [showColors, setShowColors] = useState(false);
|
|
34
|
+
const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
|
|
35
|
+
harmony: 'Analogous',
|
|
36
|
+
baseColor: '',
|
|
37
|
+
accentColor: '',
|
|
38
|
+
theme: 'Light',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
42
|
+
const [customTemplate, setCustomTemplate] = useState(
|
|
43
|
+
prompts.aiPaneCreativePrompt.user_template
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
47
|
+
const [error, setError] = useState<string | null>(null);
|
|
48
|
+
|
|
49
|
+
const [reviewMode, setReviewMode] = useState(false);
|
|
50
|
+
const [previewHtml, setPreviewHtml] = useState<string>('');
|
|
51
|
+
const [pendingTemplate, setPendingTemplate] = useState<TemplatePane | null>(
|
|
52
|
+
null
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const handleGenerate = async () => {
|
|
56
|
+
if (!topic.trim()) {
|
|
57
|
+
setError('Please enter a topic.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setIsGenerating(true);
|
|
62
|
+
setError(null);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const systemPrompt = prompts.aiPaneCreativePrompt.system;
|
|
66
|
+
let userPrompt = showAdvanced
|
|
67
|
+
? customTemplate
|
|
68
|
+
: prompts.aiPaneCreativePrompt.user_template;
|
|
69
|
+
|
|
70
|
+
let colorContext = '';
|
|
71
|
+
if (showColors) {
|
|
72
|
+
colorContext = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
73
|
+
if (aiDesignConfig.baseColor)
|
|
74
|
+
colorContext += ` Base the colors around **${aiDesignConfig.baseColor}**.`;
|
|
75
|
+
if (aiDesignConfig.accentColor)
|
|
76
|
+
colorContext += ` Use **${aiDesignConfig.accentColor}** as an accent color.`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const combinedNotes = [
|
|
80
|
+
designNotes || 'Clean, modern, high contrast.',
|
|
81
|
+
colorContext,
|
|
82
|
+
]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(' ');
|
|
85
|
+
|
|
86
|
+
userPrompt = userPrompt.replace('{{TOPIC}}', topic);
|
|
87
|
+
userPrompt = userPrompt.replace('{{DESIGN_NOTES}}', combinedNotes);
|
|
88
|
+
|
|
89
|
+
// Use shared infrastructure utility
|
|
90
|
+
const rawHtml = await callAskLemurAPI({
|
|
91
|
+
prompt: userPrompt,
|
|
92
|
+
context: systemPrompt,
|
|
93
|
+
expectJson: false,
|
|
94
|
+
isSandboxMode,
|
|
95
|
+
temperature: 0.5,
|
|
96
|
+
maxTokens: 20000,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const htmlAst = await htmlToHtmlAst(rawHtml, '');
|
|
100
|
+
|
|
101
|
+
const template: TemplatePane = {
|
|
102
|
+
id: '',
|
|
103
|
+
nodeType: 'Pane',
|
|
104
|
+
parentId: '',
|
|
105
|
+
title: `Creative: ${topic.slice(0, 20)}`,
|
|
106
|
+
slug: '',
|
|
107
|
+
isDecorative: false,
|
|
108
|
+
htmlAst,
|
|
109
|
+
markdown: {
|
|
110
|
+
id: '',
|
|
111
|
+
nodeType: 'Markdown',
|
|
112
|
+
parentId: '',
|
|
113
|
+
type: 'markdown',
|
|
114
|
+
markdownId: '',
|
|
115
|
+
defaultClasses: {},
|
|
116
|
+
parentClasses: [],
|
|
117
|
+
nodes: [],
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const goBackend =
|
|
122
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
123
|
+
|
|
124
|
+
const tenantId =
|
|
125
|
+
(window as any).TRACTSTACK_CONFIG?.tenantId ||
|
|
126
|
+
import.meta.env.PUBLIC_TENANTID ||
|
|
127
|
+
'default';
|
|
128
|
+
|
|
129
|
+
const previewResponse = await fetch(
|
|
130
|
+
`${goBackend}/api/v1/fragments/ast-preview`,
|
|
131
|
+
{
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'X-Tenant-ID': tenantId,
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
id: 'preview-temp',
|
|
139
|
+
title: 'Editor Preview',
|
|
140
|
+
tree: htmlAst.tree,
|
|
141
|
+
simulateFrontend: true,
|
|
142
|
+
}),
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (!previewResponse.ok) {
|
|
147
|
+
throw new Error('Failed to generate preview HTML');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const htmlString = await previewResponse.text();
|
|
151
|
+
|
|
152
|
+
setPendingTemplate(template);
|
|
153
|
+
setPreviewHtml(htmlString);
|
|
154
|
+
setReviewMode(true);
|
|
155
|
+
} catch (err: any) {
|
|
156
|
+
console.error('Creative Generation Error:', err);
|
|
157
|
+
setError(err.message || 'Failed to generate design.');
|
|
158
|
+
} finally {
|
|
159
|
+
setIsGenerating(false);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleAccept = () => {
|
|
164
|
+
if (pendingTemplate) {
|
|
165
|
+
onCreatePane(pendingTemplate);
|
|
166
|
+
const ctx = getCtx();
|
|
167
|
+
ctx.showSaveBypass.set(true);
|
|
168
|
+
onSuccess();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const handleCancel = () => {
|
|
173
|
+
setReviewMode(false);
|
|
174
|
+
setPendingTemplate(null);
|
|
175
|
+
setPreviewHtml('');
|
|
176
|
+
onBack();
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleRedo = () => {
|
|
180
|
+
setReviewMode(false);
|
|
181
|
+
setPendingTemplate(null);
|
|
182
|
+
setPreviewHtml('');
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (isGenerating) {
|
|
186
|
+
return (
|
|
187
|
+
<div className="flex min-h-96 flex-col items-center justify-center space-y-4 p-8">
|
|
188
|
+
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
189
|
+
<div className="text-center">
|
|
190
|
+
<p className="font-bold text-gray-900">Generating Design...</p>
|
|
191
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
192
|
+
This may take a few moments while AI codes your layout.
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (reviewMode && previewHtml) {
|
|
200
|
+
return (
|
|
201
|
+
<div className="relative flex h-full flex-col">
|
|
202
|
+
<div className="absolute right-4 top-4 z-50 flex gap-2">
|
|
203
|
+
<button
|
|
204
|
+
onClick={handleCancel}
|
|
205
|
+
className="rounded-full border border-gray-200 bg-white p-2 text-red-500 shadow-md transition-colors hover:bg-gray-100"
|
|
206
|
+
title="Cancel"
|
|
207
|
+
>
|
|
208
|
+
<XMarkIcon className="h-6 w-6" />
|
|
209
|
+
</button>
|
|
210
|
+
<button
|
|
211
|
+
onClick={handleRedo}
|
|
212
|
+
className="rounded-full border border-gray-200 bg-white p-2 text-blue-500 shadow-md transition-colors hover:bg-gray-100"
|
|
213
|
+
title="Redo with same params"
|
|
214
|
+
>
|
|
215
|
+
<ArrowPathRoundedSquareIcon className="h-6 w-6" />
|
|
216
|
+
</button>
|
|
217
|
+
<button
|
|
218
|
+
onClick={handleAccept}
|
|
219
|
+
className="rounded-full border border-green-600 bg-green-500 p-2 text-white shadow-md transition-colors hover:bg-green-600"
|
|
220
|
+
title="Accept"
|
|
221
|
+
>
|
|
222
|
+
<CheckIcon className="h-6 w-6" />
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<style
|
|
227
|
+
dangerouslySetInnerHTML={{
|
|
228
|
+
__html: pendingTemplate?.htmlAst?.css || '',
|
|
229
|
+
}}
|
|
230
|
+
/>
|
|
231
|
+
<div
|
|
232
|
+
className="w-full flex-1 overflow-y-auto rounded-md border bg-gray-50"
|
|
233
|
+
dangerouslySetInnerHTML={{ __html: previewHtml }}
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div className="space-y-6 p-4">
|
|
241
|
+
<div className="text-center">
|
|
242
|
+
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-pink-50">
|
|
243
|
+
<SparklesIcon className="h-6 w-6 text-pink-600" aria-hidden="true" />
|
|
244
|
+
</div>
|
|
245
|
+
<h3 className="mt-2 text-lg font-bold text-gray-900">
|
|
246
|
+
Creative Design
|
|
247
|
+
</h3>
|
|
248
|
+
<p className="text-sm text-gray-500">
|
|
249
|
+
Describe what you want, and AI will code a unique HTML structure for
|
|
250
|
+
you.
|
|
251
|
+
</p>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div className="space-y-4">
|
|
255
|
+
{!showAdvanced && (
|
|
256
|
+
<>
|
|
257
|
+
<div>
|
|
258
|
+
<label className="block text-sm font-bold text-gray-700">
|
|
259
|
+
Topic / Content Brief
|
|
260
|
+
</label>
|
|
261
|
+
<textarea
|
|
262
|
+
rows={6}
|
|
263
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-pink-500 focus:ring-pink-500"
|
|
264
|
+
placeholder="e.g. A pricing table for a SaaS product..."
|
|
265
|
+
value={topic}
|
|
266
|
+
onChange={(e) => setTopic(e.target.value)}
|
|
267
|
+
disabled={isGenerating}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div>
|
|
272
|
+
<label className="block text-sm font-bold text-gray-700">
|
|
273
|
+
Design Notes (Optional)
|
|
274
|
+
</label>
|
|
275
|
+
<textarea
|
|
276
|
+
rows={3}
|
|
277
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-pink-500 focus:ring-pink-500"
|
|
278
|
+
placeholder="e.g. Dark mode, use rounded cards, neon accents..."
|
|
279
|
+
value={designNotes}
|
|
280
|
+
onChange={(e) => setDesignNotes(e.target.value)}
|
|
281
|
+
disabled={isGenerating}
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div className="my-4 flex items-center">
|
|
286
|
+
<BooleanToggle
|
|
287
|
+
label="Customize Colors"
|
|
288
|
+
value={showColors}
|
|
289
|
+
onChange={setShowColors}
|
|
290
|
+
size="sm"
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
{showColors && (
|
|
295
|
+
<div className="rounded-lg border border-gray-100 bg-gray-50 p-4">
|
|
296
|
+
<AiDesignStep
|
|
297
|
+
designConfig={aiDesignConfig}
|
|
298
|
+
onDesignConfigChange={setAiDesignConfig}
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
<div className="my-4 flex items-center border-t border-gray-100 pt-4">
|
|
306
|
+
<BooleanToggle
|
|
307
|
+
label="Advanced: Edit Full Prompt"
|
|
308
|
+
value={showAdvanced}
|
|
309
|
+
onChange={setShowAdvanced}
|
|
310
|
+
size="sm"
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
{showAdvanced && (
|
|
315
|
+
<div className="rounded-md border border-yellow-200 bg-yellow-50 p-4">
|
|
316
|
+
<label className="mb-2 block text-sm font-bold text-gray-800">
|
|
317
|
+
Master Prompt Template
|
|
318
|
+
</label>
|
|
319
|
+
<p className="mb-3 text-xs text-gray-600">
|
|
320
|
+
This is the raw scaffolding ruleset. Leave{' '}
|
|
321
|
+
<code className="bg-gray-100 px-1 font-mono font-bold">
|
|
322
|
+
{`{{TOPIC}}`}
|
|
323
|
+
</code>{' '}
|
|
324
|
+
and{' '}
|
|
325
|
+
<code className="bg-gray-100 px-1 font-mono font-bold">
|
|
326
|
+
{`{{DESIGN_NOTES}}`}
|
|
327
|
+
</code>{' '}
|
|
328
|
+
as-is; they will be replaced by your inputs (which are hidden
|
|
329
|
+
while this mode is active).
|
|
330
|
+
</p>
|
|
331
|
+
<textarea
|
|
332
|
+
rows={12}
|
|
333
|
+
className="block w-full rounded-md border-gray-300 p-2 font-mono text-xs shadow-sm focus:border-pink-500 focus:ring-pink-500"
|
|
334
|
+
value={customTemplate}
|
|
335
|
+
onChange={(e) => setCustomTemplate(e.target.value)}
|
|
336
|
+
/>
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
{error && (
|
|
342
|
+
<div className="rounded-md bg-red-50 p-3">
|
|
343
|
+
<p className="text-sm text-red-700">{error}</p>
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
<div className="text-center">
|
|
348
|
+
<button
|
|
349
|
+
onClick={onDirectInject}
|
|
350
|
+
className="text-xs text-gray-400 underline hover:text-gray-600"
|
|
351
|
+
>
|
|
352
|
+
Want to write raw HTML yourself? Use Direct Inject.
|
|
353
|
+
</button>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<div className="flex justify-between border-t border-gray-100 pt-4">
|
|
357
|
+
<button
|
|
358
|
+
onClick={onBack}
|
|
359
|
+
disabled={isGenerating}
|
|
360
|
+
className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-50"
|
|
361
|
+
>
|
|
362
|
+
Cancel
|
|
363
|
+
</button>
|
|
364
|
+
<button
|
|
365
|
+
onClick={handleGenerate}
|
|
366
|
+
disabled={isGenerating || !topic.trim()}
|
|
367
|
+
className="flex items-center gap-2 rounded-md bg-pink-600 px-6 py-2 text-sm font-bold text-white shadow-sm hover:bg-pink-700 disabled:bg-gray-400"
|
|
368
|
+
>
|
|
369
|
+
<SparklesIcon className="h-4 w-4" />
|
|
370
|
+
Generate
|
|
371
|
+
</button>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
};
|