astro-tractstack 2.0.17 → 2.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +18 -0
- package/package.json +1 -1
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +1 -1
- package/templates/src/components/compositor/Compositor.tsx +1 -0
- package/templates/src/components/compositor/Node.tsx +41 -17
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +9 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +124 -0
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +33 -0
- package/templates/src/components/compositor/nodes/Markdown.tsx +67 -37
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +56 -0
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +8 -2
- package/templates/src/components/edit/PanelSwitch.tsx +232 -75
- package/templates/src/components/edit/SettingsPanel.tsx +0 -1
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +3 -3
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +402 -167
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +2 -2
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -7
- package/templates/src/components/edit/pane/PanePanel_impression.tsx +1 -1
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +8 -5
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +6 -6
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +3 -3
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -4
- package/templates/src/components/edit/panels/StyleElementPanel.tsx +11 -4
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +8 -8
- package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +14 -4
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +16 -4
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +7 -3
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +9 -2
- package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +5 -2
- package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +5 -2
- package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +7 -3
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +9 -2
- package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +5 -2
- package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +5 -2
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +530 -171
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +77 -42
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +38 -22
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +171 -66
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +166 -98
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +7 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +9 -2
- package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +5 -2
- package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +6 -2
- package/templates/src/components/edit/state/SaveModal.tsx +10 -2
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +6 -6
- package/templates/src/components/fields/PaneBreakShapeSelector.tsx +1 -1
- package/templates/src/components/widgets/ImpressionWrapper.tsx +4 -1
- package/templates/src/constants/prompts.json +23 -2
- package/templates/src/stores/nodes.ts +356 -212
- package/templates/src/stores/storykeep.ts +3 -1
- package/templates/src/types/compositorTypes.ts +56 -3
- package/templates/src/types/tractstack.ts +1 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +8 -0
- package/templates/src/utils/compositor/aiPaneParser.ts +263 -83
- package/templates/src/utils/compositor/designLibraryHelper.ts +12 -9
- package/templates/src/utils/compositor/nodesHelper.ts +229 -0
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +40 -1
- package/templates/src/utils/compositor/typeGuards.ts +7 -0
- package/templates/src/utils/etl/extractor.ts +1 -5
- package/templates/src/utils/etl/index.ts +1 -0
- package/templates/src/utils/etl/transformer.ts +70 -25
- package/utils/inject-files.ts +18 -0
|
@@ -3,6 +3,8 @@ import { useState, useCallback } from 'react';
|
|
|
3
3
|
import DocumentPlusIcon from '@heroicons/react/24/outline/DocumentPlusIcon';
|
|
4
4
|
import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
|
|
5
5
|
import SwatchIcon from '@heroicons/react/24/outline/SwatchIcon';
|
|
6
|
+
import SquaresPlusIcon from '@heroicons/react/24/outline/SquaresPlusIcon';
|
|
7
|
+
import DocumentIcon from '@heroicons/react/24/outline/DocumentIcon';
|
|
6
8
|
import { NodesContext, getCtx } from '@/stores/nodes';
|
|
7
9
|
import { cloneDeep } from '@/utils/helpers';
|
|
8
10
|
import { hasAssemblyAIStore } from '@/stores/storykeep';
|
|
@@ -25,6 +27,7 @@ type Step =
|
|
|
25
27
|
| 'initial'
|
|
26
28
|
| 'copyInput'
|
|
27
29
|
| 'designLibrary'
|
|
30
|
+
| 'layoutChoice'
|
|
28
31
|
| 'aiDesign'
|
|
29
32
|
| 'loading'
|
|
30
33
|
| 'error'
|
|
@@ -32,6 +35,8 @@ type Step =
|
|
|
32
35
|
|
|
33
36
|
type InitialChoice = 'library' | 'ai' | 'blank';
|
|
34
37
|
type CopyMode = 'prompt' | 'raw';
|
|
38
|
+
type LayoutChoice = 'standard' | 'grid';
|
|
39
|
+
type ColumnPresetKey = 'left' | 'right';
|
|
35
40
|
|
|
36
41
|
interface GenerationResponse {
|
|
37
42
|
success: boolean;
|
|
@@ -135,10 +140,25 @@ const AddPaneNewPanel = ({
|
|
|
135
140
|
const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
|
|
136
141
|
null
|
|
137
142
|
);
|
|
143
|
+
const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
|
|
138
144
|
const [error, setError] = useState<string | null>(null);
|
|
145
|
+
|
|
139
146
|
const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
|
|
140
147
|
const [promptValue, setPromptValue] = useState('');
|
|
141
148
|
const [copyValue, setCopyValue] = useState('');
|
|
149
|
+
|
|
150
|
+
const [overallPrompt, setOverallPrompt] = useState(
|
|
151
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.default
|
|
152
|
+
);
|
|
153
|
+
const [copyModeCol1, setCopyModeCol1] = useState<CopyMode>('prompt');
|
|
154
|
+
const [promptValueCol1, setPromptValueCol1] = useState('');
|
|
155
|
+
const [copyValueCol1, setCopyValueCol1] = useState('');
|
|
156
|
+
const [copyModeCol2, setCopyModeCol2] = useState<CopyMode>('prompt');
|
|
157
|
+
const [promptValueCol2, setPromptValueCol2] = useState('');
|
|
158
|
+
const [copyValueCol2, setCopyValueCol2] = useState('');
|
|
159
|
+
|
|
160
|
+
const [selectedLibraryEntry, setSelectedLibraryEntry] =
|
|
161
|
+
useState<DesignLibraryEntry | null>(null);
|
|
142
162
|
const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
|
|
143
163
|
harmony: 'Analogous',
|
|
144
164
|
baseColor: '',
|
|
@@ -153,27 +173,36 @@ const AddPaneNewPanel = ({
|
|
|
153
173
|
|
|
154
174
|
if (choice === 'blank') {
|
|
155
175
|
handleBlankSlate();
|
|
156
|
-
} else {
|
|
157
|
-
setStep('
|
|
176
|
+
} else if (choice === 'library') {
|
|
177
|
+
setStep('designLibrary');
|
|
178
|
+
} else if (choice === 'ai') {
|
|
179
|
+
setStep('layoutChoice');
|
|
158
180
|
}
|
|
159
181
|
};
|
|
160
182
|
|
|
183
|
+
const handleLayoutChoice = (choice: LayoutChoice) => {
|
|
184
|
+
setLayoutChoice(choice);
|
|
185
|
+
setStep('aiDesign');
|
|
186
|
+
};
|
|
187
|
+
|
|
161
188
|
const handleBack = () => {
|
|
162
189
|
setError(null);
|
|
163
190
|
if (step === 'copyInput') {
|
|
164
|
-
|
|
191
|
+
if (initialChoice === 'library') {
|
|
192
|
+
setStep('designLibrary');
|
|
193
|
+
} else if (initialChoice === 'ai') {
|
|
194
|
+
setStep('aiDesign');
|
|
195
|
+
} else {
|
|
196
|
+
setStep('initial');
|
|
197
|
+
}
|
|
198
|
+
} else if (step === 'aiDesign') {
|
|
199
|
+
setStep('layoutChoice');
|
|
165
200
|
} else if (step === 'directInject') {
|
|
166
201
|
setStep('aiDesign');
|
|
167
|
-
} else if (step === '
|
|
168
|
-
setStep('
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const handleCopyContinue = () => {
|
|
173
|
-
if (initialChoice === 'library') {
|
|
174
|
-
setStep('designLibrary');
|
|
175
|
-
} else if (initialChoice === 'ai') {
|
|
176
|
-
setStep('aiDesign');
|
|
202
|
+
} else if (step === 'layoutChoice') {
|
|
203
|
+
setStep('initial');
|
|
204
|
+
} else if (step === 'designLibrary' || step === 'error') {
|
|
205
|
+
setStep('initial');
|
|
177
206
|
}
|
|
178
207
|
};
|
|
179
208
|
|
|
@@ -182,7 +211,7 @@ const AddPaneNewPanel = ({
|
|
|
182
211
|
id: '',
|
|
183
212
|
nodeType: 'Pane',
|
|
184
213
|
parentId: '',
|
|
185
|
-
title: '
|
|
214
|
+
title: '',
|
|
186
215
|
slug: '',
|
|
187
216
|
isDecorative: false,
|
|
188
217
|
markdown: {
|
|
@@ -199,130 +228,25 @@ const AddPaneNewPanel = ({
|
|
|
199
228
|
handleApplyTemplate(blankTemplate);
|
|
200
229
|
};
|
|
201
230
|
|
|
202
|
-
const handleDesignLibrarySelect =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
mergeCopyIntoTemplate(entry.template, [])
|
|
206
|
-
);
|
|
207
|
-
if (liveTemplate.markdown) {
|
|
208
|
-
liveTemplate.markdown.markdownBody = copyValue;
|
|
209
|
-
}
|
|
210
|
-
handleApplyTemplate(liveTemplate);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (copyMode === 'prompt') {
|
|
215
|
-
setError(null);
|
|
216
|
-
setStep('loading');
|
|
217
|
-
try {
|
|
218
|
-
const liveTemplate = convertStorageToLiveTemplate(entry.template);
|
|
219
|
-
if (!liveTemplate.markdown) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
'The selected design library item is not compatible with this workflow as it has no markdown section.'
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const shellJson = convertTemplateToAIShell(liveTemplate);
|
|
226
|
-
if (!shellJson || shellJson === '{}') {
|
|
227
|
-
throw new Error(
|
|
228
|
-
'Could not generate a valid AI shell from this design.'
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
233
|
-
const layout = 'Text Only';
|
|
234
|
-
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
235
|
-
.replace('{{COPY_INPUT}}', promptValue)
|
|
236
|
-
.replace(
|
|
237
|
-
'{{DESIGN_INPUT}}',
|
|
238
|
-
"N/A - Use the provided Shell JSON's design."
|
|
239
|
-
)
|
|
240
|
-
.replace('{{LAYOUT_TYPE}}', layout)
|
|
241
|
-
.replace('{{SHELL_JSON}}', shellJson);
|
|
242
|
-
|
|
243
|
-
const copyResult = await callAskLemurAPI(
|
|
244
|
-
formattedCopyPrompt,
|
|
245
|
-
copyPromptDetails.system || '',
|
|
246
|
-
false,
|
|
247
|
-
isSandboxMode
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
const newNodes = parseAiCopyHtml(copyResult, liveTemplate.markdown.id);
|
|
251
|
-
|
|
252
|
-
const finalPane = cloneDeep(liveTemplate);
|
|
253
|
-
|
|
254
|
-
finalPane.markdown!.nodes = newNodes;
|
|
255
|
-
|
|
256
|
-
handleApplyTemplate(finalPane);
|
|
257
|
-
} catch (err: any) {
|
|
258
|
-
setError(err.message || 'Failed to generate AI copy for this design.');
|
|
259
|
-
setStep('error');
|
|
260
|
-
}
|
|
261
|
-
}
|
|
231
|
+
const handleDesignLibrarySelect = (entry: DesignLibraryEntry) => {
|
|
232
|
+
setSelectedLibraryEntry(entry);
|
|
233
|
+
setStep('copyInput');
|
|
262
234
|
};
|
|
263
235
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
269
|
-
if (aiDesignConfig.baseColor)
|
|
270
|
-
designInput += ` Base the colors around **${aiDesignConfig.baseColor}**.`;
|
|
271
|
-
if (aiDesignConfig.accentColor)
|
|
272
|
-
designInput += ` Use **${aiDesignConfig.accentColor}** as an accent color.`;
|
|
273
|
-
if (aiDesignConfig.additionalNotes)
|
|
274
|
-
designInput += ` Refine with these notes: "${aiDesignConfig.additionalNotes}"`;
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
const shellPromptDetails = prompts.aiPaneShellPrompt;
|
|
278
|
-
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
279
|
-
const layout = 'Text Only';
|
|
280
|
-
|
|
281
|
-
const formattedShellPrompt = shellPromptDetails.user_template
|
|
282
|
-
.replace('{{DESIGN_INPUT}}', designInput)
|
|
283
|
-
.replace('{{LAYOUT_TYPE}}', layout);
|
|
284
|
-
|
|
285
|
-
const shellResult = await callAskLemurAPI(
|
|
286
|
-
formattedShellPrompt,
|
|
287
|
-
shellPromptDetails.system || '',
|
|
288
|
-
true,
|
|
289
|
-
isSandboxMode
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
const copyInputContent = copyMode === 'prompt' ? promptValue : copyValue;
|
|
293
|
-
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
294
|
-
.replace('{{COPY_INPUT}}', copyInputContent)
|
|
295
|
-
.replace('{{DESIGN_INPUT}}', designInput)
|
|
296
|
-
.replace('{{LAYOUT_TYPE}}', layout)
|
|
297
|
-
.replace('{{SHELL_JSON}}', shellResult);
|
|
298
|
-
|
|
299
|
-
const copyResult = await callAskLemurAPI(
|
|
300
|
-
formattedCopyPrompt,
|
|
301
|
-
copyPromptDetails.system || '',
|
|
302
|
-
false,
|
|
303
|
-
isSandboxMode
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const finalPane = parseAiPane(shellResult, copyResult, layout);
|
|
307
|
-
handleApplyTemplate(finalPane);
|
|
308
|
-
} catch (err: any) {
|
|
309
|
-
setError(err.message || 'Failed to generate AI pane.');
|
|
310
|
-
setStep('error');
|
|
311
|
-
}
|
|
312
|
-
}, [aiDesignConfig, copyMode, promptValue, copyValue, isSandboxMode]);
|
|
236
|
+
const handleAiDesignContinue = () => {
|
|
237
|
+
setStep('copyInput');
|
|
238
|
+
};
|
|
313
239
|
|
|
314
|
-
const handleApplyTemplate = async (template:
|
|
315
|
-
console.log(template);
|
|
240
|
+
const handleApplyTemplate = async (template: any) => {
|
|
316
241
|
if (!ctx) return;
|
|
317
242
|
try {
|
|
318
243
|
const insertTemplate = cloneDeep(template);
|
|
319
|
-
insertTemplate.title = insertTemplate.title || 'New Pane';
|
|
320
|
-
insertTemplate.slug = insertTemplate.slug || '';
|
|
321
|
-
|
|
322
244
|
const ownerId =
|
|
323
245
|
isStoryFragment || isContextPane
|
|
324
246
|
? nodeId
|
|
325
247
|
: ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
|
|
248
|
+
insertTemplate.title = '';
|
|
249
|
+
insertTemplate.slug = '';
|
|
326
250
|
|
|
327
251
|
if (isContextPane) {
|
|
328
252
|
insertTemplate.isContextPane = true;
|
|
@@ -352,6 +276,199 @@ const AddPaneNewPanel = ({
|
|
|
352
276
|
}
|
|
353
277
|
};
|
|
354
278
|
|
|
279
|
+
const handleFinalGenerate = useCallback(async () => {
|
|
280
|
+
setError(null);
|
|
281
|
+
setStep('loading');
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
if (initialChoice === 'library') {
|
|
285
|
+
if (!selectedLibraryEntry) {
|
|
286
|
+
throw new Error('No design library item was selected.');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (copyMode === 'raw') {
|
|
290
|
+
const liveTemplate = convertStorageToLiveTemplate(
|
|
291
|
+
mergeCopyIntoTemplate(selectedLibraryEntry.template, [])
|
|
292
|
+
);
|
|
293
|
+
if (liveTemplate.markdown) {
|
|
294
|
+
liveTemplate.markdown.markdownBody = copyValue;
|
|
295
|
+
}
|
|
296
|
+
handleApplyTemplate(liveTemplate);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (copyMode === 'prompt') {
|
|
301
|
+
const liveTemplate = convertStorageToLiveTemplate(
|
|
302
|
+
selectedLibraryEntry.template
|
|
303
|
+
);
|
|
304
|
+
if (!liveTemplate.markdown) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
'The selected design library item is not compatible with this workflow as it has no markdown section.'
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const shellJson = convertTemplateToAIShell(liveTemplate);
|
|
311
|
+
if (!shellJson || shellJson === '{}') {
|
|
312
|
+
throw new Error(
|
|
313
|
+
'Could not generate a valid AI shell from this design.'
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
318
|
+
const layout = 'Text Only';
|
|
319
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
320
|
+
.replace('{{COPY_INPUT}}', promptValue)
|
|
321
|
+
.replace(
|
|
322
|
+
'{{DESIGN_INPUT}}',
|
|
323
|
+
"N/A - Use the provided Shell JSON's design."
|
|
324
|
+
)
|
|
325
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
326
|
+
.replace('{{SHELL_JSON}}', shellJson);
|
|
327
|
+
|
|
328
|
+
const copyResult = await callAskLemurAPI(
|
|
329
|
+
formattedCopyPrompt,
|
|
330
|
+
copyPromptDetails.system || '',
|
|
331
|
+
false,
|
|
332
|
+
isSandboxMode
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const newNodes = parseAiCopyHtml(
|
|
336
|
+
copyResult,
|
|
337
|
+
liveTemplate.markdown.id
|
|
338
|
+
);
|
|
339
|
+
const finalPane = cloneDeep(liveTemplate);
|
|
340
|
+
finalPane.markdown!.nodes = newNodes;
|
|
341
|
+
handleApplyTemplate(finalPane);
|
|
342
|
+
}
|
|
343
|
+
} else if (initialChoice === 'ai') {
|
|
344
|
+
let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
345
|
+
if (aiDesignConfig.baseColor)
|
|
346
|
+
designInput += ` Base the colors around **${aiDesignConfig.baseColor}**.`;
|
|
347
|
+
if (aiDesignConfig.accentColor)
|
|
348
|
+
designInput += ` Use **${aiDesignConfig.accentColor}** as an accent color.`;
|
|
349
|
+
if (aiDesignConfig.additionalNotes)
|
|
350
|
+
designInput += ` Refine with these notes: "${aiDesignConfig.additionalNotes}"`;
|
|
351
|
+
|
|
352
|
+
const layout = 'Text Only';
|
|
353
|
+
|
|
354
|
+
if (layoutChoice === 'standard') {
|
|
355
|
+
const shellPromptDetails = prompts.aiPaneShellPrompt;
|
|
356
|
+
const formattedShellPrompt = shellPromptDetails.user_template
|
|
357
|
+
.replace('{{DESIGN_INPUT}}', designInput)
|
|
358
|
+
.replace('{{LAYOUT_TYPE}}', layout);
|
|
359
|
+
|
|
360
|
+
const shellResult = await callAskLemurAPI(
|
|
361
|
+
formattedShellPrompt,
|
|
362
|
+
shellPromptDetails.system || '',
|
|
363
|
+
true,
|
|
364
|
+
isSandboxMode
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
368
|
+
const copyInputContent =
|
|
369
|
+
copyMode === 'prompt' ? promptValue : copyValue;
|
|
370
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
371
|
+
.replace('{{COPY_INPUT}}', copyInputContent)
|
|
372
|
+
.replace('{{DESIGN_INPUT}}', designInput)
|
|
373
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
374
|
+
.replace('{{SHELL_JSON}}', shellResult);
|
|
375
|
+
|
|
376
|
+
const copyResult = await callAskLemurAPI(
|
|
377
|
+
formattedCopyPrompt,
|
|
378
|
+
copyPromptDetails.system || '',
|
|
379
|
+
false,
|
|
380
|
+
isSandboxMode
|
|
381
|
+
);
|
|
382
|
+
const finalPane = parseAiPane(shellResult, copyResult, layout);
|
|
383
|
+
handleApplyTemplate(finalPane);
|
|
384
|
+
} else if (layoutChoice === 'grid') {
|
|
385
|
+
const shellPromptDetails = prompts.aiPaneShellPrompt_2cols;
|
|
386
|
+
const formattedShellPrompt = shellPromptDetails.user_template
|
|
387
|
+
.replace('{{COPY_INPUT}}', overallPrompt)
|
|
388
|
+
.replace('{{DESIGN_INPUT}}', designInput);
|
|
389
|
+
|
|
390
|
+
const shellResult = await callAskLemurAPI(
|
|
391
|
+
formattedShellPrompt,
|
|
392
|
+
shellPromptDetails.system || '',
|
|
393
|
+
true,
|
|
394
|
+
isSandboxMode
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt_2cols;
|
|
398
|
+
const preset = copyPromptDetails.presets.heroDefault;
|
|
399
|
+
const copyResults: string[] = [];
|
|
400
|
+
|
|
401
|
+
const promptsToRun: {
|
|
402
|
+
prompt: string;
|
|
403
|
+
copy: string;
|
|
404
|
+
mode: CopyMode;
|
|
405
|
+
presetKey: ColumnPresetKey;
|
|
406
|
+
}[] = [
|
|
407
|
+
{
|
|
408
|
+
prompt: promptValueCol1,
|
|
409
|
+
copy: copyValueCol1,
|
|
410
|
+
mode: copyModeCol1,
|
|
411
|
+
presetKey: 'left',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
prompt: promptValueCol2,
|
|
415
|
+
copy: copyValueCol2,
|
|
416
|
+
mode: copyModeCol2,
|
|
417
|
+
presetKey: 'right',
|
|
418
|
+
},
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
for (const item of promptsToRun) {
|
|
422
|
+
if (item.mode === 'raw') {
|
|
423
|
+
copyResults.push(item.copy);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const columnPreset = preset[item.presetKey];
|
|
428
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
429
|
+
.replace('{{SHELL_JSON}}', shellResult)
|
|
430
|
+
.replace('{{COPY_INPUT}}', overallPrompt)
|
|
431
|
+
.replace('{{COLUMN_PROMPT}}', item.prompt)
|
|
432
|
+
.replace('{{DESIGN_INPUT}}', designInput)
|
|
433
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
434
|
+
.replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
|
|
435
|
+
|
|
436
|
+
const copyResult = await callAskLemurAPI(
|
|
437
|
+
formattedCopyPrompt,
|
|
438
|
+
copyPromptDetails.system || '',
|
|
439
|
+
false,
|
|
440
|
+
isSandboxMode
|
|
441
|
+
);
|
|
442
|
+
copyResults.push(copyResult);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const finalPane = parseAiPane(shellResult, copyResults, layout);
|
|
446
|
+
handleApplyTemplate(finalPane);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} catch (err: any) {
|
|
450
|
+
setError(err.message || 'Failed to generate AI pane.');
|
|
451
|
+
setStep('error');
|
|
452
|
+
}
|
|
453
|
+
}, [
|
|
454
|
+
aiDesignConfig,
|
|
455
|
+
copyMode,
|
|
456
|
+
promptValue,
|
|
457
|
+
copyValue,
|
|
458
|
+
overallPrompt,
|
|
459
|
+
copyModeCol1,
|
|
460
|
+
promptValueCol1,
|
|
461
|
+
copyValueCol1,
|
|
462
|
+
copyModeCol2,
|
|
463
|
+
promptValueCol2,
|
|
464
|
+
copyValueCol2,
|
|
465
|
+
isSandboxMode,
|
|
466
|
+
initialChoice,
|
|
467
|
+
layoutChoice,
|
|
468
|
+
selectedLibraryEntry,
|
|
469
|
+
handleApplyTemplate,
|
|
470
|
+
]);
|
|
471
|
+
|
|
355
472
|
const renderInitialStep = () => (
|
|
356
473
|
<div className="p-4">
|
|
357
474
|
<h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
|
|
@@ -394,51 +511,156 @@ const AddPaneNewPanel = ({
|
|
|
394
511
|
</div>
|
|
395
512
|
);
|
|
396
513
|
|
|
397
|
-
const
|
|
398
|
-
<div className="
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
onPromptValueChange={setPromptValue}
|
|
404
|
-
copyValue={copyValue}
|
|
405
|
-
onCopyValueChange={setCopyValue}
|
|
406
|
-
defaultPrompt={
|
|
407
|
-
first
|
|
408
|
-
? prompts.aiPaneCopyPrompt.heroDefault
|
|
409
|
-
: prompts.aiPaneCopyPrompt.contentDefault
|
|
410
|
-
}
|
|
411
|
-
/>
|
|
412
|
-
<div className="flex justify-between">
|
|
514
|
+
const renderLayoutChoiceStep = () => (
|
|
515
|
+
<div className="p-4">
|
|
516
|
+
<h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
|
|
517
|
+
Choose a Layout Structure
|
|
518
|
+
</h3>
|
|
519
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
413
520
|
<button
|
|
414
|
-
onClick={
|
|
415
|
-
className="
|
|
521
|
+
onClick={() => handleLayoutChoice('standard')}
|
|
522
|
+
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"
|
|
416
523
|
>
|
|
417
|
-
|
|
524
|
+
<DocumentIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
525
|
+
<h4 className="font-bold text-gray-800">Standard Layout</h4>
|
|
526
|
+
<p className="text-sm text-gray-600">
|
|
527
|
+
A single, continuous column of content.
|
|
528
|
+
</p>
|
|
418
529
|
</button>
|
|
419
530
|
<button
|
|
420
|
-
onClick={
|
|
421
|
-
|
|
422
|
-
copyMode === 'prompt' ? !promptValue.trim() : !copyValue.trim()
|
|
423
|
-
}
|
|
424
|
-
className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
531
|
+
onClick={() => handleLayoutChoice('grid')}
|
|
532
|
+
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"
|
|
425
533
|
>
|
|
426
|
-
|
|
534
|
+
<SquaresPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
535
|
+
<h4 className="font-bold text-gray-800">2-Column Grid</h4>
|
|
536
|
+
<p className="text-sm text-gray-600">
|
|
537
|
+
Side-by-side content that stacks on mobile.
|
|
538
|
+
</p>
|
|
427
539
|
</button>
|
|
428
540
|
</div>
|
|
429
|
-
|
|
430
|
-
<
|
|
431
|
-
|
|
541
|
+
<div className="mt-6 flex justify-center">
|
|
542
|
+
<button
|
|
543
|
+
onClick={handleBack}
|
|
544
|
+
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"
|
|
545
|
+
>
|
|
546
|
+
← Back
|
|
547
|
+
</button>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
const renderContentStep = () => {
|
|
553
|
+
if (layoutChoice === 'grid') {
|
|
554
|
+
const isGenerateDisabled =
|
|
555
|
+
(copyModeCol1 === 'prompt' && !promptValueCol1.trim()) ||
|
|
556
|
+
(copyModeCol1 === 'raw' && !copyValueCol1.trim()) ||
|
|
557
|
+
(copyModeCol2 === 'prompt' && !promptValueCol2.trim()) ||
|
|
558
|
+
(copyModeCol2 === 'raw' && !copyValueCol2.trim()) ||
|
|
559
|
+
!overallPrompt.trim();
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
<div className="space-y-4 p-4">
|
|
563
|
+
<div>
|
|
564
|
+
<h4 className="mb-2 block text-sm font-bold text-gray-700">
|
|
565
|
+
Overall Component Brief
|
|
566
|
+
</h4>
|
|
567
|
+
<textarea
|
|
568
|
+
value={overallPrompt}
|
|
569
|
+
onChange={(e) => setOverallPrompt(e.target.value)}
|
|
570
|
+
placeholder="e.g., A compelling hero section for a website about Tract Stack..."
|
|
571
|
+
rows={3}
|
|
572
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
573
|
+
/>
|
|
574
|
+
</div>
|
|
575
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
576
|
+
<div>
|
|
577
|
+
<h4 className="mb-2 block text-sm font-bold text-gray-700">
|
|
578
|
+
Left Column Content
|
|
579
|
+
</h4>
|
|
580
|
+
<CopyInputStep
|
|
581
|
+
copyMode={copyModeCol1}
|
|
582
|
+
onCopyModeChange={setCopyModeCol1}
|
|
583
|
+
promptValue={promptValueCol1}
|
|
584
|
+
onPromptValueChange={setPromptValueCol1}
|
|
585
|
+
copyValue={copyValueCol1}
|
|
586
|
+
onCopyValueChange={setCopyValueCol1}
|
|
587
|
+
defaultPrompt={
|
|
588
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
|
|
589
|
+
}
|
|
590
|
+
/>
|
|
591
|
+
</div>
|
|
592
|
+
<div>
|
|
593
|
+
<h4 className="mb-2 block text-sm font-bold text-gray-700">
|
|
594
|
+
Right Column Content
|
|
595
|
+
</h4>
|
|
596
|
+
<CopyInputStep
|
|
597
|
+
copyMode={copyModeCol2}
|
|
598
|
+
onCopyModeChange={setCopyModeCol2}
|
|
599
|
+
promptValue={promptValueCol2}
|
|
600
|
+
onPromptValueChange={setPromptValueCol2}
|
|
601
|
+
copyValue={copyValueCol2}
|
|
602
|
+
onCopyValueChange={setCopyValueCol2}
|
|
603
|
+
defaultPrompt={
|
|
604
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right
|
|
605
|
+
.prompt
|
|
606
|
+
}
|
|
607
|
+
/>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
<div className="flex justify-between">
|
|
611
|
+
<button
|
|
612
|
+
onClick={handleBack}
|
|
613
|
+
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"
|
|
614
|
+
>
|
|
615
|
+
← Back
|
|
616
|
+
</button>
|
|
617
|
+
<button
|
|
618
|
+
onClick={handleFinalGenerate}
|
|
619
|
+
disabled={isGenerateDisabled}
|
|
620
|
+
className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
621
|
+
>
|
|
622
|
+
✨ Generate Pane
|
|
623
|
+
</button>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return (
|
|
630
|
+
<div className="space-y-4 p-4">
|
|
631
|
+
<CopyInputStep
|
|
632
|
+
copyMode={copyMode}
|
|
633
|
+
onCopyModeChange={setCopyMode}
|
|
634
|
+
promptValue={promptValue}
|
|
635
|
+
onPromptValueChange={setPromptValue}
|
|
636
|
+
copyValue={copyValue}
|
|
637
|
+
onCopyValueChange={setCopyValue}
|
|
638
|
+
defaultPrompt={
|
|
639
|
+
first
|
|
640
|
+
? prompts.aiPaneCopyPrompt.heroDefault
|
|
641
|
+
: prompts.aiPaneCopyPrompt.contentDefault
|
|
642
|
+
}
|
|
643
|
+
/>
|
|
644
|
+
<div className="flex justify-between">
|
|
432
645
|
<button
|
|
433
|
-
onClick={
|
|
434
|
-
className="font-bold text-
|
|
646
|
+
onClick={handleBack}
|
|
647
|
+
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"
|
|
435
648
|
>
|
|
436
|
-
|
|
649
|
+
← Back
|
|
650
|
+
</button>
|
|
651
|
+
<button
|
|
652
|
+
onClick={handleFinalGenerate}
|
|
653
|
+
disabled={
|
|
654
|
+
copyMode === 'prompt' ? !promptValue.trim() : !copyValue.trim()
|
|
655
|
+
}
|
|
656
|
+
className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
657
|
+
>
|
|
658
|
+
✨ Generate Pane
|
|
437
659
|
</button>
|
|
438
660
|
</div>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
663
|
+
};
|
|
442
664
|
|
|
443
665
|
const renderDesignLibraryStep = () => (
|
|
444
666
|
<div className="space-y-4 p-4">
|
|
@@ -447,7 +669,7 @@ const AddPaneNewPanel = ({
|
|
|
447
669
|
onClick={handleBack}
|
|
448
670
|
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"
|
|
449
671
|
>
|
|
450
|
-
← Back to
|
|
672
|
+
← Back to Choice
|
|
451
673
|
</button>
|
|
452
674
|
</div>
|
|
453
675
|
<DesignLibraryStep
|
|
@@ -472,12 +694,23 @@ const AddPaneNewPanel = ({
|
|
|
472
694
|
← Back
|
|
473
695
|
</button>
|
|
474
696
|
<button
|
|
475
|
-
onClick={
|
|
697
|
+
onClick={handleAiDesignContinue}
|
|
476
698
|
className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700"
|
|
477
699
|
>
|
|
478
|
-
|
|
700
|
+
Continue →
|
|
479
701
|
</button>
|
|
480
702
|
</div>
|
|
703
|
+
{initialChoice === `ai` && !isSandboxMode && (
|
|
704
|
+
<div className="mt-6 text-center text-sm text-gray-600">
|
|
705
|
+
ADVANCED:{' '}
|
|
706
|
+
<button
|
|
707
|
+
onClick={() => setStep('directInject')}
|
|
708
|
+
className="font-bold text-cyan-700 underline hover:text-cyan-900 focus:outline-none"
|
|
709
|
+
>
|
|
710
|
+
Direct Inject
|
|
711
|
+
</button>
|
|
712
|
+
</div>
|
|
713
|
+
)}
|
|
481
714
|
</div>
|
|
482
715
|
);
|
|
483
716
|
|
|
@@ -486,7 +719,7 @@ const AddPaneNewPanel = ({
|
|
|
486
719
|
);
|
|
487
720
|
|
|
488
721
|
const renderLoading = () => (
|
|
489
|
-
<div className="flex min-h-
|
|
722
|
+
<div className="flex min-h-80 flex-col items-center justify-center space-y-4 p-6">
|
|
490
723
|
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
491
724
|
<p className="text-sm text-gray-600">Generating AI Pane...</p>
|
|
492
725
|
<p className="text-xs text-gray-500">This may take a moment.</p>
|
|
@@ -510,6 +743,8 @@ const AddPaneNewPanel = ({
|
|
|
510
743
|
switch (step) {
|
|
511
744
|
case 'initial':
|
|
512
745
|
return renderInitialStep();
|
|
746
|
+
case 'layoutChoice':
|
|
747
|
+
return renderLayoutChoiceStep();
|
|
513
748
|
case 'copyInput':
|
|
514
749
|
return renderContentStep();
|
|
515
750
|
case 'designLibrary':
|
|
@@ -550,7 +785,7 @@ const AddPaneNewPanel = ({
|
|
|
550
785
|
</>
|
|
551
786
|
)}
|
|
552
787
|
</div>
|
|
553
|
-
<div className="min-h-
|
|
788
|
+
<div className="min-h-96 rounded-md border bg-gray-50">
|
|
554
789
|
{renderStep()}
|
|
555
790
|
</div>
|
|
556
791
|
</div>
|