astro-tractstack 2.0.44 → 2.0.46
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 -6
- package/package.json +1 -1
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +24 -3
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +230 -0
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +27 -10
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +29 -1
- package/templates/src/pages/sandbox.astro +0 -3
- package/templates/src/stores/nodes.ts +65 -0
- package/templates/src/stores/selection.ts +2 -0
- package/utils/inject-files.ts +6 -6
package/dist/index.js
CHANGED
|
@@ -150,12 +150,6 @@ async function w(t, e, c) {
|
|
|
150
150
|
),
|
|
151
151
|
dest: "src/components/compositor/nodes/Pane_layout.tsx"
|
|
152
152
|
},
|
|
153
|
-
{
|
|
154
|
-
src: t(
|
|
155
|
-
"../templates/src/components/codehooks/SandboxAuthWrapper.tsx"
|
|
156
|
-
),
|
|
157
|
-
dest: "src/components/codehooks/SandboxAuthWrapper.tsx"
|
|
158
|
-
},
|
|
159
153
|
{
|
|
160
154
|
src: t(
|
|
161
155
|
"../templates/src/components/codehooks/SandboxRegisterForm.tsx"
|
|
@@ -476,6 +470,12 @@ async function w(t, e, c) {
|
|
|
476
470
|
),
|
|
477
471
|
dest: "src/components/edit/pane/RestylePaneModal.tsx"
|
|
478
472
|
},
|
|
473
|
+
{
|
|
474
|
+
src: t(
|
|
475
|
+
"../templates/src/components/edit/pane/AiRestylePaneModal.tsx"
|
|
476
|
+
),
|
|
477
|
+
dest: "src/components/edit/pane/AiRestylePaneModal.tsx"
|
|
478
|
+
},
|
|
479
479
|
{
|
|
480
480
|
src: t(
|
|
481
481
|
"../templates/src/components/edit/pane/steps/CopyInputStep.tsx"
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import ArchiveBoxArrowDownIcon from '@heroicons/react/24/outline/ArchiveBoxArrow
|
|
|
4
4
|
import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
|
|
5
5
|
import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon';
|
|
6
6
|
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
7
|
+
import SparklesIcon from '@heroicons/react/24/solid/SparklesIcon';
|
|
7
8
|
import { viewportKeyStore } from '@/stores/storykeep';
|
|
8
9
|
import { getCtx } from '@/stores/nodes';
|
|
9
10
|
import { RenderChildren } from './RenderChildren';
|
|
@@ -12,6 +13,7 @@ import type { NodeProps } from '@/types/nodeProps';
|
|
|
12
13
|
import type { BgImageNode, ArtpackImageNode } from '@/types/compositorTypes';
|
|
13
14
|
import { SaveToLibraryModal } from '@/components/edit/state/SaveToLibraryModal';
|
|
14
15
|
import { RestylePaneModal } from '@/components/edit/pane/RestylePaneModal';
|
|
16
|
+
import { AiRestylePaneModal } from '@/components/edit/pane/AiRestylePaneModal';
|
|
15
17
|
import { selectionStore } from '@/stores/selection';
|
|
16
18
|
import { copyPaneToClipboard } from '@/utils/compositor/designLibraryHelper';
|
|
17
19
|
|
|
@@ -35,9 +37,12 @@ function getSizeClasses(
|
|
|
35
37
|
|
|
36
38
|
export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
37
39
|
const ctx = getCtx(props);
|
|
38
|
-
const { isRestyleModalOpen } = useStore(
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const { isRestyleModalOpen, isAiRestyleModalOpen } = useStore(
|
|
41
|
+
selectionStore,
|
|
42
|
+
{
|
|
43
|
+
keys: ['isRestyleModalOpen', 'isAiRestyleModalOpen'],
|
|
44
|
+
}
|
|
45
|
+
);
|
|
41
46
|
const [currentViewport, setCurrentViewport] = useState(
|
|
42
47
|
viewportKeyStore.get().value
|
|
43
48
|
);
|
|
@@ -81,6 +86,12 @@ export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
|
81
86
|
selectionStore.setKey('isRestyleModalOpen', true);
|
|
82
87
|
};
|
|
83
88
|
|
|
89
|
+
const handleAiRestyleClick = (e: React.MouseEvent) => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
selectionStore.setKey('paneToRestyleId', props.nodeId);
|
|
92
|
+
selectionStore.setKey('isAiRestyleModalOpen', true);
|
|
93
|
+
};
|
|
94
|
+
|
|
84
95
|
const handleSaveClick = (e: React.MouseEvent) => {
|
|
85
96
|
e.stopPropagation();
|
|
86
97
|
setIsSaveModalOpen(true);
|
|
@@ -106,6 +117,13 @@ export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
|
106
117
|
<ArchiveBoxArrowDownIcon className="h-7 w-7 text-white" />
|
|
107
118
|
</button>
|
|
108
119
|
)}
|
|
120
|
+
<button
|
|
121
|
+
title="Re-Color"
|
|
122
|
+
onClick={handleAiRestyleClick}
|
|
123
|
+
className="flex h-10 w-10 items-center justify-center rounded-full bg-purple-600 p-1.5 shadow-lg hover:bg-purple-700"
|
|
124
|
+
>
|
|
125
|
+
<SparklesIcon className="h-5 w-5 text-white" />
|
|
126
|
+
</button>
|
|
109
127
|
<button
|
|
110
128
|
title="Restyle Pane from Design Library"
|
|
111
129
|
onClick={handleRestyleClick}
|
|
@@ -243,6 +261,9 @@ export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
|
243
261
|
)}
|
|
244
262
|
|
|
245
263
|
{isRestyleModalOpen && <RestylePaneModal />}
|
|
264
|
+
{isAiRestyleModalOpen && (
|
|
265
|
+
<AiRestylePaneModal isSandboxMode={props.isSandboxMode} />
|
|
266
|
+
)}
|
|
246
267
|
</div>
|
|
247
268
|
);
|
|
248
269
|
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useStore } from '@nanostores/react';
|
|
4
|
+
import { Dialog } from '@ark-ui/react';
|
|
5
|
+
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
6
|
+
import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
|
|
7
|
+
import { getCtx } from '@/stores/nodes';
|
|
8
|
+
import { selectionStore } from '@/stores/selection';
|
|
9
|
+
import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
|
|
10
|
+
import prompts from '@/constants/prompts.json';
|
|
11
|
+
import { TractStackAPI } from '@/utils/api';
|
|
12
|
+
import { parseAiPane } from '@/utils/compositor/aiPaneParser';
|
|
13
|
+
|
|
14
|
+
const callAskLemurAPI = async (
|
|
15
|
+
prompt: string,
|
|
16
|
+
context: string,
|
|
17
|
+
expectJson: boolean,
|
|
18
|
+
isSandboxMode: boolean
|
|
19
|
+
): Promise<string> => {
|
|
20
|
+
const tenantId =
|
|
21
|
+
(window as any).TRACTSTACK_CONFIG?.tenantId ||
|
|
22
|
+
import.meta.env.PUBLIC_TENANTID ||
|
|
23
|
+
'default';
|
|
24
|
+
const api = new TractStackAPI(tenantId);
|
|
25
|
+
|
|
26
|
+
const requestBody = {
|
|
27
|
+
prompt,
|
|
28
|
+
input_text: context,
|
|
29
|
+
final_model: '',
|
|
30
|
+
temperature: 0.5,
|
|
31
|
+
max_tokens: 2000,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let resultData: any;
|
|
35
|
+
|
|
36
|
+
if (isSandboxMode) {
|
|
37
|
+
const response = await fetch(`/api/sandbox`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'X-Tenant-ID': tenantId,
|
|
42
|
+
},
|
|
43
|
+
credentials: 'include',
|
|
44
|
+
body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorText = await response.text();
|
|
49
|
+
throw new Error(`Sandbox API failed: ${response.status} ${errorText}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const json = await response.json();
|
|
53
|
+
if (!json.success) {
|
|
54
|
+
throw new Error(json.error || 'Sandbox generation failed');
|
|
55
|
+
}
|
|
56
|
+
resultData = json.data;
|
|
57
|
+
} else {
|
|
58
|
+
const response = await api.post('/api/v1/aai/askLemur', requestBody);
|
|
59
|
+
if (!response.success || !response.data?.response) {
|
|
60
|
+
throw new Error(response.error || 'AI generation failed');
|
|
61
|
+
}
|
|
62
|
+
resultData = response.data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let raw = resultData.response;
|
|
66
|
+
if (typeof raw === 'string') {
|
|
67
|
+
if (raw.startsWith('```json')) raw = raw.slice(7, -3).trim();
|
|
68
|
+
}
|
|
69
|
+
if (expectJson && typeof raw === 'object') return JSON.stringify(raw);
|
|
70
|
+
return raw;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
interface AiRestylePaneModalProps {
|
|
74
|
+
isSandboxMode?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const AiRestylePaneModal = ({
|
|
78
|
+
isSandboxMode = false,
|
|
79
|
+
}: AiRestylePaneModalProps) => {
|
|
80
|
+
const ctx = getCtx();
|
|
81
|
+
const { isAiRestyleModalOpen, paneToRestyleId } = useStore(selectionStore);
|
|
82
|
+
|
|
83
|
+
const [loading, setLoading] = useState(false);
|
|
84
|
+
const [error, setError] = useState<string | null>(null);
|
|
85
|
+
const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
|
|
86
|
+
harmony: 'Analogous',
|
|
87
|
+
baseColor: '',
|
|
88
|
+
accentColor: '',
|
|
89
|
+
theme: 'Light',
|
|
90
|
+
additionalNotes: '',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const handleClose = () => {
|
|
94
|
+
if (loading) return;
|
|
95
|
+
selectionStore.setKey('isAiRestyleModalOpen', false);
|
|
96
|
+
selectionStore.setKey('paneToRestyleId', null);
|
|
97
|
+
setError(null);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleGenerate = async () => {
|
|
101
|
+
if (!paneToRestyleId) return;
|
|
102
|
+
setLoading(true);
|
|
103
|
+
setError(null);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const childIds = ctx.getChildNodeIDs(paneToRestyleId);
|
|
107
|
+
const nodesMap = ctx.allNodes.get();
|
|
108
|
+
const gridNode = childIds.find(
|
|
109
|
+
(id) => nodesMap.get(id)?.nodeType === 'GridLayoutNode'
|
|
110
|
+
);
|
|
111
|
+
const isGrid = !!gridNode;
|
|
112
|
+
|
|
113
|
+
const promptConfig = isGrid
|
|
114
|
+
? prompts.aiPromptsIndex.find((p) => p.layout === 'grid')
|
|
115
|
+
: prompts.aiPromptsIndex.find((p) => p.layout === 'standard');
|
|
116
|
+
|
|
117
|
+
if (!promptConfig) throw new Error('No suitable prompt found');
|
|
118
|
+
|
|
119
|
+
let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
120
|
+
if (aiDesignConfig.baseColor)
|
|
121
|
+
designInput += ` Base around **${aiDesignConfig.baseColor}**.`;
|
|
122
|
+
if (aiDesignConfig.accentColor)
|
|
123
|
+
designInput += ` Accent with **${aiDesignConfig.accentColor}**.`;
|
|
124
|
+
if (aiDesignConfig.additionalNotes)
|
|
125
|
+
designInput += ` Notes: "${aiDesignConfig.additionalNotes}"`;
|
|
126
|
+
|
|
127
|
+
const shellPromptKey = promptConfig.prompts.shell as keyof typeof prompts;
|
|
128
|
+
const shellPromptDetails = prompts[shellPromptKey] as any;
|
|
129
|
+
|
|
130
|
+
const formattedPrompt = shellPromptDetails.user_template
|
|
131
|
+
.replace('{{DESIGN_INPUT}}', designInput)
|
|
132
|
+
.replace('{{COPY_INPUT}}', 'A generic content section')
|
|
133
|
+
.replace('{{LAYOUT_TYPE}}', 'Text Only');
|
|
134
|
+
|
|
135
|
+
const resultStr = await callAskLemurAPI(
|
|
136
|
+
formattedPrompt,
|
|
137
|
+
shellPromptDetails.system || '',
|
|
138
|
+
true,
|
|
139
|
+
isSandboxMode
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
let dummyCopy: string | string[] = '';
|
|
143
|
+
if (isGrid) {
|
|
144
|
+
dummyCopy = ['', ''];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const hydratedTemplate = parseAiPane(resultStr, dummyCopy, 'Text Only');
|
|
148
|
+
|
|
149
|
+
ctx.applyShellToPane(paneToRestyleId, hydratedTemplate);
|
|
150
|
+
handleClose();
|
|
151
|
+
} catch (err: any) {
|
|
152
|
+
console.error(err);
|
|
153
|
+
setError(err.message || 'Failed to restyle pane');
|
|
154
|
+
} finally {
|
|
155
|
+
setLoading(false);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (!isAiRestyleModalOpen) return null;
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<Dialog.Root
|
|
163
|
+
open={isAiRestyleModalOpen}
|
|
164
|
+
onOpenChange={(e) => !e.open && handleClose()}
|
|
165
|
+
>
|
|
166
|
+
<Dialog.Backdrop className="z-103 fixed inset-0 bg-black bg-opacity-75" />
|
|
167
|
+
<Dialog.Positioner className="z-104 fixed inset-0 flex items-center justify-center p-4">
|
|
168
|
+
<Dialog.Content className="flex max-w-2xl flex-col rounded-lg bg-white shadow-2xl">
|
|
169
|
+
<div className="flex items-center justify-between border-b p-4">
|
|
170
|
+
<h3 className="flex items-center gap-2 text-lg font-bold">
|
|
171
|
+
<SparklesIcon className="h-5 w-5 text-purple-600" />
|
|
172
|
+
Re-Color Pane
|
|
173
|
+
</h3>
|
|
174
|
+
<button
|
|
175
|
+
onClick={handleClose}
|
|
176
|
+
disabled={loading}
|
|
177
|
+
className="rounded-full p-1 text-gray-600 hover:bg-gray-100 disabled:opacity-50"
|
|
178
|
+
>
|
|
179
|
+
<XMarkIcon className="h-6 w-6" />
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<form className="p-6" onSubmit={(e) => e.preventDefault()}>
|
|
184
|
+
<div className={loading ? 'pointer-events-none opacity-50' : ''}>
|
|
185
|
+
<AiDesignStep
|
|
186
|
+
designConfig={aiDesignConfig}
|
|
187
|
+
onDesignConfigChange={setAiDesignConfig}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{error && (
|
|
192
|
+
<div className="mt-4 rounded border border-red-200 bg-red-50 p-3 text-sm text-red-700">
|
|
193
|
+
{error}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
<div className="mt-8 flex justify-end gap-3">
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
onClick={handleClose}
|
|
201
|
+
disabled={loading}
|
|
202
|
+
className="rounded-lg px-4 py-2 font-bold text-gray-600 hover:bg-gray-100 disabled:opacity-50"
|
|
203
|
+
>
|
|
204
|
+
Cancel
|
|
205
|
+
</button>
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
onClick={handleGenerate}
|
|
209
|
+
disabled={loading}
|
|
210
|
+
className="flex items-center gap-2 rounded-lg bg-gradient-to-r from-purple-600 to-indigo-600 px-6 py-2 font-bold text-white shadow transition-all hover:from-purple-500 hover:to-indigo-500 hover:shadow-lg disabled:cursor-not-allowed disabled:opacity-75"
|
|
211
|
+
>
|
|
212
|
+
{loading ? (
|
|
213
|
+
<>
|
|
214
|
+
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
|
215
|
+
Generating...
|
|
216
|
+
</>
|
|
217
|
+
) : (
|
|
218
|
+
<>
|
|
219
|
+
<SparklesIcon className="h-5 w-5" />
|
|
220
|
+
Apply Re-Color
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
</form>
|
|
226
|
+
</Dialog.Content>
|
|
227
|
+
</Dialog.Positioner>
|
|
228
|
+
</Dialog.Root>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
2
|
+
import { findClosestTailwindColor } from '@/utils/compositor/tailwindColors';
|
|
2
3
|
|
|
3
4
|
export interface AiDesignConfig {
|
|
4
5
|
harmony: string;
|
|
@@ -11,6 +12,7 @@ export interface AiDesignConfig {
|
|
|
11
12
|
interface AiDesignStepProps {
|
|
12
13
|
designConfig: AiDesignConfig;
|
|
13
14
|
onDesignConfigChange: (newConfig: AiDesignConfig) => void;
|
|
15
|
+
idPrefix?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const harmonyOptions = [
|
|
@@ -24,6 +26,7 @@ const themeOptions = ['Light', 'Dark', 'Bright', 'Muted', 'Pastel', 'Earthy'];
|
|
|
24
26
|
export const AiDesignStep = ({
|
|
25
27
|
designConfig,
|
|
26
28
|
onDesignConfigChange,
|
|
29
|
+
idPrefix = '',
|
|
27
30
|
}: AiDesignStepProps) => {
|
|
28
31
|
const updateField = <K extends keyof AiDesignConfig>(
|
|
29
32
|
field: K,
|
|
@@ -32,6 +35,20 @@ export const AiDesignStep = ({
|
|
|
32
35
|
onDesignConfigChange({ ...designConfig, [field]: value });
|
|
33
36
|
};
|
|
34
37
|
|
|
38
|
+
const handleColorChange = (
|
|
39
|
+
field: 'baseColor' | 'accentColor',
|
|
40
|
+
color: string
|
|
41
|
+
) => {
|
|
42
|
+
if (!color) {
|
|
43
|
+
updateField(field, '');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const closest = findClosestTailwindColor(color);
|
|
47
|
+
if (closest) {
|
|
48
|
+
updateField(field, `${closest.name}-${closest.shade}`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
35
52
|
return (
|
|
36
53
|
<div className="space-y-6">
|
|
37
54
|
<div>
|
|
@@ -43,15 +60,15 @@ export const AiDesignStep = ({
|
|
|
43
60
|
<div key={option} className="flex items-center space-x-2">
|
|
44
61
|
<input
|
|
45
62
|
type="radio"
|
|
46
|
-
id={
|
|
47
|
-
name=
|
|
63
|
+
id={`${idPrefix}harmony-${option}`}
|
|
64
|
+
name={`${idPrefix}harmonyOptions`}
|
|
48
65
|
value={option}
|
|
49
66
|
checked={designConfig.harmony === option}
|
|
50
67
|
onChange={(e) => updateField('harmony', e.target.value)}
|
|
51
68
|
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
52
69
|
/>
|
|
53
70
|
<label
|
|
54
|
-
htmlFor={
|
|
71
|
+
htmlFor={`${idPrefix}harmony-${option}`}
|
|
55
72
|
className="text-sm font-bold text-gray-700"
|
|
56
73
|
>
|
|
57
74
|
{option}
|
|
@@ -66,7 +83,7 @@ export const AiDesignStep = ({
|
|
|
66
83
|
<ColorPickerCombo
|
|
67
84
|
title="Base Color (Optional)"
|
|
68
85
|
defaultColor={designConfig.baseColor}
|
|
69
|
-
onColorChange={(color) =>
|
|
86
|
+
onColorChange={(color) => handleColorChange('baseColor', color)}
|
|
70
87
|
allowNull={true}
|
|
71
88
|
/>
|
|
72
89
|
</div>
|
|
@@ -74,7 +91,7 @@ export const AiDesignStep = ({
|
|
|
74
91
|
<ColorPickerCombo
|
|
75
92
|
title="Accent Color (Optional)"
|
|
76
93
|
defaultColor={designConfig.accentColor}
|
|
77
|
-
onColorChange={(color) =>
|
|
94
|
+
onColorChange={(color) => handleColorChange('accentColor', color)}
|
|
78
95
|
allowNull={true}
|
|
79
96
|
/>
|
|
80
97
|
</div>
|
|
@@ -89,15 +106,15 @@ export const AiDesignStep = ({
|
|
|
89
106
|
<div key={option} className="flex items-center space-x-2">
|
|
90
107
|
<input
|
|
91
108
|
type="radio"
|
|
92
|
-
id={
|
|
93
|
-
name=
|
|
109
|
+
id={`${idPrefix}theme-${option}`}
|
|
110
|
+
name={`${idPrefix}themeOptions`}
|
|
94
111
|
value={option}
|
|
95
112
|
checked={designConfig.theme === option}
|
|
96
113
|
onChange={(e) => updateField('theme', e.target.value)}
|
|
97
114
|
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
98
115
|
/>
|
|
99
116
|
<label
|
|
100
|
-
htmlFor={
|
|
117
|
+
htmlFor={`${idPrefix}theme-${option}`}
|
|
101
118
|
className="text-sm font-bold text-gray-700"
|
|
102
119
|
>
|
|
103
120
|
{option}
|
|
@@ -109,7 +126,7 @@ export const AiDesignStep = ({
|
|
|
109
126
|
|
|
110
127
|
<div>
|
|
111
128
|
<label
|
|
112
|
-
htmlFor=
|
|
129
|
+
htmlFor={`${idPrefix}additional-notes`}
|
|
113
130
|
className="block text-base font-bold text-gray-800"
|
|
114
131
|
>
|
|
115
132
|
Additional Design Notes (Optional)
|
|
@@ -119,7 +136,7 @@ export const AiDesignStep = ({
|
|
|
119
136
|
texture".
|
|
120
137
|
</p>
|
|
121
138
|
<textarea
|
|
122
|
-
id=
|
|
139
|
+
id={`${idPrefix}additional-notes`}
|
|
123
140
|
value={designConfig.additionalNotes}
|
|
124
141
|
onChange={(e) => updateField('additionalNotes', e.target.value)}
|
|
125
142
|
placeholder="Enter additional notes..."
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useStore } from '@nanostores/react';
|
|
3
|
+
import { Portal } from '@ark-ui/react';
|
|
4
|
+
import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
|
|
2
5
|
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
|
|
3
6
|
import ArrowUturnLeftIcon from '@heroicons/react/24/outline/ArrowUturnLeftIcon';
|
|
4
7
|
import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon';
|
|
5
8
|
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon';
|
|
9
|
+
import { selectionStore } from '@/stores/selection';
|
|
6
10
|
import {
|
|
7
11
|
settingsPanelStore,
|
|
8
12
|
stylePanelTargetMemoryStore,
|
|
@@ -18,6 +22,7 @@ import {
|
|
|
18
22
|
import SelectedTailwindClass from '@/components/fields/SelectedTailwindClass';
|
|
19
23
|
import BackgroundImageWrapper from '@/components/fields/BackgroundImageWrapper';
|
|
20
24
|
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
25
|
+
import { AiRestylePaneModal } from '@/components/edit/pane/AiRestylePaneModal';
|
|
21
26
|
import { cloneDeep } from '@/utils/helpers';
|
|
22
27
|
import {
|
|
23
28
|
convertToGrid,
|
|
@@ -60,6 +65,7 @@ const StyleParentPanel = ({
|
|
|
60
65
|
const [selectedTargetIndex, setSelectedTargetIndex] = useState(0);
|
|
61
66
|
|
|
62
67
|
const ctx = getCtx();
|
|
68
|
+
const { isAiRestyleModalOpen } = useStore(selectionStore);
|
|
63
69
|
|
|
64
70
|
useEffect(() => {
|
|
65
71
|
if (
|
|
@@ -518,6 +524,19 @@ const StyleParentPanel = ({
|
|
|
518
524
|
})}
|
|
519
525
|
</div>
|
|
520
526
|
</div>
|
|
527
|
+
<div className="space-y-3 border-t border-gray-200 pt-4">
|
|
528
|
+
<button
|
|
529
|
+
onClick={() => {
|
|
530
|
+
ctx.toolModeValStore.set({ value: 'styles' });
|
|
531
|
+
selectionStore.setKey('paneToRestyleId', paneNode.id);
|
|
532
|
+
selectionStore.setKey('isAiRestyleModalOpen', true);
|
|
533
|
+
}}
|
|
534
|
+
className="flex w-full items-center justify-center gap-2 rounded bg-purple-600 px-4 py-2 text-sm font-bold text-white hover:bg-purple-700"
|
|
535
|
+
>
|
|
536
|
+
<SparklesIcon className="h-5 w-5" />
|
|
537
|
+
Re-Color this Pane
|
|
538
|
+
</button>
|
|
539
|
+
</div>
|
|
521
540
|
</div>
|
|
522
541
|
);
|
|
523
542
|
};
|
|
@@ -645,7 +664,16 @@ const StyleParentPanel = ({
|
|
|
645
664
|
}
|
|
646
665
|
};
|
|
647
666
|
|
|
648
|
-
return
|
|
667
|
+
return (
|
|
668
|
+
<div className="space-y-4">
|
|
669
|
+
{renderContent()}
|
|
670
|
+
{isAiRestyleModalOpen && (
|
|
671
|
+
<Portal>
|
|
672
|
+
<AiRestylePaneModal />
|
|
673
|
+
</Portal>
|
|
674
|
+
)}
|
|
675
|
+
</div>
|
|
676
|
+
);
|
|
649
677
|
};
|
|
650
678
|
|
|
651
679
|
export default StyleParentPanel;
|
|
@@ -12,7 +12,6 @@ import StoryKeepToolBar from '@/components/edit/ToolBar';
|
|
|
12
12
|
import StoryKeepToolMode from '@/components/edit/ToolMode';
|
|
13
13
|
import SettingsPanel from '@/components/edit/SettingsPanel';
|
|
14
14
|
import { Compositor } from '@/components/compositor/Compositor';
|
|
15
|
-
import SandboxAuthWrapper from '@/components/codehooks/SandboxAuthWrapper.tsx';
|
|
16
15
|
import { preHealthCheck } from '@/utils/backend';
|
|
17
16
|
|
|
18
17
|
if (!import.meta.env.PRIVATE_SANDBOX_SECRET) {
|
|
@@ -63,8 +62,6 @@ const hasProfile = Astro.request.headers
|
|
|
63
62
|
isStoryKeep={true}
|
|
64
63
|
isEditor={true}
|
|
65
64
|
>
|
|
66
|
-
<SandboxAuthWrapper client:load isServerSideAuthenticated={!!hasProfile} />
|
|
67
|
-
|
|
68
65
|
<CodeHook
|
|
69
66
|
target="get-tractstack"
|
|
70
67
|
options={{
|
|
@@ -560,6 +560,71 @@ export class NodesContext {
|
|
|
560
560
|
);
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
+
applyShellToPane(paneId: string, template: TemplatePane) {
|
|
564
|
+
const allNodes = new Map(this.allNodes.get());
|
|
565
|
+
const paneNode = allNodes.get(paneId) as PaneNode;
|
|
566
|
+
if (!paneNode) return;
|
|
567
|
+
|
|
568
|
+
if (template.bgColour) {
|
|
569
|
+
paneNode.bgColour = template.bgColour;
|
|
570
|
+
paneNode.isChanged = true;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const childrenIds = this.getChildNodeIDs(paneId);
|
|
574
|
+
|
|
575
|
+
const gridLayoutNode = childrenIds
|
|
576
|
+
.map((id) => allNodes.get(id))
|
|
577
|
+
.find((n) => n?.nodeType === 'GridLayoutNode') as
|
|
578
|
+
| GridLayoutNode
|
|
579
|
+
| undefined;
|
|
580
|
+
|
|
581
|
+
const markdownNodes = childrenIds
|
|
582
|
+
.map((id) => allNodes.get(id))
|
|
583
|
+
.filter((n) => n?.nodeType === 'Markdown') as MarkdownPaneFragmentNode[];
|
|
584
|
+
|
|
585
|
+
if (gridLayoutNode && template.gridLayout) {
|
|
586
|
+
if (template.gridLayout.parentClasses) {
|
|
587
|
+
gridLayoutNode.parentClasses = template.gridLayout.parentClasses;
|
|
588
|
+
}
|
|
589
|
+
if (template.gridLayout.defaultClasses) {
|
|
590
|
+
gridLayoutNode.defaultClasses = template.gridLayout.defaultClasses;
|
|
591
|
+
}
|
|
592
|
+
gridLayoutNode.isChanged = true;
|
|
593
|
+
|
|
594
|
+
if (
|
|
595
|
+
template.gridLayout.nodes &&
|
|
596
|
+
Array.isArray(template.gridLayout.nodes)
|
|
597
|
+
) {
|
|
598
|
+
const columnIds = this.getChildNodeIDs(gridLayoutNode.id);
|
|
599
|
+
|
|
600
|
+
columnIds.forEach((colId, index) => {
|
|
601
|
+
const templateCol = template.gridLayout!.nodes![index];
|
|
602
|
+
if (templateCol && templateCol.gridClasses) {
|
|
603
|
+
const liveColNode = allNodes.get(colId) as MarkdownPaneFragmentNode;
|
|
604
|
+
if (liveColNode) {
|
|
605
|
+
liveColNode.gridClasses = templateCol.gridClasses;
|
|
606
|
+
liveColNode.isChanged = true;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
} else if (markdownNodes.length > 0 && template.markdown) {
|
|
612
|
+
const primaryMarkdown = markdownNodes[0];
|
|
613
|
+
|
|
614
|
+
if (template.markdown.parentClasses) {
|
|
615
|
+
primaryMarkdown.parentClasses = template.markdown.parentClasses;
|
|
616
|
+
}
|
|
617
|
+
if (template.markdown.defaultClasses) {
|
|
618
|
+
primaryMarkdown.defaultClasses = template.markdown.defaultClasses;
|
|
619
|
+
}
|
|
620
|
+
primaryMarkdown.isChanged = true;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.allNodes.set(allNodes);
|
|
624
|
+
this.notifyNode(paneId);
|
|
625
|
+
this.notifyNode('root');
|
|
626
|
+
}
|
|
627
|
+
|
|
563
628
|
/**
|
|
564
629
|
* Splits a text node at a given character offset.
|
|
565
630
|
* This is a robust function that correctly handles splits at offset 0
|
|
@@ -20,6 +20,7 @@ export interface SelectionStoreState extends SelectionRange {
|
|
|
20
20
|
selectionBox: SelectionBox | null;
|
|
21
21
|
pendingAction: 'style' | 'link' | 'carousel' | null;
|
|
22
22
|
isRestyleModalOpen: boolean;
|
|
23
|
+
isAiRestyleModalOpen: boolean;
|
|
23
24
|
paneToRestyleId: string | null;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -35,6 +36,7 @@ const DEFAULT_SELECTION_STATE: SelectionStoreState = {
|
|
|
35
36
|
selectionBox: null,
|
|
36
37
|
pendingAction: null,
|
|
37
38
|
isRestyleModalOpen: false,
|
|
39
|
+
isAiRestyleModalOpen: false,
|
|
38
40
|
paneToRestyleId: null,
|
|
39
41
|
};
|
|
40
42
|
|
package/utils/inject-files.ts
CHANGED
|
@@ -151,12 +151,6 @@ export async function injectTemplateFiles(
|
|
|
151
151
|
),
|
|
152
152
|
dest: 'src/components/compositor/nodes/Pane_layout.tsx',
|
|
153
153
|
},
|
|
154
|
-
{
|
|
155
|
-
src: resolve(
|
|
156
|
-
'../templates/src/components/codehooks/SandboxAuthWrapper.tsx'
|
|
157
|
-
),
|
|
158
|
-
dest: 'src/components/codehooks/SandboxAuthWrapper.tsx',
|
|
159
|
-
},
|
|
160
154
|
{
|
|
161
155
|
src: resolve(
|
|
162
156
|
'../templates/src/components/codehooks/SandboxRegisterForm.tsx'
|
|
@@ -477,6 +471,12 @@ export async function injectTemplateFiles(
|
|
|
477
471
|
),
|
|
478
472
|
dest: 'src/components/edit/pane/RestylePaneModal.tsx',
|
|
479
473
|
},
|
|
474
|
+
{
|
|
475
|
+
src: resolve(
|
|
476
|
+
'../templates/src/components/edit/pane/AiRestylePaneModal.tsx'
|
|
477
|
+
),
|
|
478
|
+
dest: 'src/components/edit/pane/AiRestylePaneModal.tsx',
|
|
479
|
+
},
|
|
480
480
|
{
|
|
481
481
|
src: resolve(
|
|
482
482
|
'../templates/src/components/edit/pane/steps/CopyInputStep.tsx'
|