astro-tractstack 2.0.15 → 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.
- package/dist/index.js +27 -13
- package/package.json +1 -1
- package/templates/custom/with-examples/CodeHook.astro +4 -0
- package/templates/custom/with-examples/SandboxLauncher.tsx +65 -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 +28 -24
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +10 -8
- package/templates/src/components/edit/state/SaveModal.tsx +41 -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/profileStorage.ts +13 -0
- package/utils/inject-files.ts +27 -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';
|
|
@@ -21,7 +20,6 @@ import {
|
|
|
21
20
|
convertTemplateToAIShell,
|
|
22
21
|
} from '@/utils/compositor/designLibraryHelper';
|
|
23
22
|
|
|
24
|
-
// --- Types for Workflow State ---
|
|
25
23
|
type Step =
|
|
26
24
|
| 'initial'
|
|
27
25
|
| 'copyInput'
|
|
@@ -33,7 +31,6 @@ type Step =
|
|
|
33
31
|
type InitialChoice = 'library' | 'ai' | 'blank';
|
|
34
32
|
type CopyMode = 'prompt' | 'raw';
|
|
35
33
|
|
|
36
|
-
// --- API Call Helper ---
|
|
37
34
|
interface GenerationResponse {
|
|
38
35
|
success: boolean;
|
|
39
36
|
data?: { response: string | object };
|
|
@@ -43,7 +40,8 @@ interface GenerationResponse {
|
|
|
43
40
|
const callAskLemurAPI = async (
|
|
44
41
|
prompt: string,
|
|
45
42
|
context: string,
|
|
46
|
-
expectJson: boolean
|
|
43
|
+
expectJson: boolean,
|
|
44
|
+
isSandboxMode: boolean
|
|
47
45
|
): Promise<string> => {
|
|
48
46
|
const goBackend =
|
|
49
47
|
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
@@ -56,12 +54,22 @@ const callAskLemurAPI = async (
|
|
|
56
54
|
max_tokens: 2000,
|
|
57
55
|
};
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
let response: Response;
|
|
58
|
+
if (isSandboxMode) {
|
|
59
|
+
response = await fetch(`/api/sandbox`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
|
|
62
|
+
credentials: 'include',
|
|
63
|
+
body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
response = await fetch(`${goBackend}/api/v1/aai/askLemur`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
|
|
69
|
+
credentials: 'include',
|
|
70
|
+
body: JSON.stringify(requestBody),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
65
73
|
|
|
66
74
|
if (!response.ok) {
|
|
67
75
|
const errorText = await response.text();
|
|
@@ -98,7 +106,6 @@ const callAskLemurAPI = async (
|
|
|
98
106
|
throw new Error('Unexpected response format received from API.');
|
|
99
107
|
};
|
|
100
108
|
|
|
101
|
-
// --- Main Component ---
|
|
102
109
|
interface AddPaneNewPanelProps {
|
|
103
110
|
nodeId: string;
|
|
104
111
|
first: boolean;
|
|
@@ -107,6 +114,7 @@ interface AddPaneNewPanelProps {
|
|
|
107
114
|
isStoryFragment?: boolean;
|
|
108
115
|
isContextPane?: boolean;
|
|
109
116
|
config?: BrandConfig;
|
|
117
|
+
isSandboxMode?: boolean;
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
const AddPaneNewPanel = ({
|
|
@@ -117,23 +125,18 @@ const AddPaneNewPanel = ({
|
|
|
117
125
|
isStoryFragment = false,
|
|
118
126
|
isContextPane = false,
|
|
119
127
|
config,
|
|
128
|
+
isSandboxMode = false,
|
|
120
129
|
}: AddPaneNewPanelProps) => {
|
|
121
130
|
const ctx = providedCtx || getCtx();
|
|
122
131
|
const hasAssemblyAI = useStore(hasAssemblyAIStore);
|
|
123
|
-
|
|
124
|
-
// --- State Machine and Data Stores ---
|
|
125
132
|
const [step, setStep] = useState<Step>('initial');
|
|
126
133
|
const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
|
|
127
134
|
null
|
|
128
135
|
);
|
|
129
136
|
const [error, setError] = useState<string | null>(null);
|
|
130
|
-
|
|
131
|
-
// State for CopyInputStep
|
|
132
|
-
const [copyMode, setCopyMode] = useState<CopyMode>('raw');
|
|
137
|
+
const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
|
|
133
138
|
const [promptValue, setPromptValue] = useState('');
|
|
134
139
|
const [copyValue, setCopyValue] = useState('');
|
|
135
|
-
|
|
136
|
-
// State for AiDesignStep
|
|
137
140
|
const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
|
|
138
141
|
harmony: 'Analogous',
|
|
139
142
|
baseColor: '',
|
|
@@ -142,8 +145,6 @@ const AddPaneNewPanel = ({
|
|
|
142
145
|
additionalNotes: '',
|
|
143
146
|
});
|
|
144
147
|
|
|
145
|
-
// --- Handlers & Logic ---
|
|
146
|
-
|
|
147
148
|
const handleInitialChoice = (choice: InitialChoice) => {
|
|
148
149
|
setInitialChoice(choice);
|
|
149
150
|
setError(null);
|
|
@@ -243,7 +244,8 @@ const AddPaneNewPanel = ({
|
|
|
243
244
|
const copyResult = await callAskLemurAPI(
|
|
244
245
|
formattedCopyPrompt,
|
|
245
246
|
copyPromptDetails.system || '',
|
|
246
|
-
false
|
|
247
|
+
false,
|
|
248
|
+
isSandboxMode
|
|
247
249
|
);
|
|
248
250
|
|
|
249
251
|
// 4. Parse ONLY the AI-generated HTML into content nodes
|
|
@@ -288,7 +290,8 @@ const AddPaneNewPanel = ({
|
|
|
288
290
|
const shellResult = await callAskLemurAPI(
|
|
289
291
|
formattedShellPrompt,
|
|
290
292
|
shellPromptDetails.system || '',
|
|
291
|
-
true
|
|
293
|
+
true,
|
|
294
|
+
isSandboxMode
|
|
292
295
|
);
|
|
293
296
|
|
|
294
297
|
const copyInputContent = copyMode === 'prompt' ? promptValue : copyValue;
|
|
@@ -301,7 +304,8 @@ const AddPaneNewPanel = ({
|
|
|
301
304
|
const copyResult = await callAskLemurAPI(
|
|
302
305
|
formattedCopyPrompt,
|
|
303
306
|
copyPromptDetails.system || '',
|
|
304
|
-
false
|
|
307
|
+
false,
|
|
308
|
+
isSandboxMode
|
|
305
309
|
);
|
|
306
310
|
|
|
307
311
|
const finalPane = parseAiPane(shellResult, copyResult, layout);
|
|
@@ -310,7 +314,7 @@ const AddPaneNewPanel = ({
|
|
|
310
314
|
setError(err.message || 'Failed to generate AI pane.');
|
|
311
315
|
setStep('error');
|
|
312
316
|
}
|
|
313
|
-
}, [aiDesignConfig, copyMode, promptValue, copyValue]);
|
|
317
|
+
}, [aiDesignConfig, copyMode, promptValue, copyValue, isSandboxMode]);
|
|
314
318
|
|
|
315
319
|
const handleApplyTemplate = async (template: TemplatePane) => {
|
|
316
320
|
if (!ctx) return;
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ulid } from 'ulid';
|
|
3
|
+
import Layout from '@/layouts/Layout.astro';
|
|
4
|
+
import Header from '@/components/Header.astro';
|
|
5
|
+
import { getFullContentMap } from '@/stores/analytics';
|
|
6
|
+
import { getBrandConfig } from '@/utils/api/brandConfig';
|
|
7
|
+
import { components as codeHookComponents } from '@/custom/CodeHook.astro';
|
|
8
|
+
import StoryKeepHeader from '@/components/edit/Header';
|
|
9
|
+
import StoryKeepToolBar from '@/components/edit/ToolBar';
|
|
10
|
+
import StoryKeepToolMode from '@/components/edit/ToolMode';
|
|
11
|
+
import SettingsPanel from '@/components/edit/SettingsPanel';
|
|
12
|
+
import { Compositor } from '@/components/compositor/Compositor';
|
|
13
|
+
import SandboxAuthWrapper from '@/components/codehooks/SandboxAuthWrapper.tsx';
|
|
14
|
+
import { preHealthCheck } from '@/utils/backend';
|
|
15
|
+
|
|
16
|
+
if (!import.meta.env.PRIVATE_SANDBOX_SECRET) {
|
|
17
|
+
return Astro.redirect('/');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tenantId =
|
|
21
|
+
Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
|
|
22
|
+
|
|
23
|
+
const healthCheckRedirect = await preHealthCheck(tenantId);
|
|
24
|
+
if (healthCheckRedirect !== undefined) {
|
|
25
|
+
return healthCheckRedirect;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const brandConfig = await getBrandConfig(tenantId);
|
|
29
|
+
|
|
30
|
+
const emptyStoryFragment = {
|
|
31
|
+
id: ulid(),
|
|
32
|
+
nodeType: 'StoryFragment' as const,
|
|
33
|
+
parentId: null,
|
|
34
|
+
title: 'Sandbox Page',
|
|
35
|
+
slug: 'sandbox-page',
|
|
36
|
+
paneIds: [],
|
|
37
|
+
isChanged: false,
|
|
38
|
+
created: new Date(),
|
|
39
|
+
changed: new Date(),
|
|
40
|
+
};
|
|
41
|
+
const loadData = {
|
|
42
|
+
storyfragmentNodes: [emptyStoryFragment],
|
|
43
|
+
};
|
|
44
|
+
const title = 'Sandbox - TractStack Editor';
|
|
45
|
+
const storyFragmentID = emptyStoryFragment.id;
|
|
46
|
+
|
|
47
|
+
const fullContentMap = await getFullContentMap(tenantId);
|
|
48
|
+
const urlParams: Record<string, string | boolean> = {};
|
|
49
|
+
for (const [key, value] of Astro.url.searchParams) {
|
|
50
|
+
urlParams[key] = value === '' ? true : value;
|
|
51
|
+
}
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<Layout
|
|
55
|
+
title={title}
|
|
56
|
+
slug="sandbox"
|
|
57
|
+
brandConfig={brandConfig}
|
|
58
|
+
storyfragmentId={storyFragmentID}
|
|
59
|
+
isStoryKeep={true}
|
|
60
|
+
isEditor={true}
|
|
61
|
+
>
|
|
62
|
+
<SandboxAuthWrapper client:load />
|
|
63
|
+
<Header
|
|
64
|
+
title={title}
|
|
65
|
+
slug="sandbox"
|
|
66
|
+
brandConfig={brandConfig}
|
|
67
|
+
isContext={false}
|
|
68
|
+
isStoryKeep={true}
|
|
69
|
+
isEditable={false}
|
|
70
|
+
menu={null}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
<section
|
|
74
|
+
id="storykeepHeader"
|
|
75
|
+
role="banner"
|
|
76
|
+
class="z-101 bg-mywhite left-0 right-0 drop-shadow transition-all duration-200"
|
|
77
|
+
>
|
|
78
|
+
<StoryKeepHeader
|
|
79
|
+
slug="sandbox"
|
|
80
|
+
isContext={false}
|
|
81
|
+
isSandboxMode={true}
|
|
82
|
+
client:only="react"
|
|
83
|
+
/>
|
|
84
|
+
</section>
|
|
85
|
+
|
|
86
|
+
<div class="flex min-h-screen">
|
|
87
|
+
<StoryKeepToolMode isContext={false} client:only="react" />
|
|
88
|
+
|
|
89
|
+
<main id="mainContent" class="relative flex-1 overflow-x-auto">
|
|
90
|
+
<div class="bg-myblue/20 bg-mylightgrey h-full p-1.5">
|
|
91
|
+
<div
|
|
92
|
+
class="h-fit min-h-screen pb-96"
|
|
93
|
+
style={{
|
|
94
|
+
backgroundImage:
|
|
95
|
+
'repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)',
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
<Compositor
|
|
99
|
+
id={storyFragmentID}
|
|
100
|
+
nodes={loadData}
|
|
101
|
+
config={brandConfig}
|
|
102
|
+
fullContentMap={fullContentMap}
|
|
103
|
+
fullCanonicalURL="/sandbox"
|
|
104
|
+
urlParams={urlParams}
|
|
105
|
+
availableCodeHooks={Object.keys(codeHookComponents)}
|
|
106
|
+
isSandboxMode={true}
|
|
107
|
+
client:only="react"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</main>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<aside
|
|
115
|
+
id="settingsControls"
|
|
116
|
+
class="z-101 pointer-events-none fixed bottom-16 right-2 flex flex-col items-end gap-2 md:bottom-2"
|
|
117
|
+
>
|
|
118
|
+
<div class="pointer-events-none flex-grow"></div>
|
|
119
|
+
|
|
120
|
+
<div class="pointer-events-auto flex-shrink-0">
|
|
121
|
+
<StoryKeepToolBar client:only="react" />
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="pointer-events-auto max-h-full">
|
|
125
|
+
<SettingsPanel
|
|
126
|
+
config={brandConfig}
|
|
127
|
+
availableCodeHooks={Object.keys(codeHookComponents)}
|
|
128
|
+
client:only="react"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</aside>
|
|
132
|
+
</Layout>
|
|
133
|
+
|
|
134
|
+
<script>
|
|
135
|
+
import { setupLayoutObservers } from '@/utils/layout';
|
|
136
|
+
document.addEventListener('astro:page-load', setupLayoutObservers);
|
|
137
|
+
</script>
|
|
@@ -148,6 +148,13 @@ export class ProfileStorage {
|
|
|
148
148
|
StorageManager.set(this.STORAGE_KEYS.profileToken, token);
|
|
149
149
|
StorageManager.set(this.STORAGE_KEYS.hasProfile, '1');
|
|
150
150
|
StorageManager.set(this.STORAGE_KEYS.unlockedProfile, '1');
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const maxAge = 60 * 60 * 24;
|
|
154
|
+
document.cookie = `tractstack_profile=true; path=/; SameSite=Lax; max-age=${maxAge}`;
|
|
155
|
+
} catch {
|
|
156
|
+
// Silently fail if cookies are blocked
|
|
157
|
+
}
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
/**
|
|
@@ -157,6 +164,12 @@ export class ProfileStorage {
|
|
|
157
164
|
StorageManager.remove(this.STORAGE_KEYS.profileToken);
|
|
158
165
|
StorageManager.remove(this.STORAGE_KEYS.hasProfile);
|
|
159
166
|
StorageManager.remove(this.STORAGE_KEYS.unlockedProfile);
|
|
167
|
+
try {
|
|
168
|
+
document.cookie =
|
|
169
|
+
'tractstack_profile=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
|
|
170
|
+
} catch {
|
|
171
|
+
// Silently fail
|
|
172
|
+
}
|
|
160
173
|
}
|
|
161
174
|
|
|
162
175
|
/**
|
package/utils/inject-files.ts
CHANGED
|
@@ -139,6 +139,18 @@ export async function injectTemplateFiles(
|
|
|
139
139
|
),
|
|
140
140
|
dest: 'src/components/compositor/nodes/Pane_layout.tsx',
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
src: resolve(
|
|
144
|
+
'../templates/src/components/codehooks/SandboxAuthWrapper.tsx'
|
|
145
|
+
),
|
|
146
|
+
dest: 'src/components/codehooks/SandboxAuthWrapper.tsx',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
src: resolve(
|
|
150
|
+
'../templates/src/components/codehooks/SandboxRegisterForm.tsx'
|
|
151
|
+
),
|
|
152
|
+
dest: 'src/components/codehooks/SandboxRegisterForm.tsx',
|
|
153
|
+
},
|
|
142
154
|
{
|
|
143
155
|
src: resolve('../templates/src/components/compositor/nodes/Markdown.tsx'),
|
|
144
156
|
dest: 'src/components/compositor/nodes/Markdown.tsx',
|
|
@@ -441,14 +453,6 @@ export async function injectTemplateFiles(
|
|
|
441
453
|
),
|
|
442
454
|
dest: 'src/components/edit/pane/RestylePaneModal.tsx',
|
|
443
455
|
},
|
|
444
|
-
{
|
|
445
|
-
src: resolve('../templates/src/components/edit/pane/AiPaneGenerator.tsx'),
|
|
446
|
-
dest: 'src/components/edit/pane/AiPaneGenerator.tsx',
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
src: resolve('../templates/src/components/edit/pane/AiPanePreview.tsx'),
|
|
450
|
-
dest: 'src/components/edit/pane/AiPanePreview.tsx',
|
|
451
|
-
},
|
|
452
456
|
{
|
|
453
457
|
src: resolve(
|
|
454
458
|
'../templates/src/components/edit/pane/steps/CopyInputStep.tsx'
|
|
@@ -603,12 +607,6 @@ export async function injectTemplateFiles(
|
|
|
603
607
|
dest: 'src/stores/selection.ts',
|
|
604
608
|
},
|
|
605
609
|
|
|
606
|
-
// AAI utils
|
|
607
|
-
{
|
|
608
|
-
src: resolve('../templates/src/utils/aai/getTitleSlug.ts'),
|
|
609
|
-
dest: 'src/utils/aai/getTitleSlug.ts',
|
|
610
|
-
},
|
|
611
|
-
|
|
612
610
|
// Compositor utils - etl
|
|
613
611
|
{
|
|
614
612
|
src: resolve('../templates/src/utils/etl/index.ts'),
|
|
@@ -844,6 +842,10 @@ export async function injectTemplateFiles(
|
|
|
844
842
|
),
|
|
845
843
|
dest: 'src/pages/context/[...contextSlug]/edit.astro',
|
|
846
844
|
},
|
|
845
|
+
{
|
|
846
|
+
src: resolve('../templates/src/pages/sandbox.astro'),
|
|
847
|
+
dest: 'src/pages/sandbox.astro',
|
|
848
|
+
},
|
|
847
849
|
{
|
|
848
850
|
src: resolve('../templates/src/pages/storykeep.astro'),
|
|
849
851
|
dest: 'src/pages/storykeep.astro',
|
|
@@ -888,6 +890,10 @@ export async function injectTemplateFiles(
|
|
|
888
890
|
src: resolve('../templates/src/pages/api/tailwind.ts'),
|
|
889
891
|
dest: 'src/pages/api/tailwind.ts',
|
|
890
892
|
},
|
|
893
|
+
{
|
|
894
|
+
src: resolve('../templates/src/pages/api/sandbox.ts'),
|
|
895
|
+
dest: 'src/pages/api/sandbox.ts',
|
|
896
|
+
},
|
|
891
897
|
|
|
892
898
|
// Authentication Pages
|
|
893
899
|
{
|
|
@@ -2156,6 +2162,13 @@ export async function injectTemplateFiles(
|
|
|
2156
2162
|
// Example Components (Conditional)
|
|
2157
2163
|
...(config?.includeExamples
|
|
2158
2164
|
? [
|
|
2165
|
+
{
|
|
2166
|
+
src: resolve(
|
|
2167
|
+
'../templates/custom/with-examples/SandboxLauncher.tsx'
|
|
2168
|
+
),
|
|
2169
|
+
dest: 'src/custom/SandboxLauncher.tsx',
|
|
2170
|
+
protected: true,
|
|
2171
|
+
},
|
|
2159
2172
|
{
|
|
2160
2173
|
src: resolve('../templates/custom/with-examples/CustomHero.astro'),
|
|
2161
2174
|
dest: 'src/custom/CustomHero.astro',
|