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