astro-tractstack 2.0.42 → 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.42",
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",
@@ -34,7 +34,6 @@ const {
34
34
  } = Astro.props;
35
35
 
36
36
  const isHome = slug === brandConfig?.HOME_SLUG;
37
- console.log(slug, isHome);
38
37
 
39
38
  const tenantId =
40
39
  Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
@@ -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':