astro-tractstack 2.1.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +54 -266
  2. package/bin/create-tractstack.js +9 -6
  3. package/dist/index.js +109 -71
  4. package/package.json +4 -2
  5. package/templates/css/custom.css +5 -0
  6. package/templates/icons/code.svg +18 -0
  7. package/templates/icons/li.svg +4 -0
  8. package/templates/icons/link.svg +22 -0
  9. package/templates/icons/p.svg +3 -0
  10. package/templates/src/client/app.js +80 -1
  11. package/templates/src/components/Footer.astro +1 -1
  12. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
  13. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
  14. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
  15. package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
  16. package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
  17. package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
  18. package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
  19. package/templates/src/components/compositor/Compositor.tsx +25 -9
  20. package/templates/src/components/compositor/Node.tsx +168 -496
  21. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
  22. package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
  23. package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
  24. package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
  25. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
  26. package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
  27. package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
  28. package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
  29. package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
  30. package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
  31. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
  32. package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
  33. package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
  34. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
  35. package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
  36. package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
  37. package/templates/src/components/edit/Header.tsx +68 -9
  38. package/templates/src/components/edit/PanelSwitch.tsx +42 -4
  39. package/templates/src/components/edit/SettingsPanel.tsx +2 -3
  40. package/templates/src/components/edit/ToolMode.tsx +1 -31
  41. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
  42. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
  43. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
  44. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
  45. package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
  46. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
  47. package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
  48. package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
  49. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
  50. package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
  51. package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
  52. package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
  53. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
  54. package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
  55. package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
  56. package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
  57. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
  58. package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
  59. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
  60. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
  61. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
  62. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
  63. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
  64. package/templates/src/components/edit/state/SaveModal.tsx +19 -787
  65. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
  66. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
  67. package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
  68. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
  69. package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
  70. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
  71. package/templates/src/components/fields/ArtpackImage.tsx +11 -3
  72. package/templates/src/components/fields/BackgroundImage.tsx +8 -0
  73. package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
  74. package/templates/src/components/fields/ImageUpload.tsx +6 -0
  75. package/templates/src/components/form/ActionBuilderField.tsx +15 -5
  76. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
  77. package/templates/src/components/form/ColorPicker.tsx +1 -1
  78. package/templates/src/components/form/EnumSelect.tsx +1 -1
  79. package/templates/src/components/form/NumberInput.tsx +1 -1
  80. package/templates/src/components/form/StringArrayInput.tsx +1 -1
  81. package/templates/src/components/form/StringInput.tsx +1 -1
  82. package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
  83. package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
  84. package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
  85. package/templates/src/components/profile/ProfileCreate.tsx +1 -1
  86. package/templates/src/components/profile/ProfileEdit.tsx +1 -1
  87. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
  88. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
  89. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
  90. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
  91. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
  92. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
  93. package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
  94. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
  95. package/templates/src/constants/prompts.json +18 -10
  96. package/templates/src/constants.ts +3 -0
  97. package/templates/src/hooks/usePaneFragments.ts +60 -0
  98. package/templates/src/lib/session.ts +71 -16
  99. package/templates/src/pages/[...slug].astro +4 -46
  100. package/templates/src/pages/api/css.ts +149 -0
  101. package/templates/src/pages/maint.astro +1 -1
  102. package/templates/src/pages/storykeep/login.astro +2 -2
  103. package/templates/src/stores/nodes.ts +162 -49
  104. package/templates/src/stores/orphanAnalysis.ts +6 -30
  105. package/templates/src/stores/previews.ts +7 -0
  106. package/templates/src/stores/storykeep.ts +0 -8
  107. package/templates/src/types/compositorTypes.ts +53 -10
  108. package/templates/src/utils/compositor/aiGeneration.ts +93 -0
  109. package/templates/src/utils/compositor/allowInsert.ts +2 -0
  110. package/templates/src/utils/compositor/htmlAst.ts +704 -0
  111. package/templates/src/utils/compositor/nodesHelper.ts +281 -102
  112. package/templates/src/utils/compositor/savePipeline.ts +893 -0
  113. package/templates/src/utils/etl/index.ts +3 -0
  114. package/templates/src/utils/etl/transformer.ts +10 -0
  115. package/templates/src/utils/helpers.ts +101 -0
  116. package/utils/inject-files.ts +100 -62
  117. package/templates/icons/text.svg +0 -6
  118. package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
  119. package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
  120. package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
  121. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
  122. package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
  123. package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
  124. package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
  125. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
  126. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
  127. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
  128. package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
@@ -1,118 +1,36 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useState, useCallback, useMemo, useEffect } from 'react';
3
- import DocumentPlusIcon from '@heroicons/react/24/outline/DocumentPlusIcon';
4
- import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
2
+ import { useState } from 'react';
5
3
  import SwatchIcon from '@heroicons/react/24/outline/SwatchIcon';
6
4
  import SquaresPlusIcon from '@heroicons/react/24/outline/SquaresPlusIcon';
7
5
  import DocumentIcon from '@heroicons/react/24/outline/DocumentIcon';
6
+ import PaintBrushIcon from '@heroicons/react/24/outline/PaintBrushIcon';
8
7
  import { NodesContext, getCtx } from '@/stores/nodes';
9
8
  import { cloneDeep } from '@/utils/helpers';
10
- import { hasAssemblyAIStore, sandboxTokenStore } from '@/stores/storykeep';
11
- import prompts from '@/constants/prompts.json';
9
+ import { hasAssemblyAIStore } from '@/stores/storykeep';
12
10
  import type { DesignLibraryEntry } from '@/types/tractstack';
13
11
  import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
14
12
  import { useStore } from '@nanostores/react';
15
- import { CopyInputStep, type CopyMode } from './steps/CopyInputStep';
16
13
  import { DesignLibraryStep } from './steps/DesignLibraryStep';
17
- import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
18
- import { parseAiPane, parseAiCopyHtml } from '@/utils/compositor/aiPaneParser';
19
- import {
20
- convertStorageToLiveTemplate,
21
- convertTemplateToAIShell,
22
- } from '@/utils/compositor/designLibraryHelper';
14
+ import { AiCreativeDesignStep } from './steps/AiCreativeDesignStep';
15
+ import { AiStandardDesignStep } from './steps/AiStandardDesignStep';
16
+ import { AiLibraryCopyStep } from './steps/AiLibraryCopyStep';
17
+ import { convertStorageToLiveTemplate } from '@/utils/compositor/designLibraryHelper';
23
18
  import { DirectInjectStep } from './steps/DirectInjectStep';
24
- import BooleanToggle from '@/components/form/BooleanToggle';
19
+ import { CreativeInjectStep } from './steps/CreativeInjectStep';
25
20
  import type { StoryFragmentNode } from '@/types/compositorTypes';
26
- import { TractStackAPI } from '@/utils/api';
27
21
 
28
22
  type Step =
29
23
  | 'initial'
30
24
  | 'dashboard'
31
25
  | 'designLibrary'
32
- | 'loading'
26
+ | 'library-copy'
33
27
  | 'error'
34
- | 'directInject';
28
+ | 'creativeInject'
29
+ | 'directInject'
30
+ | 'ai-creative';
35
31
 
36
32
  type InitialChoice = 'library' | 'ai' | 'blank';
37
- type LayoutChoice = 'standard' | 'grid';
38
- type ColumnPresetKey = 'left' | 'right';
39
-
40
- const callAskLemurAPI = async (
41
- prompt: string,
42
- context: string,
43
- expectJson: boolean,
44
- isSandboxMode: boolean
45
- ): Promise<string> => {
46
- const tenantId =
47
- window.TRACTSTACK_CONFIG?.tenantId ||
48
- import.meta.env.PUBLIC_TENANTID ||
49
- 'default';
50
- const api = new TractStackAPI(tenantId);
51
-
52
- const requestBody = {
53
- prompt,
54
- input_text: context,
55
- final_model: '',
56
- temperature: 0.5,
57
- max_tokens: 2000,
58
- };
59
-
60
- let resultData: any;
61
-
62
- if (isSandboxMode) {
63
- const token = sandboxTokenStore.get();
64
- const response = await fetch(`/api/sandbox`, {
65
- method: 'POST',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- 'X-Tenant-ID': tenantId,
69
- 'X-Sandbox-Token': token || '',
70
- },
71
- credentials: 'include',
72
- body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
73
- });
74
-
75
- if (!response.ok) {
76
- const errorText = await response.text();
77
- throw new Error(`Sandbox API failed: ${response.status} ${errorText}`);
78
- }
79
-
80
- const json = await response.json();
81
- if (!json.success) {
82
- throw new Error(json.error || 'Sandbox generation failed');
83
- }
84
- resultData = json.data;
85
- } else {
86
- const response = await api.post('/api/v1/aai/askLemur', requestBody);
87
-
88
- if (!response.success) {
89
- throw new Error(
90
- response.error || 'Generation failed to return valid response.'
91
- );
92
- }
93
- resultData = response.data;
94
- }
95
-
96
- if (!resultData?.response) {
97
- throw new Error('Generation failed to return a response object.');
98
- }
99
-
100
- let rawResponseData = resultData.response;
101
-
102
- if (expectJson && typeof rawResponseData === 'object') {
103
- return JSON.stringify(rawResponseData);
104
- }
105
- if (typeof rawResponseData === 'string') {
106
- let responseString = rawResponseData;
107
- if (responseString.startsWith('```json')) {
108
- responseString = responseString.slice(7, -3).trim();
109
- } else if (responseString.startsWith('```html')) {
110
- responseString = responseString.slice(7, -3).trim();
111
- }
112
- return responseString;
113
- }
114
- throw new Error('Unexpected response format received from API.');
115
- };
33
+ type LayoutChoice = 'standard' | 'grid' | 'creative';
116
34
 
117
35
  interface AddPaneNewPanelProps {
118
36
  nodeId: string;
@@ -135,94 +53,18 @@ const AddPaneNewPanel = ({
135
53
  }: AddPaneNewPanelProps) => {
136
54
  const ctx = providedCtx || getCtx();
137
55
  const hasAssemblyAI = useStore(hasAssemblyAIStore);
56
+ const isTemplate = useStore(ctx.isTemplate);
138
57
 
139
58
  const [step, setStep] = useState<Step>('initial');
140
- const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
141
- null
142
- );
143
59
  const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
144
60
  const [error, setError] = useState<string | null>(null);
145
-
146
- const [topic, setTopic] = useState('');
147
- const [showAdvancedPrompts, setShowAdvancedPrompts] = useState(false);
148
- const [showStyles, setShowStyles] = useState(false);
149
-
150
- const [selectedPromptId, setSelectedPromptId] = useState<string>('');
151
- const [isAiStyling, setIsAiStyling] = useState(false);
152
-
153
- const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
154
- const [promptValue, setPromptValue] = useState('');
155
- const [copyValue, setCopyValue] = useState('');
156
-
157
- const [overallPrompt, setOverallPrompt] = useState('');
158
- const [promptValueCol1, setPromptValueCol1] = useState('');
159
- const [promptValueCol2, setPromptValueCol2] = useState('');
160
- const [col1Copy, setCol1Copy] = useState('');
161
- const [col2Copy, setCol2Copy] = useState('');
162
-
163
61
  const [selectedLibraryEntry, setSelectedLibraryEntry] =
164
62
  useState<DesignLibraryEntry | null>(null);
165
- const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
166
- harmony: 'Analogous',
167
- baseColor: '',
168
- accentColor: '',
169
- theme: 'Light',
170
- additionalNotes: '',
171
- });
172
-
173
- const promptOptions = useMemo(() => {
174
- return prompts.aiPromptsIndex
175
- .filter((p) => p.layout === layoutChoice)
176
- .map((p) => ({ label: p.label, value: p.id }));
177
- }, [layoutChoice]);
178
-
179
- useEffect(() => {
180
- if (promptOptions.length > 0) {
181
- const currentValid = promptOptions.find(
182
- (p) => p.value === selectedPromptId
183
- );
184
- if (!currentValid) {
185
- setSelectedPromptId(promptOptions[0].value);
186
- }
187
- }
188
- }, [promptOptions, selectedPromptId]);
189
-
190
- useEffect(() => {
191
- if (!selectedPromptId) return;
192
-
193
- const activeConfig = prompts.aiPromptsIndex.find(
194
- (p) => p.id === selectedPromptId
195
- );
196
- if (!activeConfig) return;
197
-
198
- const promptKey = activeConfig.prompts.copy;
199
- const copyPromptGroup = (prompts as any)[promptKey];
200
- if (!copyPromptGroup) return;
201
-
202
- const variant = activeConfig.variants
203
- ? activeConfig.variants[0]
204
- : 'default';
205
-
206
- const injectTopic = (template: string) => {
207
- if (!template) return '';
208
- return template.replace('[topic]', topic.trim() || '[topic]');
209
- };
210
-
211
- if (layoutChoice === 'standard') {
212
- const baseText = copyPromptGroup[variant] || '';
213
- setPromptValue(injectTopic(baseText));
214
- } else if (layoutChoice === 'grid') {
215
- const preset = copyPromptGroup.presets?.[variant];
216
- if (preset) {
217
- setOverallPrompt(injectTopic(preset.default || ''));
218
- setPromptValueCol1(preset.left?.prompt || '');
219
- setPromptValueCol2(preset.right?.prompt || '');
220
- }
221
- }
222
- }, [selectedPromptId, layoutChoice, topic]);
223
63
 
224
- const handleInitialChoice = (choice: InitialChoice) => {
225
- setInitialChoice(choice);
64
+ const handleInitialChoice = (
65
+ choice: InitialChoice,
66
+ layout?: LayoutChoice
67
+ ) => {
226
68
  setError(null);
227
69
 
228
70
  if (choice === 'blank') {
@@ -230,23 +72,30 @@ const AddPaneNewPanel = ({
230
72
  } else if (choice === 'library') {
231
73
  setStep('designLibrary');
232
74
  } else if (choice === 'ai') {
233
- setStep('dashboard');
75
+ if (layout === 'creative') {
76
+ setLayoutChoice('creative');
77
+ setStep('ai-creative');
78
+ } else {
79
+ setLayoutChoice(layout || 'standard');
80
+ setStep('dashboard');
81
+ }
234
82
  }
235
83
  };
236
84
 
237
85
  const handleBack = () => {
238
86
  setError(null);
239
- if (step === 'dashboard') {
87
+ if (step === 'dashboard' || step === 'ai-creative') {
240
88
  setStep('initial');
241
- setTopic('');
242
- setShowAdvancedPrompts(false);
243
- setShowStyles(false);
244
89
  } else if (
245
90
  step === 'designLibrary' ||
246
91
  step === 'error' ||
247
- step === 'directInject'
92
+ step === 'directInject' ||
93
+ step === 'creativeInject'
248
94
  ) {
249
95
  setStep('initial');
96
+ } else if (step === 'library-copy') {
97
+ setStep('designLibrary');
98
+ setSelectedLibraryEntry(null);
250
99
  }
251
100
  };
252
101
 
@@ -318,506 +167,152 @@ const AddPaneNewPanel = ({
318
167
  const handleDesignLibrarySelect = (entry: DesignLibraryEntry) => {
319
168
  setSelectedLibraryEntry(entry);
320
169
 
321
- if (entry.template.gridLayout) {
322
- setLayoutChoice('grid');
323
- } else {
324
- setLayoutChoice('standard');
325
- }
326
-
327
170
  if (entry.locked) {
328
171
  const liveTemplate = convertStorageToLiveTemplate(entry.template);
329
172
  handleApplyTemplate(liveTemplate);
330
173
  return;
331
174
  }
332
175
 
333
- if (entry.retain) {
334
- setCopyMode('original');
335
- } else {
336
- setCopyMode('prompt');
337
- }
338
- setStep('dashboard');
176
+ setStep('library-copy');
339
177
  };
340
178
 
341
- const handleFinalGenerate = useCallback(async () => {
342
- setError(null);
343
- setStep('loading');
344
-
345
- try {
346
- if (initialChoice === 'library') {
347
- if (!selectedLibraryEntry) {
348
- throw new Error('No design library item was selected.');
349
- }
350
-
351
- const liveTemplate = convertStorageToLiveTemplate(
352
- selectedLibraryEntry.template
353
- );
354
-
355
- if (copyMode === 'original') {
356
- handleApplyTemplate(liveTemplate);
357
- return;
358
- }
359
-
360
- const shellResult = convertTemplateToAIShell(liveTemplate);
361
- const layout = 'Text Only';
362
-
363
- if (layoutChoice === 'grid' && liveTemplate.gridLayout) {
364
- if (copyMode === 'raw' && isAiStyling) {
365
- const activeConfig =
366
- prompts.aiPromptsIndex.find((p) => p.id === selectedPromptId) ||
367
- prompts.aiPromptsIndex.find((p) => p.layout === 'grid') ||
368
- prompts.aiPromptsIndex[0];
369
-
370
- const stylePromptKey = activeConfig.prompts.style;
371
- const stylePromptDetails = (prompts as any)[stylePromptKey];
372
-
373
- const copyResults: string[] = [];
374
- const rawContents = [col1Copy, col2Copy];
375
-
376
- for (const rawContent of rawContents) {
377
- const formattedStylePrompt = stylePromptDetails.user_template
378
- .replace('{{SHELL_JSON}}', shellResult)
379
- .replace('{{COPY_INPUT}}', rawContent);
380
-
381
- const styledResult = await callAskLemurAPI(
382
- formattedStylePrompt,
383
- stylePromptDetails.system || '',
384
- false,
385
- isSandboxMode
386
- );
387
- copyResults.push(styledResult);
388
- }
389
- const finalPane = parseAiPane(shellResult, copyResults, layout);
390
- handleApplyTemplate(finalPane);
391
- return;
392
- }
393
-
394
- if (copyMode === 'raw') {
395
- const nodes = liveTemplate.gridLayout.nodes;
396
- if (nodes && nodes[0]) nodes[0].markdownBody = col1Copy;
397
- if (nodes && nodes[1]) nodes[1].markdownBody = col2Copy;
398
- handleApplyTemplate(liveTemplate);
399
- return;
400
- }
401
-
402
- const copyPromptDetails = prompts.aiPaneCopyPrompt_2cols;
403
- const preset = copyPromptDetails.presets.heroDefault;
404
- const copyResults: string[] = [];
405
- const promptsToRun = [
406
- { prompt: promptValueCol1, presetKey: 'left' as ColumnPresetKey },
407
- { prompt: promptValueCol2, presetKey: 'right' as ColumnPresetKey },
408
- ];
409
-
410
- for (const item of promptsToRun) {
411
- const columnPreset = preset[item.presetKey];
412
- const formattedCopyPrompt = copyPromptDetails.user_template
413
- .replace('{{SHELL_JSON}}', shellResult)
414
- .replace('{{COPY_INPUT}}', overallPrompt)
415
- .replace('{{COLUMN_PROMPT}}', item.prompt)
416
- .replace(
417
- '{{DESIGN_INPUT}}',
418
- "N/A - Use the provided Shell JSON's design."
419
- )
420
- .replace('{{LAYOUT_TYPE}}', layout)
421
- .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
422
-
423
- const copyResult = await callAskLemurAPI(
424
- formattedCopyPrompt,
425
- copyPromptDetails.system || '',
426
- false,
427
- isSandboxMode
428
- );
429
- copyResults.push(copyResult);
430
- }
431
- const finalPane = parseAiPane(shellResult, copyResults, layout);
432
- handleApplyTemplate(finalPane);
433
- } else if (layoutChoice === 'standard' && liveTemplate.markdown) {
434
- if (copyMode === 'raw' && isAiStyling) {
435
- const activeConfig =
436
- prompts.aiPromptsIndex.find((p) => p.id === selectedPromptId) ||
437
- prompts.aiPromptsIndex[0];
438
- const stylePromptKey = activeConfig.prompts.style;
439
- const stylePromptDetails = (prompts as any)[stylePromptKey];
440
-
441
- const formattedStylePrompt = stylePromptDetails.user_template
442
- .replace('{{SHELL_JSON}}', shellResult)
443
- .replace('{{COPY_INPUT}}', copyValue);
444
-
445
- const styledResult = await callAskLemurAPI(
446
- formattedStylePrompt,
447
- stylePromptDetails.system || '',
448
- false,
449
- isSandboxMode
450
- );
451
- const finalPane = parseAiPane(shellResult, styledResult, layout);
452
- handleApplyTemplate(finalPane);
453
- return;
454
- }
455
-
456
- if (copyMode === 'raw') {
457
- liveTemplate.markdown.markdownBody = copyValue;
458
- handleApplyTemplate(liveTemplate);
459
- return;
460
- }
461
- if (copyMode === 'prompt') {
462
- if (!shellResult || shellResult === '{}') {
463
- throw new Error(
464
- 'Could not generate a valid AI shell from this design.'
465
- );
466
- }
467
-
468
- const copyPromptDetails = prompts.aiPaneCopyPrompt;
469
- const formattedCopyPrompt = copyPromptDetails.user_template
470
- .replace('{{COPY_INPUT}}', promptValue)
471
- .replace(
472
- '{{DESIGN_INPUT}}',
473
- "N/A - Use the provided Shell JSON's design."
474
- )
475
- .replace('{{LAYOUT_TYPE}}', layout)
476
- .replace('{{SHELL_JSON}}', shellResult);
477
-
478
- const copyResult = await callAskLemurAPI(
479
- formattedCopyPrompt,
480
- copyPromptDetails.system || '',
481
- false,
482
- isSandboxMode
483
- );
484
-
485
- const newNodes = parseAiCopyHtml(
486
- copyResult,
487
- liveTemplate.markdown.id
488
- );
489
- const finalPane = cloneDeep(liveTemplate);
490
- finalPane.markdown!.nodes = newNodes;
491
- handleApplyTemplate(finalPane);
492
- }
493
- } else {
494
- throw new Error(
495
- 'Template and layout mismatch. Please go back and try again.'
496
- );
497
- }
498
- } else if (initialChoice === 'ai') {
499
- const activeConfig = prompts.aiPromptsIndex.find(
500
- (p) => p.id === selectedPromptId
501
- );
502
- if (!activeConfig) throw new Error('Selected prompt type not found.');
503
-
504
- let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
505
- if (aiDesignConfig.baseColor)
506
- designInput += ` Base the colors around **${aiDesignConfig.baseColor}**.`;
507
- if (aiDesignConfig.accentColor)
508
- designInput += ` Use **${aiDesignConfig.accentColor}** as an accent color.`;
509
- if (aiDesignConfig.additionalNotes)
510
- designInput += ` Refine with these notes: "${aiDesignConfig.additionalNotes}"`;
511
-
512
- const layout = 'Text Only';
513
- const promptMap = prompts as any;
514
-
515
- if (layoutChoice === 'standard') {
516
- const shellPromptKey = activeConfig.prompts.shell;
517
- const shellPromptDetails = promptMap[shellPromptKey];
518
- const formattedShellPrompt = shellPromptDetails.user_template
519
- .replace('{{DESIGN_INPUT}}', designInput)
520
- .replace('{{LAYOUT_TYPE}}', layout);
521
-
522
- const shellResult = await callAskLemurAPI(
523
- formattedShellPrompt,
524
- shellPromptDetails.system || '',
525
- true,
526
- isSandboxMode
527
- );
528
-
529
- const copyPromptKey = activeConfig.prompts.copy;
530
- const copyPromptDetails = promptMap[copyPromptKey];
531
- const copyInputContent =
532
- copyMode === 'prompt' ? promptValue : copyValue;
533
- const formattedCopyPrompt = copyPromptDetails.user_template
534
- .replace('{{COPY_INPUT}}', copyInputContent)
535
- .replace('{{DESIGN_INPUT}}', designInput)
536
- .replace('{{LAYOUT_TYPE}}', layout)
537
- .replace('{{SHELL_JSON}}', shellResult);
538
-
539
- const copyResult = await callAskLemurAPI(
540
- formattedCopyPrompt,
541
- copyPromptDetails.system || '',
542
- false,
543
- isSandboxMode
544
- );
545
- const finalPane = parseAiPane(shellResult, copyResult, layout);
546
- handleApplyTemplate(finalPane);
547
- } else if (layoutChoice === 'grid') {
548
- const shellPromptKey = activeConfig.prompts.shell;
549
- const shellPromptDetails = promptMap[shellPromptKey];
550
- const formattedShellPrompt = shellPromptDetails.user_template
551
- .replace('{{COPY_INPUT}}', overallPrompt)
552
- .replace('{{DESIGN_INPUT}}', designInput);
553
-
554
- const shellResult = await callAskLemurAPI(
555
- formattedShellPrompt,
556
- shellPromptDetails.system || '',
557
- true,
558
- isSandboxMode
559
- );
560
-
561
- if (copyMode === 'raw') {
562
- const rawContents = [col1Copy, col2Copy];
563
- const finalPane = parseAiPane(shellResult, rawContents, layout);
564
- handleApplyTemplate(finalPane);
565
- } else {
566
- const copyPromptKey = activeConfig.prompts.copy;
567
- const copyPromptDetails = promptMap[copyPromptKey];
568
- const preset =
569
- copyPromptDetails.presets?.[activeConfig.variants[0]] ||
570
- copyPromptDetails.presets?.heroDefault;
571
- const copyResults: string[] = [];
179
+ const renderInitialStep = () => {
180
+ const designLibraryButton = !isTemplate && (
181
+ <button
182
+ key="library"
183
+ onClick={() => handleInitialChoice('library')}
184
+ className="group relative flex flex-col items-center justify-center rounded-xl border-2 border-gray-200 bg-white p-6 transition-all duration-200 hover:border-cyan-500 hover:shadow-md"
185
+ >
186
+ <div className="mb-3 rounded-full bg-cyan-50 p-3 text-cyan-600 group-hover:bg-cyan-100 group-hover:text-cyan-700">
187
+ <SwatchIcon className="h-8 w-8" />
188
+ </div>
189
+ <h4 className="text-base font-bold text-gray-800">Design Library</h4>
190
+ <p className="mt-1 text-center text-xs text-gray-500">
191
+ Browse pre-built templates and saved designs.
192
+ </p>
193
+ </button>
194
+ );
572
195
 
573
- const promptsToRun = [
574
- { prompt: promptValueCol1, presetKey: 'left' as ColumnPresetKey },
575
- {
576
- prompt: promptValueCol2,
577
- presetKey: 'right' as ColumnPresetKey,
578
- },
579
- ];
196
+ const standardLayoutButton = hasAssemblyAI && (
197
+ <button
198
+ key="standard"
199
+ onClick={() => handleInitialChoice('ai', 'standard')}
200
+ className="group relative flex flex-col items-center justify-center rounded-xl border-2 border-gray-200 bg-white p-6 transition-all duration-200 hover:border-purple-500 hover:shadow-md"
201
+ >
202
+ <div className="text-md absolute right-3 top-3 rounded-full bg-purple-100 px-2 py-0.5 font-bold text-purple-700">
203
+ Design with AI
204
+ </div>
205
+ <div className="mb-3 rounded-full bg-purple-50 p-3 text-purple-600 group-hover:bg-purple-100 group-hover:text-purple-700">
206
+ <DocumentIcon className="h-8 w-8" />
207
+ </div>
208
+ <h4 className="text-base font-bold text-gray-800">Standard Layout</h4>
209
+ <p className="mt-1 text-center text-xs text-gray-500">
210
+ Single column flow. Perfect for articles and intros.
211
+ </p>
212
+ </button>
213
+ );
580
214
 
581
- for (const item of promptsToRun) {
582
- const columnPreset = preset[item.presetKey];
583
- const formattedCopyPrompt = copyPromptDetails.user_template
584
- .replace('{{SHELL_JSON}}', shellResult)
585
- .replace('{{COPY_INPUT}}', overallPrompt)
586
- .replace('{{COLUMN_PROMPT}}', item.prompt)
587
- .replace('{{DESIGN_INPUT}}', designInput)
588
- .replace('{{LAYOUT_TYPE}}', layout)
589
- .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
215
+ const gridLayoutButton = hasAssemblyAI && (
216
+ <button
217
+ key="grid"
218
+ onClick={() => handleInitialChoice('ai', 'grid')}
219
+ className="group relative flex flex-col items-center justify-center rounded-xl border-2 border-gray-200 bg-white p-6 transition-all duration-200 hover:border-purple-500 hover:shadow-md"
220
+ >
221
+ <div className="text-md absolute right-3 top-3 rounded-full bg-purple-100 px-2 py-0.5 font-bold text-purple-700">
222
+ Design with AI
223
+ </div>
224
+ <div className="mb-3 rounded-full bg-purple-50 p-3 text-purple-600 group-hover:bg-purple-100 group-hover:text-purple-700">
225
+ <SquaresPlusIcon className="h-8 w-8" />
226
+ </div>
227
+ <h4 className="text-base font-bold text-gray-800">Two-Column Grid</h4>
228
+ <p className="mt-1 text-center text-xs text-gray-500">
229
+ Split content. Great for features and comparisons.
230
+ </p>
231
+ </button>
232
+ );
590
233
 
591
- const copyResult = await callAskLemurAPI(
592
- formattedCopyPrompt,
593
- copyPromptDetails.system || '',
594
- false,
595
- isSandboxMode
596
- );
597
- copyResults.push(copyResult);
598
- }
599
- const finalPane = parseAiPane(shellResult, copyResults, layout);
600
- handleApplyTemplate(finalPane);
601
- }
602
- }
603
- }
604
- } catch (err: any) {
605
- setError(err.message || 'Failed to generate AI pane.');
606
- setStep('error');
607
- }
608
- }, [
609
- aiDesignConfig,
610
- copyMode,
611
- promptValue,
612
- copyValue,
613
- col1Copy,
614
- col2Copy,
615
- overallPrompt,
616
- promptValueCol1,
617
- promptValueCol2,
618
- isSandboxMode,
619
- initialChoice,
620
- layoutChoice,
621
- selectedLibraryEntry,
622
- handleApplyTemplate,
623
- selectedPromptId,
624
- isAiStyling,
625
- ]);
234
+ const creativeDesignButton = hasAssemblyAI && (
235
+ <button
236
+ key="creative"
237
+ onClick={() => handleInitialChoice('ai', 'creative')}
238
+ className="group relative flex flex-col items-center justify-center rounded-xl border-2 border-gray-200 bg-white p-6 transition-all duration-200 hover:border-pink-500 hover:shadow-md"
239
+ >
240
+ <div className="text-md absolute right-3 top-3 rounded-full bg-pink-100 px-2 py-0.5 font-bold text-pink-700">
241
+ Design with AI
242
+ </div>
243
+ <div className="mb-3 rounded-full bg-pink-50 p-3 text-pink-600 group-hover:bg-pink-100 group-hover:text-pink-700">
244
+ <PaintBrushIcon className="h-8 w-8" />
245
+ </div>
246
+ <h4 className="text-base font-bold text-gray-800">Creative Design</h4>
247
+ <p className="mt-1 text-center text-xs text-gray-500">
248
+ Free-form HTML/CSS generation. Unique layouts.
249
+ </p>
250
+ </button>
251
+ );
626
252
 
627
- const renderInitialStep = () => (
628
- <div className="p-4">
629
- <h3 className="mb-4 text-center font-action text-xl font-bold text-gray-800">
630
- How would you like to design your new pane?
631
- </h3>
632
- <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
633
- <button
634
- onClick={() => handleInitialChoice('library')}
635
- className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
636
- >
637
- <SwatchIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
638
- <h4 className="font-bold text-gray-800">Use Design Library</h4>
639
- <p className="text-sm text-gray-600">
640
- Start with a pre-made design and add your own content.
641
- </p>
642
- </button>
643
- {hasAssemblyAI && (
644
- <button
645
- onClick={() => handleInitialChoice('ai')}
646
- className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
647
- >
648
- <SparklesIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
649
- <h4 className="font-bold text-gray-800">Design with AI</h4>
650
- <p className="text-sm text-gray-600">
651
- Let AI generate a complete design and copy from your prompt.
652
- </p>
653
- </button>
654
- )}
655
- <button
656
- onClick={() => handleInitialChoice('blank')}
657
- className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
658
- >
659
- <DocumentPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
660
- <h4 className="font-bold text-gray-800">Blank Slate</h4>
661
- <p className="text-sm text-gray-600">
662
- Add a simple, empty pane to build from scratch.
663
- </p>
664
- </button>
665
- </div>
666
- </div>
667
- );
253
+ const buttonList = first
254
+ ? [
255
+ creativeDesignButton,
256
+ gridLayoutButton,
257
+ designLibraryButton,
258
+ standardLayoutButton,
259
+ ]
260
+ : [
261
+ standardLayoutButton,
262
+ designLibraryButton,
263
+ creativeDesignButton,
264
+ gridLayoutButton,
265
+ ];
668
266
 
669
- const renderDashboard = () => {
670
267
  return (
671
- <div className="space-y-6 p-4">
672
- {/* Layout Selection */}
673
- {initialChoice === 'ai' && (
674
- <div className="flex justify-center border-b pb-4">
675
- <div className="inline-flex rounded-lg bg-gray-100 p-1">
676
- <button
677
- onClick={() => setLayoutChoice('standard')}
678
- className={`flex items-center space-x-2 rounded-md px-4 py-2 text-sm font-bold transition-all ${
679
- layoutChoice === 'standard'
680
- ? 'bg-white text-cyan-700 shadow-sm'
681
- : 'text-gray-500 hover:text-gray-900'
682
- }`}
268
+ <div className="space-y-4 p-4">
269
+ {!hasAssemblyAI && (
270
+ <div className="rounded-lg border-l-4 border-blue-400 bg-blue-50 p-4 shadow-sm">
271
+ <p className="text-sm text-blue-800">
272
+ Tract Stack uses AssemblyAI AskLemur service to generate designs,
273
+ describe content, and streamline the management of your site. We
274
+ strongly recommend enabling these features. See{' '}
275
+ <a
276
+ href="https://freewebpress.org"
277
+ target="_blank"
278
+ rel="noopener noreferrer"
279
+ className="font-bold underline"
683
280
  >
684
- <DocumentIcon className="h-5 w-5" />
685
- <span>Standard Layout</span>
686
- </button>
687
- <button
688
- onClick={() => setLayoutChoice('grid')}
689
- className={`flex items-center space-x-2 rounded-md px-4 py-2 text-sm font-bold transition-all ${
690
- layoutChoice === 'grid'
691
- ? 'bg-white text-cyan-700 shadow-sm'
692
- : 'text-gray-500 hover:text-gray-900'
693
- }`}
694
- >
695
- <SquaresPlusIcon className="h-5 w-5" />
696
- <span>2-Column Grid</span>
697
- </button>
698
- </div>
281
+ https://freewebpress.org
282
+ </a>{' '}
283
+ for detailed instructions.
284
+ </p>
699
285
  </div>
700
286
  )}
701
287
 
702
- <CopyInputStep
703
- layoutChoice={layoutChoice}
704
- copyMode={copyMode}
705
- onCopyModeChange={setCopyMode}
706
- topic={topic}
707
- onTopicChange={setTopic}
708
- showAdvancedPrompts={showAdvancedPrompts}
709
- onShowAdvancedPromptsChange={setShowAdvancedPrompts}
710
- promptValue={promptValue}
711
- onPromptValueChange={setPromptValue}
712
- copyValue={copyValue}
713
- onCopyValueChange={setCopyValue}
714
- overallPrompt={overallPrompt}
715
- onOverallPromptChange={setOverallPrompt}
716
- promptValueCol1={promptValueCol1}
717
- onPromptValueCol1Change={setPromptValueCol1}
718
- promptValueCol2={promptValueCol2}
719
- onPromptValueCol2Change={setPromptValueCol2}
720
- col1Copy={col1Copy}
721
- onCol1CopyChange={setCol1Copy}
722
- col2Copy={col2Copy}
723
- onCol2CopyChange={setCol2Copy}
724
- hasRetainedContent={selectedLibraryEntry?.retain}
725
- promptOptions={promptOptions}
726
- selectedPromptId={selectedPromptId}
727
- onSelectedPromptIdChange={setSelectedPromptId}
728
- isAiStyling={isAiStyling}
729
- onIsAiStylingChange={setIsAiStyling}
730
- showStyleToggle={initialChoice === 'library'}
731
- />
732
-
733
- {initialChoice === 'ai' && (
734
- <>
735
- <div className="my-4 flex items-center">
736
- <BooleanToggle
737
- label="Customize Styles"
738
- value={showStyles}
739
- onChange={setShowStyles}
740
- size="sm"
741
- />
742
- </div>
288
+ <div className="mx-auto mb-6 max-w-xl text-center">
289
+ <h3 className="text-lg font-bold text-gray-800">
290
+ Don't publish a page. Stack your story.
291
+ </h3>
292
+ <p className="text-sm text-gray-500">
293
+ A static page is a wall of text. A Smart Tract is a series of beats.
294
+ Build one Pane at a time and give each one a single job. Every Pane
295
+ acts as a listening device—telling you exactly when your audience
296
+ leans in, and when they drift away.
297
+ </p>
298
+ </div>
743
299
 
744
- {showStyles && (
745
- <div className="rounded-lg border border-gray-100 bg-gray-50 p-4">
746
- <AiDesignStep
747
- designConfig={aiDesignConfig}
748
- onDesignConfigChange={setAiDesignConfig}
749
- />
750
- </div>
751
- )}
752
- </>
753
- )}
300
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
301
+ {buttonList}
302
+ </div>
754
303
 
755
- <div className="flex justify-between pt-4">
304
+ <div className="mt-4 flex justify-center border-t border-gray-100 pt-4">
756
305
  <button
757
- onClick={handleBack}
758
- 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"
306
+ onClick={() => handleBlankSlate()}
307
+ className="text-sm text-gray-500 underline transition-colors hover:text-gray-800"
759
308
  >
760
- Cancel
761
- </button>
762
- <button
763
- onClick={handleFinalGenerate}
764
- disabled={
765
- copyMode === 'prompt'
766
- ? !promptValue.trim() && !overallPrompt.trim()
767
- : copyMode === 'raw'
768
- ? !copyValue.trim() && (!col1Copy.trim() || !col2Copy.trim())
769
- : false
770
- }
771
- className="rounded-md bg-cyan-600 px-6 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
772
- >
773
- ✨ Generate Pane
309
+ Blank Slate
774
310
  </button>
775
311
  </div>
776
-
777
- {initialChoice === 'ai' && !isSandboxMode && (
778
- <div className="text-center text-sm text-gray-600">
779
- <button
780
- onClick={() => setStep('directInject')}
781
- className="font-bold text-cyan-700 underline hover:text-cyan-900 focus:outline-none"
782
- >
783
- Direct Inject
784
- </button>
785
- </div>
786
- )}
787
312
  </div>
788
313
  );
789
314
  };
790
315
 
791
- const renderDesignLibraryStep = () => (
792
- <div className="space-y-4 p-4">
793
- <div className="flex justify-start">
794
- <button
795
- onClick={handleBack}
796
- 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"
797
- >
798
- ← Back to Choice
799
- </button>
800
- </div>
801
- <DesignLibraryStep onSelect={handleDesignLibrarySelect} />
802
- </div>
803
- );
804
-
805
- const renderDirectInjectStep = () => (
806
- <DirectInjectStep
807
- onBack={handleBack}
808
- onCreatePane={handleApplyTemplate}
809
- layout={layoutChoice}
810
- />
811
- );
812
-
813
- const renderLoading = () => (
814
- <div className="flex min-h-80 flex-col items-center justify-center space-y-4 p-6">
815
- <div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
816
- <p className="text-sm text-gray-600">Generating AI Pane...</p>
817
- <p className="text-xs text-gray-500">This may take a moment.</p>
818
- </div>
819
- );
820
-
821
316
  const renderError = () => (
822
317
  <div className="space-y-4 rounded-lg bg-red-50 p-6 text-center">
823
318
  <h4 className="text-lg font-bold text-red-800">Generation Failed</h4>
@@ -836,13 +331,52 @@ const AddPaneNewPanel = ({
836
331
  case 'initial':
837
332
  return renderInitialStep();
838
333
  case 'dashboard':
839
- return renderDashboard();
334
+ return (
335
+ <AiStandardDesignStep
336
+ layoutChoice={layoutChoice === 'grid' ? 'grid' : 'standard'}
337
+ onBack={handleBack}
338
+ onCreatePane={handleApplyTemplate}
339
+ isSandboxMode={isSandboxMode}
340
+ onDirectInject={() => setStep('directInject')}
341
+ />
342
+ );
840
343
  case 'designLibrary':
841
- return renderDesignLibraryStep();
842
- case 'loading':
843
- return renderLoading();
344
+ return <DesignLibraryStep onSelect={handleDesignLibrarySelect} />;
345
+ case 'library-copy':
346
+ if (!selectedLibraryEntry) return renderInitialStep();
347
+ return (
348
+ <AiLibraryCopyStep
349
+ entry={selectedLibraryEntry}
350
+ onBack={handleBack}
351
+ onCreatePane={handleApplyTemplate}
352
+ isSandboxMode={isSandboxMode}
353
+ />
354
+ );
355
+ case 'creativeInject':
356
+ return (
357
+ <CreativeInjectStep
358
+ onBack={handleBack}
359
+ onCreatePane={handleApplyTemplate}
360
+ />
361
+ );
844
362
  case 'directInject':
845
- return renderDirectInjectStep();
363
+ return (
364
+ <DirectInjectStep
365
+ onBack={handleBack}
366
+ onCreatePane={handleApplyTemplate}
367
+ layout={layoutChoice === 'grid' ? 'grid' : 'standard'}
368
+ />
369
+ );
370
+ case 'ai-creative':
371
+ return (
372
+ <AiCreativeDesignStep
373
+ onBack={handleBack}
374
+ onSuccess={() => setParentMode(PaneAddMode.DEFAULT, true)}
375
+ onDirectInject={() => setStep('creativeInject')}
376
+ onCreatePane={handleApplyTemplate}
377
+ isSandboxMode={isSandboxMode}
378
+ />
379
+ );
846
380
  case 'error':
847
381
  return renderError();
848
382
  default: