astro-tractstack 2.0.16 → 2.0.17
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 +6 -0
- package/package.json +1 -1
- package/templates/custom/with-examples/SandboxLauncher.tsx +11 -9
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +1 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +33 -22
- package/templates/src/components/edit/pane/steps/DirectInjectStep.tsx +96 -0
- package/templates/src/constants.ts +1 -0
- package/templates/src/utils/compositor/aiPaneParser.ts +8 -2
- package/utils/inject-files.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -470,6 +470,12 @@ async function w(t, e, c) {
|
|
|
470
470
|
),
|
|
471
471
|
dest: "src/components/edit/pane/steps/AiDesignStep.tsx"
|
|
472
472
|
},
|
|
473
|
+
{
|
|
474
|
+
src: t(
|
|
475
|
+
"../templates/src/components/edit/pane/steps/DirectInjectStep.tsx"
|
|
476
|
+
),
|
|
477
|
+
dest: "src/components/edit/pane/steps/DirectInjectStep.tsx"
|
|
478
|
+
},
|
|
473
479
|
{
|
|
474
480
|
src: t(
|
|
475
481
|
"../templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx"
|
package/package.json
CHANGED
|
@@ -31,15 +31,17 @@ export default function SandboxLauncher() {
|
|
|
31
31
|
<p className="mt-4 text-lg text-gray-600">
|
|
32
32
|
Create an interactive webpage in a sandbox! No credit card required.
|
|
33
33
|
</p>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
{!profileExists && (
|
|
35
|
+
<p className="mt-8 text-sm text-gray-500">
|
|
36
|
+
Already connected?{' '}
|
|
37
|
+
<a
|
|
38
|
+
href="/storykeep/profile"
|
|
39
|
+
className="font-bold text-blue-600 underline hover:text-blue-500"
|
|
40
|
+
>
|
|
41
|
+
Unlock your profile
|
|
42
|
+
</a>
|
|
43
|
+
</p>
|
|
44
|
+
)}
|
|
43
45
|
</div>
|
|
44
46
|
|
|
45
47
|
{/* Column 2: The Action (Switches between Form and Button) */}
|
|
@@ -68,7 +68,7 @@ export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
|
68
68
|
}}
|
|
69
69
|
>
|
|
70
70
|
<div className="absolute left-2 top-2 z-10 flex flex-col gap-y-2">
|
|
71
|
-
{props.isSandboxMode && (
|
|
71
|
+
{!props.isSandboxMode && (
|
|
72
72
|
<button
|
|
73
73
|
title="Save Pane to Design Library"
|
|
74
74
|
onClick={handleSaveClick}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
mergeCopyIntoTemplate,
|
|
20
20
|
convertTemplateToAIShell,
|
|
21
21
|
} from '@/utils/compositor/designLibraryHelper';
|
|
22
|
+
import { DirectInjectStep } from './steps/DirectInjectStep';
|
|
22
23
|
|
|
23
24
|
type Step =
|
|
24
25
|
| 'initial'
|
|
@@ -26,7 +27,8 @@ type Step =
|
|
|
26
27
|
| 'designLibrary'
|
|
27
28
|
| 'aiDesign'
|
|
28
29
|
| 'loading'
|
|
29
|
-
| 'error'
|
|
30
|
+
| 'error'
|
|
31
|
+
| 'directInject';
|
|
30
32
|
|
|
31
33
|
type InitialChoice = 'library' | 'ai' | 'blank';
|
|
32
34
|
type CopyMode = 'prompt' | 'raw';
|
|
@@ -160,6 +162,8 @@ const AddPaneNewPanel = ({
|
|
|
160
162
|
setError(null);
|
|
161
163
|
if (step === 'copyInput') {
|
|
162
164
|
setStep('initial');
|
|
165
|
+
} else if (step === 'directInject') {
|
|
166
|
+
setStep('aiDesign');
|
|
163
167
|
} else if (step === 'designLibrary' || step === 'aiDesign' || 'error') {
|
|
164
168
|
setStep('copyInput');
|
|
165
169
|
}
|
|
@@ -175,18 +179,18 @@ const AddPaneNewPanel = ({
|
|
|
175
179
|
|
|
176
180
|
const handleBlankSlate = () => {
|
|
177
181
|
const blankTemplate: TemplatePane = {
|
|
178
|
-
id: '',
|
|
182
|
+
id: '',
|
|
179
183
|
nodeType: 'Pane',
|
|
180
|
-
parentId: '',
|
|
184
|
+
parentId: '',
|
|
181
185
|
title: 'New Pane',
|
|
182
186
|
slug: '',
|
|
183
187
|
isDecorative: false,
|
|
184
188
|
markdown: {
|
|
185
|
-
id: '',
|
|
189
|
+
id: '',
|
|
186
190
|
nodeType: 'Markdown',
|
|
187
|
-
parentId: '',
|
|
191
|
+
parentId: '',
|
|
188
192
|
type: 'markdown',
|
|
189
|
-
markdownId: '',
|
|
193
|
+
markdownId: '',
|
|
190
194
|
defaultClasses: {},
|
|
191
195
|
parentClasses: [],
|
|
192
196
|
nodes: [],
|
|
@@ -196,10 +200,9 @@ const AddPaneNewPanel = ({
|
|
|
196
200
|
};
|
|
197
201
|
|
|
198
202
|
const handleDesignLibrarySelect = async (entry: DesignLibraryEntry) => {
|
|
199
|
-
// This flow is for "Design Library + Provide Copy"
|
|
200
203
|
if (copyMode === 'raw') {
|
|
201
204
|
const liveTemplate = convertStorageToLiveTemplate(
|
|
202
|
-
mergeCopyIntoTemplate(entry.template, [])
|
|
205
|
+
mergeCopyIntoTemplate(entry.template, [])
|
|
203
206
|
);
|
|
204
207
|
if (liveTemplate.markdown) {
|
|
205
208
|
liveTemplate.markdown.markdownBody = copyValue;
|
|
@@ -208,12 +211,10 @@ const AddPaneNewPanel = ({
|
|
|
208
211
|
return;
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
// This flow is for "Design Library + Write a Prompt" (Hybrid AI)
|
|
212
214
|
if (copyMode === 'prompt') {
|
|
213
215
|
setError(null);
|
|
214
216
|
setStep('loading');
|
|
215
217
|
try {
|
|
216
|
-
// 1. Get the full, rich template from the library
|
|
217
218
|
const liveTemplate = convertStorageToLiveTemplate(entry.template);
|
|
218
219
|
if (!liveTemplate.markdown) {
|
|
219
220
|
throw new Error(
|
|
@@ -221,7 +222,6 @@ const AddPaneNewPanel = ({
|
|
|
221
222
|
);
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
// 2. Create the simplified shell for the AI
|
|
225
225
|
const shellJson = convertTemplateToAIShell(liveTemplate);
|
|
226
226
|
if (!shellJson || shellJson === '{}') {
|
|
227
227
|
throw new Error(
|
|
@@ -229,7 +229,6 @@ const AddPaneNewPanel = ({
|
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
// 3. Get the AI to write copy based on the shell and prompt
|
|
233
232
|
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
234
233
|
const layout = 'Text Only';
|
|
235
234
|
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
@@ -248,16 +247,12 @@ const AddPaneNewPanel = ({
|
|
|
248
247
|
isSandboxMode
|
|
249
248
|
);
|
|
250
249
|
|
|
251
|
-
// 4. Parse ONLY the AI-generated HTML into content nodes
|
|
252
250
|
const newNodes = parseAiCopyHtml(copyResult, liveTemplate.markdown.id);
|
|
253
251
|
|
|
254
|
-
// 5. Create the final pane by cloning the original rich template
|
|
255
252
|
const finalPane = cloneDeep(liveTemplate);
|
|
256
253
|
|
|
257
|
-
// 6. Inject the new AI content, preserving the original rich design
|
|
258
254
|
finalPane.markdown!.nodes = newNodes;
|
|
259
255
|
|
|
260
|
-
// 7. Apply the complete, correctly merged pane
|
|
261
256
|
handleApplyTemplate(finalPane);
|
|
262
257
|
} catch (err: any) {
|
|
263
258
|
setError(err.message || 'Failed to generate AI copy for this design.');
|
|
@@ -281,7 +276,7 @@ const AddPaneNewPanel = ({
|
|
|
281
276
|
try {
|
|
282
277
|
const shellPromptDetails = prompts.aiPaneShellPrompt;
|
|
283
278
|
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
284
|
-
const layout = 'Text Only';
|
|
279
|
+
const layout = 'Text Only';
|
|
285
280
|
|
|
286
281
|
const formattedShellPrompt = shellPromptDetails.user_template
|
|
287
282
|
.replace('{{DESIGN_INPUT}}', designInput)
|
|
@@ -317,6 +312,7 @@ const AddPaneNewPanel = ({
|
|
|
317
312
|
}, [aiDesignConfig, copyMode, promptValue, copyValue, isSandboxMode]);
|
|
318
313
|
|
|
319
314
|
const handleApplyTemplate = async (template: TemplatePane) => {
|
|
315
|
+
console.log(template);
|
|
320
316
|
if (!ctx) return;
|
|
321
317
|
try {
|
|
322
318
|
const insertTemplate = cloneDeep(template);
|
|
@@ -356,8 +352,6 @@ const AddPaneNewPanel = ({
|
|
|
356
352
|
}
|
|
357
353
|
};
|
|
358
354
|
|
|
359
|
-
// --- Render Logic ---
|
|
360
|
-
|
|
361
355
|
const renderInitialStep = () => (
|
|
362
356
|
<div className="p-4">
|
|
363
357
|
<h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
|
|
@@ -369,7 +363,7 @@ const AddPaneNewPanel = ({
|
|
|
369
363
|
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"
|
|
370
364
|
>
|
|
371
365
|
<SwatchIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
372
|
-
<h4 className="font-
|
|
366
|
+
<h4 className="font-bold text-gray-800">Use Design Library</h4>
|
|
373
367
|
<p className="text-sm text-gray-600">
|
|
374
368
|
Start with a pre-made design and add your own content.
|
|
375
369
|
</p>
|
|
@@ -380,7 +374,7 @@ const AddPaneNewPanel = ({
|
|
|
380
374
|
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"
|
|
381
375
|
>
|
|
382
376
|
<SparklesIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
383
|
-
<h4 className="font-
|
|
377
|
+
<h4 className="font-bold text-gray-800">Design with AI</h4>
|
|
384
378
|
<p className="text-sm text-gray-600">
|
|
385
379
|
Let AI generate a complete design and copy from your prompt.
|
|
386
380
|
</p>
|
|
@@ -391,7 +385,7 @@ const AddPaneNewPanel = ({
|
|
|
391
385
|
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"
|
|
392
386
|
>
|
|
393
387
|
<DocumentPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
394
|
-
<h4 className="font-
|
|
388
|
+
<h4 className="font-bold text-gray-800">Blank Slate</h4>
|
|
395
389
|
<p className="text-sm text-gray-600">
|
|
396
390
|
Add a simple, empty pane to build from scratch.
|
|
397
391
|
</p>
|
|
@@ -432,6 +426,17 @@ const AddPaneNewPanel = ({
|
|
|
432
426
|
Continue →
|
|
433
427
|
</button>
|
|
434
428
|
</div>
|
|
429
|
+
{initialChoice === `ai` && !isSandboxMode && (
|
|
430
|
+
<div className="mt-6 text-center text-sm text-gray-600">
|
|
431
|
+
ADVANCED:{' '}
|
|
432
|
+
<button
|
|
433
|
+
onClick={() => setStep('directInject')}
|
|
434
|
+
className="font-bold text-cyan-700 underline hover:text-cyan-900 focus:outline-none"
|
|
435
|
+
>
|
|
436
|
+
Direct Inject
|
|
437
|
+
</button>
|
|
438
|
+
</div>
|
|
439
|
+
)}
|
|
435
440
|
</div>
|
|
436
441
|
);
|
|
437
442
|
|
|
@@ -476,6 +481,10 @@ const AddPaneNewPanel = ({
|
|
|
476
481
|
</div>
|
|
477
482
|
);
|
|
478
483
|
|
|
484
|
+
const renderDirectInjectStep = () => (
|
|
485
|
+
<DirectInjectStep onBack={handleBack} onCreatePane={handleApplyTemplate} />
|
|
486
|
+
);
|
|
487
|
+
|
|
479
488
|
const renderLoading = () => (
|
|
480
489
|
<div className="flex min-h-[300px] flex-col items-center justify-center space-y-4 p-6">
|
|
481
490
|
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
@@ -509,6 +518,8 @@ const AddPaneNewPanel = ({
|
|
|
509
518
|
return renderAiDesignStep();
|
|
510
519
|
case 'loading':
|
|
511
520
|
return renderLoading();
|
|
521
|
+
case 'directInject':
|
|
522
|
+
return renderDirectInjectStep();
|
|
512
523
|
case 'error':
|
|
513
524
|
return renderError();
|
|
514
525
|
default:
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { parseAiPane } from '@/utils/compositor/aiPaneParser';
|
|
3
|
+
import type { TemplatePane } from '@/types/compositorTypes';
|
|
4
|
+
|
|
5
|
+
interface DirectInjectStepProps {
|
|
6
|
+
onBack: () => void;
|
|
7
|
+
onCreatePane: (template: TemplatePane) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DirectInjectStep = ({
|
|
11
|
+
onBack,
|
|
12
|
+
onCreatePane,
|
|
13
|
+
}: DirectInjectStepProps) => {
|
|
14
|
+
const [shellJson, setShellJson] = useState('');
|
|
15
|
+
const [copyHtml, setCopyHtml] = useState('');
|
|
16
|
+
const [error, setError] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
const handleCreate = () => {
|
|
19
|
+
setError(null);
|
|
20
|
+
if (!shellJson.trim() || !copyHtml.trim()) {
|
|
21
|
+
setError('Both Shell JSON and Inner HTML must be provided.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const finalPane = parseAiPane(shellJson, copyHtml, 'DirectInject');
|
|
27
|
+
onCreatePane(finalPane);
|
|
28
|
+
} catch (err: any) {
|
|
29
|
+
console.error('Direct Inject Error:', err);
|
|
30
|
+
setError(
|
|
31
|
+
`Failed to parse inputs: ${err.message || 'Unknown error'}. Check console.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="space-y-6 p-4">
|
|
38
|
+
<div className="space-y-4">
|
|
39
|
+
<div>
|
|
40
|
+
<label
|
|
41
|
+
htmlFor="shellJson"
|
|
42
|
+
className="block text-sm font-bold text-gray-700"
|
|
43
|
+
>
|
|
44
|
+
Shell JSON
|
|
45
|
+
</label>
|
|
46
|
+
<textarea
|
|
47
|
+
id="shellJson"
|
|
48
|
+
rows={10}
|
|
49
|
+
value={shellJson}
|
|
50
|
+
onChange={(e) => setShellJson(e.target.value)}
|
|
51
|
+
className="mt-1 block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
|
|
52
|
+
placeholder={`{ "bgColour": "#ffffff", "parentClasses": [...], "defaultClasses": {...} }`}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
<div>
|
|
56
|
+
<label
|
|
57
|
+
htmlFor="copyHtml"
|
|
58
|
+
className="block text-sm font-bold text-gray-700"
|
|
59
|
+
>
|
|
60
|
+
Inner HTML
|
|
61
|
+
</label>
|
|
62
|
+
<textarea
|
|
63
|
+
id="copyHtml"
|
|
64
|
+
rows={10}
|
|
65
|
+
value={copyHtml}
|
|
66
|
+
onChange={(e) => setCopyHtml(e.target.value)}
|
|
67
|
+
className="mt-1 block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
|
|
68
|
+
placeholder={`<h2 class="...">...</h2>\n<p class="...">...</p>`}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{error && (
|
|
74
|
+
<div className="rounded-md bg-red-50 p-4">
|
|
75
|
+
<p className="text-sm font-bold text-red-800">{error}</p>
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<div className="flex justify-between">
|
|
80
|
+
<button
|
|
81
|
+
onClick={onBack}
|
|
82
|
+
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"
|
|
83
|
+
>
|
|
84
|
+
← Back
|
|
85
|
+
</button>
|
|
86
|
+
<button
|
|
87
|
+
onClick={handleCreate}
|
|
88
|
+
disabled={!shellJson.trim() || !copyHtml.trim()}
|
|
89
|
+
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"
|
|
90
|
+
>
|
|
91
|
+
Create Pane
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
@@ -60,6 +60,8 @@ let BUTTON_CLASS_LOOKUP: Map<string, { key: string; value: string }> | null =
|
|
|
60
60
|
null;
|
|
61
61
|
|
|
62
62
|
const ALLOWED_TAGS = new Set([
|
|
63
|
+
'ul',
|
|
64
|
+
'li',
|
|
63
65
|
'h2',
|
|
64
66
|
'h3',
|
|
65
67
|
'h4',
|
|
@@ -69,6 +71,7 @@ const ALLOWED_TAGS = new Set([
|
|
|
69
71
|
'em',
|
|
70
72
|
'strong',
|
|
71
73
|
'button',
|
|
74
|
+
'a',
|
|
72
75
|
]);
|
|
73
76
|
|
|
74
77
|
function buildKeyNormalizationLookup(): Map<string, string> {
|
|
@@ -309,7 +312,10 @@ function walkDom(
|
|
|
309
312
|
if (tagName === 'button') {
|
|
310
313
|
let finalParentId = parentId;
|
|
311
314
|
|
|
312
|
-
|
|
315
|
+
const parentDomEl = el.parentNode as Element;
|
|
316
|
+
const parentTagName = parentDomEl?.tagName?.toLowerCase();
|
|
317
|
+
|
|
318
|
+
if (parentId === markdownId || parentTagName === 'li') {
|
|
313
319
|
const pNodeId = ulid();
|
|
314
320
|
const pNode: TemplateNode = {
|
|
315
321
|
id: pNodeId,
|
|
@@ -330,7 +336,7 @@ function walkDom(
|
|
|
330
336
|
id: ulid(),
|
|
331
337
|
nodeType: 'TagElement',
|
|
332
338
|
parentId: finalParentId,
|
|
333
|
-
tagName: 'a',
|
|
339
|
+
tagName: 'a',
|
|
334
340
|
href: '#',
|
|
335
341
|
buttonPayload: {
|
|
336
342
|
...buttonPayload,
|
package/utils/inject-files.ts
CHANGED
|
@@ -471,6 +471,12 @@ export async function injectTemplateFiles(
|
|
|
471
471
|
),
|
|
472
472
|
dest: 'src/components/edit/pane/steps/AiDesignStep.tsx',
|
|
473
473
|
},
|
|
474
|
+
{
|
|
475
|
+
src: resolve(
|
|
476
|
+
'../templates/src/components/edit/pane/steps/DirectInjectStep.tsx'
|
|
477
|
+
),
|
|
478
|
+
dest: 'src/components/edit/pane/steps/DirectInjectStep.tsx',
|
|
479
|
+
},
|
|
474
480
|
{
|
|
475
481
|
src: resolve(
|
|
476
482
|
'../templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx'
|