Using AI Effectively in 2025
BeJS 2025 / 44:08
Transcript
41 paragraphs
This is an automatic transcript of the recording above. It is published in full and unedited, apart from correcting names the recogniser reliably mishears. It will contain mistakes.
00:01Thank you. Thank you. GR. Thank you so much. React Paris. It is such an honor and privilege to be here with you today. Uh I will say this fully honestly I had no talk prepared at all because I wanted to meet you first and hear what you wanted and thank you for telling me. Yeah, many of you came and said, "Hey, I want to learn this and I want to learn this." And so today we're going to do exactly what you paid for. Uh and hope you get your money's worth here. Okay. Uh just I have to do this out of like tradition. Uh my name is Tis. That's pronounced like contagious. Um I'm not though. And I've been building on the web for well over 20 years at various places, Vercel, Spotify, Zeta, etc. And it's just been an absolute joy. Today I'm an AI engineer at a company called Data Stacks. Um, our color is purple and I have purple shoes and they paid for it. Anyway, um, and I'm I I live and breathe and work with AI. And today we're here to talk about AI but in Nope.
00:51We're here to talk about AI but in React. Um, to on the UI side of things. Honestly, I have like six slides. So, that's four of them. And one's like, "Hey, if you want more, I have a podcast. Spoiler alert." And then the the last one is like just thank you. Yeah. So, um what we're going to do is get straight into demos. Uh and and practical like we're just going to learn and we're going to write code. Okay. So, I asked all of you um what do you want to learn? Like what is the thing you want to learn? And uh the answer by most of you was I want to learn how to handle like AI streaming UI workloads in React. Like literally I asked you this and many of your hands went up but I asked you individually in the breaks and you're like I want to learn how to do that.
01:32What does that mean? Um when you work with language models when you work with AI they take a long time to generate stuff. This is not good user experience cuz the user is just having to wait. Then you can go like okay but what if we stream? Yeah, that's how chat Gvt works. Like it will stream tokens like you have the words update in place. Yes. Not if you if you understand what I'm saying. Yeah. Okay. And so that works but not for structured output. Like if you're getting JSON back um and you try to JSON.parse something, a partial a partial JSON string will fail. Uh and how do you how do you do that? You and so that's what we're going to do today in our time together. And then we're going to look at this cool movie demo thing and so on. Um but I have 45 minutes and we're just going to try and make the most of it. So here's what I have. I have an express server that does nothing except say hi. And I've got a client that's just a basic React app that um that also just does nothing.
02:22Yeah, it's just a form. This is literally all we have. And so I'm going to go start my dev servers. So I will um npx vit and then it's in the client. Exactly. And I will npx. It's actually vit node for the server. Just like that. Nope, it's server.ts, I think. No, it's okay. What? Um, wow, we're already off to a bad start. No, I'm just kidding. So, what what do we have here? Main.ts. Okay, so uh vit node. Could you imagine if this actually just like doesn't work? Okay, it totally doesn't. Um, do that's so weird. npmi-G vit node npx v node. How do I start a TypeScript server? Uh, I swear this totally worked. Okay, there you go. Whatever. So, it's I guess the answer is npx. So, um, that's it. So now if we go to localhost 3000, we should see hello world. Exactly. If we go to localhost 5173, we should see our front end. And cool. The problem is they're not talking to each other. And when you fetch from an OpenAI service or something, you always fetch from the back end. Yes, this shouldn't be new to you. You fetch from the back end because you don't leak authentication tokens to the browser dev tools. Okay. And so we're going to just talk to OpenAI here um and incrementally build like a nice UI. So we go side by side. Um and on the server side I want maybe an endpoint like ask and a query param for four. So we have um const you know exact that's perfect question or uh for is rec.query.
03:53I'm not using nux uh server side. Does it work server side? Anyway um and so what we need to do is just get the answer which is uh let's an async function and we're just going to wow thanks. We're going to await um new open AI. Yeah. Dot uh chat. Exactly. Dot completions. We're just going to vibe code this entire thing. Okay. Chat.comp completions.create. And we're going to just do this. Um this is actually exactly what we want. The role is user and the content is this. It's likely that you're missing a comma to separate them. Oh, thanks. Thanks. Yeah. Uh stuff. Does it work? Okay, cool. Um, and then what? Oh, for stuff. Okay, perfect. There we go. So, we have this. We're going to get some responses. And this is the schema that gives you. It gives you a bunch of choices. You usually want the first one. Get the message, get the content, and we're just sending this over JSON. Uh, so that's our server side. Now, we need to update our client side to actually consume that. So, handle search is the function that happens when you click search. And I'm just going to vibe code that. Um, and let's, you know, sorry, David, uh, but we have two used states now. And, uh, if you were here yesterday, you'll get that. If you weren't, you should feel bad about yourself. And that's So, and now let's go render the answer. Yeah, like maybe here. Uh, thanks cursor. And so, we're going to save this. And this should just
05:20work. So, I'm going to be like, um, tell me about about Paris and hit enter. And, uh, nothing happens. This is great. I love demos. Um, my my entire life is a demo. And so, it's cores. Of course, it's corores. Anyone work with cores? Anyone have issues with cores here? How do you solve it? Yeah. Okay, everybody. Wow. Thank you, browser vendors. Um, so let's let's go fix it by setting the right headers. Uh, we'll just I bet you AI is even going to know that if I just Yeah, look at that. Um, thank you. And so now, let's go restart our server. I don't have hot reload because I'm not cool like that. But um let's restart. And now I'll submit the same request. Tell me about Paris. No error, but also no response.
06:06Let's reload the entire page. Tell me about Paris. And if this network request succeeds, we'll get somewhere. So it's pending. Yeah, it's this is the horrible UI experience we were talking about. Just like generate for eternity and uh at some point there. Wow. Thanks. That took longer than I wanted. Okay, so this sucks. I think we can all agree this sucks. Yeah. Um, let's do a streamed response. How do we now turn this into something that's streamed? Well, on the server side, we need to set some headers. We need to set the connection to keep alive. Keep the connection alive so I can repeatedly write stuff to the stream and then close it. Okay. So, we just need to set another header. In fact, instead of res.json, we need to do some work here. We'll first set stream to true. And we'll do that. I also need to send a header set a header. So I'll do this. And so we're saying the content type is no longer just plain text. It's a stream of events. Okay. And we're going to when you turn stream to true, the OpenAI SDK gives you this. It gives you a stream type and you can for await chunks in this stream. That's basically it. And then every time a chunk is available from the language model, we write it in the response. Okay. Um, so we're going to go to the client side now and consume that differently. Can anyone tell me how to consume this? No. So when you This is standard procedure. We get
07:27back this response when we fetch and always just turn it into JSON. But this is actually a stream. It's a readable stream. And so we're just going to read it. So we're going to do this. Um we're going to call res.body is the stream. We're going to get the reader and we're going to get a text decoder because it sends binary data encoded as binary and we're going to decode that into text. Does that make sense? And so if there's no reader, we're not doing anything. Other words, we're going to recursively read stuff from this stream. So, we call read once and then we call read inside read. So, we recursively, it's kind of like while true. In fact, let's see if we can vibe code this differently and just be like use while true.
08:09Um, yeah, this is probably better. Um, but it doesn't matter. Just do you like imperative or declarative? Right, let's go back to the original design. Okay, so we're reading from the stream. And this is literally how it works. We call reader readad which returns a promise and when it's done it tells us it's done so we just return otherwise we keep decoding the value and then setting it into the state. Is this clear so far? Yeah. So let's see how this works. So we come here and we're like tell me about Paris and now o everything broke. What happened? Um it's course again. Okay. Um, I think it's allow headers or Yeah, maybe something here is uh maybe we'll just set the header here. I don't know. Um, let's reload this.
08:57Tell me about Paris. I can't believe it's Oh, thanks. Yeah, I have to restart the But I did restart the server last time. No, but I Okay, whatever. Tell me about Paris. Pairs. What? No access control. Allow origin header is present. But it is though. Unless I change the endpoint. Maybe I change the endpoint. Ask for stuff. Headers content. You know what? But it I I allow it. No. Maybe it's up here. Allow headers content type. Okay. Well, it's fine. This is totally fine. You know the meme with the dog in the fire? Anyway, um I I really don't get this at all. Um we will spend two minutes debugging this and then move on. Let's go to the in I don't whatever this is. Go away. Okay. Hi now. Yeah. Now you Okay. Wow. I just didn't like the header at all. So tell me about Paris. This is fine. This is to it's because we didn't serialize our JSON.
10:09Okay. JSON.stringify. It's because I removed the header. I think if I do this, we'll be fine. So, we'll try this again. Thanks for laughing at me. It does wonders for my confidence. Um, okay, cool. It totally works. But it's Thank you. Thank you. It's It's faster for sure. Uh, but let's let's fix this a little bit cuz I don't want this kind of thing. I want JSON. Yeah, I want JSON. I don't want some random markdown. So, let's fix this. Um, the OpenAI SDK has a um, let's see, it has output format or something like this. Yeah, response format maybe. Yeah, JSON. Type JSON object. I'm going to say type JSON schema, right? And now it expects, it's actually really great with TypeScript because now it expects JSON schema. And I have to write JSON schema here, which is the name of my schema thing, I guess, and then the actual schema, which is it's not that. See, the AI expects ZOD.
11:04We're not doing Zod, we're doing JSON schema. So now um I will say again I'm just vibe coding this. Make a JSON schema for uh title thing. Yeah, something like this. And let's see what we get. I don't know JSON schema honestly. There we go. Perfect. Um but I want an array. Make it an array of things. And uh yeah, that's perfect. Actually, it's a type array and each item in the array is an object with these properties. That's perfect. Okay. So now I'm going to get this and we're still streaming this. Yeah. Over the network. So I'm going to restart my server. Um which crashed. Pay no attention. And uh 20 I want 20 kinds of cake, I guess. Um I So it says invalid schema for response. This has to actually be an object. Okay, fine. So we need to change the schema because it has to be an object that contains array elements. Make it an object with key things that has an array as described. This is kind of prompt engineering. Yesterday you mentioned you wanted prompt. Okay, this is perfect.
12:13It's actually an object with an array inside. This will work. Let's restart the server. Try again. And now there we go. So we have this. And it's actually giving me things which is an array. It's the schema is fine. But the problem is I can't JSON parse this, right? because um the server keeps crashing because I don't uh like serialize my my response. Anyway, I this is it's partial JSON. So, if I try to let's let's try and read this in the client. Yeah, let's do like um let's add another use state. Sorry, David. Uh set cakes. And it's an array of strings. Yeah. And so what I can do is I can set it to things. So I get this response here and if done before I return I can set it. So I could be like set cakes.
13:04This should work. Uh let's let's try that. So let's do 20 kinds of cakes and hit enter. I my connection was refused. So let's start the server again. I kind of like that. It's like forced hot reload. Um and now yeah it works but I'm not rendering my cakes anywhere. So, let's go and uh do it do it right here. Cakes.m map. Perfect. I want this to be an LI. I like lists. And we'll wrap this in a ul. Oh, wow. Wow. Okay. So, um 20 kinds of cakes. My server died. I'm prepared for it. This is called machine learning. And now, uh it's going to do this. And then when I set cakes, wow, it's so slow. It's painful. Okay, now it crashed. The server crashed, but also I don't have my array. I don't have my array because the JSON parse will not work. Yeah. Um, so how can we fix all of this? Well, you're always going to get partial JSON if you do this. And par partial JSON JSON cannot be parsed because it's partial.
14:09So there's a special format for this called NDJSON. Anyone using NDJSON? Yeah, cool. NDJSON stands for new line delimited JSON and it's an array of JSON except the delimiter is not a comma but it's a new line character. So you get one JSON object per line and this is how you know um this is actually how React server components works also. Uh React server components will send a special message per component on a line of JSON. So uh let's let's implement ND how do you get NDJSON with OpenAI? There's no I wish I w if if anyone from OpenAI is listening right I wish we could just do this um you can't in fact there's absolutely no way to enforce I want this JSON one per line except with prompt engineering believe it or not GPT40 the GPT4 line of models has the greatest adherence to the system prompt the system prompt is when you give identity to a language model so we're going to literally just come here it already knows knows what I'm doing. Um, and instead of this I will say uh you respond respond in perfectly valid JSON um one result per line as many as the user wants and and I'll continue and say use this schema and it's like title string. Yeah, this is like believe it or not this works. Uh, and so let's go restart my server that always crashed. In fact, let's just JSON. So, the problem is, by the way, we need to do this. Uh, and now the server will never crash because it was crashing because there was no chunk.
15:49Okay, so let's go back and restart everything. And now, let's ask for 20 kinds of cake. Um, look at this. Look at this. It's like giving me code. Um, we need to adjust the system prompt. And watch this. Watch this. No code, no markdown, no back ticks, just JSON, please. Okay. And I'm telling you, this like totally works. And so, um, let's go back. 20 kinds of cake. Uh, did I restart the server? Actually, I forgot now that it just doesn't crash. But let's restart. Um, 20 kinds of cake. Look at that. It's perfect. It's absolutely perfect. So now we can actually parse this. Yeah. Um so let's go to the JSON parse crashed here. Um because it's still partial. But as we're reading the stream, we can start by just console log driven development.
16:46Okay. So we can like if done we can just do like console.log uh done with Yeah, thank you. And for each chunk here, we can also say come back down console.log we received this chunk. So now let's try this again. 20 kinds of wazong um I don't even know what's going to happen but um okay so look at this. Look look we're still receiving like chunked stuff. Will this JSON.parse yes or no? No. How do we fix this? We're so close. We're so close. We fix it on the client side or the server side. Where do we fix? We can actually fix it on both. What we need to do is create a buffer. a place to collect chunks, find a new line character, be like, "This line is done. Now parse." Yeah, we can do this on the client or the server. Um, and we should do this on the server because usually you want to do everything as much as you can on the server. So, um, let's just let's do it. We know how to write code.
17:46Yes, I hope so. So, let's just write some code. Let buffer equals this. We increment the buffer and then we write the buffer. But, we need to handle the new line thing. How do we do that? Well, if buffer ends with new line, write it. Um, otherwise, uh, and set it to empty. So, we don't actually need to do this here. That's it. This is all we need to do. Now, let's go back to our server, restart it, and we'll do, um, 20 kinds of uh, juice or something. And look at Oh, look, look, look, look, look. Wow. You see this? Thank you. That's that's uh that's parsible. And because it's parsible, what can we do? Because it's parsible, we can um first of all, let's do Thank you. Uh if answer.length is zero, we just set cakes. But we don't do this. We set cakes to the previous plus the answer is title. And now we have an actual array which are we mapping with a key.
18:46Perfect. This should just totally work. So let's What's your problem, brother? Title string. Is it what? This some This is some TypeScript nonsense. Yeah. Thank you, Adam. Thank you. Thank you, Adam. I appreciate you screaming from over there. So, uh, now let's 20 kinds of, um, Belgians. I don't I don't even know, but wow, that's awesome. Um, Flemish and Woon and stuff. Okay, but why don't I see it in my here? Um, let's get rid of this answer. If cake.length length is greater than zero. Are we setting cakes? If answer it's answer hm interesting. Let's let's just console log. We're almost done by the way. It didn't even take that long, huh? So, let's 20 kinds of ice cream. Enter. Okay. So, we totally received it. But we received it here. We didn't receive it here.
19:50Uh, 20 kinds of Tell me something. I'm I'm out of ideas. Health. Cows. Cars. Cars. Sorry, man. Cars. Cars. Okay. So, we receive What's happening. What's I'm so lost. I'm setting the case in the done. Yeah. Yeah. Yeah. So this is fine but it says done with and then answer is empty. Do I not capture? I'm not capturing. That's so true. All right. All right. So we need we need to like incrementally adopt. So instead of received, we could just set cakes here. Yeah. So like we could do like if if there we go. Yeah. How about that? And then we don't need this done. Actually done return. That's perfect. So now we have 20 kinds of uh soup. iPhones. Islands Islands. Enter. I'm so close. Unexpected end of JSON input. What is happening? So, we have to do if Where's my JSON parse? So if if if text I'm using answer 20 kinds of errors. There we go. Perfect. And it actually the the ironic.
21:24So thank you. Thank you. We could do like 200 kinds of errors. This will probably crash because it'll hallucinate. Oh, or not. Great. Um, and so it's streaming in as it becomes ready. And this is Yeah, it crashed anyway. This is it's the AI. This is unpredictable. So, how do we make this even more predictable? This is actually pretty good. Um, because if you try catch a bit, you could go quite far. How do we make this even better? Um, yesterday David did a brilliant talk uh and said, "Hey, just use the library." Just honestly, there's people who they they have 40 hours a week at work to solve this problem for you. Thank you TK Dodo uh for working on React Query. He's shipping support for this in React Query uh soon. Yeah, he two days ago. He shipped it on Wednesday. Just two days ago. So uh React Query will support you with this. Yeah, thank you TK.
22:14Um but there's there's a much better way to do all this uh which is let's go to the code and not roll our own. This is by the way how you work from scratch and I think it's important for you to know this. But now that you know this, we can just use an abstraction. We we'll we'll use the great Verscell AI SDK. Anyone using this at all? One of you. Wow. Okay. We're going to So, we're going to import OpenAI from not OpenAI, but from AI SDK/ OpenAI. And we're going to import a function called stream object from AI. AI is Verscell's package. And we're going to get rid of all this nonsense and just be like way simp. Look at all this, bro. We don't need any of this. So, we're going to just like get rid of like look just listen to how absurd you respond in perfectly valid JSON. Yes, master. Totally like this is not going to Yeah. So, we can fix this by let's remove all of this and go const answer is stream object. And here we give it some stuff.
23:16We give it um the model, the schema, and the prompt. Okay, the schema is validated by ZOD. If you're not using Zod, you really, really need to use Zod is the greatest validation library of all time. In fact, the Verscell AI SDK is just a validation library with nice developer experience. The Verscell AI SDK is going to validate that it is JSON, that it fits your ZOD schema. It's going to take the the Zod schema here and include it in the system prompt. You know, I just wrote that system prompt. You respond with perfectly valid JSON. I give as a developer the ZOD schema here and it's going to include it in the prompt and be like, "Hey, respond." And then when the machine when the AI model responds, it will run ZOD to validate it in a try catch and try and if if it fails, it will request it again. It does all this under the hood. It's a great piece of software. Okay, so we're just going to do this and then we're going to um vibe code the rest. We get back chunks of answers and we're just going to write the chunk. This is wrong because it's element stream. What?
24:10That's that's not supposed to be wrong. Um what are you talking about? Is this like a weight or something? Uh, strange. Um, anyway, this is totally not wrong. Uh, yeah. So, this is much much better. In fact, let's go try it. Maybe it is wrong. Um, but as far as I can read, this is weird, right? We start doubting our human knowledge because the machines tell us stuff. Um, let's try again. Um, 20 kinds of mustache. I don't totally failed. Um, why did it? This is fine. Element streams in object mode. I didn't say stream is true. Um, that's why stream. Yeah. Yeah. Perfect. What is your problem now? It doesn't have stream. What do you want, bro? It's called stream object. You know what I mean? Um, that's wild. I don't object. Oh, stream object. I'm I'm so lost. I'm super lost. Um, and I guess you all are as well, huh? Because you never like text stream.
25:16I have no idea. Let's uh partial object this one. Let's try. Is this new? Did they just update the Versa AI SDK yesterday when I prepared this? 20 kinds of iPhones. Okay. No, it's not. Thanks. Oh, actually it is. I just need a JSON stringify. If this is the case, I swear to you this was a breaking change like one day. One day cake. Um Oh my gosh. Kind of. Kind of. It's um something's wrong with this uh as you can tell. But how much time do I have? I have I have quite a bit of time actually. We can just debug this together. Uh if if if you want uh street let's go read the docs actually act. Whoa. What? No, let's go read the docs. I said came up with a but actually here. Let's try this also. Fix it. Um and we can work in this is called vibe code. O Whoa. It's fixing my cores and stuff. Wow. Oh, that's totally wrong. Okay.
26:15So, for awake. Okay. Yeah. Um I think it's maybe not stream object. It's maybe something else. What is it? Is it stream text? Is it stream chunk? Stream object result. Maybe it's stream object result. Yo, did they change the name? All right, that's the type. I love that word. Okay, we'll spend like three minutes on this, but we're not going to spend more than that. Okay. Um, it is totally this. It is totally this. I'm telling you. Ah, that's it. Thank you, David. That was It said it failed in object mode. Uh, because it it's object mode and I want an array. Exactly. David was right. Let's undo all this uh stress uh breathe. And we'll do output, right? Yeah, this this is the one. Output array. And look, it doesn't even look at this nonsense. Doesn't even give you IntelliSense. Let's get do let's get what's his name? Um Dominic on the phone. Not Not you, Dominic. The other one. Um, and we'll choose output as array. This will fix it. And now we save. Look at this. It's not partial object stream, but it's element stream because elements of an array. Um, and now we're good. Um, and we'll npx. And it's basically the same behavior as before. Um, but with an abstraction.
27:28Yeah. So, we'll do 20 kinds of um Thanks. She she uses WhatsApp stories. Um, and here we go. So, we're receiving them. Uh, and we're not parsing this in the front end. Let's go fix that. So, we Where's our received? Received here. We can actually be a lot simpler and just be like this. Set cakes and uh read. This should This should totally work. Let's try 20 kinds of WhatsApp. Um, yeah, perfect. There you go. Wow. It's actually like I love AI and so uh WhatsApp multi-device support. Awesome. Um so that's how you do it and look the code is not even that um scary. I mean it's here this is the entire thing. It's less than 50 lines of code. Yeah. And most of it is corores. So if we actually like use if we use this properly it can fit in one screen above the fold there.
28:25And so this is all it takes. You get streaming JSON. You can use it with suspense. You get it in order. And this really opens the door to that cool movie demo I showed you. I have like 15 minutes and so I'll show you that. Um, which is basically this. Uh, so I'm going to go to my SF AAI demo and we'll check out the early branch npm rundev. Um, actually let's open the editor first and then npm rundev. So, we'll open code and uh we will put this to the side and we will npm rundev now. And it's going to be on port I don't know 3001 and so we'll go to 3001. And as you you some of you saw this yesterday, some of you left uh to go get drunk. Uh but you know I we we'll see it again. So it's this like movie app. Um it's not interactive. It doesn't do anything because it it the code is hasn't been written. And so how you enable AI here. Thank you for leaving your phone on loud. That's very respectful. Um what we're going to do is use server and we're going to export const AI. Literally just export const AI and we're calling create AI. This a function from the Vercel AI SDK. And what you give it to initialize is actions which is just no no which is just an array of actions. And you have initial AI state and initial UI state. That's it. And these two are totally hallucinated because they're supposed to be arrays, not objects. Okay, this gives you an AI context. And if you're working in Nex.js, you go to layout and you just
29:55wrap your entire application in AI and it's just a context provider. And that's basically it. Now, this app is hooked up in React to use the Verscell AI SDK. Okay. So, what can I do with this? Well, I can't do anything because I have no actions. So, I'm going to create an action. I'm not going to do that. Um, but let's walk through this a little bit because we have this form and so if we go intro by intro, we have page and page has main and has search form. Search form has a form with a text input. That's this thing. And it has should show suggestions and it has suggestions. Top movies and this is live. We could be like sup movies. Yeah. Or or that. And if I save Yeah. It's totally live. So it's this thing. And now if I click on it, something should happen. But if I go to suggestion, it has use movie search.
30:46Use movie search does nothing. And so we can implement this together. Um what we need to do is call some hooks. We need to call a hook called continue conversation. This comes from use actions which is from airc. And we need to actually get the conversation state. We actually need to set it. So instead of use state, it's use state from airc. We need these two things. Okay. And then it's pretty much as you'd expect. So we don't do that. First, we set the conversation to the previous conversation, but we include the previous and we add one where the role is is the user. The role here is user and the content is the prompt. We get this from the argument. So we set the conversation. Then we get the response is await and we call continue. This is where we ask the LLM like, "Hey, give me something." We call continue conversation with the prompt. And then we set conversation again. Does this make sense? We we're just slowly building a conversation history. So, we set conversation to the previous conversation, but this time it's the response. We're adding the response from here. And um if you're a Typescript guy, cover your eyes. Uh but this is what we're doing. Yeah. We're saying like, "Hey, the user said this. Now, LLM, give me text." And I'm going to set this into state. Finally, we come here and we just read the the UI state from use UI state.
32:16This is it. The only thing left is to do that generate stream object thing. So, I'm going to skip forward uh because I just showed you how to bootstrap your app. But I will get reset hard. Just clean everything clean uh df and I will check out the the final branch which is different demo um npm rundev. Nothing really changed here um at all actually. This is we just wrote this. The only thing that's different is our AI context has one action continue conversation and we'll walk through this in a little bit. I'm going to fold all the code here so you don't um so we have continue conversation and we get a result from stream UI just like stream object and we return it. Ro assistant displays result value. Yeah. Um let's take a look. So this is the demo. So, if I come here and I ask for movies about Paris, whoa, movies about Paris, hit enter. Um, it searches the internet. Um, gets me these movies. And I'd be like, "Show me this as UI." And it just like gives me this as UI. And then finally, I'd be like, "Um, show me the trailer for Ratta 2."
33:25and just like actually like starts playing the the trailer like right here. Um with sound as well next party and uh and then I can you know hey where can I watch this um and it will ask me for location permission. It's New York by default, but if I allow, it's going to like get my I'm in. We're right here. And apparently there's a movie theater, uh, right here. And so, um, this is fully interactive using the AI SDK. And so, what's happening here? Uh, let's look at the code. So, this is stream UI. And there's two cases, text and tools. Text is text happens when I say this like, um, how's your mom? And uh I hope I get a like decent answer. Yeah. So this um how's your grandmom? Um and this is text. You see I just get text and append it. Nothing special is happening in text. So if you're done we just update the history. Otherwise we return markdown uh and text. Tools is where the magic happens. These are all the tools. So when I ask for movies um I call this tool get movies. And a tool has a description, a a tool used to get a list of movies. It has parameters which is just a zod schema. So a query in this case movies about Paris. And this is where the magic happens. The generate function. How does the LLM know when to call a tool is based on the language used in the description property. You could literally write like call this tool when a user says cheese, right? And it will do that. Okay. Um
35:00what's happening in generate and this is people confuse this a lot. People think these components, people think this map component, these movies component, people think these are like generated by the LLM. That would be dangerous and bad because you don't have like control over what it could generate anything. Yeah, it's not generated by the LLM. Let's look at the implementation of our get movie posters function. So if we come to it's called create generative UI, a tool used to generate UI in response to a user explicitly asking for UI. So I asked show me this as UI and it gave me this. So what it's going to do is this is a generator function by the way. Um it's going to yield immediately it's going to yield a spinner. So if I unfold this you'll see a spinner getting movie posters. And again let's let's look at this in practice. What might these movies look like as UI? And so we should see I guess it cached but there's a little spinner. Let's let's do it properly. So scary movies set in the woods, right? what might this look like as UI? So I'm explicitly asking for UI.
36:06So it's going to call that. And you'll see first the spinner and then the image. Um, and so we yield the spinner and then we get the response. We promise.all. We just basically fetch movie posters. You know how to fetch. So I'm not going to show you this uh because like it's literally like we're calling the movie DB, right? And we're just getting like images. We get results and then we basically just return this. we return movies which is a react component that I wrote and so the llm returns the props the value of the props but the function I wrote that okay what is movies then it's just a react component um we get as props an array of movies JSON you saw stream object right I can generate JSON so we get this via props and then we just render a react component and movies is literally like array map movies and we're rendering a single movie. That's it. And a single movie is just an image. It's a figure.
37:02And the cool thing is, you may think, okay, but these are server components. Indeed, these are server components that the AI SDK is streaming line by line using JSON LD because what are React components? They're just they're functions that return JSON. So, this is not magic. And the cool thing is they're server components, but they also hydrate thanks to the integration between the AI SDK and Nex.js. And so since it's a client component, I could if I wanted to do something like this and something like figure um no no stop that figure on click. I could like actually go bananas and be like alert sup. Um and maybe like e.prevent default. Uh yeah. And now that's it. And I should hear movies um about uh uh uh Pokemon. I don't know.
37:55And uh so okay, show me this as UI. And now these are client components. If I click on it, it literally like alerts in the browser this JavaScript, right? So it's uh very very cool. By the way, this movie has an awesome soundtrack. Uh the Lugia song. Um is this clear so far? Yeah, you can. This is how you use AI to create better experiences than chat bots. If you leave this talk, I want you to leave here going like, I'm not building another chatbot. I'm building something better. I'm streaming to the user. I'm getting them information instantly. I have five minutes left. Uh and I want to spend it talking to you about where AI is going. Uh anyone heard of model context protocol? Okay. Yeah. Model context protocol is a standard way a way to share context with a language model from server to client. Any MCP server can talk to any MCP client. Um what that means is any MCP server can give tools like tools to generate UI to an MCP client. Okay, what are MCP clients? Um claude desktop is a client but also cursor. So I've been writing code in cursor here and cursor is an MCP client. Um okay, stop that. Cursor is an MCP client and so I can actually configure cursor to use any MCP server I want, including um a database. So this is Astrad. This is my database. Um, and it's absolutely crazy. I have to show you this because I want I want you to feel like we're all cooked. So, this is my this is my database. It's an empty
39:25database. There's nothing in here. There's no collections. Um, and normally you would have to write TypeScript and work with an API and do a bunch of stuff, but not anymore. So, I can literally go into agent mode here with cursor and be like, add a collection about cheese to my database and insert 20 rows. Um, and like it's going to try and understand what I want. Let me bump the font size a little bit. And here, so I'll add cheese collection to your database. And so it's just like using my MCP tool. Um, and it's just like making collections and it's just doing this job. It's operating my database for me. This is, by the way, I work at data stacks. That's our database. Um, and it you our database can be used by like AI.
40:11People don't need to use it anymore. And so let's refresh this page. And what we should have look look oh my goodness we have a collection cheese and we have um some records inserted and uh if I reload this um I will have come on I will have more yeah so it's like actually like inserting stuff into my database and it just knows how to use this. Yeah, which is over MCP. This is an open- source project uh called Astradmcp. It's a server that gives context to things like cursor that can operate them on your behalf. But the reason I'm telling you this is because there's also tanstack router. Anyone using tanstack router here? He's like not sure. Um so another tool here is ts which is tanstack router and it can do these things. It can create tanstack react application. So if we have a database operator agent and an agent that can create a tanstack application front end back end you don't you can make an entire application end to end just by prompts. Now with MCP uh this is the work by Jack Harrington. Some of you may know him a YouTuber. Uh he created the MCP server for Tanstack Router. So um I can literally here be like create me um a Tanstack Tanstack router app for Pokemon, right? And uh it will just know again. Do I need to like send this or something? Um it should I think it's busy generating something else, right?
41:38Let's let's go back to agent mode. Send send. Wow, I love this. Oo, that's such a new error, bro. I've totally broke cursor. Okay, let's let's uh Hey, cursor team, if you're watching. Um, okay, so here here, create a tanstack router app for Pokemon and we'll send this. And check this out. It's going to like I'll create a It just knows um that it's a tan. So, we have a package JSON, and it's just going to do its thing. And um yeah, it just it's going to use the tool from Tanstack router. Anyway, I'm not going to I'm going to save you that because we're almost out of time, but this is the future we're going for. Um nice UX, streamed responses, beautiful things, but also um you can create like full-on full stack Tanstack router applications uh just with with prompts. If you need a front end, Tanstack router has support for this. Um, it's npx-ycreate-ts router-app space-mc.
42:38That's the command. And this will start an MCP server. These servers live locally on your machine. So, authentication not an issue. Um, just like the database, you just give it your environment variables and it will operate your database. Um, so you can make a tanstack router app with a coding agent. If you need a database, that also just exists now. And the API also um is programmable over MCP. So like end to end applications. This is where we're going. I want you to stop and think about that because people think oh AI is never going to take my job and it it it may never do that but it is changing. There there used to be a time where we wrote assembly. There there used to be a time where we wrote go to um and then we we had better abstractions JavaScript, TypeScript, Go, Rust. Um the the best abstraction is English, you know. Um if we can uh and it's slowly coming there and I think we need to be aware of it.
43:31All right. Um this was this was great. I have way more to say. I was actually saying yesterday, man, I wish I had 3 hours. Um understandably, maybe you don't want that, but um I do have 3 hours on on on this podcast that I host. Uh and there's usually very long form episodes. Um Kenty Dods was on it. TK Dota, we should get you. We should lots of great open source maintainers. uh people with skill um also me sometimes and we just host deep dives very very technical deep dives on things if you're interested or if you know someone you'd like to be on uh let me know and with that I want to say hey thank you so much for coming to this talk this
More talks
- 2026
The New UX
CityJS London 2026 - 2026
Frontend after AI: The New UX
Future Frontend 2026 - 2026
Harnesses in AI: A Deep Dive
AI Engineer Europe 2026 - 2026
AI Yesterday, Today, and Tomorrow: Extending AI Systems with Model Context Protocol
How to Web 2025 - 2026
How to Thrive as a Professional with AI
React fwdays 2025 - 2026
How to Optimize Your Health with React Native
React Universe Meetup Berlin 2026
Elsewhere
There is every talk I have given, all 69 of them, ConTejas Code, the podcast, and Fluent React, the O'Reilly book on how React works inside.