Skip to content

Commit 57878ee

Browse files
authored
Add loading state BEN-1074 (#24)
3# Rename Builder to Chat Page with Improved Generation Flow ### TL;DR Renamed `/builder` to `/chat` page with a new step-by-step UI generation flow that provides visual feedback during component creation. ### What changed? - Renamed `/builder/page.tsx` to `/chat/page.tsx` for better semantic clarity - Added a visual step-by-step generation process with progress indicators - Implemented automatic component generation on page load instead of pre-generating - Enhanced `PreviewCard` to display generation steps with visual indicators (analyzing, generating, building, deploying) - Updated `PromptForm` to immediately navigate to the chat page without waiting for generation to complete - Added proper loading and error states throughout the generation flow ### How to test? 1. Enter a component description on the home page and click "Build it" 2. You should be immediately redirected to the `/chat` page 3. Verify the step-by-step generation process appears with animated indicators 4. Once generation completes, confirm the preview displays correctly 5. Test the code editor tab to ensure it shows the generated files ### Why make this change? This change improves the user experience by providing immediate feedback during the component generation process. Instead of making users wait on the home page during generation, they're immediately taken to the chat interface where they can see the progress. The step-by-step indicators give users a better understanding of what's happening behind the scenes, reducing perceived wait time and increasing engagement.
2 parents 533a0e5 + 6d048f2 commit 57878ee

File tree

4 files changed

+236
-116
lines changed

4 files changed

+236
-116
lines changed

app/builder/page.tsx

Lines changed: 0 additions & 64 deletions
This file was deleted.

app/chat/page.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { ChatInterface } from '@/components/ui-builder/chat-interface';
6+
import { PreviewCard } from '@/components/ui-builder/preview-card';
7+
import { benchifyFileSchema } from '@/lib/schemas';
8+
import { z } from 'zod';
9+
10+
type GenerationResult = {
11+
repairedFiles?: z.infer<typeof benchifyFileSchema>;
12+
originalFiles?: z.infer<typeof benchifyFileSchema>;
13+
buildOutput: string;
14+
previewUrl: string;
15+
};
16+
17+
export default function ChatPage() {
18+
const router = useRouter();
19+
const [result, setResult] = useState<GenerationResult | null>(null);
20+
const [initialPrompt, setInitialPrompt] = useState<string>('');
21+
const [isGenerating, setIsGenerating] = useState(false);
22+
23+
useEffect(() => {
24+
// Get the prompt and check if we have an existing result
25+
const storedPrompt = sessionStorage.getItem('initialPrompt');
26+
const storedResult = sessionStorage.getItem('builderResult');
27+
28+
if (!storedPrompt) {
29+
// If no prompt found, redirect back to home
30+
router.push('/');
31+
return;
32+
}
33+
34+
setInitialPrompt(storedPrompt);
35+
36+
if (storedResult) {
37+
// If we have a stored result, use it
38+
setResult(JSON.parse(storedResult));
39+
} else {
40+
// If no result, start the generation process
41+
setIsGenerating(true);
42+
startGeneration(storedPrompt);
43+
}
44+
}, [router]);
45+
46+
const startGeneration = async (prompt: string) => {
47+
try {
48+
const response = await fetch('/api/generate', {
49+
method: 'POST',
50+
headers: {
51+
'Content-Type': 'application/json',
52+
},
53+
body: JSON.stringify({
54+
type: 'component',
55+
description: prompt,
56+
preview: true,
57+
}),
58+
});
59+
60+
if (!response.ok) {
61+
throw new Error('Failed to generate component');
62+
}
63+
64+
const generationResult = await response.json();
65+
66+
// Store the result
67+
sessionStorage.setItem('builderResult', JSON.stringify(generationResult));
68+
setResult(generationResult);
69+
setIsGenerating(false);
70+
} catch (error) {
71+
console.error('Error generating component:', error);
72+
setIsGenerating(false);
73+
// Handle error state
74+
}
75+
};
76+
77+
// Show loading spinner if we don't have prompt yet
78+
if (!initialPrompt) {
79+
return (
80+
<div className="min-h-screen flex items-center justify-center">
81+
<div className="text-center">
82+
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary mx-auto mb-4"></div>
83+
<p className="text-muted-foreground">Loading...</p>
84+
</div>
85+
</div>
86+
);
87+
}
88+
89+
return (
90+
<div className="min-h-screen bg-background flex">
91+
{/* Chat Interface - Left Side */}
92+
<div className="w-1/4 min-w-80 border-r border-border bg-card flex-shrink-0">
93+
<ChatInterface
94+
initialPrompt={initialPrompt}
95+
onUpdateResult={setResult}
96+
/>
97+
</div>
98+
99+
{/* Preview Area - Right Side */}
100+
<div className="flex-1 p-4 overflow-hidden">
101+
<PreviewCard
102+
previewUrl={result?.previewUrl}
103+
code={result?.repairedFiles || result?.originalFiles || []}
104+
isGenerating={isGenerating}
105+
prompt={initialPrompt}
106+
/>
107+
</div>
108+
</div>
109+
);
110+
}

components/ui-builder/preview-card.tsx

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,70 @@
1+
import { useEffect, useState } from 'react';
2+
import { CheckCircle, Circle, Loader2 } from 'lucide-react';
13
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
25
import { benchifyFileSchema } from "@/lib/schemas";
36
import { z } from "zod";
47
import { CodeEditor } from "./code-editor";
58

9+
interface Step {
10+
id: string;
11+
label: string;
12+
description: string;
13+
}
14+
15+
const GENERATION_STEPS: Step[] = [
16+
{
17+
id: 'analyzing',
18+
label: 'Analyzing Request',
19+
description: 'Understanding your requirements and design specifications',
20+
},
21+
{
22+
id: 'generating',
23+
label: 'Generating Code',
24+
description: 'Creating UI components with AI assistance',
25+
},
26+
{
27+
id: 'building',
28+
label: 'Building Project',
29+
description: 'Setting up development environment and dependencies',
30+
},
31+
{
32+
id: 'deploying',
33+
label: 'Creating Preview',
34+
description: 'Deploying your project for live preview',
35+
},
36+
];
37+
638
interface PreviewCardProps {
7-
previewUrl: string;
39+
previewUrl?: string;
840
code: z.infer<typeof benchifyFileSchema>;
41+
isGenerating?: boolean;
42+
prompt?: string;
943
}
1044

11-
export function PreviewCard({ previewUrl, code }: PreviewCardProps) {
45+
export function PreviewCard({ previewUrl, code, isGenerating = false, prompt }: PreviewCardProps) {
1246
const files = code || [];
47+
const [currentStep, setCurrentStep] = useState(0);
48+
49+
useEffect(() => {
50+
if (isGenerating) {
51+
// Reset to first step when generation starts
52+
setCurrentStep(0);
53+
54+
// Automatically advance through steps for visual feedback
55+
const stepTimer = setInterval(() => {
56+
setCurrentStep(prev => {
57+
// Cycle through steps, but don't go past the last one
58+
if (prev < GENERATION_STEPS.length - 1) {
59+
return prev + 1;
60+
}
61+
return prev;
62+
});
63+
}, 2000); // Advance every 2 seconds
64+
65+
return () => clearInterval(stepTimer);
66+
}
67+
}, [isGenerating]);
1368

1469
return (
1570
<div className="h-full">
@@ -20,14 +75,69 @@ export function PreviewCard({ previewUrl, code }: PreviewCardProps) {
2075
</TabsList>
2176

2277
<TabsContent value="preview" className="flex-1 m-0">
23-
<div className="w-full h-full overflow-hidden rounded-md border bg-background">
24-
<iframe
25-
title="Preview"
26-
src={previewUrl}
27-
className="w-full h-full"
28-
sandbox="allow-scripts allow-same-origin"
29-
/>
30-
</div>
78+
{isGenerating && prompt ? (
79+
// Show loading progress inside the preview tab
80+
<div className="w-full h-full flex items-center justify-center rounded-md border bg-background">
81+
<Card className="w-full max-w-md mx-auto">
82+
<CardHeader>
83+
<CardTitle>Building Your UI</CardTitle>
84+
<p className="text-sm text-muted-foreground mt-2">
85+
"{prompt.substring(0, 100)}{prompt.length > 100 ? '...' : ''}"
86+
</p>
87+
</CardHeader>
88+
<CardContent className="space-y-4">
89+
{GENERATION_STEPS.map((step, index) => {
90+
const isCompleted = index < currentStep;
91+
const isCurrent = index === currentStep;
92+
93+
return (
94+
<div key={step.id} className="flex items-start space-x-3">
95+
<div className="flex-shrink-0 mt-1">
96+
{isCompleted ? (
97+
<CheckCircle className="h-5 w-5 text-green-500" />
98+
) : isCurrent ? (
99+
<Loader2 className="h-5 w-5 text-primary animate-spin" />
100+
) : (
101+
<Circle className="h-5 w-5 text-muted-foreground" />
102+
)}
103+
</div>
104+
<div className="flex-1 min-w-0">
105+
<p className={`text-sm font-medium ${isCompleted ? 'text-green-700 dark:text-green-400' :
106+
isCurrent ? 'text-primary' :
107+
'text-muted-foreground'
108+
}`}>
109+
{step.label}
110+
</p>
111+
<p className={`text-xs ${isCompleted || isCurrent ? 'text-muted-foreground' : 'text-muted-foreground/60'
112+
}`}>
113+
{step.description}
114+
</p>
115+
</div>
116+
</div>
117+
);
118+
})}
119+
</CardContent>
120+
</Card>
121+
</div>
122+
) : previewUrl ? (
123+
// Show the actual preview iframe when ready
124+
<div className="w-full h-full overflow-hidden rounded-md border bg-background">
125+
<iframe
126+
title="Preview"
127+
src={previewUrl}
128+
className="w-full h-full"
129+
sandbox="allow-scripts allow-same-origin"
130+
/>
131+
</div>
132+
) : (
133+
// Show loading spinner if no preview URL yet
134+
<div className="w-full h-full flex items-center justify-center rounded-md border bg-background">
135+
<div className="text-center">
136+
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary mx-auto mb-4"></div>
137+
<p className="text-muted-foreground">Loading your project...</p>
138+
</div>
139+
</div>
140+
)}
31141
</TabsContent>
32142

33143
<TabsContent value="code" className="flex-1 m-0">

0 commit comments

Comments
 (0)