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.
Files changed (63) hide show
  1. package/dist/index.js +18 -0
  2. package/package.json +1 -1
  3. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
  4. package/templates/src/components/codehooks/ListContentSetup.tsx +1 -1
  5. package/templates/src/components/compositor/Compositor.tsx +1 -0
  6. package/templates/src/components/compositor/Node.tsx +41 -17
  7. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +9 -6
  8. package/templates/src/components/compositor/nodes/GridLayout.tsx +124 -0
  9. package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +33 -0
  10. package/templates/src/components/compositor/nodes/Markdown.tsx +67 -37
  11. package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +56 -0
  12. package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +8 -2
  13. package/templates/src/components/edit/PanelSwitch.tsx +232 -75
  14. package/templates/src/components/edit/SettingsPanel.tsx +0 -1
  15. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +3 -3
  16. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +402 -167
  17. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +2 -2
  18. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -7
  19. package/templates/src/components/edit/pane/PanePanel_impression.tsx +1 -1
  20. package/templates/src/components/edit/pane/RestylePaneModal.tsx +8 -5
  21. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +6 -6
  22. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +3 -3
  23. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -4
  24. package/templates/src/components/edit/panels/StyleElementPanel.tsx +11 -4
  25. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +8 -8
  26. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +14 -4
  27. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +16 -4
  28. package/templates/src/components/edit/panels/StyleImagePanel.tsx +7 -3
  29. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +9 -2
  30. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +5 -2
  31. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +5 -2
  32. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +7 -3
  33. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +9 -2
  34. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +5 -2
  35. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +5 -2
  36. package/templates/src/components/edit/panels/StyleParentPanel.tsx +530 -171
  37. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +77 -42
  38. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +38 -22
  39. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +171 -66
  40. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +166 -98
  41. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +7 -3
  42. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +9 -2
  43. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +5 -2
  44. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +6 -2
  45. package/templates/src/components/edit/state/SaveModal.tsx +10 -2
  46. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +6 -6
  47. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +1 -1
  48. package/templates/src/components/widgets/ImpressionWrapper.tsx +4 -1
  49. package/templates/src/constants/prompts.json +23 -2
  50. package/templates/src/stores/nodes.ts +356 -212
  51. package/templates/src/stores/storykeep.ts +3 -1
  52. package/templates/src/types/compositorTypes.ts +56 -3
  53. package/templates/src/types/tractstack.ts +1 -0
  54. package/templates/src/utils/compositor/TemplateNodes.ts +8 -0
  55. package/templates/src/utils/compositor/aiPaneParser.ts +263 -83
  56. package/templates/src/utils/compositor/designLibraryHelper.ts +12 -9
  57. package/templates/src/utils/compositor/nodesHelper.ts +229 -0
  58. package/templates/src/utils/compositor/reduceNodesClassNames.ts +40 -1
  59. package/templates/src/utils/compositor/typeGuards.ts +7 -0
  60. package/templates/src/utils/etl/extractor.ts +1 -5
  61. package/templates/src/utils/etl/index.ts +1 -0
  62. package/templates/src/utils/etl/transformer.ts +70 -25
  63. 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('copyInput');
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
- setStep('initial');
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 === 'designLibrary' || step === 'aiDesign' || 'error') {
168
- setStep('copyInput');
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: 'New Pane',
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 = async (entry: DesignLibraryEntry) => {
203
- if (copyMode === 'raw') {
204
- const liveTemplate = convertStorageToLiveTemplate(
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 handleAiDesignGenerate = useCallback(async () => {
265
- setError(null);
266
- setStep('loading');
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: TemplatePane) => {
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 renderContentStep = () => (
398
- <div className="space-y-4 p-4">
399
- <CopyInputStep
400
- copyMode={copyMode}
401
- onCopyModeChange={setCopyMode}
402
- promptValue={promptValue}
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={handleBack}
415
- 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"
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
- Back
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={handleCopyContinue}
421
- disabled={
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
- Continue
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
- {initialChoice === `ai` && !isSandboxMode && (
430
- <div className="mt-6 text-center text-sm text-gray-600">
431
- ADVANCED:{' '}
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={() => setStep('directInject')}
434
- className="font-bold text-cyan-700 underline hover:text-cyan-900 focus:outline-none"
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
- Direct Inject
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
- </div>
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 Content
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={handleAiDesignGenerate}
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
- Generate with AI
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-[300px] flex-col items-center justify-center space-y-4 p-6">
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-[400px] rounded-md border bg-gray-50">
788
+ <div className="min-h-96 rounded-md border bg-gray-50">
554
789
  {renderStep()}
555
790
  </div>
556
791
  </div>