@@ -11,11 +11,6 @@ import { ArrowUpOnSquareIcon } from "@heroicons/react/24/outline";
1111import * as Select from "@radix-ui/react-select" ;
1212import * as Switch from "@radix-ui/react-switch" ;
1313import * as Tooltip from "@radix-ui/react-tooltip" ;
14- import {
15- createParser ,
16- ParsedEvent ,
17- ReconnectInterval ,
18- } from "eventsource-parser" ;
1914import { AnimatePresence , motion } from "framer-motion" ;
2015import { FormEvent , useEffect , useState } from "react" ;
2116import { toast , Toaster } from "sonner" ;
@@ -26,6 +21,24 @@ export default function Home() {
2621 let [ status , setStatus ] = useState <
2722 "initial" | "creating" | "created" | "updating" | "updated"
2823 > ( "initial" ) ;
24+ let [ prompt , setPrompt ] = useState ( "" ) ;
25+ let models = [
26+ {
27+ label : "Llama 3.1 405B" ,
28+ value : "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" ,
29+ } ,
30+ {
31+ label : "Llama 3.1 70B" ,
32+ value : "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo" ,
33+ } ,
34+ {
35+ label : "Gemma 2 27B" ,
36+ value : "google/gemma-2-27b-it" ,
37+ } ,
38+ ] ;
39+ let [ model , setModel ] = useState ( models [ 0 ] . value ) ;
40+ let [ shadcn , setShadcn ] = useState ( false ) ;
41+ let [ modification , setModification ] = useState ( "" ) ;
2942 let [ generatedCode , setGeneratedCode ] = useState ( "" ) ;
3043 let [ initialAppConfig , setInitialAppConfig ] = useState ( {
3144 model : "" ,
@@ -39,7 +52,7 @@ export default function Home() {
3952
4053 let loading = status === "creating" || status === "updating" ;
4154
42- async function generateCode ( e : FormEvent < HTMLFormElement > ) {
55+ async function createApp ( e : FormEvent < HTMLFormElement > ) {
4356 e . preventDefault ( ) ;
4457
4558 if ( status !== "initial" ) {
@@ -49,134 +62,70 @@ export default function Home() {
4962 setStatus ( "creating" ) ;
5063 setGeneratedCode ( "" ) ;
5164
52- let formData = new FormData ( e . currentTarget ) ;
53- let model = formData . get ( "model" ) ;
54- let prompt = formData . get ( "prompt" ) ;
55- let shadcn = ! ! formData . get ( "shadcn" ) ;
56- if ( typeof prompt !== "string" || typeof model !== "string" ) {
57- return ;
58- }
59- let newMessages = [ { role : "user" , content : prompt } ] ;
60-
61- const chatRes = await fetch ( "/api/generateCode" , {
65+ let res = await fetch ( "/api/generateCode" , {
6266 method : "POST" ,
6367 headers : {
6468 "Content-Type" : "application/json" ,
6569 } ,
6670 body : JSON . stringify ( {
67- messages : newMessages ,
6871 model,
6972 shadcn,
73+ messages : [ { role : "user" , content : prompt } ] ,
7074 } ) ,
7175 } ) ;
72- if ( ! chatRes . ok ) {
73- throw new Error ( chatRes . statusText ) ;
74- }
7576
76- // This data is a ReadableStream
77- const data = chatRes . body ;
78- if ( ! data ) {
79- return ;
77+ if ( ! res . ok ) {
78+ throw new Error ( res . statusText ) ;
8079 }
81- const onParse = ( event : ParsedEvent | ReconnectInterval ) => {
82- if ( event . type === "event" ) {
83- const data = event . data ;
84- try {
85- const text = JSON . parse ( data ) . text ?? "" ;
86- setGeneratedCode ( ( prev ) => prev + text ) ;
87- } catch ( e ) {
88- console . error ( e ) ;
89- }
90- }
91- } ;
92-
93- // https://web.dev/streams/#the-getreader-and-read-methods
94- const reader = data . getReader ( ) ;
95- const decoder = new TextDecoder ( ) ;
96- const parser = createParser ( onParse ) ;
97- let done = false ;
98-
99- while ( ! done ) {
100- const { value, done : doneReading } = await reader . read ( ) ;
101- done = doneReading ;
102- const chunkValue = decoder . decode ( value ) ;
103- parser . feed ( chunkValue ) ;
80+
81+ if ( ! res . body ) {
82+ throw new Error ( "No response body" ) ;
10483 }
10584
106- newMessages = [
107- ...newMessages ,
108- { role : "assistant" , content : generatedCode } ,
109- ] ;
85+ for await ( let chunk of readStream ( res . body ) ) {
86+ setGeneratedCode ( ( prev ) => prev + chunk ) ;
87+ }
11088
89+ setMessages ( [ { role : "user" , content : prompt } ] ) ;
11190 setInitialAppConfig ( { model, shadcn } ) ;
112- setMessages ( newMessages ) ;
11391 setStatus ( "created" ) ;
11492 }
11593
116- async function modifyCode ( e : FormEvent < HTMLFormElement > ) {
94+ async function updateApp ( e : FormEvent < HTMLFormElement > ) {
11795 e . preventDefault ( ) ;
11896
11997 setStatus ( "updating" ) ;
12098
121- let formData = new FormData ( e . currentTarget ) ;
122- let prompt = formData . get ( "prompt" ) ;
123- if ( typeof prompt !== "string" ) {
124- return ;
125- }
126- let newMessages = [ ...messages , { role : "user" , content : prompt } ] ;
99+ let codeMessage = { role : "assistant" , content : generatedCode } ;
100+ let modificationMessage = { role : "user" , content : modification } ;
127101
128102 setGeneratedCode ( "" ) ;
129- const chatRes = await fetch ( "/api/generateCode" , {
103+
104+ const res = await fetch ( "/api/generateCode" , {
130105 method : "POST" ,
131106 headers : {
132107 "Content-Type" : "application/json" ,
133108 } ,
134109 body : JSON . stringify ( {
135- messages : newMessages ,
110+ messages : [ ... messages , codeMessage , modificationMessage ] ,
136111 model : initialAppConfig . model ,
137112 shadcn : initialAppConfig . shadcn ,
138113 } ) ,
139114 } ) ;
140- if ( ! chatRes . ok ) {
141- throw new Error ( chatRes . statusText ) ;
142- }
143115
144- // This data is a ReadableStream
145- const data = chatRes . body ;
146- if ( ! data ) {
147- return ;
116+ if ( ! res . ok ) {
117+ throw new Error ( res . statusText ) ;
148118 }
149- const onParse = ( event : ParsedEvent | ReconnectInterval ) => {
150- if ( event . type === "event" ) {
151- const data = event . data ;
152- try {
153- const text = JSON . parse ( data ) . text ?? "" ;
154- setGeneratedCode ( ( prev ) => prev + text ) ;
155- } catch ( e ) {
156- console . error ( e ) ;
157- }
158- }
159- } ;
160-
161- // https://web.dev/streams/#the-getreader-and-read-methods
162- const reader = data . getReader ( ) ;
163- const decoder = new TextDecoder ( ) ;
164- const parser = createParser ( onParse ) ;
165- let done = false ;
166-
167- while ( ! done ) {
168- const { value, done : doneReading } = await reader . read ( ) ;
169- done = doneReading ;
170- const chunkValue = decoder . decode ( value ) ;
171- parser . feed ( chunkValue ) ;
119+
120+ if ( ! res . body ) {
121+ throw new Error ( "No response body" ) ;
172122 }
173123
174- newMessages = [
175- ...newMessages ,
176- { role : "assistant" , content : generatedCode } ,
177- ] ;
124+ for await ( let chunk of readStream ( res . body ) ) {
125+ setGeneratedCode ( ( prev ) => prev + chunk ) ;
126+ }
178127
179- setMessages ( newMessages ) ;
128+ setMessages ( ( m ) => [ ... m , codeMessage , modificationMessage ] ) ;
180129 setStatus ( "updated" ) ;
181130 }
182131
@@ -208,14 +157,16 @@ export default function Home() {
208157 < br /> into an < span className = "text-blue-600" > app</ span >
209158 </ h1 >
210159
211- < form className = "w-full max-w-xl" onSubmit = { generateCode } >
160+ < form className = "w-full max-w-xl" onSubmit = { createApp } >
212161 < fieldset disabled = { loading } className = "disabled:opacity-75" >
213162 < div className = "relative mt-5" >
214163 < div className = "absolute -inset-2 rounded-[32px] bg-gray-300/50" />
215164 < div className = "relative flex rounded-3xl bg-white shadow-sm" >
216165 < div className = "relative flex flex-grow items-stretch focus-within:z-10" >
217166 < input
218167 required
168+ value = { prompt }
169+ onChange = { ( e ) => setPrompt ( e . target . value ) }
219170 name = "prompt"
220171 className = "w-full rounded-l-3xl bg-transparent px-6 py-5 text-lg focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500"
221172 placeholder = "Build me a calculator app..."
@@ -239,8 +190,9 @@ export default function Home() {
239190 < p className = "text-gray-500 sm:text-xs" > Model:</ p >
240191 < Select . Root
241192 name = "model"
242- defaultValue = "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo"
243193 disabled = { loading }
194+ value = { model }
195+ onValueChange = { ( value ) => setModel ( value ) }
244196 >
245197 < Select . Trigger className = "group flex w-60 max-w-xs items-center rounded-2xl border-[6px] border-gray-300 bg-white px-4 py-2 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500" >
246198 < Select . Value />
@@ -251,22 +203,7 @@ export default function Home() {
251203 < Select . Portal >
252204 < Select . Content className = "overflow-hidden rounded-md bg-white shadow-lg" >
253205 < Select . Viewport className = "p-2" >
254- { [
255- {
256- label : "Llama 3.1 405B" ,
257- value :
258- "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" ,
259- } ,
260- {
261- label : "Llama 3.1 70B" ,
262- value :
263- "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo" ,
264- } ,
265- {
266- label : "Gemma 2 27B" ,
267- value : "google/gemma-2-27b-it" ,
268- } ,
269- ] . map ( ( model ) => (
206+ { models . map ( ( model ) => (
270207 < Select . Item
271208 key = { model . value }
272209 value = { model . value }
@@ -299,6 +236,8 @@ export default function Home() {
299236 className = "group flex w-20 max-w-xs items-center rounded-2xl border-[6px] border-gray-300 bg-white p-1.5 text-sm shadow-inner transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500 data-[state=checked]:bg-blue-500"
300237 id = "shadcn"
301238 name = "shadcn"
239+ checked = { shadcn }
240+ onCheckedChange = { ( value ) => setShadcn ( value ) }
302241 >
303242 < Switch . Thumb className = "size-7 rounded-lg bg-gray-200 shadow-[0_1px_2px] shadow-gray-400 transition data-[state=checked]:translate-x-7 data-[state=checked]:bg-white data-[state=checked]:shadow-gray-600" />
304243 </ Switch . Root >
@@ -323,14 +262,16 @@ export default function Home() {
323262 ref = { ref }
324263 >
325264 < div className = "mt-5 flex gap-4" >
326- < form className = "w-full" onSubmit = { modifyCode } >
265+ < form className = "w-full" onSubmit = { updateApp } >
327266 < fieldset disabled = { loading } className = "group" >
328267 < div className = "relative" >
329268 < div className = "relative flex rounded-3xl bg-white shadow-sm group-disabled:bg-gray-50" >
330269 < div className = "relative flex flex-grow items-stretch focus-within:z-10" >
331270 < input
332271 required
333- name = "prompt"
272+ name = "modification"
273+ value = { modification }
274+ onChange = { ( e ) => setModification ( e . target . value ) }
334275 className = "w-full rounded-l-3xl bg-transparent px-6 py-5 text-lg focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500 disabled:cursor-not-allowed"
335276 placeholder = "Make changes to your app here"
336277 />
@@ -455,3 +396,17 @@ async function minDelay<T>(promise: Promise<T>, ms: number) {
455396
456397 return p ;
457398}
399+
400+ async function * readStream ( response : ReadableStream ) {
401+ let reader = response . pipeThrough ( new TextDecoderStream ( ) ) . getReader ( ) ;
402+ let done = false ;
403+
404+ while ( ! done ) {
405+ let { value, done : streamDone } = await reader . read ( ) ;
406+ done = streamDone ;
407+
408+ if ( value ) yield value ;
409+ }
410+
411+ reader . releaseLock ( ) ;
412+ }
0 commit comments