astro-tractstack 2.0.12 → 2.0.14
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 +22 -0
- package/package.json +1 -1
- package/templates/src/client/view.js +5 -0
- package/templates/src/components/compositor/Compositor.tsx +3 -2
- package/templates/src/components/compositor/Node.tsx +18 -2
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +105 -0
- package/templates/src/components/edit/ToolMode.tsx +7 -0
- package/templates/src/components/edit/pane/AddPanePanel.tsx +5 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +4 -1
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +264 -94
- package/templates/src/components/edit/pane/AiPanePreview.tsx +60 -210
- package/templates/src/components/edit/pane/PageGen.tsx +1 -1
- package/templates/src/components/edit/pane/PageGenSelector.tsx +4 -0
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +573 -0
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +205 -0
- package/templates/src/constants/prompts.json +3 -3
- package/templates/src/stores/selection.ts +4 -0
- package/templates/src/types/compositorTypes.ts +51 -1
- package/templates/src/types/tractstack.ts +36 -31
- package/templates/src/utils/aai/getTitleSlug.ts +1 -1
- package/templates/src/utils/api/brandConfig.ts +8 -2
- package/templates/src/utils/api/brandHelpers.ts +4 -0
- package/templates/src/utils/compositor/aiPaneParser.ts +39 -13
- package/templates/src/utils/compositor/designLibraryHelper.ts +331 -0
- package/templates/src/utils/compositor/processMarkdown.ts +1 -1
- package/utils/inject-files.ts +22 -0
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
import { useState, useEffect,
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import type { TemplatePane } from '@/types/compositorTypes';
|
|
3
3
|
import { parseAiPane } from '@/utils/compositor/aiPaneParser';
|
|
4
|
-
import {
|
|
5
|
-
PaneSnapshotGenerator,
|
|
6
|
-
type SnapshotData,
|
|
7
|
-
} from '@/components/compositor/preview/PaneSnapshotGenerator';
|
|
8
|
-
import { ulid } from 'ulid';
|
|
9
|
-
|
|
10
|
-
type LLMShellLayer = {
|
|
11
|
-
mobile?: Record<string, string>;
|
|
12
|
-
tablet?: Record<string, string>;
|
|
13
|
-
desktop?: Record<string, string>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type ShellJson = {
|
|
17
|
-
bgColour: string;
|
|
18
|
-
parentClasses: LLMShellLayer[];
|
|
19
|
-
defaultClasses: Record<string, any>;
|
|
20
|
-
};
|
|
21
4
|
|
|
22
5
|
interface AiPanePreviewProps {
|
|
23
6
|
shellJson: string;
|
|
@@ -28,49 +11,6 @@ interface AiPanePreviewProps {
|
|
|
28
11
|
onBack: () => void;
|
|
29
12
|
}
|
|
30
13
|
|
|
31
|
-
function convertObjectToTailwindString(
|
|
32
|
-
styleObj: Record<string, string> | undefined
|
|
33
|
-
): string {
|
|
34
|
-
if (!styleObj) return '';
|
|
35
|
-
return Object.entries(styleObj)
|
|
36
|
-
.map(([key, value]) => {
|
|
37
|
-
// Basic mapping, might need adjustment based on tailwindClasses structure if prefixes differ
|
|
38
|
-
const prefixMap: Record<string, string> = {
|
|
39
|
-
mx: 'mx',
|
|
40
|
-
my: 'my',
|
|
41
|
-
px: 'px',
|
|
42
|
-
py: 'py',
|
|
43
|
-
textALIGN: 'text',
|
|
44
|
-
textSIZE: 'text',
|
|
45
|
-
textCOLOR: 'text',
|
|
46
|
-
fontWEIGHT: 'font',
|
|
47
|
-
fontFACE: 'font',
|
|
48
|
-
letterSPACING: 'tracking',
|
|
49
|
-
lineHEIGHT: 'leading',
|
|
50
|
-
bgCOLOR: 'bg',
|
|
51
|
-
rounded: 'rounded',
|
|
52
|
-
shadow: 'shadow',
|
|
53
|
-
maxW: 'max-w',
|
|
54
|
-
// Add other mappings as needed based on keys used in compositorTypes vs tailwindClasses
|
|
55
|
-
};
|
|
56
|
-
const prefix = prefixMap[key] || key.toLowerCase();
|
|
57
|
-
if (value === '') return key; // Handle boolean classes like 'relative', 'flex'
|
|
58
|
-
return `${prefix}-${value}`;
|
|
59
|
-
})
|
|
60
|
-
.join(' ');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getPreviewClasses(classes: LLMShellLayer | undefined): string {
|
|
64
|
-
if (!classes) return '';
|
|
65
|
-
|
|
66
|
-
const mobileStyles = convertObjectToTailwindString(classes.mobile);
|
|
67
|
-
const tabletStyles = convertObjectToTailwindString(classes.tablet);
|
|
68
|
-
const desktopStyles = convertObjectToTailwindString(classes.desktop);
|
|
69
|
-
|
|
70
|
-
const combined = `${mobileStyles} ${tabletStyles ? `md:${tabletStyles.split(' ').join(' md:')}` : ''} ${desktopStyles ? `xl:${desktopStyles.split(' ').join(' xl:')}` : ''}`;
|
|
71
|
-
return combined.replace(/\s+/g, ' ').trim();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
14
|
export function AiPanePreview({
|
|
75
15
|
shellJson,
|
|
76
16
|
copyHtml,
|
|
@@ -78,152 +18,82 @@ export function AiPanePreview({
|
|
|
78
18
|
onComplete,
|
|
79
19
|
onBack,
|
|
80
20
|
}: AiPanePreviewProps) {
|
|
81
|
-
const [parsedPaneForApply, setParsedPaneForApply] =
|
|
82
|
-
useState<TemplatePane | null>(null);
|
|
83
21
|
const [error, setError] = useState<string | null>(null);
|
|
84
|
-
const [
|
|
85
|
-
const [
|
|
86
|
-
useState<boolean>(false);
|
|
87
|
-
const previewId = useMemo(() => `ai-preview-${ulid()}`, []);
|
|
22
|
+
const [hasCompleted, setHasCompleted] = useState<boolean>(false);
|
|
23
|
+
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
88
24
|
|
|
89
25
|
useEffect(() => {
|
|
90
|
-
let isActive = true;
|
|
91
26
|
setError(null);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const pane = parseAiPane(shellJson, copyHtml, layout);
|
|
97
|
-
if (isActive) {
|
|
98
|
-
setParsedPaneForApply(pane);
|
|
99
|
-
}
|
|
100
|
-
} catch (err: any) {
|
|
101
|
-
console.error('Error parsing AI Pane for apply:', err);
|
|
102
|
-
if (isActive) {
|
|
103
|
-
setError(
|
|
104
|
-
err.message || 'Failed to parse generated content for application.'
|
|
105
|
-
);
|
|
106
|
-
setParsedPaneForApply(null);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return () => {
|
|
110
|
-
isActive = false;
|
|
111
|
-
};
|
|
112
|
-
}, [shellJson, copyHtml, layout]);
|
|
113
|
-
|
|
114
|
-
const previewHtmlString = useMemo(() => {
|
|
115
|
-
try {
|
|
116
|
-
if (!shellJson || !copyHtml) return '';
|
|
117
|
-
const shell: ShellJson = JSON.parse(shellJson);
|
|
118
|
-
|
|
119
|
-
let currentHtml = copyHtml;
|
|
120
|
-
if (shell.parentClasses && shell.parentClasses.length > 0) {
|
|
121
|
-
[...shell.parentClasses].reverse().forEach((layer) => {
|
|
122
|
-
const layerClasses = getPreviewClasses(layer);
|
|
123
|
-
currentHtml = `<div class="${layerClasses}">${currentHtml}</div>`;
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const outerStyle = shell.bgColour
|
|
128
|
-
? `background-color: ${shell.bgColour};`
|
|
129
|
-
: '';
|
|
130
|
-
// Wrap in a div that sets width similar to snapshot generator default for better preview consistency
|
|
131
|
-
return `<div style="${outerStyle} width: 800px; padding: 1px; margin: auto;">${currentHtml}</div>`;
|
|
132
|
-
} catch (err: any) {
|
|
133
|
-
console.error('Error constructing preview HTML string:', err);
|
|
134
|
-
setError(err.message || 'Failed to construct preview HTML.');
|
|
135
|
-
return '';
|
|
136
|
-
}
|
|
137
|
-
}, [shellJson, copyHtml]);
|
|
138
|
-
|
|
139
|
-
const handleSnapshotComplete = useCallback(
|
|
140
|
-
(id: string, data: SnapshotData) => {
|
|
141
|
-
if (id === previewId) {
|
|
142
|
-
setSnapshotData(data);
|
|
143
|
-
setIsGeneratingSnapshot(false);
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
[previewId]
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
const handleSnapshotError = useCallback(
|
|
150
|
-
(id: string, errorMsg: string) => {
|
|
151
|
-
if (id === previewId) {
|
|
152
|
-
console.error(`Snapshot generation failed for ${id}:`, errorMsg);
|
|
153
|
-
setError(`Snapshot generation failed: ${errorMsg}`);
|
|
154
|
-
setIsGeneratingSnapshot(false);
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
[previewId]
|
|
158
|
-
);
|
|
27
|
+
setHasCompleted(false);
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
let isActive = true;
|
|
159
30
|
|
|
160
|
-
|
|
161
|
-
if (parsedPaneForApply) {
|
|
162
|
-
onComplete(parsedPaneForApply);
|
|
163
|
-
console.log('FINAL TEMPLATE PANE PAYLOAD:', parsedPaneForApply);
|
|
164
|
-
} else if (!error) {
|
|
165
|
-
// Attempt parsing again if it failed silently initially
|
|
31
|
+
if (shellJson && copyHtml) {
|
|
166
32
|
try {
|
|
167
33
|
const pane = parseAiPane(shellJson, copyHtml, layout);
|
|
168
|
-
|
|
34
|
+
if (isActive && !hasCompleted) {
|
|
35
|
+
onComplete(pane);
|
|
36
|
+
setHasCompleted(true);
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
}
|
|
169
39
|
} catch (err: any) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
40
|
+
console.error('Error parsing AI Pane:', err);
|
|
41
|
+
if (isActive) {
|
|
42
|
+
setError(err.message || 'Failed to parse generated content.');
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
}
|
|
173
45
|
}
|
|
46
|
+
} else {
|
|
47
|
+
// Handle case where inputs might be initially empty
|
|
48
|
+
setIsLoading(false);
|
|
174
49
|
}
|
|
175
|
-
};
|
|
176
50
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
51
|
+
return () => {
|
|
52
|
+
isActive = false;
|
|
53
|
+
};
|
|
54
|
+
}, [shellJson, copyHtml, layout, onComplete, hasCompleted]);
|
|
55
|
+
|
|
56
|
+
const displayContent = useMemo(() => {
|
|
57
|
+
if (isLoading) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="p-4 text-center text-gray-500">
|
|
60
|
+
<div className="mx-auto mb-2 h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400"></div>
|
|
61
|
+
<p className="text-sm">Processing...</p>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
180
64
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
65
|
+
if (error) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="p-4 text-center text-red-600">
|
|
68
|
+
<p className="font-semibold">Error:</p>
|
|
69
|
+
<p className="mt-1 text-sm">{error}</p>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (hasCompleted) {
|
|
74
|
+
return (
|
|
75
|
+
<div className="p-4 text-center text-green-700">
|
|
76
|
+
<p className="font-semibold">Pane Applied Successfully!</p>
|
|
77
|
+
<p className="mt-1 text-sm">
|
|
78
|
+
You can now go back or continue editing.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
// Fallback/initial state before useEffect runs if needed
|
|
84
|
+
return (
|
|
85
|
+
<div className="p-4 text-center text-gray-500">
|
|
86
|
+
<p className="text-sm">Preparing...</p>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}, [isLoading, error, hasCompleted]);
|
|
188
90
|
|
|
189
91
|
return (
|
|
190
92
|
<div className="flex h-full flex-col p-4">
|
|
191
93
|
<div className="relative mb-4 flex min-h-[200px] flex-grow items-center justify-center overflow-auto rounded border bg-gray-50">
|
|
192
|
-
{
|
|
193
|
-
<div className="p-4 text-center text-red-600">
|
|
194
|
-
<p className="font-semibold">Error:</p>
|
|
195
|
-
<p className="mt-1 text-sm">{error}</p>
|
|
196
|
-
</div>
|
|
197
|
-
)}
|
|
198
|
-
{showLoading && !showError && (
|
|
199
|
-
<div className="p-4 text-center text-gray-500">
|
|
200
|
-
<div className="mx-auto mb-2 h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400"></div>
|
|
201
|
-
<p className="text-sm">
|
|
202
|
-
{isGeneratingSnapshot
|
|
203
|
-
? 'Generating Snapshot...'
|
|
204
|
-
: 'Constructing Preview...'}
|
|
205
|
-
</p>
|
|
206
|
-
</div>
|
|
207
|
-
)}
|
|
208
|
-
{isGeneratingSnapshot && previewHtmlString && (
|
|
209
|
-
<div className="pointer-events-none absolute left-[-9999px] top-[-9999px] w-[800px] opacity-0">
|
|
210
|
-
<PaneSnapshotGenerator
|
|
211
|
-
id={previewId}
|
|
212
|
-
htmlString={previewHtmlString}
|
|
213
|
-
onComplete={handleSnapshotComplete}
|
|
214
|
-
onError={handleSnapshotError}
|
|
215
|
-
/>
|
|
216
|
-
</div>
|
|
217
|
-
)}
|
|
218
|
-
{showPreview && snapshotData && (
|
|
219
|
-
<img
|
|
220
|
-
src={snapshotData.imageData}
|
|
221
|
-
alt="AI Pane Preview"
|
|
222
|
-
className="block h-auto max-w-full"
|
|
223
|
-
/>
|
|
224
|
-
)}
|
|
94
|
+
{displayContent}
|
|
225
95
|
</div>
|
|
226
|
-
<div className="flex flex-shrink-0 justify-
|
|
96
|
+
<div className="flex flex-shrink-0 justify-start">
|
|
227
97
|
<button
|
|
228
98
|
onClick={onBack}
|
|
229
99
|
className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
|
@@ -231,26 +101,6 @@ export function AiPanePreview({
|
|
|
231
101
|
>
|
|
232
102
|
Back
|
|
233
103
|
</button>
|
|
234
|
-
<button
|
|
235
|
-
onClick={handleApply}
|
|
236
|
-
disabled={
|
|
237
|
-
!parsedPaneForApply ||
|
|
238
|
-
!!error ||
|
|
239
|
-
!snapshotData ||
|
|
240
|
-
isGeneratingSnapshot
|
|
241
|
-
}
|
|
242
|
-
className={`rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors duration-150 ${
|
|
243
|
-
!parsedPaneForApply ||
|
|
244
|
-
!!error ||
|
|
245
|
-
!snapshotData ||
|
|
246
|
-
isGeneratingSnapshot
|
|
247
|
-
? 'cursor-not-allowed bg-gray-400'
|
|
248
|
-
: 'bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2'
|
|
249
|
-
}`}
|
|
250
|
-
type="button"
|
|
251
|
-
>
|
|
252
|
-
Apply Pane
|
|
253
|
-
</button>
|
|
254
104
|
</div>
|
|
255
105
|
</div>
|
|
256
106
|
);
|
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
/* hasAssemblyAIStore,*/ fullContentMapStore,
|
|
13
13
|
} from '@/stores/storykeep';
|
|
14
14
|
import type { NodesContext } from '@/stores/nodes';
|
|
15
|
+
import type { BrandConfig } from '@/types/tractstack';
|
|
15
16
|
|
|
16
17
|
interface PageCreationSelectorProps {
|
|
17
18
|
nodeId: string;
|
|
18
19
|
ctx: NodesContext;
|
|
19
20
|
isTemplate?: boolean;
|
|
21
|
+
config?: BrandConfig;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
type CreationMode = {
|
|
@@ -33,6 +35,7 @@ export const PageCreationSelector = ({
|
|
|
33
35
|
nodeId,
|
|
34
36
|
ctx,
|
|
35
37
|
isTemplate = false,
|
|
38
|
+
config,
|
|
36
39
|
}: PageCreationSelectorProps) => {
|
|
37
40
|
const [selectedMode, setSelectedMode] =
|
|
38
41
|
useState<CreationMode['id']>('design');
|
|
@@ -137,6 +140,7 @@ export const PageCreationSelector = ({
|
|
|
137
140
|
first={true}
|
|
138
141
|
ctx={ctx}
|
|
139
142
|
isStoryFragment={true}
|
|
143
|
+
config={config!}
|
|
140
144
|
/>
|
|
141
145
|
);
|
|
142
146
|
else if (showGen) return <PageCreationGen nodeId={nodeId} ctx={ctx} />;
|