astro-tractstack 2.0.14 → 2.0.15
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/compositor/Node.tsx +21 -8
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +459 -561
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +18 -81
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +140 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +105 -0
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +395 -0
- package/templates/src/constants/prompts.json +3 -1
- package/templates/src/utils/compositor/aiPaneParser.ts +32 -84
- package/templates/src/utils/compositor/designLibraryHelper.ts +87 -2
- package/utils/inject-files.ts +18 -0
- package/templates/src/components/edit/pane/PageGen.tsx +0 -485
- package/templates/src/components/edit/pane/PageGenSelector.tsx +0 -245
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +0 -339
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react';
|
|
2
|
-
import { AiPanePreview } from './AiPanePreview';
|
|
3
|
-
import type { TemplatePane } from '@/types/compositorTypes';
|
|
4
2
|
import prompts from '@/constants/prompts.json';
|
|
5
3
|
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
4
|
+
import { AiPanePreview } from './AiPanePreview';
|
|
5
|
+
import { CopyInputStep } from './steps/CopyInputStep';
|
|
6
6
|
import { parseAiPane } from '@/utils/compositor/aiPaneParser';
|
|
7
7
|
import { classNames } from '@/utils/helpers';
|
|
8
|
+
import type { TemplatePane } from '@/types/compositorTypes';
|
|
8
9
|
import type { BrandConfig } from '@/types/tractstack';
|
|
9
10
|
|
|
10
11
|
interface AiPaneGeneratorProps {
|
|
@@ -300,7 +301,7 @@ export function AiPaneGenerator({
|
|
|
300
301
|
<div>
|
|
301
302
|
<label
|
|
302
303
|
htmlFor="shell-json"
|
|
303
|
-
className="block text-lg font-
|
|
304
|
+
className="block text-lg font-bold text-gray-800"
|
|
304
305
|
>
|
|
305
306
|
Shell JSON Payload
|
|
306
307
|
</label>
|
|
@@ -317,7 +318,7 @@ export function AiPaneGenerator({
|
|
|
317
318
|
<div>
|
|
318
319
|
<label
|
|
319
320
|
htmlFor="copy-html"
|
|
320
|
-
className="block text-lg font-
|
|
321
|
+
className="block text-lg font-bold text-gray-800"
|
|
321
322
|
>
|
|
322
323
|
Copy HTML Payload
|
|
323
324
|
</label>
|
|
@@ -361,7 +362,7 @@ export function AiPaneGenerator({
|
|
|
361
362
|
return (
|
|
362
363
|
<div className="space-y-6 p-4">
|
|
363
364
|
<div>
|
|
364
|
-
<label className="block text-lg font-
|
|
365
|
+
<label className="block text-lg font-bold text-gray-800">
|
|
365
366
|
Color Harmony
|
|
366
367
|
</label>
|
|
367
368
|
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-2">
|
|
@@ -378,7 +379,7 @@ export function AiPaneGenerator({
|
|
|
378
379
|
/>
|
|
379
380
|
<label
|
|
380
381
|
htmlFor={`harmony-${option}`}
|
|
381
|
-
className="text-sm font-
|
|
382
|
+
className="text-sm font-bold text-gray-700"
|
|
382
383
|
>
|
|
383
384
|
{option}
|
|
384
385
|
</label>
|
|
@@ -409,7 +410,7 @@ export function AiPaneGenerator({
|
|
|
409
410
|
</div>
|
|
410
411
|
|
|
411
412
|
<div>
|
|
412
|
-
<label className="block text-lg font-
|
|
413
|
+
<label className="block text-lg font-bold text-gray-800">
|
|
413
414
|
Theme / Mood
|
|
414
415
|
</label>
|
|
415
416
|
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-2">
|
|
@@ -426,7 +427,7 @@ export function AiPaneGenerator({
|
|
|
426
427
|
/>
|
|
427
428
|
<label
|
|
428
429
|
htmlFor={`theme-${option}`}
|
|
429
|
-
className="text-sm font-
|
|
430
|
+
className="text-sm font-bold text-gray-700"
|
|
430
431
|
>
|
|
431
432
|
{option}
|
|
432
433
|
</label>
|
|
@@ -438,7 +439,7 @@ export function AiPaneGenerator({
|
|
|
438
439
|
<div>
|
|
439
440
|
<label
|
|
440
441
|
htmlFor="additional-notes"
|
|
441
|
-
className="block text-lg font-
|
|
442
|
+
className="block text-lg font-bold text-gray-800"
|
|
442
443
|
>
|
|
443
444
|
Additional Design Notes (Optional)
|
|
444
445
|
</label>
|
|
@@ -456,78 +457,14 @@ export function AiPaneGenerator({
|
|
|
456
457
|
/>
|
|
457
458
|
</div>
|
|
458
459
|
|
|
459
|
-
<
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
id="copy-prompt-mode"
|
|
468
|
-
name="copyModeOptions"
|
|
469
|
-
value="prompt"
|
|
470
|
-
checked={copyMode === 'prompt'}
|
|
471
|
-
onChange={(e) => setCopyMode(e.target.value as CopyMode)}
|
|
472
|
-
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
473
|
-
/>
|
|
474
|
-
<label
|
|
475
|
-
htmlFor="copy-prompt-mode"
|
|
476
|
-
className="text-sm font-medium text-gray-700"
|
|
477
|
-
>
|
|
478
|
-
Write a prompt
|
|
479
|
-
</label>
|
|
480
|
-
</div>
|
|
481
|
-
<div className="flex items-center space-x-2">
|
|
482
|
-
<input
|
|
483
|
-
type="radio"
|
|
484
|
-
id="copy-raw-mode"
|
|
485
|
-
name="copyModeOptions"
|
|
486
|
-
value="raw"
|
|
487
|
-
checked={copyMode === 'raw'}
|
|
488
|
-
onChange={(e) => setCopyMode(e.target.value as CopyMode)}
|
|
489
|
-
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
490
|
-
/>
|
|
491
|
-
<label
|
|
492
|
-
htmlFor="copy-raw-mode"
|
|
493
|
-
className="text-sm font-medium text-gray-700"
|
|
494
|
-
>
|
|
495
|
-
Provide Copy
|
|
496
|
-
</label>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
|
|
500
|
-
{copyMode === 'prompt' ? (
|
|
501
|
-
<>
|
|
502
|
-
<p className="mb-2 text-sm text-gray-500">
|
|
503
|
-
Let the AI write the copy based on your prompt.
|
|
504
|
-
</p>
|
|
505
|
-
<textarea
|
|
506
|
-
id="copy-prompt"
|
|
507
|
-
value={copyPrompt}
|
|
508
|
-
onChange={(e) => setCopyPrompt(e.target.value)}
|
|
509
|
-
placeholder="Enter copy prompt..."
|
|
510
|
-
rows={4}
|
|
511
|
-
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
512
|
-
/>
|
|
513
|
-
</>
|
|
514
|
-
) : (
|
|
515
|
-
<>
|
|
516
|
-
<p className="mb-2 text-sm text-gray-500">
|
|
517
|
-
Provide your raw copy text here. The AI will structure and style
|
|
518
|
-
it.
|
|
519
|
-
</p>
|
|
520
|
-
<textarea
|
|
521
|
-
id="raw-copy"
|
|
522
|
-
value={rawCopy}
|
|
523
|
-
onChange={(e) => setRawCopy(e.target.value)}
|
|
524
|
-
placeholder="Paste or type your copy text..."
|
|
525
|
-
rows={6}
|
|
526
|
-
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
527
|
-
/>
|
|
528
|
-
</>
|
|
529
|
-
)}
|
|
530
|
-
</div>
|
|
460
|
+
<CopyInputStep
|
|
461
|
+
copyMode={copyMode}
|
|
462
|
+
onCopyModeChange={setCopyMode}
|
|
463
|
+
promptValue={copyPrompt}
|
|
464
|
+
onPromptValueChange={setCopyPrompt}
|
|
465
|
+
copyValue={rawCopy}
|
|
466
|
+
onCopyValueChange={setRawCopy}
|
|
467
|
+
/>
|
|
531
468
|
|
|
532
469
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
533
470
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
2
|
+
import type { BrandConfig } from '@/types/tractstack';
|
|
3
|
+
|
|
4
|
+
export interface AiDesignConfig {
|
|
5
|
+
harmony: string;
|
|
6
|
+
baseColor: string;
|
|
7
|
+
accentColor: string;
|
|
8
|
+
theme: string;
|
|
9
|
+
additionalNotes: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface AiDesignStepProps {
|
|
13
|
+
config: BrandConfig;
|
|
14
|
+
designConfig: AiDesignConfig;
|
|
15
|
+
onDesignConfigChange: (newConfig: AiDesignConfig) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const harmonyOptions = [
|
|
19
|
+
'Analogous',
|
|
20
|
+
'Monochromatic',
|
|
21
|
+
'Complementary',
|
|
22
|
+
'Triadic',
|
|
23
|
+
];
|
|
24
|
+
const themeOptions = ['Light', 'Dark', 'Bright', 'Muted', 'Pastel', 'Earthy'];
|
|
25
|
+
|
|
26
|
+
export const AiDesignStep = ({
|
|
27
|
+
config,
|
|
28
|
+
designConfig,
|
|
29
|
+
onDesignConfigChange,
|
|
30
|
+
}: AiDesignStepProps) => {
|
|
31
|
+
const updateField = <K extends keyof AiDesignConfig>(
|
|
32
|
+
field: K,
|
|
33
|
+
value: AiDesignConfig[K]
|
|
34
|
+
) => {
|
|
35
|
+
onDesignConfigChange({ ...designConfig, [field]: value });
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="space-y-6 rounded-lg bg-gray-50 p-4 shadow-inner">
|
|
40
|
+
<label className="block text-lg font-semibold text-gray-800">
|
|
41
|
+
2. Configure AI Design
|
|
42
|
+
</label>
|
|
43
|
+
<div>
|
|
44
|
+
<label className="block text-base font-semibold text-gray-800">
|
|
45
|
+
Color Harmony
|
|
46
|
+
</label>
|
|
47
|
+
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-2">
|
|
48
|
+
{harmonyOptions.map((option) => (
|
|
49
|
+
<div key={option} className="flex items-center space-x-2">
|
|
50
|
+
<input
|
|
51
|
+
type="radio"
|
|
52
|
+
id={`harmony-${option}`}
|
|
53
|
+
name="harmonyOptions"
|
|
54
|
+
value={option}
|
|
55
|
+
checked={designConfig.harmony === option}
|
|
56
|
+
onChange={(e) => updateField('harmony', e.target.value)}
|
|
57
|
+
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
58
|
+
/>
|
|
59
|
+
<label
|
|
60
|
+
htmlFor={`harmony-${option}`}
|
|
61
|
+
className="text-sm font-medium text-gray-700"
|
|
62
|
+
>
|
|
63
|
+
{option}
|
|
64
|
+
</label>
|
|
65
|
+
</div>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
71
|
+
<div>
|
|
72
|
+
<ColorPickerCombo
|
|
73
|
+
title="Base Color (Optional)"
|
|
74
|
+
config={config}
|
|
75
|
+
defaultColor={designConfig.baseColor}
|
|
76
|
+
onColorChange={(color) => updateField('baseColor', color)}
|
|
77
|
+
allowNull={true}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<ColorPickerCombo
|
|
82
|
+
title="Accent Color (Optional)"
|
|
83
|
+
config={config}
|
|
84
|
+
defaultColor={designConfig.accentColor}
|
|
85
|
+
onColorChange={(color) => updateField('accentColor', color)}
|
|
86
|
+
allowNull={true}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div>
|
|
92
|
+
<label className="block text-base font-semibold text-gray-800">
|
|
93
|
+
Theme / Mood
|
|
94
|
+
</label>
|
|
95
|
+
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-2">
|
|
96
|
+
{themeOptions.map((option) => (
|
|
97
|
+
<div key={option} className="flex items-center space-x-2">
|
|
98
|
+
<input
|
|
99
|
+
type="radio"
|
|
100
|
+
id={`theme-${option}`}
|
|
101
|
+
name="themeOptions"
|
|
102
|
+
value={option}
|
|
103
|
+
checked={designConfig.theme === option}
|
|
104
|
+
onChange={(e) => updateField('theme', e.target.value)}
|
|
105
|
+
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
106
|
+
/>
|
|
107
|
+
<label
|
|
108
|
+
htmlFor={`theme-${option}`}
|
|
109
|
+
className="text-sm font-medium text-gray-700"
|
|
110
|
+
>
|
|
111
|
+
{option}
|
|
112
|
+
</label>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div>
|
|
119
|
+
<label
|
|
120
|
+
htmlFor="additional-notes"
|
|
121
|
+
className="block text-base font-semibold text-gray-800"
|
|
122
|
+
>
|
|
123
|
+
Additional Design Notes (Optional)
|
|
124
|
+
</label>
|
|
125
|
+
<p className="mb-2 mt-1 text-sm text-gray-500">
|
|
126
|
+
Add specific requests like "use rounded corners", "add subtle
|
|
127
|
+
texture".
|
|
128
|
+
</p>
|
|
129
|
+
<textarea
|
|
130
|
+
id="additional-notes"
|
|
131
|
+
value={designConfig.additionalNotes}
|
|
132
|
+
onChange={(e) => updateField('additionalNotes', e.target.value)}
|
|
133
|
+
placeholder="Enter additional notes..."
|
|
134
|
+
rows={3}
|
|
135
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
type CopyMode = 'prompt' | 'raw';
|
|
4
|
+
|
|
5
|
+
interface CopyInputStepProps {
|
|
6
|
+
copyMode: CopyMode;
|
|
7
|
+
onCopyModeChange: (mode: CopyMode) => void;
|
|
8
|
+
promptValue: string;
|
|
9
|
+
onPromptValueChange: (value: string) => void;
|
|
10
|
+
copyValue: string;
|
|
11
|
+
onCopyValueChange: (value: string) => void;
|
|
12
|
+
defaultPrompt?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const CopyInputStep = ({
|
|
16
|
+
copyMode,
|
|
17
|
+
onCopyModeChange,
|
|
18
|
+
promptValue,
|
|
19
|
+
onPromptValueChange,
|
|
20
|
+
copyValue,
|
|
21
|
+
onCopyValueChange,
|
|
22
|
+
defaultPrompt,
|
|
23
|
+
}: CopyInputStepProps) => {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// Pre-populate the prompt field if a default is provided and the field is empty
|
|
26
|
+
if (defaultPrompt && !promptValue) {
|
|
27
|
+
onPromptValueChange(defaultPrompt);
|
|
28
|
+
}
|
|
29
|
+
}, [defaultPrompt, promptValue, onPromptValueChange]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="space-y-4 rounded-lg bg-gray-50 p-4 shadow-inner">
|
|
33
|
+
<label className="block text-lg font-semibold text-gray-800">
|
|
34
|
+
1. Provide Content
|
|
35
|
+
</label>
|
|
36
|
+
<div className="my-2 flex space-x-4">
|
|
37
|
+
<div className="flex items-center space-x-2">
|
|
38
|
+
<input
|
|
39
|
+
type="radio"
|
|
40
|
+
id="copy-prompt-mode"
|
|
41
|
+
name="copyModeOptions"
|
|
42
|
+
value="prompt"
|
|
43
|
+
checked={copyMode === 'prompt'}
|
|
44
|
+
onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
|
|
45
|
+
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
46
|
+
/>
|
|
47
|
+
<label
|
|
48
|
+
htmlFor="copy-prompt-mode"
|
|
49
|
+
className="text-sm font-medium text-gray-700"
|
|
50
|
+
>
|
|
51
|
+
Write a prompt
|
|
52
|
+
</label>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex items-center space-x-2">
|
|
55
|
+
<input
|
|
56
|
+
type="radio"
|
|
57
|
+
id="copy-raw-mode"
|
|
58
|
+
name="copyModeOptions"
|
|
59
|
+
value="raw"
|
|
60
|
+
checked={copyMode === 'raw'}
|
|
61
|
+
onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
|
|
62
|
+
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
63
|
+
/>
|
|
64
|
+
<label
|
|
65
|
+
htmlFor="copy-raw-mode"
|
|
66
|
+
className="text-sm font-medium text-gray-700"
|
|
67
|
+
>
|
|
68
|
+
Provide Copy (Markdown)
|
|
69
|
+
</label>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{copyMode === 'prompt' ? (
|
|
74
|
+
<>
|
|
75
|
+
<p className="mb-2 text-sm text-gray-500">
|
|
76
|
+
Let the AI write the copy based on your prompt.
|
|
77
|
+
</p>
|
|
78
|
+
<textarea
|
|
79
|
+
id="copy-prompt"
|
|
80
|
+
value={promptValue}
|
|
81
|
+
onChange={(e) => onPromptValueChange(e.target.value)}
|
|
82
|
+
placeholder="e.g., A hero section for a SaaS product that helps teams collaborate..."
|
|
83
|
+
rows={4}
|
|
84
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
85
|
+
/>
|
|
86
|
+
</>
|
|
87
|
+
) : (
|
|
88
|
+
<>
|
|
89
|
+
<p className="mb-2 text-sm text-gray-500">
|
|
90
|
+
Provide your raw copy here. Use Markdown for formatting (e.g., ##
|
|
91
|
+
Headline, **bold**).
|
|
92
|
+
</p>
|
|
93
|
+
<textarea
|
|
94
|
+
id="raw-copy"
|
|
95
|
+
value={copyValue}
|
|
96
|
+
onChange={(e) => onCopyValueChange(e.target.value)}
|
|
97
|
+
placeholder="## My Awesome Headline..."
|
|
98
|
+
rows={6}
|
|
99
|
+
className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
|
|
100
|
+
/>
|
|
101
|
+
</>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|