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.
Files changed (26) hide show
  1. package/dist/index.js +22 -0
  2. package/package.json +1 -1
  3. package/templates/src/client/view.js +5 -0
  4. package/templates/src/components/compositor/Compositor.tsx +3 -2
  5. package/templates/src/components/compositor/Node.tsx +18 -2
  6. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +105 -0
  7. package/templates/src/components/edit/ToolMode.tsx +7 -0
  8. package/templates/src/components/edit/pane/AddPanePanel.tsx +5 -1
  9. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +4 -1
  10. package/templates/src/components/edit/pane/AiPaneGenerator.tsx +264 -94
  11. package/templates/src/components/edit/pane/AiPanePreview.tsx +60 -210
  12. package/templates/src/components/edit/pane/PageGen.tsx +1 -1
  13. package/templates/src/components/edit/pane/PageGenSelector.tsx +4 -0
  14. package/templates/src/components/edit/pane/RestylePaneModal.tsx +573 -0
  15. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +205 -0
  16. package/templates/src/constants/prompts.json +3 -3
  17. package/templates/src/stores/selection.ts +4 -0
  18. package/templates/src/types/compositorTypes.ts +51 -1
  19. package/templates/src/types/tractstack.ts +36 -31
  20. package/templates/src/utils/aai/getTitleSlug.ts +1 -1
  21. package/templates/src/utils/api/brandConfig.ts +8 -2
  22. package/templates/src/utils/api/brandHelpers.ts +4 -0
  23. package/templates/src/utils/compositor/aiPaneParser.ts +39 -13
  24. package/templates/src/utils/compositor/designLibraryHelper.ts +331 -0
  25. package/templates/src/utils/compositor/processMarkdown.ts +1 -1
  26. package/utils/inject-files.ts +22 -0
@@ -1,23 +1,6 @@
1
- import { useState, useEffect, useCallback, useMemo } from 'react';
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 [snapshotData, setSnapshotData] = useState<SnapshotData | null>(null);
85
- const [isGeneratingSnapshot, setIsGeneratingSnapshot] =
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
- setParsedPaneForApply(null);
93
- setSnapshotData(null);
94
- setIsGeneratingSnapshot(false);
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
- const handleApply = () => {
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
- onComplete(pane);
34
+ if (isActive && !hasCompleted) {
35
+ onComplete(pane);
36
+ setHasCompleted(true);
37
+ setIsLoading(false);
38
+ }
169
39
  } catch (err: any) {
170
- setError(
171
- err.message || 'Failed to parse generated content before applying.'
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
- useEffect(() => {
178
- if (previewHtmlString && !snapshotData && !error && !isGeneratingSnapshot) {
179
- setIsGeneratingSnapshot(true);
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
- }, [previewHtmlString, snapshotData, error, isGeneratingSnapshot]);
182
-
183
- const showLoading =
184
- isGeneratingSnapshot ||
185
- (!previewHtmlString && !error && !parsedPaneForApply);
186
- const showPreview = !isGeneratingSnapshot && snapshotData && !error;
187
- const showError = !!error;
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
- {showError && (
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-between">
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
  );
@@ -98,7 +98,7 @@ ${additionalInstructions}`;
98
98
  body: JSON.stringify({
99
99
  prompt: finalPrompt,
100
100
  input_text: referenceContext,
101
- final_model: 'anthropic/claude-3-5-sonnet',
101
+ final_model: '',
102
102
  temperature: 0.7,
103
103
  max_tokens: 4000,
104
104
  }),
@@ -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} />;