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,118 +1,36 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useState
|
|
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
|
|
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 {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
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
|
|
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
|
-
| '
|
|
26
|
+
| 'library-copy'
|
|
33
27
|
| 'error'
|
|
34
|
-
| '
|
|
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 = (
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
setCopyMode('original');
|
|
335
|
-
} else {
|
|
336
|
-
setCopyMode('prompt');
|
|
337
|
-
}
|
|
338
|
-
setStep('dashboard');
|
|
176
|
+
setStep('library-copy');
|
|
339
177
|
};
|
|
340
178
|
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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-
|
|
672
|
-
{
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
<
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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-
|
|
304
|
+
<div className="mt-4 flex justify-center border-t border-gray-100 pt-4">
|
|
756
305
|
<button
|
|
757
|
-
onClick={
|
|
758
|
-
className="
|
|
306
|
+
onClick={() => handleBlankSlate()}
|
|
307
|
+
className="text-sm text-gray-500 underline transition-colors hover:text-gray-800"
|
|
759
308
|
>
|
|
760
|
-
|
|
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
|
|
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
|
|
842
|
-
case '
|
|
843
|
-
return
|
|
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
|
|
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:
|