astro-tractstack 2.0.14 → 2.0.16

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 (34) hide show
  1. package/dist/index.js +41 -9
  2. package/package.json +1 -1
  3. package/templates/custom/with-examples/CodeHook.astro +4 -0
  4. package/templates/custom/with-examples/SandboxLauncher.tsx +65 -0
  5. package/templates/env.example +3 -0
  6. package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +75 -0
  7. package/templates/src/components/codehooks/SandboxRegisterForm.tsx +202 -0
  8. package/templates/src/components/compositor/Compositor.tsx +2 -0
  9. package/templates/src/components/compositor/Node.tsx +27 -9
  10. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +13 -11
  11. package/templates/src/components/compositor/nodes/Pane_layout.tsx +16 -14
  12. package/templates/src/components/edit/Header.tsx +8 -2
  13. package/templates/src/components/edit/PanelSwitch.tsx +4 -4
  14. package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -0
  15. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +463 -561
  16. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +140 -0
  17. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +105 -0
  18. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +395 -0
  19. package/templates/src/components/edit/panels/StyleImagePanel.tsx +10 -8
  20. package/templates/src/components/edit/state/SaveModal.tsx +41 -0
  21. package/templates/src/constants/prompts.json +3 -1
  22. package/templates/src/pages/api/sandbox.ts +86 -0
  23. package/templates/src/pages/sandbox.astro +137 -0
  24. package/templates/src/types/nodeProps.ts +1 -0
  25. package/templates/src/utils/compositor/aiPaneParser.ts +32 -84
  26. package/templates/src/utils/compositor/designLibraryHelper.ts +87 -2
  27. package/templates/src/utils/profileStorage.ts +13 -0
  28. package/utils/inject-files.ts +41 -10
  29. package/templates/src/components/edit/pane/AiPaneGenerator.tsx +0 -575
  30. package/templates/src/components/edit/pane/AiPanePreview.tsx +0 -107
  31. package/templates/src/components/edit/pane/PageGen.tsx +0 -485
  32. package/templates/src/components/edit/pane/PageGenSelector.tsx +0 -245
  33. package/templates/src/components/edit/pane/PageGenSpecial.tsx +0 -339
  34. package/templates/src/utils/aai/getTitleSlug.ts +0 -72
@@ -1,107 +0,0 @@
1
- import { useState, useEffect, useMemo } from 'react';
2
- import type { TemplatePane } from '@/types/compositorTypes';
3
- import { parseAiPane } from '@/utils/compositor/aiPaneParser';
4
-
5
- interface AiPanePreviewProps {
6
- shellJson: string;
7
- copyHtml: string;
8
- layout: string;
9
- ownerId: string;
10
- onComplete: (pane: TemplatePane) => void;
11
- onBack: () => void;
12
- }
13
-
14
- export function AiPanePreview({
15
- shellJson,
16
- copyHtml,
17
- layout,
18
- onComplete,
19
- onBack,
20
- }: AiPanePreviewProps) {
21
- const [error, setError] = useState<string | null>(null);
22
- const [hasCompleted, setHasCompleted] = useState<boolean>(false);
23
- const [isLoading, setIsLoading] = useState<boolean>(true);
24
-
25
- useEffect(() => {
26
- setError(null);
27
- setHasCompleted(false);
28
- setIsLoading(true);
29
- let isActive = true;
30
-
31
- if (shellJson && copyHtml) {
32
- try {
33
- const pane = parseAiPane(shellJson, copyHtml, layout);
34
- if (isActive && !hasCompleted) {
35
- onComplete(pane);
36
- setHasCompleted(true);
37
- setIsLoading(false);
38
- }
39
- } catch (err: any) {
40
- console.error('Error parsing AI Pane:', err);
41
- if (isActive) {
42
- setError(err.message || 'Failed to parse generated content.');
43
- setIsLoading(false);
44
- }
45
- }
46
- } else {
47
- // Handle case where inputs might be initially empty
48
- setIsLoading(false);
49
- }
50
-
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
- );
64
- }
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]);
90
-
91
- return (
92
- <div className="flex h-full flex-col p-4">
93
- <div className="relative mb-4 flex min-h-[200px] flex-grow items-center justify-center overflow-auto rounded border bg-gray-50">
94
- {displayContent}
95
- </div>
96
- <div className="flex flex-shrink-0 justify-start">
97
- <button
98
- onClick={onBack}
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"
100
- type="button"
101
- >
102
- Back
103
- </button>
104
- </div>
105
- </div>
106
- );
107
- }
@@ -1,485 +0,0 @@
1
- import { useState, useRef, useEffect } from 'react';
2
- import { Dialog } from '@ark-ui/react/dialog';
3
- import { Portal } from '@ark-ui/react/portal';
4
- import {
5
- RadioGroup,
6
- type RadioGroup as RadioGroupNamespace,
7
- } from '@ark-ui/react/radio-group';
8
- import CheckCircleIcon from '@heroicons/react/20/solid/CheckCircleIcon';
9
- import {
10
- formatPrompt,
11
- pagePrompts,
12
- pagePromptsDetails,
13
- } from '@/constants/prompts.json';
14
- import {
15
- parsePageMarkdown,
16
- createPagePanes,
17
- } from '@/utils/compositor/processMarkdown';
18
- import PageCreationPreview from './PageGen_preview';
19
- import type { NodesContext } from '@/stores/nodes';
20
- import type { StoryFragmentNode, PageDesign } from '@/types/compositorTypes';
21
-
22
- type PromptType = keyof typeof pagePrompts;
23
-
24
- interface GenerationResponse {
25
- success: boolean;
26
- data?: {
27
- response: string;
28
- };
29
- error?: string;
30
- }
31
-
32
- type GenerationStatus = 'idle' | 'generating' | 'success' | 'error';
33
-
34
- interface PageCreationGenProps {
35
- nodeId: string;
36
- ctx: NodesContext;
37
- }
38
-
39
- export const PageCreationGen = ({ nodeId, ctx }: PageCreationGenProps) => {
40
- const [selectedPromptType, setSelectedPromptType] =
41
- useState<PromptType>('landing');
42
- const [customizedPrompt, setCustomizedPrompt] = useState(
43
- pagePrompts[selectedPromptType]
44
- );
45
- const [referenceContext, setReferenceContext] = useState('');
46
- const [additionalInstructions, setAdditionalInstructions] = useState('');
47
- const [showPreview, setShowPreview] = useState(false);
48
- const [isApplying, setIsApplying] = useState(false);
49
-
50
- const [generationStatus, setGenerationStatus] =
51
- useState<GenerationStatus>('idle');
52
- const [error, setError] = useState<string | null>(null);
53
- const [generatedContent, setGeneratedContent] = useState<string | null>(null);
54
- const [showModal, setShowModal] = useState(false);
55
-
56
- const [showProgressModal, setShowProgressModal] = useState(false);
57
- const [progressValue, setProgressValue] = useState(0);
58
- const [progressTotal, setProgressTotal] = useState(0);
59
-
60
- const [activeDesign, setActiveDesign] = useState<PageDesign | null>(null);
61
-
62
- const dialogButtonRef = useRef<HTMLButtonElement>(null);
63
-
64
- const handlePromptTypeChange = (
65
- details: RadioGroupNamespace.ValueChangeDetails
66
- ) => {
67
- if (!details.value) return;
68
- const type = details.value as PromptType;
69
- setSelectedPromptType(type);
70
- setCustomizedPrompt(pagePrompts[type]);
71
- };
72
-
73
- const handleGenerate = async () => {
74
- setShowModal(true);
75
- setGenerationStatus('generating');
76
- setError(null);
77
-
78
- const finalPrompt = `${formatPrompt}
79
-
80
- Writing Style Instructions:
81
- ${customizedPrompt}
82
-
83
- Additional Instructions:
84
- ${additionalInstructions}`;
85
-
86
- try {
87
- const goBackend =
88
- import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
89
- const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
90
-
91
- const response = await fetch(`${goBackend}/api/v1/aai/askLemur`, {
92
- method: 'POST',
93
- headers: {
94
- 'Content-Type': 'application/json',
95
- 'X-Tenant-ID': tenantId,
96
- },
97
- credentials: 'include',
98
- body: JSON.stringify({
99
- prompt: finalPrompt,
100
- input_text: referenceContext,
101
- final_model: '',
102
- temperature: 0.7,
103
- max_tokens: 4000,
104
- }),
105
- });
106
-
107
- if (!response.ok) {
108
- throw new Error('Generation failed');
109
- }
110
-
111
- const result = (await response.json()) as GenerationResponse;
112
-
113
- if (!result.success || !result.data?.response) {
114
- throw new Error(result.error || 'Generation failed');
115
- }
116
-
117
- setGeneratedContent(result.data.response);
118
- setGenerationStatus('success');
119
- } catch (err) {
120
- setError(err instanceof Error ? err.message : 'An error occurred');
121
- setGenerationStatus('error');
122
- }
123
- };
124
-
125
- const handleModalClose = (details: { open: boolean }) => {
126
- if (generationStatus === 'generating') {
127
- return;
128
- }
129
- if (generationStatus === 'success' && !details.open) {
130
- setShowModal(false);
131
- setShowPreview(true);
132
- return;
133
- }
134
- setShowModal(false);
135
- setGenerationStatus('idle');
136
- };
137
-
138
- const handlePreviewApply = (markdownContent: string, design: PageDesign) => {
139
- if (isApplying) return;
140
- setGeneratedContent(markdownContent);
141
- setActiveDesign(design);
142
- setIsApplying(true);
143
- };
144
-
145
- useEffect(() => {
146
- if (isApplying && generatedContent && activeDesign) {
147
- const applyDesignAsync = async () => {
148
- setShowProgressModal(true);
149
- setProgressValue(0);
150
- setProgressTotal(0);
151
-
152
- const onProgress = (current: number, total: number) => {
153
- setProgressValue(current);
154
- setProgressTotal(total);
155
- };
156
-
157
- try {
158
- await ctx.applyAtomicUpdate(async (tmpCtx) => {
159
- const processedPage = parsePageMarkdown(generatedContent);
160
- const paneIds = await createPagePanes(
161
- processedPage,
162
- activeDesign,
163
- tmpCtx,
164
- true,
165
- nodeId,
166
- onProgress
167
- );
168
-
169
- const storyfragment = tmpCtx.allNodes
170
- .get()
171
- .get(nodeId) as StoryFragmentNode;
172
- if (storyfragment) {
173
- storyfragment.paneIds = paneIds;
174
- storyfragment.isChanged = true;
175
- tmpCtx.modifyNodes([storyfragment]);
176
- }
177
- });
178
-
179
- setTimeout(() => {
180
- setShowPreview(false);
181
- setShowProgressModal(false);
182
- setIsApplying(false);
183
- }, 500);
184
- } catch (error) {
185
- console.error('Error applying design:', error);
186
- setError('An error occurred while applying the design.');
187
- setShowProgressModal(false);
188
- setIsApplying(false);
189
- }
190
- };
191
-
192
- applyDesignAsync();
193
- }
194
- }, [isApplying, generatedContent, activeDesign]);
195
-
196
- const dialogStyles = `
197
- [data-part="backdrop"] {
198
- background-color: rgba(0, 0, 0, 0.3);
199
- }
200
- [data-part="content"] {
201
- max-width: 32rem;
202
- background-color: white;
203
- border-radius: 0.5rem;
204
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
205
- padding: 1.5rem;
206
- }
207
- [data-part="title"] {
208
- font-size: 1.125rem;
209
- font-weight: bold;
210
- color: #1f2937;
211
- margin-bottom: 0.5rem;
212
- }
213
- `;
214
-
215
- const radioGroupStyles = `
216
- .radio-item[data-highlighted] {
217
- outline: none;
218
- }
219
- .radio-item[data-state="checked"] {
220
- background-color: #efefef;
221
- color: white;
222
- }
223
- .radio-item[data-state="checked"] .radio-description {
224
- color: black;
225
- }
226
- .radio-item[data-state="checked"] .check-icon {
227
- display: flex;
228
- }
229
- .radio-item .check-icon {
230
- display: none;
231
- }
232
- `;
233
-
234
- return (
235
- <>
236
- <style>{dialogStyles}</style>
237
- <style>{radioGroupStyles}</style>
238
-
239
- {showPreview && generatedContent ? (
240
- <PageCreationPreview
241
- markdownContent={generatedContent}
242
- onComplete={handlePreviewApply}
243
- onBack={() => setShowPreview(false)}
244
- isApplying={isApplying}
245
- />
246
- ) : (
247
- <div className="p-0.5 shadow-inner">
248
- <div className="w-full rounded-md bg-white p-6">
249
- <h2 className="font-action mb-6 text-2xl font-bold text-gray-900">
250
- Generate Page Content with AI
251
- </h2>
252
- <div className="space-y-8">
253
- <div className="w-full">
254
- <RadioGroup.Root
255
- defaultValue={selectedPromptType}
256
- onValueChange={handlePromptTypeChange}
257
- >
258
- <RadioGroup.Label className="mb-4 block text-sm font-bold text-gray-900">
259
- Select Page Type
260
- </RadioGroup.Label>
261
- <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
262
- {Object.entries(pagePromptsDetails).map(
263
- ([key, details]) => (
264
- <RadioGroup.Item
265
- key={key}
266
- value={key}
267
- className="radio-item relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none"
268
- >
269
- <div className="flex w-full items-center justify-between">
270
- <div className="flex items-center">
271
- <RadioGroup.ItemControl className="hidden" />
272
- <RadioGroup.ItemText>
273
- <div className="text-sm">
274
- <p className="font-bold text-black">
275
- {details.title}
276
- </p>
277
- <span className="radio-description inline text-gray-500">
278
- {details.description}
279
- </span>
280
- </div>
281
- </RadioGroup.ItemText>
282
- </div>
283
- <div className="check-icon shrink-0 text-white">
284
- <CheckCircleIcon className="h-6 w-6" />
285
- </div>
286
- </div>
287
- <RadioGroup.ItemHiddenInput />
288
- </RadioGroup.Item>
289
- )
290
- )}
291
- </div>
292
- </RadioGroup.Root>
293
- </div>
294
-
295
- <div>
296
- <label
297
- htmlFor="customPrompt"
298
- className="mb-2 block text-sm font-bold text-gray-900"
299
- >
300
- Customize Writing Style (Optional)
301
- </label>
302
- <textarea
303
- id="customPrompt"
304
- rows={6}
305
- className="w-full rounded-md border-gray-300 p-3.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
306
- value={customizedPrompt}
307
- onChange={(e) => setCustomizedPrompt(e.target.value)}
308
- maxLength={1000}
309
- />
310
- </div>
311
-
312
- <div>
313
- <label
314
- htmlFor="referenceContext"
315
- className="block text-sm font-bold text-gray-900"
316
- >
317
- Reference Content &ndash; copy and paste dump here; no
318
- formatting required...
319
- </label>
320
- <textarea
321
- id="referenceContext"
322
- rows={8}
323
- className="w-full rounded-md border-gray-300 p-3.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
324
- value={referenceContext}
325
- onChange={(e) => setReferenceContext(e.target.value)}
326
- maxLength={200000}
327
- placeholder="Paste your reference content here..."
328
- />
329
- </div>
330
-
331
- <div>
332
- <label
333
- htmlFor="additionalInstructions"
334
- className="mb-2 block text-sm font-bold text-gray-900"
335
- >
336
- Additional Instructions (Optional)
337
- </label>
338
- <textarea
339
- id="additionalInstructions"
340
- rows={4}
341
- className="w-full rounded-md border-gray-300 p-3.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
342
- value={additionalInstructions}
343
- onChange={(e) => setAdditionalInstructions(e.target.value)}
344
- maxLength={2000}
345
- placeholder="Any additional instructions or requirements..."
346
- />
347
- </div>
348
-
349
- <div className="flex items-center justify-end pt-4">
350
- <div className="flex gap-4">
351
- <button
352
- type="button"
353
- onClick={() => {
354
- ctx.setPanelMode(nodeId, 'add', 'DEFAULT');
355
- ctx.notifyNode('root');
356
- }}
357
- className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2"
358
- >
359
- Back
360
- </button>
361
- <button
362
- type="button"
363
- onClick={handleGenerate}
364
- disabled={
365
- !referenceContext.trim() ||
366
- generationStatus === 'generating'
367
- }
368
- className={`rounded-md px-4 py-2 text-sm font-bold text-white focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2 ${
369
- referenceContext.trim() &&
370
- generationStatus !== 'generating'
371
- ? 'bg-cyan-600 hover:bg-cyan-700'
372
- : 'cursor-not-allowed bg-gray-300'
373
- }`}
374
- >
375
- {generationStatus === 'generating'
376
- ? 'Generating...'
377
- : 'Generate Content'}
378
- </button>
379
- </div>
380
- </div>
381
- </div>
382
- <Dialog.Root
383
- open={showModal}
384
- onOpenChange={handleModalClose}
385
- modal={true}
386
- >
387
- <Portal>
388
- <Dialog.Backdrop />
389
- <Dialog.Positioner className="fixed inset-0 z-50 flex items-center justify-center p-4">
390
- <Dialog.Content className="w-full max-w-md overflow-hidden text-left align-middle">
391
- <Dialog.Title>
392
- {generationStatus === 'error'
393
- ? 'Generation Error'
394
- : generationStatus === 'success'
395
- ? 'Content Generated'
396
- : 'Generating Content'}
397
- </Dialog.Title>
398
- <div className="mt-2">
399
- {generationStatus === 'error' ? (
400
- <p className="text-sm text-red-600">{error}</p>
401
- ) : generationStatus === 'success' ? (
402
- <div className="space-y-4">
403
- <p className="text-sm text-gray-600">
404
- Content has been generated successfully!
405
- </p>
406
- <div className="max-h-60 overflow-y-auto rounded-md bg-gray-50 p-3">
407
- <pre className="whitespace-pre-wrap text-sm text-gray-800">
408
- {generatedContent}
409
- </pre>
410
- </div>
411
- </div>
412
- ) : (
413
- <div className="flex items-center gap-3">
414
- <div className="h-5 w-5 animate-spin rounded-full border-b-2 border-cyan-600"></div>
415
- <p className="text-sm text-gray-500">
416
- Generating your page content...
417
- </p>
418
- </div>
419
- )}
420
- </div>
421
- <div className="mt-4">
422
- <button
423
- ref={dialogButtonRef}
424
- type="button"
425
- className="inline-flex justify-center rounded-md border border-transparent bg-cyan-600 px-4 py-2 text-sm font-bold text-white hover:bg-cyan-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500 focus-visible:ring-offset-2"
426
- onClick={() => {
427
- if (generationStatus === 'success') {
428
- setShowModal(false);
429
- setShowPreview(true);
430
- } else if (generationStatus === 'error') {
431
- setShowModal(false);
432
- setGenerationStatus('idle');
433
- }
434
- }}
435
- disabled={generationStatus === 'generating'}
436
- >
437
- {generationStatus === 'error'
438
- ? 'Try Again'
439
- : generationStatus === 'success'
440
- ? 'Continue'
441
- : 'Cancel'}
442
- </button>
443
- </div>
444
- </Dialog.Content>
445
- </Dialog.Positioner>
446
- </Portal>
447
- </Dialog.Root>
448
- </div>
449
- </div>
450
- )}
451
-
452
- <Dialog.Root open={showProgressModal} modal={true}>
453
- <Portal>
454
- <Dialog.Backdrop className="fixed inset-0 bg-black/75" />
455
- <Dialog.Positioner className="fixed inset-0 z-50 flex items-center justify-center p-4">
456
- <Dialog.Content className="w-full max-w-md overflow-hidden rounded-lg bg-white p-6 text-left align-middle shadow-xl">
457
- <Dialog.Title className="text-lg font-bold text-gray-900">
458
- Applying Design
459
- </Dialog.Title>
460
- <div className="mt-4 space-y-2">
461
- <p className="text-sm text-gray-500">
462
- Creating page panes... {progressValue} of {progressTotal}
463
- </p>
464
- <div className="h-2.5 w-full rounded-full bg-gray-200">
465
- <div
466
- className="h-2.5 rounded-full bg-cyan-600 transition-all duration-300"
467
- style={{
468
- width: `${
469
- progressTotal > 0
470
- ? (progressValue / progressTotal) * 100
471
- : 0
472
- }%`,
473
- }}
474
- ></div>
475
- </div>
476
- </div>
477
- </Dialog.Content>
478
- </Dialog.Positioner>
479
- </Portal>
480
- </Dialog.Root>
481
- </>
482
- );
483
- };
484
-
485
- export default PageCreationGen;