Skip to content

Commit 46bab4a

Browse files
Juan Castañojuancastano
authored andcommitted
Add chat interface
1 parent c59d8e5 commit 46bab4a

File tree

5 files changed

+292
-58
lines changed

5 files changed

+292
-58
lines changed

app/builder/page.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
export default function BuilderPage() {
11+
const router = useRouter();
12+
const [result, setResult] = useState<{
13+
repairedFiles?: z.infer<typeof benchifyFileSchema>;
14+
originalFiles?: z.infer<typeof benchifyFileSchema>;
15+
buildOutput: string;
16+
previewUrl: string;
17+
} | null>(null);
18+
const [initialPrompt, setInitialPrompt] = useState<string>('');
19+
20+
useEffect(() => {
21+
// Get the result from sessionStorage
22+
const storedResult = sessionStorage.getItem('builderResult');
23+
const storedPrompt = sessionStorage.getItem('initialPrompt');
24+
25+
if (storedResult && storedPrompt) {
26+
setResult(JSON.parse(storedResult));
27+
setInitialPrompt(storedPrompt);
28+
} else {
29+
// If no result found, redirect back to home
30+
router.push('/');
31+
}
32+
}, [router]);
33+
34+
if (!result) {
35+
return (
36+
<div className="min-h-screen flex items-center justify-center">
37+
<div className="text-center">
38+
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary mx-auto mb-4"></div>
39+
<p className="text-muted-foreground">Loading your project...</p>
40+
</div>
41+
</div>
42+
);
43+
}
44+
45+
return (
46+
<div className="min-h-screen bg-background flex">
47+
{/* Chat Interface - Left Side */}
48+
<div className="w-1/4 min-w-80 border-r border-border bg-card flex-shrink-0">
49+
<ChatInterface
50+
initialPrompt={initialPrompt}
51+
onUpdateResult={setResult}
52+
/>
53+
</div>
54+
55+
{/* Preview Area - Right Side */}
56+
<div className="flex-1 p-4 overflow-hidden">
57+
<PreviewCard
58+
previewUrl={result.previewUrl}
59+
code={result.repairedFiles || result.originalFiles || []}
60+
/>
61+
</div>
62+
</div>
63+
);
64+
}

app/page.tsx

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
11
// app/page.tsx
22
'use client';
33

4-
import { useEffect, useState } from 'react';
54
import { PromptForm } from '@/components/ui-builder/prompt-form';
6-
import { PreviewCard } from '@/components/ui-builder/preview-card';
75
import { Card, CardContent } from '@/components/ui/card';
8-
import { benchifyFileSchema } from '@/lib/schemas';
9-
import { z } from 'zod';
106

117
export default function Home() {
12-
const [result, setResult] = useState<{
13-
repairedFiles?: z.infer<typeof benchifyFileSchema>;
14-
originalFiles?: z.infer<typeof benchifyFileSchema>;
15-
buildOutput: string;
16-
previewUrl: string;
17-
} | null>(null);
18-
19-
useEffect(() => {
20-
if (result) {
21-
console.log(result);
22-
}
23-
}, [result]);
24-
258
return (
269
<main className="min-h-screen flex flex-col items-center justify-center bg-background">
2710
<div className="w-full max-w-3xl mx-auto">
@@ -31,18 +14,11 @@ export default function Home() {
3114
<p className="text-lg text-muted-foreground mb-8 text-center">
3215
Generate UI components with AI and automatically repair issues with Benchify
3316
</p>
34-
{!result ? (
35-
<Card className="border-border bg-card">
36-
<CardContent className="pt-6">
37-
<PromptForm onGenerate={setResult} />
38-
</CardContent>
39-
</Card>
40-
) : (
41-
<PreviewCard
42-
previewUrl={result.previewUrl}
43-
code={result.repairedFiles || result.originalFiles || []}
44-
/>
45-
)}
17+
<Card className="border-border bg-card">
18+
<CardContent className="pt-6">
19+
<PromptForm />
20+
</CardContent>
21+
</Card>
4622
</div>
4723
</main>
4824
);
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
'use client';
2+
3+
import { useState, useRef, useEffect } from 'react';
4+
import { Send, RotateCcw } from 'lucide-react';
5+
import { Button } from '@/components/ui/button';
6+
import { Input } from '@/components/ui/input';
7+
import { ScrollArea } from '@/components/ui/scroll-area';
8+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
9+
import { benchifyFileSchema } from '@/lib/schemas';
10+
import { z } from 'zod';
11+
12+
interface Message {
13+
id: string;
14+
type: 'user' | 'assistant';
15+
content: string;
16+
timestamp: Date;
17+
}
18+
19+
interface ChatInterfaceProps {
20+
initialPrompt: string;
21+
onUpdateResult: (result: {
22+
repairedFiles?: z.infer<typeof benchifyFileSchema>;
23+
originalFiles?: z.infer<typeof benchifyFileSchema>;
24+
buildOutput: string;
25+
previewUrl: string;
26+
}) => void;
27+
}
28+
29+
export function ChatInterface({ initialPrompt, onUpdateResult }: ChatInterfaceProps) {
30+
const [messages, setMessages] = useState<Message[]>([
31+
{
32+
id: '1',
33+
type: 'user',
34+
content: initialPrompt,
35+
timestamp: new Date(),
36+
},
37+
{
38+
id: '2',
39+
type: 'assistant',
40+
content: "I've generated your UI component! You can see the preview on the right. What would you like to modify or improve?",
41+
timestamp: new Date(),
42+
},
43+
]);
44+
const [newMessage, setNewMessage] = useState('');
45+
const [isLoading, setIsLoading] = useState(false);
46+
const messagesEndRef = useRef<HTMLDivElement>(null);
47+
48+
const scrollToBottom = () => {
49+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
50+
};
51+
52+
useEffect(() => {
53+
scrollToBottom();
54+
}, [messages]);
55+
56+
const handleSendMessage = async () => {
57+
if (!newMessage.trim() || isLoading) return;
58+
59+
const userMessage: Message = {
60+
id: Date.now().toString(),
61+
type: 'user',
62+
content: newMessage,
63+
timestamp: new Date(),
64+
};
65+
66+
setMessages(prev => [...prev, userMessage]);
67+
setNewMessage('');
68+
setIsLoading(true);
69+
70+
try {
71+
// Here you would call your API to process the new request
72+
// For now, we'll add a placeholder response
73+
const assistantMessage: Message = {
74+
id: (Date.now() + 1).toString(),
75+
type: 'assistant',
76+
content: "I understand your request. Let me update the component for you...",
77+
timestamp: new Date(),
78+
};
79+
80+
setMessages(prev => [...prev, assistantMessage]);
81+
82+
// TODO: Implement actual regeneration with the new prompt
83+
// This would call your generate API with the conversation context
84+
85+
} catch (error) {
86+
console.error('Error processing message:', error);
87+
const errorMessage: Message = {
88+
id: (Date.now() + 1).toString(),
89+
type: 'assistant',
90+
content: "I'm sorry, there was an error processing your request. Please try again.",
91+
timestamp: new Date(),
92+
};
93+
setMessages(prev => [...prev, errorMessage]);
94+
} finally {
95+
setIsLoading(false);
96+
}
97+
};
98+
99+
const handleKeyPress = (e: React.KeyboardEvent) => {
100+
if (e.key === 'Enter' && !e.shiftKey) {
101+
e.preventDefault();
102+
handleSendMessage();
103+
}
104+
};
105+
106+
const handleStartOver = () => {
107+
// Clear session storage and redirect to home
108+
sessionStorage.removeItem('builderResult');
109+
sessionStorage.removeItem('initialPrompt');
110+
window.location.href = '/';
111+
};
112+
113+
return (
114+
<Card className="h-full flex flex-col border-0 rounded-none">
115+
<CardHeader className="pb-3 border-b">
116+
<div className="flex items-center justify-between">
117+
<CardTitle className="text-lg">Chat</CardTitle>
118+
<Button
119+
variant="ghost"
120+
size="sm"
121+
onClick={handleStartOver}
122+
className="h-8 px-2"
123+
>
124+
<RotateCcw className="h-4 w-4 mr-1" />
125+
Start Over
126+
</Button>
127+
</div>
128+
</CardHeader>
129+
130+
<CardContent className="flex-1 flex flex-col p-0">
131+
<ScrollArea className="flex-1 p-4">
132+
<div className="space-y-4">
133+
{messages.map((message) => (
134+
<div
135+
key={message.id}
136+
className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
137+
>
138+
<div
139+
className={`max-w-[80%] p-3 rounded-lg ${message.type === 'user'
140+
? 'bg-primary text-primary-foreground'
141+
: 'bg-muted text-muted-foreground'
142+
}`}
143+
>
144+
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
145+
<p className="text-xs opacity-70 mt-1">
146+
{message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
147+
</p>
148+
</div>
149+
</div>
150+
))}
151+
{isLoading && (
152+
<div className="flex justify-start">
153+
<div className="bg-muted text-muted-foreground p-3 rounded-lg">
154+
<div className="flex items-center space-x-2">
155+
<div className="animate-pulse flex space-x-1">
156+
<div className="w-2 h-2 bg-current rounded-full"></div>
157+
<div className="w-2 h-2 bg-current rounded-full"></div>
158+
<div className="w-2 h-2 bg-current rounded-full"></div>
159+
</div>
160+
<span className="text-sm">Thinking...</span>
161+
</div>
162+
</div>
163+
</div>
164+
)}
165+
</div>
166+
<div ref={messagesEndRef} />
167+
</ScrollArea>
168+
169+
<div className="p-4 border-t">
170+
<div className="flex space-x-2">
171+
<Input
172+
value={newMessage}
173+
onChange={(e) => setNewMessage(e.target.value)}
174+
onKeyPress={handleKeyPress}
175+
placeholder="Describe your changes..."
176+
disabled={isLoading}
177+
className="flex-1"
178+
/>
179+
<Button
180+
onClick={handleSendMessage}
181+
disabled={!newMessage.trim() || isLoading}
182+
size="sm"
183+
>
184+
<Send className="h-4 w-4" />
185+
</Button>
186+
</div>
187+
</div>
188+
</CardContent>
189+
</Card>
190+
);
191+
}
Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Card, CardContent } from "@/components/ui/card";
21
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
32
import { benchifyFileSchema } from "@/lib/schemas";
43
import { z } from "zod";
@@ -13,30 +12,30 @@ export function PreviewCard({ previewUrl, code }: PreviewCardProps) {
1312
const files = code || [];
1413

1514
return (
16-
<Card className="border-border bg-card">
17-
<CardContent>
18-
<Tabs defaultValue="preview" className="w-full">
19-
<TabsList className="mb-4">
20-
<TabsTrigger value="preview">Preview</TabsTrigger>
21-
<TabsTrigger value="code">Code</TabsTrigger>
22-
</TabsList>
15+
<div className="h-full">
16+
<Tabs defaultValue="preview" className="w-full h-full flex flex-col">
17+
<TabsList className="mb-4 self-start">
18+
<TabsTrigger value="preview">Preview</TabsTrigger>
19+
<TabsTrigger value="code">Code</TabsTrigger>
20+
</TabsList>
2321

24-
<TabsContent value="preview" className="w-full">
25-
<div className="w-full overflow-hidden rounded-md border">
26-
<iframe
27-
title="Preview"
28-
src={previewUrl}
29-
className="w-full h-[700px]"
30-
sandbox="allow-scripts allow-same-origin"
31-
/>
32-
</div>
33-
</TabsContent>
22+
<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>
31+
</TabsContent>
3432

35-
<TabsContent value="code" className="w-full h-[700px]">
33+
<TabsContent value="code" className="flex-1 m-0">
34+
<div className="h-full">
3635
<CodeEditor files={files} />
37-
</TabsContent>
38-
</Tabs>
39-
</CardContent>
40-
</Card>
36+
</div>
37+
</TabsContent>
38+
</Tabs>
39+
</div>
4140
);
4241
}

0 commit comments

Comments
 (0)