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.
- package/dist/index.js +41 -9
- 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 +27 -9
- 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 +463 -561
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +140 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +105 -0
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +395 -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/prompts.json +3 -1
- 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 +32 -84
- package/templates/src/utils/compositor/designLibraryHelper.ts +87 -2
- package/templates/src/utils/profileStorage.ts +13 -0
- package/utils/inject-files.ts +41 -10
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +0 -575
- package/templates/src/components/edit/pane/AiPanePreview.tsx +0 -107
- package/templates/src/components/edit/pane/PageGen.tsx +0 -485
- package/templates/src/components/edit/pane/PageGenSelector.tsx +0 -245
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +0 -339
- package/templates/src/utils/aai/getTitleSlug.ts +0 -72
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',
|
|
@@ -442,12 +454,22 @@ export async function injectTemplateFiles(
|
|
|
442
454
|
dest: 'src/components/edit/pane/RestylePaneModal.tsx',
|
|
443
455
|
},
|
|
444
456
|
{
|
|
445
|
-
src: resolve(
|
|
446
|
-
|
|
457
|
+
src: resolve(
|
|
458
|
+
'../templates/src/components/edit/pane/steps/CopyInputStep.tsx'
|
|
459
|
+
),
|
|
460
|
+
dest: 'src/components/edit/pane/steps/CopyInputStep.tsx',
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
src: resolve(
|
|
464
|
+
'../templates/src/components/edit/pane/steps/DesignLibraryStep.tsx'
|
|
465
|
+
),
|
|
466
|
+
dest: 'src/components/edit/pane/steps/DesignLibraryStep.tsx',
|
|
447
467
|
},
|
|
448
468
|
{
|
|
449
|
-
src: resolve(
|
|
450
|
-
|
|
469
|
+
src: resolve(
|
|
470
|
+
'../templates/src/components/edit/pane/steps/AiDesignStep.tsx'
|
|
471
|
+
),
|
|
472
|
+
dest: 'src/components/edit/pane/steps/AiDesignStep.tsx',
|
|
451
473
|
},
|
|
452
474
|
{
|
|
453
475
|
src: resolve(
|
|
@@ -585,12 +607,6 @@ export async function injectTemplateFiles(
|
|
|
585
607
|
dest: 'src/stores/selection.ts',
|
|
586
608
|
},
|
|
587
609
|
|
|
588
|
-
// AAI utils
|
|
589
|
-
{
|
|
590
|
-
src: resolve('../templates/src/utils/aai/getTitleSlug.ts'),
|
|
591
|
-
dest: 'src/utils/aai/getTitleSlug.ts',
|
|
592
|
-
},
|
|
593
|
-
|
|
594
610
|
// Compositor utils - etl
|
|
595
611
|
{
|
|
596
612
|
src: resolve('../templates/src/utils/etl/index.ts'),
|
|
@@ -826,6 +842,10 @@ export async function injectTemplateFiles(
|
|
|
826
842
|
),
|
|
827
843
|
dest: 'src/pages/context/[...contextSlug]/edit.astro',
|
|
828
844
|
},
|
|
845
|
+
{
|
|
846
|
+
src: resolve('../templates/src/pages/sandbox.astro'),
|
|
847
|
+
dest: 'src/pages/sandbox.astro',
|
|
848
|
+
},
|
|
829
849
|
{
|
|
830
850
|
src: resolve('../templates/src/pages/storykeep.astro'),
|
|
831
851
|
dest: 'src/pages/storykeep.astro',
|
|
@@ -870,6 +890,10 @@ export async function injectTemplateFiles(
|
|
|
870
890
|
src: resolve('../templates/src/pages/api/tailwind.ts'),
|
|
871
891
|
dest: 'src/pages/api/tailwind.ts',
|
|
872
892
|
},
|
|
893
|
+
{
|
|
894
|
+
src: resolve('../templates/src/pages/api/sandbox.ts'),
|
|
895
|
+
dest: 'src/pages/api/sandbox.ts',
|
|
896
|
+
},
|
|
873
897
|
|
|
874
898
|
// Authentication Pages
|
|
875
899
|
{
|
|
@@ -2138,6 +2162,13 @@ export async function injectTemplateFiles(
|
|
|
2138
2162
|
// Example Components (Conditional)
|
|
2139
2163
|
...(config?.includeExamples
|
|
2140
2164
|
? [
|
|
2165
|
+
{
|
|
2166
|
+
src: resolve(
|
|
2167
|
+
'../templates/custom/with-examples/SandboxLauncher.tsx'
|
|
2168
|
+
),
|
|
2169
|
+
dest: 'src/custom/SandboxLauncher.tsx',
|
|
2170
|
+
protected: true,
|
|
2171
|
+
},
|
|
2141
2172
|
{
|
|
2142
2173
|
src: resolve('../templates/custom/with-examples/CustomHero.astro'),
|
|
2143
2174
|
dest: 'src/custom/CustomHero.astro',
|
|
@@ -1,575 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import { AiPanePreview } from './AiPanePreview';
|
|
3
|
-
import type { TemplatePane } from '@/types/compositorTypes';
|
|
4
|
-
import prompts from '@/constants/prompts.json';
|
|
5
|
-
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
6
|
-
import { parseAiPane } from '@/utils/compositor/aiPaneParser';
|
|
7
|
-
import { classNames } from '@/utils/helpers';
|
|
8
|
-
import type { BrandConfig } from '@/types/tractstack';
|
|
9
|
-
|
|
10
|
-
interface AiPaneGeneratorProps {
|
|
11
|
-
ownerId: string;
|
|
12
|
-
onComplete: (pane: TemplatePane) => void;
|
|
13
|
-
onCancel: () => void;
|
|
14
|
-
config?: BrandConfig;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type GenerationStep = 'input' | 'preview' | 'loading';
|
|
18
|
-
type CopyMode = 'prompt' | 'raw';
|
|
19
|
-
|
|
20
|
-
interface GenerationResponse {
|
|
21
|
-
success: boolean;
|
|
22
|
-
data?: {
|
|
23
|
-
response: string | object;
|
|
24
|
-
};
|
|
25
|
-
error?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const harmonyOptions = [
|
|
29
|
-
'Analogous',
|
|
30
|
-
'Monochromatic',
|
|
31
|
-
'Complementary',
|
|
32
|
-
'Triadic',
|
|
33
|
-
];
|
|
34
|
-
const themeOptions = ['Light', 'Dark', 'Bright', 'Muted', 'Pastel', 'Earthy'];
|
|
35
|
-
|
|
36
|
-
export function AiPaneGenerator({
|
|
37
|
-
ownerId,
|
|
38
|
-
onComplete,
|
|
39
|
-
onCancel,
|
|
40
|
-
config,
|
|
41
|
-
}: AiPaneGeneratorProps) {
|
|
42
|
-
const [currentStep, setCurrentStep] = useState<GenerationStep>('input');
|
|
43
|
-
const [selectedLayout] = useState<string>('Text Only');
|
|
44
|
-
const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
|
|
45
|
-
const [copyPrompt, setCopyPrompt] = useState('');
|
|
46
|
-
const [rawCopy, setRawCopy] = useState('');
|
|
47
|
-
const [generatedShell, setGeneratedShell] = useState<string | null>(null);
|
|
48
|
-
const [generatedCopy, setGeneratedCopy] = useState<string | null>(null);
|
|
49
|
-
const [error, setError] = useState<string | null>(null);
|
|
50
|
-
|
|
51
|
-
const [selectedHarmony, setSelectedHarmony] = useState<string>(
|
|
52
|
-
harmonyOptions[0]
|
|
53
|
-
);
|
|
54
|
-
const [baseColor, setBaseColor] = useState<string>('');
|
|
55
|
-
const [accentColor, setAccentColor] = useState<string>('');
|
|
56
|
-
const [selectedTheme, setSelectedTheme] = useState<string>(themeOptions[0]);
|
|
57
|
-
const [additionalNotes, setAdditionalNotes] = useState<string>('');
|
|
58
|
-
|
|
59
|
-
const [isInjectMode, setIsInjectMode] = useState(false);
|
|
60
|
-
const [injectShell, setInjectShell] = useState('');
|
|
61
|
-
const [injectCopy, setInjectCopy] = useState('');
|
|
62
|
-
|
|
63
|
-
const callAskLemurAPI = useCallback(
|
|
64
|
-
async (
|
|
65
|
-
prompt: string,
|
|
66
|
-
context: string,
|
|
67
|
-
expectJson: boolean
|
|
68
|
-
): Promise<string> => {
|
|
69
|
-
const goBackend =
|
|
70
|
-
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
71
|
-
const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
|
|
72
|
-
|
|
73
|
-
const requestBody = {
|
|
74
|
-
prompt: prompt,
|
|
75
|
-
input_text: context,
|
|
76
|
-
final_model: '',
|
|
77
|
-
temperature: 0.5,
|
|
78
|
-
max_tokens: 2000,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const response = await fetch(`${goBackend}/api/v1/aai/askLemur`, {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
headers: {
|
|
84
|
-
'Content-Type': 'application/json',
|
|
85
|
-
'X-Tenant-ID': tenantId,
|
|
86
|
-
},
|
|
87
|
-
credentials: 'include',
|
|
88
|
-
body: JSON.stringify(requestBody),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
const errorText = await response.text();
|
|
93
|
-
console.error('AskLemur API Error Response:', errorText);
|
|
94
|
-
let backendError = `API call failed: ${response.status} ${response.statusText}`;
|
|
95
|
-
try {
|
|
96
|
-
const errorJson = JSON.parse(errorText);
|
|
97
|
-
if (errorJson && errorJson.error) {
|
|
98
|
-
backendError = errorJson.error;
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {
|
|
101
|
-
/* Ignore */
|
|
102
|
-
}
|
|
103
|
-
throw new Error(backendError);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const result = (await response.json()) as GenerationResponse;
|
|
107
|
-
|
|
108
|
-
if (!result.success || !result.data?.response) {
|
|
109
|
-
throw new Error(
|
|
110
|
-
result.error || 'Generation failed to return valid response.'
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let rawResponseData = result.data.response;
|
|
115
|
-
|
|
116
|
-
if (expectJson && typeof rawResponseData === 'object') {
|
|
117
|
-
return JSON.stringify(rawResponseData);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (typeof rawResponseData === 'string') {
|
|
121
|
-
let responseString = rawResponseData;
|
|
122
|
-
try {
|
|
123
|
-
if (
|
|
124
|
-
responseString.startsWith('```json') &&
|
|
125
|
-
responseString.endsWith('```')
|
|
126
|
-
) {
|
|
127
|
-
responseString = responseString.slice(7, -3).trim();
|
|
128
|
-
} else if (
|
|
129
|
-
responseString.startsWith('```html') &&
|
|
130
|
-
responseString.endsWith('```')
|
|
131
|
-
) {
|
|
132
|
-
responseString = responseString.slice(7, -3).trim();
|
|
133
|
-
}
|
|
134
|
-
} catch (e) {
|
|
135
|
-
/* Ignore stripping errors */
|
|
136
|
-
}
|
|
137
|
-
return responseString;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
throw new Error('Unexpected response format received from API.');
|
|
141
|
-
},
|
|
142
|
-
[]
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const handleGenerate = useCallback(async () => {
|
|
146
|
-
setError(null);
|
|
147
|
-
setCurrentStep('loading');
|
|
148
|
-
setGeneratedShell(null);
|
|
149
|
-
setGeneratedCopy(null);
|
|
150
|
-
|
|
151
|
-
let designInput = `Generate a design using a **${selectedHarmony.toLowerCase()}** color scheme with a **${selectedTheme.toLowerCase()}** theme.`;
|
|
152
|
-
if (baseColor) {
|
|
153
|
-
designInput += ` Base the colors around **${baseColor}**.`;
|
|
154
|
-
}
|
|
155
|
-
if (accentColor) {
|
|
156
|
-
designInput += ` Use **${accentColor}** as an accent color.`;
|
|
157
|
-
}
|
|
158
|
-
if (additionalNotes) {
|
|
159
|
-
designInput += ` Refine the design with these additional notes: "${additionalNotes}"`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const shellPromptDetails = prompts.aiPaneShellPrompt;
|
|
164
|
-
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
!shellPromptDetails?.user_template ||
|
|
168
|
-
!copyPromptDetails?.user_template
|
|
169
|
-
) {
|
|
170
|
-
throw new Error('AI prompts not found or incomplete in prompts.json');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const formattedShellPrompt = shellPromptDetails.user_template
|
|
174
|
-
.replace('{{DESIGN_INPUT}}', designInput)
|
|
175
|
-
.replace('{{LAYOUT_TYPE}}', selectedLayout);
|
|
176
|
-
|
|
177
|
-
const shellResult = await callAskLemurAPI(
|
|
178
|
-
formattedShellPrompt,
|
|
179
|
-
shellPromptDetails.system || '',
|
|
180
|
-
true
|
|
181
|
-
);
|
|
182
|
-
setGeneratedShell(shellResult);
|
|
183
|
-
|
|
184
|
-
const copyInputContent = copyMode === 'prompt' ? copyPrompt : rawCopy;
|
|
185
|
-
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
186
|
-
.replace('{{COPY_INPUT}}', copyInputContent)
|
|
187
|
-
.replace('{{DESIGN_INPUT}}', designInput)
|
|
188
|
-
.replace('{{LAYOUT_TYPE}}', selectedLayout)
|
|
189
|
-
.replace('{{SHELL_JSON}}', shellResult);
|
|
190
|
-
|
|
191
|
-
const copyResult = await callAskLemurAPI(
|
|
192
|
-
formattedCopyPrompt,
|
|
193
|
-
copyPromptDetails.system || '',
|
|
194
|
-
false
|
|
195
|
-
);
|
|
196
|
-
setGeneratedCopy(copyResult);
|
|
197
|
-
|
|
198
|
-
setCurrentStep('preview');
|
|
199
|
-
} catch (err: any) {
|
|
200
|
-
console.error('AI Pane Generation Error:', err);
|
|
201
|
-
setError(err.message || 'Failed to generate AI pane.');
|
|
202
|
-
setCurrentStep('input');
|
|
203
|
-
}
|
|
204
|
-
}, [
|
|
205
|
-
selectedHarmony,
|
|
206
|
-
baseColor,
|
|
207
|
-
accentColor,
|
|
208
|
-
selectedTheme,
|
|
209
|
-
additionalNotes,
|
|
210
|
-
selectedLayout,
|
|
211
|
-
copyMode,
|
|
212
|
-
copyPrompt,
|
|
213
|
-
rawCopy,
|
|
214
|
-
callAskLemurAPI,
|
|
215
|
-
]);
|
|
216
|
-
|
|
217
|
-
const handleInject = useCallback(() => {
|
|
218
|
-
setError(null);
|
|
219
|
-
if (!injectShell || !injectCopy) {
|
|
220
|
-
setError('Both Shell JSON and Copy HTML must be provided.');
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
const shellResponse = JSON.parse(injectShell);
|
|
225
|
-
const copyResponse = JSON.parse(injectCopy);
|
|
226
|
-
|
|
227
|
-
const shellPayloadString = JSON.stringify(shellResponse?.data?.response);
|
|
228
|
-
const copyPayloadString = copyResponse?.data?.response;
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
!shellPayloadString ||
|
|
232
|
-
shellPayloadString === 'null' ||
|
|
233
|
-
typeof copyPayloadString !== 'string'
|
|
234
|
-
) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
'Payloads are in an unexpected format. Could not find "data.response".'
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const pane = parseAiPane(
|
|
241
|
-
shellPayloadString,
|
|
242
|
-
copyPayloadString,
|
|
243
|
-
selectedLayout
|
|
244
|
-
);
|
|
245
|
-
onComplete(pane);
|
|
246
|
-
} catch (err: any) {
|
|
247
|
-
console.error('Payload Injection Error:', err);
|
|
248
|
-
setError(err.message || 'Failed to parse payloads. Check JSON format.');
|
|
249
|
-
}
|
|
250
|
-
}, [injectShell, injectCopy, selectedLayout, onComplete]);
|
|
251
|
-
|
|
252
|
-
const handleBack = () => {
|
|
253
|
-
setError(null);
|
|
254
|
-
if (currentStep === 'preview') {
|
|
255
|
-
setCurrentStep('input');
|
|
256
|
-
} else if (currentStep === 'input') {
|
|
257
|
-
if (isInjectMode) {
|
|
258
|
-
setIsInjectMode(false);
|
|
259
|
-
} else {
|
|
260
|
-
onCancel();
|
|
261
|
-
}
|
|
262
|
-
} else if (currentStep === 'loading') {
|
|
263
|
-
setCurrentStep('input');
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
if (currentStep === 'loading') {
|
|
268
|
-
return (
|
|
269
|
-
<div className="flex min-h-[200px] flex-col items-center justify-center space-y-4 p-6">
|
|
270
|
-
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400"></div>
|
|
271
|
-
<p className="text-sm text-gray-500">Generating AI Pane...</p>
|
|
272
|
-
<button
|
|
273
|
-
type="button"
|
|
274
|
-
onClick={handleBack}
|
|
275
|
-
className="mt-2 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"
|
|
276
|
-
>
|
|
277
|
-
Cancel
|
|
278
|
-
</button>
|
|
279
|
-
</div>
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (currentStep === 'preview' && generatedShell && generatedCopy) {
|
|
284
|
-
return (
|
|
285
|
-
<AiPanePreview
|
|
286
|
-
shellJson={generatedShell}
|
|
287
|
-
copyHtml={generatedCopy}
|
|
288
|
-
layout={selectedLayout}
|
|
289
|
-
ownerId={ownerId}
|
|
290
|
-
onComplete={onComplete}
|
|
291
|
-
onBack={handleBack}
|
|
292
|
-
/>
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (currentStep === 'input') {
|
|
297
|
-
if (isInjectMode) {
|
|
298
|
-
return (
|
|
299
|
-
<div className="space-y-6 p-4">
|
|
300
|
-
<div>
|
|
301
|
-
<label
|
|
302
|
-
htmlFor="shell-json"
|
|
303
|
-
className="block text-lg font-semibold text-gray-800"
|
|
304
|
-
>
|
|
305
|
-
Shell JSON Payload
|
|
306
|
-
</label>
|
|
307
|
-
<textarea
|
|
308
|
-
id="shell-json"
|
|
309
|
-
value={injectShell}
|
|
310
|
-
onChange={(e) => setInjectShell(e.target.value)}
|
|
311
|
-
placeholder="Paste raw API response for ShellJson here..."
|
|
312
|
-
rows={8}
|
|
313
|
-
className="mt-2 block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
|
|
314
|
-
/>
|
|
315
|
-
</div>
|
|
316
|
-
|
|
317
|
-
<div>
|
|
318
|
-
<label
|
|
319
|
-
htmlFor="copy-html"
|
|
320
|
-
className="block text-lg font-semibold text-gray-800"
|
|
321
|
-
>
|
|
322
|
-
Copy HTML Payload
|
|
323
|
-
</label>
|
|
324
|
-
<textarea
|
|
325
|
-
id="copy-html"
|
|
326
|
-
value={injectCopy}
|
|
327
|
-
onChange={(e) => setInjectCopy(e.target.value)}
|
|
328
|
-
placeholder="Paste raw API response for copyHtml here..."
|
|
329
|
-
rows={8}
|
|
330
|
-
className="mt-2 block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
|
|
331
|
-
/>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
335
|
-
|
|
336
|
-
<div className="flex justify-between pt-4">
|
|
337
|
-
<button
|
|
338
|
-
type="button"
|
|
339
|
-
onClick={handleBack}
|
|
340
|
-
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"
|
|
341
|
-
>
|
|
342
|
-
Back to Generator
|
|
343
|
-
</button>
|
|
344
|
-
<button
|
|
345
|
-
type="button"
|
|
346
|
-
onClick={handleInject}
|
|
347
|
-
disabled={!injectShell || !injectCopy}
|
|
348
|
-
className={`rounded-md border border-transparent px-4 py-2 text-sm font-bold text-white shadow-sm ${
|
|
349
|
-
!injectShell || !injectCopy
|
|
350
|
-
? 'cursor-not-allowed bg-gray-400'
|
|
351
|
-
: 'bg-cyan-600 hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2'
|
|
352
|
-
}`}
|
|
353
|
-
>
|
|
354
|
-
Create from Payloads
|
|
355
|
-
</button>
|
|
356
|
-
</div>
|
|
357
|
-
</div>
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return (
|
|
362
|
-
<div className="space-y-6 p-4">
|
|
363
|
-
<div>
|
|
364
|
-
<label className="block text-lg font-semibold text-gray-800">
|
|
365
|
-
Color Harmony
|
|
366
|
-
</label>
|
|
367
|
-
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-2">
|
|
368
|
-
{harmonyOptions.map((option) => (
|
|
369
|
-
<div key={option} className="flex items-center space-x-2">
|
|
370
|
-
<input
|
|
371
|
-
type="radio"
|
|
372
|
-
id={`harmony-${option}`}
|
|
373
|
-
name="harmonyOptions"
|
|
374
|
-
value={option}
|
|
375
|
-
checked={selectedHarmony === option}
|
|
376
|
-
onChange={(e) => setSelectedHarmony(e.target.value)}
|
|
377
|
-
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
378
|
-
/>
|
|
379
|
-
<label
|
|
380
|
-
htmlFor={`harmony-${option}`}
|
|
381
|
-
className="text-sm font-medium text-gray-700"
|
|
382
|
-
>
|
|
383
|
-
{option}
|
|
384
|
-
</label>
|
|
385
|
-
</div>
|
|
386
|
-
))}
|
|
387
|
-
</div>
|
|
388
|
-
</div>
|
|
389
|
-
|
|
390
|
-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
391
|
-
<div>
|
|
392
|
-
<ColorPickerCombo
|
|
393
|
-
title="Base Color (Optional)"
|
|
394
|
-
config={config!}
|
|
395
|
-
defaultColor={baseColor}
|
|
396
|
-
onColorChange={setBaseColor}
|
|
397
|
-
allowNull={true}
|
|
398
|
-
/>
|
|
399
|
-
</div>
|
|
400
|
-
<div>
|
|
401
|
-
<ColorPickerCombo
|
|
402
|
-
title="Accent Color (Optional)"
|
|
403
|
-
config={config!}
|
|
404
|
-
defaultColor={accentColor}
|
|
405
|
-
onColorChange={setAccentColor}
|
|
406
|
-
allowNull={true}
|
|
407
|
-
/>
|
|
408
|
-
</div>
|
|
409
|
-
</div>
|
|
410
|
-
|
|
411
|
-
<div>
|
|
412
|
-
<label className="block text-lg font-semibold text-gray-800">
|
|
413
|
-
Theme / Mood
|
|
414
|
-
</label>
|
|
415
|
-
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-2">
|
|
416
|
-
{themeOptions.map((option) => (
|
|
417
|
-
<div key={option} className="flex items-center space-x-2">
|
|
418
|
-
<input
|
|
419
|
-
type="radio"
|
|
420
|
-
id={`theme-${option}`}
|
|
421
|
-
name="themeOptions"
|
|
422
|
-
value={option}
|
|
423
|
-
checked={selectedTheme === option}
|
|
424
|
-
onChange={(e) => setSelectedTheme(e.target.value)}
|
|
425
|
-
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
426
|
-
/>
|
|
427
|
-
<label
|
|
428
|
-
htmlFor={`theme-${option}`}
|
|
429
|
-
className="text-sm font-medium text-gray-700"
|
|
430
|
-
>
|
|
431
|
-
{option}
|
|
432
|
-
</label>
|
|
433
|
-
</div>
|
|
434
|
-
))}
|
|
435
|
-
</div>
|
|
436
|
-
</div>
|
|
437
|
-
|
|
438
|
-
<div>
|
|
439
|
-
<label
|
|
440
|
-
htmlFor="additional-notes"
|
|
441
|
-
className="block text-lg font-semibold text-gray-800"
|
|
442
|
-
>
|
|
443
|
-
Additional Design Notes (Optional)
|
|
444
|
-
</label>
|
|
445
|
-
<p className="mb-2 mt-1 text-sm text-gray-500">
|
|
446
|
-
Add specific requests like "use rounded corners", "add subtle
|
|
447
|
-
texture".
|
|
448
|
-
</p>
|
|
449
|
-
<textarea
|
|
450
|
-
id="additional-notes"
|
|
451
|
-
value={additionalNotes}
|
|
452
|
-
onChange={(e) => setAdditionalNotes(e.target.value)}
|
|
453
|
-
placeholder="Enter additional notes..."
|
|
454
|
-
rows={3}
|
|
455
|
-
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
456
|
-
/>
|
|
457
|
-
</div>
|
|
458
|
-
|
|
459
|
-
<div>
|
|
460
|
-
<label className="block text-lg font-semibold text-gray-800">
|
|
461
|
-
Provide Content
|
|
462
|
-
</label>
|
|
463
|
-
<div className="my-2 flex space-x-4">
|
|
464
|
-
<div className="flex items-center space-x-2">
|
|
465
|
-
<input
|
|
466
|
-
type="radio"
|
|
467
|
-
id="copy-prompt-mode"
|
|
468
|
-
name="copyModeOptions"
|
|
469
|
-
value="prompt"
|
|
470
|
-
checked={copyMode === 'prompt'}
|
|
471
|
-
onChange={(e) => setCopyMode(e.target.value as CopyMode)}
|
|
472
|
-
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
473
|
-
/>
|
|
474
|
-
<label
|
|
475
|
-
htmlFor="copy-prompt-mode"
|
|
476
|
-
className="text-sm font-medium text-gray-700"
|
|
477
|
-
>
|
|
478
|
-
Write a prompt
|
|
479
|
-
</label>
|
|
480
|
-
</div>
|
|
481
|
-
<div className="flex items-center space-x-2">
|
|
482
|
-
<input
|
|
483
|
-
type="radio"
|
|
484
|
-
id="copy-raw-mode"
|
|
485
|
-
name="copyModeOptions"
|
|
486
|
-
value="raw"
|
|
487
|
-
checked={copyMode === 'raw'}
|
|
488
|
-
onChange={(e) => setCopyMode(e.target.value as CopyMode)}
|
|
489
|
-
className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
490
|
-
/>
|
|
491
|
-
<label
|
|
492
|
-
htmlFor="copy-raw-mode"
|
|
493
|
-
className="text-sm font-medium text-gray-700"
|
|
494
|
-
>
|
|
495
|
-
Provide Copy
|
|
496
|
-
</label>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
|
|
500
|
-
{copyMode === 'prompt' ? (
|
|
501
|
-
<>
|
|
502
|
-
<p className="mb-2 text-sm text-gray-500">
|
|
503
|
-
Let the AI write the copy based on your prompt.
|
|
504
|
-
</p>
|
|
505
|
-
<textarea
|
|
506
|
-
id="copy-prompt"
|
|
507
|
-
value={copyPrompt}
|
|
508
|
-
onChange={(e) => setCopyPrompt(e.target.value)}
|
|
509
|
-
placeholder="Enter copy prompt..."
|
|
510
|
-
rows={4}
|
|
511
|
-
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
512
|
-
/>
|
|
513
|
-
</>
|
|
514
|
-
) : (
|
|
515
|
-
<>
|
|
516
|
-
<p className="mb-2 text-sm text-gray-500">
|
|
517
|
-
Provide your raw copy text here. The AI will structure and style
|
|
518
|
-
it.
|
|
519
|
-
</p>
|
|
520
|
-
<textarea
|
|
521
|
-
id="raw-copy"
|
|
522
|
-
value={rawCopy}
|
|
523
|
-
onChange={(e) => setRawCopy(e.target.value)}
|
|
524
|
-
placeholder="Paste or type your copy text..."
|
|
525
|
-
rows={6}
|
|
526
|
-
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
527
|
-
/>
|
|
528
|
-
</>
|
|
529
|
-
)}
|
|
530
|
-
</div>
|
|
531
|
-
|
|
532
|
-
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
533
|
-
|
|
534
|
-
<div className="flex justify-between pt-4">
|
|
535
|
-
<button
|
|
536
|
-
type="button"
|
|
537
|
-
onClick={handleBack}
|
|
538
|
-
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"
|
|
539
|
-
>
|
|
540
|
-
Cancel
|
|
541
|
-
</button>
|
|
542
|
-
<button
|
|
543
|
-
type="button"
|
|
544
|
-
onClick={handleGenerate}
|
|
545
|
-
disabled={copyMode === 'prompt' ? !copyPrompt : !rawCopy}
|
|
546
|
-
className={classNames(
|
|
547
|
-
`rounded-md border border-transparent px-4 py-2 text-sm font-bold shadow-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2`,
|
|
548
|
-
(copyMode === 'prompt' && !copyPrompt) ||
|
|
549
|
-
(copyMode === `raw` && !rawCopy)
|
|
550
|
-
? 'cursor-not-allowed bg-gray-300 text-gray-500'
|
|
551
|
-
: 'bg-cyan-600 text-white hover:bg-cyan-700'
|
|
552
|
-
)}
|
|
553
|
-
>
|
|
554
|
-
Generate Pane
|
|
555
|
-
</button>
|
|
556
|
-
</div>
|
|
557
|
-
|
|
558
|
-
<div className="border-t border-gray-200 pt-4 text-center">
|
|
559
|
-
<button
|
|
560
|
-
type="button"
|
|
561
|
-
onClick={() => {
|
|
562
|
-
setError(null);
|
|
563
|
-
setIsInjectMode(true);
|
|
564
|
-
}}
|
|
565
|
-
className="text-sm text-cyan-600 hover:text-cyan-800 hover:underline"
|
|
566
|
-
>
|
|
567
|
-
Direct Inject Payload
|
|
568
|
-
</button>
|
|
569
|
-
</div>
|
|
570
|
-
</div>
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return null;
|
|
575
|
-
}
|