astro-tractstack 2.1.2 → 2.2.0
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/README.md +54 -266
- package/bin/create-tractstack.js +9 -6
- package/dist/index.js +109 -71
- package/package.json +4 -2
- package/templates/css/custom.css +5 -0
- package/templates/custom/minimal/CodeHook.astro +1 -0
- package/templates/custom/with-examples/CodeHook.astro +1 -0
- package/templates/icons/code.svg +18 -0
- package/templates/icons/li.svg +4 -0
- package/templates/icons/link.svg +22 -0
- package/templates/icons/p.svg +3 -0
- package/templates/src/client/app.js +80 -1
- package/templates/src/components/Footer.astro +1 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
- package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
- package/templates/src/components/compositor/Compositor.tsx +25 -9
- package/templates/src/components/compositor/Node.tsx +168 -496
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
- package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
- package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
- package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
- package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
- package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
- package/templates/src/components/edit/Header.tsx +68 -9
- package/templates/src/components/edit/PanelSwitch.tsx +42 -4
- package/templates/src/components/edit/SettingsPanel.tsx +2 -3
- package/templates/src/components/edit/ToolMode.tsx +1 -31
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
- package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
- package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
- package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +19 -787
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
- package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
- package/templates/src/components/fields/ArtpackImage.tsx +11 -3
- package/templates/src/components/fields/BackgroundImage.tsx +8 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
- package/templates/src/components/fields/ImageUpload.tsx +6 -0
- package/templates/src/components/form/ActionBuilderField.tsx +15 -5
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
- package/templates/src/components/form/ColorPicker.tsx +1 -1
- package/templates/src/components/form/EnumSelect.tsx +1 -1
- package/templates/src/components/form/NumberInput.tsx +1 -1
- package/templates/src/components/form/StringArrayInput.tsx +1 -1
- package/templates/src/components/form/StringInput.tsx +1 -1
- package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
- package/templates/src/components/profile/ProfileCreate.tsx +1 -1
- package/templates/src/components/profile/ProfileEdit.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
- package/templates/src/constants/prompts.json +18 -10
- package/templates/src/constants.ts +3 -0
- package/templates/src/hooks/usePaneFragments.ts +60 -0
- package/templates/src/lib/session.ts +71 -16
- package/templates/src/pages/[...slug].astro +5 -46
- package/templates/src/pages/api/css.ts +149 -0
- package/templates/src/pages/context/[...contextSlug].astro +1 -0
- package/templates/src/pages/maint.astro +1 -1
- package/templates/src/pages/storykeep/login.astro +2 -2
- package/templates/src/stores/nodes.ts +162 -49
- package/templates/src/stores/orphanAnalysis.ts +6 -30
- package/templates/src/stores/previews.ts +7 -0
- package/templates/src/stores/storykeep.ts +0 -8
- package/templates/src/types/compositorTypes.ts +53 -10
- package/templates/src/utils/compositor/aiGeneration.ts +93 -0
- package/templates/src/utils/compositor/allowInsert.ts +2 -0
- package/templates/src/utils/compositor/htmlAst.ts +704 -0
- package/templates/src/utils/compositor/nodesHelper.ts +281 -102
- package/templates/src/utils/compositor/savePipeline.ts +893 -0
- package/templates/src/utils/etl/index.ts +3 -0
- package/templates/src/utils/etl/transformer.ts +10 -0
- package/templates/src/utils/helpers.ts +101 -0
- package/utils/inject-files.ts +100 -62
- package/templates/icons/text.svg +0 -6
- package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
|
@@ -6,7 +6,6 @@ export interface AiDesignConfig {
|
|
|
6
6
|
baseColor: string;
|
|
7
7
|
accentColor: string;
|
|
8
8
|
theme: string;
|
|
9
|
-
additionalNotes: string;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
interface AiDesignStepProps {
|
|
@@ -78,7 +77,7 @@ export const AiDesignStep = ({
|
|
|
78
77
|
</div>
|
|
79
78
|
</div>
|
|
80
79
|
|
|
81
|
-
<div className="
|
|
80
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
82
81
|
<div>
|
|
83
82
|
<ColorPickerCombo
|
|
84
83
|
title="Base Color (Optional)"
|
|
@@ -123,27 +122,6 @@ export const AiDesignStep = ({
|
|
|
123
122
|
))}
|
|
124
123
|
</div>
|
|
125
124
|
</div>
|
|
126
|
-
|
|
127
|
-
<div>
|
|
128
|
-
<label
|
|
129
|
-
htmlFor={`${idPrefix}additional-notes`}
|
|
130
|
-
className="block text-base font-bold text-gray-800"
|
|
131
|
-
>
|
|
132
|
-
Additional Design Notes (Optional)
|
|
133
|
-
</label>
|
|
134
|
-
<p className="mb-2 mt-1 text-sm text-gray-500">
|
|
135
|
-
Add specific requests like "use rounded corners", "add subtle
|
|
136
|
-
texture".
|
|
137
|
-
</p>
|
|
138
|
-
<textarea
|
|
139
|
-
id={`${idPrefix}additional-notes`}
|
|
140
|
-
value={designConfig.additionalNotes}
|
|
141
|
-
onChange={(e) => updateField('additionalNotes', e.target.value)}
|
|
142
|
-
placeholder="Enter additional notes..."
|
|
143
|
-
rows={3}
|
|
144
|
-
className="sm:text-sm block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
|
|
145
|
-
/>
|
|
146
|
-
</div>
|
|
147
125
|
</div>
|
|
148
126
|
);
|
|
149
127
|
};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
2
|
+
import { cloneDeep } from '@/utils/helpers';
|
|
3
|
+
import prompts from '@/constants/prompts.json';
|
|
4
|
+
import {
|
|
5
|
+
convertStorageToLiveTemplate,
|
|
6
|
+
convertTemplateToAIShell,
|
|
7
|
+
} from '@/utils/compositor/designLibraryHelper';
|
|
8
|
+
import { parseAiPane, parseAiCopyHtml } from '@/utils/compositor/aiPaneParser';
|
|
9
|
+
import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
|
|
10
|
+
import { CopyInputStep, type CopyMode } from './CopyInputStep';
|
|
11
|
+
import type { DesignLibraryEntry } from '@/types/tractstack';
|
|
12
|
+
import type { TemplatePane } from '@/types/compositorTypes';
|
|
13
|
+
|
|
14
|
+
interface AiLibraryCopyStepProps {
|
|
15
|
+
entry: DesignLibraryEntry;
|
|
16
|
+
onBack: () => void;
|
|
17
|
+
onCreatePane: (template: TemplatePane) => void;
|
|
18
|
+
isSandboxMode?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ColumnPresetKey = 'left' | 'right';
|
|
22
|
+
|
|
23
|
+
export const AiLibraryCopyStep = ({
|
|
24
|
+
entry,
|
|
25
|
+
onBack,
|
|
26
|
+
onCreatePane,
|
|
27
|
+
isSandboxMode = false,
|
|
28
|
+
}: AiLibraryCopyStepProps) => {
|
|
29
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
30
|
+
const [error, setError] = useState<string | null>(null);
|
|
31
|
+
|
|
32
|
+
const [copyMode, setCopyMode] = useState<CopyMode>(
|
|
33
|
+
entry.retain ? 'original' : 'prompt'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const [topic, setTopic] = useState('');
|
|
37
|
+
const [promptValue, setPromptValue] = useState('');
|
|
38
|
+
const [copyValue, setCopyValue] = useState('');
|
|
39
|
+
|
|
40
|
+
const [overallPrompt, setOverallPrompt] = useState('');
|
|
41
|
+
const [promptValueCol1, setPromptValueCol1] = useState('');
|
|
42
|
+
const [promptValueCol2, setPromptValueCol2] = useState('');
|
|
43
|
+
const [col1Copy, setCol1Copy] = useState('');
|
|
44
|
+
const [col2Copy, setCol2Copy] = useState('');
|
|
45
|
+
|
|
46
|
+
const [showAdvancedPrompts, setShowAdvancedPrompts] = useState(false);
|
|
47
|
+
const [selectedPromptId, setSelectedPromptId] = useState('');
|
|
48
|
+
const [isAiStyling, setIsAiStyling] = useState(false);
|
|
49
|
+
|
|
50
|
+
const liveTemplate = useMemo(() => {
|
|
51
|
+
return convertStorageToLiveTemplate(entry.template);
|
|
52
|
+
}, [entry]);
|
|
53
|
+
|
|
54
|
+
const isGrid = !!liveTemplate.gridLayout;
|
|
55
|
+
const layoutChoice = isGrid ? 'grid' : 'standard';
|
|
56
|
+
|
|
57
|
+
const promptOptions = useMemo(() => {
|
|
58
|
+
return prompts.aiPromptsIndex
|
|
59
|
+
.filter((p) => p.layout === layoutChoice)
|
|
60
|
+
.map((p) => ({ label: p.label, value: p.id }));
|
|
61
|
+
}, [layoutChoice]);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (promptOptions.length > 0) {
|
|
65
|
+
const currentValid = promptOptions.find(
|
|
66
|
+
(p) => p.value === selectedPromptId
|
|
67
|
+
);
|
|
68
|
+
if (!currentValid) {
|
|
69
|
+
setSelectedPromptId(promptOptions[0].value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, [promptOptions, selectedPromptId]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!selectedPromptId) return;
|
|
76
|
+
|
|
77
|
+
const activeConfig = prompts.aiPromptsIndex.find(
|
|
78
|
+
(p) => p.id === selectedPromptId
|
|
79
|
+
);
|
|
80
|
+
if (!activeConfig) return;
|
|
81
|
+
|
|
82
|
+
const promptKey = activeConfig.prompts.copy;
|
|
83
|
+
const copyPromptGroup = (prompts as any)[promptKey];
|
|
84
|
+
if (!copyPromptGroup) return;
|
|
85
|
+
|
|
86
|
+
const variant = activeConfig.variants
|
|
87
|
+
? activeConfig.variants[0]
|
|
88
|
+
: 'default';
|
|
89
|
+
|
|
90
|
+
if (layoutChoice === 'standard') {
|
|
91
|
+
const baseText = copyPromptGroup[variant] || '';
|
|
92
|
+
setPromptValue(baseText);
|
|
93
|
+
} else if (layoutChoice === 'grid') {
|
|
94
|
+
const preset = copyPromptGroup.presets?.[variant];
|
|
95
|
+
if (preset) {
|
|
96
|
+
setOverallPrompt(preset.default || '');
|
|
97
|
+
setPromptValueCol1(preset.left?.prompt || '');
|
|
98
|
+
setPromptValueCol2(preset.right?.prompt || '');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}, [selectedPromptId, layoutChoice]);
|
|
102
|
+
|
|
103
|
+
const handleGenerate = async () => {
|
|
104
|
+
setError(null);
|
|
105
|
+
setIsGenerating(true);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (copyMode === 'original') {
|
|
109
|
+
onCreatePane(liveTemplate);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const shellResult = convertTemplateToAIShell(liveTemplate);
|
|
114
|
+
const layoutType = 'Text Only';
|
|
115
|
+
|
|
116
|
+
const activeConfig = prompts.aiPromptsIndex.find(
|
|
117
|
+
(p) => p.id === selectedPromptId
|
|
118
|
+
);
|
|
119
|
+
if (!activeConfig) throw new Error('Selected prompt type not found.');
|
|
120
|
+
|
|
121
|
+
const injectTopic = (text: string) => {
|
|
122
|
+
return text.replace('{{TOPIC}}', topic.trim());
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (isGrid && liveTemplate.gridLayout) {
|
|
126
|
+
if (copyMode === 'raw') {
|
|
127
|
+
const nodes = liveTemplate.gridLayout.nodes;
|
|
128
|
+
if (nodes && nodes[0]) nodes[0].markdownBody = col1Copy;
|
|
129
|
+
if (nodes && nodes[1]) nodes[1].markdownBody = col2Copy;
|
|
130
|
+
onCreatePane(liveTemplate);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (copyMode === 'prompt') {
|
|
135
|
+
const copyPromptKey = activeConfig.prompts.copy;
|
|
136
|
+
const copyPromptDetails = (prompts as any)[copyPromptKey];
|
|
137
|
+
|
|
138
|
+
const preset =
|
|
139
|
+
copyPromptDetails.presets?.[activeConfig.variants[0]] ||
|
|
140
|
+
copyPromptDetails.presets?.heroDefault;
|
|
141
|
+
|
|
142
|
+
const copyResults: string[] = [];
|
|
143
|
+
|
|
144
|
+
const promptsToRun = [
|
|
145
|
+
{ prompt: promptValueCol1, presetKey: 'left' as ColumnPresetKey },
|
|
146
|
+
{ prompt: promptValueCol2, presetKey: 'right' as ColumnPresetKey },
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
for (const item of promptsToRun) {
|
|
150
|
+
const columnPreset = preset[item.presetKey];
|
|
151
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
152
|
+
.replace('{{SHELL_JSON}}', shellResult)
|
|
153
|
+
.replace('{{COPY_INPUT}}', injectTopic(overallPrompt))
|
|
154
|
+
.replace('{{COLUMN_PROMPT}}', item.prompt)
|
|
155
|
+
.replace(
|
|
156
|
+
'{{DESIGN_INPUT}}',
|
|
157
|
+
"N/A - Use the provided Shell JSON's design."
|
|
158
|
+
)
|
|
159
|
+
.replace('{{LAYOUT_TYPE}}', layoutType)
|
|
160
|
+
.replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
|
|
161
|
+
|
|
162
|
+
const copyResult = await callAskLemurAPI({
|
|
163
|
+
prompt: formattedCopyPrompt,
|
|
164
|
+
context: copyPromptDetails.system || '',
|
|
165
|
+
expectJson: false,
|
|
166
|
+
isSandboxMode,
|
|
167
|
+
maxTokens: 2000,
|
|
168
|
+
});
|
|
169
|
+
copyResults.push(copyResult);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const finalPane = parseAiPane(shellResult, copyResults, layoutType);
|
|
173
|
+
onCreatePane(finalPane);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!isGrid && liveTemplate.markdown) {
|
|
179
|
+
if (copyMode === 'raw') {
|
|
180
|
+
liveTemplate.markdown.markdownBody = copyValue;
|
|
181
|
+
onCreatePane(liveTemplate);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (copyMode === 'prompt') {
|
|
186
|
+
if (!shellResult || shellResult === '{}') {
|
|
187
|
+
throw new Error(
|
|
188
|
+
'Could not generate a valid AI shell from this design.'
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const copyPromptKey = activeConfig.prompts.copy;
|
|
193
|
+
const copyPromptDetails = (prompts as any)[copyPromptKey];
|
|
194
|
+
|
|
195
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
196
|
+
.replace('{{COPY_INPUT}}', injectTopic(promptValue))
|
|
197
|
+
.replace(
|
|
198
|
+
'{{DESIGN_INPUT}}',
|
|
199
|
+
"N/A - Use the provided Shell JSON's design."
|
|
200
|
+
)
|
|
201
|
+
.replace('{{LAYOUT_TYPE}}', layoutType)
|
|
202
|
+
.replace('{{SHELL_JSON}}', shellResult);
|
|
203
|
+
|
|
204
|
+
const copyResult = await callAskLemurAPI({
|
|
205
|
+
prompt: formattedCopyPrompt,
|
|
206
|
+
context: copyPromptDetails.system || '',
|
|
207
|
+
expectJson: false,
|
|
208
|
+
isSandboxMode,
|
|
209
|
+
maxTokens: 2000,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const newNodes = parseAiCopyHtml(
|
|
213
|
+
copyResult,
|
|
214
|
+
liveTemplate.markdown.id
|
|
215
|
+
);
|
|
216
|
+
const finalPane = cloneDeep(liveTemplate);
|
|
217
|
+
if (finalPane.markdown) {
|
|
218
|
+
finalPane.markdown.nodes = newNodes;
|
|
219
|
+
}
|
|
220
|
+
onCreatePane(finalPane);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
throw new Error(
|
|
226
|
+
'Template configuration mismatch. Please try a different design.'
|
|
227
|
+
);
|
|
228
|
+
} catch (err: any) {
|
|
229
|
+
console.error('Library Generation Error:', err);
|
|
230
|
+
setError(err.message || 'Failed to generate content for this design.');
|
|
231
|
+
} finally {
|
|
232
|
+
setIsGenerating(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (isGenerating) {
|
|
237
|
+
return (
|
|
238
|
+
<div className="flex min-h-96 flex-col items-center justify-center space-y-4 p-6">
|
|
239
|
+
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
240
|
+
<p className="text-sm text-gray-600">Writing Content...</p>
|
|
241
|
+
<p className="text-xs text-gray-500">
|
|
242
|
+
Adapting the design to your text.
|
|
243
|
+
</p>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div className="space-y-6 p-4">
|
|
250
|
+
<div className="flex items-center justify-between border-b pb-4">
|
|
251
|
+
<div>
|
|
252
|
+
<h3 className="font-bold text-gray-800">Customize Design</h3>
|
|
253
|
+
<p className="text-xs text-gray-500">
|
|
254
|
+
Selected: <span className="font-bold">{entry.title}</span>
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
<button
|
|
258
|
+
onClick={onBack}
|
|
259
|
+
className="text-xs text-gray-500 hover:text-gray-700"
|
|
260
|
+
>
|
|
261
|
+
Change Design
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<CopyInputStep
|
|
266
|
+
layoutChoice={layoutChoice}
|
|
267
|
+
copyMode={copyMode}
|
|
268
|
+
onCopyModeChange={setCopyMode}
|
|
269
|
+
topic={topic}
|
|
270
|
+
onTopicChange={setTopic}
|
|
271
|
+
showAdvancedPrompts={showAdvancedPrompts}
|
|
272
|
+
onShowAdvancedPromptsChange={setShowAdvancedPrompts}
|
|
273
|
+
promptValue={promptValue}
|
|
274
|
+
onPromptValueChange={setPromptValue}
|
|
275
|
+
copyValue={copyValue}
|
|
276
|
+
onCopyValueChange={setCopyValue}
|
|
277
|
+
overallPrompt={overallPrompt}
|
|
278
|
+
onOverallPromptChange={setOverallPrompt}
|
|
279
|
+
promptValueCol1={promptValueCol1}
|
|
280
|
+
onPromptValueCol1Change={setPromptValueCol1}
|
|
281
|
+
promptValueCol2={promptValueCol2}
|
|
282
|
+
onPromptValueCol2Change={setPromptValueCol2}
|
|
283
|
+
col1Copy={col1Copy}
|
|
284
|
+
onCol1CopyChange={setCol1Copy}
|
|
285
|
+
col2Copy={col2Copy}
|
|
286
|
+
onCol2CopyChange={setCol2Copy}
|
|
287
|
+
hasRetainedContent={entry.retain}
|
|
288
|
+
showStyleToggle={false}
|
|
289
|
+
promptOptions={promptOptions}
|
|
290
|
+
selectedPromptId={selectedPromptId}
|
|
291
|
+
onSelectedPromptIdChange={setSelectedPromptId}
|
|
292
|
+
isAiStyling={isAiStyling}
|
|
293
|
+
onIsAiStylingChange={setIsAiStyling}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
{error && (
|
|
297
|
+
<div className="rounded-md bg-red-50 p-3 text-sm text-red-700">
|
|
298
|
+
{error}
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
|
|
302
|
+
<div className="mt-4 flex justify-end border-t pt-4">
|
|
303
|
+
<button
|
|
304
|
+
onClick={handleGenerate}
|
|
305
|
+
disabled={
|
|
306
|
+
copyMode === 'prompt'
|
|
307
|
+
? isGrid
|
|
308
|
+
? !promptValueCol1.trim() &&
|
|
309
|
+
!promptValueCol2.trim() &&
|
|
310
|
+
!overallPrompt.trim()
|
|
311
|
+
: !promptValue.trim()
|
|
312
|
+
: copyMode === 'raw'
|
|
313
|
+
? isGrid
|
|
314
|
+
? !col1Copy.trim() || !col2Copy.trim()
|
|
315
|
+
: !copyValue.trim()
|
|
316
|
+
: false
|
|
317
|
+
}
|
|
318
|
+
className="rounded-md bg-cyan-600 px-6 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
319
|
+
>
|
|
320
|
+
{copyMode === 'original'
|
|
321
|
+
? 'Use Original Content'
|
|
322
|
+
: '✨ Generate Pane'}
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
|
|
3
|
+
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
4
|
+
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
5
|
+
import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
|
|
6
|
+
import prompts from '@/constants/prompts.json';
|
|
7
|
+
import { htmlToHtmlAst } from '@/utils/compositor/htmlAst';
|
|
8
|
+
import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
|
|
9
|
+
import type { TemplatePane } from '@/types/compositorTypes';
|
|
10
|
+
|
|
11
|
+
interface AiRefineDesignStepProps {
|
|
12
|
+
onBack: () => void;
|
|
13
|
+
onSuccess: () => void;
|
|
14
|
+
onUpdatePane: (template: TemplatePane) => void;
|
|
15
|
+
isSandboxMode?: boolean;
|
|
16
|
+
initialHtml: string;
|
|
17
|
+
initialCss: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const AiRefineDesignStep = ({
|
|
21
|
+
onBack,
|
|
22
|
+
onSuccess,
|
|
23
|
+
onUpdatePane,
|
|
24
|
+
isSandboxMode = false,
|
|
25
|
+
initialHtml,
|
|
26
|
+
initialCss,
|
|
27
|
+
}: AiRefineDesignStepProps) => {
|
|
28
|
+
const [prompt, setPrompt] = useState('');
|
|
29
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
30
|
+
const [error, setError] = useState<string | null>(null);
|
|
31
|
+
|
|
32
|
+
const [reviewMode, setReviewMode] = useState(false);
|
|
33
|
+
const [previewHtml, setPreviewHtml] = useState<string>('');
|
|
34
|
+
const [pendingTemplate, setPendingTemplate] = useState<TemplatePane | null>(
|
|
35
|
+
null
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const handleGenerate = async () => {
|
|
39
|
+
if (!prompt.trim()) {
|
|
40
|
+
setError('Please enter a refinement request.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setIsGenerating(true);
|
|
45
|
+
setError(null);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const promptConfig = prompts.aiPaneCreativeRefinePrompt;
|
|
49
|
+
const systemPrompt = promptConfig.system;
|
|
50
|
+
let userPrompt = promptConfig.user_template;
|
|
51
|
+
|
|
52
|
+
userPrompt = userPrompt.replace('{{DESIGN_NOTES}}', prompt);
|
|
53
|
+
userPrompt = userPrompt.replace('{{CSS_INPUT}}', initialCss);
|
|
54
|
+
userPrompt = userPrompt.replace('{{HTML_INPUT}}', initialHtml);
|
|
55
|
+
|
|
56
|
+
// 1. Get RAW output from AI
|
|
57
|
+
const resultHtml = await callAskLemurAPI({
|
|
58
|
+
prompt: userPrompt,
|
|
59
|
+
context: systemPrompt,
|
|
60
|
+
expectJson: false,
|
|
61
|
+
isSandboxMode,
|
|
62
|
+
maxTokens: 4000,
|
|
63
|
+
temperature: 0.5,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 2. Generate AST immediately
|
|
67
|
+
const htmlAst = await htmlToHtmlAst(resultHtml, '');
|
|
68
|
+
|
|
69
|
+
// 3. Construct the candidate TemplatePane
|
|
70
|
+
const candidateTemplate: TemplatePane = {
|
|
71
|
+
id: '',
|
|
72
|
+
nodeType: 'Pane',
|
|
73
|
+
parentId: '',
|
|
74
|
+
title: 'Refined Pane',
|
|
75
|
+
slug: '',
|
|
76
|
+
isDecorative: false,
|
|
77
|
+
htmlAst,
|
|
78
|
+
markdown: {
|
|
79
|
+
id: '',
|
|
80
|
+
nodeType: 'Markdown',
|
|
81
|
+
parentId: '',
|
|
82
|
+
type: 'markdown',
|
|
83
|
+
markdownId: '',
|
|
84
|
+
defaultClasses: {},
|
|
85
|
+
parentClasses: [],
|
|
86
|
+
nodes: [],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const tenantId =
|
|
91
|
+
(window as any).TRACTSTACK_CONFIG?.tenantId ||
|
|
92
|
+
import.meta.env.PUBLIC_TENANTID ||
|
|
93
|
+
'default';
|
|
94
|
+
|
|
95
|
+
const goBackend =
|
|
96
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
97
|
+
|
|
98
|
+
// 4. Send AST to backend to get SAFE/Sanitized HTML for preview
|
|
99
|
+
const response = await fetch(
|
|
100
|
+
`${goBackend}/api/v1/fragments/ast-preview`,
|
|
101
|
+
{
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'X-Tenant-ID': tenantId,
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
id: 'temp-refine-preview',
|
|
109
|
+
title: 'Refine Preview',
|
|
110
|
+
tree: htmlAst.tree,
|
|
111
|
+
simulateFrontend: true,
|
|
112
|
+
}),
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const text = await response.text();
|
|
118
|
+
throw new Error(
|
|
119
|
+
text || `Preview generation failed: ${response.status}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const safeHtml = await response.text();
|
|
124
|
+
|
|
125
|
+
// 5. Store SAFE HTML for display and valid Template for acceptance
|
|
126
|
+
setPendingTemplate(candidateTemplate);
|
|
127
|
+
setPreviewHtml(safeHtml);
|
|
128
|
+
setReviewMode(true);
|
|
129
|
+
} catch (err: any) {
|
|
130
|
+
console.error('Refine Generation Error:', err);
|
|
131
|
+
setError(err.message || 'Failed to refine design.');
|
|
132
|
+
} finally {
|
|
133
|
+
setIsGenerating(false);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const handleAccept = () => {
|
|
138
|
+
if (pendingTemplate) {
|
|
139
|
+
onUpdatePane(pendingTemplate);
|
|
140
|
+
onSuccess();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const handleCancel = () => {
|
|
145
|
+
setReviewMode(false);
|
|
146
|
+
setPendingTemplate(null);
|
|
147
|
+
setPreviewHtml('');
|
|
148
|
+
onBack();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const handleRedo = () => {
|
|
152
|
+
setReviewMode(false);
|
|
153
|
+
setPendingTemplate(null);
|
|
154
|
+
setPreviewHtml('');
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (isGenerating) {
|
|
158
|
+
return (
|
|
159
|
+
<div className="flex min-h-96 flex-col items-center justify-center space-y-4 p-8">
|
|
160
|
+
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
161
|
+
<div className="text-center">
|
|
162
|
+
<p className="font-bold text-gray-900">
|
|
163
|
+
{reviewMode ? 'Compiling Design...' : 'Refining Design...'}
|
|
164
|
+
</p>
|
|
165
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
166
|
+
AI is refactoring your component.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (reviewMode && previewHtml) {
|
|
174
|
+
return (
|
|
175
|
+
<div className="relative flex h-full flex-col">
|
|
176
|
+
<div className="absolute right-4 top-4 z-50 flex gap-2">
|
|
177
|
+
<button
|
|
178
|
+
onClick={handleCancel}
|
|
179
|
+
className="rounded-full border border-gray-200 bg-white p-2 text-red-500 shadow-md transition-colors hover:bg-gray-100"
|
|
180
|
+
title="Cancel"
|
|
181
|
+
>
|
|
182
|
+
<XMarkIcon className="h-6 w-6" />
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={handleRedo}
|
|
186
|
+
className="rounded-full border border-gray-200 bg-white p-2 text-blue-500 shadow-md transition-colors hover:bg-gray-100"
|
|
187
|
+
title="Redo with same prompt"
|
|
188
|
+
>
|
|
189
|
+
<ArrowPathRoundedSquareIcon className="h-6 w-6" />
|
|
190
|
+
</button>
|
|
191
|
+
<button
|
|
192
|
+
onClick={handleAccept}
|
|
193
|
+
className="rounded-full border border-green-600 bg-green-500 p-2 text-white shadow-md transition-colors hover:bg-green-600"
|
|
194
|
+
title="Accept"
|
|
195
|
+
>
|
|
196
|
+
<CheckIcon className="h-6 w-6" />
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<style
|
|
201
|
+
dangerouslySetInnerHTML={{
|
|
202
|
+
__html: pendingTemplate?.htmlAst?.css || '',
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
<div
|
|
206
|
+
className="w-full flex-1 overflow-y-auto rounded-md border bg-gray-50 p-4"
|
|
207
|
+
dangerouslySetInnerHTML={{ __html: previewHtml }}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className="space-y-6 p-4">
|
|
215
|
+
<div className="text-center">
|
|
216
|
+
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-purple-50">
|
|
217
|
+
<SparklesIcon
|
|
218
|
+
className="h-6 w-6 text-purple-600"
|
|
219
|
+
aria-hidden="true"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
<h3 className="mt-2 text-lg font-bold text-gray-900">Refine Design</h3>
|
|
223
|
+
<p className="text-sm text-gray-500">
|
|
224
|
+
Modify the existing design using AI instructions.
|
|
225
|
+
</p>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<div>
|
|
229
|
+
<label className="block text-sm font-bold text-gray-700">
|
|
230
|
+
Refinement Instructions
|
|
231
|
+
</label>
|
|
232
|
+
<textarea
|
|
233
|
+
rows={6}
|
|
234
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-purple-500 focus:ring-purple-500"
|
|
235
|
+
placeholder="e.g. Change the background to dark blue, make the title larger, and switch the button color to yellow."
|
|
236
|
+
value={prompt}
|
|
237
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
238
|
+
disabled={isGenerating}
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{error && (
|
|
243
|
+
<div className="rounded-md bg-red-50 p-3">
|
|
244
|
+
<p className="text-sm text-red-700">{error}</p>
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
<div className="flex justify-between border-t border-gray-100 pt-4">
|
|
249
|
+
<button
|
|
250
|
+
onClick={onBack}
|
|
251
|
+
disabled={isGenerating}
|
|
252
|
+
className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-50"
|
|
253
|
+
>
|
|
254
|
+
Cancel
|
|
255
|
+
</button>
|
|
256
|
+
<button
|
|
257
|
+
onClick={handleGenerate}
|
|
258
|
+
disabled={isGenerating || !prompt.trim()}
|
|
259
|
+
className="flex items-center gap-2 rounded-md bg-purple-600 px-6 py-2 text-sm font-bold text-white shadow-sm hover:bg-purple-700 disabled:bg-gray-400"
|
|
260
|
+
>
|
|
261
|
+
<SparklesIcon className="h-4 w-4" />
|
|
262
|
+
Refine
|
|
263
|
+
</button>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
};
|