astro-tractstack 2.0.15 → 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 +33 -13
- package/package.json +1 -1
- package/templates/custom/with-examples/CodeHook.astro +4 -0
- package/templates/custom/with-examples/SandboxLauncher.tsx +67 -0
- package/templates/env.example +3 -0
- package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +75 -0
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +202 -0
- package/templates/src/components/compositor/Compositor.tsx +2 -0
- package/templates/src/components/compositor/Node.tsx +6 -1
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +13 -11
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +16 -14
- package/templates/src/components/edit/Header.tsx +8 -2
- package/templates/src/components/edit/PanelSwitch.tsx +4 -4
- package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +61 -46
- package/templates/src/components/edit/pane/steps/DirectInjectStep.tsx +96 -0
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +10 -8
- package/templates/src/components/edit/state/SaveModal.tsx +41 -0
- package/templates/src/constants.ts +1 -0
- package/templates/src/pages/api/sandbox.ts +86 -0
- package/templates/src/pages/sandbox.astro +137 -0
- package/templates/src/types/nodeProps.ts +1 -0
- package/templates/src/utils/compositor/aiPaneParser.ts +8 -2
- package/templates/src/utils/profileStorage.ts +13 -0
- package/utils/inject-files.ts +33 -14
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +0 -512
- package/templates/src/components/edit/pane/AiPanePreview.tsx +0 -107
- package/templates/src/utils/aai/getTitleSlug.ts +0 -72
|
@@ -10,7 +10,6 @@ import prompts from '@/constants/prompts.json';
|
|
|
10
10
|
import type { BrandConfig, DesignLibraryEntry } from '@/types/tractstack';
|
|
11
11
|
import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
|
|
12
12
|
import { useStore } from '@nanostores/react';
|
|
13
|
-
|
|
14
13
|
import { CopyInputStep } from './steps/CopyInputStep';
|
|
15
14
|
import { DesignLibraryStep } from './steps/DesignLibraryStep';
|
|
16
15
|
import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
|
|
@@ -20,20 +19,20 @@ import {
|
|
|
20
19
|
mergeCopyIntoTemplate,
|
|
21
20
|
convertTemplateToAIShell,
|
|
22
21
|
} from '@/utils/compositor/designLibraryHelper';
|
|
22
|
+
import { DirectInjectStep } from './steps/DirectInjectStep';
|
|
23
23
|
|
|
24
|
-
// --- Types for Workflow State ---
|
|
25
24
|
type Step =
|
|
26
25
|
| 'initial'
|
|
27
26
|
| 'copyInput'
|
|
28
27
|
| 'designLibrary'
|
|
29
28
|
| 'aiDesign'
|
|
30
29
|
| 'loading'
|
|
31
|
-
| 'error'
|
|
30
|
+
| 'error'
|
|
31
|
+
| 'directInject';
|
|
32
32
|
|
|
33
33
|
type InitialChoice = 'library' | 'ai' | 'blank';
|
|
34
34
|
type CopyMode = 'prompt' | 'raw';
|
|
35
35
|
|
|
36
|
-
// --- API Call Helper ---
|
|
37
36
|
interface GenerationResponse {
|
|
38
37
|
success: boolean;
|
|
39
38
|
data?: { response: string | object };
|
|
@@ -43,7 +42,8 @@ interface GenerationResponse {
|
|
|
43
42
|
const callAskLemurAPI = async (
|
|
44
43
|
prompt: string,
|
|
45
44
|
context: string,
|
|
46
|
-
expectJson: boolean
|
|
45
|
+
expectJson: boolean,
|
|
46
|
+
isSandboxMode: boolean
|
|
47
47
|
): Promise<string> => {
|
|
48
48
|
const goBackend =
|
|
49
49
|
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
@@ -56,12 +56,22 @@ const callAskLemurAPI = async (
|
|
|
56
56
|
max_tokens: 2000,
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
let response: Response;
|
|
60
|
+
if (isSandboxMode) {
|
|
61
|
+
response = await fetch(`/api/sandbox`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
|
|
64
|
+
credentials: 'include',
|
|
65
|
+
body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
response = await fetch(`${goBackend}/api/v1/aai/askLemur`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
|
|
71
|
+
credentials: 'include',
|
|
72
|
+
body: JSON.stringify(requestBody),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
65
75
|
|
|
66
76
|
if (!response.ok) {
|
|
67
77
|
const errorText = await response.text();
|
|
@@ -98,7 +108,6 @@ const callAskLemurAPI = async (
|
|
|
98
108
|
throw new Error('Unexpected response format received from API.');
|
|
99
109
|
};
|
|
100
110
|
|
|
101
|
-
// --- Main Component ---
|
|
102
111
|
interface AddPaneNewPanelProps {
|
|
103
112
|
nodeId: string;
|
|
104
113
|
first: boolean;
|
|
@@ -107,6 +116,7 @@ interface AddPaneNewPanelProps {
|
|
|
107
116
|
isStoryFragment?: boolean;
|
|
108
117
|
isContextPane?: boolean;
|
|
109
118
|
config?: BrandConfig;
|
|
119
|
+
isSandboxMode?: boolean;
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
const AddPaneNewPanel = ({
|
|
@@ -117,23 +127,18 @@ const AddPaneNewPanel = ({
|
|
|
117
127
|
isStoryFragment = false,
|
|
118
128
|
isContextPane = false,
|
|
119
129
|
config,
|
|
130
|
+
isSandboxMode = false,
|
|
120
131
|
}: AddPaneNewPanelProps) => {
|
|
121
132
|
const ctx = providedCtx || getCtx();
|
|
122
133
|
const hasAssemblyAI = useStore(hasAssemblyAIStore);
|
|
123
|
-
|
|
124
|
-
// --- State Machine and Data Stores ---
|
|
125
134
|
const [step, setStep] = useState<Step>('initial');
|
|
126
135
|
const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
|
|
127
136
|
null
|
|
128
137
|
);
|
|
129
138
|
const [error, setError] = useState<string | null>(null);
|
|
130
|
-
|
|
131
|
-
// State for CopyInputStep
|
|
132
|
-
const [copyMode, setCopyMode] = useState<CopyMode>('raw');
|
|
139
|
+
const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
|
|
133
140
|
const [promptValue, setPromptValue] = useState('');
|
|
134
141
|
const [copyValue, setCopyValue] = useState('');
|
|
135
|
-
|
|
136
|
-
// State for AiDesignStep
|
|
137
142
|
const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
|
|
138
143
|
harmony: 'Analogous',
|
|
139
144
|
baseColor: '',
|
|
@@ -142,8 +147,6 @@ const AddPaneNewPanel = ({
|
|
|
142
147
|
additionalNotes: '',
|
|
143
148
|
});
|
|
144
149
|
|
|
145
|
-
// --- Handlers & Logic ---
|
|
146
|
-
|
|
147
150
|
const handleInitialChoice = (choice: InitialChoice) => {
|
|
148
151
|
setInitialChoice(choice);
|
|
149
152
|
setError(null);
|
|
@@ -159,6 +162,8 @@ const AddPaneNewPanel = ({
|
|
|
159
162
|
setError(null);
|
|
160
163
|
if (step === 'copyInput') {
|
|
161
164
|
setStep('initial');
|
|
165
|
+
} else if (step === 'directInject') {
|
|
166
|
+
setStep('aiDesign');
|
|
162
167
|
} else if (step === 'designLibrary' || step === 'aiDesign' || 'error') {
|
|
163
168
|
setStep('copyInput');
|
|
164
169
|
}
|
|
@@ -174,18 +179,18 @@ const AddPaneNewPanel = ({
|
|
|
174
179
|
|
|
175
180
|
const handleBlankSlate = () => {
|
|
176
181
|
const blankTemplate: TemplatePane = {
|
|
177
|
-
id: '',
|
|
182
|
+
id: '',
|
|
178
183
|
nodeType: 'Pane',
|
|
179
|
-
parentId: '',
|
|
184
|
+
parentId: '',
|
|
180
185
|
title: 'New Pane',
|
|
181
186
|
slug: '',
|
|
182
187
|
isDecorative: false,
|
|
183
188
|
markdown: {
|
|
184
|
-
id: '',
|
|
189
|
+
id: '',
|
|
185
190
|
nodeType: 'Markdown',
|
|
186
|
-
parentId: '',
|
|
191
|
+
parentId: '',
|
|
187
192
|
type: 'markdown',
|
|
188
|
-
markdownId: '',
|
|
193
|
+
markdownId: '',
|
|
189
194
|
defaultClasses: {},
|
|
190
195
|
parentClasses: [],
|
|
191
196
|
nodes: [],
|
|
@@ -195,10 +200,9 @@ const AddPaneNewPanel = ({
|
|
|
195
200
|
};
|
|
196
201
|
|
|
197
202
|
const handleDesignLibrarySelect = async (entry: DesignLibraryEntry) => {
|
|
198
|
-
// This flow is for "Design Library + Provide Copy"
|
|
199
203
|
if (copyMode === 'raw') {
|
|
200
204
|
const liveTemplate = convertStorageToLiveTemplate(
|
|
201
|
-
mergeCopyIntoTemplate(entry.template, [])
|
|
205
|
+
mergeCopyIntoTemplate(entry.template, [])
|
|
202
206
|
);
|
|
203
207
|
if (liveTemplate.markdown) {
|
|
204
208
|
liveTemplate.markdown.markdownBody = copyValue;
|
|
@@ -207,12 +211,10 @@ const AddPaneNewPanel = ({
|
|
|
207
211
|
return;
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
// This flow is for "Design Library + Write a Prompt" (Hybrid AI)
|
|
211
214
|
if (copyMode === 'prompt') {
|
|
212
215
|
setError(null);
|
|
213
216
|
setStep('loading');
|
|
214
217
|
try {
|
|
215
|
-
// 1. Get the full, rich template from the library
|
|
216
218
|
const liveTemplate = convertStorageToLiveTemplate(entry.template);
|
|
217
219
|
if (!liveTemplate.markdown) {
|
|
218
220
|
throw new Error(
|
|
@@ -220,7 +222,6 @@ const AddPaneNewPanel = ({
|
|
|
220
222
|
);
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
// 2. Create the simplified shell for the AI
|
|
224
225
|
const shellJson = convertTemplateToAIShell(liveTemplate);
|
|
225
226
|
if (!shellJson || shellJson === '{}') {
|
|
226
227
|
throw new Error(
|
|
@@ -228,7 +229,6 @@ const AddPaneNewPanel = ({
|
|
|
228
229
|
);
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
// 3. Get the AI to write copy based on the shell and prompt
|
|
232
232
|
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
233
233
|
const layout = 'Text Only';
|
|
234
234
|
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
@@ -243,19 +243,16 @@ const AddPaneNewPanel = ({
|
|
|
243
243
|
const copyResult = await callAskLemurAPI(
|
|
244
244
|
formattedCopyPrompt,
|
|
245
245
|
copyPromptDetails.system || '',
|
|
246
|
-
false
|
|
246
|
+
false,
|
|
247
|
+
isSandboxMode
|
|
247
248
|
);
|
|
248
249
|
|
|
249
|
-
// 4. Parse ONLY the AI-generated HTML into content nodes
|
|
250
250
|
const newNodes = parseAiCopyHtml(copyResult, liveTemplate.markdown.id);
|
|
251
251
|
|
|
252
|
-
// 5. Create the final pane by cloning the original rich template
|
|
253
252
|
const finalPane = cloneDeep(liveTemplate);
|
|
254
253
|
|
|
255
|
-
// 6. Inject the new AI content, preserving the original rich design
|
|
256
254
|
finalPane.markdown!.nodes = newNodes;
|
|
257
255
|
|
|
258
|
-
// 7. Apply the complete, correctly merged pane
|
|
259
256
|
handleApplyTemplate(finalPane);
|
|
260
257
|
} catch (err: any) {
|
|
261
258
|
setError(err.message || 'Failed to generate AI copy for this design.');
|
|
@@ -279,7 +276,7 @@ const AddPaneNewPanel = ({
|
|
|
279
276
|
try {
|
|
280
277
|
const shellPromptDetails = prompts.aiPaneShellPrompt;
|
|
281
278
|
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
282
|
-
const layout = 'Text Only';
|
|
279
|
+
const layout = 'Text Only';
|
|
283
280
|
|
|
284
281
|
const formattedShellPrompt = shellPromptDetails.user_template
|
|
285
282
|
.replace('{{DESIGN_INPUT}}', designInput)
|
|
@@ -288,7 +285,8 @@ const AddPaneNewPanel = ({
|
|
|
288
285
|
const shellResult = await callAskLemurAPI(
|
|
289
286
|
formattedShellPrompt,
|
|
290
287
|
shellPromptDetails.system || '',
|
|
291
|
-
true
|
|
288
|
+
true,
|
|
289
|
+
isSandboxMode
|
|
292
290
|
);
|
|
293
291
|
|
|
294
292
|
const copyInputContent = copyMode === 'prompt' ? promptValue : copyValue;
|
|
@@ -301,7 +299,8 @@ const AddPaneNewPanel = ({
|
|
|
301
299
|
const copyResult = await callAskLemurAPI(
|
|
302
300
|
formattedCopyPrompt,
|
|
303
301
|
copyPromptDetails.system || '',
|
|
304
|
-
false
|
|
302
|
+
false,
|
|
303
|
+
isSandboxMode
|
|
305
304
|
);
|
|
306
305
|
|
|
307
306
|
const finalPane = parseAiPane(shellResult, copyResult, layout);
|
|
@@ -310,9 +309,10 @@ const AddPaneNewPanel = ({
|
|
|
310
309
|
setError(err.message || 'Failed to generate AI pane.');
|
|
311
310
|
setStep('error');
|
|
312
311
|
}
|
|
313
|
-
}, [aiDesignConfig, copyMode, promptValue, copyValue]);
|
|
312
|
+
}, [aiDesignConfig, copyMode, promptValue, copyValue, isSandboxMode]);
|
|
314
313
|
|
|
315
314
|
const handleApplyTemplate = async (template: TemplatePane) => {
|
|
315
|
+
console.log(template);
|
|
316
316
|
if (!ctx) return;
|
|
317
317
|
try {
|
|
318
318
|
const insertTemplate = cloneDeep(template);
|
|
@@ -352,8 +352,6 @@ const AddPaneNewPanel = ({
|
|
|
352
352
|
}
|
|
353
353
|
};
|
|
354
354
|
|
|
355
|
-
// --- Render Logic ---
|
|
356
|
-
|
|
357
355
|
const renderInitialStep = () => (
|
|
358
356
|
<div className="p-4">
|
|
359
357
|
<h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
|
|
@@ -365,7 +363,7 @@ const AddPaneNewPanel = ({
|
|
|
365
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"
|
|
366
364
|
>
|
|
367
365
|
<SwatchIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
368
|
-
<h4 className="font-
|
|
366
|
+
<h4 className="font-bold text-gray-800">Use Design Library</h4>
|
|
369
367
|
<p className="text-sm text-gray-600">
|
|
370
368
|
Start with a pre-made design and add your own content.
|
|
371
369
|
</p>
|
|
@@ -376,7 +374,7 @@ const AddPaneNewPanel = ({
|
|
|
376
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"
|
|
377
375
|
>
|
|
378
376
|
<SparklesIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
379
|
-
<h4 className="font-
|
|
377
|
+
<h4 className="font-bold text-gray-800">Design with AI</h4>
|
|
380
378
|
<p className="text-sm text-gray-600">
|
|
381
379
|
Let AI generate a complete design and copy from your prompt.
|
|
382
380
|
</p>
|
|
@@ -387,7 +385,7 @@ const AddPaneNewPanel = ({
|
|
|
387
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"
|
|
388
386
|
>
|
|
389
387
|
<DocumentPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
|
|
390
|
-
<h4 className="font-
|
|
388
|
+
<h4 className="font-bold text-gray-800">Blank Slate</h4>
|
|
391
389
|
<p className="text-sm text-gray-600">
|
|
392
390
|
Add a simple, empty pane to build from scratch.
|
|
393
391
|
</p>
|
|
@@ -428,6 +426,17 @@ const AddPaneNewPanel = ({
|
|
|
428
426
|
Continue →
|
|
429
427
|
</button>
|
|
430
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
|
+
)}
|
|
431
440
|
</div>
|
|
432
441
|
);
|
|
433
442
|
|
|
@@ -472,6 +481,10 @@ const AddPaneNewPanel = ({
|
|
|
472
481
|
</div>
|
|
473
482
|
);
|
|
474
483
|
|
|
484
|
+
const renderDirectInjectStep = () => (
|
|
485
|
+
<DirectInjectStep onBack={handleBack} onCreatePane={handleApplyTemplate} />
|
|
486
|
+
);
|
|
487
|
+
|
|
475
488
|
const renderLoading = () => (
|
|
476
489
|
<div className="flex min-h-[300px] flex-col items-center justify-center space-y-4 p-6">
|
|
477
490
|
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
@@ -505,6 +518,8 @@ const AddPaneNewPanel = ({
|
|
|
505
518
|
return renderAiDesignStep();
|
|
506
519
|
case 'loading':
|
|
507
520
|
return renderLoading();
|
|
521
|
+
case 'directInject':
|
|
522
|
+
return renderDirectInjectStep();
|
|
508
523
|
case 'error':
|
|
509
524
|
return renderError();
|
|
510
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
|
+
};
|
|
@@ -25,14 +25,7 @@ const StyleImagePanel = ({
|
|
|
25
25
|
parentNode,
|
|
26
26
|
}: StyleImagePanelProps) => {
|
|
27
27
|
const [altDescription, setAltDescription] = useState(node.alt || '');
|
|
28
|
-
|
|
29
|
-
!node?.tagName ||
|
|
30
|
-
!containerNode?.tagName ||
|
|
31
|
-
!outerContainerNode?.tagName ||
|
|
32
|
-
!isMarkdownPaneFragmentNode(parentNode)
|
|
33
|
-
) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
28
|
+
|
|
36
29
|
const imgDefaultClasses = parentNode.defaultClasses?.[node.tagName];
|
|
37
30
|
const imgOverrideClasses = node.overrideClasses;
|
|
38
31
|
const containerDefaultClasses =
|
|
@@ -303,6 +296,15 @@ const StyleImagePanel = ({
|
|
|
303
296
|
ctx.modifyNodes([{ ...imgNode, isChanged: true }]);
|
|
304
297
|
};
|
|
305
298
|
|
|
299
|
+
if (
|
|
300
|
+
!node?.tagName ||
|
|
301
|
+
!containerNode?.tagName ||
|
|
302
|
+
!outerContainerNode?.tagName ||
|
|
303
|
+
!isMarkdownPaneFragmentNode(parentNode)
|
|
304
|
+
) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
306
308
|
return (
|
|
307
309
|
<div className="space-y-8">
|
|
308
310
|
<div className="space-y-4">
|
|
@@ -45,6 +45,7 @@ interface SaveModalProps {
|
|
|
45
45
|
slug: string;
|
|
46
46
|
isContext: boolean;
|
|
47
47
|
onClose: () => void;
|
|
48
|
+
isSandboxMode?: boolean;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const PROGRESS_PHASES = {
|
|
@@ -61,11 +62,47 @@ const INDETERMINATE_STAGES: SaveStage[] = [
|
|
|
61
62
|
'UPDATING_HOME_PAGE',
|
|
62
63
|
];
|
|
63
64
|
|
|
65
|
+
const SandboxUpgradeNotice = ({ onClose }: { onClose: () => void }) => (
|
|
66
|
+
<Dialog.Root open={true} onOpenChange={() => onClose()} modal={true}>
|
|
67
|
+
<Portal>
|
|
68
|
+
<Dialog.Backdrop className="fixed inset-0 z-[9005] bg-black bg-opacity-75" />
|
|
69
|
+
<Dialog.Positioner className="fixed inset-0 z-[9005] flex items-center justify-center p-4">
|
|
70
|
+
<Dialog.Content className="w-full max-w-md overflow-hidden rounded-lg bg-white shadow-xl">
|
|
71
|
+
<div className="p-6 text-center">
|
|
72
|
+
<Dialog.Title className="text-xl font-bold text-gray-900">
|
|
73
|
+
Save Your Work
|
|
74
|
+
</Dialog.Title>
|
|
75
|
+
<Dialog.Description className="mt-2 text-gray-600">
|
|
76
|
+
To save your changes and get a shareable link, please sign up for
|
|
77
|
+
a full account.
|
|
78
|
+
</Dialog.Description>
|
|
79
|
+
<div className="mt-6 flex justify-center gap-3">
|
|
80
|
+
<a
|
|
81
|
+
href="/sandbox/register"
|
|
82
|
+
className="bg-myblue hover:bg-myorange rounded-md px-4 py-2 font-bold text-white"
|
|
83
|
+
>
|
|
84
|
+
Sign Up Now
|
|
85
|
+
</a>
|
|
86
|
+
<button
|
|
87
|
+
onClick={onClose}
|
|
88
|
+
className="rounded-md bg-gray-200 px-4 py-2 text-gray-800 hover:bg-gray-300"
|
|
89
|
+
>
|
|
90
|
+
Keep Editing
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</Dialog.Content>
|
|
95
|
+
</Dialog.Positioner>
|
|
96
|
+
</Portal>
|
|
97
|
+
</Dialog.Root>
|
|
98
|
+
);
|
|
99
|
+
|
|
64
100
|
export default function SaveModal({
|
|
65
101
|
show,
|
|
66
102
|
slug,
|
|
67
103
|
isContext,
|
|
68
104
|
onClose,
|
|
105
|
+
isSandboxMode = false,
|
|
69
106
|
}: SaveModalProps) {
|
|
70
107
|
const [stage, setStage] = useState<SaveStage>('PREPARING');
|
|
71
108
|
const [progress, setProgress] = useState(0);
|
|
@@ -871,6 +908,10 @@ export default function SaveModal({
|
|
|
871
908
|
}
|
|
872
909
|
})();
|
|
873
910
|
|
|
911
|
+
if (isSandboxMode) {
|
|
912
|
+
return show ? <SandboxUpgradeNotice onClose={onClose} /> : null;
|
|
913
|
+
}
|
|
914
|
+
|
|
874
915
|
return (
|
|
875
916
|
<Dialog.Root
|
|
876
917
|
open={show}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { APIRoute } from '@/types/astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
4
|
+
console.log(1);
|
|
5
|
+
const goBackend =
|
|
6
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
7
|
+
const sharedSecret = import.meta.env.PRIVATE_SANDBOX_SECRET;
|
|
8
|
+
const tenantId =
|
|
9
|
+
request.headers.get('X-Tenant-ID') ||
|
|
10
|
+
import.meta.env.PUBLIC_TENANTID ||
|
|
11
|
+
'default';
|
|
12
|
+
console.log(goBackend);
|
|
13
|
+
console.log(sharedSecret);
|
|
14
|
+
console.log(tenantId);
|
|
15
|
+
|
|
16
|
+
if (!sharedSecret || sharedSecret === 'false' || sharedSecret === 'true') {
|
|
17
|
+
return new Response(
|
|
18
|
+
JSON.stringify({
|
|
19
|
+
success: false,
|
|
20
|
+
error: 'Sandbox feature is not configured on the server.',
|
|
21
|
+
}),
|
|
22
|
+
{ status: 501, headers: { 'Content-Type': 'application/json' } }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const profileCookie = request.headers
|
|
27
|
+
.get('cookie')
|
|
28
|
+
?.includes('tractstack_profile');
|
|
29
|
+
if (!profileCookie) {
|
|
30
|
+
return new Response(
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
success: false,
|
|
33
|
+
error: 'Forbidden: Missing sandbox profile.',
|
|
34
|
+
}),
|
|
35
|
+
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
console.log(profileCookie);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const body = await request.json();
|
|
42
|
+
const { action, payload } = body;
|
|
43
|
+
console.log(action, payload);
|
|
44
|
+
|
|
45
|
+
if (action !== 'askLemur') {
|
|
46
|
+
return new Response(
|
|
47
|
+
JSON.stringify({ success: false, error: 'Invalid action.' }),
|
|
48
|
+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const backendResponse = await fetch(`${goBackend}/api/v1/aai/askLemur`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'X-Tenant-ID': tenantId,
|
|
57
|
+
Authorization: `Bearer ${sharedSecret}`,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify(payload),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!backendResponse.ok) {
|
|
63
|
+
const errorText = await backendResponse.text();
|
|
64
|
+
return new Response(errorText, {
|
|
65
|
+
status: backendResponse.status,
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await backendResponse.json();
|
|
71
|
+
return new Response(JSON.stringify(data), {
|
|
72
|
+
status: 200,
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const errorMessage =
|
|
77
|
+
error instanceof Error ? error.message : 'An unknown error occurred.';
|
|
78
|
+
return new Response(
|
|
79
|
+
JSON.stringify({ success: false, error: errorMessage }),
|
|
80
|
+
{
|
|
81
|
+
status: 500,
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
};
|