astro-tractstack 2.0.43 → 2.0.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.43",
3
+ "version": "2.0.44",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,16 +22,13 @@ import {
22
22
  } from '@/utils/compositor/designLibraryHelper';
23
23
  import { DirectInjectStep } from './steps/DirectInjectStep';
24
24
  import BooleanToggle from '@/components/form/BooleanToggle';
25
- import EnumSelect from '@/components/form/EnumSelect';
26
25
  import type { StoryFragmentNode } from '@/types/compositorTypes';
27
26
  import { TractStackAPI } from '@/utils/api';
28
27
 
29
28
  type Step =
30
29
  | 'initial'
31
- | 'copyInput'
30
+ | 'dashboard'
32
31
  | 'designLibrary'
33
- | 'layoutChoice'
34
- | 'aiDesign'
35
32
  | 'loading'
36
33
  | 'error'
37
34
  | 'directInject';
@@ -63,7 +60,6 @@ const callAskLemurAPI = async (
63
60
  let resultData: any;
64
61
 
65
62
  if (isSandboxMode) {
66
- // Sandbox mode still uses local fetch, but we pass the correct tenant ID
67
63
  const response = await fetch(`/api/sandbox`, {
68
64
  method: 'POST',
69
65
  headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
@@ -80,9 +76,8 @@ const callAskLemurAPI = async (
80
76
  if (!json.success) {
81
77
  throw new Error(json.error || 'Sandbox generation failed');
82
78
  }
83
- resultData = json.data; // { response: ... }
79
+ resultData = json.data;
84
80
  } else {
85
- // Production mode: Use the robust API class
86
81
  const response = await api.post('/api/v1/aai/askLemur', requestBody);
87
82
 
88
83
  if (!response.success) {
@@ -90,9 +85,7 @@ const callAskLemurAPI = async (
90
85
  response.error || 'Generation failed to return valid response.'
91
86
  );
92
87
  }
93
-
94
- // TractStackAPI unwraps the 'data' field automatically
95
- resultData = response.data; // { response: ... }
88
+ resultData = response.data;
96
89
  }
97
90
 
98
91
  if (!resultData?.response) {
@@ -137,6 +130,7 @@ const AddPaneNewPanel = ({
137
130
  }: AddPaneNewPanelProps) => {
138
131
  const ctx = providedCtx || getCtx();
139
132
  const hasAssemblyAI = useStore(hasAssemblyAIStore);
133
+
140
134
  const [step, setStep] = useState<Step>('initial');
141
135
  const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
142
136
  null
@@ -144,6 +138,10 @@ const AddPaneNewPanel = ({
144
138
  const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
145
139
  const [error, setError] = useState<string | null>(null);
146
140
 
141
+ const [topic, setTopic] = useState('');
142
+ const [showAdvancedPrompts, setShowAdvancedPrompts] = useState(false);
143
+ const [showStyles, setShowStyles] = useState(false);
144
+
147
145
  const [selectedPromptId, setSelectedPromptId] = useState<string>('');
148
146
  const [isAiStyling, setIsAiStyling] = useState(false);
149
147
 
@@ -151,15 +149,9 @@ const AddPaneNewPanel = ({
151
149
  const [promptValue, setPromptValue] = useState('');
152
150
  const [copyValue, setCopyValue] = useState('');
153
151
 
154
- const [overallPrompt, setOverallPrompt] = useState(
155
- prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.default
156
- );
157
- const [promptValueCol1, setPromptValueCol1] = useState(
158
- prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
159
- );
160
- const [promptValueCol2, setPromptValueCol2] = useState(
161
- prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right.prompt
162
- );
152
+ const [overallPrompt, setOverallPrompt] = useState('');
153
+ const [promptValueCol1, setPromptValueCol1] = useState('');
154
+ const [promptValueCol2, setPromptValueCol2] = useState('');
163
155
  const [col1Copy, setCol1Copy] = useState('');
164
156
  const [col2Copy, setCol2Copy] = useState('');
165
157
 
@@ -206,18 +198,23 @@ const AddPaneNewPanel = ({
206
198
  ? activeConfig.variants[0]
207
199
  : 'default';
208
200
 
201
+ const injectTopic = (template: string) => {
202
+ if (!template) return '';
203
+ return template.replace('[topic]', topic.trim() || '[topic]');
204
+ };
205
+
209
206
  if (layoutChoice === 'standard') {
210
- const newText = copyPromptGroup[variant] || '';
211
- setPromptValue(newText);
207
+ const baseText = copyPromptGroup[variant] || '';
208
+ setPromptValue(injectTopic(baseText));
212
209
  } else if (layoutChoice === 'grid') {
213
210
  const preset = copyPromptGroup.presets?.[variant];
214
211
  if (preset) {
215
- setOverallPrompt(preset.default || '');
212
+ setOverallPrompt(injectTopic(preset.default || ''));
216
213
  setPromptValueCol1(preset.left?.prompt || '');
217
214
  setPromptValueCol2(preset.right?.prompt || '');
218
215
  }
219
216
  }
220
- }, [selectedPromptId, layoutChoice]);
217
+ }, [selectedPromptId, layoutChoice, topic]);
221
218
 
222
219
  const handleInitialChoice = (choice: InitialChoice) => {
223
220
  setInitialChoice(choice);
@@ -228,32 +225,22 @@ const AddPaneNewPanel = ({
228
225
  } else if (choice === 'library') {
229
226
  setStep('designLibrary');
230
227
  } else if (choice === 'ai') {
231
- setStep('layoutChoice');
228
+ setStep('dashboard');
232
229
  }
233
230
  };
234
231
 
235
- const handleLayoutChoice = (choice: LayoutChoice) => {
236
- setLayoutChoice(choice);
237
- setStep('aiDesign');
238
- };
239
-
240
232
  const handleBack = () => {
241
233
  setError(null);
242
- if (step === 'copyInput') {
243
- if (initialChoice === 'library') {
244
- setStep('designLibrary');
245
- } else if (initialChoice === 'ai') {
246
- setStep('aiDesign');
247
- } else {
248
- setStep('initial');
249
- }
250
- } else if (step === 'aiDesign') {
251
- setStep('layoutChoice');
252
- } else if (step === 'directInject') {
253
- setStep('aiDesign');
254
- } else if (step === 'layoutChoice') {
234
+ if (step === 'dashboard') {
255
235
  setStep('initial');
256
- } else if (step === 'designLibrary' || step === 'error') {
236
+ setTopic('');
237
+ setShowAdvancedPrompts(false);
238
+ setShowStyles(false);
239
+ } else if (
240
+ step === 'designLibrary' ||
241
+ step === 'error' ||
242
+ step === 'directInject'
243
+ ) {
257
244
  setStep('initial');
258
245
  }
259
246
  };
@@ -280,10 +267,6 @@ const AddPaneNewPanel = ({
280
267
  handleApplyTemplate(blankTemplate);
281
268
  };
282
269
 
283
- const handleAiDesignContinue = () => {
284
- setStep('copyInput');
285
- };
286
-
287
270
  const handleApplyTemplate = async (template: any) => {
288
271
  if (!ctx) return;
289
272
  try {
@@ -347,8 +330,7 @@ const AddPaneNewPanel = ({
347
330
  } else {
348
331
  setCopyMode('prompt');
349
332
  }
350
-
351
- setStep('copyInput');
333
+ setStep('dashboard');
352
334
  };
353
335
 
354
336
  const handleFinalGenerate = useCallback(async () => {
@@ -571,39 +553,47 @@ const AddPaneNewPanel = ({
571
553
  isSandboxMode
572
554
  );
573
555
 
574
- const copyPromptKey = activeConfig.prompts.copy;
575
- const copyPromptDetails = promptMap[copyPromptKey];
576
- const preset =
577
- copyPromptDetails.presets?.[activeConfig.variants[0]] ||
578
- copyPromptDetails.presets?.heroDefault;
579
- const copyResults: string[] = [];
580
-
581
- const promptsToRun = [
582
- { prompt: promptValueCol1, presetKey: 'left' as ColumnPresetKey },
583
- { prompt: promptValueCol2, presetKey: 'right' as ColumnPresetKey },
584
- ];
585
-
586
- for (const item of promptsToRun) {
587
- const columnPreset = preset[item.presetKey];
588
- const formattedCopyPrompt = copyPromptDetails.user_template
589
- .replace('{{SHELL_JSON}}', shellResult)
590
- .replace('{{COPY_INPUT}}', overallPrompt)
591
- .replace('{{COLUMN_PROMPT}}', item.prompt)
592
- .replace('{{DESIGN_INPUT}}', designInput)
593
- .replace('{{LAYOUT_TYPE}}', layout)
594
- .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
556
+ if (copyMode === 'raw') {
557
+ const rawContents = [col1Copy, col2Copy];
558
+ const finalPane = parseAiPane(shellResult, rawContents, layout);
559
+ handleApplyTemplate(finalPane);
560
+ } else {
561
+ const copyPromptKey = activeConfig.prompts.copy;
562
+ const copyPromptDetails = promptMap[copyPromptKey];
563
+ const preset =
564
+ copyPromptDetails.presets?.[activeConfig.variants[0]] ||
565
+ copyPromptDetails.presets?.heroDefault;
566
+ const copyResults: string[] = [];
595
567
 
596
- const copyResult = await callAskLemurAPI(
597
- formattedCopyPrompt,
598
- copyPromptDetails.system || '',
599
- false,
600
- isSandboxMode
601
- );
602
- copyResults.push(copyResult);
568
+ const promptsToRun = [
569
+ { prompt: promptValueCol1, presetKey: 'left' as ColumnPresetKey },
570
+ {
571
+ prompt: promptValueCol2,
572
+ presetKey: 'right' as ColumnPresetKey,
573
+ },
574
+ ];
575
+
576
+ for (const item of promptsToRun) {
577
+ const columnPreset = preset[item.presetKey];
578
+ const formattedCopyPrompt = copyPromptDetails.user_template
579
+ .replace('{{SHELL_JSON}}', shellResult)
580
+ .replace('{{COPY_INPUT}}', overallPrompt)
581
+ .replace('{{COLUMN_PROMPT}}', item.prompt)
582
+ .replace('{{DESIGN_INPUT}}', designInput)
583
+ .replace('{{LAYOUT_TYPE}}', layout)
584
+ .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
585
+
586
+ const copyResult = await callAskLemurAPI(
587
+ formattedCopyPrompt,
588
+ copyPromptDetails.system || '',
589
+ false,
590
+ isSandboxMode
591
+ );
592
+ copyResults.push(copyResult);
593
+ }
594
+ const finalPane = parseAiPane(shellResult, copyResults, layout);
595
+ handleApplyTemplate(finalPane);
603
596
  }
604
-
605
- const finalPane = parseAiPane(shellResult, copyResults, layout);
606
- handleApplyTemplate(finalPane);
607
597
  }
608
598
  }
609
599
  } catch (err: any) {
@@ -671,235 +661,62 @@ const AddPaneNewPanel = ({
671
661
  </div>
672
662
  );
673
663
 
674
- const renderLayoutChoiceStep = () => (
675
- <div className="p-4">
676
- <h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
677
- Choose a Layout Structure
678
- </h3>
679
- <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
680
- <button
681
- onClick={() => handleLayoutChoice('standard')}
682
- className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
683
- >
684
- <DocumentIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
685
- <h4 className="font-bold text-gray-800">Standard Layout</h4>
686
- <p className="text-sm text-gray-600">
687
- A single, continuous column of content.
688
- </p>
689
- </button>
690
- <button
691
- onClick={() => handleLayoutChoice('grid')}
692
- className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
693
- >
694
- <SquaresPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
695
- <h4 className="font-bold text-gray-800">2-Column Grid</h4>
696
- <p className="text-sm text-gray-600">
697
- Side-by-side content that stacks on mobile.
698
- </p>
699
- </button>
700
- </div>
701
- <div className="mt-6 flex justify-center">
702
- <button
703
- onClick={handleBack}
704
- 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"
705
- >
706
- ← Back
707
- </button>
708
- </div>
709
- </div>
710
- );
711
-
712
- const renderContentStep = () => {
713
- if (layoutChoice === 'grid') {
714
- const isGenerateDisabled =
715
- copyMode === 'prompt'
716
- ? !overallPrompt.trim() ||
717
- !promptValueCol1.trim() ||
718
- !promptValueCol2.trim()
719
- : copyMode === 'raw'
720
- ? !col1Copy.trim() || !col2Copy.trim()
721
- : false;
722
-
723
- return (
724
- <div className="space-y-4 p-4">
725
- <label className="block text-lg font-bold text-gray-800">
726
- 1. Provide Content
727
- </label>
728
-
729
- <div className="my-2 flex flex-wrap gap-4">
730
- <div className="flex items-center space-x-2">
731
- <input
732
- type="radio"
733
- checked={copyMode === 'prompt'}
734
- onChange={() => setCopyMode('prompt')}
735
- className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
736
- />
737
- <label className="text-sm font-bold text-gray-700">Prompt</label>
738
- </div>
739
- <div className="flex items-center space-x-2">
740
- <input
741
- type="radio"
742
- checked={copyMode === 'raw'}
743
- onChange={() => setCopyMode('raw')}
744
- className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
745
- />
746
- <label className="text-sm font-bold text-gray-700">
747
- Manual Markdown
748
- </label>
749
- </div>
750
- {selectedLibraryEntry?.retain && (
751
- <div className="flex items-center space-x-2">
752
- <input
753
- type="radio"
754
- checked={copyMode === 'original'}
755
- onChange={() => setCopyMode('original')}
756
- className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
757
- />
758
- <label className="text-sm font-bold text-gray-700">
759
- Use Original
760
- </label>
761
- </div>
762
- )}
763
- </div>
764
-
765
- {copyMode === 'raw' && initialChoice === 'library' && (
766
- <div className="mb-4 flex items-center justify-between rounded-lg border border-gray-100 bg-gray-50 p-2">
767
- <span className="text-sm text-gray-600">
768
- Style this content with AI?
769
- </span>
770
- <div className="flex items-center">
771
- <BooleanToggle
772
- label="AI Styles"
773
- value={isAiStyling}
774
- onChange={setIsAiStyling}
775
- size="sm"
776
- />
777
- </div>
778
- </div>
779
- )}
780
-
781
- {copyMode === 'prompt' && (
782
- <>
783
- <div className="mb-4">
784
- <EnumSelect
785
- label="Section Type"
786
- value={selectedPromptId}
787
- onChange={setSelectedPromptId}
788
- options={promptOptions}
789
- placeholder="Select a section type..."
790
- className="w-full"
791
- />
792
- </div>
793
- <div>
794
- <label className="mb-2 block text-sm font-bold text-gray-700">
795
- Overall Component Brief
796
- </label>
797
- <textarea
798
- value={overallPrompt}
799
- onChange={(e) => setOverallPrompt(e.target.value)}
800
- rows={3}
801
- className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
802
- />
803
- <p className="mt-1 text-xs text-gray-500">
804
- This context is applied to both columns.
805
- </p>
806
- </div>
807
- <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
808
- <div>
809
- <label className="mb-2 block text-sm font-bold text-gray-700">
810
- Left Column Prompt
811
- </label>
812
- <textarea
813
- value={promptValueCol1}
814
- onChange={(e) => setPromptValueCol1(e.target.value)}
815
- rows={4}
816
- className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
817
- />
818
- </div>
819
- <div>
820
- <label className="mb-2 block text-sm font-bold text-gray-700">
821
- Right Column Prompt
822
- </label>
823
- <textarea
824
- value={promptValueCol2}
825
- onChange={(e) => setPromptValueCol2(e.target.value)}
826
- rows={4}
827
- className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
828
- />
829
- </div>
830
- </div>
831
- </>
832
- )}
833
-
834
- {copyMode === 'raw' && (
835
- <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
836
- <div>
837
- <label className="mb-2 block text-sm font-bold text-gray-700">
838
- Left Column Markdown
839
- </label>
840
- <textarea
841
- value={col1Copy}
842
- onChange={(e) => setCol1Copy(e.target.value)}
843
- rows={8}
844
- className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
845
- />
846
- </div>
847
- <div>
848
- <label className="mb-2 block text-sm font-bold text-gray-700">
849
- Right Column Markdown
850
- </label>
851
- <textarea
852
- value={col2Copy}
853
- onChange={(e) => setCol2Copy(e.target.value)}
854
- rows={8}
855
- className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
856
- />
857
- </div>
858
- </div>
859
- )}
860
-
861
- {copyMode === 'original' && (
862
- <div className="rounded-md border border-blue-200 bg-blue-50 p-4 text-blue-700">
863
- <p className="text-sm">
864
- The original text saved with this design will be used.
865
- </p>
664
+ const renderDashboard = () => {
665
+ return (
666
+ <div className="space-y-6 p-4">
667
+ {/* Layout Selection */}
668
+ {initialChoice === 'ai' && (
669
+ <div className="flex justify-center border-b pb-4">
670
+ <div className="inline-flex rounded-lg bg-gray-100 p-1">
671
+ <button
672
+ onClick={() => setLayoutChoice('standard')}
673
+ className={`flex items-center space-x-2 rounded-md px-4 py-2 text-sm font-bold transition-all ${
674
+ layoutChoice === 'standard'
675
+ ? 'bg-white text-cyan-700 shadow-sm'
676
+ : 'text-gray-500 hover:text-gray-900'
677
+ }`}
678
+ >
679
+ <DocumentIcon className="h-5 w-5" />
680
+ <span>Standard Layout</span>
681
+ </button>
682
+ <button
683
+ onClick={() => setLayoutChoice('grid')}
684
+ className={`flex items-center space-x-2 rounded-md px-4 py-2 text-sm font-bold transition-all ${
685
+ layoutChoice === 'grid'
686
+ ? 'bg-white text-cyan-700 shadow-sm'
687
+ : 'text-gray-500 hover:text-gray-900'
688
+ }`}
689
+ >
690
+ <SquaresPlusIcon className="h-5 w-5" />
691
+ <span>2-Column Grid</span>
692
+ </button>
866
693
  </div>
867
- )}
868
-
869
- <div className="flex justify-between">
870
- <button
871
- onClick={handleBack}
872
- 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"
873
- >
874
- ← Back
875
- </button>
876
- <button
877
- onClick={handleFinalGenerate}
878
- disabled={isGenerateDisabled}
879
- className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
880
- >
881
- ✨ Generate Pane
882
- </button>
883
694
  </div>
884
- </div>
885
- );
886
- }
695
+ )}
887
696
 
888
- return (
889
- <div className="space-y-4 p-4">
890
697
  <CopyInputStep
698
+ layoutChoice={layoutChoice}
891
699
  copyMode={copyMode}
892
700
  onCopyModeChange={setCopyMode}
701
+ topic={topic}
702
+ onTopicChange={setTopic}
703
+ showAdvancedPrompts={showAdvancedPrompts}
704
+ onShowAdvancedPromptsChange={setShowAdvancedPrompts}
893
705
  promptValue={promptValue}
894
706
  onPromptValueChange={setPromptValue}
895
707
  copyValue={copyValue}
896
708
  onCopyValueChange={setCopyValue}
709
+ overallPrompt={overallPrompt}
710
+ onOverallPromptChange={setOverallPrompt}
711
+ promptValueCol1={promptValueCol1}
712
+ onPromptValueCol1Change={setPromptValueCol1}
713
+ promptValueCol2={promptValueCol2}
714
+ onPromptValueCol2Change={setPromptValueCol2}
715
+ col1Copy={col1Copy}
716
+ onCol1CopyChange={setCol1Copy}
717
+ col2Copy={col2Copy}
718
+ onCol2CopyChange={setCol2Copy}
897
719
  hasRetainedContent={selectedLibraryEntry?.retain}
898
- defaultPrompt={
899
- first
900
- ? prompts.aiPaneCopyPrompt.heroDefault
901
- : prompts.aiPaneCopyPrompt.contentDefault
902
- }
903
720
  promptOptions={promptOptions}
904
721
  selectedPromptId={selectedPromptId}
905
722
  onSelectedPromptIdChange={setSelectedPromptId}
@@ -907,27 +724,61 @@ const AddPaneNewPanel = ({
907
724
  onIsAiStylingChange={setIsAiStyling}
908
725
  showStyleToggle={initialChoice === 'library'}
909
726
  />
910
- <div className="flex justify-between">
727
+
728
+ {initialChoice === 'ai' && (
729
+ <>
730
+ <div className="my-4 flex items-center">
731
+ <BooleanToggle
732
+ label="Customize Styles"
733
+ value={showStyles}
734
+ onChange={setShowStyles}
735
+ size="sm"
736
+ />
737
+ </div>
738
+
739
+ {showStyles && (
740
+ <div className="rounded-lg border border-gray-100 bg-gray-50 p-4">
741
+ <AiDesignStep
742
+ designConfig={aiDesignConfig}
743
+ onDesignConfigChange={setAiDesignConfig}
744
+ />
745
+ </div>
746
+ )}
747
+ </>
748
+ )}
749
+
750
+ <div className="flex justify-between pt-4">
911
751
  <button
912
752
  onClick={handleBack}
913
753
  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"
914
754
  >
915
- ← Back
755
+ Cancel
916
756
  </button>
917
757
  <button
918
758
  onClick={handleFinalGenerate}
919
759
  disabled={
920
760
  copyMode === 'prompt'
921
- ? !promptValue.trim()
761
+ ? !promptValue.trim() && !overallPrompt.trim()
922
762
  : copyMode === 'raw'
923
- ? !copyValue.trim()
763
+ ? !copyValue.trim() && (!col1Copy.trim() || !col2Copy.trim())
924
764
  : false
925
765
  }
926
- className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
766
+ 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"
927
767
  >
928
768
  ✨ Generate Pane
929
769
  </button>
930
770
  </div>
771
+
772
+ {initialChoice === 'ai' && !isSandboxMode && (
773
+ <div className="text-center text-sm text-gray-600">
774
+ <button
775
+ onClick={() => setStep('directInject')}
776
+ className="font-bold text-cyan-700 underline hover:text-cyan-900 focus:outline-none"
777
+ >
778
+ Direct Inject
779
+ </button>
780
+ </div>
781
+ )}
931
782
  </div>
932
783
  );
933
784
  };
@@ -946,40 +797,6 @@ const AddPaneNewPanel = ({
946
797
  </div>
947
798
  );
948
799
 
949
- const renderAiDesignStep = () => (
950
- <div className="space-y-4 p-4">
951
- <AiDesignStep
952
- designConfig={aiDesignConfig}
953
- onDesignConfigChange={setAiDesignConfig}
954
- />
955
- <div className="flex justify-between">
956
- <button
957
- onClick={handleBack}
958
- 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"
959
- >
960
- ← Back
961
- </button>
962
- <button
963
- onClick={handleAiDesignContinue}
964
- className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700"
965
- >
966
- Continue →
967
- </button>
968
- </div>
969
- {initialChoice === `ai` && !isSandboxMode && (
970
- <div className="mt-6 text-center text-sm text-gray-600">
971
- ADVANCED:{' '}
972
- <button
973
- onClick={() => setStep('directInject')}
974
- className="font-bold text-cyan-700 underline hover:text-cyan-900 focus:outline-none"
975
- >
976
- Direct Inject
977
- </button>
978
- </div>
979
- )}
980
- </div>
981
- );
982
-
983
800
  const renderDirectInjectStep = () => (
984
801
  <DirectInjectStep
985
802
  onBack={handleBack}
@@ -1013,14 +830,10 @@ const AddPaneNewPanel = ({
1013
830
  switch (step) {
1014
831
  case 'initial':
1015
832
  return renderInitialStep();
1016
- case 'layoutChoice':
1017
- return renderLayoutChoiceStep();
1018
- case 'copyInput':
1019
- return renderContentStep();
833
+ case 'dashboard':
834
+ return renderDashboard();
1020
835
  case 'designLibrary':
1021
836
  return renderDesignLibraryStep();
1022
- case 'aiDesign':
1023
- return renderAiDesignStep();
1024
837
  case 'loading':
1025
838
  return renderLoading();
1026
839
  case 'directInject':
@@ -211,7 +211,8 @@ export const RestylePaneModal = () => {
211
211
  (entry: DesignLibraryEntry) =>
212
212
  (selectedCategory === 'all' || entry.category === selectedCategory) &&
213
213
  entry.title.toLowerCase().includes(searchTerm.toLowerCase()) &&
214
- entry.markdownCount === targetMarkdownCount
214
+ entry.markdownCount === targetMarkdownCount &&
215
+ !entry.locked
215
216
  );
216
217
  }, [designLibrary, selectedCategory, searchTerm, targetMarkdownCount]);
217
218
 
@@ -350,14 +351,15 @@ export const RestylePaneModal = () => {
350
351
  <Dialog.Root
351
352
  open={isRestyleModalOpen}
352
353
  onOpenChange={handleDialogStateChange}
353
- modal={false}
354
+ modal={true}
355
+ preventScroll={true}
354
356
  >
355
- <Dialog.Backdrop className="z-103 fixed inset-0 bg-black/70" />
357
+ <Dialog.Backdrop className="z-103 fixed inset-0 bg-black bg-opacity-75" />
356
358
  <Dialog.Positioner className="z-104 fixed inset-0 flex items-center justify-center">
357
359
  <Dialog.Content
358
360
  ref={contentRef}
359
- className="flex flex-col rounded-lg bg-white shadow-2xl"
360
- style={{ maxHeight: '90vw', width: '90vw' }}
361
+ className="flex max-w-5xl flex-col rounded-lg bg-white shadow-2xl xl:max-w-7xl"
362
+ style={{ maxHeight: '90vh', width: '90vw' }}
361
363
  >
362
364
  <header className="flex items-center justify-between border-b p-4">
363
365
  <Dialog.Title className="text-xl font-bold">
@@ -33,10 +33,7 @@ export const AiDesignStep = ({
33
33
  };
34
34
 
35
35
  return (
36
- <div className="space-y-6 rounded-lg bg-gray-50 p-4 shadow-inner">
37
- <label className="block text-lg font-bold text-gray-800">
38
- 2. Configure AI Design
39
- </label>
36
+ <div className="space-y-6">
40
37
  <div>
41
38
  <label className="block text-base font-bold text-gray-800">
42
39
  Color Harmony
@@ -1,4 +1,3 @@
1
- import { useEffect } from 'react';
2
1
  import BooleanToggle from '@/components/form/BooleanToggle';
3
2
  import EnumSelect from '@/components/form/EnumSelect';
4
3
 
@@ -10,13 +9,32 @@ interface PromptOption {
10
9
  }
11
10
 
12
11
  interface CopyInputStepProps {
12
+ layoutChoice: 'standard' | 'grid';
13
13
  copyMode: CopyMode;
14
14
  onCopyModeChange: (mode: CopyMode) => void;
15
+
16
+ topic: string;
17
+ onTopicChange: (value: string) => void;
18
+
19
+ showAdvancedPrompts: boolean;
20
+ onShowAdvancedPromptsChange: (value: boolean) => void;
21
+
15
22
  promptValue: string;
16
23
  onPromptValueChange: (value: string) => void;
17
24
  copyValue: string;
18
25
  onCopyValueChange: (value: string) => void;
19
- defaultPrompt?: string;
26
+
27
+ overallPrompt: string;
28
+ onOverallPromptChange: (value: string) => void;
29
+ promptValueCol1: string;
30
+ onPromptValueCol1Change: (value: string) => void;
31
+ promptValueCol2: string;
32
+ onPromptValueCol2Change: (value: string) => void;
33
+ col1Copy: string;
34
+ onCol1CopyChange: (value: string) => void;
35
+ col2Copy: string;
36
+ onCol2CopyChange: (value: string) => void;
37
+
20
38
  hasRetainedContent?: boolean;
21
39
  promptOptions: PromptOption[];
22
40
  selectedPromptId: string;
@@ -27,13 +45,27 @@ interface CopyInputStepProps {
27
45
  }
28
46
 
29
47
  export const CopyInputStep = ({
48
+ layoutChoice,
30
49
  copyMode,
31
50
  onCopyModeChange,
51
+ topic,
52
+ onTopicChange,
53
+ showAdvancedPrompts,
54
+ onShowAdvancedPromptsChange,
32
55
  promptValue,
33
56
  onPromptValueChange,
34
57
  copyValue,
35
58
  onCopyValueChange,
36
- defaultPrompt,
59
+ overallPrompt,
60
+ onOverallPromptChange,
61
+ promptValueCol1,
62
+ onPromptValueCol1Change,
63
+ promptValueCol2,
64
+ onPromptValueCol2Change,
65
+ col1Copy,
66
+ onCol1CopyChange,
67
+ col2Copy,
68
+ onCol2CopyChange,
37
69
  hasRetainedContent = false,
38
70
  promptOptions,
39
71
  selectedPromptId,
@@ -42,77 +74,182 @@ export const CopyInputStep = ({
42
74
  onIsAiStylingChange,
43
75
  showStyleToggle = true,
44
76
  }: CopyInputStepProps) => {
45
- useEffect(() => {
46
- if (defaultPrompt && !promptValue) {
47
- onPromptValueChange(defaultPrompt);
48
- }
49
- }, [defaultPrompt, promptValue, onPromptValueChange]);
50
-
51
- return (
52
- <div className="space-y-4 rounded-lg bg-gray-50 p-4 shadow-inner">
53
- <label className="block text-lg font-bold text-gray-800">
54
- 1. Provide Content
55
- </label>
56
- <div className="my-2 flex flex-wrap gap-4">
77
+ const renderModeSelection = () => (
78
+ <div className="my-2 flex flex-wrap gap-4">
79
+ <div className="flex items-center space-x-2">
80
+ <input
81
+ type="radio"
82
+ id="copy-prompt-mode"
83
+ name="copyModeOptions"
84
+ value="prompt"
85
+ checked={copyMode === 'prompt'}
86
+ onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
87
+ className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
88
+ />
89
+ <label
90
+ htmlFor="copy-prompt-mode"
91
+ className="text-sm font-bold text-gray-700"
92
+ >
93
+ Write a prompt
94
+ </label>
95
+ </div>
96
+ <div className="flex items-center space-x-2">
97
+ <input
98
+ type="radio"
99
+ id="copy-raw-mode"
100
+ name="copyModeOptions"
101
+ value="raw"
102
+ checked={copyMode === 'raw'}
103
+ onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
104
+ className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
105
+ />
106
+ <label
107
+ htmlFor="copy-raw-mode"
108
+ className="text-sm font-bold text-gray-700"
109
+ >
110
+ Provide Copy (Markdown)
111
+ </label>
112
+ </div>
113
+ {hasRetainedContent && (
57
114
  <div className="flex items-center space-x-2">
58
115
  <input
59
116
  type="radio"
60
- id="copy-prompt-mode"
117
+ id="copy-original-mode"
61
118
  name="copyModeOptions"
62
- value="prompt"
63
- checked={copyMode === 'prompt'}
119
+ value="original"
120
+ checked={copyMode === 'original'}
64
121
  onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
65
122
  className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
66
123
  />
67
124
  <label
68
- htmlFor="copy-prompt-mode"
125
+ htmlFor="copy-original-mode"
69
126
  className="text-sm font-bold text-gray-700"
70
127
  >
71
- Write a prompt
128
+ Use Original
72
129
  </label>
73
130
  </div>
74
- <div className="flex items-center space-x-2">
75
- <input
76
- type="radio"
77
- id="copy-raw-mode"
78
- name="copyModeOptions"
79
- value="raw"
80
- checked={copyMode === 'raw'}
81
- onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
82
- className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
83
- />
84
- <label
85
- htmlFor="copy-raw-mode"
86
- className="text-sm font-bold text-gray-700"
87
- >
88
- Provide Copy (Markdown)
89
- </label>
131
+ )}
132
+ </div>
133
+ );
134
+
135
+ const renderPromptMode = () => (
136
+ <>
137
+ <div className="mb-4">
138
+ <EnumSelect
139
+ label="Section Type"
140
+ value={selectedPromptId}
141
+ onChange={onSelectedPromptIdChange}
142
+ options={promptOptions}
143
+ placeholder="Select a type..."
144
+ className="w-full"
145
+ />
146
+ </div>
147
+
148
+ <div className="mb-4">
149
+ <label className="mb-2 block text-sm font-bold text-gray-700">
150
+ Topic / Context
151
+ </label>
152
+ <textarea
153
+ value={topic}
154
+ onChange={(e) => onTopicChange(e.target.value)}
155
+ placeholder="e.g. a SaaS product for team collaboration"
156
+ rows={2}
157
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
158
+ />
159
+ </div>
160
+
161
+ <div className="mb-4 flex items-center">
162
+ <BooleanToggle
163
+ label="Advanced: Edit Full Prompts"
164
+ value={showAdvancedPrompts}
165
+ onChange={onShowAdvancedPromptsChange}
166
+ size="sm"
167
+ />
168
+ </div>
169
+
170
+ {showAdvancedPrompts && (
171
+ <div className="mt-4 space-y-4 rounded-md border border-gray-200 bg-white p-4">
172
+ {layoutChoice === 'standard' ? (
173
+ <div>
174
+ <label className="mb-2 block text-sm font-bold text-gray-700">
175
+ Full Prompt
176
+ </label>
177
+ <textarea
178
+ value={promptValue}
179
+ onChange={(e) => onPromptValueChange(e.target.value)}
180
+ rows={4}
181
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
182
+ />
183
+ <p className="mt-1 text-xs text-gray-500">
184
+ Leave [topic] as it will be replaced with your prompt.
185
+ </p>
186
+ </div>
187
+ ) : (
188
+ <>
189
+ <div>
190
+ <label className="mb-2 block text-sm font-bold text-gray-700">
191
+ Overall Component Brief
192
+ </label>
193
+ <textarea
194
+ value={overallPrompt}
195
+ onChange={(e) => onOverallPromptChange(e.target.value)}
196
+ rows={3}
197
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
198
+ />
199
+ </div>
200
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
201
+ <div>
202
+ <label className="mb-2 block text-sm font-bold text-gray-700">
203
+ Left Column Prompt
204
+ </label>
205
+ <textarea
206
+ value={promptValueCol1}
207
+ onChange={(e) => onPromptValueCol1Change(e.target.value)}
208
+ rows={4}
209
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
210
+ />
211
+ </div>
212
+ <div>
213
+ <label className="mb-2 block text-sm font-bold text-gray-700">
214
+ Right Column Prompt
215
+ </label>
216
+ <textarea
217
+ value={promptValueCol2}
218
+ onChange={(e) => onPromptValueCol2Change(e.target.value)}
219
+ rows={4}
220
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
221
+ />
222
+ </div>
223
+ </div>
224
+ </>
225
+ )}
90
226
  </div>
91
- {hasRetainedContent && (
92
- <div className="flex items-center space-x-2">
93
- <input
94
- type="radio"
95
- id="copy-original-mode"
96
- name="copyModeOptions"
97
- value="original"
98
- checked={copyMode === 'original'}
99
- onChange={(e) => onCopyModeChange(e.target.value as CopyMode)}
100
- className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
227
+ )}
228
+ </>
229
+ );
230
+
231
+ const renderRawMode = () => (
232
+ <>
233
+ <div className="mb-2 flex items-center justify-between">
234
+ <p className="text-sm text-gray-500">
235
+ Provide your raw copy here. Use Markdown.
236
+ </p>
237
+ {showStyleToggle && (
238
+ <div className="flex items-center">
239
+ <BooleanToggle
240
+ label="Style with AI"
241
+ value={isAiStyling}
242
+ onChange={onIsAiStylingChange}
243
+ size="sm"
101
244
  />
102
- <label
103
- htmlFor="copy-original-mode"
104
- className="text-sm font-bold text-gray-700"
105
- >
106
- Use Original
107
- </label>
108
245
  </div>
109
246
  )}
110
247
  </div>
111
248
 
112
- {(copyMode === 'prompt' || (copyMode === 'raw' && isAiStyling)) && (
249
+ {isAiStyling && (
113
250
  <div className="mb-4">
114
251
  <EnumSelect
115
- label="Section Type"
252
+ label="Section Type (for Styling)"
116
253
  value={selectedPromptId}
117
254
  onChange={onSelectedPromptIdChange}
118
255
  options={promptOptions}
@@ -122,49 +259,54 @@ export const CopyInputStep = ({
122
259
  </div>
123
260
  )}
124
261
 
125
- {copyMode === 'prompt' && (
126
- <>
127
- <p className="mb-2 text-sm text-gray-500">
128
- Let the AI write the copy based on your prompt.
129
- </p>
130
- <textarea
131
- id="copy-prompt"
132
- value={promptValue}
133
- onChange={(e) => onPromptValueChange(e.target.value)}
134
- placeholder="e.g., A hero section for a SaaS product that helps teams collaborate..."
135
- rows={4}
136
- className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
137
- />
138
- </>
139
- )}
140
-
141
- {copyMode === 'raw' && (
142
- <>
143
- <div className="mb-2 flex items-center justify-between">
144
- <p className="text-sm text-gray-500">
145
- Provide your raw copy here. Use Markdown.
146
- </p>
147
- {showStyleToggle && (
148
- <div className="flex items-center">
149
- <BooleanToggle
150
- label="Style with AI"
151
- value={isAiStyling}
152
- onChange={onIsAiStylingChange}
153
- size="sm"
154
- />
155
- </div>
156
- )}
262
+ {layoutChoice === 'standard' ? (
263
+ <textarea
264
+ id="raw-copy"
265
+ value={copyValue}
266
+ onChange={(e) => onCopyValueChange(e.target.value)}
267
+ placeholder="## My Awesome Headline..."
268
+ rows={6}
269
+ className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
270
+ />
271
+ ) : (
272
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
273
+ <div>
274
+ <label className="mb-2 block text-sm font-bold text-gray-700">
275
+ Left Column Markdown
276
+ </label>
277
+ <textarea
278
+ value={col1Copy}
279
+ onChange={(e) => onCol1CopyChange(e.target.value)}
280
+ rows={8}
281
+ className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
282
+ />
157
283
  </div>
158
- <textarea
159
- id="raw-copy"
160
- value={copyValue}
161
- onChange={(e) => onCopyValueChange(e.target.value)}
162
- placeholder="## My Awesome Headline..."
163
- rows={6}
164
- className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
165
- />
166
- </>
284
+ <div>
285
+ <label className="mb-2 block text-sm font-bold text-gray-700">
286
+ Right Column Markdown
287
+ </label>
288
+ <textarea
289
+ value={col2Copy}
290
+ onChange={(e) => onCol2CopyChange(e.target.value)}
291
+ rows={8}
292
+ className="block w-full rounded-md border-gray-300 p-2 font-mono text-sm shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
293
+ />
294
+ </div>
295
+ </div>
167
296
  )}
297
+ </>
298
+ );
299
+
300
+ return (
301
+ <>
302
+ <label className="block text-lg font-bold text-gray-800">
303
+ Content Configuration
304
+ </label>
305
+
306
+ {renderModeSelection()}
307
+
308
+ {copyMode === 'prompt' && renderPromptMode()}
309
+ {copyMode === 'raw' && renderRawMode()}
168
310
 
169
311
  {copyMode === 'original' && (
170
312
  <div className="rounded-md border border-blue-200 bg-blue-50 p-4 text-blue-700">
@@ -173,6 +315,6 @@ export const CopyInputStep = ({
173
315
  </p>
174
316
  </div>
175
317
  )}
176
- </div>
318
+ </>
177
319
  );
178
320
  };